外卖平台及移动端开发–优化篇

一、使用git管理代码

​ 使用Gitee作为远程仓库:https://gitee.com/

1、创建远程仓库

2、IDEA连接远程仓库

    1. 在gitee中获取远程仓库的https地址
    1. 在IDEA中通过VCS连接git远程仓库

3、配置.gitignore文件

4、提交文件并推送

5、创建新分支v1.0并推送

​ 在IDEA中创建新分支v1.0,便于后面代码优化检测

二、缓存优化

​ 使用Redis缓存数据库内容,减少对数据库的访问

1、环境搭建

1.1 添加maven坐标

​ pom文件中添加

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

1.2 yml文件配置

​ 在application.yml文件中添加redis相关配置:

1
2
3
4
5
6
string:  
redis:
host: localhost
port: 6379
password:
database: 0

1.3 添加配置类RedisConfig

​ 在config包下添加配置类RedisConfig,以解决key的序列化冗长问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.itreggie.reggie.config;

import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
* Redis配置类
*/

@Configuration
public class RedisConfig extends CachingConfigurerSupport {

@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {

RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();

//默认的Key序列化器为:JdkSerializationRedisSerializer
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());

redisTemplate.setConnectionFactory(connectionFactory);

return redisTemplate;
}

}

1.4 推送到远程仓库

​ 在分支v1.0上进行环境搭建,并上传到远程仓库的v1.0分支上。

2、缓存短信验证码

2.1 实现思路

    1. 在服务端UserController中注入RedisTemplate对象,用于操作Redis
    1. 在服务端UserController的sendMsg方法中,将随机生成的验证码缓存到Redis中,并设置有效时间为5分钟
    1. 在服务端UserController的login方法中,从Redis中获取缓存的验证码,如果登陆成功则删除Redis中的验证码

2.2 代码优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Autowired
private RedisTemplate redisTemplate;

/**
* 发送手机验证码
* @param user
* @return
*/
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user,HttpSession session){
//获取手机号
String phone = user.getPhone();

if (StringUtils.isNotEmpty(phone)){
//随机生成4位验证码
String code = ValidateCodeUtils.generateValidateCode(4).toString();
log.info("code={}",code);

//调用阿里云(or榛子云)提供的短信服务API完成发送短信
//SendSms.send(phone,code);

//需要将生成的验证码保存到session中
//session.setAttribute(phone,code);

//将生成的验证码缓存到redis中,并且设置有效期为5mins
redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);

return R.success("手机验证码短信发送成功");
}

return R.error("手机验证码短信发送失败");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* 移动端用户登录
* @param map
* @param session
* @return
*/
@PostMapping("/login")
public R<User> login(@RequestBody Map map, HttpSession session){
log.info(map.toString());
//获取手机号
String phone = map.get("phone").toString();

//获取验证码
String code = map.get("code").toString();

//从session中获取保存的验证码
//Object codeInSession = session.getAttribute(phone);
//从redis中获取缓存的验证码
Object codeInSession = redisTemplate.opsForValue().get(phone);


//进行验证码比对(页面提交的验证码和session中保存的验证码比对)
if (codeInSession != null && codeInSession.equals(code)){
//如果比对成功,说明登录成功
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getPhone,phone);

User user = userService.getOne(queryWrapper);

if (user == null){
//判断当前手机号对应的用户是否为新用户,如果为新用户,自动完成注册
user = new User();
user.setPhone(phone);
user.setStatus(1);
userService.save(user);
}
session.setAttribute("user",user.getId());
//如果用户登录成功,删除redis中缓存的验证码
redisTemplate.delete(phone);

return R.success(user);
}

return R.error("登录失败");
}

2.3 功能测试

    - 打开本地的Redis,运行项目,在移动端页面点击"获取验证码",查看Redis客户端中是否产生key
    - 登陆成功后,查看Redis客户端中key是否删除

3、缓存菜品数据

​ 移动端菜品查看功能是通过调用DishController中的list方法实现,list方法会根据前端提供的查询条件对数据库进行查询操作。在高并发的情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长。因此需要对数据进行缓存优化,提供系统性能。

3.1 实现思路

1. 改造DishController的list方法,先从Redis中获取菜品数据,如果有则直接返回,无需查询数据库;如果没有则查询数据库,并将查询到的菜品数据放入Redis中
1. 改造DishController的save和update方法,加入清理缓存的逻辑

