
🎬 那我掉的头发算什么 :个人主页
🔥 个人专栏 : 《javaSE》《数据结构》《数据库》《javaEE》
⛺️待到苦尽甘来日

引言
上一篇博客,我们已经完成了图书管理系统的全部准备工作------从数据库表设计、依赖引入、配置文件编写,到实体类创建、代码分层,再到前端页面(登录、图书列表、添加/修改图书)的布局与交互逻辑,每一步都扎实落地,为我们今天的接口实现打下了坚实的基础。
今天这篇,我们就聚焦后端接口实现,手把手完成整个图书管理系统的核心接口,包括登录接口、图书增删改查接口、分页查询接口,还会补充强制登录拦截器、统一结果返回、异常处理等拓展功能,让我们的系统更规范、更健壮。
温馨提示:接口实现全程基于Spring全家桶(SpringBoot + MyBatis),所有代码都和上一篇的准备工作完全衔接,建议大家先回顾上一篇的内容,再跟着本文一步步操作,避免出现代码报错、衔接不上的问题。另外,每段代码后面我都会补充关键说明和新手踩坑点,耐心看完,你一定会有收获
文章目录
接口实现
在正式开始写接口之前,先和大家梳理一下接口实现的整体逻辑和分层职责,帮大家理清思路:
-
Controller层:接收前端发送的请求(比如登录请求、添加图书请求),进行简单的参数校验,调用Service层的方法,最终给前端返回结果;
-
Service层:处理核心业务逻辑(比如登录时的密码校验、添加图书时的参数判断),调用Mapper层的方法,完成数据的处理与流转;
-
Mapper层(数据访问层):直接与数据库交互,通过MyBatis的注解或XML文件,执行SQL语句,获取或操作数据库中的数据;
-
拓展功能:拦截器实现强制登录、统一结果返回规范响应格式、异常处理器捕获全局异常,这些功能是项目规范化的关键,也是实际开发中必用的知识点。
接下来,我们就按照"核心接口→拓展功能→补充代码"的顺序,一步步实现所有接口,每一个接口都会附上完整代码+详细说明,新手也能轻松跟上。
登录接口
登录接口是整个系统的入口,核心功能是:接收前端传递的用户名和密码,校验用户名和密码是否正确,若正确则将用户信息存入Session(用于后续登录状态校验),返回登录成功;若错误则返回登录失败。
这里有两个关键注意点:① 必须先进行参数校验,防止用户名或密码为空导致的空指针异常;② 密码校验成功后,要将密码置空再存入Session,避免密码泄露,保证系统安全。
java
package com.hbu.book.controller;
import com.hbu.book.Mapper.UserMapper;
import com.hbu.book.constant.Constants;
import com.hbu.book.model.UserInfo;
import com.hbu.book.service.UserService;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/login")
public Boolean login(String name, String password, HttpSession session){
if(!StringUtils.hasLength(name) || !StringUtils.hasLength(password)){
return false;
}
UserInfo userInfo = userService.queryPasswordByName(name);
log.info("获取到密码:" + userInfo.getPassword());
if(password.equals(userInfo.getPassword())){
userInfo.setPassword("");
session.setAttribute(Constants.SESSION_USER_KEY,userInfo);
return true;
}
return false;
}
}
java
package com.hbu.book.service;
import com.hbu.book.Mapper.UserMapper;
import com.hbu.book.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public UserInfo queryPasswordByName(String name){
return userMapper.queryPasswordByUsername(name);
}
}
java
package com.hbu.book.Mapper;
import com.hbu.book.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
@Select("select * from user_info where username = #{username}")
UserInfo queryPasswordByUsername(String username);
}
补充说明:这里我们用MyBatis的@Select注解直接编写SQL,适合简单的查询语句;复杂的SQL语句,我们后续会用XML文件编写,两种方式各有优势,大家可以根据实际场景选择。另外,实际开发中,用户密码一定不能明文存储,建议用MD5、BCrypt等加密方式,这里为了简化开发,暂时用明文,后续可以自行优化。
Mapper接口
Mapper层是整个后端接口的核心枢纽,负责所有与数据库的交互操作,包括图书的查询、新增、修改、删除、分页查询等。上一篇我们已经创建了BookMapper接口,现在我们完善它的所有方法,涵盖图书管理的全部数据操作。
这里需要注意:① 接口中的方法名要规范,与业务逻辑对应,方便后续调用和维护;② 简单的SQL用MyBatis注解编写,复杂的SQL(比如动态修改、批量删除)用XML文件编写,提高代码可读性;③ 分页查询的SQL的limit关键字,参数要与前端传递的分页参数(offset、pageSize)对应,避免分页错乱。
java
package com.hbu.book.Mapper;
import com.hbu.book.model.BookInfo;
import com.hbu.book.model.PageRequest;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@Mapper
public interface BookMapper {
@Select("select * from book_info")
List<BookInfo> queryAll();
@Insert("insert into book_info (book_name, author, count, price, publish, `status`) " +
"values (#{bookName}, #{author}, #{count}, #{price}, #{publish}, #{status})")
Integer insertBook(BookInfo bookInfo);
@Select("select * from book_info where status != 0 order by id limit #{offset} ,#{pageSize} ")
List<BookInfo> selectBookByPage(PageRequest pageRequest);
BookInfo queryBookById(Integer id);
void updateBook(BookInfo bookInfo);
void deleteBookById(Integer id);
void batchDelete(List<Integer> ids);
@Select("select count(*) from book_info")
Integer count();
}
添加图书
添加图书接口的核心功能是:接收前端添加图书页面传递的所有图书信息,进行参数校验(确保必填字段不为空),调用Service层和Mapper层的方法,将图书信息插入到数据库,返回添加结果。
这里的关键是参数校验------前端的参数校验可以被绕过,后端必须再做一次参数校验,防止非法参数插入到数据库,比如图书名称为空、库存为null、定价为null等情况,都要拦截并返回错误信息。另外,要捕获异常,避免添加过程中出现异常(比如SQL报错),导致系统崩溃,同时打印日志,方便后续排查问题。
java
@RequestMapping(value = "/addBook", produces = "application/json")
public String addBook(BookInfo bookInfo){
log.info("添加图书:{}",bookInfo);
if (!StringUtils.hasLength(bookInfo.getBookName())
|| !StringUtils.hasLength(bookInfo.getAuthor())
|| bookInfo.getCount()==null
|| bookInfo.getPrice()==null
|| !StringUtils.hasLength(bookInfo.getPublish())
|| bookInfo.getStatus() ==null
) {
return "输⼊参数不合法, 请检查参数!";
}
try{
bookService.addBook(bookInfo);
return "";
}catch(Exception e){
log.error("添加图书失败: e {}",e);
return "添加图书异常";
}
}
java
public void addBook(BookInfo bookInfo) {
Integer ret = bookMapper.insertBook(bookInfo);
}
补充说明:前端与后端的交互约定很重要,这里我们约定"返回空字符串表示添加成功,返回非空字符串表示添加失败",与上一篇前端代码的success回调函数逻辑对应,避免前后端交互错乱。另外,bookInfo中的price字段是BigDecimal类型,前端传递过来的参数会自动转换,无需手动处理,但要确保前端传递的是合法的数字格式。
分页查询&图书列表
图书列表页面需要展示分页数据,所以分页查询接口是整个系统中最常用的接口之一,核心功能是:接收前端传递的分页参数(currentPage、pageSize),查询当前页的图书数据和图书总数量,对图书状态进行中文转换(比如1转为"正常",2转为"不允许借阅"),最终返回分页结果给前端,供前端渲染页面和分页组件。
这里有两个关键要点:① 登录状态校验------必须先判断用户是否登录(通过Session中的用户信息),若未登录,返回未登录状态,跳转到登录页面;② 图书状态中文转换------数据库中存储的是状态码(0、1、2),前端需要显示中文描述,所以在Service层对查询结果进行二次处理,通过枚举类实现状态码与中文描述的映射,简化代码。
java
@RequestMapping("/getListByPage")
public Result getListByPage(PageRequest pageRequest, HttpSession session){
//参数校验
//返回数据
if (session.getAttribute(Constants.SESSION_USER_KEY)==null){
return Result.unlogin();
}
UserInfo userInfo = (UserInfo)session.getAttribute(Constants.SESSION_USER_KEY);
if (userInfo==null || userInfo.getId()<=0){
//用户未登录
return Result.unlogin();
}
PageResp<BookInfo> listByPage = bookService.getListByPage(pageRequest);
log.info("此页码的数据:{}",listByPage);
return Result.success(listByPage);
}
java
public PageResp<BookInfo> getListByPage(PageRequest pageRequest){
// 1. 获得总图书数
//2. 获取当前页的数据
Integer count = bookMapper.count();
List<BookInfo> bookInfos = bookMapper.selectBookByPage(pageRequest);
//对结果进行二次处理
for (BookInfo bookInfo: bookInfos){
bookInfo.setStatusCN(BookStatusEnum.getStatusByCode(bookInfo.getStatus()).getDesc());
}
return new PageResp<>(count, bookInfos, pageRequest);
}
补充说明:分页参数的offset是通过PageRequest类的getOffset方法自动计算的(offset = (currentPage - 1) * pageSize),无需手动计算,简化了代码。另外,状态码的中文转换用枚举类实现,比if-else判断更规范、更易维护,后续如果需要新增状态,只需在枚举类中添加即可,无需修改业务代码。
修改图书
修改图书接口分为两个步骤:① 根据图书ID查询单本图书信息,返回给前端,供前端回显(比如用户点击"修改"按钮,页面自动填充该图书的原有信息);② 接收前端修改后的图书信息,执行修改操作,返回修改结果。
关键注意点:① 修改操作采用动态SQL(XML文件编写),只修改前端传递过来的非空字段,避免将原有不为空的字段改为空(比如用户只修改了图书价格,就只修改price字段,其他字段保持不变);② 同样需要捕获异常,打印日志,确保系统稳定性;③ 修改操作也要校验登录状态(后续通过拦截器统一实现,这里暂时在分页查询中校验,后续会优化)。
java
@RequestMapping("/queryBookById")
public BookInfo queryBookById(Integer bookId){
return bookService.queryBookById(bookId);
}
@RequestMapping(value = "/updateBook")
public Result updateBook(BookInfo bookInfo){
log.info("修改图书, bookInfo: {}", bookInfo);
try {
bookService.updateBook(bookInfo);
//成功
return Result.success("");
}catch (Exception e){
log.error("修改图书发生异常, e: ", e);
return Result.fail("修改图书发生异常");
}
}
java
public BookInfo queryBookById(Integer id) {
return bookMapper.queryBookById(id);
}
public void updateBook(BookInfo bookInfo) {
bookMapper.updateBook(bookInfo);
}
补充说明:queryBookById接口没有单独做登录校验,是因为前端只有登录后才能进入图书列表页面,才能点击"修改"按钮,后续我们会通过拦截器统一拦截所有需要登录的接口,无需在每个接口中单独校验,简化代码、提高规范性。
删除图书&批量删除图书
删除图书分为两种场景:单个删除和批量删除,两种场景都采用逻辑删除(将图书的status改为0),而非物理删除,这样可以保留数据,便于后续数据恢复和统计。
关键注意点:① 单个删除:接收图书ID,将该图书的status改为0;② 批量删除:接收前端传递的图书ID列表,批量将这些图书的status改为0,这里用到了MyBatis的foreach标签,遍历ID列表;③ 批量删除需要校验前端传递的ID列表是否为空,避免出现空指针异常(后续可补充参数校验)。
java
@RequestMapping(value = "/deleteBook")
public Result deleteBook(Integer bookId){
log.info("删除图书, bookId: {}", bookId);
try {
BookInfo bookInfo = new BookInfo();
bookInfo.setId(bookId);
bookInfo.setStatus(BookStatusEnum.DELETED.getCode());
bookService.updateBook(bookInfo);
//成功
return Result.success("");
}catch (Exception e){
log.error("删除图书发生异常, e: ", e);
return Result.fail("删除图书发生异常");
}
}
@RequestMapping("/batchDelete")
public Boolean batchDelete(@RequestParam List<Integer> ids){
log.info("批量删除图书, ids:{}", ids);
try {
bookService.batchDelete(ids);
return true;
}catch (Exception e) {
log.error("批量删除图书失败, e:", e);
return false;
}
}
java
public void deleteBookById(Integer id) {
bookMapper.deleteBookById(id);
}
public void batchDelete(List<Integer> ids) {
bookMapper.batchDelete(ids);
}
补充说明:单个删除我们复用了修改图书的方法,将status改为0,这样可以减少代码冗余,无需单独编写删除的Service和Mapper方法,这是一种常用的代码优化技巧。批量删除的ids参数,前端传递的是选中的图书ID列表,后端用@RequestParam接收,确保参数传递正确。
拓展功能
完成核心业务接口后,我们还需要补充几个拓展功能,让我们的系统更规范、更健壮、更符合实际开发需求。这些功能虽然不是核心业务,但却是实际开发中必用的,也是区分新手和资深开发者的关键细节。
强制登录(拦截器)
上一篇我们在分页查询接口中做了登录校验,但如果每个接口都单独做校验,会产生大量冗余代码,而且容易遗漏。这里我们用SpringMVC的拦截器,实现强制登录校验------拦截所有需要登录才能访问的接口,若用户未登录,直接返回401状态码和"用户未登录"提示,跳转到登录页面。
拦截器实现分为两步:① 编写拦截器类,实现HandlerInterceptor接口,重写preHandle方法(请求执行前拦截);② 编写配置类,注册拦截器,指定拦截的路径和需要排除的路径(比如登录接口、前端静态资源)。
实现拦截器:
java
package com.hbu.book.interceptor;
import com.hbu.book.constant.Constants;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.jspecify.annotations.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession httpSession = request.getSession(false);
if(httpSession != null && httpSession.getAttribute(Constants.SESSION_USER_KEY)!=null){
return true;
}
response.setContentType("text/html;charset=utf-8");
response.setStatus(401);
String msg = "用户未登录";
response.getOutputStream().write(msg.getBytes("UTF-8"));
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
System.out.println("方法执行之后执行-postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
注册配置拦截器:
java
package com.hbu.book.config;
import com.hbu.book.interceptor.LoginInterceptor;
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 WebConfig implements WebMvcConfigurer {
//自己定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")//拦截器拦截的请求路径
.excludePathPatterns("/user/login")
.excludePathPatterns("/**/*.js") //排除前端静态资源
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.png")
.excludePathPatterns("/**/*.html");
}
}
统一结果返回
之前我们的接口返回格式不统一,比如登录接口返回Boolean,添加图书接口返回String,分页查询接口返回Result对象,这样前端接收数据时,需要针对不同的接口处理不同的返回格式,非常繁琐。这里我们用@ControllerAdvice注解,实现统一结果返回,让所有接口的返回格式保持一致,简化前后端交互。
统一结果返回分为两种情况:① 正常情况:无论接口返回什么类型(String、List、实体类),都封装成Result对象返回;② 异常情况:全局捕获接口执行过程中出现的所有异常,封装成Result对象返回,避免返回默认的错误页面,提升用户体验。
正常情况:
java
package com.hbu.book.config;
import com.hbu.book.model.Result;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import tools.jackson.databind.ObjectMapper;
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite( Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if(body instanceof Result){
return body;
}
if(body instanceof String){
try{
System.out.println("1222222");
return objectMapper.writeValueAsString(Result.success(body));
}catch (Exception e){
System.out.println(e.getMessage());
}
}
return Result.success(body);
}
}
异常情况:
java
package com.hbu.book.config;
import com.hbu.book.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@ResponseBody
@Slf4j
public class ExceptionAdvice {
@ExceptionHandler
public Object handler(Exception e){
log.error("出现异常:",e);
return Result.fail("出现内部异常");
}
// @ExceptionHandler
// public Object handler(NullPointerException e){
// log.error("出现空指针异常:",e);
// return Result.fail(e.getMessage());
// }
// @ExceptionHandler
// public Object handler(ArithmeticException e){
// log.error("出现除0异常:",e);
// return Result.fail(e.getMessage());
// }
// @ExceptionHandler
// public Object handler(ArrayIndexOutOfBoundsException e){
// log.error("出现数组越界异常:",e);
// return Result.fail(e.getMessage());
// }
@ExceptionHandler(NullPointerException.class)
public Object handler1(Exception e){
log.error("出现异常:",e);
return Result.fail("出现空指针异常");
}
@ExceptionHandler(ArrayIndexOutOfBoundsException.class)
public Object handler2(Exception e){
log.error("出现异常:",e);
return Result.fail("出现数组越界异常");
}
}
补充代码
前面的接口实现中,用到了一些枚举类、常量类和MyBatis XML文件,这些是接口正常运行的基础,之前没有详细说明,这里统一补充,确保所有代码完整可运行,新手可以直接复制使用。
两个枚举类:
java
package com.hbu.book.enums;
public enum BookStatusEnum {
DELETED(0, "删除"),
NORMAL(1, "正常"),
FORBIDDEN(2, "不允许借阅");
private Integer code;
private String desc;
BookStatusEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
public static BookStatusEnum getStatusByCode(Integer code){
switch (code){
case 0: return BookStatusEnum.DELETED;
case 1: return BookStatusEnum.NORMAL;
case 2: return BookStatusEnum.FORBIDDEN;
default:
return null;
}
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
java
package com.hbu.book.enums;
public enum ResultCodeEnum {
UNLOGIN(-1),
SUCCESS(200),
FAIL(-2);
private int code;
ResultCodeEnum(int code) {
this.code = code;
}
}
一个常量类:
java
package com.hbu.book.constant;
public class Constants {
public static final String SESSION_USER_KEY = "session_user_info";
}
数据库xml:
前面的BookMapper接口中,queryBookById、updateBook、batchDelete、deleteBookById这四个方法的SQL比较复杂(动态SQL、foreach遍历),我们用XML文件编写,提高代码可读性。XML文件的命名空间要与BookMapper接口的全路径一致,方法名要与接口中的方法名一致。
java
<?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.hbu.book.Mapper.BookMapper">
<update id="updateBook">
update book_info
<set>
<if test="bookName!= null">
book_name = #{bookName},
</if>
<if test="author!= null">
author = #{author},
</if>
<if test="count!= null">
count = #{count},
</if>
<if test="price!= null">
price = #{price},
</if>
<if test="publish!= null">
publish = #{publish},
</if>
<if test="status!= null">
status = #{status},
</if>
</set>
where id = #{id}
</update>
<update id="batchDelete">
update book_info set status = 0 where id in
<foreach collection="ids" separator="," open="(" close=")" item="id">
#{id}
</foreach>
</update>
<delete id="deleteBookById">
update book_info set status = 0 where id = #{id}
</delete>
<select id="queryBookById" resultType="com.hbu.book.model.BookInfo">
select id,book_name ,author,count,price,publish,`status` from book_info where id = #{id};
</select>
</mapper>
项目测试
运行项目后根据环回ip访问进程:
登录为系统唯一入口,未登录状态下无法访问任何核心页面,登录校验逻辑如下:
1.1 正确登录:输入合法的账号密码,可成功进入系统首页,跳转至图书列表页面。
1.2 错误登录:输入错误的账号或密码,系统会弹出"密码错误,请重试"的提示弹窗,阻止登录,确保账号安全性。

1.3 未登录拦截:若不进行登录操作,直接通过URL访问系统内部页面(如图书列表),会被系统拦截,无法访问,强制跳转至登录相关校验页面。

成功登录系统后,默认进入图书列表页面,系统采用分页模式展示所有图书数据,默认加载第一页内容,分页布局清晰,可正常切换页码。

点击图书列表页面的"添加图书"按钮,会弹出添加图书弹窗,弹窗内包含图书名称、作者、库存、定价等必填字段,填写完整信息后点击"确定",即可完成图书添加。


点击确定之后会回到页面第一页,咱们翻到第四页就能看到数据了:

在图书列表页面,找到需要修改的图书,点击对应图书的"修改"按钮,系统会弹出修改图书弹窗,弹窗内会自动回显该图书的原有数据(图书名称、作者、库存等),无需重新输入所有信息。


系统支持单个图书删除和多个图书批量删除两种方式,两种方式均有二次确认机制,防止误操作删除数据。
5.1 单个删除:点击单本图书的"删除"按钮,弹出确认删除弹窗,点击"确认"后,该图书从列表中移除,数据同步删除(逻辑删除,保留数据库记录);点击"取消",则取消删除操作。
5.2 批量删除:勾选多本需要删除的图书,点击页面的"批量删除"按钮,弹出确认删除弹窗,点击"确认"后,所有勾选的图书全部移除;点击"取消",则取消批量删除操作。

小结
到这里,我们图书管理系统的所有后端接口就全部实现完成了,同时还补充了拦截器、统一结果返回、异常处理等拓展功能,整个系统的结构更规范、更健壮、更符合实际开发需求。回顾一下本文的核心内容,我们主要完成了以下工作:
-
核心业务接口:实现了登录接口、图书增删改查接口、分页查询接口、批量删除接口,覆盖了图书管理系统的所有基础功能,三层架构(Controller→Service→Mapper)分工明确,层层调用,逻辑清晰;
-
拓展功能:通过拦截器实现强制登录,避免未登录用户访问接口;通过统一结果返回规范前后端交互格式,简化前端开发;通过全局异常处理器捕获所有异常,提升系统稳定性和用户体验;
-
补充代码:完善了枚举类、常量类、MyBatis XML文件,确保所有代码完整可运行,同时补充了大量新手踩坑点和注意事项,帮助大家避开常见错误。
结合上一篇的前端页面开发和准备工作,我们现在已经拥有了一个完整可运行的图书管理系统------前端负责页面布局和交互,后端负责接口实现和数据处理,前后端通过AJAX交互,实现了登录、图书列表展示、添加图书、修改图书、删除图书、批量删除图书等所有功能。
当然,这个系统还有很多可以优化的地方,比如用户密码加密存储、图书搜索功能、借阅功能、用户管理功能等,后续我会继续更新,带大家一步步优化这个系统,让它更完善、更贴近企业实际开发场景。
最后,提醒大家,一定要动手把所有代码复制到自己的项目中,亲自运行测试,遇到报错不要慌,根据日志排查问题,多动手、多调试,才能真正掌握这些知识点。很多新手小伙伴只看不动手,看似看懂了,实际动手写的时候还是会出现各种问题,动手实践才是掌握技术的关键!
以上就是本篇博客全部内容!感谢阅读,我们下期再见~