ssm web项目的整体开发流程

技术栈

springboot+mybatis

后端api的测试:knife4jknife4j参考资料

整理一套开发规范

image-20240623222551476

文件目录

image-20240624000220806

项目背景

创建一个简单数据库表user

image-20240623223427731

执行简单的增删改查业务逻辑

开发过程

domain层开发

定义一个Java实体类com.example.domain.User与数据库表user相对应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String userName;
private String passWord;
}

dao层开发

dao层我们采用mybatis框架

step1 在application.yml配置文件中配置mysql数据库和mybatis mapper扫描路径

1
2
3
4
5
6
7
8
9
10
11
spring:
application:
name: ssm-demo
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/my-test?useUnicode=true&characterEncoding=utf8&useSSL=false
password: 123456
username: root

mybatis:
mapper-locations: classpath:mapper/*.xml

step2 编写dao层接口

创建com.example.mapper.UserMapper接口,里面定义争对数据库表userdao层业务逻辑

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


import com.example.domain.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface UserMapper {
//根据id查询单个用户
User findById(int id);
//查找所有用户
List<User> findAll();
//更新用户信息
int updateUser(User user);
//新增用户
int insert(User user);
//删除用户
int delete(User user);
}

step3 编写具体SQL语句的Mybatis映射文件

文件路劲src/main/resources/mapper/UserMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<select id="findById" resultType="com.example.domain.User">
select * from user where id=#{id};
</select>
<select id="findAll" resultType="com.example.domain.User">
select * from user;
</select>
<update id="updateUser" parameterType="com.example.domain.User">
update user set username=#{userName},password=#{passWord} where id=#{id};
</update>
<insert id="insert" parameterType="com.example.domain.User">
insert into user values(#{id},#{userName},#{passWord});
</insert>
<delete id="delete" parameterType="com.example.domain.User">
delete from user where id=#{id};
</delete>
</mapper>

service层开发

service往往是业务中最繁琐最终的地方,大对数代码都在这一层,这一层需要先声明一个接口,然后是一个接口实现类,又因为需要根据业务逻辑做异常处理,所以需要首先自定义异常

自定义异常

我们自己定义一个异常类,继承自RuntimeException,各种不太的业务异常我们用一个枚举类来进行描述

枚举类异常类型申明

com.example.exception.UserExceptionEnum

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
package com.example.exception;

/**
* 用户模块异常类型枚举类
* 主要包括:
* 参数类型错误异常
* 无请求权限异常
* 未知异常
*/
public enum UserExceptionEnum {
TYPE_NOTtMATCH_EXCEPTION("请求参数错误"),
PERMISSION_DENIED_EXCEPTION("无请求权限"),
UNKNOWN_EXCEPTION("未知类型错误");
private String desc;

UserExceptionEnum(String desc) {
this.desc = desc;
}

public String getDesc() {
return desc;
}

public void setDesc(String desc) {
this.desc = desc;
}

@Override
public String toString() {
return "UserExceptionEnum{" +
"desc='" + desc + '\'' +
"} " + super.toString();
}
}
自定义异常类

com.example.exception.UserException

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.example.exception;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserException extends RuntimeException{
private UserExceptionEnum userExceptionEnum;

}

service层接口申明

com.example.service.IUserService

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.example.service;

import com.example.domain.User;

import java.util.List;

public interface IUserService {
User getUserById(int id);
List<User> findAllUser();
boolean updateUser(User user);
boolean addUser(User user);
boolean deleteUser(User user);
}

service层实现类

com.example.service.impl.UserServiceImpl

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

import com.example.domain.User;
import com.example.exception.UserException;
import com.example.exception.UserExceptionEnum;
import com.example.mapper.UserMapper;
import com.example.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserServiceImpl implements IUserService {
@Autowired
UserMapper userMapper;
@Override
public User getUserById(int id) {
if(id<0){
throw new UserException(UserExceptionEnum.TYPE_NOTtMATCH_EXCEPTION);
}
try {
return userMapper.findById(id);
}catch (Exception e){
e.printStackTrace();
throw new UserException(UserExceptionEnum.UNKNOWN_EXCEPTION);
}
}

@Override
public List<User> findAllUser() {
return userMapper.findAll();
}

@Override
public boolean updateUser(User user) {
return userMapper.updateUser(user)>0;
}

@Override
public boolean addUser(User user) {
return userMapper.insert(user)>0;
}

@Override
public boolean deleteUser(User user) {
return userMapper.delete(user)>0;
}
}

