SpringBoot不同的@Mapping使用

SpringBoot不同的@Mapping使用

一、介绍

一般@Mapping类注解在Spring框架中用于将HTTP请求映射到对应的处理器方法。它们各自对应于不同类型的HTTP方法,主要用于RESTful Web服务中。以下是每个注解的作用:

  1. @GetMapping: 用于映射HTTP GET请求到处理器方法。通常用于读取操作。

  2. @PostMapping: 用于映射HTTP POST请求到处理器方法。通常用于创建操作。

  3. @PutMapping: 用于映射HTTP PUT请求到处理器方法。通常用于更新操作,其中更新操作是幂等的,意味着多次执行同一个请求会得到相同的结果。

  4. @DeleteMapping: 用于映射HTTP DELETE请求到处理器方法。通常用于删除操作。

  5. @PatchMapping: 用于映射HTTP PATCH请求到处理器方法。PATCH与PUT类似,但通常用于部分更新资源。

  6. @RequestMapping: 是一个通用的注解,可以用于映射任何HTTP方法,通过指定method属性来明确映射类型。如果不指定method,它将映射到所有HTTP方法。这是最早的注解,后来为了方便使用,根据HTTP方法细分出了上述的专用注解。

二、使用

在Spring框架中,不同的@Mapping注解通常与不同的数据获取方式结合使用,以适应各种HTTP请求的特点:

  1. @GetMapping:

    • 用于获取数据,通常与@RequestParam一起使用来接收查询参数。

    • 示例: 获取URL中查询参数的值。

      language-java
      1
      2
      3
      4
      5
      @GetMapping("/users")
      public String getUsers(@RequestParam String name) {

      // ...
      }
    • 也可以用@PathVariable来获取URL中的路径变量。

      language-java
      1
      2
      3
      4
      5
      @GetMapping("/users/{id}")
      public String getUserById(@PathVariable Long id) {

      // ...
      }
  2. @PostMapping:

    • 用于创建数据,通常与@RequestBody结合使用来接收请求体中的内容,如JSON或XML。

    • 示例: 从请求体中获取JSON对象。

      language-java
      1
      2
      3
      4
      5
      @PostMapping("/users")
      public String addUser(@RequestBody User user) {

      // ...
      }
    • 有时也与@RequestParam结合使用来处理表单数据。

  3. @PutMapping:

    • 用于更新数据,通常与@RequestBody结合使用来接收请求体中的内容。

    • 示例: 从请求体中获取JSON对象来更新数据。

      language-java
      1
      2
      3
      4
      5
      @PutMapping("/users/{id}")
      public String updateUser(@PathVariable Long id, @RequestBody User user) {

      // ...
      }
  4. @DeleteMapping:

    • 用于删除数据,通常与@PathVariable结合使用来指定要删除的资源的ID。

    • 示例: 删除指定ID的用户。

      language-java
      1
      2
      3
      4
      5
      @DeleteMapping("/users/{id}")
      public String deleteUser(@PathVariable Long id) {

      // ...
      }
  5. @PatchMapping:

    • 用于部分更新资源,与@RequestBody结合使用来接收部分数据。

    • 示例: 部分更新用户信息。

      language-java
      1
      2
      3
      4
      5
      @PatchMapping("/users/{id}")
      public String patchUser(@PathVariable Long id, @RequestBody Map<String, Object> updates) {

      // ...
      }
  6. @RequestMapping:

    • 这是一个通用注解,可以用来处理任何HTTP方法,其具体方法由method属性指定。
    • 数据获取方式取决于具体的HTTP方法。

每个注解的使用依赖于HTTP请求的语义,以及你如何打算处理请求数据。总体而言,@RequestParam@PathVariable用于从URL获取数据,而@RequestBody用于处理复杂的请求体内容。

SpringBoot注解@GetMapping处理Get请求

SpringBoot注解@GetMapping处理Get请求

一、如何从URL中获取参数

当你想从URL中获取名为courseId的参数时,可以使用@GetMapping("/course/{courseId}")@GetMapping("/course")两种办法,主要体现在URL模式和如何获取参数上。

  1. 使用@GetMapping("/course/{courseId}"):

    • 这种方式表示你正在定义一个REST风格的API,其中{courseId}是URL的一部分,通常被称为路径变量(path variable)。

    • 你可以通过在方法的参数中加上@PathVariable注解来获取这个路径变量。例如:

      language-java
      1
      2
      3
      4
      5
      @GetMapping("/course/{courseId}")
      public String getCourse(@PathVariable String courseId) {

      // 使用courseId
      }
    • 这种方式适合于当courseId是必需的,并且每个课程的URL都是唯一的情况。

  2. 使用@GetMapping("/course"):

    • 这种方式下,URL不直接包含courseId。相反,courseId可以作为请求参数(query parameter)来传递。

    • 你可以通过在方法的参数中加上@RequestParam注解来获取这个请求参数。例如:

      language-java
      1
      2
      3
      4
      5
      @GetMapping("/course")
      public String getCourse(@RequestParam String courseId) {

      // 使用courseId
      }
    • 这种方式适合于当你想要让courseId作为一个可选参数或者你希望从一组标准的URL中筛选特定课程的情况。

二、获取多个参数

