1.概述

软件开发整体介绍

软件开发流程

image-20221101215252928

角色分工

image-20221101215522519

软件环境

image-20221101215648587

瑞吉外卖项目介绍

项目介绍

image-20221101215938996

image-20221101220015602

image-20221101220026945

image-20221101220045837

产品原型展示

image-20221101220205580

技术选型

image-20221101220659178

功能架构

image-20221101221042664

角色

image-20221102111505518

开发环境搭建

数据库环境搭建

image-20221102111654252

image-20221102111841746

image-20221102112238507

image-20221102112545353

maven项目搭建

image-20221102113158106

image-20221102113400348

配置pom文件

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.itheima</groupId>
<artifactId>reggie_take_out</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>

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

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>

<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.23</version>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.5</version>
</plugin>
</plugins>
</build>
</project>

配置springboot配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server:
port: 8080
spring:
application:
name: reggie_take_out
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: Xuwei19941214~
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

编写springboot启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.itheima.reggie;


import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

image-20221102115402524

导入前端页面的静态资源

image-20221102115954946

由于MVC无法识别非static下的静态资源,需通过配置修改

修改mvc配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.itheima.reggie.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

/**
* 设置资源映射
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始进行静态资源映射...");
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
super.addResourceHandlers(registry);
}
}

image-20221102124855698

image-20221102125237449

成功访问。

后台开发登录功能

需求分析

image-20221102125402985

image-20221102130411085

image-20221102133155426

image-20221102133224942

代码开发

1)创建实体类Employee,和employee表进行映射

image-20221102133739054

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
package com.itheima.reggie.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

@Data
public class Employee implements Serializable {

private static final long serialVersionUID = 1L;

private Long id;

private String username;

private String name;

private String password;

private String phone;

private String sex;

private String idNumber;

private Integer status;

private LocalDateTime createTime;

private LocalDateTime updateTime;

@TableField(fill = FieldFill.INSERT)
private Long createUser;

@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;

}

2)创建contrller、service、mapper

image-20221102134134327

3)导入返回结果类R

image-20221102135118235

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
package com.itheima.reggie.common;

import lombok.Data;
import java.util.HashMap;
import java.util.Map;

@Data
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) {
this.map.put(key, value);
return this;
}

}

4)在Controller中创建登录方法

image-20221102140144318

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
/**
* 员工登录
* @param request
* @param employee
* @return
*/
@PostMapping("/login")
public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){

// 1.将页面提交的密码password进行md5加密处理
String password=employee.getPassword();
DigestUtils.md5DigestAsHex(password.getBytes());

// 2.根据页面提交的用户名username查询数据库
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Employee::getUpdateTime,employee.getUsername());
Employee emp = employeeService.getOne(queryWrapper);

// 3.如果没有查询到则返回登录失败结果
if(emp==null){
return R.error("登录失败");
}

// 4.密码比对,如果不一致则返回登录失败
if(!emp.getPassword().equals(password)){
return R.error("登录失败");
}

// 5.查看员工状态,如果为已禁用状态,则返回员工已禁用结果
if (emp.getStatus()==0){
return R.error("账号已禁用");
}

// 6.登录成功,将员工id存入Session并返回登陆成功结果
request.getSession().setAttribute("employee",emp.getId());
return R.success(emp);
}

修改前端超时时间方便调试,并清除浏览器缓存

image-20221102144021812

登录测试

登录成功

image-20221102145713338

账号密码错误

image-20221102145905661

账号禁用

image-20221102150012340

后台退出功能

需求分析

image-20221102151147651

代码开发

1
2
3
4
5
@PostMapping("/logout")
public R<String> logout(HttpServletRequest request){
request.getSession().removeAttribute("employee");
return R.success("退出成功");
}

验证:

image-20221102162928587

2.员工管理业务开发

效果展示

image-20221102164027916

image-20221102164105514

完善登录功能

问题分析

image-20221102164324523

代码实现

image-20221102164459486

image-20221102164946971

创建过滤器

image-20221102173922296

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
64
65
66
67
package com.itheima.reggie.filter;

import com.alibaba.fastjson.JSON;
import com.itheima.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.logging.LogRecord;

@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
//路径匹配器,支持通配符
public static final AntPathMatcher PATH_MATCHER=new AntPathMatcher();

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request=(HttpServletRequest)servletRequest;
HttpServletResponse response=(HttpServletResponse)servletResponse;


//1、获取本次请求的URI
String requestURI = request.getRequestURI();
log.info("拦截到请求:{}",requestURI);
//定义不需要处理的请求路径
String[] urls=new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};

//2.判断本次请求是否需要处理
boolean check= check(urls,requestURI);

//3.如果不需要处理,则直接方形
if (check){
log.info("本次请求不需要处理",requestURI);
filterChain.doFilter(request,response);
return;
}
//4.判断登录状态,如果已经登录,则直接放行
if (request.getSession().getAttribute("employee")!=null){
log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
filterChain.doFilter(request,response);
return;
}
log.info("用户未登录");
//5.如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
}

private boolean check(String[] urls, String requestURI) {
for (String url:urls){
if (PATH_MATCHER.match(url,requestURI)){
return true;
}
}
return false;
}
}

启动类添加注解@ServletComponentScan

image-20221102174034585

演示效果

未登录状态下直接请求[瑞吉外卖管理端](http://localhost:8080/backend/index.html)

image-20221102174205079

新增员工

需求分析

image-20221103204327722

数据模型

image-20221103204610527

image-20221103204907141

代码开发

image-20221103204932886

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
/**
* 新增员工
* @param request
* @param employee
* @return
*/
@PostMapping
public R<String> save(HttpServletRequest request,Employee employee){
log.info("新增员工,员工信息:{}",employee.toString());
//设置初始密码123456,需要进行md5加密处理
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));

employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());

//获取当前登录用户的id
Long empId = (Long)request.getSession().getAttribute("employee");
employee.setCreateUser(empId);
employee.setUpdateUser(empId);

employeeService.save(employee);

return R.success("新增员工成功");

}

image-20221103215656433

image-20221103215758520

定义异常类

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
package com.itheima.reggie.common;


import lombok.extern.slf4j.Slf4j;
import org.omg.CORBA.PUBLIC_MEMBER;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import java.sql.SQLIntegrityConstraintViolationException;

@ControllerAdvice
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {

/**
* 异常处理方法
* @param ex
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.error(ex.getMessage());
if (ex.getMessage().contains("Duplicate entry")){
String[] split = ex.getMessage().split(" ");
String msg = split[2]+"已存在";
return R.error(msg);
}
return R.error("失败了");
}
}

image-20221104122116103

效果

image-20221104122154880

image-20221104122521515

员工信息分页查询

需求分析

image-20221104142652236

代码开发

image-20221104142847268

配置MP的分页插件

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

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* 配置MP的分页插件
*/
@Configuration
public class MybatisPlusConfig {

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}

image-20221104150140458

编写分页接口

1
2
3
4
5
6
7
8
9
10
11
12
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
//构造分页构造器
Page<Employee> pageInfo = new Page<>(page,pageSize);
//构造条件构造器
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
//添加过滤条件
queryWrapper.like(StringUtils.isNotBlank(name),Employee::getName,name);
//添加排序条件
employeeService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}