Controller层开发

controller层涉及到具体的与前端进行交互,所以我们需要与前端进行约定

  • 前端传递过来的参数统一格式
  • 后端最终给前端传递参数的统一格式

step1 协商前端查询参数统一格式

我们定义类com.example.req.UserReq,前端按照此格式传递参数发送请求

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

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserReq {
private int id;
private String userName;
private String passWord;

private String token;
}

step2 协商后端传递前端结果统一格式

我们定义类com.example.resp.CommonResp

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.example.resp;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* 给前端统一返回的数据格式
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResp<T> {
//是否成功
private boolean success=true;
//响应描述信息
private String message="OK";
//具体响应内容
private T data;

public CommonResp(T data) {
this.data = data;
}
}

step3 统一异常处理

注意,在项目中我们一般将异常不断向外抛,统一在Controller层进行异常处理

通过@ControllerAdvice 配合 @ExceptionHandler 实现全局异常处理

我们编写类com.example.controller.ControllerExceptionHandler来进行全局异常处理,即触发对应类型异常,返回特定类型的结果给前端

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
package com.example.controller;


import cn.hutool.core.bean.BeanUtil;
import com.example.domain.User;
import com.example.req.UserReq;
import com.example.resp.CommonResp;
import com.example.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@Slf4j
@RequestMapping("/user")
@Api(tags = "用户模块相关接口")
public class UserController {

@Autowired
IUserService userService;
//查找单个用户
@GetMapping("/queryOne")
@ApiOperation("根据id查询单个用户")
public CommonResp<User> getUserById(UserReq userReq){
log.info("请求queryOne");
User user = userService.getUserById(userReq.getId());
return new CommonResp<>(user);
}

//查找所有用户
@GetMapping("/queryAll")
@ApiOperation("查询所有用户")
public CommonResp<List<User>> getAllUser(UserReq userReq){
List<User> userList = userService.findAllUser();
return new CommonResp<>(userList);
}

//更新用户信息
@PutMapping("/updateInfo")
@ApiOperation("更新用户信息")
public CommonResp<Boolean> upDateUserInfo(UserReq userReq){
User user = BeanUtil.toBean(userReq, User.class);
boolean s = userService.updateUser(user);
return new CommonResp<>(s);
}

//新增用户
@PutMapping("/addInfo")
@ApiOperation("添加用户")
public CommonResp<Boolean> addUserInfo(UserReq userReq){
User user = BeanUtil.toBean(userReq, User.class);
boolean s = userService.addUser(user);
return new CommonResp<>(s);
}


//删除用户
@PutMapping("/deleteInfo")
@ApiOperation("删除用户")
public CommonResp<Boolean> deleteUserInfo(UserReq userReq){
User user = BeanUtil.toBean(userReq, User.class);
boolean s = userService.deleteUser(user);
return new CommonResp<>(s);
}
}

step4 controller层具体业务逻辑

com.example.controller.UserController

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
package com.example.controller;

import cn.hutool.core.bean.BeanUtil;
import com.example.domain.User;
import com.example.req.UserReq;
import com.example.resp.CommonResp;
import com.example.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@Slf4j
@RequestMapping("/user")
@Api(tags = "用户模块相关接口")
public class UserController {

@Autowired
IUserService userService;
//查找单个用户
@GetMapping("/queryOne")
@ApiOperation("根据id查询单个用户")
public CommonResp<User> getUserById(UserReq userReq){
log.info("请求queryOne");
User user = userService.getUserById(userReq.getId());
return new CommonResp<>(user);
}

//查找所有用户
@GetMapping("/queryAll")
@ApiOperation("查询所有用户")
public CommonResp<List<User>> getAllUser(UserReq userReq){
List<User> userList = userService.findAllUser();
return new CommonResp<>(userList);
}

//更新用户信息
@PutMapping("/updateInfo")
@ApiOperation("更新用户信息")
public CommonResp<Boolean> upDateUserInfo(UserReq userReq){
User user = BeanUtil.toBean(userReq, User.class);
boolean s = userService.updateUser(user);
return new CommonResp<>(s);
}

//新增用户
@PutMapping("/addInfo")
@ApiOperation("添加用户")
public CommonResp<Boolean> addUserInfo(UserReq userReq){
User user = BeanUtil.toBean(userReq, User.class);
boolean s = userService.addUser(user);
return new CommonResp<>(s);
}


//删除用户
@PutMapping("/deleteInfo")
@ApiOperation("删除用户")
public CommonResp<Boolean> deleteUserInfo(UserReq userReq){
User user = BeanUtil.toBean(userReq, User.class);
boolean s = userService.deleteUser(user);
return new CommonResp<>(s);
}
}