当URL中有多个参数时,@GetMapping("/course/{courseId}")@GetMapping("/course")的使用方式和它们之间的区别仍然基于路径变量(Path Variable)和请求参数(Request Parameter)的概念。这两种方法可以根据参数的性质和用途灵活组合使用。

  1. 使用路径变量(Path Variables):

    • 当你使用@GetMapping("/course/{courseId}")并且URL中有多个参数时,这些参数通常是URL路径的一部分,并且每个参数都是资源定位的关键部分。

    • 例如,如果你有一个URL像这样:/course/{courseId}/module/{moduleId},你可以这样使用:

      language-java
      1
      2
      3
      4
      5
      @GetMapping("/course/{courseId}/module/{moduleId}")
      public String getModule(@PathVariable String courseId, @PathVariable String moduleId) {

      // 使用courseId和moduleId
      }
    • 在这个例子中,courseIdmoduleId都是路径的一部分,用来定位特定的资源。

  2. 使用请求参数(Request Parameters):

    • 当你使用@GetMapping("/course")并且URL中有多个参数时,这些参数通常作为URL的查询字符串(query string)。

    • 例如,URL可能是这样的:/course?courseId=123&moduleId=456,你可以这样使用:

      language-java
      1
      2
      3
      4
      5
      @GetMapping("/course")
      public String getCourse(@RequestParam String courseId, @RequestParam String moduleId) {

      // 使用courseId和moduleId
      }
    • 在这个例子中,courseIdmoduleId是作为查询字符串的一部分传递的,通常用于过滤、排序或其他非资源定位的操作。

三、用一个类接收多个参数

对于URL中包含多个请求参数(如 /course?courseId=123&moduleId=456),你可以使用一个Java类来接收所有这些参数。这种方法可以使代码更加整洁,尤其是当处理具有多个参数的复杂请求时。

这里是如何使用一个类来接收所有请求参数的示例:

  1. 定义一个类来表示请求参数 :
    首先,你需要定义一个类,其属性对应于URL中的请求参数。例如,对于参数courseIdmoduleId,你可以定义如下类:

    language-java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class CourseQuery {

    private String courseId;
    private String moduleId;

    // 标准的getter和setter方法
    public String getCourseId() {

    return courseId;
    }

    public void setCourseId(String courseId) {

    this.courseId = courseId;
    }

    public String getModuleId() {

    return moduleId;
    }

    public void setModuleId(String moduleId) {

    this.moduleId = moduleId;
    }

    // 可以添加构造函数、toString()等
    }
  2. 在控制器方法中使用这个类 :
    接下来,在你的控制器方法中,你可以将这个类作为参数,并使用@ModelAttribute注解(或者不使用任何注解,因为Spring MVC会自动将请求参数绑定到类的属性上):

    language-java
    1
    2
    3
    4
    5
    @GetMapping("/course")
    public String getCourse(@ModelAttribute CourseQuery query) {

    // 使用query.getCourseId()和query.getModuleId()
    }

    或者,如果你不使用@ModelAttribute注解,Spring MVC默认也会按照相同的方式处理这个参数:

    language-java
    1
    2
    3
    4
    5
    @GetMapping("/course")
    public String getCourse(CourseQuery query) {

    // 使用query.getCourseId()和query.getModuleId()
    }

使用这种方式,你可以轻松地处理包含许多参数的复杂请求,同时保持代码的清晰和可维护性。这也让你能够在需要时对参数进行验证,比如使用Spring的验证框架来确保传入的参数满足特定条件。

四、疑问

Q:定义一个类来表示请求参数时,不需要使用@RequestParam吗?还是说@RequestParam用于简单的类型获取单个参数,比如String?

在定义一个类来表示请求参数时,通常不需要在类的属性上使用@RequestParam注解。当你使用一个类来接收请求参数,Spring MVC的数据绑定机制会自动将请求中的参数名称映射到类的属性上。这是基于类的属性名和请求参数名的匹配来完成的。

使用类接收参数:

  • 当你有多个请求参数,并且想要将它们组织在一个类中时,你可以定义一个类,其属性与请求参数的名称相对应。

  • 在控制器方法中,你可以使用这个类的实例作为参数,而无需为每个属性单独使用@RequestParam。Spring会自动处理参数到类属性的映射。

  • 例如:

    language-java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class CourseQuery {

    private String courseId;
    private String moduleId;

    // getter和setter省略
    }

    @GetMapping("/course")
    public String getCourse(CourseQuery query) {

    // 使用query.getCourseId()和query.getModuleId()
    }

使用@RequestParam:

  • @RequestParam通常用于获取单个请求参数,特别是当这个参数是一个简单类型(如Stringint等)时。

  • 当你只需要从请求中获取一个或少数几个参数,而不是一个复杂对象时,@RequestParam是一个很好的选择。

  • 例如:

    language-java
    1
    2
    3
    4
    5
    @GetMapping("/course")
    public String getCourse(@RequestParam String courseId, @RequestParam String moduleId) {

    // 直接使用courseId和moduleId
    }

总结来说,当处理包含多个参数的复杂请求时,使用一个类来封装这些参数可以使代码更加清晰和易于维护。而@RequestParam适用于处理更简单或直接的参数获取需求。在实际的应用中,你可以根据具体情况选择最适合的方法。

Q:假如我已经有一个在/course上的controller了,如下
@PostMapping("/course") public CourseBaseInfoDto createCourseBase(@RequestBody AddCourseDto dto)
我还能继续写
@GetMapping("/course") public String getCourse(@RequestParam String courseId, @RequestParam String moduleId)
吗?

是的,你完全可以在同一个控制器(Controller)中同时定义一个@PostMapping和一个@GetMapping方法,即使它们都映射到了相同的路径(如/course)。这是因为HTTP请求的方法类型(GET、POST等)作为映射的一部分,允许区分不同的操作。在你的例子中,一个方法用于处理POST请求,另一个用于处理GET请求。

  1. @PostMapping("/course") 用于创建课程(通常是添加新资源),接收一个请求体(RequestBody)。

  2. @GetMapping("/course") 用于获取课程信息(通常是读取资源),接收URL中的请求参数(RequestParam)。