效果

image-20221104152321531

image-20221104152337263

启用/禁用员工账号

需求分析

image-20221104153501608

image-20221104153540185

image-20221104153638758

代码开发

image-20221104153709080

image-20221104154042965

image-20221104154128808

image-20221104154630285

编写更新接口

1
2
3
4
5
6
7
8
9
10
11
@PutMapping
public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
log.info(employee.toString());

Long empId =(Long) request.getSession().getAttribute("employee");
employee.setUpdateUser(empId);
employee.setUpdateTime(LocalDateTime.now());
employeeService.updateById(employee);

return R.success("员工信息修改成功");
}

image-20221104164039545

image-20221104164213608

image-20221104164236300

image-20221104164457087

拷贝消息转换器

image-20221104164710276

image-20221104164915310

配置扩展mvc框架的消息转换器

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 扩展mvc框架的消息转换器
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//创建消息转换器
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用jackson将Java对象转换为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0,messageConverter);
}

image-20221104170101932

效果

image-20221104170519064

编辑员工信息

需求分析

image-20221104170728020

代码开发

image-20221104170746991

image-20221104180340877

编写查询详情接口

1
2
3
4
5
6
7
8
9
10
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id){
log.info("根据id查询员工信息...");
Employee employee = employeeService.getById(id);
if (employee!=null){
return R.success(employee);
}else {
return R.error("没有查询到对应的员工信息");
}
}

3.分类管理业务

效果展示

image-20221104201612375

公共字段自动填充

问题分析

image-20221104201938676

代码实现

image-20221104202215456

image-20221104202742328

添加@TableField注解

1
2
3
4
5
6
7
8
9
10
11
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;

@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;

@TableField(fill = FieldFill.INSERT)
private Long createUser;

@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;

image-20221104202806866

image-20221104205304230

image-20221104205416007

image-20221104205946570

image-20221104210307481

编写baseContext工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.itheima.reggie.common;

/**
* 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
*/
public class BaseContext {
private static ThreadLocal<Long> threadLocal= new ThreadLocal<>();

public static void setCurrentId(Long id){
threadLocal.set(id);
}

public static Long getCurrentId(){
return threadLocal.get();
}
}

在拦截器中设置threadlocal变量

image-20221104212002309

完善自动填充

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
package com.itheima.reggie.common;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;


@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {


/**
* 插入操作,自动填充
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充【insert】....");
metaObject.setValue("creatTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("creatUser", BaseContext.getCurrentId());
metaObject.setValue("updateUser", BaseContext.getCurrentId());

}

/**
* 修改操作,自动填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充【update】....");
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser", BaseContext.getCurrentId());
}
}

取消手动填充

image-20221104212207534

演示

image-20221104213926639

image-20221104214108671

新增分类

需求分析

image-20221105162932019

image-20221105163102177

数据模型

image-20221105163245262

代码开发

image-20221105163421685

image-20221105165654880

image-20221105165709164

1
2
3
4
5
6
7
8
9
10
11
/**
* 新增分类
* @param category
* @return
*/
@PostMapping
public R<String> save(@RequestBody Category category){
log.info("category:{}",category);
categoryService.save(category);
return R.success("新增分类成功");
}

分类信息的分页查询

需求分析

image-20221105170716656

代码开发

image-20221105170740877

1
2
3
4
5
6
7
8
9
10
11
12
@GetMapping("/page")
public R<Page> page(int page,int pageSize){
//分页构造器
Page<Category> pageInfo = new Page<>(page, pageSize);
//条件构造器
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
//添加排序条件,根据sort进行排序
queryWrapper.orderByAsc(Category::getSort);
//进行分页查询
categoryService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}

删除分类

需求分析

image-20221105172302655

代码开发

image-20221105172347617

1
2
3
4
5
6
7
8
9
10
11

/**
* 根据id删除分类
* @param id
* @return
*/
@DeleteMapping
public R<String> delete(Long id){
log.info("删除分类,id为:{}",id);
return R.success("分类信息删除成功");
}

功能完善

image-20221105172615362

定义业务异常类

1
2
3
4
5
6
7
8
9
10
11
12
package com.itheima.reggie.common;

/**
* 自定义业务异常
*/
public class CustomException extends RuntimeException{

public CustomException (String message){
super(message);
}
}

image-20221106155650159

处理业务异常

1
2
3
4
5
6
7
8
9
10
/**
* 异常处理方法
* @param ex
* @return
*/
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException ex){
log.error(ex.getMessage());
return R.error(ex.getMessage());
}

image-20221106155708529

定义删除方法

1
2
3
4
5
6
7
8
9
package com.itheima.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.Category;

public interface CategoryService extends IService<Category> {

void remove(Long id);
}

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
package com.itheima.reggie.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.common.CustomException;
import com.itheima.reggie.entity.Category;
import com.itheima.reggie.entity.Dish;
import com.itheima.reggie.entity.Setmeal;
import com.itheima.reggie.mapper.CategoryMapper;
import com.itheima.reggie.service.CategoryService;
import com.itheima.reggie.service.DishService;
import com.itheima.reggie.service.SetmealService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {

@Autowired
private DishService dishService;

@Autowired
private SetmealService setmealService;
/**
* 根据id删除分类,删除之前需要进行判断
* @param id
*/
@Override
public void remove(Long id) {
//查询当前分类是否关联了菜品,如果已经关联,抛出一个异常
LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
//添加查询条件,根据分类id进行查询
dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
int count1 = dishService.count(dishLambdaQueryWrapper);
if (count1>1){
//已经关联菜品,抛出一个业务异常
throw new CustomException("当前分类下关联了菜品,不能删除");
}
//查询当前分是否关联了套餐,如果已经关联,抛出一个异常
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
//添加查询条件,根据分类id进行查询
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
int count2 = setmealService.count(setmealLambdaQueryWrapper);
if (count2>0){
//已经关联套餐,抛出一个业务异常
throw new CustomException("当前分类下关联了套餐,不能删除");
}
//正常删除分类
super.removeById(id);
}
}

调用删除方法

1
2
3
4
5
6
7
8
9
10
11
/**
* 根据id删除分类
* @param id
* @return
*/
@DeleteMapping
public R<String> delete(Long id){
log.info("删除分类,id为:{}",id);
categoryService.remove(id);
return R.success("分类信息删除成功");
}

修改分类

需求分析

image-20221106160040285

代码开发

1
2
3
4
5
6
7
8
9
10
11
/**
* 根据id修改分类信息
* @param category
* @return
*/
@PutMapping
public R<String> update(@RequestBody Category category){
log.info("修改分类信息:{}",category);
categoryService.updateById(category);
return R.success("修改分类信息成功");
}