注意:在使用缓存过程中,要保证数据库中的数据和缓存中的数据一致,如果数据库中的数据发生变化,要及时清理缓存数据并更新。

3.2 代码优化

3.2.1 list方法优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@Autowired
private RedisTemplate redisTemplate;

/**
* 根据条件查询对应的菜品数据
* @param dish
* @return
*/
@GetMapping("/list")
public R<List<DishDto>> list(Dish dish){
List<DishDto> dishDtoList = null;

//动态构造key
String key = "dish_" + dish.getCategoryId() + "_" + dish.getStatus();

//在redis中获取缓存数据
dishDtoList = (List<DishDto>) redisTemplate.opsForValue().get(key);

if (dishDtoList != null){
//如果存在,直接返回,无需查询数据库
return R.success(dishDtoList);
}

//构造查询条件
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper();
queryWrapper.eq(dish.getCategoryId() != null,Dish::getCategoryId,dish.getCategoryId());

//添加条件,查询status状态为1的菜品(启售)
queryWrapper.eq(Dish::getStatus,1);

//添加排序条件
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

List<Dish> list = dishService.list(queryWrapper);

dishDtoList = list.stream().map((item)->{
DishDto dishDto = new DishDto();

BeanUtils.copyProperties(item,dishDto);

Long categoryId = item.getCategoryId();//分类id
//根据id查询分类对象
Category category = categoryService.getById(categoryId);

if (category != null){
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
}
//当前菜品id
Long dishId = item.getId();
LambdaQueryWrapper<DishFlavor> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DishFlavor::getDishId,dishId);
//sql:select * from dish_flavor where dish_id = ?
List<DishFlavor> dishFlavorList = dishFlavorService.list(wrapper);
dishDto.setFlavors(dishFlavorList);
return dishDto;
}).collect(Collectors.toList());

//如果不存在,需要查询数据库,将查询到的菜品数据缓存到redis
redisTemplate.opsForValue().set(key,dishDtoList,1, TimeUnit.HOURS);

