当前位置: 首页 > news >正文

黑马程序员Java项目实战《苍穹外卖》Day01

苍穹外卖-day01

课程内容

  • 软件开发整体介绍
  • 苍穹外卖项目介绍
  • 开发环境搭建
  • 导入接口文档
  • Swagger

项目整体效果展示:

管理端-外卖商家使用

管理端-外卖商家使用

在这里插入图片描述

用户端-点餐用户使用

当我们完成该项目的学习,可以培养以下能力:

在这里插入图片描述

1. 软件开发整体介绍

作为一名软件开发工程师,我们需要了解在软件开发过程中的开发流程, 以及软件开发过程中涉及到的岗位角色,角色的分工、职责, 并了解软件开发中涉及到的三种软件环境。那么这一小节,我们将从 软件开发流程、角色分工、软件环境 三个方面整体介绍一下软件开发。

1.1 软件开发流程

在这里插入图片描述

1). 第1阶段: 需求分析

完成需求规格说明书、产品原型编写。

需求规格说明书, 一般来说就是使用 Word 文档来描述当前项目的各个组成部分,如:系统定义、应用环境、功能规格、性能需求等,都会在文档中描述。例如:

在这里插入图片描述

产品原型,一般是通过网页(html)的形式展示当前的页面展示什么样的数据, 页面的布局是什么样子的,点击某个菜单,打开什么页面,点击某个按钮,出现什么效果,都可以通过产品原型看到。 例如:

在这里插入图片描述

2). 第2阶段: 设计

设计的内容包含 UI设计、数据库设计、接口设计。

UI设计:用户界面的设计,主要设计项目的页面效果,小到一个按钮,大到一个页面布局,还有人机交互逻辑的体现。例如:

在这里插入图片描述

数据库设计:需要设计当前项目中涉及到哪些数据库,每一个数据库里面包含哪些表,这些表结构之间的关系是什么样的,表结构中包含哪些字段。例如:

在这里插入图片描述

接口设计:通过分析原型图,首先,粗粒度地分析每个页面有多少接口,然后,再细粒度地分析每个接口的传入参数,返回值参数,同时明确接口路径及请求方式。例如:

在这里插入图片描述

3). 第3阶段: 编码

编写项目代码、并完成单元测试。

项目代码编写:作为软件开发工程师,我们需要对项目的模块功能分析后,进行编码实现。

单元测试:编码实现完毕后,进行单元测试,单元测试通过后再进入到下一阶段。例如:

在这里插入图片描述

4). 第4阶段: 测试

在该阶段中主要由测试人员, 对部署在测试环境的项目进行功能测试, 并出具测试报告。

5). 第5阶段: 上线运维

在项目上线之前, 会由运维人员准备服务器上的软件环境安装、配置, 配置完毕后, 再将我们开发好的项目,部署在服务器上运行。

1.2 角色分工

在对整个软件开发流程熟悉后, 我们还有必要了解一下在整个软件开发流程中涉及到的岗位角色,以及各个角色的职责分工。

在这里插入图片描述

岗位/角色对应阶段职责/分工
项目经理全阶段对整个项目负责,任务分配、把控进度
产品经理需求分析进行需求调研,输出需求调研文档、产品原型等
UI设计师设计根据产品原型输出界面效果图
架构师设计项目整体架构设计、技术选型等
开发工程师编码功能代码实现
测试工程师测试编写测试用例,输出测试报告
运维工程师上线运维软件环境搭建、项目上线

上述我们讲解的角色分工, 是在一个项目组中比较标准的角色分工, 但是在实际的项目中, 有一些项目组由于人员配置紧张, 可能并没有专门的架构师或测试人员, 这个时候可能需要有项目经理或者程序员兼任。

1.3 软件环境

作为软件开发工程师,在编码的过程中就不可避免地会接触多种软件环境,我们主要来分析在工作中经常遇到的三套环境, 分别是: 开发环境、测试环境、生产环境。 接下来,我们分别介绍一下这三套环境的作用和特点。

1). 开发环境(development)

我们作为软件开发人员,在开发阶段使用的环境,就是开发环境,一般外部用户无法访问。

比如,我们在开发中使用的MySQL数据库和其他的一些常用软件,我们可以安装在本地, 也可以安装在一台专门的服务器中, 这些应用软件仅仅在软件开发过程中使用, 项目测试、上线时,我们不会使用这套环境了,这个环境就是开发环境。

2). 测试环境(testing)

当软件开发工程师,将项目的功能模块开发完毕,并且单元测试通过后,就需要将项目部署到测试服务器上,让测试人员对项目进行测试。那这台测试服务器就是专门给测试人员使用的环境, 也就是测试环境,用于项目测试,一般外部用户无法访问。

3). 生产环境(production)

当项目开发完毕,并且由测试人员测试通过之后,就可以上线项目,将项目部署到线上环境,并正式对外提供服务,这个线上环境也称之为生产环境。

开发环境 测试环境 生产环境
在这里插入图片描述

首先,会在开发环境中进行项目开发,往往开发环境大多数都是本地的电脑环境和局域网内的环境,当开发完毕后,然后会把项目部署到测试环境,测试环境一般是一台独立测试服务器的环境,项目测试通过后,最终把项目部署到生产环境,生产环境可以是机房或者云服务器等线上环境。

2. 苍穹外卖项目介绍

在开发苍穹外卖这个项目之前,我们需要全方位的来介绍一下当前我们学习的这个项目。接下来,我们将从项目简介、产品原型、技术选型三个方面来介绍苍穹外卖这个项目。

2.1 项目介绍

本项目(苍穹外卖)是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括 系统管理后台 和 小程序端应用 两部分。其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的分类、菜品、套餐、订单、员工等进行管理维护,对餐厅的各类数据进行统计,同时也可进行来单语音播报功能。小程序端主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单、支付、催单等。

在这里插入图片描述

接下来,通过功能架构图来展示管理端用户端的具体业务功能模块。

在这里插入图片描述