4.菜品管理业务开发

image-20221106162407984

image-20221106181217021

文件上传下载

文件上传介绍

image-20221106204412870

image-20221106204518698

image-20221106204553132

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
@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {

@Value("${reggie.path}")
private String basePath;


/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
public R<String> upload(MultipartFile file){
//file文件是一个临时文件,需要转存在指定位置,否则本次请求完成后临时文件会删除
log.info(file.toString());
//原始文件名
String originalFileName =file.getOriginalFilename();//abc.jpg
String suffix=originalFileName.substring(originalFileName.lastIndexOf("."));

//使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
String fileName= UUID.randomUUID().toString()+suffix;//dfdsfds.jpg

//创建一个目录对象
File dir=new File(basePath);
if (!dir.exists()){
//目录不存在,需要创建
dir.mkdirs();
}
try {
//将临时文件转存到指定位置
file.transferTo(new File(basePath+fileName));
} catch (IOException e) {
e.printStackTrace();
}
return R.success(fileName);
}
}

文件下载介绍

image-20221106204733897

文件上传代码实现

image-20221106204903990

image-20221106205122339

文件下载代码实现

image-20221107103725180

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
/**
* 文件下载
* @param name
* @param response
*/
@GetMapping("/download")
public void download(String name, HttpServletResponse response){
try {
//输入流,通过输入流读取文件内容
FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));
//输出流,通过输出流将文件写回浏览器在浏览器展示图片
ServletOutputStream outputStream = response.getOutputStream();
response.setContentType("image/jpge");
int len=0;
byte[] bytes = new byte[1024];
while((len=fileInputStream.read(bytes))!=-1){
outputStream.write(bytes,0,len);
outputStream.flush();
}
//关闭资源
outputStream.close();
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}


新增菜品

需求分析

image-20221107110235693

数据模型

image-20221107110304274

image-20221107110353525

代码开发

image-20221107110624649

image-20221107111050099

编写菜品分类查询列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 根据条件查询分类数据2
* @param category
* @return
*/
@GetMapping("/list")
public R<List<Category>> list(Category category){
//条件构造器
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
//添加条件
queryWrapper.eq(category.getType()!=null,Category::getType,category.getType());
//添加排序条件
queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);

List<Category> list = categoryService.list(queryWrapper);
return R.success(list);
}

image-20221107115053805

编写菜品以及其口味保存方法

1
2
3
4
5
public interface DishService extends IService<Dish> {

void saveWithFlavor(DishDto dishDto);

}
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
package com.itheima.reggie.service.impl;

import com.itheima.reggie.dto.DishDto;
import com.itheima.reggie.entity.Dish;
import com.itheima.reggie.entity.DishFlavor;
import com.itheima.reggie.mapper.DishMapper;
import com.itheima.reggie.service.DishFlavorService;
import com.itheima.reggie.service.DishService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

/**
* <p>
* 菜品管理 服务实现类
* </p>
*
* @author 威少
* @since 2022-11-05
*/
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {

@Autowired
private DishFlavorService dishFlavorService;

/**
* 新增菜品的同时保存对应的口味数据
* @param dishDto
*/
@Override
@Transactional
public void saveWithFlavor(DishDto dishDto) {
//保存菜品的基本信息到菜品表
this.save(dishDto);

Long dishId = dishDto.getId();//菜品

//菜品口味
List<DishFlavor> flavors = dishDto.getFlavors();
flavors = flavors.stream().map((item) -> {
item.setDishId(dishId);
return item;
}).collect(Collectors.toList());

//保存菜品口味数据到菜品口味表dish_flavors
dishFlavorService.saveBatch(flavors);
}
}

springboot开启事务支持

image-20221107140352624

controller层调用方法

1
2
3
4
5
6
7
8
9
10
11
/**
* 新增菜品
* @param dishDto
* @return
*/
@PostMapping
public R<String> save(@RequestBody DishDto dishDto){
log.info(dishDto.toString());
dishService.saveWithFlavor(dishDto);
return R.success("新增菜品成功");
}

菜品分页查询

需求分析

image-20221107140536800

代码开发

image-20221107140622474

编写分页查询

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
/**
* 分页查询菜品信息
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name){
//构造分页构造器对象
Page<Dish> pageInfo = new Page<>(page, pageSize);
Page<DishDto> dishDtoPage = new Page<>();
//条件构造器
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
//添加过滤条件
queryWrapper.like(name!=null,Dish::getName,name);
//添加排序条件
queryWrapper.orderByDesc(Dish::getUpdateTime);
//执行分页查询
dishService.page(pageInfo,queryWrapper);
//对象拷贝
BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");
List<Dish> records = pageInfo.getRecords();
List<DishDto> collect = records.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);
}
return dishDto;
}).collect(Collectors.toList());
dishDtoPage.setRecords(collect);
return R.success(dishDtoPage);
}

修改菜品

需求分析

image-20221107161751308

代码开发

image-20221107162023429

编写菜品信息回显代码

1
2
3
4
5
public interface DishService extends IService<Dish> {

DishDto getByIdWithFlavor(Long id);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 根据id查询菜品信息对应的口味信息
* @param id
* @return
*/
@Override
public DishDto getByIdWithFlavor(Long id) {
//查询菜品基本信息,从dish表查询
Dish dish = this.getById(id);

DishDto dishDto = new DishDto();
BeanUtils.copyProperties(dish,dishDto);

LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId,dish.getId());
List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);
dishDto.setFlavors(flavors);

return dishDto;


}

controller层调用方法

1
2
3
4
5
6
7
8
9
10
11
/**
* 根据id查询菜品信息和对应的口味信息
* @param id
* @return
*/
@GetMapping("/{id}")
public R<DishDto> get(@PathVariable Long id){

DishDto byIdWithFlavor = dishService.getByIdWithFlavor(id);
return R.success(byIdWithFlavor);
}

演示

image-20221107164923137

正常回显

编写更新方法

1
void updateWithFlavor(DishDto dishDto);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
@Transactional
public void updateWithFlavor(DishDto dishDto) {
//更新dish表信息
this.updateById(dishDto);
//清理当前菜品对应口味数据--dish_flavor标的delete操作
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());

dishFlavorService.remove(queryWrapper);

//添加当前提交过来的口味数据--dish_flavor表的inster操作
List<DishFlavor> flavors = dishDto.getFlavors();
List<DishFlavor> collect = flavors.stream().map(flavor -> {
flavor.setDishId(dishDto.getId());
return flavor;
}).collect(Collectors.toList());
dishFlavorService.saveBatch(collect);
}

controller层调用

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 修改菜品
* @param dishDto
* @return
*/
@PutMapping
public R<String> update(@RequestBody DishDto dishDto){
log.info(dishDto.toString());

dishService.updateWithFlavor(dishDto);

return R.success("新增菜品成功");
}