你的控制器可能看起来像这样:

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping("/path-to-controller")
public class CourseController {


@PostMapping("/course")
public CourseBaseInfoDto createCourseBase(@RequestBody AddCourseDto dto) {

// 创建课程的逻辑
}

@GetMapping("/course")
public String getCourse(@RequestParam String courseId, @RequestParam String moduleId) {

// 获取课程信息的逻辑
}
}

在这种配置中,当一个POST请求发送到/course时,createCourseBase方法将被调用;而当一个GET请求发送到/course时,带有请求参数的getCourse方法将被调用。

这种方法是RESTful API设计中的常见实践,它允许你在同一路径上对资源进行不同类型的操作,同时保持了代码的清晰和逻辑的分离。

JavaWeb MyBatisPlus添加一个能够批量插入的Mapper通用方法

JavaWeb MyBatisPlus添加一个能够批量插入的Mapper通用方法

众所周知,mybatisplus提供的BaseMapper里只有单条插入的方法,没有批量插入的方法,
而在Service层的批量插入并不是真的批量插入,实际上是遍历insert,但也不是一次insert就一次IO,而是到一定数量才会去IO一次,性能不是很差,但也不够好。

怎么才能实现真正的批量插入呢?

这里是mybatisplus官方的演示仓库,可以先去了解一下。

一、注册自定义通用方法流程

  1. 把自定义方法写到BaseMapper,因为没法改BaseMapper,所以继承一下它
language-java
1
2
3
4
5
public interface MyBaseMapper<T> extends BaseMapper<T> {


int batchInsert(@Param("list") List<T> entityList);
}

MyBaseMapper扩展了原有的BaseMapper,所以你之后的Mapper层都继承自MyBaseMapper而不是BaseMapper即可。

  1. 把通用方法注册到mybatisplus
language-java
1
2
3
4
5
6
7
8
9
10
11
@Component
public class MySqlInjector extends DefaultSqlInjector {

@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {

List<AbstractMethod> defaultMethodList = super.getMethodList(mapperClass, tableInfo);
defaultMethodList.add(new BatchInsert("batchInsert"));
return defaultMethodList;
}
}

关键的一句在于defaultMethodList.add(new BatchInsert("batchInsert"));,意为注册一个新的方法叫batchInsert,具体实现在BatchInsert类。

  1. 实现BatchInsert类
language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class BatchInsert extends AbstractMethod {

public BatchInsert(String name) {

super(name);
}

@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {

KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
String columnScript = getAllInsertSqlColumn(tableInfo.getFieldList());
String valuesScript = SqlScriptUtils.convertForeach(LEFT_BRACKET + getAllInsertSqlProperty("item.", tableInfo.getFieldList()) + RIGHT_BRACKET,
LIST, null, "item", COMMA);
String keyProperty = null;
String keyColumn = null;
// 表包含主键处理逻辑,如果不包含主键当普通字段处理
if (StringUtils.isNotBlank(tableInfo.getKeyProperty())) {

if (tableInfo.getIdType() == IdType.AUTO) {

/* 自增主键 */
keyGenerator = Jdbc3KeyGenerator.INSTANCE;
keyProperty = tableInfo.getKeyProperty();
// 去除转义符
keyColumn = SqlInjectionUtils.removeEscapeCharacter(tableInfo.getKeyColumn());
} else if (null != tableInfo.getKeySequence()) {

keyGenerator = TableInfoHelper.genKeyGenerator(methodName, tableInfo, builderAssistant);
keyProperty = tableInfo.getKeyProperty();
keyColumn = tableInfo.getKeyColumn();
}
}
String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
SqlSource sqlSource = super.createSqlSource(configuration, sql, modelClass);
return this.addInsertMappedStatement(mapperClass, modelClass, methodName, sqlSource, keyGenerator, keyProperty, keyColumn);
}

/**
* 获取 insert 时所有列名组成的sql片段
* @param fieldList 表字段信息列表
* @return sql 脚本片段
*/
private String getAllInsertSqlColumn(List<TableFieldInfo> fieldList) {

return LEFT_BRACKET + fieldList.stream()
.map(TableFieldInfo::getColumn).filter(Objects::nonNull).collect(joining(COMMA + NEWLINE)) + RIGHT_BRACKET;
}

/**
* 获取 insert 时所有属性值组成的sql片段
* @param prefix 前缀
* @param fieldList 表字段信息列表
* @return sql 脚本片段
*/
private String getAllInsertSqlProperty(final String prefix, List<TableFieldInfo> fieldList) {

final String newPrefix = prefix == null ? EMPTY : prefix;
return fieldList.stream()
.map(i -> i.getInsertSqlProperty(newPrefix).replace(",", ""))
.filter(Objects::nonNull)
.collect(joining(COMMA + NEWLINE));
}
}

二、BatchInsert具体实现逻辑解析

以如下简单的表user举例,

列名 描述 类型
id 主键,自增 bigint
user_name 用户名 varchar(64)
user_age 用户年龄 int

对于的entity大抵如下

language-java
1
2
3
4
5
6
7
8
9
10
11
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("user")
public class User{

@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String userName;
private int userAge;
}

那么对于batchInsert,我们希望传入List<User>并希望得到类似如下的mybtaisplus xml sql语句

language-xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<insert id="batchInsert" parameterType="java.util.List">
insert into user(
id,
user_name,
user_age
)values
<foreach collection="list" item="item" separator=",">
(
#{item.id},
#{item.userName},
#{item.userAge}
)
</foreach>
</insert>