拦截器开发

自定义拦截器

如果请求需要做参数或者其他权限校验,我们可以自定义拦截器类,实现HandlerInterceptor接口

com.example.interceptor.UserInterceptor

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
package com.example.interceptor;

import com.example.exception.UserException;
import com.example.exception.UserExceptionEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* 用户模块拦截器
*/
@Slf4j
@Component
public class UserInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("进入拦截器,开始进行权限校验");
String token = request.getParameter("token");
if(!"bang".equals(token)){
throw new UserException(UserExceptionEnum.PERMISSION_DENIED_EXCEPTION);
}
log.info("权限校验通过");
return true;
}
}

在配置类中配置拦截器,及其对应白黑名单

com.example.config.WebMvcConfig

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

import com.example.interceptor.UserInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
UserInterceptor userInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userInterceptor)
.addPathPatterns("/user/**")
.excludePathPatterns("/doc.html");
}
}

后端接口测试

对于dao层以及service层的功能开发,我们编写单元测试进行测试

对于整体接口测试,我们借助knife4j来进行在线接口文档测试,SpringBoot整合Knife4j

配置文件配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
knife4j:
enable: true
openapi:
title: SSM-Template-Project
description: "测试SSM-Template-Project"
# aaa"
email: bang@google.com
concat: bang
url: https://bang.world.com
version: v1.0
license: Apache 1.0
group:
test1:
group-name: 用户模块测试
api-rule: package
api-rule-resources: #controller包名
- com.example.controller

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
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
package com.example.controller;

import cn.hutool.core.bean.BeanUtil;
import com.example.domain.User;
import com.example.req.UserReq;
import com.example.resp.CommonResp;
import com.example.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@Slf4j
@RequestMapping("/user")
@Api(tags = "用户模块相关接口") //测试模块名称
public class UserController {

@Autowired
IUserService userService;
//查找单个用户
@GetMapping("/queryOne")
@ApiOperation("根据id查询单个用户")
public CommonResp<User> getUserById(UserReq userReq){
log.info("请求queryOne");
User user = userService.getUserById(userReq.getId());
return new CommonResp<>(user);
}

//查找所有用户
@GetMapping("/queryAll")
@ApiOperation("查询所有用户")
public CommonResp<List<User>> getAllUser(UserReq userReq){
List<User> userList = userService.findAllUser();
return new CommonResp<>(userList);
}

//更新用户信息
@PutMapping("/updateInfo")
@ApiOperation("更新用户信息")
public CommonResp<Boolean> upDateUserInfo(UserReq userReq){
User user = BeanUtil.toBean(userReq, User.class);
boolean s = userService.updateUser(user);
return new CommonResp<>(s);
}

//新增用户
@PutMapping("/addInfo")
@ApiOperation("添加用户") //接口名称
public CommonResp<Boolean> addUserInfo(UserReq userReq){
User user = BeanUtil.toBean(userReq, User.class);
boolean s = userService.addUser(user);
return new CommonResp<>(s);
}


//删除用户
@PutMapping("/deleteInfo")
@ApiOperation("删除用户")
public CommonResp<Boolean> deleteUserInfo(UserReq userReq){
User user = BeanUtil.toBean(userReq, User.class);
boolean s = userService.deleteUser(user);
return new CommonResp<>(s);
}
}

在线文档测试

访问http://localhost:8080/doc.html

image-20240624003139210

image-20240624003206334