删除菜品

DishController.java

1
2
3
4
5
6
7
8
9
10
/**
* 删除菜品
* @param ids
* @return
*/
@DeleteMapping
public R<String> delete(String ids){
dishService.deleteWithFlavor(ids);
return R.success("删除成功");
};

DishService.java

1
void deleteWithFlavor(String ids);

DishServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
@Transactional
public void deleteWithFlavor(String ids) {
List<String> stringList= Arrays.asList(ids.split(","));
stringList.forEach(item->{
Long id=Long.parseLong(item);
//删除菜品
this.removeById(id);
//删除口味
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId,id);
dishFlavorService.remove(queryWrapper);
});
}

5.套餐管理业务开发

效果展示

image-20221108160229477

image-20221108160138452

image-20221108160204854

新增套餐

需求分析

image-20221108160411804

数据模型

image-20221108160445169

image-20221108160516755

image-20221108160613679

开发工作

image-20221108160628295

image-20221108171529341

完成根据菜品分类获取对应菜品数据的接口

DishController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 根据条件查询对应的菜品数据
* @param dish
* @return
*/
@GetMapping("/list")
public R<List<Dish>> list(Dish dish){
//构造查询条件
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
//添加条件,查询状态为1(起售状态)的菜品
queryWrapper.eq(Dish::getStatus,1);
//添加排序条件
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

List<Dish> list = dishService.list(queryWrapper);
return R.success(list);
}

完成套餐表单提交接口

编写保存方法

setmealService

1
void saveWithDish(SetmealDto setmealDto);

SetmealServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Transactional
@Override
public void saveWithDish(SetmealDto setmealDto) {
//保存套餐的基本信息,操作setmeal,执行insert操作
this.save(setmealDto);

List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
setmealDishes.stream().map(setmealDish -> {
setmealDish.setSetmealId(setmealDto.getId());
return setmealDish;
}).collect(Collectors.toList());

//保存套餐和菜品的关联信息,操作setmeal_dish,执行insert操作
setmealDishService.saveBatch(setmealDishes);
}

SetmealController.java

1
2
3
4
5
6
7
8
9
10
11
/**
* 新增套餐
* @param setmealDto
* @return
*/
@PostMapping()
public R<String> save (@RequestBody SetmealDto setmealDto){
log.info("套餐信息:{}",setmealDto);
setmealService.saveWithDish(setmealDto);
return R.success("新增套餐成功");
}

套餐信息分页查询

需求分析

image-20221108175546635

代码开发

image-20221108175656941

编写分页接口

SetmealController.java

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
/**
* 套餐分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){

//分页构造器
Page<Setmeal> pageInfo = new Page<>(page, pageSize);
Page<SetmealDto> dtoPage = new Page<SetmealDto>();
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
//添加查询条件,根据name进行like模糊查询
queryWrapper.like(name!=null,Setmeal::getName,name);
//添加查询条件,根据更新时间降序排列
queryWrapper.orderByDesc(Setmeal::getUpdateTime);
setmealService.page(pageInfo,queryWrapper);

//对象的拷贝
BeanUtils.copyProperties(pageInfo,dtoPage,"records");
List<Setmeal> records = pageInfo.getRecords();
List<SetmealDto> collect = records.stream().map(item -> {
SetmealDto setmealDto = new SetmealDto();
//对象拷贝
BeanUtils.copyProperties(item, setmealDto);
//分类id
Long categoryId = item.getCategoryId();
//根据分类id查询分类对象
Category category = categoryService.getById(categoryId);
if (category != null) {
//分类名称
String categoryName = category.getName();
setmealDto.setCategoryName(categoryName);
}
return setmealDto;
}).collect(Collectors.toList());
dtoPage.setRecords(collect);
return R.success(dtoPage);
}

删除套餐

需求分析

image-20221108213145244

代码开发

image-20221108213213278

SetmealService

1
void removeWithDish(List<Long> ids);

SetmealServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Transactional
@Override
public void removeWithDish(List<Long> ids) {

LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(Setmeal::getId,ids);
queryWrapper.eq(Setmeal::getStatus,1);
int count = this.count(queryWrapper);
if (count>0){
throw new CustomException("套餐正在售卖中,不能删除");
}
//如果可以删,先删除套餐表中的数据---setmeal
this.removeByIds(ids);

LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);
//删除关系表中的数据----setmeak——dish
setmealDishService.remove(lambdaQueryWrapper);
}

SetmealController.java

1
2
3
4
5
6
7
8
9
10
11
/**
* 删除套餐
* @param ids
* @return
*/
@DeleteMapping
public R<String> delete(@RequestParam List<Long> ids){
log.info("ids:{}",ids);
setmealService.removeWithDish(ids);
return R.success("套餐数据删除成功");
}

更新套餐

编写获取详情接口

SetmealController.java

1
2
3
4
5
6
7
8
9
10
11
12
@GetMapping("/{id}")
public R<SetmealDto> getDetail(@PathVariable String id){
log.info(id);
Setmeal byId = setmealService.getById(id);
SetmealDto setmealDto = new SetmealDto();
BeanUtils.copyProperties(byId,setmealDto);
LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SetmealDish::getSetmealId,id);
List<SetmealDish> list = setmealDishService.list(queryWrapper);
setmealDto.setSetmealDishes(list);
return R.success(setmealDto);
}

编写更新接口

SetmealService

1
void updateWithDish(SetmealDto setmealDto);

SetmealServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
@Transactional
public void updateWithDish(SetmealDto setmealDto) {
//保存套餐的基本信息,操作setmeal,执行update操作
this.updateById(setmealDto);
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
setmealDishes.stream().map(setmealDish -> {
setmealDish.setSetmealId(setmealDto.getId());
return setmealDish;
}).collect(Collectors.toList());

//删除旧的关联系信息
LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SetmealDish::getSetmealId,setmealDto.getId());
setmealDishService.remove(queryWrapper);

//保存套餐和菜品的关联信息,操作setmeal_dish,执行insert操作
setmealDishService.saveBatch(setmealDishes);
}

SetmealController.java

1
2
3
4
5
6
@PutMapping()
public R<String> update(@RequestBody SetmealDto setmealDto){
log.info("套餐信息:{}",setmealDto);
setmealService.updateWithDish(setmealDto);
return R.success("更新套餐成功");
}

更新套餐状态

编写更新状态接口

1
2
3
4
5
6
7
8
9
10
11
@PostMapping("/status/{status}")
public R<String> changeStatus(@PathVariable Integer status,@RequestParam List<Long> ids){
List<Setmeal> collect = ids.stream().map(item -> {
Setmeal setmeal = new Setmeal();
setmeal.setStatus(status);
setmeal.setId(item);
return setmeal;
}).collect(Collectors.toList());
setmealService.updateBatchById(collect);
return R.success("状态更新成功");
}

6.手机验证码登录