但是我们并不自己写这个xml,不然这需要对每一个数据表都要写一个,就像不那么硬的硬代码一样,我们希望有段逻辑,只需要传入entity,就能自己解析其中列名和对应的属性名,生成这段xml实现批量插入的功能。

假设你的UserMapper已经继承自MyBaseMapper,如果调用UserMapper.bacthInsert(List<User> entityList),那么会进入这个函数

language-java
1
2
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo)

其中mapperClass是映射器类,modelClass是模型类,我们并不需要了解,最主要的是tableInfo,这是表信息,它包含了关于数据库表的各种信息,如表名、列名、主键等。这个参数提供了详细的表信息,这对于生成针对特定表的SQL语句是必要的。

然后执行如下

language-java
1
2
3
4
5
6
7
//如果你的表名没有主键,那么你需要指定keyGenerator 为NoKeyGenerator,
//因为重写injectMappedStatement最后需要返回return this.addInsertMappedStatement
//其中就需要KeyGenerator
KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
//SqlMethod.INSERT_ONE就是"INSERT INTO %s %s VALUES %s"
//我们依据表的信息生成列名sql片段和属性名sql片段后填入%s就可以得到近似最后的xml sql
SqlMethod sqlMethod = SqlMethod.INSERT_ONE;

然后执行如下

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//tableInfo.getFieldList()会得到一个包含数据表列信息(不包含主键)的TableFieldInfo类组成的List
String columnScript = getAllInsertSqlColumn(tableInfo.getFieldList());
//这行代码就是在调用这个函数
private String getAllInsertSqlColumn(List<TableFieldInfo> fieldList) {

return LEFT_BRACKET + fieldList.stream()
//从TableFieldInfo中只拿取列名
.map(TableFieldInfo::getColumn)
//过滤null
.filter(Objects::nonNull)
//在元素间以逗号和分行分割
.collect(joining(COMMA + NEWLINE)) + RIGHT_BRACKET;
}
//对于User表,这个函数返回以下String
/*
(user_name,
user_age)
*/

然后执行如下

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//首先调用了getAllInsertSqlProperty
String valuesScript = SqlScriptUtils.convertForeach(
// 这也是个内置函数,可以直接去看看
LEFT_BRACKET + getAllInsertSqlProperty("item.", tableInfo.getFieldList()) + RIGHT_BRACKET,
LIST,
null,
"item",
COMMA
);
//LEFT_BRACKET + getAllInsertSqlProperty("item.", tableInfo.getFieldList()) + RIGHT_BRACKET
//得到
/*
(#{userName},
#{userAge})
*/
//经过convertForeach函数后,得到如下字符串
/*
<foreach collection="list" item="item" separator=",">
(#{userName},
#{userAge})
</foreach>
*/

//getAllInsertSqlProperty函数如下
private String getAllInsertSqlProperty(final String prefix, List<TableFieldInfo> fieldList) {

//这里newPrefix 就是"item."
final String newPrefix = prefix == null ? EMPTY : prefix;
return fieldList.stream()
//i.getInsertSqlProperty("item.")是内置函数,假设i现在遍历到了user_name列
//那么得到的就是"#{userName},"
//然后,被删了
//所以本来每个元素从TableFieldInfo变成了形如"#{userName}"的字符串
.map(i -> i.getInsertSqlProperty(newPrefix).replace(",", ""))
.filter(Objects::nonNull)
//在元素间插入逗号和分行
.collect(joining(COMMA + NEWLINE));
}
//对于User表,这个函数返回以下String
/*
#{userName},
#{userAge}
*/

然后执行如下

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
		//定义主键属性名
String keyProperty = null;
//定义主键列名
String keyColumn = null;
// 表包含主键处理逻辑,如果不包含主键当普通字段处理
if (StringUtils.isNotBlank(tableInfo.getKeyProperty())) {

if (tableInfo.getIdType() == IdType.AUTO) {

/* 自增主键 */
keyGenerator = Jdbc3KeyGenerator.INSTANCE;
keyProperty = tableInfo.getKeyProperty();
// 去除转义符
keyColumn = SqlInjectionUtils.removeEscapeCharacter(tableInfo.getKeyColumn());
} else if (null != tableInfo.getKeySequence()) {

keyGenerator = TableInfoHelper.genKeyGenerator(methodName, tableInfo, builderAssistant);
keyProperty = tableInfo.getKeyProperty();
keyColumn = tableInfo.getKeyColumn();
}
}
//这段代码没什么好说的,就是根据不同情况,得到三个变量
//keyGenerator keyProperty keyColumn

然后执行如下

language-java
1
2
3
4
5
6
7
8
9
String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
//就是把表名user,列名片段,属性名片段,填入%s中,得到如下
/*
INSERT INTO user (user_name,
user_age) VALUES <foreach collection="list" item="item" separator=",">
(#{userName},
#{userAge})
</foreach>
*/

然后执行如下

language-java
1
2
3
4
//这两句没有什么可说的,是重写injectMappedStatement函数的必要的操作
//自定义的内容就在于sql和主键
SqlSource sqlSource = super.createSqlSource(configuration, sql, modelClass);
return this.addInsertMappedStatement(mapperClass, modelClass, methodName, sqlSource, keyGenerator, keyProperty, keyColumn);
JavaWeb若依分页插件使用

JavaWeb若依分页插件使用

后端只需要在你本身的contoller调用service取得数据那条命令前后加上2行代码,就可以使用mybatis的分页插件。

