瑞吉外卖
这个实战项目是我目前做的最完整的一个项目,前前后后做了两遍,感觉从中可以学到不少的东西,以前做的项目一般都是不做笔记的,但是感觉不做笔记就会感觉空落落的,这次会完整记录下本次项目的开发过程,供自己学习参考。
Day1
软件开发整体介绍
软件开发流程

作为软件开发人员,我们的侧重点主要在编码上,也就是第三个阶段,但是一些小公司需要我们的能力更强一些,不局限于某一个步骤,也就是俗话说的脏话累活都我们干
角色分工

这是规范的,但是小公司的话可能大部分角色都得我们自己担任
软件环境
分为开发环境,测试环境,生产环境

瑞吉外卖项目介绍
项目介绍
本项目(瑞吉外卖)是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括系统管理后台和移动端应用两部分。其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的菜品、套餐、订单等进行管理维护。移动端应用主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单等。
本项目共分为3期进行开发:
第一期主要实现基本需求,其中移动端应用通过H5实现,用户可以通过手机浏览器访问。
第二期主要针对移动端应用进行改进,使用微信小程序实现,用户使用起来更加方便。
第三期主要针对系统进行优化升级,提高系统的访问性能。
产品原型展示
产品原型,就是一款产品成型之前的一个简单的框架,就是将页面的排版布局展现出来,使产品的初步构思有一个可视化的展示。通过原型展示,可以更加直观的了解项目的需求和提供的功能。
产品原型是项目经理在需求分析时期进行制作的
注意:产品原型主要用于展示项目的功能,并不是最终的页面效果。
大概就是这个样子

技术选型

功能架构

角色

开发环境搭建
- 创建瑞吉外卖数据库(课程资料里面有)
 

对于这些表的解释
菜品和套餐分类指的是荤菜、素菜、儿童套餐…..等等
每一个菜里面都有菜品的id
每一个套餐里面都有对应的套餐id

- Maven项目搭建
 
新建一个Maven项目
在这里需要注意的是,每一次创建Maven项目,都要先检查Maven仓库的地址,以及JDK的版本,以及JRE的版本,避免未知的错误



==JDK和SDK的区别==
SDK是Software Development Kit的缩写,中文意思是“软件开发工具包”。这是一个覆盖面相当广泛的名词,可以这么说:辅助开发某一类软件的相关文档、范例和工具的集合都可以叫做“SDK”。SDK是一系列文件的组合,它为软件的开发提供一个平台(它为软件开发使用各种API提供便利)。
JDK(Java Development Kit)是Sun Microsystems针对Java开发员的产品。自从Java推出以来,JDK已经成为使用最广泛的Java SDK(Software development kit)。
导入pom文件(在项目资料里面可以直接导入)
pom里面的基本上都是springboot的依赖以及之前需要用到的依赖
之后导入yml配置文件(也是从项目资料里面直接导入)
里面可以修改数据源里面的数据库账号密码

之后配置启动类
@Slf4j是打印日志的注解,属于Lombok技术(就是那个提供get set方法的注解的技术)
加了这个注解就可以使用这个方法



对于@SpringBootApplication的理解可以看我Springboot的博客
之后可以把前端页面导入(从项目资料中可以copy)

但是我们会发现一个问题,如果访问localhost:8080/页面地址,是访问不到的,这是为什么呢,这是因为默认用户是不可以访问resources下面的东西的,要不然服务器就会不安全了
所以为了能够访问下面的资源,我们需要编写一个配置类来配置MVC框架资源的映射,需要注意的是继承了WebMvcConfigurationSupport,重写了拦截器方法

之后就可以访问前端页面了
后台登录功能开发
需求分析
通过前端发axios请求,请求到后端进行登录验证,之后如果返回码为1,则说明登录成功,之后把user的信息保存到浏览器中,then前端跳转到首页,否则就发送一个错误显示的效果
这个是user信息封装到浏览器的格式

需要注意的是这个:

代码开发
- 实现登录功能,首先我们要导入与数据库相对应的实体类employee
 

都是和数据库字段进行一一对应的
需要注意的是java的明明规则是驼峰,但是mysql是用(下划线)_进行分割的,但是之前由于我们已经配置过mp的驼峰命名法映射,现在已经可以进行映射了(这个很方便!)
之后我们创建mapper(利用mp)

之后创建Service和ServiceImpl


导入返回结果类R(从课程文件中导入)
此类是一个通用结果类,也就是说服务端响应的所有结果最终都会包装成这种类型的数据返回给前端
之后前端就可以根据这个进行解析了(这个就不用转换成json了,因为我们已经在controller类上加了@RestController注解,spring会自动给我们把数据封装成json)
public class R<T> {
private Integer code; //编码:1成功,0和其它数字为失败
private String msg; //错误信息
private T data; //数据
private Map map = new HashMap(); //动态数据,(几乎不用)
public static <T> R<T> success(T object) {//响应成功的时候返回
R<T> r = new R<T>();
r.data = object;
r.code = 1;
return r;
}
public static <T> R<T> error(String msg) {//响应失败的时候返回
R r = new R();
r.msg = msg;
r.code = 0;
return r;
}
public R<T> add(String key, Object value) {//操作上面的map,用到了再说
this.map.put(key, value);
return this;
}
}之后创建controller
由于前端发送的请求为:

根据这些我们可以编写controller
由于前端发回的信息是json数据
我们利用mp进行封装信息,发回的是employee,用@RequestBody注解声明是json数据
其中涉及了一个md5加密的方法md5DigestAsHex,这个会用就可以了
  | 
之前我们说到的利用浏览器存储信息可以在这里查看