return R.success(dishDtoList);
}
3.2.2 save和update方法优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@PostMapping
public R<String> save(@RequestBody DishDto dishDto){
log.info(dishDto.toString());

dishService.saveWithFlavor(dishDto);

/* //清理所有菜品的缓存数据
Set keys = redisTemplate.keys("dish_*");
redisTemplate.delete(keys);*/

//精确清理某个分类下的菜品缓存数据
String key = "dish_" + dishDto.getCategoryId() + "_1";
redisTemplate.delete(key);

return R.success("新增菜品成功");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    /**
* 修改菜单
* @param dishDto
* @return
*/
@PutMapping
public R<String> update(@RequestBody DishDto dishDto){
log.info(dishDto.toString());

dishService.updateWithFlavor(dishDto);

/* //清理所有菜品的缓存数据
Set keys = redisTemplate.keys("dish_*");
redisTemplate.delete(keys);*/

//精确清理某个分类下的菜品缓存数据
String key = "dish_" + dishDto.getCategoryId() + "_1";
redisTemplate.delete(key);

return R.success("新增菜品成功");
}
3.2.3 菜品启售/停售方法修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 对菜品批量或者是单个 进行停售或者是起售
* @return
*/
@PostMapping("/status/{status}")
//这个参数这里一定记得加注解才能获取到参数,否则这里非常容易出问题
public R<String> status(@PathVariable("status") Integer status,@RequestParam List<Long> ids){
//log.info("status:{}",status);
//log.info("ids:{}",ids);
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper();
queryWrapper.in(ids !=null,Dish::getId,ids);
//根据数据进行批量查询
List<Dish> list = dishService.list(queryWrapper);

for (Dish dish : list) {
if (dish != null){
dish.setStatus(status);
dishService.updateById(dish);
Long categoryId = dish.getCategoryId();
//精确清理某个分类下的菜品缓存数据
String key = "dish_" + categoryId + "_1";
redisTemplate.delete(key);
}
}

return R.success("售卖状态修改成功");
}

3.3 功能测试

- 首次点击菜品分类按钮(粤菜...),查看redis是否存入key,IDEA控制台是否查询了数据库
- 再次点击菜品分类按钮,查看IDEA控制台是否没有数据库
- 打开系统后台管理页面,增加/修改菜品,点击保存后查看redis是否删除key
- 再次点击移动端菜品分类按钮,查看新增菜品是否更新

3.4 推送到远程仓库

​ 功能测试没问题后,推送至v1.0分支上,再合并到master分支上。

4、缓存套餐数据

4.1 实现思路

1. 导入Spring Cache和Redis相关的maven坐标(Part3已完成↓)
1. 在application.yml中配置缓存数据的过期时间(Part3已完成↓)
1. 在启动类上加入@EnableCaching注解,开启缓存注解功能(Part3已完成↓)
1. 在SetmealController的list方法加上@Cacheable注解
1. 在SetmealController的save和update方法加上@CacheEvict注解

4.2 代码实现

4.2.1 list方法修改

​ 添加@Cacheable注释

1
2
3
4
5
6
7
8
9
10
/**
* 根据条件查询套餐数据
* @param setmeal
* @return
*/
@Cacheable(value = "setmealCache",key = "#setmeal.categoryId + '_' + #setmeal.status")
@GetMapping("/list")
public R<List<Setmeal>> list(Setmeal setmeal){

}

​ 进行功能测试时会报错,原因是返回结果类R未实现序列化,因此需对R.class添加序列化接口

1
public class R<T> implements Serializable {
4.2.2 save&delete方法修改

​ 添加@CacheEvict注释

1
2
3
4
5
6
7
8
9
10
/**
* 新增套餐
* @param setmealDto
* @return
*/
@PostMapping
@CacheEvict(value = "setmealCache",allEntries = true)
public R<String> save(@RequestBody SetmealDto setmealDto){

}
1
2
3
4
5
6
7
8
9
10
/**
* 删除套餐
* @param ids
* @return
*/
@DeleteMapping
@CacheEvict(value = "setmealCache",allEntries = true)
public R<String> delete(@RequestParam List<Long> ids){

}

​ 注意:完善了套餐修改和停/启售功能也需要添加@CacheEvict注释,以保证缓存数据和数据库数据保持一致

4.2.3 移动端套餐菜品显示缓存

​ 添加@Cacheable注释

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 移动端点击套餐图片查看套餐具体内容
* 这里返回的是dto 对象,因为前端需要copies这个属性
* 前端主要要展示的信息是:套餐中菜品的基本信息,图片,菜品描述,以及菜品的份数
* @param SetmealId
* @return
*/
//这里前端是使用路径来传值的,要注意,不然你前端的请求都接收不到,就有点尴尬哈
@GetMapping("/dish/{id}")
@Cacheable(value = "setmealCache",key = "#SetmealId")
public R<List<DishDto>> dish(@PathVariable("id") Long SetmealId){

}

三、Spring Cache

​ Spring Cache是一个框架,实现基于注解的缓存功能。Spring Cache提供了一层抽象,底层可以切换不同的cache实现:通过CacheManager接口统一不同的缓存技术。

1、CacheManager 接口

​ CacheManager是Spring提供的各种缓存技术抽象接口,针对不同的缓存技术需要不同的CacheManager:

CacheManager 描述
EhCacheCacheManager 使用EhCache作为缓存技术
GuavaCacheManager 使用Google的GuavaCache作为缓存技术
RedisCacheManager 使用Redis作为缓存技术

2、Spring Cache常用注解

注解 说明
@EnableCaching 开启缓存注解功能
@Cacheable 在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;如果无数据,调用方法并将方法返回值放入缓存中
@CachePut 将方法的返回值放到缓存中
@CacheEvict 将一条或多条数据从缓存中删除

在springboot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包吗,并在启动类上使用@EnableCaching开启缓存支持即可。例如使用Redis作为缓存技术,只需要导入Spring data Redis 的maven坐标即可。

3、Spring Cache使用方式

​ 在springboot项目中使用spring cache的操作步骤:

3.1 导入maven坐标

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

3.2 配置application.yml文件

1
2
3
4
string:  
cache:
redis:
time-to-live: 180000

3.3 在启动类上加入@EnableCaching注解,开启缓存注解功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.itreggie.reggie;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Slf4j
@SpringBootApplication
@ServletComponentScan
@EnableTransactionManagement
@EnableCaching
public class ReggieApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieApplication.class,args);
log.info("项目启动成功。。。");
}
}

3.4 在Controller的方法上加入@Cacheable、@CacheEvict等注解,进行缓存操作

四、读写分离

1、MySQL主从复制