效果展示

image-20221109162126441

短信服务介绍

image-20221109162237927

阿里云短信服务

image-20221109163025134

注册账号

image-20221109163237187

设置短信签名

image-20221109163629269

image-20221109164842912

完成申请后,等待审核

image-20221109165203932

审核无法通过 可以采用测试api

image-20221111130617488

image-20221111130732697

赠送了一个模板

image-20221109165250598

设置AccessKey

image-20221109164059519

image-20221109165333823

注意:accesskeyid 和secret需要及时保存,secret只会出现一次,也可以重新创建accesskey

编辑accesskey对应的权限

image-20221109164543334

代码开发

image-20221109213053688

image-20221109213107072

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.16</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>2.1.0</version>
</dependency>

image-20221109213129606

需求分析

image-20221109213532440

数据模型

image-20221109213721665

代码开发

image-20221109213837227

image-20221109214006572

image-20221109215240468

编写短信发送接口

UserController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession httpSession){
//获取手机号
String phone = user.getPhone();
if (StringUtils.isNotBlank(phone)){
//生成随机的4位验证码
String code = ValidateCodeUtils.generateValidateCode(4).toString();
log.info("code={}",code);

//调用阿里云提供的短信服务测试API完成发送短信
SMSUtils.sendMessage("阿里云短信测试","SMS_154950909",phone,code);
//需要将生成的验证码保存到Session中
httpSession.setAttribute(phone,code);
return R.success("手机验证码短信发送成功");
}

return R.error("短信发送失败");
}

image-20221111131320292

编写登录接口

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
/**
* 移动端用户登录
* @param map
* @param httpSession
* @return
*/
@PostMapping("/login")
public R<Object> login(@RequestBody Map map, HttpSession httpSession){
log.info(map.toString());
//获取手机
String phone=map.get("phone").toString();
//获取验证码
String code=map.get("code").toString();
//从session中获取保存的验证码
Object codeInSession = httpSession.getAttribute(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);
}
httpSession.setAttribute("user",user.getId());
return R.success(user);
}
return R.error("登陆成功失败");
}

7.菜品展示、购物车、下单

效果展示

image-20221111145007769

导入用户地址簿相关功能代码

需求分析

image-20221111145244062

数据模型

image-20221111145300709

导入功能代码

image-20221111145829438

controller层直接导入

测试效果

image-20221111161623862

菜品展示

需求分析

image-20221111160255868

代码开发-梳理交互过程

image-20221111160415240

代码开发

修改DishController.java下的/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
@GetMapping("/list")
public R<List<DishDto>> list(Dish dish){
//构造查询条件
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
//添加条件,查询状态为1(起售状态)的菜品
queryWrapper.eq(Dish::getStatus,1);
//添加排序条件
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

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

List<DishDto> collect = 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> queryWrapper1 = new LambdaQueryWrapper<>();
queryWrapper1.eq(DishFlavor::getDishId,dishId);
List<DishFlavor> list1 = dishFlavorService.list(queryWrapper1);
dishDto.setFlavors(list1);
return dishDto;
}).collect(Collectors.toList());
return R.success(collect);
}

效果

image-20221111162703131

编写根据套餐获取菜品信息接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 根据条件查询套餐数据
* @param setmeal
* @return
*/
@GetMapping("/list")
public R<List<Setmeal>> list(Setmeal setmeal){
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(setmeal.getCategoryId()!=null,Setmeal::getCategoryId,setmeal.getCategoryId());
queryWrapper.eq(setmeal.getStatus()!=null,Setmeal::getStatus,setmeal.getStatus());
queryWrapper.orderByDesc(Setmeal::getUpdateTime);

List<Setmeal> list = setmealService.list(queryWrapper);
return R.success(list);

}

效果

image-20221111163730915

购物车

需求分析

image-20221111163844080

数据模型

image-20221111164001731

代码开发-梳理交互过程

image-20221111164155684

代码开发-准备工作

image-20221111164549451

接口1:购物车新增

ShoppingCartController

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
@PostMapping("/add")
public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart){
log.info("购物车数据:{}",shoppingCart);
//设置用户id,指定当前是哪个用户的购物车数据
Long currentId = BaseContext.getCurrentId();
shoppingCart.setUserId(currentId);
Long dishId = shoppingCart.getDishId();
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId,currentId);
if (dishId!=null){
//添加到购物车的是菜品
queryWrapper.eq(ShoppingCart::getDishId,dishId);
}else {
//添加到购物车的是套餐
queryWrapper.eq(ShoppingCart::getSetmealId,shoppingCart.getSetmealId());
}
//查询当前菜品或者套餐是否在购物车中
ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);

if (cartServiceOne!=null){
//如果已经存在,就在原来数量基础上加一
Integer number = cartServiceOne.getNumber();
cartServiceOne.setNumber(number+1);
shoppingCartService.updateById(cartServiceOne);
}else {
//如果不存在,则添加到购物车数量默认就是一
shoppingCart.setNumber(1);
shoppingCartService.save(shoppingCart);
cartServiceOne=shoppingCart;
}
return R.success(cartServiceOne);
}

接口2:获取购物车列表

1
2
3
4
5
6
7
8
9
@GetMapping("/list")
public R<List<ShoppingCart>> list(){
log.info("查看购物车...");
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
queryWrapper.orderByAsc(ShoppingCart::getCreateTime);
List<ShoppingCart> list = shoppingCartService.list(queryWrapper);
return R.success(list);
}

接口3:清空购物车

1
2
3
4
5
6
7
8
9
10
11
/**
* 清空购物车
* @return
*/
@DeleteMapping("/clean")
public R<String> clean(){
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
shoppingCartService.remove(queryWrapper);
return R.success("清空成功!");
}

接口4:购物车商品减数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 购物车减数
* @return
*/
@PostMapping("/sub")
public R<String> sub(@RequestBody Map map){
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
if (map.get("dishId")!=null){
queryWrapper.eq(ShoppingCart::getDishId,map.get("dishId"));

}else {
queryWrapper.eq(ShoppingCart::getDishId,map.get("setmealId"));
}
queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);
if (cartServiceOne.getNumber()>1){
cartServiceOne.setNumber(cartServiceOne.getNumber()-1);
shoppingCartService.updateById(cartServiceOne);
}else {
shoppingCartService.removeById(cartServiceOne);
}
return R.success("减数成功!");
}

用户下单

需求分析

image-20221111203151150

数据模型

image-20221111203420431

image-20221111203456363

image-20221111205745436

代码开发-梳理交互过程

image-20221111205825918

代码开发-准备工作

image-20221111210238470

接口1:下单接口

OrdersService

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* <p>
* 订单表 服务类
* </p>
*
* @author 威少
* @since 2022-11-11
*/
public interface OrdersService extends IService<Orders> {

void submit(Orders orders);
}