1). 管理端功能

员工登录/退出 , 员工信息管理 , 分类管理 , 菜品管理 , 套餐管理 , 菜品口味管理 , 订单管理 ,数据统计,来单提醒。

2). 用户端功能

微信登录 , 收件人地址管理 , 用户历史订单查询 , 菜品规格查询 , 购物车功能 , 下单 , 支付、分类及菜品浏览。

2.2 产品原型

产品原型,用于展示项目的业务功能,一般由产品经理进行设计。

注意事项: 产品原型主要用于展示项目的功能,并不是最终的页面效果。

在课程资料的产品原型文件夹下,提供了两份产品原型。

在这里插入图片描述

管理端原型图:

在这里插入图片描述

用户端原型图:

在这里插入图片描述

1). 管理端

餐饮企业内部员工使用。 主要功能有:

模块描述
登录/退出内部员工必须登录后,才可以访问系统管理后台
员工管理管理员可以在系统后台对员工信息进行管理,包含查询、新增、编辑、禁用等功能
分类管理主要对当前餐厅经营的 菜品分类 或 套餐分类 进行管理维护, 包含查询、新增、修改、删除等功能
菜品管理主要维护各个分类下的菜品信息,包含查询、新增、修改、删除、启售、停售等功能
套餐管理主要维护当前餐厅中的套餐信息,包含查询、新增、修改、删除、启售、停售等功能
订单管理主要维护用户在移动端下的订单信息,包含查询、取消、派送、完成,以及订单报表下载等功能
数据统计主要完成对餐厅的各类数据统计,如营业额、用户数量、订单等

2). 用户端

移动端应用主要提供给消费者使用。主要功能有:

模块描述
登录/退出用户需要通过微信授权后登录使用小程序进行点餐
点餐-菜单在点餐界面需要展示出菜品分类/套餐分类, 并根据当前选择的分类加载其中的菜品信息, 供用户查询选择
点餐-购物车用户选中的菜品就会加入用户的购物车, 主要包含 查询购物车、加入购物车、删除购物车、清空购物车等功能
订单支付用户选完菜品/套餐后, 可以对购物车菜品进行结算支付, 这时就需要进行订单的支付
个人信息在个人中心页面中会展示当前用户的基本信息, 用户可以管理收货地址, 也可以查询历史订单数据

2.3 技术选型

关于本项目的技术选型, 我们将会从 用户层、网关层、应用层、数据层 这几个方面进行介绍,主要用于展示项目中使用到的技术框架和中间件等。

在这里插入图片描述

1). 用户层

本项目中在构建系统管理后台的前端页面,我们会用到H5、Vue.js、ElementUI、apache echarts(展示图表)等技术。而在构建移动端应用时,我们会使用到微信小程序。

2). 网关层

Nginx是一个服务器,主要用来作为Http服务器,部署静态资源,访问性能高。在Nginx中还有两个比较重要的作用: 反向代理和负载均衡, 在进行项目部署时,要实现Tomcat的负载均衡,就可以通过Nginx来实现。

3). 应用层

SpringBoot: 快速构建Spring项目, 采用 “约定优于配置” 的思想, 简化Spring项目的配置开发。

SpringMVC:SpringMVC是spring框架的一个模块,springmvc和spring无需通过中间整合层进行整合,可以无缝集成。

Spring Task: 由Spring提供的定时任务框架。

httpclient: 主要实现了对http请求的发送。

Spring Cache: 由Spring提供的数据缓存框架

JWT: 用于对应用程序上的用户进行身份验证的标记。

阿里云OSS: 对象存储服务,在项目中主要存储文件,如图片等。

Swagger: 可以自动的帮助开发人员生成接口文档,并对接口进行测试。

POI: 封装了对Excel表格的常用操作。

WebSocket: 一种通信网络协议,使客户端和服务器之间的数据交换更加简单,用于项目的来单、催单功能实现。

4). 数据层

MySQL: 关系型数据库, 本项目的核心业务数据都会采用MySQL进行存储。

Redis: 基于key-value格式存储的内存数据库, 访问速度快, 经常使用它做缓存。

Mybatis: 本项目持久层将会使用Mybatis开发。

pagehelper: 分页插件。

spring data redis: 简化java代码操作Redis的API。

5). 工具

git: 版本控制工具, 在团队协作中, 使用该工具对项目中的代码进行管理。

maven: 项目构建工具。

junit:单元测试工具,开发人员功能实现完毕后,需要通过junit对功能进行单元测试。

postman: 接口测工具,模拟用户发起的各类HTTP请求,获取对应的响应结果。

3. 开发环境搭建

在这里插入图片描述

开发环境搭建主要包含前端环境后端环境两部分。作为服务端开发工程师, 我们课程学习的重心应该放在后端的业务代码上, 前端的页面我们只需要导入资料中的nginx, 前端页面的代码我们只需要能看懂即可。

3.1 前端环境搭建

1). 前端工程基于 nginx

从资料中找到前端运行环境的nginx,移动到非中文目录下。

在这里插入图片描述

sky目录中存放了管理端的前端资源,具体如下:

在这里插入图片描述

2). 启动nginx,访问测试

双击 nginx.exe 即可启动 nginx 服务,访问端口号为 80

http://localhost:80

在这里插入图片描述

3.2 后端环境搭建

3.2.1 熟悉项目结构

后端工程基于 maven 进行项目构建,并且进行分模块开发。

1). 从当天资料中找到后端初始工程:

在这里插入图片描述

2). 用 IDEA 打开初始工程,了解项目的整体结构:

在这里插入图片描述

对工程的每个模块作用说明:

序号名称说明
1sky-take-outmaven父工程,统一管理依赖版本,聚合其他子模块
2sky-common子模块,存放公共类,例如:工具类、常量类、异常类等
3sky-pojo子模块,存放实体类、VO、DTO等
4sky-server子模块,后端服务,存放配置文件、Controller、Service、Mapper等