后台退出功能开发
需求分析
用户点击页面中退出按钮,发送请求,请求地址为/employee/logout,请求方式为POST。我们只需要在Controller中创建对应的处理方法即可,具体的处理逻辑:
1、清理Session中的用户id
2、返回结果
代码开发
/**  | 
至于浏览器存储的数据userinfor,则被前端给清除掉了,不需要我们关心
Day2
这一章主要讲的是员工管理业务开发
完善登录功能
***** 自定义过滤器的使用,用来过滤未登录的用户以及登录过用户的放行  | 
问题分析
我们发现,如果直接进入index页面,不登陆仍然可以进入,这样会导致系统不安全,所以我们要对登录功能进行完善
我们希望看到的效果应该是,只有登录成功后才可以访问系统中的页面,如果没有登录则跳转到登录页面。
那么,具体应该怎么实现呢?
答案就是使用过滤器(Web)或者拦截器(SpringMVC),在过滤器或者拦截器中判断用户是否已经完成登录,如果没有登录则跳转到登录页面。
实现步骤:
1、创建自定义过滤器LoginCheckFilter
2、在启动类上加入注解@ServletComponentScan
3、完善过滤器的处理逻辑
代码实现
实现Filter接口,再实现相应的方法
关于AntPathMatcher这个路径匹配器,会用就可以了
并且,本次请求的URI也可以通过HttpServletRequest的API来获取
这个代码要看
/**  | 
除了编写过滤器类,还要在启动类上加上@ServletComponentScan注解
关于为什么我们需要@ServletComponentScan?问题出在嵌入式Servlet容器中。
由于嵌入式容器不支持@WebServlet,@WebFilter(就是声明过滤器的那个注解)和@WebListener注释,Spring Boot在很大程度上依赖于嵌入式容器,因此引入了新的注释@ServletComponentScan来支持一些使用这3个注释的从属jar

新增员工
***** 可以看一下这个方法的全局异常捕获  | 
需求分析

数据模型
新增员工,其实就是将我们新增页面录入的员工数据插入到employee表。需要注意,employee表中对username字段加入了唯一约束,因为username是员工的登录账号,必须是唯一的

代码开发
根据前端请求写代码

在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端
2、服务端Controller接收页面提交的数据并调用Service将数据进行保存
3、Service调用Mapper操作数据库,保存数据
下面是代码实现
编写EmployeeController中的方法(mp底层已经帮我们写好了save方法)
/**  | 
但是如果我们添加一个账号相同的用户,会报错,抛异常
所以我们要进行异常的处理
通常有两种方式:
1、在Controller方法中加入try、catch进行异常捕获(这种方法如果代码里多的话就会显得很臃肿)
2、使用异常处理器进行全局异常捕获
全局异常捕获
基本原理是AOP代理
类上的@ControllerAdvice注解是Spring3.2中新增的注解,学名是Controller增强器,作用是给Controller控制器添加统一的操作或处理。我们熟知的作用是做全局异常的处理
方法上的@ExceptionHandler可以用来统一处理方法抛出的异常,可以添加参数,参数是某个异常类的class,代表这个方法专门处理该类异常
/**  | 
员工信息分页查询
***** MP的分页插件的运用  | 
需求分析
系统中的员工很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。
代码开发
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端
2、服务端Controller接收页面提交的数据并调用Service查询数据
3、Service调用Mapper操作数据库,查询分页数据
4、Controller将查询到的分页数据响应给页面
5、页面接收到分页数据并通过ElementUI的Table组件展示到页面上
看前端发的请求写后端代码

需要注意的是可以看到前端页面发到后端的数据并不是以url的格式,而是json的格式,但是前端有一个全局的拦截器对数据进行了处理(只有get请求才会进行处理),封装成了url格式,之后才以url的形式(组装)发送到后端(这个并不是重点,是前端的东西)

==下面开始写后端的东西==
配置分页插件
/**
* 配置MP的分页插件,至于为什么要返回这个bean,要问mp的开发者了
*/
public class MybatisPlusConfig {
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}创建controller方法
这里需要注意的是springMVC为我们把url里面的参数直接封装到了变量里面
并且根据前端的代码可知,我们需要返回值是一个Page对象(这个对象是MP给我们封装的)

