# spirngmvc-learn **Repository Path**: hrbu2023/spirngmvc-learn ## Basic Information - **Project Name**: spirngmvc-learn - **Description**: spirngmvc-learn 框架学习 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-03-17 - **Last Updated**: 2026-03-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 框架简介 Spring MVC 是 Spring 提供给 Web 应用的框架设计。。 Spring MVC 角色划分清晰,分工明细,并且和 Spring 框架无缝结合。作为当今业界最主流的 Web 开发框架,Spring MVC 已经成为当前javaWeb框架事实上的标准。 ## 执行机制 springMVC核心架构的具体流程步骤如下: 1. 首先用户发送请求——>DispatcherServlet(前端控制器),前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制; 2. DispatcherServlet——>HandlerMapping, HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略; 3. DispatcherServlet——>HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器; 4. HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名); 5. ModelAndView的逻辑视图名——> ViewResolver, ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术; 6. View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术; 7. 返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。 ![image-20260313092831428](assets/image-20260313092831428.png) ## 需要编写的代码 - DispatcherServlet 配置到 应用程序中 映射的路径 /* - web.xml - `` - ❌️ @WebServlet : 走不通 - 在容器中配置HandlerMapping、HandlerAdapter, 默认有内置 - 编写自己的Controller(Handler) - 编写视图 # 编写案例 ## 准备说明 - 创建一个父工程 - packing: pom - 不同的知识点分散在其他的应用中 - 骨架 : quickstart webapp - packaging: war 最终发布到Tomcat中 - 子项目使用的是webapp骨架 - 因为使用的webmvc 版本是 6.2.16使用的是 jakartaEE,准备一个**Tomcat 10.xx++++++** 最低版本 : - https://dlcdn.apache.org/tomcat/tomcat-10/v10.1.52/bin/apache-tomcat-10.1.52-windows-x64.zip ![image-20260313094016569](assets/image-20260313094016569.png) ![image-20260313094148910](assets/image-20260313094148910.png)、 ![image-20260313094206104](assets/image-20260313094206104.png) ![image-20260313094223025](assets/image-20260313094223025.png) ## 创建父子项目 创建普通的web工程添加到Tomcat中测试(index.jsp: Hello WebMVC) ### 创建父工程 ![image-20260313094327468](assets/image-20260313094327468.png)
版本号 不要 --SNAPSHOT
![image-20260313094450836](assets/image-20260313094450836.png) ### 创建子项目(webapp) ![image-20260313094515375](assets/image-20260313094515375.png) ![image-20260313094639185](assets/image-20260313094639185.png) ![image-20260313094736483](assets/image-20260313094736483.png) ### 修改index.jsp并发布 ![image-20260313094756744](assets/image-20260313094756744.png) 配置Tomcat并发布 ![image-20260313094813949](assets/image-20260313094813949.png) ![image-20260313094831419](assets/image-20260313094831419.png) ![image-20260313094904821](assets/image-20260313094904821.png) 将web应用发布到Tomcat中 ![image-20260313095143689](assets/image-20260313095143689.png) ![image-20260313095204549](assets/image-20260313095204549.png) ![image-20260313095217057](assets/image-20260313095217057.png) ![image-20260313095259425](assets/image-20260313095259425.png) ## 整合MVC ### 添加依赖 - 添加依赖 spring-webmvc ```xml 4.0.0 com.neuedu.mvc springmvc-learn 1.0 springmvc-01-helloworld war springmvc-01-helloworld Maven Webapp org.springframework spring-webmvc springmvc-01-helloworld ``` ![image-20260313103219962](assets/image-20260313103219962.png) ### 编写Controller ```java package com.neuedu.mvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import java.util.ArrayList; @Controller public class IndexController { /** * http://127.0.0.1:8080/mvc/????? * http://127.0.0.1:8080/mvc/index * @return */ @RequestMapping("/index") public ModelAndView index() { ModelAndView modelAndView = new ModelAndView(); System.out.println("通过Controller 跳转到 jsp "); //设置响应的视图(JSP) modelAndView.setViewName("index.jsp"); //modelAndView.addObject("userList",new ArrayList<>()); return modelAndView; } } ``` ### 编写Spring的配置配置文件 - spring-mvc.xml ```xml ``` ### 在web.xml中配置DispatcherServlet ```xml DispatcherServlet org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath*:spring-mvc.xml DispatcherServlet /* ``` ### 测试 访问Controller 能够看到 index.jsp的内容 ``` http://127.0.0.1:8080/mvc/index ``` ![image-20260313105327737](assets/image-20260313105327737.png) ![image-20260313105437777](assets/image-20260313105437777.png) ```ini -Dfile.encoding=utf-8 ``` # 常见的HandlerMappring、HandlerApapter [localhost:8080/mvc/index](http://localhost:8080/mvc/index) IndexController 默认使用的是 **RequestMappingHandlerMapping** : 通过注解(RequestMapping: GetMapping、DeleteMapping、PatchMapping、PostMapping、PutMapping)的形式查找@Controller 修饰的类 **RequestMappingHandlerAdapter**: 执行使用注解声明的Controller ![image-20260313112453875](assets/image-20260313112453875.png) ![image-20260313112537517](assets/image-20260313112537517.png) # 常用的SpringMVC注解 ## @ResponseBody注解 @responseBody注解的作用是将controller的方法返回的数据写入到response对象的body区,也就是直接将数据写入到输出流中,效果等同于使用 response.getWriter() 输出流对象向前端返回数据。需要注意的是,在使用此注解之后,响应不会再走视图处理器。 1. @responseBody 应用在处理器类上:此处理器类中的所有方法都直接返回数据。 2. @responseBody 应用在处理器类的某个方法上:此处理器类中的某个方法直接返回数据。 ## @RequestMapping注解 用于建立请求URL和处理器方法之间的对应关系。 @RequestMapping 应用在处理器类上: 设置请求URL的第一级访问目录。此处不写的话,就相当于应用的根目录。写的话需要以 / 开头。 它出现的目的是为了使我们的URL可以按照模块化管理。 将springmvc-01-helloworld 复制到 springmvc-02-common-annotation **springmvc-02-common-annotation** ![image-20260313135108997](assets/image-20260313135108997.png) ![image-20260313135335462](assets/image-20260313135335462.png) ## @ResponseBody 、@Requestmapping的使用 - 可以在方法上使用@Requestmapping映射地址,相同的命名空间可以在类上添加@RequestMapping - 如果类的所有方法都需要添加@ResponseBody ,可以统一再类上 @ResponseBody - 如果一个类需要@Controller注解+@ResponseBody 可以使用 @RestController 替换 参考项目 参考项目 ```java package com.neuedu.mvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping("/menu") public class MenuController { /** * 默认视图的名称 * http://127.0.0.1:8080/mvc/menu/list * @return */ @ResponseBody @RequestMapping("/list") public String list() { System.out.println("通过 MenuController 直接返回数据 "); return "Menu list"; } /** * http://127.0.0.1:8080/mvc/menu/getById * @return */ @ResponseBody @RequestMapping("/getById") public String getById() { System.out.println("通过 MenuController 直接返回数据单条数据 "); return "Menu One"; } } ``` ![image-20260313141454236](assets/image-20260313141454236.png) ``` ``` RequestMapping支持所有的请求方法:Post Get 等 使用Method属性定义支持那些 请求方法 ```java @RequestMapping(value="/getById",method= RequestMethod.POST) ``` ![image-20260313143532155](assets/image-20260313143532155.png) 可以根据参数限制 ![image-20260313143738607](assets/image-20260313143738607.png) ![image-20260313143815906](assets/image-20260313143815906.png) 可以使用 - @PostMapping 相当于 @RequestMapping(method=RequestMethod.Post) - GetMapping相当于 @RequestMapping(method=RequestMethod.Get) - DeleteMapping相当于 @RequestMapping(method=RequestMethod.DELETE) - PatchMapping相当于 @RequestMapping(method=RequestMethod.Patch) - PutMapping相当于 @RequestMapping(method=RequestMethod.Put) - DeleteMapping相当于 @RequestMapping(method=RequestMethod.DELETE) # 参数处理 [Method Arguments :: Spring Framework](https://docs.spring.io/spring-framework/reference/6.2/web/webmvc/mvc-controller/ann-methods/arguments.html) ![image-20260317105855480](assets/image-20260317105855480.png) ## 内置参数 - 内置类型直接在方法的形参上直接声明,然后使用: request、response、session,ModelMap等 ```java /** * http://192.168.84.47:8080/mvc/param * http://localhost:8080/mvc/param * http://127.0.0.1:8080/mvc/param/innerParam * @param request * @param response * @param session * @return */ @RequestMapping("/innerParam") public String innerParam(HttpServletRequest request, HttpServletResponse response, HttpSession session) { System.out.println(request); System.out.println(response); System.out.println(session); return "innerParam-success"; } ``` ## 表单参数 从浏览器传递的参数: url?name=zhangsan&age=100, 在方法的形参上根据类型直接声明方法的参数就可以 使用@RequestParam绑定的参数,默认都必须提供,如果不提供,则报错(400) ![image-20260317113241854](assets/image-20260317113241854.png) ![image-20260317113258472](assets/image-20260317113258472.png) 使用required = false 则为非必填项 ![image-20260317113341480](assets/image-20260317113341480.png) ```java package com.neuedu.mvc.controller; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/param") public class ParameterController { /** * http://192.168.84.47:8080/mvc/param * http://localhost:8080/mvc/param * http://127.0.0.1:8080/mvc/param/innerParam * @param request * @param response * @param session * @return */ @RequestMapping("/innerParam") public String innerParam(HttpServletRequest request, HttpServletResponse response , HttpSession session){ System.out.println(request); System.out.println(response); System.out.println(session); return "innerParam-success"; } /** * http://192.168.84.47:8080/mvc/param/queryString * http://localhost:8080/mvc/param/queryString * * * http://127.0.0.1:8080/mvc/param/queryString?name=zs&age=18&fav=bastball&fav=footbal 或者使用表单传餐 * @return */ @RequestMapping("/queryString") public String queryString(@RequestParam("name") String name,@RequestParam("age") int age,@RequestParam("fav") String[] fav){ System.out.println("name = " + name); System.out.println("age = " + age); System.out.println("fav = " + fav); if(fav != null ){ for (String f : fav) { System.out.println("f = " + f); } } return "queryString-success"; } } ``` ![image-20260313145825996](assets/image-20260313145825996.png) ### 参数绑定的异常 现在使用JDK17 编译后,如果去掉@RequestParam注解会报错 ![image-20260317090244351](assets/image-20260317090244351.png) ![image-20260317084922938](assets/image-20260317084922938.png) 由于使用的是jDK17的版本,由源码编译到字节码中会忽略参数的名称 `public void method1(Int numa ,int numb){}` 类似之后 `public void method1(Int var1 ,int var2){}`,需要给编译器添加参数 -parameters 保留原始参数名称,方便反编译的时候获取参数名称. 参考;[Spring Framework 6.0 Release Notes · spring-projects/spring-framework Wiki · GitHub](https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-6.0-Release-Notes) ![image-20260317090330972](assets/image-20260317090330972.png) 在maven中添加插件配置 ```xml org.apache.maven.plugins maven-compiler-plugin 3.14.0 17 17 true ``` **重新编译后启动问题解决** ### 整合前端项目 将Vue项目整合到springmvc中,使用前后端分离的方式来开发(真实项目) ![image-20260317092720503](assets/image-20260317092720503.png) 从 [vue-vite-工程化开发: vue-vite-工程化开发 使用ts语言、vite、pinia、vue-router、Elementplus](https://gitee.com/hrbu2023/vue-vite-Engineering) 拉取项目. - 安装node依赖 `npm install` - 启动 `npm run ` - 安装axios 用于发起网络请求`npm install axios` ### 使用Vue发起表单请求 ```vue ``` ```java /** * @return */ @ResponseBody @RequestMapping("/user/save") public String save(String name, String city, boolean delivery, @RequestParam("type[]") String[] types, int gender, String desc) { System.out.println("name = " + name); System.out.println("city = " + city); System.out.println("delivery = " + delivery); System.out.println("types = " + types); System.out.println("gender = " + gender); System.out.println("desc = " + desc); return "user list"; } ``` ### 使用实体类接受表单参数 ```java package com.neuedu.mvc.controller.po; import lombok.Data; import org.springframework.web.bind.annotation.RequestParam; @Data //setter getter tostring public class User { String name; String city; boolean delivery; String[] type; Integer gender; String desc; } ``` ```vue ``` ``` @ResponseBody @RequestMapping("/user/save2") //public String save(String name, String city, boolean delivery, @RequestParam("type[]") String[] types, int gender, String desc) { public String save2(User user) { System.out.println(user); return "user list"; } ``` # 响应类型 ## 没有使用@ResponseBody ### String 返回的Stirng作为 视图的名字(jsp、html) - index.jsp 视图的名称 ```java @RequestMapping("/index") public String index() { System.out.println("通过Controller 跳转到 jsp "); return "index.jsp"; } ``` - ModelAndView : 里面的Viewname属性是视图名称,而且可以携带数据 类似于在`Request.setAttribute ` ``` @RequestMapping("/index") public ModelAndView index() { ModelAndView modelAndView = new ModelAndView(); System.out.println("通过Controller 跳转到 jsp "); //设置响应的视图(JSP) modelAndView.setViewName("index.jsp"); //modelAndView.addObject("userList",new ArrayList<>()); return modelAndView; } ``` ## 使用@ResponseBody 可以在方法上添加@ResponseBody 返回值通过实现转换并写入HttpMessageConverter 到浏览器。请看[ `@ResponseBody`](https://docs.spring.io/spring-framework/reference/6.2/web/webmvc/mvc-controller/ann-methods/responsebody.html)。 `HttpMessageConverter` 各种数据类型序列化并发送到浏览器 @ResponseBody 除了可以加到某一个、一些方法上也可以在 类上写 ![image-20260317135142483](assets/image-20260317135142483.png) 可以使用@RestController 代替 `@ResponseBody 、@Controller` ![image-20260317135233507](assets/image-20260317135233507.png) | 原始数据类型 | 浏览器接到的(文本类型、二进制) | | |--------------|------------------|--------------------------------------| | String | 原样输出 | | | 对象(Map、User) | JSON 对象 | 需要用到第三方的库Gson、FastJSON 、**Jackson** | | List | JSON数组 | | ### 使用Jackson将内容存对象转换成 JSON ![image-20260318092523140](assets/image-20260318092523140.png) Spring6 需要Jackson 2.15+ ```xml com.fasterxml.jackson.core jackson-databind 2.17.1 compile ``` 利用MappingJackson2HttpMessageConverter来进行消息转换 ![image-20260317144146628](assets/image-20260317144146628.png) 需要把MappingJackson2HttpMessageConverter 注册到IOC容器中 ```java @GetMapping("/getById") Map getById(int id) { Map user = new HashMap(); user.put("id",id); user.put("username","zhagnsan"); user.put("nickname","张三"); return user; } ``` ### 在容器中开启mvc驱动 ``` ``` ![image-20260317144745250](assets/image-20260317144745250.png) ![image-20260317144444575](assets/image-20260317144444575.png) 测试 ![image-20260317144800458](assets/image-20260317144800458.png) ## springmvc-00-common 为了后面测试方便通用的代码我们放到 springmvc-00-common ![1773795163463](assets/1773795163463.png) 其他工程可以依赖我们的 00-common 重复代码可以不用反复的编写了 # 整合MyBatis - springmvc - springmvc 配置Mybatis SqlSessionFactory Mapper 需要的依赖 - spring-webmvc - jakarta.servlet-api - junit - spring-test - lombok - springmvc-00-common - spring-jdbc - aspectjweaver - mysql驱动 - 数据源 - mybatis - mybatis-spring ![image-20260318094440744](assets/image-20260318094440744.png) ![image-20260318094939987](assets/image-20260318094939987.png) ## 依赖 ### 父项目 ```xml 4.0.0 com.neuedu.mvc springmvc-learn 1.0 pom springmvc-learn springmvc-01-helloworld springmvc-02-common-annotation springmvc-03-return-type springmvc-00-common springmvc-04-mybatis UTF-8 6.2.16 6.1.0 1.18.42 5.13.4 2.17.1 8.2.0 3.5.19 4.0.0 1.8.7 1.2.28 org.apache.maven.plugins maven-compiler-plugin 3.14.0 17 17 true org.springframework spring-webmvc ${spring.version} org.springframework spring-test ${spring.version} test org.junit.jupiter junit-jupiter-api ${junit.version} test org.springframework spring-test jakarta.servlet jakarta.servlet-api ${servlet.version} provided com.fasterxml.jackson.core jackson-databind ${jackson.version} compile org.projectlombok lombok ${lombok.version} org.springframework spring-jdbc ${spring.version} com.mysql mysql-connector-j ${mysql.version} org.mybatis mybatis ${mybatis.version} org.mybatis mybatis-spring ${mybatis.spring.version} compile com.alibaba druid ${druid.version} compile ``` ### 子项目 ```xml 4.0.0 com.neuedu.mvc springmvc-learn 1.0 springmvc-04-mybatis war springmvc-04-mybatis Maven Webapp org.springframework spring-webmvc org.springframework spring-test org.junit.jupiter junit-jupiter-api org.springframework spring-test jakarta.servlet jakarta.servlet-api com.fasterxml.jackson.core jackson-databind org.projectlombok lombok org.springframework spring-jdbc com.mysql mysql-connector-j org.mybatis mybatis org.mybatis mybatis-spring com.alibaba druid spring-mvc-04-mybatis ``` ## 搭建springmvc - spring-ssm.xml - web.xml中配置DispatcherServlet - Controller测试 http://127.0.0.1:8080/mvc/user/index ![image-20260318095604327](assets/image-20260318095604327.png) ## 整合Mybatis - 先生成MyBatis代码 - 数据源 - 在容器中注册 SQLSessionFactory - 扫描Mapper - 配置事务管理器 ![image-20260318095842818](assets/image-20260318095842818.png) ```xml ``` ### 从容器中获取一个Mapper进行测试 # 整合Vue实现增删改查(以User表为例) - 接口层 - Controller - list - getById - save - update - delete - Service - Dao(Mapper) - 前端(user/index.vue) - 列表查询 - 修改 - 删除 ## 接口 > 完善生成的代码(Mapper) 添加查询集合的功能 ### mapper添加查询集合 ``` ``` ### Service ```java package com.neuedu.mvc.service; import com.neuedu.mvc.mapper.UserMapper; import com.neuedu.mvc.po.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class UserService { @Autowired private UserMapper userMapper; public User getById(Long id){ return userMapper.selectByPrimaryKey(id); } public List selectList(User user){ return userMapper.selectList(user); } public boolean removeById(Long id){ return userMapper.deleteByPrimaryKey(id)>0; } public boolean save(User record){ return userMapper.insert(record)>0; } public boolean updateById(User record){ return userMapper.updateByPrimaryKeySelective(record)>0; } } ``` ### Controller ```java package com.neuedu.mvc.controller; import com.neuedu.mvc.po.User; import com.neuedu.mvc.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; /** * http://127.0.0.1:8080/mvc/user/index * * @return */ @RequestMapping("/index") String index() { return "success"; } /** * 查询集合 * @param user * @return */ @RequestMapping("/list") public List list(User user){ return userService.queryList(user); } /** * 根据主键查询单条数据 用于更新前 展示数据 * @param id * @return */ @RequestMapping("/getById") public User getById(long id ){ return userService.getById(id); } /** * 更新操作 * @param user * @return */ @RequestMapping("/updateById") public boolean update(User user){ return userService.updateById(user); } /** * 删除操作 * @param user * @return */ @RequestMapping("/removeById") public boolean removeById(long id){ return userService.removeById(id); } } ``` ### 测试Controller ![image-20260318110228610](assets/image-20260318110228610.png) 解决json序列化的时候日期格式的问题 输出的默认是 时间戳,如果希望自定义格式,可以在PO上添加 @JsonFormat注解 ```java package com.neuedu.mvc.po; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.util.Date; /** * 用户表 * @TableName user */ @Data public class User { /** * 主键 */ private Long userId; /** * 用户名 */ private String username; /** * 密码 */ private String password; /** * 过期时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date expire; /** * 最后一次登录四件 */ private Date lastLogin; /** * 真实姓名 */ private String realName; /** * 部门ID */ private Long deptId; /** * 部门名称 */ private String deptName; /** * 级别ID */ private Long levelId; /** * 创建时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date createTime; @Override public boolean equals(Object that) { if (this == that) { return true; } if (that == null) { return false; } if (getClass() != that.getClass()) { return false; } User other = (User) that; return (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId())) && (this.getUsername() == null ? other.getUsername() == null : this.getUsername().equals(other.getUsername())) && (this.getPassword() == null ? other.getPassword() == null : this.getPassword().equals(other.getPassword())) && (this.getExpire() == null ? other.getExpire() == null : this.getExpire().equals(other.getExpire())) && (this.getLastLogin() == null ? other.getLastLogin() == null : this.getLastLogin().equals(other.getLastLogin())) && (this.getRealName() == null ? other.getRealName() == null : this.getRealName().equals(other.getRealName())) && (this.getDeptId() == null ? other.getDeptId() == null : this.getDeptId().equals(other.getDeptId())) && (this.getDeptName() == null ? other.getDeptName() == null : this.getDeptName().equals(other.getDeptName())) && (this.getLevelId() == null ? other.getLevelId() == null : this.getLevelId().equals(other.getLevelId())) && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime())); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode()); result = prime * result + ((getUsername() == null) ? 0 : getUsername().hashCode()); result = prime * result + ((getPassword() == null) ? 0 : getPassword().hashCode()); result = prime * result + ((getExpire() == null) ? 0 : getExpire().hashCode()); result = prime * result + ((getLastLogin() == null) ? 0 : getLastLogin().hashCode()); result = prime * result + ((getRealName() == null) ? 0 : getRealName().hashCode()); result = prime * result + ((getDeptId() == null) ? 0 : getDeptId().hashCode()); result = prime * result + ((getDeptName() == null) ? 0 : getDeptName().hashCode()); result = prime * result + ((getLevelId() == null) ? 0 : getLevelId().hashCode()); result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode()); return result; } @Override public String toString() { String sb = getClass().getSimpleName() + " [" + "Hash = " + hashCode() + ", userId=" + userId + ", username=" + username + ", password=" + password + ", expire=" + expire + ", lastLogin=" + lastLogin + ", realName=" + realName + ", deptId=" + deptId + ", deptName=" + deptName + ", levelId=" + levelId + ", createTime=" + createTime + "]"; return sb; } } ``` ![image-20260318110400028](assets/image-20260318110400028.png) ## 使用 spring-test测试 Controller [传送门](https://docs.spring.io/spring-framework/reference/6.2/testing/mockmvc/hamcrest/setup-steps.html) 可以使用MockMvc mockMvc 启动一个没有Tomcat的测试环境 - 声明 WebApplicationContext wac;对象 需要添加 @WebAppConfiguration 注解 //告诉测试框架,基于 web的环境, 在容器中注册 ApplicationContenxt - MockMvc mockMvc ![image-20260318112948944](assets/image-20260318112948944.png) ```java package com.neuedu.mvc.controller; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import static org.junit.jupiter.api.Assertions.*; import static org.springframework.test.web.client.match.MockRestRequestMatchers.jsonPath; @SpringJUnitConfig(locations = "classpath:spring-ssm.xml") @WebAppConfiguration //告诉测试框架,基于 web的环境, 在容器中注册 ApplicationContenxt class UserControllerTest { @Autowired WebApplicationContext wac; //不是autowired 需要 每次动态创建 private MockMvc mockMvc; @BeforeEach void setUp() { mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); } @Test void list() throws Exception { //mockMvc 的作用是 模拟浏览器 模拟数据 ,发起网络请求 //mockMvc.perform(MockMvcRequestBuilders.get("/user/abc")) // 404 mockMvc.perform(MockMvcRequestBuilders.get("/user/list")) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(result -> System.out.println(result.getResponse().getContentAsString())); } @Test void getById() throws Exception { //mockMvc 的作用是 模拟浏览器 模拟数据 ,发起网络请求 mockMvc.perform(MockMvcRequestBuilders.get("/user/getById?id=1")) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(result -> System.out.println(result.getResponse().getContentAsString())); } } ``` ## 前端调用接口完成CRUD ### 列表查询 > vue-vite-Engineering/src/views/crud/user/index.vue 列表页面 - 添加路由 ```js { path: '/crud', name : 'CRUD', component : Layout, children : [ { name: 'CRUDUser', path: '/crud/user', // vue-vite-Engineering/src/views/crud/user/index.vue component: () => import('@/views/crud/user/index.vue'), meta: { title: '用户管理', icon: 'user' } }, ] } , ``` - 在NavMenu中添加菜单 ```js let menus = [ { title: '参数绑定', path: '/parameter', children: [ {title: '基本参数', path: '/parameter/common'}, ] }, { title: '返回类型', path: '/returntype', children: [ {title: '基本参数', path: '/returntype/index'}, ] }, { title: '增删改查', path: '/crud', children: [ {title: '用户管理', path: '/crud/user'}, ] }, ``` - 完成列表功能 ```vue ``` ![image-20260318134739861](assets/image-20260318134739861.png) ### 添加功能 @RequestBody注解,将浏览器以body方式发送的JSON转换后绑定到user对象中 ![image-20260318143902598](assets/image-20260318143902598.png) - index.vue ```vue ``` - edit.vue ```vue ``` ### 完成修改 - 添加操作列 - 打开编辑的Dialog 传递一个需要修改id - 让编辑页面 判断是否存在id,如果存在id,则查询 - 保存或者更新 - 有ID,更新 - 否则 添加 # RESTFul风格API | 序号 | 方法 | 描述 | |:---|:--------|:-------------------------------------------------------------------------------| | 1 | GET | 从服务器获取资源。用于请求数据而不对数据进行更改。例如,从服务器获取网页、图片等。 | | 2 | POST | 向服务器发送数据以创建新资源。常用于提交表单数据或上传文件。发送的数据包含在请求体中。 | | 3 | PUT | 向服务器发送数据以更新现有资源。如果资源不存在,则创建新的资源。与 POST 不同,PUT 通常是幂等的,即多次执行相同的 PUT 请求不会产生不同的结果。 | | 4 | DELETE | 从服务器删除指定的资源。请求中包含要删除的资源标识符。 | | 5 | PATCH | 对资源进行部分修改。与 PUT 类似,但 PATCH 只更改部分数据而不是替换整个资源。 | | 6 | HEAD | 类似于 GET,但服务器只返回响应的头部,不返回实际数据。用于检查资源的元数据(例如,检查资源是否存在,查看响应的头部信息)。 | | 7 | OPTIONS | 返回服务器支持的 HTTP 方法。用于检查服务器支持哪些请求方法,通常用于跨域资源共享(CORS)的预检请求。 | | 8 | TRACE | 回显服务器收到的请求,主要用于诊断。客户端可以查看请求在服务器中的处理路径。 | | 9 | CONNECT | 建立一个到服务器的隧道,通常用于 HTTPS 连接。客户端可以通过该隧道发送加密的数据。 | 语义化的API > 获取用户信息 Get : /user/getUserById/100 Get /user/getUserById/200 > > 保存用户信息 Post /user/save/admin/123456 把参数通过 body或者queryString的方式 传递, 使用@PathVariable 获取路径中的数据信息 ![image-20260319085200649](assets/image-20260319085200649.png) ![image-20260319085941194](assets/image-20260319085941194.png) # 文件上传 SpringMVC 实现上传的功能 - 使用 StandardServletMultipartResolver 将Request中的 上文文件的内容解析到 方法的参数中并绑定 - 并配置 上传大小xxxxx - 在方法中声明 MultipartFile 或者是 MultipartFile[] 、或者是集合(List) - 使用APIFox 测试上传的接口 - 前端表单 - 传统的方式 post enctype:multipart-forms - Vue: [Upload 上传 | Element Plus](https://element-plus.org/zh-CN/component/upload#upload-上传) ## 后台接口实现 ### 配置MultipartResolver ```xml ``` ![image-20260319100418474](assets/image-20260319100418474.png) ### web.xml中配置Servlet上传信息 ![image-20260319095913766](assets/image-20260319095913766.png) ### 使用Controller接受上传信息 ```java package com.neuedu.mvc.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @RestController() @RequestMapping("/file") public class FileUploadController { @RequestMapping("/upload") String upload(MultipartFile file){ System.out.println("file.getOriginalFilename() = " + file.getOriginalFilename()); System.out.println("file.getSize() = " + file.getSize()); return "success"; } } ``` 可以从MultipartFile中解析到所有文件信息 ### 使用Apifox测试上传 ![image-20260319100306674](assets/image-20260319100306674.png) ## Vue界面 使用Upload组件 ## 使用数据库存储上传的信息 - 创建表 - 上传成功之后,存储到数据库中 ```sql CREATE TABLE `his`.`upload`( `id` BIGINT NOT NULL COMMENT '主键', `origin_name` VARCHAR(100) COMMENT '原始文件名称', `ext` VARCHAR(10) COMMENT '扩展名', `size` BIGINT COMMENT '大小', `content_type` VARCHAR(50) COMMENT '媒体类型', `readl_name` VARCHAR(200) COMMENT '实际文件名称(存储的)', `create_time` DATETIME DEFAULT NOW() COMMENT '上传时间', PRIMARY KEY (`id`) ); ALTER TABLE `his`.`upload` CHANGE `id` `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键'; ALTER TABLE `his`.`upload` CHANGE `content_type` `content_type` VARCHAR(200) CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '媒体类型'; ``` 使用工具生成PO、Mapper ![image-20260319113537278](assets/image-20260319113537278.png) ### 修改FileUploadContorller 调用service存储数据 ```java package com.neuedu.mvc.service; import com.neuedu.mvc.mapper.UploadMapper; import com.neuedu.mvc.mapper.UserMapper; import com.neuedu.mvc.po.Upload; import com.neuedu.mvc.po.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class UploadService { @Autowired private UploadMapper uploadMapper; public Upload getById(Long id) { return uploadMapper.selectByPrimaryKey(id); } public List queryList(Upload po) { return uploadMapper.selectList(po); } public boolean removeById(Long id) { return uploadMapper.deleteByPrimaryKey(id) > 0; } public boolean save(Upload po) { return uploadMapper.insert(po) > 0; } public boolean updateById(Upload po) { return uploadMapper.updateByPrimaryKeySelective(po) > 0; } } ``` ```java package com.neuedu.mvc.controller; import com.neuedu.mvc.po.Upload; import com.neuedu.mvc.service.UploadService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.UUID; @RestController() @RequestMapping("/file") public class FileUploadController { @Autowired private UploadService uploadService; @RequestMapping("/upload") boolean upload(MultipartFile file) throws IOException { //准备信息 String uudid = UUID.randomUUID().toString(); String originalFilename = file.getOriginalFilename(); //win.jpg String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String realName = uudid + ext; //转出 File dist = new File("d:\\upload", realName); file.transferTo(dist); //将信息存储到数据 Upload uploadInfo = new Upload(); uploadInfo.setExt(ext); uploadInfo.setOriginName(originalFilename); uploadInfo.setSize(file.getSize()); uploadInfo.setReadlName(realName); uploadInfo.setContentType(file.getContentType()); //持久化数据 boolean success = uploadService.save(uploadInfo); return success; } } ``` ## 使用Vue实现列表查询 ```java package com.neuedu.mvc.controller; import com.neuedu.mvc.po.Upload; import com.neuedu.mvc.po.User; import com.neuedu.mvc.service.UploadService; import com.neuedu.mvc.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.util.List; @RestController @RequestMapping("/upload_info") public class UploadInfoController { @Autowired private UploadService uploadService; /** * http://127.0.0.1:8080/mvc/upload_info/list * 查询集合 * * @param upload * @return */ @RequestMapping("/list") public List list(Upload upload) { return uploadService.queryList(upload); } /** * @param id * @return */ @GetMapping("/{id}") public Upload getById(@PathVariable("id") long id) { return uploadService.getById(id); } /** * 添加修改操作 * * @param upload * @return */ @PostMapping("/saveOrUpdate") public boolean saveOrUpdate(@RequestBody Upload upload) { if (upload.getId() != null) { return uploadService.updateById(upload); } else { return uploadService.save(upload); } } /** * 删除操作 * * @return */ //@DeleteMapping("/{id}") @RequestMapping("/removeById") public boolean removeById(long id) { return uploadService.removeById(id); } } ``` 使用ResponseEntity 响应内容 # 拦截器 类似于 Servlet中的Filter能够对请求进行拦截 ``` springmvc-04-mybatis ``` ![image-20260320084200839](assets/image-20260320084200839.png) - preHandle返回true/false,如果 返回false则 后续的 所有功能都不执行了(PostHanlder、afterCompletion 、Controller方法方法), 如果为true则放行 - postHandle: 在Controller方法之后执行,如果 Controller方法报错了则无法执行 - afterCompletion 无论是否存在异常都能执行 , (true / false **???**): ## 实现拦截器 - 定义拦截器 - 配置到SpringMVC中 执行顺序如下: ![image-20260320085813001](assets/image-20260320085813001.png) | preHandle | Controller方法 | postHandle | afterCompletion | |-----------|--------------|------------|-----------------| | true | 执行,并没有报错 | 执行 | 执行 | | true | 执行,但是报错 | x | 执行 | | false | x | x | x | # 登录的实现 ## 登录逻辑 ![image-20260320100534149](assets/image-20260320100534149.png) ## JWT Java Web Token **JWT(JSON Web Token)** 一种基于 JSON 的开放标准([RFC 7519](https://zhida.zhihu.com/search?content_id=260899166&content_type=Article&match_order=1&q=RFC+7519&zd_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ6aGlkYV9zZXJ2ZXIiLCJleHAiOjE3NzQxNDMxOTQsInEiOiJSRkMgNzUxOSIsInpoaWRhX3NvdXJjZSI6ImVudGl0eSIsImNvbnRlbnRfaWQiOjI2MDg5OTE2NiwiY29udGVudF90eXBlIjoiQXJ0aWNsZSIsIm1hdGNoX29yZGVyIjoxLCJ6ZF90b2tlbiI6bnVsbH0.SFYo-dYsEmAR3hKztJJf5wZxYju06GWhGBnCB-Pbg3g&zhida_source=entity) ),用于在各方之间安全地传递信息。 **特点** - **紧凑(Compact)**:可通过 URL、POST 参数或 HTTP 头传输 - **自包含(Self-contained)**:携带了用户的基本身份信息和声明(Claims) - **可验证(Verifiable)**:签名保证数据未被篡改 **典型场景**:用户登录后,服务器颁发一个 JWT,客户端每次请求都携带它,无需再查数据库做 Session 验证。 | 部分 | 用途 | 示例内容 | |---------------|---------------------------|------------------------------------------------------------------| | Header(头部) | 指定算法和类型 | {"alg":"HS256","typ":"JWT"} | | Payload(负载) | 存放声明(Claims),如用户 ID、过期时间等 | {"sub":"1234567890","name":"Alice","exp":1600000000} | | Signature(签名) | 保证前两部分不被篡改 | HMACSHA256(Base64Url(Header) + "." + Base64Url(Payload), Secret) | **JWT 明文的**,不存放 秘钥、指令等信息 服务器端 - 发放令牌 信息: admin 签名(秘钥)+加密算法 : Token (明文 heander payload signnature ) - 验证令牌 token拿过来(payload -- 重新生成签名 跟signnature 再对比 ) ### 使用jwt库 生成 token 和验证token 使用[com.auth0](https://mvnrepository.com/artifact/com.auth0) » [java-jwt](https://mvnrepository.com/artifact/com.auth0/java-jwt) ``` com.auth0 java-jwt 4.5.0 compile ``` ## LoginController 完成登录的逻辑 - LoginInterceptor ```java package com.neuedu.mvc.controller; import com.neuedu.mvc.po.User; import com.neuedu.mvc.service.LoginService; import com.neuedu.mvc.util.JwtUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController public class LoginController { @Autowired private LoginService loginService; @RequestMapping("/login") public Map login(User user){ Map map = new HashMap(); map.put("success",false); System.out.println(user); User queryLoginUser = loginService.login(user); if(queryLoginUser != null){ map.put("success",true); String token = JwtUtils.create(queryLoginUser); map.put("token",token); } return map; } } ``` ![image-20260320103657733](assets/image-20260320103657733.png) ![image-20260320103714855](assets/image-20260320103714855.png) ## 实现Vue登录并存储token ```vue ``` ## 登录拦截器,判断是否有token - 由token放行 - 没有token 、token验证失败,返回错误信息 ```java package com.neuedu.mvc.interceptor; import com.fasterxml.jackson.databind.ObjectMapper; import com.neuedu.mvc.po.User; import com.neuedu.mvc.util.JwtUtils; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import java.io.PrintWriter; import java.util.Arrays; import java.util.HashMap; import java.util.Map; public class LoginInterceptor implements HandlerInterceptor { private final String[] ignoreUrls = {"/login"}; /** * 判断是否是例外的URL * * @param request * @return */ private boolean isIgnoreUrl(HttpServletRequest request) { // /mvc/user/list // /mvc/login String requestURI = request.getRequestURI(); // /mvc String contextPath = request.getContextPath(); String uri = requestURI.substring(contextPath.length()); boolean match = Arrays.stream(ignoreUrls).anyMatch(url -> url.equals(uri)); return match; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //忽略的路径 if (isIgnoreUrl(request)) { return true; } //获取Token boolean loginInfoSuccess = false; // 是否放行 默认为false String token = request.getHeader("token"); // 验证token是否正确 if (token != null && !"".equals(token)) { try { User loginUser = JwtUtils.verify(token); loginInfoSuccess = true; } catch (Exception e) { } } //登录失败需要响应 401 if (!loginInfoSuccess) { Map map = new HashMap(); map.put("code", 401); map.put("msg", "登录回话已过期,请重新登录"); ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(map); response.setContentType("application/json;charset=utf-8"); PrintWriter out = response.getWriter(); out.write(json); } return loginInfoSuccess; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("LoginInterceptor.postHandle"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("LoginInterceptor.afterCompletion"); } } ``` ## axios发送请求的时候携带Token ```vue ``` ## Vue封装request.ts 每次请求都携带token ```ts import axios, {type AxiosRequestConfig} from "axios"; // 创建实例 let instance = axios.create(); /** * 请求拦截器, 添加token */ instance.interceptors.request.use((config) => { // 在发送请求之前做些什么 const token = sessionStorage.getItem("token") if (token) { //请求头中添加Token config.headers['token'] = token } return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); }); export default instance ``` ## axios封装(通用请求方法 ```ts import axios from "axios"; import type {AxiosResponse, AxiosRequestConfig} from "axios"; // 创建实例 let instance = axios.create(); /** * 请求拦截器, 添加token */ instance.interceptors.request.use((config) => { // 在发送请求之前做些什么 const token = sessionStorage.getItem("token") if (token) { //请求头中添加Token config.headers['token'] = token } return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); }); /** * 响应拦截器 */ // 添加响应拦截器(暂时没有特殊处理,响应结果的封装放到下面request方法中了) instance.interceptors.response.use(function (response: AxiosResponse) { return response }, function (error) { // 超出 2xx 范围的状态码都会触发该函数。 // 对响应错误做点什么 return Promise.reject(error); }); export default function request(config: AxiosRequestConfig): Promise { return new Promise((resolve, reject) => { instance .request(config) .then((response: AxiosResponse) => { const data = response.data resolve(data) // let { code, msg, data } = response.data; // // 异常情况 // if (code !== 200) { // //401 未登录 --> 跳转到登录画面 // // // alert("失败") // if (code === 401) { // // ElMessageBox.confirm('登录已超时,是否重新登录', 'Warning', { // confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', // }).then(() => { // // 跳转到登录页面 // router.push('/login'); // // }) // // // // // } // // //pedding 状态 success fail // reject(msg); // // return; // } //200的情况正常返回 // resolve(data) }) .catch((err) => { // alert("失败了 。。。。") console.log(err) // if (err.message) { // // alert(msg||"请求失败"); // let msg = err.message; // if (err.status == 404) { // msg = "请求失败,请检查接口地址是否正确" // } // // // toast_error(msg || "请求失败") // } reject(err) }) }) } // export default instance ``` ## 通用的返回结果 - code: 200 404 403 401 500 - msg - data ```java package com.neuedu.mvc.common; import java.io.Serializable; /** * 通用返回结果的包装类 * * @param */ public class Ret implements Serializable { private Integer code; private String msg; private T data; public Ret(Integer code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; // } public static Ret ok(T data) { return new Ret<>(200, "操作成功", data); } public static Ret fail() { return fail("操作失败"); } public static Ret fail(String msg) { return new Ret<>(500, msg, null); } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } } ``` ![image-20260320142624699](assets/image-20260320142624699.png) ![image-20260320143445312](assets/image-20260320143445312.png) # 统一的异常处理 在容器中注册 类型为 HandlerExceptionResolver的全局异常处理器 ```java package com.neuedu.mvc.common; import com.fasterxml.jackson.databind.ObjectMapper; import com.neuedu.mvc.po.User; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import java.io.IOException; import java.io.PrintWriter; import java.util.List; @Component public class BusinessHandlerExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { String message = ex.getMessage(); ObjectMapper objectMapper = new ObjectMapper(); //String userJson = objectMapper.writeValueAsString(users); try { Ret ret = Ret.fail(message != null ? message : "操作失败"); String json = objectMapper.writeValueAsString(ret); PrintWriter out = response.getWriter(); out.write(json); out.flush(); out.close(); } catch (IOException e) { throw new RuntimeException(e); } return null; } } ``` # api定义 - vue-vite-Engineering/src/api/crud/user/index.ts ```ts import request from "@/utils/request.ts"; import type {User} from "@/types"; export function list(){ return request({ url: "/api/mvc/user/list" }) } export function removeById(id: number){ return request({ url: "/api/mvc/user/removeById?id="+id }) } export function saveOrUpdate(data:User){ return request({ method:"POST", url: "/api/mvc/user/saveOrUpdate", data }) } export function getById(id: number){ return request({ method:"GET", url: "/api/mvc/user/"+id, }) } ``` ## 使用api - vue-vite-Engineering/src/views/crud/user/index.vue ```vue ``` - vue-vite-Engineering/src/views/crud/user/edit.vue ```vue ``` # Mybatis分页插件 - 添加依赖 - 注册插件 - 给SqlSessionFactory - 使用分页功能 ## 依赖 ```xml com.github.pagehelper pagehelper 6.1.1 compile ``` ## 注册插件 ```xml helperDialect=mysql reasonable=true supportMethodsArguments=true params=count=countSql autoRuntimeDialect=true ``` ## 使用分页插件 ```java package com.neuedu.mvc.mapper; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.neuedu.mvc.common.Ret; import com.neuedu.mvc.po.User; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import java.util.List; @SpringJUnitConfig(locations = "classpath:spring-ssm.xml") public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void test() throws JsonProcessingException { int current = 1; //当前的页号 int size = 3; // 每页显示条数 Page page = PageHelper.startPage(current, size); userMapper.selectList(null); // 获取分页信息 int pageNum = page.getPageNum(); //总页数 int pages = page.getPages(); //当前页显示的条数 int pageSize = page.getPageSize(); //总记录数 long total = page.getTotal(); //当前页的数据 List result = page.getResult(); System.out.println("pageNum = " + pageNum); System.out.println("pages = " + pages ); System.out.println("pageSize = " + pageSize); System.out.println("total = " + total); System.out.println("result = " + result); System.out.println("\r\n\r\n"); current = 2; //当前的页号 size = 3; // 每页显示条数 page = PageHelper.startPage(current, size); userMapper.selectList(null); // 获取分页信息 pageNum = page.getPageNum(); //总页数 pages = page.getPages(); //当前页显示的条数 pageSize = page.getPageSize(); //总记录数 total = page.getTotal(); //当前页的数据 result = page.getResult(); System.out.println("pageNum = " + pageNum); System.out.println("pages = " + pages ); System.out.println("pageSize = " + pageSize); System.out.println("total = " + total); System.out.println("result = " + result); } } ``` ## 调用分页接口 - vue-vite-Engineering/src/api/crud/user/index.ts ```ts export function page(params: any) { return request>({ url: "/api/user/page", params }) } ``` 定义分页类型 - vue-vite-Engineering/src/types/index.ts ```ts export interface User { userId?: number, username: string, password: string, expire?: string, lastLogin?: number, realName: string, deptId?: number, deptName?: string, levelId?: number, createTime?: string } /** * 分页类型 */ export interface PageInfo{ records:T[]; total:number; pages:number; size:number; current:number; } ``` ## 使用分页插件 ![image-20260324094747358](assets/image-20260324094747358.png) # 基于SSM+Vue3.x+ElementPlus+JWT CRUD - 后端 - Controller - Service - mapper 生成 - PO 生成 - 列表: index.vue - 编辑edit.vue - API: api/xxx/index.ts - 配置路由:router/index.ts - 配置菜单 ## 后台 ### DeptController ```java package com.neuedu.mvc.controller; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.neuedu.mvc.common.Ret; import com.neuedu.mvc.po.Dept; import com.neuedu.mvc.po.User; import com.neuedu.mvc.service.DeptService; import com.neuedu.mvc.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/dept") public class DeptController { @Autowired private DeptService deptService; /** * http://127.0.0.1:8080/ssm/dept/page?pageNum=1&pageSize=10 * * @return * @throws Exception */ @RequestMapping("/page") public Ret> list(Dept record, @RequestParam(value = "pageNum", required = false, defaultValue = "1") int pageNum, @RequestParam(value = "pageSize", required = false, defaultValue = "10") int pageSize) throws Exception { Page page = PageHelper.startPage(pageNum, pageSize); deptService.queryList(record); return Ret.ok(new PageInfo(page)); } /** *

* Get http://127.0.0.1:8080/mvc/user/200 获取数据 * @param id * @return */ @GetMapping("/{id}") public Ret getById(@PathVariable("id") long id) { return Ret.ok(deptService.getById(id)); } /** * 添加修改操作 * @param record * @return */ @PostMapping("/saveOrUpdate") public Ret saveOrUpdate(@RequestBody Dept record) { boolean success = false; if (record.getDeptId() != null) { success = deptService.updateById(record); } else { success = deptService.save(record); } return Ret.ok(success); } /** * 删除操作 * * @return */ @RequestMapping("/removeById") public Ret removeById(long id) { return Ret.ok(deptService.removeById(id)); } } ``` - DeptService - DeptMapper.java - DeptMapper.xml ## 前台 详见gitee代码