瑞吉外卖-登录和员工管理

瑞吉外卖项目的功能和搭建

image-20220619212642130

功能

image-20220619212559076

image-20220619212618438

技术

image-20220619212532067

数据库表的信息

image-20220619212711561

环境搭建

添加 依赖(pom.xml)

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
<?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.zlw.reggie</groupId>
<artifactId>reggie_take_out</artifactId>
<version>1.0.0</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</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>

//简化了 JavaBean 的编写,避免了冗余和样板式代码而出现的插件,让编写的类更加简洁
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
//能够支持将java bean序列化成JSON字符串,也能够将JSON字符串反序列化成Java bean。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
//Commons Lang这一组API也是提供一些基础的、通用的操作和处理,如自动生成toString()的结果、自动实现hashCode()和equals()方法...
<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>

核心配置文件(application.yml)

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: Zlw0502
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 #自定义主键策略

静态资源解析(想要通过浏览器直接访问页面生效:http://localhost:8080/backend/index.html)

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//addResourceHandler是指你想在url请求的路径
// addResourceLocations是图片存放的真实路
//当访问路径中包含"/backend/**"时,resource handler就会去classpath:/backend/目录下寻找
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
}
}

项目搭建好的结构

image-20220619213424402

登录和退出实现

需求分析

image-20220619214711189

查看前端的页面代码,可以观察到,以下三个地方,也就是前端和后端所约定的一系列问题,就是后端所传过来的时候一定要带这三个变量,

image-20220619214616019

代码实现

EmployeeController.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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@RestController
@Slf4j
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;

@PostMapping ("/login")
public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){

//1.对密码进行加密
String password = employee.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes());

//2、根据用户名查询数据库
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();//从数据库查询
queryWrapper.eq(Employee::getUsername,employee.getUsername());//比较相等
Employee emp = employeeService.getOne(queryWrapper); //返回查询结果

//3、如果没有查询到结果,则返回登录失败
if (emp==null){
return R.error("用户名不存在!");
}

//4、比较密码是否正确
if (!password.equals(emp.getPassword()))
{
return R.error("密码错误");
}

//5、判断用户是否被禁用
if (emp.getStatus()==0){
return R.error("用户被禁用");
}

//6、登录成功,将员工的用户id加入到session中
request.getSession().setAttribute("employee",employee.getId());

return R.success(emp);

}
/**
* 退出功能
* @param request
* @return
*/
@RequestMapping("/logout")
public R<String> logout(HttpServletRequest request){
request.getSession().removeAttribute("employee");
return R.success("退出成功");

}

}

完善登录功能

如果不进行登录,直接访问url的时候,也可以进入主页面,这种设计很不合理。因此完善这个功能,当用户没有登录的时候,要跳转到登录页面。

image-20220619234103147

image-20220619234143910

处理逻辑

image-20220619234811897

在这里使用过滤器实现,并在过滤器类上添加@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")注解。

在启动类上添加:@ServletComponentScan注解进行扫描,如果不加,则识别不了过滤器上面的注解。

在SpringBootApplication上使用@ServletComponentScan注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册,无需其他代码。https://blog.csdn.net/m0_37739193/article/details/85097477

代码为:

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

//获取请求访问的URI
String requestURI = request.getRequestURI();

//设置那些请求不拦截,,不拦截的是访问的uri,而不是静态js
String [] s = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};
//判断请求的URI 和 数组 s 是否匹配,也就是是否需要拦截
boolean res = just(s,requestURI);
//如果不需要拦截,则放行,
if (res){
log.info(requestURI +"不是拦截路径");
filterChain.doFilter(request,response);
return;
}
//如果是需要拦截的URI,则判断用户是否登录
//1、用户名不为空,证明已经登录,不需要拦截
if (request.getSession().getAttribute("employee")!=null){
log.info("是拦截路径,用户已经登录,id为:{}",request.getSession().getAttribute("employee"));
filterChain.doFilter(request,response);
return;
}

//以上情况不发生,则拦截,发送一个json对象,前端接收到后,进行跳转页面
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
log.info("用户未登录,拦截路径为:{}",requestURI);
return;

}
//判断是否与不拦截的路径匹配
public boolean just(String s[],String requestURI){
for (String uri:s) {
boolean match = PATH_MATCHER.match(uri, requestURI);
if (match){
return true;
}
}
return false;
}

}

新增员工

image-20220621155021710

添加新增员工的信息,点击保存,提交给服务器。