language-java
1
2
3
4
5
6
7
8
@GetMapping("move/search")
public AjaxResult moveSearch(StockMoveSearchDTO dto){

startPage();//第一句
List<BztStockMoveHeadExp> list = stockMgntService.moveSearch(dto);//这是自己本身的逻辑代码
TableDataInfo dataTable = getDataTable(list);//第二句
return success(dataTable);
}

整体看起来,startPage像是加载了某些配置,然后基于这些配置getDataTable对你本来的逻辑进行了修改,实现了分页排序效果。

1.原理解析

ctrl左键点击startPage(),两次追溯源码实现,可以看到以下代码

language-java
1
2
3
4
5
6
7
8
9
10
public static void startPage()
{

PageDomain pageDomain = TableSupport.buildPageRequest();
Integer pageNum = pageDomain.getPageNum();
Integer pageSize = pageDomain.getPageSize();
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
Boolean reasonable = pageDomain.getReasonable();
PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
}

PageHelper.startPage(pageNum, pageSize, orderBy)myabtis的分页插件,

  • 第一个参数是当前页码
  • 第二个参数是每一页数量
  • 第三个参数是排序信息,包括排序列和排序顺序

PageHelper会拦截你的sql代码然后加上类似limit ,order by等语句实现所谓的分页排序查询,数据截取是在sql中完成的,很快,所以你不必担心这个插件是不是在数据选完之后再分割导致性能问题。

那么问题来了,pageNum, pageSize, orderBy这三个参数从哪里来?毋庸置疑,肯定是从前端来,但是却不需要你写在dto中,因为若依会从你传到后端的数据中截取这三个参数。ctrl左键点击buildPageRequest(),追溯源码实现,你会看到以下代码。

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static PageDomain getPageDomain()
{

PageDomain pageDomain = new PageDomain();
pageDomain.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1));
pageDomain.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10));
pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN));
pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC));
pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE));
return pageDomain;
}

public static PageDomain buildPageRequest()
{

return getPageDomain();
}

大概意思就是会从你传到后端的数据从截取以下字段

  • pageNum
  • pageSize
  • orderByColumn
  • isAsc
    orderby由后两个字段组成。所以在你前端传到后端的数据中,添加这4个字段和值即可实现分页效果和排序效果。

2. 前端示例

language-html
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
<template>
<el-table :data="tableList" @sort-change="handleSortChange">
<el-table-column prop="formCreateTime"
label="录入日期"
sortable="custom"
:sort-orders="['ascending', 'descending']"
>
<template #default="scope">
{
{ new Date(scope.row.formCreateTime).toLocaleDateString() }}
</template>
</el-table-column>
</template>

<script setup>
const queryParams = reactive({

pageNum: undefined,
pageSize: undefined,
orderByColumn: 'formCreateTime',
isAsc: 'descending',
})
const tableList = ref([])

const handleSortChange=(column)=>{

queryParams.orderByColumn = column.prop;
queryParams.isAsc = column.order;
onSubmitQuiet();
}
const onSubmitQuiet = () => {

queryParams.pageNum = 1;
queryParams.pageSize = 10;
moveSearch(queryParams).then(rp => {

// console.log(rp)
tableList.value = rp.data.rows;
pageTotal.value = rp.data.total;
})
}
</script>

这是一个简单的表格,只包含一列,当点击这一列表头时,触发handleSortChange,更新orderByColumnisAsc 然后重新从后端查询一遍,注意用Get方式。pageNumpageSize 可以通过el-pagination组件更新,这里就不写了。后端返回值包含rows表示具体数据是一个list,total表示数据量。需要注意的时传给后端的orderByColumn所代表的字符串(这里是formCreateTime)应该和后端对应domain对应的变量名一致。

3. 后端示例

language-java
1
2
3
4
5
6
7
8
@GetMapping("move/search")
public AjaxResult moveSearch(StockMoveSearchDTO dto){

startPage();
List<BztStockMoveHeadExp> list = stockMgntService.moveSearch(dto);
TableDataInfo dataTable = getDataTable(list);
return success(dataTable);
}

其中List<BztStockMoveHeadExp> list = stockMgntService.moveSearch(dto);只需按照原来的逻辑写就行,不需要考虑分页或者排序。

JavaWeb 自动编号器Utils,用于各种自增长的编号,单号等

JavaWeb 自动编号器Utils,用于各种自增长的编号,单号等

使用以下技术栈

  • springboot
  • mybatisplus
  • mysql

一、首先设计一张表

表名假设为auto_numbering

名称 数据类型 备注 描述
id bigint 主键
name varchar(64) 编号名称
prefix varchar(8) 前缀
inti_value bigint 初始值
current_value bigint 当前值
length int 编号长度(不包含前缀)

假设有两条数据

id name prefix inti_value current_value length
1 stock_input SI 0 2 8
2 product_code PC 0 5 8

第一条表示入库单编号,编号的规则是,前缀为SI,当前编号数为2,长度为8,那么下一次使用入库单号时,对应的current_value加1变为3,得到的单号应该是字符串"SI00000003" 。第二条表示产品编号,同理,对应的current_value加1变为6,应该得到字符串"PC00000006"

那么如何设计这样一个自动编号工具类呢?这个工具类的方法最好是static,这样可以直接得到自动编号器的返回值,同时又要去让数据表对应的current_value自增。

二、创建对应的Domain类、Mapper接口、编号名称枚举类

domain

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("auto_numbering")
public class AutoNumbering{


private static final long serialVersionUID = 1L;

/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;

/**
* 编号名称
*/
private String name;

/**
* 前缀
*/
private String prefix;

/**
* 初始值
*/
private Long intiValue;

/**
* 当前值
*/
private Long currentValue;

/**
* 编号长度(不包含前缀)
*/
private Integer length;

}