这个代码值得一看,因为MP很机智的想到有的参数可能为空,很巧妙的简化了代码
/**  | 
启用/禁用员工账号
***** 这个可以看看为什么前端js只能精确保存前16位,如果出现了这种情况怎么改变转换器  | 
需求分析
在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。
账号禁用的员工不能登录系统,启用后的员工可以正常登录。(这个已经实现了)
需要注意,只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示。(前端实现)
代码开发
需要注意的是:
页面中是怎么做到只有管理员admin能够看到启用、禁用按钮的?
因为前端根据后端发送的数据(存在了浏览器中)做了判断
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将参数(id. status)提交到服务端,(如果是正常就传0,如果是禁用就传1)
2、服务端Controller接收页面提交的数据并调用Service更新数据
3、Service调用Mapper操作数据库
启用、禁用员工账号,本质上就是一个更新操作,也就是对status状态字段进行操作在Controller中创建update方法,此方法是一个通用的修改员工信息的方法
此外,在下面的编辑员工信息也是更新操作,和这个是同一个类型,可以进行代码复用
/**  | 
之后我们在测试中发现
前端在浏览器中存储的数据的精度不够准确(js只能保证数字前16位是精确的),所以发送给后端的id不正确,进而导致更新失败,怎么解决这种问题呢
==我们可以在服务端给页面响应json数据时进行处理,将long型数据统一转为String字符串==
代码修复
具体实现步骤:
1)提供对象转换器JacksonobjectMapper,基于Jackson进行Java对象到json数据的转换(资料中已经提供,直接复制到项目中使用)
2)在WebMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到json数据的转换(如果不改是使用的SpringMVC默认的消息转换器)
- 添加JacksonObjectMapper(这个直接用就行)
 
/**  | 
- 配置WebMvcConfig
 
下面这个转换器的作用就是spring把R对象封装成JSON字符串的策略,也就是我们加RestController所做的操作(虽然这个操作是spring帮我们做的,但是我们仍然可以手动干预,手动干预也就是可以添加我们自己的转换器并加入进去)

配置的转换器是有顺序的,下标是0表示我们把我们自己写的转换器排到了最前面
之后就会发现后端返回前端的long型数据变成了string型数据
编辑员工信息
*** 可以看一看数据回显  | 
需求分析
在员工管理列表页面点击编辑按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击保存按钮完成编辑操作
在开发代码之前需要梳理一下操作过程和对应的程序的执行流程:
1、点击编辑按钮时,页面跳转到add.html(add是一个公共页面,添加和修改都能用这个页面),并在url中携带参数[员工id]

2、在add.html页面获取url中的参数[员工id] (前端的活)
3、发送ajax请求,把员工id参数请求给后端,
4、服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面
5、页面接收服务端响应的json数据,通过Vue的数据绑定进行员工信息回显
6、点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端
7、服务端接收员工信息,并进行处理,完成后给页面响应
8、页面接收到服务端响应信息后进行相应处理
上面标黑的是后端需要进行的处理,其中7我们在 启用/禁用员工账号 的章节已经写过了(原理是更新此用户)
代码开发
是一个简单的查询(可以不用看)
如果忘记了@PathVariable的使用可以看看,这个是spring的注解
@PathVariable是spring3.0的一个新功能:接收请求路径中占位符的值
通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx”) 绑定到操作方法的入参中。
一般和GetMapping…xxx等联合使用,因为这个id值不是固定的,是路径变量
/**  | 
Day3
分类管理业务

公共字段自动填充
***** 这个要看,有多个公共字段,并且处理方法一致可以这样做  | 
这个功能是由MP进行的技术支持
问题分析
前面我们已经完成了后台系统的员工管理功能开发,在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工时需要设置修改时间和修改人等字段。这些字段属于公共字段,也就是很多表中都有这些字段,如下:

能不能对于这些公共字段在某个地方统一处理,来简化开发呢?
客案就是使用Mybatis Plus提供的==公共字段自动填充==功能。
Mybatis Plus公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。
实现步骤:
1、在实体类的属性上加入@TableField注解,指定自动填充的策略
2、按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口
代码实现
- 添加@TableField注解
 
//插入时填充字段  | 
编写元数据对象处理器,实现MetaObjectHandler接口
但是出现了新的问题,没有办法获取更新用户的id
之前我们把用户id存在了httpsession中,但是我们在MyMetaObjecthandler类中是得不到httpsession对象的,所以我们需要通过其他方式来获取登录用户id
我们可以使用ThreadLocal来解决此问题,它是JDK中提供的一个类

什么是ThreadLocal?
ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLoeal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
ThreadLocal常用方法:
public void set(T value) //设置当前线程的线程局部变量的值  | 
我们可以在LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值〔用户id)。
所以我们由此可以得到实现步骤
1,编写BaseContext工具类,基于ThreadLecal封装的工具类
2、在LogincheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
3、在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id
- 编写BaseContext
 
/**  | 
- 在LoginCheckFilter中设置id(可以看“完善登录功能的代码”)
 

- 使用利用BaseContext取出id
 
/**  | 
新增分类
** 就是一个简单的增加操作  | 
需求分析
后台系统中可以管理分类信息,分类包括两种类型,分别是菜品分类和套餐分类。
当我们在后台系统中添加菜品时需要选择一个菜品分类,
当我们在后台系统中添加一个套餐时需要选择一个套餐分类
在移动端也会按照菜品分类和套餐分类来展示对应的菜品和套餐。

排序的意思是根据序号进行靠前排列或者靠后排列(置顶…)
数据模型
数据模型就是category(种类/分类)

需要注意的是name是unique的
代码开发
在开发业务功能前,先将需要用到的类和接口基本结构创建好:
实体类Category(直接从课程资料中导入即可)
Mapper接口CategoryMapper
业务层接口CategoryService
业务层实现类CategoryServicelmpl
控制层CategoryController
上面的这些和employee的差不多,就不一一罗列了,可以查看源码
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面(backend/page/category/list.html 这个页面)发送ajax请求,将新增分类窗口输入的数据以json形式提交到服务端
2、服务端Controller接收页面提交的数据并调用Service将数据进行保存
3、Service调用Mapper操作数据库,保存数据
可以看到新增菜品分类和新增套餐分类请求的服务端地址和提交的json数据结构相同,并且两种类型的数据都在同一张表中存储,所以服务端只需要提供一个方法统一处理即可
  | 
分类信息分页查询
** 和employee的分页差不多(Day2的)  | 
需求分析
系统中的分类很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。
代码开发
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将分页查询参数(page. pageSize)提交到服务端
2、服务端Controller接收页面提交的数据并调用Service查询数据
3、Service调用Mapper操作数据库,查询分页数据
4、Controller将查询到的分页数据响应给页面
5、页面接收到分页数据并通过ElementUI的Table组件展示到页面上

  | 
删除分类
**** 自定义异常以及删除的安全性校验  | 
需求分析
在分类管理列表页面,可以对某个分类进行删除操作。需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除。
代码开发
在开发代码之前,需要梳理一下整个程序(基础版本)的执行过程:
1、页面发送ajax请求,将参数id提交到服务端
2、服务端Controller接收页面提交的数据并调用Service删除数据
3、Service调用Mapper操作数据库
这个版本没有考虑这句话,会在功能完善中实现这个功能
/**  | 
功能完善
前面我们已经实现了根据id删除分类的功能,但是并没有检查删除的分类是否关联了菜品或者套餐,所以我们需要进行功能完善。
要完善分类删除功能,需要先准备基础的类和接口:
1、实体类Dish和Setmeal(从课程资料中复制即可)
2、Mapper接口DishMapper和SetmealMapper
3、Service接口DishService和SetmealService
4、 Service实现类DishServicelmpl和SetmealServicelmpl
其次,如果删除的分类是否关联了菜品或者套餐,我们需要抛出一个异常
所以我们定义CustomException
/**  | 
我们还要在全局的异常处理器中处理这种异常(下面的代码是写在类GlobalExceptionHandler(全局异常处理器)中的)
/**  | 
下面我们可以写service的方法了
  | 
修改分类
** 只有简单的update  | 
需求分析
在分类管理列表页面点击修改按钮,弹出修改窗口,在修改窗口回显分类信息并进行修改,最后点击确定按钮完成修改操作
这个修改可以通过前端来进行数据回显(和上面的那个不太一样)
代码开发
/**  | 
Day4
菜品管理业务功能
文件上传下载
***** 可以单独拿出来自己做一个小实验,完成一个这样的操作,了解文件上传和下载,以及图片回显的原理  | 
文件上传介绍
文件上传,也称为upload,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。
文件上传时,对页面的form表单有如下要求:
- method=”post” 采用post方式提交数据
 - enctype=”multipart/form-data” 采用multipart格式上传文件
 - type=”file”                                                  使用input的file控件上传
举例: 
<form method="post" action="/common/upload" enctype="multipart/form-data">  | 
上面是前端的工作,下面介绍后端服务器需要做的工作
服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:
commons-fileupload
commons-io
Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端代码,我们只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件,例如:
下面这个参数里面的file表示上传上来的文件

文件下载介绍
文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程。
通过浏览器进行文件下载,通常有两种表现形式:
以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
直接在浏览器中打开
通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。
文件上传代码实现
每上传一个文件
前端就会向后端发一次请求

需要注意的是

这个file要和上面图片的Form Data 中的name属性的值相同
并且file这个变量是个临时文件,如果不进行持久化保存,本方法执行完毕后临时文件就会被清除!!
其中的API需要解释的是
file.transferTo(File)将临时文件转存到指定位置
/**  | 
文件下载代码实现
其实我一直不理解,为什么要一直向服务端一直写文件(可能前端做了控制),发送的数据怎么知道这是一整个的呢(留下悬念)

/**  | 
新增菜品
*****这个操作了多张表 有些许复杂,并且用到了事务控制,挺经常用的  | 
需求分析
后台系统中可以管理菜品信息,通过新增功能来添加一个新的菜品,在添加菜品时需要选择当前菜品所属的菜品分类,并且需要上传菜品图片
移动端会按照菜品分类来展示对应的菜品信息。

数据模型
新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flavor表插入数据。所以在新增菜品时,涉及到两个表:
- dish 菜品表
 - dish_flavor 菜品口味表
 
dish表

dish_flavor表

其中dish_flavor的表数据是这样的

代码开发
在开发业务功能前,先将需要用到的类和接口基本结构创建好:
实体类 DishFlavor(直接从课程资料中导入即可,Dish实体前面课程中已经导入过了)
Mapper接口DishFlavorMapper
业务层接口DishFlavorService
业务层实现类DishFlavorServicelmpl
控制层 DishController
在开发代码之前,需要梳理一下新增菜品时前端页面和服务端的交互过程:
1、页面(backend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中(数据回显)

2、页面发送请求进行图片上传,请求服务端将图片保存到服务器(上面写过了)
3、页面发送请求进行图片下载,将上传的图片进行回显(上面写过了)
4、点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端
开发新增菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。
- 编写controller回显菜品分类数据
 
下面的代码是写在categorecontroller里面的
/**  | 
- 编写DishController
 

普通的属性Dish中都有,但是没有flavors这个属性,所以我们可以做一个DTO来接收
DTO,全称为Data Transfer Object,即数据传输对象,一般用于展示层与服务层之间的数据传输。

categoryName和copies目前没有用到,等用到了再解释
/**  | 
其中的DishServiceImpl 中的saveWithFlavor
/**  | 
加入事务控制
我们一般在Service层中加入事务控制
具体加入事务的方法是
- 在需要添加事务的方法上添加@Transactional注解
 - 在启动类上添加@EnableTransactionManagement
 
菜品信息分页查询
**** 进行了两张表的复杂业务查询,可以看看  | 
需求分析
系统中的菜品数据很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。

上图的分页查询和之前做的两个不太一样:
图片要下载
菜品表里面只保存了菜品分类的id,如果想知道菜品分类的名称还要根据id到数据库中去查询
代码开发
在开发代码之前,需要梳理一下菜品分页查询时前端页面和服务端的交互过程:
1、页面(backend/page/food/list.html)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据,(之前做过)

2、页面发送请求,请求服务端进行图片下载,用于页面图片展示
开发菜品信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。
下面的代码是写在DishController中的
/**  | 
修改菜品
*** 这个依然是查询了两个表,但是有了上面的基础,这个就不是很困难了  | 
(其实查询两个表有时候并不需要,可以增加一个冗余字段,这也算一个巧妙的设计)
需求分析
在菜品管理列表页面点击修改按钮,跳转到修改菜品页面,在修改页面回显菜品相关信息并进行修改,最后点击确定按钮完成修改操作
代码开发
在开发代码之前,需要梳理一f下修改菜品时前端页面(add.html)和服务端的交互过程:
1、页面发送ajax请求,请求服务端获取分类数据,用于菜品分类下拉框中数据展示(实现过了)
2、页面发送ajax请求,请求服务端,根据id查询当前菜品信息,用于菜品信息回显
3、页面发送请求,请求服务端进行图片下载,用于图片回显(实现过了)
4、点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端
开发修改菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。
我们首先实现2. 实现数据回显功能
下面的代码是写在类DishController中的
/**  | 
下面的代码是写在DishServiceImpl中的 ,其中, getByIdWithFlavor方法是我们扩展的,在下面
/**  | 
再实现 4. 提交数据
以下代码写在DishController
/**  | 
以下代码写在DishServiceImpl
  | 
Day5
套餐管理业务开发
新增套餐
*** 没什么亮点,注意好表间的关系就可以了  | 
需求分析
套餐就是菜品的集合。
后台系统中可以管理套餐信息,通过新增套餐功能来添加一个新的套餐,在添加套餐时需要选择当前套餐所属的套餐分类和包含的菜品,并且需要上传套餐对应的图片,在移动端会按照套餐分类来展示对应的套餐。
数据模型
新增套餐,其实就是将新增页面录入的套餐信息插入到setmeal表,还需要向setmeal_dish表插入套餐和菜品关联数据。所以在新增套餐时,涉及到两个表:
- setmeal 套餐表
 - setmeal_dish 套餐菜品关系表
 


代码开发
在开发业务功能前,先将需要用到的类和接口基本结构创建好:
- 实体类SetmealDish(直接从课程资料中导入即可,Setmeal实体前面课程中已经导入过了
 - DTO SetmealDto(直接从课程资料中导入即可)
 - Mapper接口SetmealDishMapper
 - 业务层接口SetmealDishService
 - 业务层实现类SetmealDishServicelmpl
 - 控制层SetmealController(因为操作的主表是Setmeal)
 
代码开发-梳理交互过程
在开发代码之前,需要梳理一下新增套餐时前端页面和服务端的交互过程:
1、页面(backend/page/combo/add.html)发送ajax请求,请求服务端获取套餐分类数据并展示到下拉框中(已经做过了,在菜品管理的时候)
2、页面发送ajax请求,请求服务端获取菜品分类数据(湘菜/川菜…)并展示到添加菜品窗口中(已经做过了,查询的是菜品的分类)
3、*页面发送ajax请求,请求服务端,根据菜品分类查询对应的菜品数据并展示到添加菜品窗口中(比如说湘菜下面有什么菜)

4、页面发送请求进行图片上传,请求服务端将图片保存到服务器(写过)
5、页面发送请求进行图片下载,将上传的图片进行回显(写过)
6、*点击保存按钮,发送ajax请求,将套餐相关数据以json形式提交到服务端
开发新增套餐功能,其实就是在服务端编写代码去处理前端页面发送的这6次请求即可。
首先来写3. 
请求发过来的参数是 CategoryId,我们仍然可以用dish来接收,因为dish里面有CategoryId字段,至于为什么url里面的CategoryId参数可以封装到dish里面,这是mybatis为我们做的封装
/**  | 
再来写6.

下面的代码是写在SetmealController中的
  | 
下面的代码是写在SetmealServiceImpl中的
/**  | 
套餐信息分页查询
** 和之前的分页差不多,就是逻辑复杂了一些  | 
需求分析
系统中的套餐数据很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。
代码开发
在开发代码之前,需要梳理一下套餐分页查询时前端页面和服务端的交互过程:
1、页面(backend/page/combo/list.htmi)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据
2、页面发送请求,请求服务端进行图片下载,用于页面图片展示(写过了)
开发套餐信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

/**  | 
删除套餐
***** 批量删除的用法  | 
需求分析
在套餐管理列表页面点击删除按钮,可以删除对应的套餐信息。
也可以通过复选框选择多个套餐,点击批量删除按钮一次删除多个套餐。注意,对于状态为售卖中的套餐不能删除,需要先停售,然后才能删除。
代码开发
在开发代码之前,需要梳理一下删除套餐时前端页面和服务端的交互过程:
1、删除单个套餐时,页面发送ajax请求,根据套餐id删除对应套餐

2、删除多个套餐时,页面发送ajax请求,根据提交的多个套餐id删除对应套餐

开发删除套餐功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。
观察删除单个套餐和批量删除套餐的请求信息可以发现,两种请求的地址和请求方式都是相同的,不同的则是传递的id个数,所以在服务端可以提供一个方法来统一处理。
==可以用一个集合来接收数据,这样的话信息不管是一个还是多个都可以接收到==
下面的代码写在SetmealController
/**  | 
下面的代码写在SetmealServiceImpl
/**  | 
从这里开始开发移动端———
手机验证码登录
短信发送
短信服务介绍
目前市面上有很多第三方提供的短信服务,这些第三方短信服务会和各个运营商(移动、联通、电信)对接,我们只需要注册成为会员并且按照提供的开发文档进行调用就可以发送短信。需要说明的是,这些短信服务一般都是收费服务。
常用短信服务:
- 阿里云
 - 华为云
 - 腾讯云
 - 京东
 - 梦网
 - 乐信
 
阿里云短信服务
阿里云短信服务(Short Message Service)是广大企业客户快速触达手机用户所优选使用的通信能力。调用API或用群发助手,即可发送验证码、通知类和营销类短信;国内验证短信秒级触达,到达率最高可达99%;国际/港澳台短信覆盖200多个国家和地区,安全稳定,广受出海企业选用。
应用场景:
- 验证码
 - 短信通知
 - 推广短信
 

在阿里云使用短信服务
找到短信服务如果没有注册账号得先注册帐号
我们需要先设置短信签名
短信签名是短信发送者的署名,表示发送方的身份。【阿里云】/【菜鸟裹裹】。。。。(但是我们现在没有条件申请这个,因为条件很苛刻)
设置短信模板,就是上图签名管理的旁边有模板管理
短信模板包括短信发送内容、场景、变量信息

(这个其实也需要申请审核,条件刻苦…)
- 设置AccessKey(在头像上)
 
之后创建子用户AccessKey

之后会得到用户的账号和密码

代码开发
- 导入Maven坐标
 - 调用API
 
会提供一个工具类(直接用就行,到时候把AccessKeyID和密码填进去就可以了)
手机验证码登录
需求分析
为了方便用户登录,移动端通常都会提供通过手机验证码登录的功能。
手机验证码登录的优点:
方便快捷,无需注册,直接登录
使用短信验证码作为登录凭证,无需记忆密码
安全
登录流程:(用户端)
输入手机号>获取验证码>输入验证码>点击登录>登录成功
注意:通过手机验证码登录,手机号是区分不同用户的标识。
数据模型
通过手机验证码登录时,涉及的表为user表,即用户表。结构如下:

代码开发
在开发代码之前,需要梳理一下登录时前端页面和服务端的交互过程:
1、在登录页面(front/page/login.html)输入手机号,点击【获取验证码】按钮,页面发送ajax请求,在服务端调用短信服务API给指定手机号发送验证码短信
2、在登录页面输入验证码,点击【登录】按钮,发送ajax请求,在服务端处理登录请求
开发手机验证码登录功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。
在开发业务功能前,先将需要用到的类和接口基本结构创建好:
- 实体类User(直接从课程资料中导入即可)
 - Mapper接口UserMapper
 - 业务层接口UserService
 - 业务层实现类Userservicelmpl
 - 控制层UserController
 - 工具类SMSutils(直接从课程资料中导入即可)
 - validateCodeutils(直接从课程资料中导入即可)
 
首先对移动端的登录页面也不需要拦截
下面的代码是写在LoginCheckFilter中的

并且要在LoginCheckFilter加入拓展逻辑,判断移动端用户的登陆状态
这个和客户端很像,直接改就行
//判断登录状态,如果已登录,则直接放行  | 
发送验证码的controller
其中的ValidateCodeUtils.generateValidateCode()(会用就行其中方法参数的数字是代表的生成几位的验证码)
  | 
移动端收到 ,填写验证码之后点确定会发送另外一个请求


拓展:
Request Payload更准确的说是http request的payload body。一般用在数据通过POST请求或者PUT请求。它是HTTP请求中空行的后面那部分。(PS:这里涉及一个http常被问到的问题,http请求由哪几部分组成,一般是请求行,请求头,空行,请求体。payload body应该是对应请求体。)
下面的代码是写在UserController中的
/**  | 
Day6
菜品展示、购物车、下单
导入用户地址簿相关功能代码
* 不是自己写的 可以选看  | 
需求分析
地址簿,指的是移动端消费者用户的地址信息,用户登录成功后可以维护自己的地址信息。同一个用户可以有多个垃
址信息,但是只能有一个默认地址。

数据模型
用户的地址信息会存储在address_book表,即地址簿表中。具体表结构如下:

只有打勾的是必填的,其他的是可选的字段
导入功能代码
功能代码清单:
- 实体类AddressBook(直接从课程资料中导入即可)
 - Mapper接口AddressBookMapper
 - 业务层接口AddressBookService
 - 业务层实现类AddressBookServicelmpl
 - 控制层AddressBookController(直接从课程资料中导入即可)
 
菜品展示
保留  | 
需求分析

代码开发
代码开发-梳理交互过程
在开发代码之前,需要梳理一下前端页面和服务端的交互过程:
1、页面(front/index.html)发送ajax请求,获取分类数据(菜品分类和套餐分类)(我们之前写过这个)
2、页面发送ajax请求,获取第一个分类下的菜品或者套餐(我们之前写过这个)
开发菜品展示功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

注意:首页加载完成后,还发送了一次ajax请求用于加载购物车数据,此处可以将这次请求的地址暂时修改一下,从静态json文件获取数据,等后续开发购物车功能时再修改回来,如下:

我们要求,如果有口味数据的话,就把这个按钮改成选择规格,但是其实我们后端返回的数据有问题,需要修改

  | 
购物车
这个业务有些复杂 但是技术并没有什么亮眼的,可以跳过  | 
需求分析
移动端用户可以将菜品或者套餐添加到购物车。对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车;对于套餐来说,可以直接点击+将当前套餐加入购物车。在购物车中可以修改菜品和套餐的数量,也可以清空购物车。
数据模型
购物车对应的数据表为shopping_cart表,具体表结构如下:

代码开发
在开发代码之前,需要梳理一下购物车操作时前端页面和服务端的交互过程:
1、点击加入购物车或者+按钮,页面发送ajax请求,请求服务端,将菜品或者套餐添加到购物车
2、点击购物车图标,页面发送ajax请求,请求服务端查询购物车中的菜品和套餐
3、点击清空购物车按钮,页面发送ajax请求,请求服务端来执行清空购物车操作
开发购物车功能,其实就是在服务端编写代码去处理前端页面发送的这3次请求即可。
添加进购物车

  | 
展示购物车列表
下面的代码写在ShoppingCartController
/**  | 
清空购物车
/**  | 
用户下单
这个业务有些复杂 但是技术并没有什么亮眼的,可以跳过  | 
需求分析
移动端用户将菜品或者套餐加入购物车后,可以点击购物车中的 去结算按钮,页面跳转到订单确认页面,点击 去支付 按钮完成下单操作
仅仅是把订单保存在数据库里面
数据模型
用户下单业务对应的数据表为orders表和order_detail表:
orders:订单表
order_detail:订单明细表
orders

order_detail

代码开发
在开发代码之前,需要梳理一下用户下单操作时前端页面和服务端的交互过程:
1、在购物车中点击去结算按钮,页面跳转到订单确认页面
2、在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的默认地址
3、在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的购物车数据
4、在订单确认页面点击去支付按钮,发送ajax请求,请求服务端完成下单操作(这个需要我们再写一个方法)
开发用户下单功能,其实就是在服务端编写代码去处理前端页面发送的请求即可。


你可能会问,为什么只传这三个参数就可以了呢,因为大部分参数都根据id存储进了数据库,同样的,我们可以根据id到数据库中取出,再赋进orders里面
在开发业务功能前,先将需要用到的类和接口基本结构创建好:
- 实体类Orders、OrderDetail(直接从课程资料中导入即可)
 - Mapper接口OrderMapper.OrderDetailMapper
 - 业务层接口OrderService.OrderDetailService
 - 业务层实现类OrderServicelmpl.OrderDetailServicelmpl
 - 控制层OrderController、OrderDetailController.
 
编写OrderController层的方法
  | 
编写OrderServiceImpl
需要解释的是里面的
这个类是mp给我们提供的,供给生成一个id
  | 
Day7
项目部署
手工部署项目
- 在IDEA中开发SpringBoot项目并打成jar包
 


- 将jar包上传到Linux服务器
 
mkdir /usr/local/app #创建目录,将项目jar包放到此目录  | 
- 启动程序
 

检查防火墙 确保需要的端口对外开放
改为后台运行SpringBoot程序,并将日志输出到日志文件目前程序运行的问题
- 线上程序不会采用控制台霸屏的形式运行程序,而是将程序在后台运行
 - 线上程序不会将日志输出到控制台,而是输出到日志文件,方便运维查阅信息
 
nohup 命令:英文全称 no hang up(不挂起),用于不挂断地运行指定命令,退出终端不会影响程序的运行语法格式:
nohup Command [ Arg ...][&]
参数说明:
Command:要执行的命令
Arg:一些参数,可以指定输出文件
&:让命令在后台运行
举例:
nohup java -jar boot工程.jar &> hello.log & #后台运行java -jar命令,并将日志输出到hello.log文件则此例就可写为

停止程序

通过Shell脚本自动部署项目
操作步骤:
1、在Linux中安装Git,并且把代码上传到托管平台,这样才能自动从托管平台上拉取代码
2、在Linux中安装maven
3、编写Shell脚本(拉取代码、编译、打包、启动
4、为用户授予执行Shell脚本的权限
5、执行Shell脚本
具体步骤
clone代码

导入shell脚本(这个脚本能大概看懂就可以)
在/usr/local/sh下创建bootStart.sh
!/bin/sh  | 
- 为用户授权
 

- 执行脚本
 
在sh目录下执行  | 
- 设置静态IP
 

- 重启网络服务
 
systemctl restart network  | 
Redis基础
在Linux系统安装Redis步骤:
1.将Redis安装包上传到Linux
2.解压安装包,命令: tar -zxvf redis-4.0.0.tar.gz-C/usr/local
3.安装Redis的依赖环境gcc,命令: yum install gcc-C++
4.进入/usr/local/redis-4.0.0,进行编译,命令:make
5.进入redis的src目录,进行安装,命令: make install
启动redis服务端

启动redis客户端

Redis 中 5种常用的数据类型

在Java中操作Redis
Jedis
Jedis的maven坐标:
<dependency>  | 
使用Jedis操作Redis的步骤:
- 获取连接
 - 执行操作
 - 关闭连接
 
首先我们先启动redis服务端

之后就可以通过java代码进行客户端连接了
public void testRedis(){  | 
有很多其他的方法我就不一一演示了,因为这个不经常用
重要的是下面的这个
Spring Data Redis(重点)
在Spring Boot项目中,可以使用Spring Data Redis来简化Redis操作,maven坐标:
<dependency>  | 
Spring Data Redis中提供了一个高度封装的类: RedisTemplate,针对jedis客户端中大量ap进行了归类封装,将同一类型操作封装为operation接口,具体分类如下:
- ValueOperations:简单的String K-V操作
 - SetOperations: set类型数据操作
 - ZSetOperations: zset类型数据操作
 - HashOperations:针对map类型的数据操作
 - ListOperations:针对list类型的数据操作
 
- 配置文件yml
 
#Redis相关配置  | 
- springboot自动为我们创建了一个RedisTemplate对象,所以我们可以直接自动注入
 

之后我们基于这个对象来操作redis
  | 
需要注意的是 redisTemplate在操作redis的key的时候,自动做了key的序列化

所以前面出现了乱码一样的东西,这使得我们无法通过key得到value
如何处理这个问题呢,可以手动改变序列化的方式
创建一个配置类就可以了
  | 
key有序列化器,value有没有序列化器呢,答案是也有,我们取出结果可以看出

但是这个我们可以不去修改它,在程序代码中get出来value值还会反序列化,还原成原来的样子
Day8(开始项目优化)
缓存优化
我们可以把用户访问的菜单数据,以及短信验证码放到缓存中,这样就不用频繁的查询数据库了,提升系统性能,增强用户体验
把代码提交到git
对于这个代码提交到Git,是第三种方法,以前也使用过,但是一般都是用之前学git的那两种,这种方法是把自己已经写过但是没用过git的操作
- 创建本地代码仓库
 


- 把代码add进暂存区
 

- 提交代码至远程仓库
 

- 推送到远程仓库
 

- 配置远程仓库地址
 

环境搭建
基于redis
maven坐标
在项目的pom.xml文件中导入spring data redis的maven坐标:
<dependency>  | 
配置文件
在项目的application.yml中加入redis相关配置;
spring:  | 
配置类
==主要是设置序列化器==
在项目中加入配置类RedisConfig:
  | 
缓存短信验证码
原来是放在session,现在我们放在redis中
实现思路
前面我们已经实现了移动端手机验证码登录,随机生成的验证码我们是保存在HttpSession中的。现在需要改造为将验证码缓存在Redis中,具体的实现思路如下:
1、在服务端UserController中注入RedisTemplate对象,用于操作Redis
2、在服务端UserController的sendMsg方法中,将随机生成的验证码缓存到Redis中,并设置有效期为5分钟
3、在服务端UserController的login方法中,从Redis中获取缓存的验证码,如果登录成功(验证码就没用了)则删除Redis中的验证码
代码改造


缓存菜品数据
实现思路
前面我们已经实现了移动端菜品查看功能,对应的服务端方法为DishController的list方法,此方法会根据前端提交的查询条件进行数据库查询操作。在高并发的情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长。现在需要对此方法进行缓存优化,提高系统的性能。
具体的实现思路如下:
1、改造DishController的list方法,先从Redis中获取菜品数据,如果有则直接返回,无需查询数据库;如果没有则查询数据库,并将查询到的菜品数据放入Redis。
2、改造DishController的save和update方法,加入清理缓存的逻辑
==注意事项==
在使用缓存过程中,要注意保证数据库中的数据和缓存中的数据一致,如果数据库中的数据发生变化,需要及时清理缓存数据。
代码改造
缓存菜品

更新/新增 之后删除缓存


Spring Cache
Spring Cache介绍
Spring cache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。
Spring Cache提供了一层抽象,底层可以切换不同的cache实现。具体就是通过cacheManager接口来统一不同的缓存技术。
CacheManager是Spring提供的各种缓存技术抽象接口。
针对不同的缓存技术需要实现不同的CacheManager:
| CacheManager | 描述 | 
|---|---|
| EhCacheCacheManager | 使用EhCache作为缓存技术 | 
| GuavaCacheManager | 使用Google的GuavaCache作为缓存技术 | 
| RedisCacheManager | 使用Redis作为缓存技术 | 
Spring Cache常用注解
| 注解 | 说明 | 
|---|---|
| @EnableCaching | 开启缓存注解功能 | 
| @Cacheable | 在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中 | 
| @CachePut | 将方法的返回值放到缓存中 | 
| @CacheEvict | 将一条或多条数据从缓存中删除 | 
在spring boot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching开启缓存支持即可。
例如,使用Redis(也就是上面的第三种CacheManager)作为缓存技术,只需要导入Spring data Redis的maven坐标即可。
@CachePut
将方法的返回值放到缓存中
参数有
value:表示缓存名称,某一类数据,每个缓存名称下面可以有多个key
key:数据的key,所以这个不能写死,可以利用spring的表达式语言处理这个,比如
参数.属性名

或者result.属性名

@CacheEvict
将一条或多条数据从缓存中删除
参数有:
value:表示要删除缓存名称为xxx里面的缓存数据
key:表示key=xxx数据需要被删除,@CacheEvict同样支持spring的表达式语言,比如

@Cacheable
在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中,相当于**@CachePut 的加强版**
参数也和@CachePut 一样
但是我们会发现一个问题,如果返回值为空,仍然会把数据保存进redis
所以我们可以这样写,加一个条件参数

或者

Spring Cache使用方式
在Spring Boot项目中使用Spring cache的操作步骤(使用redis缓存技术):
1、导入maven坐标
spring-boot-starter-data-redis
spring-boot-starter-cache
2、配置application.yml
spring:  | 
3、在启动类上加入@EnableCaching注解,开启缓存注解功能
4、在Controller的方法上加入@Cacheable、@CacheEvict等注解,进行缓存操作
缓存套餐数据
实现思路
前面我们已经实现了移动端套餐查看功能,对应的服务端方法为SetmeatController的list方法,此方法会根据前端提交的查询条件进行数据库查询操作。在高并发的情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长.现在需要对此方法进行缓存优化,提高系统的性能。
具体的实现思路如下:
1、导入Spring Cache和Redis相关maven坐标
2、在application.yml中配置缓存数据的过期时间
3、在启动类上加入@EnableCaching注解,开启缓存注解功能
4、在SetmealController的list方法上加入@Cacheable注解
5、在SetmealController的save和delete方法上加入@CacheEvict注解
代码改造
1.
<dependency>  | 
- 设置过期时间以及redis的配置
 
spring:  | 
- 加上@EnableCaching
 

- 加上@Cacheable注解
 

- 加上@CacheEvict注解
 


Day9
读写分离
问题说明
读和写所有压力都由一台数据库承担,压力巨大
数据库服务器如果磁盘损坏则会数据丢失,造成单点故障
所以我们可以进行读写分离

从库相当于主库数据的备份
Mysql主从复制
介绍
MySQL主从复制是一个异步的复制过程,底层是基于Mysql数据库自带的二进制日志功能。就是一台或多台MNySQL数据库(slave,即从库)从另一台MySQL数据库((master,即主库)进行日志的复制然后再解析日志并应用到自身,最终实现从库的数据和主库的数据保持一致。MySQL主从复制是MySQL数据库自带功能,无需借助第三方工具。
MySQL复制过程分成三步:
- master将改变记录到二进制日志 ( binary log)
 - slave将master的binary log拷贝到它的中继日志(relay log)
 - slave重做中继日志中的事件,将改变应用到自己的数据库中
 

并且从库可以有多个,这个案例我们只用一个从库就可以了
配置-前置条件
提前准备好两台服务器,分别安装Mysql并启动服务成功
- 主库Master 192.168.138.100
 - 从库Slave 192.168.138.101
 
配置-主库Master
第一步:修改Mysql数据库的配置文件/etc/my.cnf
[mysqld] #这个配置文件里面原来就有  | 
第二步:重启Mysql服务
systemctl restart mysqld
第三步:登录Mysql数据库,执行下面SQL
登录到数据库的步骤就省略了
GRANT REPLICATION SLAVE ON *.* to 'xiaoming'@'%' identified by 'Root@123456';
注:上面SQL的作用是创建一个用户xiaoming,密码为Root@123456,并且给xiaoming用户授予REPLICATION SLAVE权限。常用于建立复制时所需要用到的用户权限,也就是slave必须被master授权具有该权限的用户,才能通过该用户复制。
第四步:登录Mysql数据库,执行下面SQL,记录下结果中File和Position的值
show master status;

注:上面SQL的作用是查看Master的状态,执行完此SQL后不要再执行任何操作
配置-从库Slave
第一步:修改Mysql数据库的配置文件/etc/my.cnf
[mysqld] #这个配置文件里面有  | 
第二步:重启MNysql服务
systemctl restart mysqld
第三步:登录Mysql数据库,执行下面SQL
change master to  | 
start slave;  | 
其中的mysql-bin.000005,和441都是在主库查出来的(在上面的那张图片上有)
第四步:登录Mysql数据库,执行下面SQL,查看从数据库的状态
show slave status;  | 
只要这两个都是yes就说明我们配置成功啦

读写分离案例
背景
面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。
Sharding-JDBC介绍
读写分离框架
Sharding-JDBC定位为轻量级Java框架,在Java的JDBC层提供的额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
使用Shardihg-JDBC可以在程序中轻松的实现数据库读写分离。
- 适用于任何基于JDBC的ORM框架,如:JPA, Hibernate,Mybatis, Spring JDBC Template或直接使用JDBC。
 - 支持任何第三方的数据库连接池,如:DBCP,C3PO,BoneCP, Druid,HikariCP等。
 - 支持任意实现JDBC规范的数据库。目前支持My5QL,Oracle,sQLServer,PostgreSQL以及任何遵循SQL92标准的数据库。
 
<dependency>  | 
入门案例
使用Sharding-JDBC实现读写分离步骤:
1、导入maven坐标,就是上面那个
2、在配置文件中配置读写分离规则
3、在配置文件中配置允许bean定义覆盖配置项
1导入就可以了
2.

承上

注意:其中
,
这个可以随便起名字,但是下面的名字要一一对应。
- 在配置文件中配置允许bean定义覆盖配置项,(后创建的bean会覆盖前一个创建的bean)
 

项目实现读写分离
数据库环境准备(主从复制)
直接使用我们前面在虚拟机中搭建的主从复制的数据库环境即可。
在主库中创建瑞吉外卖项目的业务数据库reggie并导入相关表结构和数据。
代码改造
在项目中加入Sharding-JDBC实现读写分离步骤:
1、导入maven坐标
2、在配置文件中配置读写分离规则
3、在配置文件中配置允许bean定义覆盖配置项
<dependency>  | 
spring:  | 
3.
Nginx
Nginx概述
Nginx介绍
Nginx是一款轻量级的web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。
其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx的网站有:百度、京东、新浪、网易、腾讯、淘宝等。
Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文: Paw6nep)开发的,第一个公开版本0.1.0发布于2004年10月4日。
Nginx下载与安装
安装过程:
1、安装依赖包yum -y install gcc pcre-devel zlib-devel openssl openssl-devel
2、下载Nginx安装包wget https: //nginx.org/download/nginx-1.16.1.tar.gz
3、解压tar -zxvf nginx-1.16.1.tar.gz
4、cd nginx-1.16.1
5、 mkidr -p /usr/local/nginx
./configure –prefix=/usr/local/nginx
6、make && make install
Nginx目录结构

conf:目录里面是一些配置文件
html:部署的静态资源文件,以后我们可以把自己的静态资源文件放到这里面
logs:日志目录
sbin:启动/停止nginx
Nginx命令
在/usr/local/nginx/sbin目录下执行这些命令
查看版本
./nginx -v  | 
检查配置文件正确性
在启动Nginx服务之前,可以先检查一下conf/nginx.conf文件配置的是否有错误,因为nginx启动默认就会加载这个配置文件,命令如下:
./nginx -t  | 
启动和停止
启动服务
./nginx  | 
停止Nginx服务使用如下命令:
./nginx -s stop  | 
启动完成后可以查看Nginx进程:
ps-ef | grep nginx  | 
重新加载配置文件
当修改Nginx配置文件后,需要重新加载才能生效,可以使用下面命令重新加载配置文件:
./nginx -s reload  | 
把nginx命令配置到环境变量中
- 打开/etc/profile
 
vim /etc/profile  | 
- 修改path
 
原来

修改后

Nginx配置文件结构

Nginx具体应用
部署静态资源
Nginx可以作为静态web服务器来部署静态资源。静态资源指在服务端真实存在并且能够直接展示的一些文件,比如常见的html页面、css文件、js文件、图片、视频等资源。
相对于Tomcat,Nginx处理静态资源的能力更加高效,所以在生产环境下,一般都会将静态资源部署到Nginx中。将静态资源部署到Nginx非常简单,只需要将文件复制到Nginx安装目录下的html目录中即可。
比如我们在html内放进了一个hello.html页面
之后我们就可以通过192.168.138.100/hello.html访问这个页面
因为nginx有一个默认的配置,如下:

反向代理
正向代理
是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。
正向代理的典型用途是为在防火墙内的局域网客户端提供访问Internet的途径。
正向代理一般是在客户端设置代理服务器,通过代理服务器转发请求,最终访问到目标服务器.

反向代理
反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源,反向代理服务器负责将请求转发给目标服务器。
用户不需要知道目标服务器的地址,也无须在用户端作任何设定。

配置反向代理

之后我们如果想访问101的hello
就可以访问100的hello
访问
其实页面已经跳转到了192.168.138.101:8080/hello
负载均衡
早期的网站流量和业务功能都比较简单,单台服务器就可以满足基本需求,但是随着互联网的发展业务流量越来越大并且业务逻辑也越来越复杂,单台服务器的性能及单点故障问题就凸显出来了,因此需要多台服务器组成应用集群,进行性能的水平扩展以及避免单点故障出现。
应用集群:将同一应用部署到多台机器上,组成应用集群,接收负载均衡器分发的请求,进行业务处理并返回响应数据
负载均衡器:将用户请求根据对应的负载均衡算法分发到应用集群中的一台服务器进行处理

负载均衡基于反向代理

前后端分离开发
问题说明

前后端分离开发
介绍
前后端分离开发,就是在项目开发过程中,对于前端代码的开发由专门的前端开发人员负责,后端代码则由后端开发人员负责,这样可以做到分工明确、各司其职,提高开发效率,前后端代码并行开发,可以加快项目开发进度。目前,前后端分离开发方式已经被越来越多的公司所采用,成为当前项目开发的主流开发方式。
前后端分离开发后,从工程结构上也会发生变化,即前后端代码不再混合在同一个maven工程中,而是分为前端工程和后端工程。

开发流程
前后端分离开发后,面临一个问题,就是前端开发人员和后端开发人员如何进行配合来共同开发一个项目?可以按照如下流程进行:

接口(API接口)就是一个http的请求地址,主要就是去定义:请求路径、请求方式、请求参数、响应数据等内容
接口举例
Yapi
定义接口的框架
介绍
YApi是高效、易用、功能强大的api管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API,YApi还为用户提供了优秀的交互体验,开发人员只需利用平台提供的接口数据写入工具以及简单的点击操作就可以实现接口的管理。
YApi让接口开发更简单高效,让接口的管理更具可读性、可维护性,让团队协作更合理。
源码地址: https://github.com/YMFE/yapi
要使用YApi,需要自己进行部署。
==那么怎么部署呢???==
使用
Yapi可以做测试(和postman一样)
并且可以导入接口

Swagger
介绍
使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,再通过Swagger衍生出来的一系列项目和工具,就可以做到生成各种格式的接口文档,以及在线接口调试页面等等。
knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。也就是说它的底层还是swagger
<dependency>  | 
使用方式
操作步骤:
1、导入knife4j的maven坐标
2、导入knife4j相关配置类
3、设置静态资源映射,否则接口文档页面无法访问
4、在LoginCheckFilter中设置不需要处理的请求路径
导入maven坐标,在上面
导入相关配置类
增加了
和
这两个方法
这个类不用特定创建哦,里面还写的有我们其他的东西,下面这个代码不用再写,需要改的只有哪个扫描包的地方
  | 
- 设置静态资源映射,否则接口文档页面无法访问
 

- 在LoginCheckFilter中设置不需要处理的请求路径
 

之后直接启动就可以了
访问localhost:8080/doc.html就可以进入页面啦
常用注解
| 注解 | 说明 | 
|---|---|
| @Api | 用在请求的类上,例如Controller,表示对类的说明 | 
| @ApiModel | 用在类上,通常是实体类,表示一个返回响应数据的信息 | 
| @ApiModelProperty | 用在属性上,描述响应类的属性 | 
| @ApiOperation | 用在请求的方法上,说明方法的用途、作用 | 
| @ApilmplicitParams | 用在请求的方法上,表示一组参数说明 | 
| @ApilmplicitParam | 用在@ApilmplicitParams注解中,指定一个请求参数的各个方面 | 
项目部署
部署架构

部署环境说明

部署前端项目
第一步:在服务器A中安装Nginx,将课程资料中的dist目录上传到Nginx的html目录下

第二步:修改Nginx配置文件nginx.conf


rewrite对发出去的请求做出处理,比如说这个/api/employee/login,进行截取就会变成/employee/login
$1的意思是截取*匹配到的 再在前面加一个/,就变成了/employee/login
部署后端项目
第一步∶在服务器B中安装jdk、git、maven、MySQL,使用git clone命令将git远程仓库的代码克隆下来

第二步︰将资料中提供的reggieStart.sh文件上传到服务器B,通过chmod命令设置执行权限

第三步:修改项目中图片的存储位置


