一、管理后台

1、分类管理

遵守的规则

(1)功能概述

实现一个分类维护功能,来维护修改分类信息

分类管理功能概述

(2)分类管理开发流程

分类管理开发流程

(3)显示后台分类编码实现

声明持久层接口方法:
/**
 * 查询所有商品类别
 *
 * @return 所有商品类别
 */
List<Category> selectAll();
持久层sql语句:
<!--查询所有商品类别-->
<select id="selectAll" resultMap="CategoryMapper">
    select *
    from category
    where display = 1
    order by order_num
</select>
创建VO
/**
 * 商品类别视图对象 封装和前台页面交互的数据
 */
public class CategoryVO {
    private Long id;
    private String name;
 
    省略...
}
创建service层:
/**
 * 查询管理页面商品分类
 * @return 管理页面商品分类
 */
List<CategoryVO> findHomepageCategories();
service层实现类:
@Autowired
    private CategoryMapper categoryMapper;

    @Override
    public List<CategoryVO> findHomepageCategories() {
        //查询所有商品类别
        List<Category> categories = categoryMapper.selectAll();
        //用stream快速构建一个新的集合
        //把商品类别对象转换为商品视图对象(Entity->VO)
        List<CategoryVO> categoryVOS = categories.stream()
                .filter(Category::getDisplay)
                .map(category -> new CategoryVO(category.getId(), category.getName()))
                .collect(Collectors.toList());

        return categoryVOS;
    }

测试service层:

@Test
void findHomepageCategories() {
    categoryService.findHomepageCategories().forEach(
            categoryVO -> {
                logger.info("CategoryVo:{}", categoryVO);
            }
    );
}

测试结果:

显示分类service层测试结果

controller层:
@RestController
public class CategoryController {
    private static final Logger logger = LoggerFactory.getLogger(CategoryController.class);
    @Autowired
    private CategoryService categoryService;

    @GetMapping("/categories")
    public List<CategoryVO> findHomePageCategories() {
        logger.info("查询首页商品类别");
        List<CategoryVO> categoryVOS = categoryService.findHomepageCategories();
        return categoryVOS; // 以JSON格式返回数据
    }
}

测试类:

@Test
void findHomePageCategories() {
    String url = "/categories"; // 请求地址
    String json = restTemplate.getForObject(url, String.class); // 发送GET请求 返回JSON字符串
    logger.info("返回结果: {}", json);
}

测试效果:

展示分类controller层测试结果

2、删除分类功能

后台分类管理界面:在分类管理界面中可以查看所有分类信息,点击删除按钮对需要删除的分类进行操作。

分类控制器:在分类控制器中,需要编写相应的方法来处理删除分类的请求。该方法需要从前端接收要删除的分类 ID,并将请求传递给分类管理业务层。

分类管理业务层:在分类管理业务层中,需要编写相应的业务逻辑来处理删除分类的请求。

  • 需要检查该分类是否存在,并且是否有与之相关的商品。
  • 如果有相关商品,则需要提示管理员先删除商品,然后再删除该分类。
  • 如果该分类没有相关商品,则需要将该分类从数据库中删除。

分类数据访问层:在分类数据访问层中,需要编写相应的 SQL 语句来实现删除分类的操作。该 SQL 语句需要根据分类 ID 来删除数据库中对应的记录。

mapper层

mapper接口方法:

/**
 * 根据id删除商品类别
 * @param id 商品类别id
 * @return 返回受影响的行数
 */
int deleteById(Long id);

mapper.xml配置文件:

<!--根据id删除商品类别-->
<delete id="deleteById">
    delete
    from category
    where id = #{id}
</delete>

测试代码:

@Test
@Transactional //事务操作 用于测试删除操作后回滚数据
void deleteById() {
    int i = categoryMapper.deleteById(7L);
    logger.info("删除了"+i+"行数据");
    //断言
    assertEquals(1,i,"删除失败");
}

测试效果:

删除分类mapper测试结果

service层

接口添加方法:

/**
 * 根据id删除商品类别
 * @param id 删除的id
 */
void deleteById(Long id);

实现类:

@Service
public class CategoryServiceImpl implements CategoryService {

    @Autowired
    private CategoryMapper categoryMapper;