对项目整体结构了解后,接下来我们详细分析上述的每个子模块:

  • sky-common: 模块中存放的是一些公共类,可以供其他模块使用

    在这里插入图片描述

分析sky-common模块的每个包的作用:

名称说明
constant存放相关常量类
context存放上下文类
enumeration项目的枚举类存储
exception存放自定义异常类
json处理json转换的类
properties存放SpringBoot相关的配置属性类
result返回结果类的封装
utils常用工具类
  • sky-pojo: 模块中存放的是一些 entity、DTO、VO

在这里插入图片描述

分析sky-pojo模块的每个包的作用:

名称说明
Entity实体,通常和数据库中的表对应
DTO数据传输对象,通常用于程序中各层之间传递数据
VO视图对象,为前端展示数据提供的对象
POJO普通Java对象,只有属性和对应的getter和setter
  • sky-server: 模块中存放的是 配置文件、配置类、拦截器、controller、service、mapper、启动类等

在这里插入图片描述

分析sky-server模块的每个包的作用:

名称说明
config存放配置类
controller存放controller类
interceptor存放拦截器类
mapper存放mapper接口
service存放service类
SkyApplication启动类
3.2.2 Git版本控制

使用Git进行项目代码的版本控制,具体操作:

1). 创建Git本地仓库

在这里插入图片描述
在这里插入图片描述

当Idea中出现:

在这里插入图片描述

说明本地仓库创建成功。

2). 创建Git远程仓库

访问https://gitee.com/,新建仓库

在这里插入图片描述

点击 创建

在这里插入图片描述

3). 将本地文件推送到Git远程仓库

  1. 提交文件至本地仓库

忽略以下类型文件

在这里插入图片描述

开始提交
在这里插入图片描述

中间出现:点击commit

在这里插入图片描述

  1. 添加Git远程仓库地址

复制远程地址:

在这里插入图片描述

添加地址:

在这里插入图片描述

在这里插入图片描述

  1. 推送

在这里插入图片描述

成功推送至远程仓库
在这里插入图片描述

3.2.3 数据库环境搭建
  1. 从资料中找到sky.sql

在这里插入图片描述

直接打开sky.sql文件

在这里插入图片描述

通过该sql文件直接可创建数据库,所以不需要提前创建数据库,直接导入该文件执行即可。

  1. 执行sky.sql文件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

执行完成后,共创建出11张表

在这里插入图片描述

每张表的说明:

序号表名中文名
1employee员工表
2category分类表
3dish菜品表
4dish_flavor菜品口味表
5setmeal套餐表
6setmeal_dish套餐菜品关系表
7user用户表
8address_book地址表
9shopping_cart购物车表
10orders订单表
11order_detail订单明细表

我们目前先简单了解大概有哪些表, 每张表存储什么数据, 有一个印象。对于具体的表结构, 以及表结构中的字段, 可以参考资料中的**《数据库设计文档》**,同时在讲解具体的功能开发时, 我们也会再详细介绍。

在这里插入图片描述

3.2.4 前后端联调

后端的初始工程中已经实现了登录功能,直接进行前后端联调测试即可

实现思路:

在这里插入图片描述

1.Controller层

在sky-server模块中,com.sky.controller.admin.EmployeeController

