苍穹外卖项目

1. 苍穹外卖项目介绍

1.1 项目介绍

定位:专门为餐饮企业(餐厅、饭店)定制的一款软件产品

项目架构:体现项目中的业务功能模块

1.2 产品原型

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

1.3 技术选型

技术选型:展示项目中使用到的技术框架和中间件等

2. 开发环境的搭建

2.1 前端环境搭建

vue node js axios

2.2 后端环境搭建

mysql 、 springboot 、springmvc、websocket

2.2.1 使用Git进行版本控制

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

  • 创建Git本地仓库

1.使用idea点击VCS,创建本地仓库

  • 创建Git远程仓库
  • 将本地文件推送到Git远程仓库

2.3 实现登录效果

2.3.1 前后端联调

nginx 反向代理的配置方式

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

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/;#反向代理}
}

2.3.2 未完成任务的注释

TODO 注释

2.3.2 完善登录效果

密码是明文存储需要加上md5加密

// md5加密
password = DigestUtils.md5DigestAsHex(password.getBytes());

3. 导入接口文档

3.1 Swagger

介绍:使用swagger你只需要按照他的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面

Knife4j是为java MVC框架集成swagger生成api文档的增强解决方案。

3.2 使用方式

      1. 导入knife4j的mavern坐标 pom.xml

<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>3.0.2</version>
</dependency>

      2. 在配置类中加入 knife4j 相关配置 WebMvcConfiguration.java

@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;}

     3. 设置静态资源映射,否则接口文档页面无法访问WebMvcConfiguration.java

 protected void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");}

3.3 常用注解

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

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

4. 员工管理

4.1 新增员工

知识点1:对象的拷贝

//对象的拷贝
BeanUtils.copyProperties(employeeDTO,employee);

知识点2:插入两个相同唯一的值,全局捕获异常 handle/GlobalExceptionHandler

    /*** 处理sql异常* @param ex* @return*/@ExceptionHandlerpublic Result exceptionHandler(SQLIntegrityConstraintViolationException ex){String message = ex.getMessage();if(message.contains("Duplicate entry")){String[] split = message.split(" ");String username = split[2];String msg = username + MessageConstant.ALREADY_EXISTS;return Result.error(msg);}else {return Result.error(MessageConstant.UNKNOWN_ERROR);}}

知识点3:动态获取用户id

ThreadLocal为每个线程提供一份存储空间,具有线程隔离效果,只有在线程内才能获取到对应的值,线程外不能访问。

controller、service、拦截器里面都是同一个。所以可以进行通信

ThreadLocal常用方法:

  • public void set(T value) 设置当前的线程局部变量的值
  • public T get()  返回当前线程所对应的线程局部变量的值
  • public void remove()  移除当前线程的局部变量值

封装一个:工具类BaseContext:

public class BaseContext {public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();public static void setCurrentId(Long id) {threadLocal.set(id);}public static Long getCurrentId() {return threadLocal.get();}public static void removeCurrentId() {threadLocal.remove();}}

存储id:

BaseContext.setCurrentId(empId);

取出id:

BaseContext.setCurrentId(empId);

4.2 员工分页查询

4.2.1 分页查询:

配置配置:

@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mybatisPlusInterceptor;}
}

 使用:

Page<Employee> employeePage = new Page<>(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());LambdaQueryWrapper<Employee> employeeWrapper = new LambdaQueryWrapper<>();employeeWrapper.like(ObjectUtils.isNotEmpty(employeePageQueryDTO.getName()),Employee::getName,employeePageQueryDTO.getName());employeeService.page(employeePage,employeeWrapper);PageResult pageResult = new PageResult();BeanUtils.copyProperties(employeePage,pageResult);return Result.success(pageResult);

4.2.2 解决时间显示问题

方式一:在属性上加入注解,对日期格式化

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;")

方式二:在WebMvcConfiguration中扩展Spring MVC 的消息转换器,统一对日期类型进行格式化处理