mapper

language-java
1
2
3
4
5
6
7
8
9
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface AutoNumberingMapper extends BaseMapper<AutoNumbering> {


}

enum

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public enum AutoNumberingEnum {


STOCK_INPUT("stock_input", "入库编号"),
PRODUCT_CODE("product_code", "产品编号");

private final String name;
private final String remark;

AutoNumberingEnum(String name, String remark) {

this.name = name;
this.remark = remark;
}

/**
* 获取枚举名称
*
* @return name
*/
public String getName() {

return this.name;
}

/**
* 获取枚举描述
*
* @return remark
*/
public String getRemark() {

return this.remark;
}
}

三、写一个Service接口和实现类

service接口类

language-java
1
2
3
4
5
6
7
8
9
10
public interface AutoNumberingService {


/**
* 获取下一个编号
* @param param 自动编号枚举值
* @return 自动编号字符串
*/
String getNextNo(AutoNumberingEnum param);
}

serviceimpl

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.*;
import java.util.ArrayList;
import java.util.List;

@Slf4j
@Service
public class AutoNumberingServiceImpl implements AutoNumberingService {


@Autowired
private AutoNumberingMapper autoNumberingMapper ;

/**
* 自动编号方法
*
* @param param 自动编号枚举值
* @return 编号
*/
@Transactional
public String getNextNo(AutoNumberingEnum param) {


// 查找当前的采番配置信息
LambdaQueryWrapper<AutoNumbering> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AutoNumbering::getName, param.getName());
List<AutoNumbering> AutoNumberings = AutoNumberingMapper.selectList(wrapper);
// 未获取到配置信息
if (AutoNumberings.isEmpty()) {

return null;
} else {

// 规则获取
AutoNumbering autoNumbering = AutoNumberings.get(0);
// 前缀
String prefix = autoNumbering.getPrefix();
// 长度
Integer length = autoNumbering.getLength();
// 顺番
Long currentNo = autoNumbering.getCurrentValue();
// 更新原数据
currentNo++;
}
autoNumbering.setCurrentValue(currentNo);
AutoNumberingMapper.updateById(autoNumbering);

// 生成编号
String formatNo = StringUtils.leftPad(String.valueOf(currentNo), length, "0");
return String.format("%s%s", prefix, formatNo);
}
}

四、写一个Utils类静态获取编号

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class NoGeneratorUtil {


private static AutoNumberingService autoNumberingService;

@Autowired
public void setAutoNumberingService(AutoNumberingService autoNumberingService) {

NoGeneratorUtil.autoNumberingService = autoNumberingService;
}

/**
* 获取最新的入库单编号
* @return 最新的入库单编号
*/
public static String getNextStockInputNo() {

return autoNumberingService.getNextNo(AutoNumberingEnum.STOCK_INPUT);
}

/**
* 获取最新的产品编号
* @return 最新的c'p 编号
*/
public static String getNextProductNo() {

return autoNumberingService.getNextNo(AutoNumberingEnum.PRODUCT_CODE);
}

这段代码是SpringBoot框架中的一种常见用法,用于将SpringBoot管理的bean注入到静态变量中。那么,autoNumberingService是如何被注入的呢?实际上,当SpringBoot启动时,它会扫描所有的类,并为带有@Component(或者@Service等)注解的类创建实例。在创建NoGeneratorUtil实例的过程中,SpringBoot会调用所有带有@Autowired注解的setter方法,包括setAutoNumberingService方法,从而将AutoNumberingService的实现类注入到autoNumberingService静态变量中。

五、使用

之后如下使用即可一行代码获取最新编号

language-java
1
2
String string1 = NoGeneratorUtil.getNextStockInputNo();
String string2 = NoGeneratorUtil.getNextProductNo();
JavaWeb 若依RuoYi-Vue3框架将Mybatis切换成MybatisPlus

JavaWeb 若依RuoYi-Vue3框架将Mybatis切换成MybatisPlus

这里是官方的做法

mybatisSqlSessionFactoryBean,而mybatis-plusMybatisSqlSessionFactoryBean,所以一般最好是项目中使用一个最好,当然想要共存也可以,mybatis-plus的版本最好要高。这里只讲如何切换成MyBatisPlus。

一、修改yml

application.yml中将mybatis配置注释并写上新的mybatisplus,如下所示

language-yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
## MyBatis配置
#mybatis:
# # 搜索指定包别名
# typeAliasesPackage: com.ruoyi.**.domain
# # 配置mapper的扫描,找到所有的mapper.xml映射文件
# mapperLocations: classpath*:mapper/**/*Mapper.xml
# # 加载全局的配置文件
# configLocation: classpath:mybatis/mybatis-config.xml
mybatis-plus:
# 配置要扫描的xml文件目录,classpath* 代表所有模块的resources目录 classpath 不加星号代表当前模块下的resources目录
mapper-locations: classpath*:mapper/**/*Mapper.xml
# 实体扫描,*通配符
typeAliasesPackage: com.ruoyi.**.domain

二、maven导入mybatisplus包,如下

language-xml
1
2
3
4
5
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>最新版本</version>
</dependency>

三、注释SqlSessionFactoryBean

找到com.ruoyi.framework.config.MyBatisConfig,注释public SqlSessionFactory sqlSessionFactory(DataSource dataSource)如下

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//    @Bean
// public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
// {

// String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
// String mapperLocations = env.getProperty("mybatis.mapperLocations");
// String configLocation = env.getProperty("mybatis.configLocation");
// typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
// VFS.addImplClass(SpringBootVFS.class);
//
// final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// sessionFactory.setDataSource(dataSource);
// sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
// sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
// sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
// return sessionFactory.getObject();
// }

到这里就完成了切换,快去试试 吧。

资料参考
若依框架集成mybatis换成mybatis-plus记录

JavaWeb el-upload图片上传SpringBoot保存并且前端使用img src直接访问(基于RuoYi-Vue3)

JavaWeb el-upload图片上传SpringBoot保存并且前端使用img src直接访问(基于RuoYi-Vue3)

一、Vue前端

language-html
1
2
3
4
5
6
7
8
9
10
11
12
<el-upload
v-model:file-list="createParams.fileList"
action="http://localhost:8080/DataMgt/uploadPic"
:headers="{ Authorization: 'Bearer ' + getToken() }"
list-type="picture-card"
:on-success="handleSuccess"
:before-upload="handlePicBeforeUpload"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
>
<el-icon><Plus /></el-icon>
</el-upload>

需要注意的是action填写后端api路径,同时header需要填写token不然会被拦截,其余的没有影响可以翻阅文档理解。

二、SpringBoot后端

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
@RequestMapping("/DataMgt")
public class Home extends BaseController {

private static String picDir = "C:\\Users\\mumu\\Desktop\\images\\";

@RequestMapping("/uploadPic")
public AjaxResult uploadPic(MultipartFile file) throws IOException {

String filePath = picDir+ UUID.randomUUID().toString() + file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf('.'));
File saveF = new File(filePath);
logger.info("save pic:"+filePath);
file.transferTo(saveF);
return success(filePath);
}
}