    @Override
    public void deleteById(Long id) {
        if (id == null || id < 1) {
            throw new BlankParameterException("删除商品类别失败,商品类别id不合法");
        }
        //执行删除
        int result = categoryMapper.deleteById(id);
        if (result < 1) {
            throw new EntityNotFoundException("删除商品类别失败,商品不存在");
        }
    }
}

测试类:

@Test
@Transactional
void deleteById() {
    logger.info("删除id为5的商品类别");
    categoryService.deleteById(5L);
    //断言测试抛异常
    assertThrows(BlankParameterException.class,()->categoryService.deleteById(null),"没有异常");
}

二、全局统一异常处理

统一异常处理

(1)统一异常处理概述

统一异常处理是一种软件开发的最佳实践,旨在捕获并处理程序运行时可能出现的异常和错误,以提高程序的可靠性和稳定性。

将异常和错误信息处理代码从主要业务逻辑中分离出来

(2)Spring MVC统一异常处理

这个功能可以使用两个注解:@RestControllerAdvice 和 @ControllerAdvice。

@ControllerAdvice 注解(已过时)
  • 通常用于处理传统的 MVC 应用程序中的异常,例如使用 Thymeleaf、JSP 或者 FreeMarker 等视图技术构建的 Web 应用程序。
  • @ControllerAdvice 注解中定义的异常处理方法可以返回 ModelAndView 对象,以实现跳转到错误页面的功能。
@RestControllerAdvice 注解
  • 通常用于处理 RESTful 服务中的异常
  • @RestControllerAdvice 注解中定义的异常处理方法则通常返回响应体,返回 JSON 格式的数据。
/**
 * @document: 全局异常处理器
 * 用于处理所有控制器遇到的☆各种☆异常
 * @Author:SmallG
 * @CreateTime:2023/9/7+14:47
 */

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(EntityNotFoundException.class) //声明异常
    public String handleBlankEntityNotFoundException(EntityNotFoundException e) {
        System.out.println("----------------");
        return e.getMessage();
    }

    @ExceptionHandler(BlankParameterException.class) //声明异常
    public String handleBlankParameterException(BlankParameterException e) {
        return e.getMessage();
    }

    @ExceptionHandler(UserNotFoundException.class) //声明异常
    public String handleUserNotFoundException(UserNotFoundException e) {

        return e.getMessage();
    }
}

三、RESTful

1、RESTful概述

RESTful是一种设计Web API的架构风格,它的核心思想是将Web资源作为唯一的标识符,并通过HTTP协议中的标准方法(例如GET、POST、PUT、DELETE)来实现对这些资源的操作

具有优点:

  • 松散耦合
  • 可伸缩性

(1)RESTful编程建议

RESTful编程建议

(2)RESTful URL风格

  • 使用名词表示资源:RESTful API中的URL应该使用名词来表示资源,例如/users、/orders等等,这样可以使URL更加清晰和易于理解。
  • 使用复数形式:URL中的名词应该使用复数形式,例如/users而不是/user,这是因为RESTful API中的URL通常表示一个资源的集合,而不是单个资源。
  • 不使用动词:URL中不应该使用动词,因为HTTP方法已经表示了动作,例如,使用PUT方法来更新资源,而不是使用/update动词来表示更新操作。
  • 避免使用大写字母:URL中的字母应该使用小写字母,以避免大小写不一致的问题。
  • 使用层次结构来表示关系:当资源之间存在关系时,应该使用层次结构来表示,例如,/users/1234/orders表示用户1234的订单列表。

2、Spring MVC REST

(1)Spring MVC 对RESTful API提供的支持

  • 支持HTTP请求方法:Spring MVC框架支持常见的HTTP方法,包括GET、POST、PUT、DELETE等等。通过使用@XXXMapping系列注解支持各种HTTP请求方法,如: @GetMapping注解支持HTTP GET方法、@PostMapping注解支持HTTP GET方法、@DeleteMapping注解支持HTTP DELETE方法等。
  • 支持URI模板参数:Spring MVC框架支持URI模板参数,这使得可以在URL中使用变量占位符,例如/users/{id},其中{id}是一个变量占位符。通过使用@PathVariable注解,可以将URI中的变量占位符的值映射到控制器中的方法参数中。
  • 支持消息转换:@RequestBody和@ResponseBody注解,可以将请求消息的JSON格式转换为Java对象,或将Java对象转换为响应消息的JSON格式。
  • 支持返回状态码:在Spring MVC中,@ResponseStatus注解用于指定控制器方法的响应状态码和响应消息。如:@ResponseStatus(HttpStatus.NO_CONTENT)
  • 支持异常处理:Spring MVC框架支持异常处理,可以捕获和处理应用程序中的异常,并返回相应的HTTP错误码和错误信息。通过使用@RestControllerAdvice注解,可以为应用程序中所有的控制器统一处理异常。