第一步:在WebMvcConfiguration编写代码

 protected void extendMessageConverters(List<HttpMessageConverter<?>> converters){log.info("开始扩展消息转换器...");//创建一个消息转化器对象MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();//设置对象转换器,可以将java对象转为json字符串converter.setObjectMapper(new JacksonObjectMapper());//将我们自己的转化器放入spring MVC框架容器中converters.add(0,converter);}

第二步:新建JacksonObjectMapper工具类中

package com.takeaway.json;import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;/*** 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]*/
public class JacksonObjectMapper extends ObjectMapper {public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";//public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";public JacksonObjectMapper() {super();//收到未知属性时不报异常this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);//反序列化时,属性不存在的兼容处理this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);SimpleModule simpleModule = new SimpleModule().addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))).addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))).addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))).addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))).addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))).addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));//注册功能模块 例如,可以添加自定义序列化器和反序列化器this.registerModule(simpleModule);}
}

4.3 员工禁用

用户的id是17位但是前端long没有那么长,会导致精度丢失:

解决方法:配置confg

@Configuration
public class IdJsonConfig {@Beanpublic ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {ObjectMapper objectMapper = builder.createXmlMapper(false).build();objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);SimpleModule module = new SimpleModule();module.addSerializer(Long.class, ToStringSerializer.instance);module.addSerializer(Long.TYPE, ToStringSerializer.instance);objectMapper.registerModule(module);return objectMapper;}
}

禁用方法:

@PostMapping("/status/{status}")//public Result startOrStop(@PathVariable("status") Integer status,Long id){ 不一致需要写public Result startOrStop(@PathVariable Integer status, Long id) {

4.4  编辑员工

4.4.1 获取用户详情

    @ApiOperation("获取用户详情")@GetMapping("/{id}")public Result<Employee> getById(@PathVariable Long id){

4.4.2 编辑员工

字段设计:

    @ApiOperation("新增分类")@PostMapping@ApiOperation("分类分页查询")@GetMapping("/page")@DeleteMapping@ApiOperation("删除分类")@ApiOperation("分类禁用和启用")@PostMapping("/status/{status}")@ApiOperation("修改分类")@PutMapping

5. 菜品管理

5.1 公共字段填充(未实现)

业务表中的公共字段:

crate_time、update_time 自动填充

1. 自定义注解 AutoFile,用于标识需要进行公共字段自动填充的方法

2.自定义切面AutoFillAspect,统一拦截加入了AutoFil 注解的方法,通过反射为公共字段赋值

3.在mapper的方法上加入 AutoFill注解

未完成。。。

6. Redis

6.1 简介

Redis是一个基于内存的 key-value 结构数据库

  • 基于内存存储,读写性能高
  • 适合存储热点数据(热点商品,咨询,新闻)
  • 企业应用广泛

6.2 Redis下载与安装

6.2.1 Redis安装包分为 Windows 版和 Linux 版:

  • Windows版下载地址:https://github.com/microsoftarchive/redis/releases
  •  Linux版下载地址:Index of /releases/

redis属于绿色健康软件:解压就可以用:

目录结构:

redis.windows.conf      Redis配置文件

redis-cli.exe                 Redis运行文件

redis-server.exe           Redis服务端

6.3 Redis的使用

第一步:启动服务端,进入redis安装目录,执行如下命令行

D:\develop\redis>redis-server.exe redis.windows.conf

出现这个就启动成功:

第二步:启动客户端,链接redis服务端

D:\develop\redis>redis-cli.exe

出现这个则成功:

或者:可以配置端口和链接主机

D:\develop\redis>redis-cli.exe -h localhost -p 6379

6.4 Redis配置密码

打开:redis.windows.conf找到requirepass解除注释,修改值为密码,就完成了

客户端链接服务器命令:

D:\develop\redis>redis-cli.exe -h localhost -p 6379 -a 123456

6.5 Redis五种常用数据类型介绍

字符串 string、哈希 hash、列表 list、集合 set、有序集合 sorted set / zset

6.6 Redis 常用命令

6.6.1 Redis 字符串类型常用命令

  • SET key value                               设置指定key的值
  • GET key                                        获取指定key的值
  • SETEX key seconds value           设置指定key的值,并将 key 的过期时间设置为 seconds 秒
  • SETNX key value                          只有在 key 不存在时设置 key 的值

6.6.2 Redis 哈希操作命令

Redis hash 是一个string 类型的 field 和 value 的映射表,hash特别适合用于存储对象,常用命令:

  • HSET key field value     将哈希表 key 中的字段 field 的值设为 value
  • HGET key field              获取存储在哈希表中指定字段的值
  • HDEL key field               删除哈希表中所有字段
  • HKEYS key                    获取哈希表中所有字段
  • HVALS key                     获取哈希表中所有值

6.6.2 Redis 列表是简单的字符串列表,按照插入顺序排序,常用命令:

  • LPUSH key value1 [value2]         将一个或多个插入到列表头部
  • LRANGE key start stop                获取列表指定范围内的元素
  • RPOP key                                     移除并获取列表最后一个元素
  • LLEN key                                       获取列表长度

6.6.3 Redis

6.7 在Java中操作Redis

6.7.1 Spring Data Redis 使用方式

        1. 导入Spring Data Redis 的maven坐标  pom.xml

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

        2. 配置Redis数据源  application.yml

spring:redis:host: localhostport: 6379password: 123456database: 0 #指定数据库,不同的数据中数据是相互隔离的,默认不用配置

        3.编写配置类,创建RedisTemplate对象,在config下新建RedisConfiguration.java

@Configuration
@Slf4j
public class RedisConfiguration {//报错是编译器的原因@Beanpublic RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){log.info("开始创建redis模块对象...");RedisTemplate redisTemplate = new RedisTemplate();//设置redis链接工厂对象redisTemplate.setConnectionFactory(redisConnectionFactory);//设置redis key的序列化器redisTemplate.setKeySerializer(new StringRedisSerializer());return redisTemplate;}
}

        4.通过RedisTemplate对象操作Redis

@SpringBootTest
public class SpringDataRedisTest {@Autowiredprivate RedisTemplate redisTemplate;@Testpublic void testRedisTemplate(){System.out.println(redisTemplate);}
}

6.7.1 Spring Data Redis 操作字符串

6.7.2 Spring Data Redis 操作Hash

操作都是一样的。

6.8  店铺营业状态设计

最后:

小技巧:

查询:对于查询来说,Result最好写类型,但是对于增删改来说可以用泛型

使用Builder构造类:

在类上加上@

@Builder
public class Employee implements Serializable {

使用:

Employee employee = Employee.builder().id(id).status(status).build();

设置:请求bean名称

log.info("设置店铺营业状态:{}",status == 1?"营业中":"打样中");

配置两个文档:

@Beanpublic Docket docket1() {ApiInfo apiInfo = new ApiInfoBuilder().title("苍穹外卖项目接口文档").version("2.0").description("苍穹外卖项目接口文档").build();Docket docket = new Docket(DocumentationType.SWAGGER_2).groupName("管理员").apiInfo(apiInfo).select().apis(RequestHandlerSelectors.basePackage("com.takeaway.controller.admin")).paths(PathSelectors.any()).build();return docket;}@Beanpublic Docket docket2() {ApiInfo apiInfo = new ApiInfoBuilder().title("苍穹外卖项目接口文档").version("2.0").description("苍穹外卖项目接口文档").build();Docket docket = new Docket(DocumentationType.SWAGGER_2).groupName("用户").apiInfo(apiInfo).select().apis(RequestHandlerSelectors.basePackage("com.takeaway.controller.user")).paths(PathSelectors.any()).build();return docket;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/153382.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安全与隐私:直播购物App开发中的重要考虑因素

随着直播购物App的崭露头角&#xff0c;开发者需要特别关注安全性和隐私问题。本文将介绍在直播购物App开发中的一些重要安全和隐私考虑因素&#xff0c;并提供相关的代码示例。 1. 数据加密 在直播购物App中&#xff0c;用户的个人信息和支付信息是极为敏感的数据。为了保护…

忘记压缩包密码?解决方法一键找回,省时又便捷!

使用在线rar/zip解密工具&#xff0c;找回rar/zip密码并解密压缩包的方法非常简单。具体步骤如下&#xff1a;首先&#xff0c;在百度上搜索“密码帝官网”&#xff0c;这是一个专业的解密服务网站。然后&#xff0c;点击搜索结果中的链接&#xff0c;进入官网首页。在页面上方…

Netty(四)NIO-优化与源码

Netty优化与源码 1. 优化 1.1 扩展序列化算法 序列化&#xff0c;反序列化主要用于消息正文的转换。 序列化&#xff1a;将java对象转为要传输对象(byte[]或json&#xff0c;最终都是byte[]) 反序列化&#xff1a;将正文还原成java对象。 //java自带的序列化 // 反序列化 b…

【Java】微服务——Feign远程调用

目录 1.Feign替代RestTemplate1&#xff09;引入依赖2&#xff09;添加注解3&#xff09;编写Feign的客户端4&#xff09;测试5&#xff09;总结 2.自定义配置2.1.配置文件方式2.2.Java代码方式 3.Feign使用优化4.最佳实践4.1.继承方式4.2.抽取方式4.3.实现基于抽取的最佳实践1…

麻省理工学院与Meta AI共同开发StreamingLLM框架,实现语言模型无限处理长度

&#x1f989; AI新闻 &#x1f680; 麻省理工学院与Meta AI共同开发StreamingLLM框架&#xff0c;实现语言模型无限处理长度 摘要&#xff1a;麻省理工学院与Meta AI的研究人员联合研发了一款名为StreamingLLM的框架&#xff0c;解决了大语言模型在RAM与泛化问题上的挑战&am…

微信小程序 获取当前屏幕的可见高宽度

很多时候我们做一下逻辑 需要用整个窗口的高度或宽度参与计算 而且很多时候我们js中拿到的单位都是px像素点 没办法和rpx同流合污 官方提供了wx.getSystemInfoSync() 可以获取到部分窗口信息 其中就包括了整个窗口的宽度和高度 wx.getSystemInfoSync().windowHeight 返回值为像…

微店商品链接获取微店商品详情数据(用 Python实现微店商品信息抓取)

在网页抓取方面&#xff0c;可以使用 Python、Java 等编程语言编写程序&#xff0c;通过模拟 HTTP 请求&#xff0c;获取微店网站上的商品页面。在数据提取方面&#xff0c;可以使用正则表达式、XPath 等方式从 HTML 代码中提取出有用的信息。值得注意的是&#xff0c;微店网站…

[stm32]外中断控制灯光

在STM32CubeMX中配置外部中断功能和参数 1、将上拉输入的引脚设置为&#xff1a;GPIO_EXTI功能 2、GPIO模式设为下降沿触发外部中断&#xff0c;使能上拉电阻&#xff0c;用户标签 3、要将NVIC的相关中断勾选 只有将中断源进行勾选&#xff0c;相关的中断请求才能得到内核的…

xshell安装完成在windows不能打开

文章目录 问题描述问题排查解决第一步第二步 问题描述 安装打开xshell的时候总是点击没有任何的反应&#xff0c;重启电脑后再次点击xshell也没有任何的响应。只有在重装软件后才能正常打开。 问题排查 点击打开xshell7的时候总是报如下错 在这里能看到具体的描述&#xff…

高频时序数据仓库

天软课堂将在本周四添加新主题--天软超高频行情数据。针对市场上高频行情数据处理业务的相关痛点&#xff0c;直观的在线演示如何通过天软高频数仓及高性能计算能力&#xff0c;将其逐个击破&#xff0c;期待各位老师的参会。

Android攻城狮学鸿蒙-配置

1、config.json配置 鸿蒙中的config.json应该类似于Android开发中Manifest.xml&#xff0c;可以进行页面的配置。根据顺序&#xff0c;会识别启动应用的时候&#xff0c;要打开哪个界面。 2、 Ability详解&#xff0c;以及与Android的Activity对比。 他人的学习文章连接&…

奖品定制经营商城小程序的作用是什么

奖品是激励人员团体很好的方式&#xff0c;也是荣誉象征&#xff0c;奖牌、奖杯、高端礼盒等&#xff0c;同时市场中团体非常多&#xff0c;其需求也是很多&#xff0c;尤其定制方面&#xff0c;就更是不用说。 对奖品定制企业来说&#xff0c;除了线下门店获客经营外&#xf…

从零学算法(LCR 180)

文件组合.待传输文件被切分成多个部分&#xff0c;按照原排列顺序&#xff0c;每部分文件编号均为一个 正整数&#xff08;至少含有两个文件&#xff09;。传输要求为&#xff1a;连续文件编号总和为接收方指定数字 target 的所有文件。请返回所有符合该要求的文件传输组合列表…

Idea JavaWeb项目,继承自HttpFilter的过滤器,启动Tomcat时部署工件出错

JDK版本&#xff1a;1.8 Tomcat版本&#xff1a;8.5 10-Oct-2023 13:55:17.586 严重 [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.core.StandardContext.startInternal One or more Filters failed to start. Full details will be found in the appropriate conta…

【数据分类】基于麻雀搜索算法优化支持向量机的数据分类方法 SSA-SVM分类算法【Matlab代码#61】

文章目录 【可更换其他群智能算法&#xff0c;获取资源请见文章第6节&#xff1a;资源获取】1. 麻雀搜索算法&#xff08;SSA&#xff09;2. 支持向量机&#xff08;SVM&#xff09;3. SSA-SVM分类模型4. 部分代码展示5. 仿真结果展示6. 资源获取 【可更换其他群智能算法&#…

【ftp篇】 vsftp(ftp) 每天生成一个动态密码

这里写目录标题 前言为什么需要动态每日生成一个密码&#xff1f;编写脚本定时任务java对应的代码 前言 社长最近接到一个需求&#xff0c;需要ftp每天动态生成一个密码 为什么需要动态每日生成一个密码&#xff1f; 在软硬件通讯过程中&#xff0c;就以共享单车为例&#xff0…

6.Docker搭建RabbitMQ

1、端口开放 如果在云服务上部署需在安全组开通一下端口&#xff1a;15672、5672、25672、61613、1883。 15672(UI页面通信口)、5672(client端通信口)、25672(server间内部通信口)、61613(stomp 消息传输)、1883(MQTT消息队列遥测传输)。 2、安装镜像 docker pull rabbitmq 3、…

ARM:使用汇编完成三个灯流水亮灭

1.汇编源代码 .text .global _start _start: 设置GPIOF寄存器的时钟使能LDR R0,0X50000A28LDR R1,[R0]ORR R1,R1,#(0x1<<5)STR R1,[R0]设置GPIOE寄存器的时钟使能LDR R0,0X50000A28LDR R1,[R0] 从r0为起始地址的4字节数据取出放在R1ORR R1,R1,#(0x1<<4) 第4位设…

腾讯云2核4G服务器一年和三年价格性能测评

腾讯云轻量2核4G5M服务器&#xff1a;CPU内存流量带宽系统盘性能测评&#xff1a;轻量应用服务器2核4G5M带宽&#xff0c;免费500GB月流量&#xff0c;60GB系统盘SSD盘&#xff0c;5M带宽下载速度可达640KB/秒&#xff0c;流量超额按照0.8元每GB的价格支付流量费&#xff0c;轻…

使用docker搭建kafka集群、可视化操作台

单机搭建 1 拉取zookeeper镜像 docker pull wurstmeister/zookeeper 2 启动zookeeper容器 docker run -d --name zookeeper -p 2181:2181 -v /etc/localtime:/etc/localtime wurstmeister/zookeeper 3 拉取kafka镜像 docker pull wurstmeister/kafka 4 启动kafka镜像 docker…