​ MySQL主从复制是一个异步的复制过程,底层是基于MySQL数据库自带二进制日志功能。

​ 原理是一台或多台MySQL数据库(slave从库)从MySQL数据库(master主库)进行日志复制再解析应用日志到自身,最终实现从库数据和主库数据保持一致

​ MySQL复制过程分为三步:

- master将改变记录到二进制日志(binary log)
- slave将master的binary log拷贝到自身的中继日志(relay log)
- slave重做中继日志中的事件,将改变应用到自己的数据库中
img

1.1 配置主库master

1.1.1 修改mysql数据库配置文件

​ 在/etc/my.cnf中添加

1
2
3
4
[mysqld]
log-bin=mysql-bin #[必须]启用二进制日志
server-id=100 #[必须]服务器唯一ID(可自定义)

1.1.2 重启mysql数据库
1
systemctl restart mysqld
1.1.3 创建用户

​ 登陆mysql数据库,执行下面sql:

1
grant replication slave on *.* to 'xiaoming'@'%' identified by 'Root@123q321q';

​ sql语句的作用是创建一个用户xiaoming,密码为Root@123q321q,并且给予xiaoming用户replication slave权限。常用于建立复制时所需要用到的用户权限,换言之,只有被master授权该权限的用户,slave才能通过该用户进行复制。

1.1.4 查看主库状态
1
show master status;

​ 获取结果中FilePosition的值。

注意:上面sql语句作用是查看master状态,执行后不要再进行其他操作,以免状态发生改变

1.2 配置从库slave

1.2.1 修改mysql数据库配置文件

​ 在/etc/my.cnf中添加

1
2
[mysqld]
server-id=101 #[必须]服务器唯一ID
1.2.2 重启mysql数据库
1
systemctl restart mysqld
1.2.3 从库连接主库

​ 登陆mysql数据库,在sql中实现(根据实际情况填写):

1
2
3
4
5
6
change master to
-> master_host='虚拟机ip',
-> master_user='',
-> master_password='',
-> master_log_file='',
-> master_log_pos=441;

​ 如遇到已有slave线程进行,可使用以下代码取消当前线程:

1
stop slave;

​ sql语句运行成功后,启动slave:

1
start slave;
1.2.4 查看slave连接情况
1
show slave status;

image-20220921143029089

image-20220921143113902

​ 能得到以上结果,说明slave连接master成功

2、使用Sharding-JDBC实现读写分离

2.1 导入maven坐标

1
2
3
4
5
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>

2.2 配置application.yml文件

2.2.1 配置读写分离规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
server:
port: 8090
mybatis-plus:
configuration:
#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID
spring:
shardingsphere:
datasource:
names:
master,slave
# 主数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://虚拟机ip:3306/rw?characterEncoding=utf-8
username: root
password: root
# 从数据源
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://虚拟机ip:3306/rw?characterEncoding=utf-8
username: root
password: root
masterslave:
# 读写分离配置
load-balance-algorithm-type: round_robin #轮询
# 最终的数据源名称
name: dataSource
# 主库数据源名称
master-data-source-name: master
# 从库数据源名称列表,多个逗号分隔
slave-data-source-names: slave
props:
sql:
show: true #开启SQL显示,默认false
2.2.2 配置允许bean定义覆盖的配置项
1
2
3
spring: 
main:
allow-bean-definition-overriding: true

3、项目实现读写分离

3.1 数据库环境准备(主从分离)

​ 直接使用已经搭建好的虚拟机主从复制的数据库环境,在master中导入数据库即可。

3.2 配置application.yml文件

3.2.1 配置读写分离规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
server:
port: 8090
mybatis-plus:
configuration:
#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID
spring:
shardingsphere:
datasource:
names:
master,slave
# 主数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://虚拟机ip:3306/reggie?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
# 从数据源
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://虚拟机ip:3306/reggie?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
masterslave:
# 读写分离配置
load-balance-algorithm-type: round_robin #轮询
# 最终的数据源名称
name: dataSource
# 主库数据源名称
master-data-source-name: master
# 从库数据源名称列表,多个逗号分隔
slave-data-source-names: slave
props:
sql:
show: true #开启SQL显示,默认false
3.2.2 配置允许bean定义覆盖的配置项
1
2
3
spring: 
main:
allow-bean-definition-overriding: true

3.3 功能测试