picDir填写的是图片保存到哪个文件夹路径,无论你是windows开发环境还是linux生产环境,这个路径都应该是绝对路径。
现在图片保存到了这个路径,那么我们前端imgsrc又如何才能经由后端直接访问图片呢?

第一,需要通过SpringBoot的@Configuration配置一个映射,如下

language-java
1
2
3
4
5
6
7
8
9
10
@Configuration
public class StaticConfig implements WebMvcConfigurer {

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {

registry.addResourceHandler("/images/**")
.addResourceLocations("file:C:\\Users\\mumu\\Desktop\\images\\");
}
}

意思是将C:\\Users\\mumu\\Desktop\\images\\路径映射到/images/,那么当你访问http://localhost:8080/images/1.png时其实就是在访问C:\\Users\\mumu\\Desktop\\images\\1.png

第二,需要在若依的com.ruoyi.framework.config.SecurityConfigconfigure函数中中配置访问权限,将/images/添加其中,对所有人开放,如下

language-java
1
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**", "/images/**").permitAll()

最后,在前端,像这样就可以经由后端访问图片啦。

language-html
1
<el-image src="http://localhost:8080/images/1.png" style="width: 200px;height: 200px"></el-image>

三、延伸

这是多图片上传组件,但其实每张都会和后端交互一次。

如果这个图片墙和文字搭配在一起成为一个图文动态发布页面,那么创建主副数据表,主表保存idtext表示id和文字,副表保存master_idname,表示主表id,图片名。
当新建动态时,在前端即刻生成当前时间作为图文动态的唯一标识。
当上传图片时,以唯一标识作为master_id并以保存的名称作为name插入副表。
当动态提交时,以唯一标识作为id并以文本内容作为text插入主表。
当取消时,以唯一标识为查询条件删除主副表相关数据和图片资源。

JavaWeb Springboot后端接收前端Date变量

JavaWeb Springboot后端接收前端Date变量

如果前端传输的日期符合ISO 8601格式,那么后端用@PostMapping + @RequestBody那么springboot就会自动解析。

如果传输的日期格式不符合ISO 8601格式或者传输用的是Get方法,那么在接收传输的实体类中加上@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss"),这里面的pattern对应传来的日期格式。

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ISO 8601是一种国际标准的日期和时间表示方式。这种格式的主要特点包括:

- 时间日期按照年月日时分秒的顺序排列,大时间单位在小时间单位之前。
- 每个时间单位的位数固定,不足时于左补0。
- 提供两种方法来表示时间:其一为只有数字的基础格式;其二为添加分隔符的扩展格式,让人能更容易阅读。

具体来说,ISO 8601的日期和时间表示方式的格式为`YYYY-MM-DDTHH:mm:ss.sssZ`,其中:

- `YYYY`代表四位数年份。
- `MM`代表月份。
- `DD`代表天数。
- `T`作为日期和时间的分隔符。
- `HH`代表小时。
- `mm`代表分钟。
- `ss.sss`代表秒和毫秒。
- `Z`代表的是时区。

例如,

"2023年11月15日06时16分50秒"可以表示为"2023-11-15T06:16:50"。
"2023-11-15"是ISO 8601日期格式的一个子集。在ISO 8601中,日期可以表示为"YYYY-MM-DD"的格式。因此,"2023-11-15"是符合ISO 8601日期格式的。

JavaWeb后端解析前端传输的excel文件(SpringBoot+Vue3)

JavaWeb后端解析前端传输的excel文件(SpringBoot+Vue3)

一、前端

前端拿Vue3+ElementPlus做上传示例

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<template>
<el-button type="primary" @click="updateData" size="small" plain>数据上传</el-button>
<el-dialog :title="upload.title" v-model="upload.open" width="400px" align-center append-to-body>
<el-upload
ref="uploadRef"
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
:action="upload.url"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag
>
<el-icon class="el-icon--upload">
<upload-filled/>
</el-icon>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip text-center">
<span>仅允许导入xls、xlsx格式文件。</span>
</div>
</template>
</el-upload>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitFileForm">确 定</el-button>
<el-button @click="upload.open = false">取 消</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>

const upload = reactive({

// 是否显示弹出层(用户导入)
open: false,
// 弹出层标题(用户导入)
title: "标题",
// 是否禁用上传
isUploading: false,
// 是否更新已经存在的用户数据
updateSupport: 0,
// 设置上传的请求头部
headers: {
Authorization: "Bearer " + getToken()},
// 上传的地址
url: import.meta.env.VITE_APP_BASE_API + "/yourControllerApi"
});

//数据上传
const updateData = () => {

upload.title = "数据上传";
upload.open = true;
}

//文件上传中处理
const handleFileUploadProgress = (event, file, fileList) => {

upload.isUploading = true;
console.log(upload.url)
};

//文件上传成功处理
const handleFileSuccess = (rp, file, fileList) => {

//这里的rp就是后端controller的响应数据
console.log(rp)
upload.open = false;
upload.isUploading = false;
proxy.$refs["uploadRef"].handleRemove(file);
};
</script>

二、后端

  1. 导入依赖
language-xml
1
2
3
4
5
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
  1. controller接收file
language-java
1
2
3
4
5
@PostMapping("/yourControllerApi")
public AjaxResult importData(@RequestBody MultipartFile file){

return stockMgntService.importData(file);
}
  1. service具体实现
language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Service
public class StockMgntServiceImpl implements StockMgntService {

@Override
public AjaxResult importData(MultipartFile file) {

try(InputStream is = file.getInputStream();) {

Workbook workbook = WorkbookFactory.create(is);
int numberOfSheets = workbook.getNumberOfSheets();
if (numberOfSheets != 1){

//要处理多个sheet,循环就可以
System.out.println("只允许有1个Sheet");
return null;
}
//取得第一个sheet
Sheet sheet = workbook.getSheetAt(0);
//获取最后一行的索引,但通常会比预想的要多出几行。
int lastRowNum = sheet.getLastRowNum();
//循环读取每一行
for (int rowNum = 0; rowNum < lastRowNum; rowNum++) {

Row rowData = sheet.getRow(rowNum);
if (rowData != null){

//可以使用rowData.getLastCellNum()获取最后一列的索引,这里只有6行,是假设现在的excel模板是固定的6行
for(int cellNum = 0; cellNum < 6; ++cellNum){

Cell cell = rowData.getCell(cellNum);
if (cell == null){
continue;}
cell.setCellType(CellType.STRING);
if (!cell.getStringCellValue().isEmpty()){

System.out.println(cell.getStringCellValue());
}
/*
利用这个方法可以更好的将所有数据以String获取到
Cell cell = productCodeRow.getCell(cellNum , Row.MissingCellPolicy.RETURN_BLANK_AS_NULL);
String cellValue = dataFormatter.formatCellValue(cell );
*/
}
}
}
}catch (Exception e) {

System.out.println(e.getMessage());
throw new RuntimeException();
}
}
}