/*** 登录** @param employeeLoginDTO* @return*/@PostMapping("/login")public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {log.info("员工登录:{}", employeeLoginDTO);//调用service方法查询数据库Employee employee = employeeService.login(employeeLoginDTO);//登录成功后,生成jwt令牌Map<String, Object> claims = new HashMap<>();claims.put(JwtClaimsConstant.EMP_ID, employee.getId());String token = JwtUtil.createJWT(jwtProperties.getAdminSecretKey(),jwtProperties.getAdminTtl(),claims);EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder().id(employee.getId()).userName(employee.getUsername()).name(employee.getName()).token(token).build();return Result.success(employeeLoginVO);}

2.Service层

在sky-server模块中,com.sky.service.impl.EmployeeServiceImpl

/*** 员工登录** @param employeeLoginDTO* @return*/public Employee login(EmployeeLoginDTO employeeLoginDTO) {String username = employeeLoginDTO.getUsername();String password = employeeLoginDTO.getPassword();//1、根据用户名查询数据库中的数据Employee employee = employeeMapper.getByUsername(username);//2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)if (employee == null) {//账号不存在throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);}//密码比对if (!password.equals(employee.getPassword())) {//密码错误throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);}if (employee.getStatus() == StatusConstant.DISABLE) {//账号被锁定throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);}//3、返回实体对象return employee;}

3.Mapper层

在sky-server模块中,com.sky.mapper.EmployeeMapper

package com.sky.mapper;import com.sky.entity.Employee;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;@Mapper
public interface EmployeeMapper {/*** 根据用户名查询员工* @param username* @return*/@Select("select * from employee where username = #{username}")Employee getByUsername(String username);}

注:可以通过断点调试跟踪后端程序的执行过程

3.2.5 nginx反向代理和负载均衡

对登录功能测试完毕后,接下来,我们思考一个问题:前端发送的请求,是如何请求到后端服务的?

前端请求地址:http://localhost/api/employee/login

后端接口地址:http://localhost:8080/admin/employee/login

前端请求地址 后端接口地址

在这里插入图片描述
在这里插入图片描述

很明显,两个地址不一致,那是如何请求到后端服务的呢?

在这里插入图片描述

1). nginx反向代理

nginx 反向代理,就是将前端发送的动态请求由 nginx 转发到后端服务器

在这里插入图片描述

那为什么不直接通过浏览器直接请求后台服务端,需要通过nginx反向代理呢?

nginx 反向代理的好处:

  • 提高访问速度

    因为nginx本身可以进行缓存,如果访问的同一接口,并且做了数据缓存,nginx就直接可把数据返回,不需要真正地访问服务端,从而提高访问速度。

  • 进行负载均衡

    所谓负载均衡,就是把大量的请求按照我们指定的方式均衡的分配给集群中的每台服务器。

  • 保证后端服务安全

    因为一般后台服务地址不会暴露,所以使用浏览器不能直接访问,可以把nginx作为请求访问的入口,请求到达nginx后转发到具体的服务中,从而保证后端服务的安全。

在这里插入图片描述

nginx 反向代理的配置方式:

server{listen 80;server_name localhost;location /api/{proxy_pass http://localhost:8080/admin/; #反向代理}
}

**proxy_pass:**该指令是用来设置代理服务器的地址,可以是主机名称,IP地址加端口号等形式。

如上代码的含义是:监听80端口号, 然后当我们访问 http://localhost:80/api/…/…这样的接口的时候,它会通过 location /api/ {} 这样的反向代理到 http://localhost:8080/admin/上来。

接下来,进到nginx-1.20.2\conf,打开nginx配置

# 反向代理,处理管理端发送的请求
location /api/ {proxy_pass   http://localhost:8080/admin/;#proxy_pass   http://webservers/admin/;
}

当在访问http://localhost/api/employee/login,nginx接收到请求后转到http://localhost:8080/admin/,故最终的请求地址为http://localhost:8080/admin/employee/login,和后台服务的访问地址一致。

2). nginx 负载均衡

当如果服务以集群的方式进行部署时,那nginx在转发请求到服务器时就需要做相应的负载均衡。其实,负载均衡从本质上来说也是基于反向代理来实现的,最终都是转发请求。

nginx 负载均衡的配置方式:

upstream webservers{server 192.168.100.128:8080;server 192.168.100.129:8080;
}
server{listen 80;server_name localhost;location /api/{proxy_pass http://webservers/admin;#负载均衡}
}

**upstream:**如果代理服务器是一组服务器的话,我们可以使用upstream指令配置后端服务器组。

如上代码的含义是:监听80端口号, 然后当我们访问 http://localhost:80/api/…/…这样的接口的时候,它会通过 location /api/ {} 这样的反向代理到 http://webservers/admin,根据webservers名称找到一组服务器,根据设置的负载均衡策略(默认是轮询)转发到具体的服务器。

**注:**upstream后面的名称可自定义,但要上下保持一致。

nginx 负载均衡策略:

名称说明
轮询默认方式
weight权重方式,默认为1,权重越高,被分配的客户端请求就越多
ip_hash依据ip分配方式,这样每个访客可以固定访问一个后端服务
least_conn依据最少连接方式,把请求优先分配给连接数少的后端服务
url_hash依据url分配方式,这样相同的url会被分配到同一个后端服务
fair依据响应时间方式,响应时间短的服务将会被优先分配

具体配置方式:

轮询:

upstream webservers{server 192.168.100.128:8080;server 192.168.100.129:8080;
}

weight:

upstream webservers{server 192.168.100.128:8080 weight=90;server 192.168.100.129:8080 weight=10;
}

ip_hash:

upstream webservers{ip_hash;server 192.168.100.128:8080;server 192.168.100.129:8080;
}

least_conn:

upstream webservers{least_conn;server 192.168.100.128:8080;server 192.168.100.129:8080;
}

url_hash:

upstream webservers{hash &request_uri;server 192.168.100.128:8080;server 192.168.100.129:8080;
}

fair:

upstream webservers{server 192.168.100.128:8080;server 192.168.100.129:8080;fair;
}

3.3 完善登录功能

**问题:**员工表中的密码是明文存储,安全性太低。

在这里插入图片描述

解决思路:

  1. 将密码加密后存储,提高安全性

在这里插入图片描述

  1. 使用MD5加密方式对明文密码加密

在这里插入图片描述

实现步骤:

  1. 修改数据库中明文密码,改为MD5加密后的密文

打开employee表,修改密码

在这里插入图片描述

  1. 修改Java代码,前端提交的密码进行MD5加密后再跟数据库中密码比对

打开EmployeeServiceImpl.java,修改比对密码

/*** 员工登录** @param employeeLoginDTO* @return*/public Employee login(EmployeeLoginDTO employeeLoginDTO) {//1、根据用户名查询数据库中的数据//2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)//.......//密码比对// TODO 后期需要进行md5加密,然后再进行比对password = DigestUtils.md5DigestAsHex(password.getBytes());if (!password.equals(employee.getPassword())) {//密码错误throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);}//........//3、返回实体对象return employee;}

4. 导入接口文档

接下来,就要进入到项目的业务开发了,而我们的开发方式就是基本当前企业主流的前后端分离开发方式,那么这种方式就要求我们之前需要先将接口定义好,这样前后端人员才能并行开发,所以,这个章节就需要将接口文档导入到管理平台,为我们后面业务开发做好准备。其实,在真实的企业开发中,接口设计过程其实是一个非常漫长的过程,可能需要多次开会讨论调整,甚至在开发的过程中才会发现某些接口定义还需要再调整,这种情况其实是非常常见的,但是由于项目时间原因,所以选择一次性导入所有的接口,在开发业务功能过程当中,也会带着大家一起来分析一下对应的接口是怎么确定下来的,为什么要这样定义,从而培养同学们的接口设计能力。

4.1 前后端分离开发流程

在这里插入图片描述

第一步:定义接口,确定接口的路径、请求方式、传入参数、返回参数。

第二步:前端开发人员和后端开发人员并行开发,同时,也可自测。

第三步:前后端人员进行连调测试。

第四步:提交给测试人员进行最终测试。

4.2 操作步骤

将课程资料中提供的项目接口导入YApi。访问地址:http://yapi.smart-xwork.cn/

1). 从资料中找到项目接口文件

在这里插入图片描述

2). 导入到YApi平台

在YApi平台创建出两个项目

在这里插入图片描述

选择苍穹外卖-管理端接口.json导入

在这里插入图片描述

导入成功

在这里插入图片描述

另一个用户端json文件也执行相同操作。

5. Swagger

5.1 介绍

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务(https://swagger.io/)。 它的主要作用是:

  1. 使得前后端分离开发更加方便,有利于团队协作

  2. 接口的文档在线自动生成,降低后端开发人员编写接口文档的负担

  3. 功能测试

Spring已经将Swagger纳入自身的标准,建立了Spring-swagger项目,现在叫Springfox。通过在项目中引入Springfox ,即可非常简单快捷的使用Swagger。

knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍!

目前,一般都使用knife4j框架。

5.2 使用步骤

  1. 导入 knife4j 的maven坐标

在pom.xml中添加依赖

<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
  1. 在配置类中加入 knife4j 相关配置

WebMvcConfiguration.java

/*** 通过knife4j生成接口文档* @return
*/@Beanpublic Docket docket() {ApiInfo apiInfo = new ApiInfoBuilder().title("苍穹外卖项目接口文档").version("2.0").description("苍穹外卖项目接口文档").build();Docket docket = new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo).select().apis(RequestHandlerSelectors.basePackage("com.sky.controller")).paths(PathSelectors.any()).build();return docket;}
  1. 设置静态资源映射,否则接口文档页面无法访问

WebMvcConfiguration.java

/*** 设置静态资源映射* @param registry
*/
protected void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
  1. 访问测试

接口文档访问路径为 http://ip:port/doc.html —> http://localhost:8080/doc.html

在这里插入图片描述

接口测试:测试登录功能

在这里插入图片描述

**思考:**通过 Swagger 就可以生成接口文档,那么我们就不需要 Yapi 了?

1、Yapi 是设计阶段使用的工具,管理和维护接口

2、Swagger 在开发阶段使用的框架,帮助后端开发人员做后端的接口测试

5.3 常用注解

通过注解可以控制生成的接口文档,使接口文档拥有更好的可读性,常用注解如下:

注解说明
@Api用在类上,例如Controller,表示对类的说明
@ApiModel用在类上,例如entity、DTO、VO
@ApiModelProperty用在属性上,描述属性信息
@ApiOperation用在方法上,例如Controller的方法,说明方法的用途、作用

接下来,使用上述注解,生成可读性更好的接口文档

在sky-pojo模块中

EmployeeLoginDTO.java

package com.sky.dto;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;import java.io.Serializable;@Data
@ApiModel(description = "员工登录时传递的数据模型")
public class EmployeeLoginDTO implements Serializable {@ApiModelProperty("用户名")private String username;@ApiModelProperty("密码")private String password;}

EmployeeLoginVo.java

package com.sky.vo;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(description = "员工登录返回的数据格式")
public class EmployeeLoginVO implements Serializable {@ApiModelProperty("主键值")private Long id;@ApiModelProperty("用户名")private String userName;@ApiModelProperty("姓名")private String name;@ApiModelProperty("jwt令牌")private String token;}

在sky-server模块中

EmployeeController.java

package com.sky.controller.admin;import com.sky.constant.JwtClaimsConstant;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.entity.Employee;
import com.sky.properties.JwtProperties;
import com.sky.result.Result;
import com.sky.service.EmployeeService;
import com.sky.utils.JwtUtil;
import com.sky.vo.EmployeeLoginVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.Map;/*** 员工管理*/
@RestController
@RequestMapping("/admin/employee")
@Slf4j
@Api(tags = "员工相关接口")
public class EmployeeController {@Autowiredprivate EmployeeService employeeService;@Autowiredprivate JwtProperties jwtProperties;/*** 登录** @param employeeLoginDTO* @return*/@PostMapping("/login")@ApiOperation(value = "员工登录")public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO)     {//..............}/*** 退出** @return*/@PostMapping("/logout")@ApiOperation("员工退出")public Result<String> logout() {return Result.success();}}

启动服务:访问http://localhost:8080/doc.html
在这里插入图片描述

资料下载

链接:https://pan.quark.cn/s/3164dc20d833

相关文章:

黑马程序员Java项目实战《苍穹外卖》Day01

苍穹外卖-day01 课程内容 软件开发整体介绍苍穹外卖项目介绍开发环境搭建导入接口文档Swagger 项目整体效果展示&#xff1a; ​ 管理端-外卖商家使用 ​ 用户端-点餐用户使用 当我们完成该项目的学习&#xff0c;可以培养以下能力&#xff1a; 1. 软件开发整体介绍 作为一…...

二叉树:堆的建立和应用

在建立堆之前&#xff0c;我们要知道什么是树和二叉树 树 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个结点组成的一个具有层次关系的集合&#xff0c;之所以把它叫做树&#xff0c;是因为它长得像一棵倒挂的树&#xff0c;也就是根在上面&…...

手搓人工智能-最优化算法(1)最速梯度下降法,及推导过程

“Men pass away, but their deeds abide.” 人终有一死&#xff0c;但是他们的业绩将永存。 ——奥古斯坦-路易柯西 目录 前言 简单函数求极值 复杂函数梯度法求极值 泰勒展开 梯度&#xff0c;Nabla算子 Cauchy-Schwarz不等式 梯度下降算法 算法流程 梯度下降法…...

【大数据学习 | Spark-SQL】Spark-SQL编程

上面的是SparkSQL的API操作。 1. 将RDD转化为DataFrame对象 DataFrame&#xff1a; DataFrame是一种以RDD为基础的分布式数据集&#xff0c;类似于传统数据库中的二维表格。带有schema元信息&#xff0c;即DataFrame所表示的二维表数据集的每一列都带有名称和类型。这样的数…...

go+gin+mysql+gorm快速实现增删改查接口

Gin 是一个用 Go (Golang) 编写的轻量级但功能强大的Web框架&#xff0c;具有极高的性能。适合开发API和微服务。 1.安装 Gin 前置任务&#xff0c;首先你本地需要安装过go语言环境 go get -u github.com/gin-gonic/gin这里直接结合项目代码解析gin的主要功能&#xff1a; …...

【国产MCU】-GD32F470-串行外设接口(SPI)

串行外设接口(SPI) 文章目录 串行外设接口(SPI)1、SPI介绍1.1 SPI特性1.2 SPI信号1.3 SPI 时序和数据帧格式1.4 NSS 功能1.5 SPI运行模式2、SPI控制器寄存器列表3、SPI控制器驱动API介绍4、SPI应用4.1 SPI初始化流程4.2 数据发送与接收串行外设接口(Serial Peripheral Int…...

RabbitMQ 集群

文章目录 前言单机多节点搭建集群创建RabbitMQ节点停止服务并重置将节点加入到集群中查看集群状态宕机 结论 前言 RabbitMQ 作为消息中间件&#xff0c;可以与多个生产者和消费者进行绑定&#xff0c;但是如何只有一台 RabbitMQ 服务的话&#xff0c;那么这个 RabbitMQ 就需要…...

Qt实现窗口内的控件自适应窗口大小

前言&#xff1a;因为有时候窗口内的控件比较大&#xff0c;但是为了同时操作多个窗口&#xff0c;希望可以根据情况&#xff0c;在调整窗口大小时&#xff0c;控件能自适应大小&#xff0c;于是通过遍历整个界面内的控件&#xff0c;并在调整大小的事件中按比率调整控件大小实…...

Android基本概念及控件

Android是Google公司基于Linux平台开发的主要应用于智能手机及平板电脑的操作系统。 ART模式与Dalvik模式最大的不同在于:在启用ART模式后&#xff0c;系统在安装应用程序的时候会进行一次预编译&#xff0c;并先将代码转换为机器语言存储在本地,这样在运行程序时就不会每次都…...

云计算的计算包括哪些内容

‌云计算的计算主要包括以下几种类型‌&#xff1a; ‌分布式计算‌&#xff1a;分布式计算是一种计算方法&#xff0c;它将大型问题分解成多个小任务&#xff0c;然后分配给多个计算机进行处理。这种方法可以提高计算效率和可靠性‌1。‌并行计算‌&#xff1a;并行计算是同时…...

D81【 python 接口自动化学习】- python基础之HTTP

day81 requests请求session用法 学习日期&#xff1a;20241127 学习目标&#xff1a;http定义及实战 -- requests请求session用法 学习笔记&#xff1a; requests请求session用法 import requests# 创建一个会话 reqrequests.session() url "http://sellshop.5istud…...

嵌入式驱动开发详解2(设备挂载问题)

文章目录 前言设备号设备号的组成设备号的分配静态分配动态分配 驱动挂载与卸载设备节点创建驱动挂载出现问题 前言 驱动的设备挂载和卸载是十分重要的内容&#xff0c;一旦操作不当可能会导致系统崩溃&#xff0c;接下来我将用字符设备的驱动挂载原理进行详细讲解&#xff0c…...

Java全栈开发:宠物医院管理系统项目实战

Java全栈开发:宠物医院管理系统项目实战 项目介绍 本文将介绍一个基于Spring Boot + Vue.js的宠物医院管理系统的开发过程。该系统主要用于帮助宠物医院管理日常运营,包括患者管理、预约挂号、处方开具等功能。 技术栈 后端技术 Spring Boot 2.7.xSpring SecurityMyBatis…...

【设计模式】1. 构建器模式(Builder Pattern)是一种创建型设计模式

构建器模式&#xff08;Builder Pattern&#xff09;是一种创建型设计模式&#xff0c;用于分步骤构建复杂对象&#xff0c;同时允许按照不同的需求生成不同的表示。该模式将对象的构建过程与其表示分离&#xff0c;使得相同的构建过程可以创建不同的对象。 核心思想 构建器模…...

Apache Zeppelin:一个基于Web的大数据可视化分析平台

今天给大家推荐一下 Apache Zeppelin&#xff0c;它是一个基于 Web 的交互式数据接入、数据分析、数据可视化以及协作文档 Notebook&#xff0c;类似于 Jupyter Notebook。 Apache Zeppelin 支持使用 SQL、Java、Scala、Python、R 等编程语言进行数据处理和分析&#xff0c;同时…...

在Manjaro Gnome桌面的基础上安装Budgie桌面环境

在Manjaro上安装Budgie桌面环境 Budgie是Solus团队开发的一种简单而优雅的桌面环境。 Budgie是由Solus项目主要开发的流行桌面环境&#xff0c;与GNOME堆栈紧密集成。它提供了简单而优雅的用户体验&#xff0c;并且可用于大多数发行版&#xff0c;如Arch、Debian、Manjaro等。…...

C语言中的指针的解读和强大功能

C语言中的指针是其最强大也是最复杂的特性之一&#xff0c;掌握指针对于深入学习C语言非常重要。以下是对C语言指针的详解&#xff1a; 1. 指针的基本概念 指针是什么&#xff1a; 指针是一个变量&#xff0c;其值是另一个变量的地址。指针的作用&#xff1a; 通过指针&#x…...

44.扫雷第二部分、放置随机的雷,扫雷,炸死或成功 C语言

按照教程打完了。好几个bug都是自己打出来的。比如统计周围8个格子时&#xff0c;有一个各自加号填成了减号。我还以为平移了&#xff0c;一会显示是0一会显示是2。结果单纯的打错了。debug的时候断点放在scanf后面会顺畅一些。中间多放一些变量名方便监视。以及mine要多显示&a…...

【Vue】最新Vue3实战教程:全面掌握响应式数据、v-on事件、v-if/v-show条件渲染及更多核心技术

文章目录 为什么选择Vue3&#xff1f;更多实用工具快速上手Vue3安装与配置项目结构 响应式数据处理创建响应式数据使用ref使用reactive 计算属性与侦听器计算属性侦听器 事件绑定与处理&#xff08;v-on&#xff09;基本用法简写语法事件修饰符绑定多个事件 条件渲染&#xff0…...

实时数仓Kappa架构:从入门到实战

引言 随着大数据技术的不断发展&#xff0c;企业对实时数据处理和分析的需求日益增长。实时数仓&#xff08;Real-Time Data Warehouse, RTDW&#xff09;应运而生&#xff0c;其中Kappa架构作为一种简化的数据处理架构&#xff0c;通过统一的流处理框架&#xff0c;解决了传统…...

爬虫笔记24——纷玩岛(某岛)自动抢票脚本笔记

纷玩岛自动抢票&#xff0c;协议抢票思路实现 一、获取Authorization凭证二、几个关键的参数三、几个关键的接口获取参数v&#xff0c;这个参数其实可以写死&#xff0c;可忽略通过价位获取演出的参数信息获取观演人信息&#xff0c;账号提前录入即可提交订单接口 先看实现图&a…...

接口性能优化宝典:解决性能瓶颈的策略与实践

目录 一、直面索引 &#xff08;一&#xff09;索引优化的常见场景 &#xff08;二&#xff09;如何检查索引的使用情况 &#xff08;三&#xff09;如何避免索引失效 &#xff08;四&#xff09;强制选择索引 二、提升 SQL 执行效率 &#xff08;一&#xff09;避免不必…...

5G NR:带宽与采样率的计算

100M 带宽是122.88Mhz sampling rate这是我们都知道的&#xff0c;那它是怎么来的呢&#xff1f; 采样率 子载波间隔 * 采样长度 38.211中对于Tc的定义&#xff0c; 在LTE是定义了Ts&#xff0c;在NR也就是5G定义了Tc。 定义这个单位会对我们以后工作中的计算至关重要。 就是在…...

python的openpyxl库设置表格样式:字体/边框/对齐/颜色等

学习目录 1. 安装和使用openpyxl库设置表格样式 2 设置字体font 3 设置边框 4 设置对齐方式 5 设置单元格数据格式 6 设置行高和列宽 7 填充单元格颜色 附录-关于颜色说明 本章节主要介绍如何使用openpyxl库设置表格中的一些样式&#xff0c;比如字体&#xff0c;边框…...

Vue报错:error Mixed spaces and tabs no-mixed-spaces-and-tabs——报错处理

最近在写后台管理系统的时候&#xff0c;遇到一个报错Vue报错&#xff1a;error Mixed spaces and tabs no-mixed-spaces-and-tabs 出现这个问题的原因&#xff0c;就是同事那边安装了eslint&#xff0c;用来规范代码风格。Eslint是语法检查工具&#xff0c;缺点是对所写代码要…...

Spring:Spring事务管理代码案例讲解

Spring事务管理知识讲解请见&#xff1a;Spring事务知识点讲解 下面演示一个代码示例进行理解。 需求 两个账户相互转账&#xff0c;并记录日志&#xff0c;即使有转账失败也要记录 需求分析 这里主要是需要开启事务机制来控制转入和转出&#xff1a; 1&#xff0c;创建一…...

【Petri网导论学习笔记】Petri网导论入门学习(十) —— 3.2 关联矩阵与状态方程

目录 3.2 关联矩阵与状态方程定义 3.3 关联矩阵引理 3.4引理 3.5定理 3.4例 3.7例 3.83.2 关联矩阵与状态方程 正如 Petri 网的一个标识可以表示成一个 $ m $ 维非负整数向量一样,Petri 网的结构也可以用一个矩阵来表示。这样,就可以引入线性代数的方法对 Petri 网的性质进行…...

(计算机网络)期末

计算机网络概述 物理层 信源就是发送方 信宿就是接收方 串行通信--一次只发一个单位的数据&#xff08;串行输入&#xff09; 并行通信--一次可以传输多个单位的数据 光纤--利用光的反射进行传输 传输之前&#xff0c;要对信源进行一个编码&#xff0c;收到信息之后要进行一个…...

自动驾驶目标检测融合全貌

1、early fusion 早期融合&#xff0c;特点用到几何空间转换3d到2d或者2d到3d的转换&#xff0c;用像素找点云或者用点云找像素。 2、deep fusion 深度融合&#xff0c;也是特征级别融合&#xff0c;也叫多模态融合&#xff0c;如bevfusion范式 3、late fusion 晚融合&#x…...

Spring中实现动态数据源切换,基于AbstractRoutingDataSource

背景 在项目开发过程中&#xff0c;我们可能会遇到一个场景&#xff1a;某个类型数据源有多个数据源实例&#xff0c;需要我们按照不同的请求切换到不同数据源去。 而目前绝大多数java应用都是基于Spring框架来开发&#xff0c;我们很多时候相关的数据源连接都是交给了Spring框…...

Linux指标之平均负载(The Average load of Linux Metrics)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…...

基于SpringBoot共享汽车管理系统【附源码】

基于SpringBoot共享汽车管理系统 效果如下&#xff1a; 系统注册页面 系统登陆页面 系统管理员主页面 用户信息管理页面 汽车投放管理页面 使用订单页面 汽车归还管理页面 研究背景 随着计算机技术和计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所。二十…...

React-useState的使用

useState 是 React 提供的一个 Hook&#xff0c;允许你在函数组件中添加和管理状态&#xff08;state&#xff09;。在类组件中&#xff0c;状态管理通常是通过 this.state 和 this.setState 来实现的&#xff0c;而在函数组件中&#xff0c;useState 提供了类似的功能。 基本…...

wordpress 中添加图片放大功能

功能描述 使用 Fancybox 实现图片放大和灯箱效果。自动为文章内容中的图片添加链接&#xff0c;使其支持 Fancybox。修改了 header.php 和 footer.php 以引入必要的 CSS 和 JS 文件。在 functions.php 中通过过滤器自动为图片添加 data-fancybox 属性。 最终代码 1. 修改 hea…...

Day 27 贪心算法 part01

贪心算法其实就是没有什么规律可言,所以大家了解贪心算法 就了解它没有规律的本质就够了。 不用花心思去研究其规律, 没有思路就立刻看题解。 基本贪心的题目 有两个极端,要不就是特简单,要不就是死活想不出来。 学完贪心之后再去看动态规划,就会了解贪心和动规的区别。…...

运维面试题.云计算面试题

一、选择题(每题1分,合计15分) 1.若当前目录为 /home,命令 ls–l 将显示 home 目录下的( )。 A.所有文件 B.所有隐含文件 C.所有非隐含文件 D.文件的具体信息 2.如果要列出一个目录下的所有文件需要使用命令行( )。 A. ls–l B. ls C. ls–a(all) D. ls–d 3.下面关于文件…...

计算机专业的真正的就业情况

首先听到计算机行业&#xff0c;大多数人岗位已经饱和&#xff0c;前端已死&#xff0c;程序员35岁危机。但是事实上这些认知都是片面的&#xff0c;今天由我来为大家分析计算机行业的内幕。 疫情过后&#xff0c;过内各种行业都受到了冲击&#xff0c;你们敢说除了体制内的行业…...

行为型模式-状态模式

状态模式&#xff08;State Pattern&#xff09;是行为型设计模式之一&#xff0c;用于允许一个对象在其内部状态改变时改变其行为。状态模式可以有效避免在对象中使用大量的条件语句&#xff0c;通过将状态的逻辑转移到独立的状态类中&#xff0c;实现状态与行为的分离。 核心…...

字节跳动青训营刷题笔记19

问题描述 小R正在组织一个比赛&#xff0c;比赛中有 n 支队伍参赛。比赛遵循以下独特的赛制&#xff1a; 如果当前队伍数为 偶数&#xff0c;那么每支队伍都会与另一支队伍配对。总共进行 n / 2 场比赛&#xff0c;且产生 n / 2 支队伍进入下一轮。如果当前队伍数为 奇数&…...

【MATLAB源码-第225期】基于matlab的计算器GUI设计仿真,能够实现基础运算,三角函数以及幂运算。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 界面布局 计算器界面的主要元素分为几大部分&#xff1a;显示屏、功能按钮、数字按钮和操作符按钮。 显示屏 显示屏&#xff08;Edit Text&#xff09;&#xff1a;位于界面顶部中央&#xff0c;用于显示用户输入的表达式和…...

C++学习日记---第14天(蓝桥杯备赛)

笔记复习 1.对象的初始化和清理 对象的初始化和清理是两个非常重要的安全问题&#xff0c;一个对象或者变量没有初始状态&#xff0c;对其使用后果是未知&#xff0c;同样的使用完一个对象或者变量&#xff0c;没有及时清理&#xff0c;也会造成一定的安全问题 构造函数&…...

windows C#-定义和读取自定义特性

使用特性&#xff0c;可以声明的方式将信息与代码相关联。 特性还可以提供能够应用于各种目标的可重用元素。 考虑 ObsoleteAttribute。 它可以应用于类、结构、方法、构造函数等。 用于声明元素已过时。 然后&#xff0c;由 C# 编译器负责查找此特性&#xff0c;并执行某响应操…...

一个vue项目如何运行在docker

将 Vue.js 应用程序通过 Docker 发布是一个非常常见的做法&#xff0c;它可以帮助你轻松地部署应用到不同的环境中。下面是一个简单的指南&#xff0c;介绍如何为 Vue.js 项目创建 Dockerfile 并进行构建和运行。 第一步&#xff1a;安装 Docker 确保你的开发机器上已经安装了…...

重读《人月神话》(15)-祸起萧墙(Hatching a Catastrophe

增加更多的人手到一个已经延期的项目中往往不会加快项目的进度&#xff0c;反而可能使情况变得更糟。 项目进度的细微延迟往往难以察觉&#xff0c;但它们却能悄无声息地累积起来&#xff0c;最终对整个项目的完成时间造成重大影响。昨天&#xff0c;一位关键成员因突发疾病未…...

【大数据学习 | Spark-Core】广播变量和累加器

1. 共享变量 Spark两种共享变量&#xff1a;广播变量&#xff08;broadcast variable&#xff09;与累加器&#xff08;accumulator&#xff09;。 累加器用来对信息进行聚合&#xff0c;相当于mapreduce中的counter&#xff1b;而广播变量用来高效分发较大的对象&#xff0c…...

C/C++基础知识复习(30)

1) 什么是 C 中的 Lambda 表达式&#xff1f;它的作用是什么&#xff1f; Lambda 表达式&#xff1a; 在 C 中&#xff0c;Lambda 表达式是一种可以定义匿名函数的机制&#xff0c;可以在代码中快速创建一个内联的函数对象&#xff0c;而不需要显式地定义一个函数。Lambda 表…...

【Spring MVC】如何获取cookie/session以及响应@RestController的理解,Header的设置

前言 &#x1f31f;&#x1f31f;本期讲解关于SpringMVC的编程之参数传递~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那么废…...

Could not locate device support files.

报错信息&#xff1a;Failure Reason: The device may be running a version of iOS (13.6.1 17G80) that is not supported by this version of Xcode.[missing string: 869a8e318f07f3e2f42e11d435502286094f76de] 问题&#xff1a;xcode15升级到xcode16之后&#xff0c;13.…...

Kafka日志索引详解以及生产常见问题分析与总结

文章目录 一、Kafka的Log日志梳理1.1、Topic下的消息如何存储1.1.1、log文件追加记录所有消息1.1.2、index和timeindex加速读取log消息日志 1.2、文件清理机制1.2.1、如何判断哪些日志文件过期了1.2.2、过期的日志文件如何处理 1.3、Kafka的文件高效读写机制1.3.1、Kafka的文件…...

Qt中QGraphics绘图类相关解释

Item(图元)坐标系、Scene(场景)坐标系、View(视图)坐标系&#xff0c;三者均为&#xff1a;x轴正方向向右&#xff0c;y轴正方向向下 1、Item(图元)&#xff1a;坐标属于局部坐标&#xff0c;通常以图元中心为原点(中心对称)。 场景坐标系统描述了顶层的图元&#xff0c;每个图…...