​ 切换数据库后重新登录后台管理页面,查看IDEA控制台的输出情况,出现增删改在master,查在slave则说明读写分离成功。

五、Nginx

​ 官网:https://nginx.org/

​ Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。特点是占有内存少,并发能力强。

1、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. 检查安装目录环境 ./configure –prefix=/usr/local/nginx
  6. 编译并安装 make && make install

2、Nginx常用命令

命令 意义 备注
./nginx -v 查看版本号 在sbin目录下
./nginx -t 检查nginx.conf文件配置 在sbin目录下
./nginx 启动Nginx 在sbin目录下
./nginx -s stop 停止Nginx服务 在sbin目录下
ps -ef | grep nginx 查看Nginx进程
./nginx -s reload 重新加载配置文件 在sbin目录下

​ 配置环境变量,使Nginx命令能直接运行,在/etc/profile的PATH中修改:

1
PATH=/usr/local/nginx/sbin:$JAVA_HOME/bin:$PATH

3、Nginx配置文件结构

  • 全局块:和Nginx运行相关的全局配置
  • events块:和网络连接相关的配置
  • http块:代理、缓存、日志记录、虚拟主机配置
    • http全局块
    • Server块
      • Server全局块
      • location块

注意:http块可以配置多个server块,每个server块可以配置多个location块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#全局块
worker_processes 2;

#events块
events {
worker_connections 1024;
}

#http块
http {
#http全局块
include mime.types;
default_type application/octet-stream;

sendfile on;

keepalive_timeout 65;

#Server块
server {
#Server全局块
listen 80;
server_name localhost;

#location块
location / {
root html;
index index.html index.htm;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}

4、Nginx具体应用

4.1 部署静态资源

​ Nginx可以作为静态web服务器来部署静态资源。静态资源指在服务端真实存在且能够直接展示的一些文件,如http页面、css文件、js文件、图片、视频等。

​ 相比于Tomcat,Nginx处理静态资源的能力更加高效,在生产环境中,一般将静态资源部署在Nginx中。将静态资源部署在Nginx中只需要将文件复制到Nginx安装目录下的html目录即可。

1
2
3
4
5
6
7
8
9
10
11
12
#Server块
server {
#Server全局块
listen 80; #监听端口
server_name localhost; #服务器名称

#location块
location / { #匹配客户端请求url
root html; #指定静态资源根目录
index index.html index.htm; #指定默认首页
}
}

4.2 反向代理

4.2.1 正向代理概念补充

​ 正向代理是位于客户端和原始服务器(origin server)之间的服务器。为了从原始服务器获取内容,客户端向代理发送请求并指定目标为原始服务器,然后代理向原始服务器转交请求并将获取到的内容返回给客户端。

​ 正向代理主要是 帮助在防火墙内的局域网客户端提供访问Internet的途径。

​ 正向代理一般在客户端设置代理服务器,通过代理服务器发送请求,最终访问到目标服务器。

4.2.2 反向代理

​ 反向代理服务器位于客户和目标服务器之间,但对于客户而言,反向代理服务器相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源,反向服务器负责将请求转发给目标服务器。

​ 用户不需要知道目标服务器的地址,也不需要用户端作任何设置。

4.2.3 反向代理配置
#Server块
server {
    #Server全局块
    listen       82;					
    server_name  localhost;				

    #location块
    location / {						
        proxy_pass http://虚拟机ip:8090;		#反向代理配置,将请求转发到指定服务器
    }
}

4.3 负载均衡

    - 应用集群:将同一应用部署到多台机器上组成应用集群,接收负载均衡器分发的请求,进行业务处理并返回响应数据
    - 负载均衡器:将用户请求根据对应的负载均衡算法分发到应用集群中的一台服务器进行处理
4.3.1 配置负载均衡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
upstream targetserver{				# upstream指令可以定义一组服务器
server 虚拟机ip:8090 weight=10; #部署负载均衡策略
server 虚拟机ip:8090 weight=5; #部署不同的服务器(也可以使用同一台服务器,调用不同的端口作为学习)
}


#Server块
server {
#Server全局块
listen 8090;
server_name localhost;
#location块
location / {
proxy_pass http://targetserver; #反向代理配置,将请求转发到指定服务器
}
}
4.2.2 负载均衡策略
名称 说明
轮询 默认方式
weight 权重方式
ip_hash 依据ip分配方式
least_conn 依据最少连接方式
url_hash 依据url分配方式
fair 依据响应时间方式

六、前后端分离开发

1、开发流程

img

接口(API接口) 就是一个http的请求地址,主要就是去定义:请求路径、请求方式、请求参数、响应参数等内容。

img

2、前端技术栈

  • 开发工具
    • visual studio code
    • hbuilder
  • 技术框架
    • node.js(基石)
    • VUE
    • ElementUI
    • mock(用于测试)
    • webpack(用于打包)

3、YApi

​ YApi是高效、易用、功能强大的api管理平台,旨在为用户提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护API,YApi还为用户提供了优秀的交互体验,开发人员只需要利用平台提供的接口数据写入工具以及简单的点击操作就可以实现接口的管理。

​ YApi让接口开发更简单高效,让接口的管理更具有可读性、可维护性,让团队协作更合理。

​ 源码地址:https://github.com/YMFE/yapi

3.1 YApi使用方式

- 添加项目
- 添加分类
- 添加接口
- 编辑接口
- 查看接口

七、Swagger

​ 官网:https://swagger.io/

1、介绍

​ 只需要按照Swagger的规范去定义接口及接口相关的信息,再通过Swagger衍生出来的一系列项目和工具,就可以做成各种格式的接口文档,以及在线接口调试页面等。

​ knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方法。

1
2
3
4
5
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>

2、使用方式

2.1 导入knife4j的maven坐标

2.2 导入knife4j的相关配置类WebMvcConfig

​ 对原有的WebMvcConfig进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Slf4j
@Configuration
@EnableSwagger2
@EnableKnife4j
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Bean
public Docket createRestApi() {
//文档类型
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.itheima.reggie.controller"))
.paths(PathSelectors.any())
.build();
}

private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("外卖")
.version("1.0")
.description("外卖接口文档")
.build();
}
}