(2)@PathVariable

@PathVariable注解用于从URI路径中获取变量的值,禁止使用基本类型接收

在usermapper接口中添加删除用户方法:

/**
 * 根据id删除用户
 * @param id
 * @return 返回受影响的行数
 */
int deleteById(Long id);

添加usermapper.xml文件中的sql语句:

<!--根据id删除用户-->
<delete id="deleteById">
    delete
    from user
    where id = #{id}
</delete>

编写service层接口:

/**
 * 根据用户id删除用户
 * @param id
 */
void deleteUser(Long id);

编写service层实现类;

@Override
public void deleteUser(Long id) {
    logger.info("删除用户id:{}", id);
    if (id == null || id < 0) {
        throw new BlankParameterException("用户id不合法");
    }
    int result = userMapper.deleteById(id);

    if (result < 1) {
        throw new UserNotFoundException("用户id不存在");
    }
    logger.info("成功删除了{}条数据",result);
}

cotroller层实现:

/**
 * 删除用户
 *
 * @return
 */
@DeleteMapping("/users/{id}") //{id} 表示参数地址中的占位符
@ResponseStatus(HttpStatus.NO_CONTENT) //删除成功状态码是204
public String deleteUser(@PathVariable Long id) {
    //参数中的id要和占位符的id保持一致
    logger.info("控制层执行删除操作:id={}", id);
    userService.deleteUser(id);
    logger.info("控制层执行删除成功");
    return "删除成功";
}

测试类:

@Test
void deleteUser() {
    String url = "/users/15";
    testRestTemplate.delete(url,String.class);
}

测试效果:

rest风格的url效果

3、重构删除分类功能为REST风格

(1)重构统一异常处理返回错误状态码

  • 200 OK:表示请求成功。
  • 201 Created:表示请求已经被成功处理,并且在服务器上创建了新的资源。
  • 204 No Content:表示请求成功,但是没有返回任何内容。
  • 400 Bad Request:表示请求不合法,服务器无法处理该请求。
  • 401 Unauthorized:表示请求需要用户认证。
  • 403 Forbidden:表示请求被服务器拒绝,通常是因为用户没有权限执行该操作。
  • 404 Not Found:表示请求的资源不存在。
  • 405 Method Not Allowed:表示请求使用了不被支持的HTTP方法。
  • 500 Internal Server Error:表示服务器内部错误。

(2)重构统一异常处理

修改全局异常处理器:

/**
 * @document: 全局异常处理器
 * 用于处理所有控制器遇到的☆各种☆异常
 * @Author:SmallG
 * @CreateTime:2023/9/7+14:47
 */

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(EntityNotFoundException.class) //声明异常
    @ResponseStatus(HttpStatus.NOT_FOUND) //404
    public String handleBlankEntityNotFoundException(EntityNotFoundException e) {
        System.out.println("----------------");
        return e.getMessage();
    }

    @ExceptionHandler(BlankParameterException.class) //声明异常
    @ResponseStatus(HttpStatus.BAD_REQUEST) //400 请求不合法
    public String handleBlankParameterException(BlankParameterException e) {
        return e.getMessage();
    }

    @ExceptionHandler(UserNotFoundException.class) //声明异常
    @ResponseStatus(HttpStatus.NOT_FOUND) //404
    public String handleUserNotFoundException(UserNotFoundException e) {

        return e.getMessage();
    }
    //处理自定义异常以外的异常
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) //500 服务器错误
    public String handlerException(Exception e){
        return e.getMessage();
    }
}

(3)重构CategoryController删除分类功能

/**
 * 删除商品类别
 */