OrdersServiceImpl

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/**
* <p>
* 订单表 服务实现类
* </p>
*
* @author 威少
* @since 2022-11-11
*/
@Service
@Slf4j
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements OrdersService {


@Autowired
private ShoppingCartService shoppingCartService;

@Autowired
private UserService userService;

@Autowired
private AddressBookService addressBookService;

@Autowired
private OrderDetailService orderDetailService;
/**
* 用户下单
* @param orders
*/
@Override
@Transactional
public void submit(Orders orders) {
//获得当前用户id
Long userId = BaseContext.getCurrentId();

//查询当前用户的购物车数据
LambdaQueryWrapper<ShoppingCart> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ShoppingCart::getUserId,userId);
List<ShoppingCart> shoppingCarts = shoppingCartService.list(wrapper);

if(shoppingCarts == null || shoppingCarts.size() == 0){
throw new CustomException("购物车为空,不能下单");
}

//查询用户数据
User user = userService.getById(userId);

//查询地址数据
Long addressBookId = orders.getAddressBookId();
AddressBook addressBook = addressBookService.getById(addressBookId);
if(addressBook == null){
throw new CustomException("用户地址信息有误,不能下单");
}

long orderId = IdWorker.getId();//订单号

AtomicInteger amount = new AtomicInteger(0);

List<OrderDetail> orderDetails = shoppingCarts.stream().map((item) -> {
OrderDetail orderDetail = new OrderDetail();
orderDetail.setOrderId(orderId);
orderDetail.setNumber(item.getNumber());
orderDetail.setDishFlavor(item.getDishFlavor());
orderDetail.setDishId(item.getDishId());
orderDetail.setSetmealId(item.getSetmealId());
orderDetail.setName(item.getName());
orderDetail.setImage(item.getImage());
orderDetail.setAmount(item.getAmount());
amount.addAndGet(item.getAmount().multiply(new BigDecimal(item.getNumber())).intValue());
return orderDetail;
}).collect(Collectors.toList());


orders.setId(orderId);
orders.setOrderTime(LocalDateTime.now());
orders.setCheckoutTime(LocalDateTime.now());
orders.setStatus(2);
orders.setAmount(new BigDecimal(amount.get()));//总金额
orders.setUserId(userId);
orders.setNumber(String.valueOf(orderId));
orders.setUserName(user.getName());
orders.setConsignee(addressBook.getConsignee());
orders.setPhone(addressBook.getPhone());
orders.setAddress((addressBook.getProvinceName() == null ? "" : addressBook.getProvinceName())
+ (addressBook.getCityName() == null ? "" : addressBook.getCityName())
+ (addressBook.getDistrictName() == null ? "" : addressBook.getDistrictName())
+ (addressBook.getDetail() == null ? "" : addressBook.getDetail()));
//向订单表插入数据,一条数据
this.save(orders);

//向订单明细表插入数据,多条数据
orderDetailService.saveBatch(orderDetails);

//清空购物车数据
shoppingCartService.remove(wrapper);
}
}

OrdersController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* <p>
* 订单表 前端控制器
* </p>
*
* @author 威少
* @since 2022-11-11
*/
@RestController
@RequestMapping("/order")
@Slf4j
public class OrdersController {

@Autowired
private OrdersService ordersService;

@PostMapping("/submit")
public R<String> submit(@RequestBody Orders orders){
log.info("订单数据:{}",orders);
ordersService.submit(orders);
return R.success("下单成功");
}
}

分页查询历史订单

OrdersController

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
@GetMapping("/userPage")
public R<Page<OrderDto>> userPage(int page,int pageSize){

Page<Orders> ordersPage = new Page<>(page,pageSize);

LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>();

queryWrapper.eq(Orders::getUserId, BaseContext.getCurrentId());

Page<Orders> ordersPage1 = ordersService.page(ordersPage, queryWrapper);
List <Orders> ordersList=ordersPage1.getRecords();
Page<OrderDto> objectPage = new Page<>();
BeanUtils.copyProperties(ordersPage1,objectPage,"records");

List<OrderDto> collect = ordersList.stream().map(item -> {
OrderDto orderDto = new OrderDto();
BeanUtils.copyProperties(item, orderDto);
LambdaQueryWrapper<OrderDetail> orderDetailLambdaQueryWrapper = new LambdaQueryWrapper<>();
orderDetailLambdaQueryWrapper.eq(OrderDetail::getOrderId, item.getId());
List<OrderDetail> list = orderDetailService.list(orderDetailLambdaQueryWrapper);
orderDto.setOrderDetails(list);
orderDto.setSumNum(list.size());
return orderDto;
}).collect(Collectors.toList());

objectPage.setRecords(collect);
return R.success(objectPage);
}

8.订单明细模块

接口1.分页查询订单

1
2
3
4
5
6
7
8
9
10
@GetMapping("/page")
public R<Page> page(int page, int pageSize, Long number, String beginTime, String endTime){
Page<Orders> ordersPage = new Page<>(page,pageSize);
LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(number!=null,Orders::getNumber,number);
queryWrapper.between(beginTime!=null&&endTime!=null,Orders::getOrderTime,beginTime,endTime);
queryWrapper.orderByDesc(Orders::getOrderTime);
Page<Orders> page1 = ordersService.page(ordersPage, queryWrapper);
return R.success(page1);
}

接口2.更新订单信息

1
2
3
4
5
@PutMapping
public R<String> put(@RequestBody Orders orders){
boolean b = ordersService.updateById(orders);
return R.success("更新成功");
}

9.缓存优化

问题说明

image-20221112161034671

使用git管理项目

创建仓库

image-20221112161455301

将项目纳入到仓库管理

image-20221112161739606

image-20221112221206876

添加gitignore文件

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
.git
logs
rebel.xml
target/
!.mvn/wrapper/maven-wrapper.jar
log.path_IS_UNDEFINED
.DS_Store
offine_user.md

### STS ###

.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
.nb-gradle/
generatorConfig.xml

Add 项目

image-20221112213631389

commit项目

image-20221112213928241

image-20221112221527479

定义远程仓库

image-20221112214245134

image-20221112214343762

推送项目至远程仓库

image-20221112214427523

如果.idea文件再ignore中添加了依旧备推送至仓库中,则打开控制台输入以下指令

1
2
3
4
5
6
# 清除.idea文件夹的git缓存  
git rm -r --cached .idea
# 提交
git commit -m 'delete .ides'
# 推送
git push

刷新仓库会返现.idea文件夹消失了。

image-20221112220534405

创建新的分支v1.0

image-20221112215333162

image-20221112215218228

推送分支v1.0

image-20221112215518875

image-20221112220555785

环境搭建

maven坐标

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

填写redis配置文件

image-20221113112341721

配置类

1
2
3
4
5
6
7
8
9
10
11
12
@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.setConnectionFactory(connectionFactory);
return redisTemplate;
}
}

image-20221113113149743

缓存短信验证码

实现思路

image-20221113113706047

代码改造

注入RedisTemplate