2.3 设置静态资源

​ 将代码添加到WebMvcConfig类中的addResourceHandlers方法中,解决接口文档页面访问问题。

1
2
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");

2.4 添加请求路径

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

1
2
3
4
"/doc.html",
"/webjars/**",
"/swagger-resources",
"/v2/api-docs"

3、常用注解

注解 说明
@Api 用在请求的类(eg:controller)上,表示对类的说明
@ApiModel 用在类上,通常是实体类,表示一个返回响应数据的信息
@ApiModelProperty 用在属性上,描述响应类的属性
@ApiOperation 用在请求的方法上,说明方法的用途、作用
@ApiImplicitParams 用在请求的方法上,表示一组参数说明
@ApiImplicitParam 用在@ApiImplicitParams注解中,指定一个请求参数的各个方面

八、项目部署

1、部署架构

img

2、部署环境说明

  • 192.168.197.100(服务器A)
    • Nginx:部署前端项目、配置反向代理
    • MySQL:主从复制结构中的主库
  • 192.168.197.101(服务器B)
    • jdk:运行java项目
    • git:版本控制工具
    • maven:项目构建工具
    • jar:spring boot项目打包成jar包基于内置Tomcat运行
    • MySQL:主从复制结构中的从库
  • localhost(服务器C)
    • Redis:缓存中间件

3、部署前端项目(存在问题–未解决)

3.1 部署Nginx

​ 导入项目前端文件dist到Nginx的html目录下。

3.2 修改Nginx配置文件

​ 修改Nginx的配置文件nginx.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 80;
server_name localhost;

location / {
root html/dist;
index index.html;
}
#反向代理配置
location ^~ /api/ {
rewrite ^/api/(.*)$ /$1 break; #对url进行重写
proxy_pass http://192.168.138.101:8090;
}
}

4、部署后端项目

4.1 工具部署

​ 安装好jdk、git、maven、MySQL等工具,使用git clone命令将git远程仓库代码克隆下来

4.2 上传reggieStart.sh文件并重新设置权限

1
chmod 777 reggieStart.sh 

注意:reggieStart.sh内的名称需要修改为自己项目的名称,否则会打包失败

​ 运行.sh文件后,查看java进程确定程序是否启动。

4.3 修改图片路径

​ 将本地的菜品图片传到linux目录下,在application.yml文件中修改文件路径:

1
2
reggie:
path: /usr/local/img/

4.4 功能测试

​ 将Nginx和reggieStart.sh同时运行,在网页进行登陆测试,能成功登陆即项目前后端分离成功。