代码为:

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
/**
* 新增员工
* @param employee
* @return
*/
@PostMapping
public R<String> saveEmployee(HttpServletRequest request, @RequestBody Employee employee){

//设置初始密码,进行md5加密
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
//因为传递过来的信息,中没有对数据库字段中的创建时间、更新时间,创建者和更新者等进行设置,因此进行设置
//设置创建时间,在实体类中可以看到时间的类型为:LocalDateTime
employee.setCreateTime(LocalDateTime.now());

//设置更新时间
employee.setUpdateTime(LocalDateTime.now());
//设置创建者
//获取目前登录用户的ID,也就是在session中的id
Long userID = (Long)request.getSession().getAttribute("employee");
employee.setCreateUser(userID);
//设置更新者
employee.setUpdateUser(userID);

employeeService.save(employee);
return R.success("保存成功");
}

但仅仅是上面的代码,还存在一些问题,就是我们规定该系统的用户名是唯一的,

image-20220621155348324

当我们添加一样的用户名的时候,控制台显示错误。

image-20220621155437188

解决这个问题;

  • (1)可以将代码中添加try..catch进行捕获异常,这种方式处理非常不好,因为后面我们这种添加操作会很多,这样我们就要处理很多遍。

    image-20220621155605677

  • (2)推荐!!–使用异常处理器进行全局异常捕获,对整个项目进行捕获,不管是那个模块,只要出现这种异常就可以捕获。

    定义一个全局异常处理器:GlobalExceptionHandler.java

    (全局异常捕获有两种方式:Spring的AOP、 @ControllerAdvice结合@ExceptionHandler

    这里使用@ControllerAdvice结合@ExceptionHandler

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @ControllerAdvice(annotations = {RestController.class, Controller.class})
    @ResponseBody
    @Slf4j
    public class GlobalExceptionHandler {

    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException e){
    // Duplicate entry 'zhangsan' fro key 'employee.idx_username'
    if (e.getMessage().contains("Duplicate entry")){
    String[] s = e.getMessage().split(" ");
    String msg = s[2]+"已存在";
    return R.error(msg);
    }
    return R.error("未知错误");
    }

    }

    流程;

image-20220621162348717

员工信息分页查询

使用mybatisplus中的分页插件进行实现。

(1)首先编写MybatisPlus的分页插件配置类

MybatisPlusConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* mybatisplus分页插件配置类
*/
@Configuration //配置类 交给Spring管理 确保在启动类的包或子包下,才能被扫描到
public class MyBatisPlusConfig {

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//创建拦截器的壳子
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//添加内部拦截器(分页的)
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return null;
}

}

(2)编写Controller

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 page
* @param pageSize
* @param name //是否根据员工的姓名查询
* @return
*/
@RequestMapping("/page")
public R<Page> page(int page,int pageSize,String name){

//构造分页构造器
Page pageInfo = new Page(page,pageSize);

//构造条件构造器
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(name),Employee::getName,name);
//添加排序条件
queryWrapper.orderByDesc(Employee::getUpdateTime);

//执行查询
employeeService.page(pageInfo,queryWrapper);

return R.success(pageInfo);
}

启用禁用员工账号功能

(1)编写启用禁用员工的Controller,也就是更新员工的信息,所以写为update,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 启动禁用员工账号功能,也就是更新员工的信息
* @return
*/
@RequestMapping()
public R<String> update(HttpServletRequest request, @RequestBody Employee employee){

Long id = (Long) request.getSession().getAttribute("employee");
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(id);
employeeService.updateById(employee);
return R.success("员工信息修改成功");

}

但以上这么写完之后,修改不成功,也不提示错误,原因是浏览器通过json字符串传递过去的id值与数据库中的id值不相同

image-20220621222838629

数据库;

image-20220621222852762

因此修改不成功,因为js对long类型的数据进行处理时丢失了精度,

如何解决:服务器给页面响应json数据的时候进行处理,将long数据类型转换为String字符串。

实现步骤:

(1)提供对象转换器JacksonObject,

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
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {

public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);


SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))

.addSerializer(BigInteger.class, ToStringSerializer.instance)
.addSerializer(Long.class, ToStringSerializer.instance)
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}

(2)在WebMvcConfig配置类中扩展Spring mvc的消息转换器,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 扩展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-20220722121148271

代码为:

1
2
3
4
5
6
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id){
log.info("根据id查询员工信息。。。");
Employee byId = employeeService.getById(id);
return R.success(byId);
}

瑞吉外卖-登录和员工管理
http://example.com/2022/06/19/瑞吉外卖-登录功能/
作者
zlw
发布于
2022年6月19日
许可协议