1
2
@Autowired
private RedisTemplate redisTemplate;

修改短信发送接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession httpSession){
//获取手机号
String phone = user.getPhone();
if (StringUtils.isNotBlank(phone)){
//生成随机的4位验证码
String code = ValidateCodeUtils.generateValidateCode(4).toString();
log.info("code={}",code);

//调用阿里云提供的短信服务API完成发送短信
SMSUtils.sendMessage("阿里云短信测试","SMS_154950909",phone,code);
//需要将生成的验证码保存到Session中
// httpSession.setAttribute(phone,code);
//将生成的验证码缓存到redis中,并且设置有效期为5分钟
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
 /**
* 移动端用户登录
* @param map
* @param httpSession
* @return
*/
@PostMapping("/login")
public R<Object> login(@RequestBody Map map, HttpSession httpSession){
log.info(map.toString());
//获取手机
String phone=map.get("phone").toString();
//获取验证码
String code=map.get("code").toString();
//从session中获取保存的验证码
// Object codeInSession = httpSession.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);
}
httpSession.setAttribute("user",user.getId());
//如果用户登录成功,删除Redis中缓存的验证码
redisTemplate.delete(phone);
return R.success(user);
}
return R.error("登陆成功失败");
}

演示

发送短信观察redis服务器

image-20221113120952143

输入验证码登录后观察redis服务器

image-20221113121102293

缓存菜品数据

实现思路

image-20221113142513825

代码改造

改造查询方法

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
@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());
//添加条件,查询状态为1(起售状态)的菜品
queryWrapper.eq(Dish::getStatus,1);
//添加排序条件
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

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

List<DishDto> collect = 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> queryWrapper1 = new LambdaQueryWrapper<>();
queryWrapper1.eq(DishFlavor::getDishId,dishId);
List<DishFlavor> list1 = dishFlavorService.list(queryWrapper1);
dishDto.setFlavors(list1);
return dishDto;
}).collect(Collectors.toList());

//如果不存在,需要查询数据库,将查询到的菜品数据缓存到Redis中
redisTemplate.opsForValue().set(key,collect,60, TimeUnit.MINUTES);
return R.success(collect);
}

改造保存更新方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@PutMapping
public R<String> update(@RequestBody DishDto dishDto){
log.info(dishDto.toString());

dishService.updateWithFlavor(dishDto);

//清理所有菜品的缓存数据
//Set keys = redisTemplate.keys("dish_*");
//redisTemplate.delete(keys);
//清理某个分类下面的菜品缓存数据
String keys="dish_"+dishDto.getCategoryId()+"_1";
redisTemplate.delete(keys);
return R.success("新增菜品成功");
}
1
2
3
4
5
6
7
8
9
10
11
12
@PostMapping
public R<String> save(@RequestBody DishDto dishDto){
log.info(dishDto.toString());
dishService.saveWithFlavor(dishDto);
//清理所有菜品的缓存数据
//Set keys = redisTemplate.keys("dish_*");
//redisTemplate.delete(keys);
//清理某个分类下面的菜品缓存数据
String keys="dish_"+dishDto.getCategoryId()+"_1";
redisTemplate.delete(keys);
return R.success("新增菜品成功");
}