三、结语

这样的方式只能处理简单的excel,对于结构更复杂的excel,需要其他工具utils结合。
以及可以结合SpringBoot有更好的poi使用方法。

JavaWeb后端将excel文件传输到前端浏览器下载(SpringBoot+Vue3)

JavaWeb后端将excel文件传输到前端浏览器下载(SpringBoot+Vue3)

一、后端

  1. 导入依赖
language-xml
1
2
3
4
5
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
  1. controller层
    其中@RequestParam String moveNo是前端传入的参数,而HttpServletResponse response不是,前者可以没有,后者一定要有。
language-java
1
2
3
4
5
6
7
@GetMapping("/yourControllerApi")
public AjaxResult exportData(@RequestParam String moveNo, HttpServletResponse response){

System.out.println(moveNo);
System.out.println(response);
return stockMgntService.exportData(moveNo, response);
}
  1. service层
language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Service
public class StockMgntServiceImpl implements StockMgntService {

@Override
public AjaxResult exportData(String moveNo, HttpServletResponse response) {

//这里是从后台resources文件夹下读取一个excel模板
ClassPathResource resource = new ClassPathResource("template/模板.xlsx");
try(InputStream fis = resource.getInputStream();
Workbook workbook = WorkbookFactory.create(fis);
ServletOutputStream os = response.getOutputStream()
){

//这块写入你的数据
//往第一个sheet的第一行的第2列和第5列写入数据
Sheet sheet = workbook.getSheetAt(0);
sheet.getRow(0).getCell(1, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellValue("test");
sheet.getRow(0).getCell(5, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellValue("test");
/*......你的操作......*/
//这块开始配置传输
response.setCharacterEncoding("UTF-8");
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-disposition", "attachment;filename=template.xlsx");
response.flushBuffer();
workbook.write(os);
os.flush();
os.close();
}catch (IOException e) {

throw new RuntimeException(e);
}
return null;
}
}

二、前端

axios向对应api发送请求并携带参数,然后使用Blob来接收后端的OutputStream输出流并保存下载保存到本地浏览器。

language-html
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
<template>
<el-button type="primary" @click="downloadData" size="small" plain>模板下载</el-button>
</template>

<script setup>
const downloadData = () => {

console.log(queryParams.moveNo)
axios({

url: '/yourControllerApi',
method: 'get',
params: {
moveNo:'0000212132142'},
responseType: "blob"
}).then(rp=>{

console.log(rp)
const blob = new Blob([rp], {
type: 'application/vnd.ms-excel'});
let link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.setAttribute('download', '模板下载.xlsx');
link.click();
link = null;
});
}
</script>