@DeleteMapping("/api/categories/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT) //204
public String deleteById(@PathVariable Long id) {
    logger.info("根据商品类别id删除商品类别,id:{}", id);
    categoryService.deleteById(id);
    return "删除成功";
}

测试类:

@Test
void deleteById() {
    //请求地址
    String url = "/api/categories/{0}"; //{0}表示一个占位符
    restTemplate.delete(url,5);
}

4、使用MockMVC测试

(1) TestRestTemplate 的事务问题

使用@Transactional注解可以将一个@Test方法标记为事务,使得该方法执行期间的所有数据库操作都在同一个事务中执行,测试结束后事务将被回滚,这样可以恢复测试对数据的影响。但是不能用于controller测试

(2)MockMvc测试

用于controller测试

使用步骤:

1、在测试类上添加@AutoConfigureMockMvc注解,以便自动配置MockMvc。

2、使用@Autowired注解将MockMvc注入到测试类中,以便在测试方法中使用MockMvc。

3、使用 MockMvcRequestBuilders 创建HTTP请求,使用MockMvc.perform方法执行HTTP请求。

4、使用MockMvcResultMatchers执行断言测试。

测试类:

//静态导入
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; //发送状态码的
//随机端口号
@SpringBootTest
@AutoConfigureMockMvc //自动配置,MockMvc
class CategoryControllerTest {
    private static final Logger logger = LoggerFactory.getLogger(CategoryControllerTest.class);

    @Autowired(required = false) //屏蔽错误 尽量不要加这个(required = false)
    MockMvc mockMvc;

    @Test
    @Transactional
        //事务回滚
    void deleteById() throws Exception {
        //请求地址
        String url = "/api/categories/{0}"; //{0}表示一个占位符
        String result = mockMvc.perform(delete(url, 5))
                .andExpect(status().isNoContent()) //状态码
                .andReturn().getResponse().getContentAsString(); //内容转换成字符串
        logger.info("result:{}",result);
    }
}

测试效果:

mockmvc测试效果

(3)MockMvcRequestBuilders

MockMvcRequestBuilders 是 Spring Framework 提供的一组工具类,用于创建 MockHttpServletRequestBuilder 对象,这些对象用于模拟 HTTP 请求,并在测试 Spring MVC 控制器时使用。

常用方法

  • get(String urlTemplate, Object... urlVariables):创建一个 HTTP GET 请求
  • post(String urlTemplate, Object... urlVariables):创建一个 HTTP POST 请求
  • put(String urlTemplate, Object... urlVariables):创建一个 HTTP PUT 请求
  • delete(String urlTemplate, Object... urlVariables):创建一个 HTTP DELETE 请求

(4)MockMvcResultMatchers

MockMvcResultMatchers 是 Spring Framework 提供的一组工具类,用于验证 Spring MVC 控制器的返回结果是否符合预期。它提供了许多方法,可用于验证返回结果的状态码、响应头、响应体等内容。

常用方法

  • status():用于验证响应的状态码
  • header(String name, String... values):用于验证响应头
  • content():用于验证响应体
  • jsonPath(String expression, Object... args):用于验证 JSON 响应体中的值是否符合预期

四、后台管理功能

1、增加分类

1、表单发送RESTAjax请求:前端表单发送Ajax请求到后端的Spring MVC控制器。

2、Spring MVC控制器处理接受CategoyeDTO数据:Spring MVC控制器接受前端传递的CategoryDTO数据。

3、业务层处理表单数据检查:业务层对表单数据进行检查,包括数据格式、数据类型、数据长度等等。如果数据有误,则返回错误信息给前端。

4、业务层将DTO转换为Category实体:业务层将DTO对象转换为Category实体对象,将表单数据存储到实体对象中。

5、Mapper负责存储数据:最后,Mapper负责将Category实体对象存储到数据库中。如果存储成功,则返回成功信息给前端。如果存储失败,则返回失败信息给前端。

mapper层添加方法

CategoryMapper接口添加方法

/**
 * 新增商品类别
 * @param category 商品类别实体类对象
 * @return 受影响的行数
 */
int insertCategory(Category category);

添加sql

<!--新增商品类别-->
<insert id="insertCategory">
    insert into category (name, display, order_num)
    values (#{name}, #{display}, #{order_num})
</insert>

service层

首先创建DTO

/**
 * @document: 商品分类数据访问对象 用于service接收controller层传来的数据
 * @Author:SmallG
 * @CreateTime:2023/9/7+22:20
 */
public class CategoryDTO {
    private String name;
    private Boolean display;
    private Integer orderNum;
    省略...
}

添加接口中的方法:

/**
 * 新增商品类别
 * @param categoryDTO 商品类别数据传输对象
 */
void addCategory(CategoryDTO categoryDTO);

实现方法:

@Override
public void addCategory(CategoryDTO categoryDTO) {
    if (categoryDTO == null ||
            categoryDTO.getName() == null ||
            categoryDTO.getName().trim().isEmpty()) {
        throw new BlankParameterException("添加商品类别失败,商品类别名不能为空");
    }
    //将DTO转化为Entity
    Category category = new Category(null,
            categoryDTO.getName(),
            categoryDTO.getDisplay(),
            categoryDTO.getOrderNum(),
            null, null);
    //保存商品类别信息
    int result = categoryMapper.insertCategory(category);
    if (result<1){
        throw new EntityNotFoundException("添加商品失败");
    }
}

controller层

/**
 * 添加商品类别
 * @return
 */
@PostMapping("/api/categories")
@ResponseStatus(HttpStatus.CREATED) //201 新增成功
//保存数据,参数要保存在请求体中,所以使用@RequestBody,获取请求体中的数据
public String addCategory(@RequestBody CategoryDTO categoryDTO){
    logger.info("控制层执行操作:添加商品类别");
    categoryService.addCategory(categoryDTO);
    logger.info("控制层添加商品类别成功");
    return "添加成功";
}

2、后台显示轮播图功能

创建实体类

/**
 * @document: 轮播图实体类 对应数据库表banner
 * @Author:SmallG
 * @CreateTime:2023/9/8+9:09
 */
public class Banner {
    private Long id;
    private String image; //图片路径+图片名
    private Integer orderNum;
    private LocalDateTime created; //创建时间
    private LocalDateTime updated;
    ...
}

创建持久层

@Mapper
public interface BannerMapper {

    /**
     * 查询所有轮播图
     * @return 所有轮播图
     */
    List<Banner> selectAll();
    
}

sql编码

<?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="cn.highedu.coolsharkhub.mapper.BannerMapper">
    <resultMap id="BannerMap" type="cn.highedu.coolsharkhub.pojo.entity.Banner">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="order_num" property="orderNum"></result>
        <result column="created" property="created"></result>
        <result column="updated" property="updated"></result>
    </resultMap>
    <!--查询所有轮播图-->
    <select id="selectAll" resultMap="BannerMap">
        SELECT *
        FROM banner
        ORDER BY order_num
    </select>

</mapper>

创建业务层

创建VO和视图层进行数据交互

public class BannerVO {
    private Long id;
    private String image;
    private Integer orderNum;
    ...
}

service层接口:

public interface BannerService {
    /**
     * 查询所有轮播图
     *
     * @return
     */
    List<BannerVO> findAll();
}

实现类:

@Service
public class BannerServiceImpl implements BannerService {
    private static final Logger logger = LoggerFactory.getLogger(BannerServiceImpl.class);

    @Autowired
    BannerMapper bannerMapper;

    @Override
    public List<BannerVO> findAll() {
        //查询所有轮播图对象
        List<Banner> banners = bannerMapper.selectAll();
        //数据转换
        List<BannerVO> bannerVOS = banners.stream().map(banner -> new BannerVO(
                banner.getId(), banner.getImage(), banner.getOrderNum()
        )).collect(Collectors.toList());
        return bannerVOS;
    }
}

创建控制层

@RestController
public class BannerController {
    private static final Logger logger = LoggerFactory.getLogger(BannerController.class);

    @Autowired
    private BannerService bannerService;

    /**
     * 查询所有轮播图
     *
     * @return
     */
    @GetMapping("/api/banners")
    public List<BannerVO> findAll() {
        logger.info("控制层查询所有轮播图");
        List<BannerVO> bannerVOS = bannerService.findAll();
        return bannerVOS;
    }
}

测试类:

//静态导入
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; //发送状态码的

/**
 * @document:
 * @Author:SmallG
 * @CreateTime:2023/9/8+10:11
 */

@SpringBootTest
@AutoConfigureMockMvc
class BannerControllerTest {
    private static final Logger logger = LoggerFactory.getLogger(BannerControllerTest.class);

    @Autowired(required = false)
    MockMvc mockMvc;

    @Test
    void findAll() throws Exception {
        String url = "/api/banners";
        //发送请求
        String json = mockMvc.perform(get(url)).andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString(); // 状态码200;
        logger.info("轮播图数据:{}", json);
        
    }
}

测试效果:

轮播图测试效果

最后修改:2023 年 09 月 13 日
如果觉得我的文章对你有用,请随意赞赏