改造删除和更新状态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@DeleteMapping
public R<String> delete(String ids){
String[] split = ids.split(",");
List<Long> collect = Arrays.stream(split).map(item -> {
Long id = Long.parseLong(item);
return id;
}).collect(Collectors.toList());
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(Dish::getId,collect);
queryWrapper.select(Dish::getCategoryId).groupBy(Dish::getCategoryId);
List<Dish> dishList=dishService.list(queryWrapper);
dishList.forEach(dish->{
//清理某个分类下面的菜品缓存数据
String keys="dish_"+dish.getCategoryId()+"_1";
redisTemplate.delete(keys);
});
dishService.deleteWithFlavor(ids);
return R.success("删除成功");
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 停用菜品
* @param ids
* @return
*/
@PostMapping("/status/{status}")
public R<String> changeStatus(@PathVariable Integer status, String ids){
String[] split = ids.split(",");
List<Long> collect = Arrays.stream(split).map(item -> {
Long id = Long.parseLong(item);
return id;
}).collect(Collectors.toList());
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(Dish::getId,collect);
queryWrapper.select(Dish::getCategoryId).groupBy(Dish::getCategoryId);
List<Dish> dishList=dishService.list(queryWrapper);
dishList.forEach(dish->{
//清理某个分类下面的菜品缓存数据
String keys="dish_"+dish.getCategoryId()+"_1";
redisTemplate.delete(keys);
});
dishService.changeStatus(status,ids);
return R.success("更新状态成功");
};

Spring Cache

Spring Cache 介绍

image-20221113183929464

Spring Cache 常用注解

image-20221113184049865

Spring Cache使用

开启缓存注解功能

注入CacheManger

image-20221113191514851

添加缓存和删除缓存-@CachePut、@CacheEvict

image-20221113191119763

image-20221113191344814

查询缓存-@Cacheable

image-20221113192206936

image-20221113192547514

修改

image-20221113193953428

多参数作为key进行缓存

image-20221113193125939

使用redis作为缓存产品

image-20221113193406176

缓存套餐数据

实现思路

image-20221113194231081

代码改造

导入maven坐标

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

配置过期时间

image-20221113201258749

开启缓存

image-20221113201416839

加入@Cacheable注解

image-20221113201558417

1
2
3
4
5
6
7
8
9
10
11
12
@GetMapping("/list")
@Cacheable(value = "setmealCache",key = "#setmeal.categoryId+'_'+#setmeal.status")
public R<List<Setmeal>> list(Setmeal setmeal){
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(setmeal.getCategoryId()!=null,Setmeal::getCategoryId,setmeal.getCategoryId());
queryWrapper.eq(setmeal.getStatus()!=null,Setmeal::getStatus,setmeal.getStatus());
queryWrapper.orderByDesc(Setmeal::getUpdateTime);

List<Setmeal> list = setmealService.list(queryWrapper);
return R.success(list);

}

返回类R实现序列化接口

image-20221113202110097

加入@CacheEvict注解

删除指定分类下所有的缓存

1
2
3
4
5
6
7
@DeleteMapping
@CacheEvict(value = "setmealCache",allEntries = true)
public R<String> delete(@RequestParam List<Long> ids){
log.info("ids:{}",ids);
setmealService.removeWithDish(ids);
return R.success("套餐数据删除成功");
}
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 新增套餐
* @param setmealDto
* @return
*/
@PostMapping()
@CacheEvict(value = "setmealCache",allEntries = true)
public R<String> save (@RequestBody SetmealDto setmealDto){
log.info("套餐信息:{}",setmealDto);
setmealService.saveWithDish(setmealDto);
return R.success("新增套餐成功");
}

10.读写分离

image-20221114111917815

image-20221114112001033

主从复制

介绍

image-20221114112414733

配置-前置条件

image-20221114115436205

配置-主库Master

image-20221114115638374

Ubuntu-Mysql8.0 修改配置文件路径

1
/etc/mysql/mysql.conf.d/mysqld.cnf

查看主服务器配置文件:sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf,看第83行:确认log_bin和server-id已经取消注释,设置无错误。

image-20221115135826754

image-20221114152438014

Mysql8.0 重启命令

1
2
3
4
5
6
weishao@ubuntuno2:/etc/mysql/mysql.conf.d$ service mysql restart
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===
Authentication is required to restart 'mysql.service'.
Authenticating as: weishao
Password:
==== AUTHENTICATION COMPLETE ===

image-20221114152505733

mysql 8.0 配置方法

1
2
3
4
5
6
7
8
mysql> create user 'slave'@'10.211.55.4' identified by '123456';
Query OK, 0 rows affected (0.03 sec)

mysql> grant all privileges on *.* to 'slave'@'10.211.55.4' with grant option;
Query OK, 0 rows affected (0.00 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

image-20221114152703758

1
2
3
4
5
6
7
mysql> show master status;
+---------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+---------------+----------+--------------+------------------+-------------------+
| binlog.000004 | 1784 | | | |
+---------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

配置-从库Slave

image-20221114152834288

image-20221114152923785 image-20221114153242911

1
2
3
4
5
mysql> change master to master_host='10.211.55.5',master_user='slave',master_password='123456',master_log_file='mysql-bin.000001',master_log_pos=157;
Query OK, 0 rows affected, 8 warnings (0.03 sec)

mysql> start slave;
Query OK, 0 rows affected, 1 warning (0.02 sec)

image-20221114153301982

1
show slave status\G;

image-20221115140535620

读写分离案例

背景

image-20221115140803486

Sharding-JDBC介绍

image-20221115141501035

入门案例

image-20221115141721098

导入maven坐标

image-20221115142105300

配置读写分离规则

image-20221115142335285

image-20221115142526450

配置允许bean定义并覆盖

image-20221115143430406

项目实现读写分离

数据库环境准备(主从复制)

image-20221115151816631

image-20221115152134249

代码改造

image-20221115152155880

导入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>

applicartion配置读写分离

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
shardingsphere:
datasource:
names:
master,slave
# 主数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.138.100: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://192.168.138.101: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

spring下添加配置允许覆盖

1
2
3
spring:
main:
allow-bean-definition-overriding: true

Idea2022.2配置会爆红但不影响运行

测试

image-20221115154759325

image-20221115155231835

11.Nginx

Nginx概述

Nginx介绍

image-20221115164739649

Nginx下载和安装

image-20221115165022693

image-20221115165123543

ubuntu安装nginx

1
2
3
# 切换至root用户
sudo su root
apt-get install nginx

image-20221115195415961

查看nginx是否安装成功

1
2
root@weishao1:/home/weishao# nginx -v
nginx version: nginx/1.18.0 (Ubuntu)

启动nginx

1
service nginx start

在网页重输入ip地址,即可看到nginx的欢迎页面

image-20221115195611516

Nginx目录结构

image-20221115195705015

ubuntu安装nginx目录结构

nginx文件安装完成之后的文件位置:

  • /usr/sbin/nginx:主程序
  • /etc/nginx:存放配置文件
  • /usr/share/nginx:存放静态文件
  • /var/log/nginx:存放日志

Nginx命令

查看版本

image-20221115200426681

1
2
root@weishao1:/usr/sbin# ./nginx -v
nginx version: nginx/1.18.0 (Ubuntu)

检查配置文件正确性

image-20221115200622943

1
2
3
root@weishao1:/usr/sbin# ./nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

启动和停止

image-20221115200932853

1
2
3
4
5
6
7
root@weishao1:/usr/sbin# ./nginx 
root@weishao1:/usr/sbin#
root@weishao1:/usr/sbin# ps -ef | grep nginx
root 6521 1 0 12:10 ? 00:00:00 nginx: master process ./nginx
www-data 6522 6521 0 12:10 ? 00:00:00 nginx: worker process
www-data 6523 6521 0 12:10 ? 00:00:00 nginx: worker process
root 6525 5802 0 12:10 pts/1 00:00:00 grep --color=auto nginx

重新加载配置文件

image-20221115201648879

centos修改环境变量

image-20221115202730888

image-20221115202817574

ubuntu添加环境变量

1
root@weishao1:/etc/nginx# vim /etc/profile

image-20221115203102870

Nginx配置文件结构

image-20221115203509278

Nginx具体应用

部署静态资源

image-20221115203954643

反向代理

image-20221115211235765

image-20221115211424173

image-20221115211718201

演示

现准备两个ubuntu系统的虚拟机服务器

  • 10.211.55.4
  • 10.211.55.5

首先在10.211.55.4上运行springboot程序

image-20221115213944953

直接访问可以成功

image-20221115214048134

接下来在10.211.55.5上做nginx配置

Ubuntu nginx1.8 修改配置文件

1
/etc/nginx/sites-enabled/default

image-20221115220935926

其余关于80端口的配置需要删干净

image-20221115221019227

访问成功

负载均衡

image-20221116110324456

image-20221116110621871

image-20221116111121784

根据权重发送

image-20221116111013425

演示

修改配置文件

image-20221116122358468

image-20221116122414756

image-20221116122431257

12.前后端分离开发

问题说明

image-20221116122705057

介绍

image-20221116145227741

开发流程

image-20221116145734033

前段技术栈

image-20221116145820783

13.YApi

介绍

image-20221116150048930

使用方式

image-20221116150220890

14.Swagger

介绍

image-20221116151312919

使用方式

image-20221116151503422

导入Maven坐标

image-20221116151623147

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>

导入Knife4j相关配置

image-20221116151707995

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
@Slf4j
@Configuration
@EnableSwagger2
@EnableKnife4j
public class WebMvcConfig extends WebMvcConfigurationSupport {

/**
* 扩展mvc框架的消息转换器
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//创建消息转换器
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用jackson将Java对象转换为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0,messageConverter);
}


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

设置静态资源映射

1

image-20221116151917423

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 设置资源映射
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始进行静态资源映射...");
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
super.addResourceHandlers(registry);
}

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

image-20221116152648949

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
log.info("拦截到请求:{}",requestURI);
//定义不需要处理的请求路径
String[] urls=new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**",
"/common/**",
"/user/sendMsg",//移动端发送短信
"/user/login",//移动端登录
"/doc.html",
"/webjars/**",
"/swagger-resources",
"/v2/api-docs"
};

访问成功

image-20221116160129794

常用注解

image-20221116160208519

15.项目部署

部署架构

image-20221116161405110

部署环境说明

image-20221116161700702

部署前端项目

image-20221116161952344

image-20221116162214263

image-20221116162837487

后端项目的部署

image-20221116163035598

image-20221116163214581

image-20221116163358445

完结撒花🙃