首页 > 其他分享 >[MyBatis-Plus]核心功能详解

[MyBatis-Plus]核心功能详解

时间:2024-10-17 09:47:39浏览次数:10  
标签:用户 private public 详解 Plus User MyBatis id user

条件构造器

MyBatisPlus支持各种复杂的where条件, 可以满足日常开发的所有需求

Wrapper 条件构造器专门用于 构造复杂的查询条件

  1. BaseMapper提供很多增删改查的方法, 其中的Wrapper就是用于构建复杂查询条件的

Wrapper的继承体系

  1. Wrapper是最顶级的父类
  2. AbstractWrapper继承了Wrapper, 定义了很多用于复杂条件查询的方法, 非常重要

  1. QueryWrapper和UpdateWrapper则是进一步扩展了AbstractWrapper的功能
  2. QueryWrapper扩展了查询语句的select 部分, 可以灵活的指定返回的字段, 默认是查询全部字段

  1. UpdateWrapper扩展了更新语句的set 部份, 可以指定更新的内容

  1. AbstractLambdaWrapper和AbstractWrapper的功能基本一样, 只是在构建条件时, 要使用Lambda语法

使用 QueryWrapper 构建复杂的查询条件

@SpringBootTest
class UserMapperTest {

    @Autowired
    private UserMapper userMapper;

    /**
     * 查询操作:
     */
   @Test
    void testQueryWrapper() {
        /**
         * 1.需求: 
         * 查询名字中带o的, 存款大于等于1000元的人的id, username, info, balance字段
         *
         * 2.sql:
         * select id,username,info,balance
         * from user
         * where username like ? ans balance ?
         */

        // 1.构建查询条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .select("id", "username", "info", "balance")
                .like("username", "o")
                .ge("balance", 1000);
        // 2.查询
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

    /**
     * 更新操作
     */
    @Test
    void testUpdateByQueryWrapper() {
        /**
         * 1.需求:
         * 更新用户名为jack的用户的余额为2000
         *
         * 2.sql:
         * update user
         * set balance = 2000
         * where username = "jack"
         */

        // 1.要更新的数据
        User user = new User();
        user.setBalance(2000);
        // 2.更新的条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .eq("username", "jack");
        // 3.执行更新
        userMapper.update(user, wrapper);
    }

}

使用 UpdateWrapper 构建复杂的更新条件

@SpringBootTest
class UserMapperTest {

    @Autowired
    private UserMapper userMapper;

    /**
     * 复杂更新
     */
    @Test
    void testUpdateWrapper() {
        /**
         *1.需求:
         *更新id为1,2,4的用户余额,扣200
         *
         *2.sql:
         * update user
         * set balance = balance - 200
         * where id in (1,2,3)
         */

        // 1.要更新的数据
        List<Long> ids = List.of(1L, 2L, 3L, 4L);

        // 3.更新的条件
        UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
                .setSql("balance = balance - 200")
                .in("id", ids);

        // 3.执行更新
        userMapper.update(null, wrapper);
    }
}

使用LamdbaQueryWrapper, 避免字符串魔法值

@SpringBootTest
class UserMapperTest {

    @Autowired
    private UserMapper userMapper;

    /**
     * 使用 LamdbaQueryWrapper
     */
    @Test
    void testLambdaQueryWrapper() {
        // 构建查询条件
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
                .like(User::getUsername, "o")
                .ge(User::getBalance, 1000);
        // 查询
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }
}
  1. 传入的不再是字符串, 而是字段的get函数
  2. MP内部通过反射就能得到操作字段的字段名
  3. 由于使用了反射机制, 所以代码执行时, 开发工具会爆出警告, 无视就可以了

使用条件构造器的总结

  1. QueryWrapper通常用来构建select delete update 语句的 where 条件部分
  2. UpdateWrapper通常只有在 set语句 比较特殊时使用
  3. 尽量使用LanbdaQueryrapper 和 LambdaUpdateWrapper, 避免硬编码

自定义sql

我们可以利用MyBatisPlus的Wrapper来构建复杂的Where条件, 然后自己定义SQL语句中剩下的部分

使用场景: 更新条件非常繁琐, 更新数据又要动态计算时

  1. 如果自己编写更新sql, 更新条件要写很多代码
  2. 如果使用MP的条件构建器自动生成更新sql, 在service层中就会耦合mapper层的代码
  3. 这时就适合自定义sql, 让MP生成更新条件, 自己定义更新数据

使用步骤

  1. 基于Wrapper构建where条件
@SpringBootTest
class UserMapperTest {

    @Autowired
    private UserMapper userMapper;
    
    /**
     * 自定义sql
     */
   @Test
    void testCustomSqlUpdate() {
        
        List<Long> ids = List.of(1L, 2L, 4L);
        int amount = 200;
        // 构建更新条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);
        // 调用自定义SQL方法
        userMapper.updateBalanceByIds(wrapper, amount);
    }

}
  1. 在mapper方法参数中使用 @Param注解 声明wrapper变量名称, 必须是ew
public interface UserMapper extends BaseMapper<User> {

    void updateBalanceByIds(@Param(Constants.WRAPPER) QueryWrapper<User> wrapper, @Param("amount") int amount);

}
  1. 自定义sql, 并使用Wrapper条件
<?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.itheima.mp.mapper.UserMapper">
 
    <update id="updateBalanceByIds">
        update user set balance = balance - #{amount} ${ew.customSqlSegment}
    </update>

</mapper>

Service接口

MP给我们提供了Service接口, 只要我们继承了该接口, 基础的增删改查代码就不用自己的开发了

IService接口定义了很多增删改查方法, ServiceImpl实现类实现了IService接口中的所有方法

使用步骤

  1. 自定义Service接口继承IService接口
public interface IUserService extends IService<User> {
}
  1. 自定义实现类, 实现自定义接口并继承ServiceImpl类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}
  1. 使用MP提供的增删改查方法
@SpringBootTest
class IUserServiceTest {

    @Autowired
    private IUserService uerService;

    /**
     * 测试新增方法
     */
    @Test
    void testSaveUser() {
        User user = new User();
        user.setUsername("liuliu");
        user.setPassword("123");
        user.setPhone("18688990011");
        user.setBalance(200);
        user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        uerService.save(user);
    }

    /**
     * 测试查询方法
     */
   @Test
    void testQuery() {
        List<Long> ids = List.of(1L, 2L, 4L);
        List<User> users = uerService.listByIds(ids);
        users.forEach(System.out::println);
    }
}

案例1

基于Restful风格实现下面的接口

  1. 准备工作
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    ... ...

    <dependencies>
        ... ...
        // 引入需要的依赖 
        <!--swagger-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
            <version>4.1.0</version>
        </dependency>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    ... ...
</project>
# 配置swagger
knife4j:
  enable: true
  openapi:
    title: 用户管理接口文档
    description: "用户管理接口文档"
    email: [email protected]
    concat: 虎哥
    url: https://www.itcast.cn
    version: v1.0.0
    group:
      default:
        group-name: default
        api-rule: package
        api-rule-resources:
          - com.itheima.mp.controller
@Data
@ApiModel(description = "用户表单实体")
public class UserFormDTO {

    @ApiModelProperty("id")
    private Long id;

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("密码")
    private String password;

    @ApiModelProperty("注册手机号")
    private String phone;

    @ApiModelProperty("详细信息,JSON风格")
    private String info;

    @ApiModelProperty("账户余额")
    private Integer balance;
}
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {

    @ApiModelProperty("用户id")
    private Long id;

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("详细信息")
    private String info;

    @ApiModelProperty("使用状态(1正常 2冻结)")
    private UserStatus status;

    @ApiModelProperty("账户余额")
    private Integer balance;

    @ApiModelProperty("用户的收货地址")
    private List<AddressVO> addresses;

}

  1. 实现简单业务接口
@Api(tags = "用户管理接口")
@RequestMapping("/users")
@RestController
@RequiredArgsConstructor
public class UserController {

    private final IUserService userService;

    @ApiOperation("新增用户接口")
    @PostMapping
    public void saveUser(@RequestBody UserFormDTO userDTO) {
        // 1,把DTO拷贝到PO
        // 使用 cn.hutool.core.bean 包下的 BeanUtil 工具类
        User user = BeanUtil.copyProperties(userDTO, User.class);
        // 2,新增
        userService.save(user);
    }

   @ApiOperation("根据id查询用户接口")
   @GetMapping("{id}")
   public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id) {
       //1,查询用户PO
       User user = userService.getById(id);
       //2,把用户PO拷贝到VO
       return BeanUtil.copyProperties(user, UserVO.class);
   }

   @ApiOperation("根据id批量查询用户接口")
   @GetMapping
   public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids) {
       //1,查询用户PO
       List<User> users = userService.listByIds(ids);
       //2,把PO拷贝到VO
       return BeanUtil.copyToList(users, UserVO.class);
   }

    @ApiOperation("删除用户接口")
    @DeleteMapping("{id}")
    public void deleteUserById(@ApiParam("用户id") @PathVariable("id") Long id) {
        userService.removeById(id);
    }
}
  • 以前注入 Service 是使用@Autowired 注解, 这种方式不是Spring推荐的

  • Spring推荐使用构造函数注解资源, 但是这种代码太臃肿了

  • 使用lombok注解, 来自动生成构造函数, 简化资源的注入

  1. 实现复杂业务接口
@Api(tags = "用户管理接口")
@RequestMapping("/users")
@RestController
@RequiredArgsConstructor
public class UserController {

    private final IUserService userService;

    @ApiOperation("扣减用户余额接口")
    @PutMapping("/{id}/deduction/{money}")
    public void deductBalance(
            @ApiParam("用户id") @PathVariable("id") long id,
            @ApiParam("扣减的金额") @PathVariable("money") Integer money) {
        userService.deductBalance(id, money);
    }

}
public interface IUserService extends IService<User> {
   
    void deductBalance(long id, Integer money);
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    @Transactional
    public void deductBalance(long id, Integer money) {
        //1,查询用户
        User user = getById(id);
        //2,校验用户状态
        if (user == null || user.getStatus() == UserStatus.FROZEN) {
            throw new RuntimeException("用户状态异常!");
        }
        //3.检查余额是否充足
        if (user.getBalance() < money) {
            throw new RuntimeException("用户余额不足!");
        }
        //4,扣减余额
       baseMapper.deductBalance(id, money);
        
    }
}
public interface UserMapper extends BaseMapper<User> {
    /**
     * 根据id扣减余额
     *
     * @param id
     * @param money
     */
    @Update("update user set balance = balance- #{money} where id = #{id}")
    void deductBalance(long id, Integer money);
}

1. 对于简单的需求, 使用MP提供的增删改查方法就可以, 非常简单

  1. 对于复杂的业务需求, 我们还是需要自定义Service和自定义Mapper进行代码实现

接口测试

案例2

使用IService的lambdaQuery方法 完成复杂查询

@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery extends PageQuery {
    @ApiModelProperty("用户名关键字")
    private String name;
    @ApiModelProperty("用户状态:1-正常,2-冻结")
    private Integer status;
    @ApiModelProperty("余额最小值")
    private Integer minBalance;
    @ApiModelProperty("余额最大值")
    private Integer maxBalance;
}
@Api(tags = "用户管理接口")
@RequestMapping("/users")
@RestController
@RequiredArgsConstructor
public class UserController {

    private final IUserService userService;

    @ApiOperation("根据复杂条件查询用户接口")
    @GetMapping("/list")
    public List<UserVO> queryUser(UserQuery query) {
        //1,查询用户PO
        List<User> users = userService.query(query.getName(), query.getStatus(), query.getMinBalance(), query.getMaxBalance());
        //2,把PO拷贝到VO
        return BeanUtil.copyToList(users,UserVO.class);
    }
}
public interface IUserService extends IService<User> {

    List<User> query(String name, Integer status, Integer minBalance, Integer maxBalance);

}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    
    @Override
    public List<User> query(String name, Integer status, Integer minBalance, Integer maxBalance) {
        List<User> userList = lambdaQuery()
                .like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
                .ge(minBalance != null, User::getBalance, minBalance)
                .le(maxBalance != null, User::getBalance, maxBalance)
                .list();
        return userList;
    }
    
}

使用IService的lambdaUpdate方法完成复杂更新

@Api(tags = "用户管理接口")
@RequestMapping("/users")
@RestController
@RequiredArgsConstructor
public class UserController {

    private final IUserService userService;

    @ApiOperation("扣减用户余额接口")
    @PutMapping("/{id}/deduction/{money}")
    public void deductBalance(
            @ApiParam("用户id") @PathVariable("id") long id,
            @ApiParam("扣减的金额") @PathVariable("money") Integer money) {
            userService.deductBalance(id, money);
    }
}
public interface IUserService extends IService<User> {

    void deductBalance(long id, Integer money);
    
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    @Transactional
    public void deductBalance(long id, Integer money) {
        //1,查询用户
        User user = getById(id);
        //2,校验用户状态
        if (user == null || user.getStatus() == UserStatus.FROZEN) {
            throw new RuntimeException("用户状态异常!");
        }
        //3.检查余额是否充足
        if (user.getBalance() < money) {
            throw new RuntimeException("用户余额不足!");
        }
        //4,扣减余额
        int remainBalance = user.getBalance() - money;
        lambdaUpdate()
                .set(User::getBalance, remainBalance)
                .set(remainBalance == 0, User::getStatus, 2)
                .eq(User::getId, id)
                .eq(User::getBalance, user.getBalance()) // 乐观锁
                .update();

    }
}
  1. 并发安全风险: 如果同时有多个线程来做查询, 有可能查到同一个用户, 同一个用户两个线程各自计算余额, 计算的结果是一样的, 就有可能应该扣费2次, 由于并发执行, 最终只扣费1次
  2. 为了避免并发风险, 我们这里加一个乐观锁, 只有用户余额等于刚才查到的余额, 才执行sql操作, 否则说明用户余额被修改过了, sql就不能执行了
  3. 添加一个事务注解, 开启事务, 保证sql执行失败后数据的一致性

案例3

需求: 批量插入10万条用户数据, 越快越好

方案1: 普通for循环插入

@SpringBootTest
class IUserServiceTest {

    @Autowired
    private IUserService uerService;

   /**
     * 创建数据
     */
    private User buildUser(int i) {
        User user = new User();
        user.setUsername("user_" + i);
        user.setPassword("123");
        user.setPhone("" + (15553266209L + i));
        user.setBalance(2000);
        user.setInfo("{\"age\":24,\"intro\":\"英语老师\",\"gender\":\"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        return user;
    }
    
    /**
     * 测试新增10万数据--普通for循环
     */
    @Test
    void testSaveOneByOne() {
        long b = System.currentTimeMillis();
        for (int i = 1; i < 100000; i++) {
            uerService.save(buildUser(i));
        }
        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - b));
    }
}
  1. 每次请求只提交一条数据, 数据越多网络请求越多
  2. 每条sql逐条执行, 执行性能也会较差
  3. 耗时

方案2: ISevice的批量提交

@SpringBootTest
class IUserServiceTest {

    @Autowired
    private IUserService uerService;

    /**
     * 创建数据
     */
    private User buildUser(int i) {
        User user = new User();
        user.setUsername("user_" + i);
        user.setPassword("123");
        user.setPhone("" + (15553266209L + i));
        user.setBalance(2000);
        user.setInfo("{\"age\":24,\"intro\":\"英语老师\",\"gender\":\"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        return user;
    }

    /**
     * 测试新增10万数据--预编译sql
     */
    @Test
    void testSaveBatch() {
        //思路: 每次插入1000条数据, 插入100次

        //1准备容量为1000的集合
        List<User> list = new ArrayList<>(1000);
        long b = System.currentTimeMillis();

        for (int i = 1; i <= 100000; i++) {
            //2添加数据到集合
            list.add(buildUser(i));
            //3集合数据插入到数据库
            if (i % 1000 == 0) {
                uerService.saveBatch(list);
                //4清空集合, 准备下一批数据
                list.clear();
            }
        }

        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - b));
    }
}
  1. 默认情况下的MP批量提交, 采用预编译sql的模式, 一次提交很多数据, 网络请求数量大幅减少
  2. 批量提交并不是批量新增, 本质还是一条sql插入一条数据, 性能并不是最优
  3. 先清除数据再测试: delete from user where id > 5;
  4. 耗时:

方案3: 配置jdbc参数

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 1234
@SpringBootTest
class IUserServiceTest {

    @Autowired
    private IUserService uerService;

    /**
     * 创建数据
     */
    private User buildUser(int i) {
        User user = new User();
        user.setUsername("user_" + i);
        user.setPassword("123");
        user.setPhone("" + (15553266209L + i));
        user.setBalance(2000);
        user.setInfo("{\"age\":24,\"intro\":\"英语老师\",\"gender\":\"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        return user;
    }

    /**
     * 测试新增10万数据--预编译sql
     */
    @Test
    void testSaveBatch() {
        //思路: 每次插入1000条数据, 插入100次

        //1准备容量为1000的集合
        List<User> list = new ArrayList<>(1000);
        long b = System.currentTimeMillis();

        for (int i = 1; i <= 100000; i++) {
            //2添加数据到集合
            list.add(buildUser(i));
            //3集合数据插入到数据库
            if (i % 1000 == 0) {
                uerService.saveBatch(list);
                //4清空集合, 准备下一批数据
                list.clear();
            }
        }

        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - b));
    }
}
  1. url参数后面手动拼接 &rewriteBatchedStatements=true
  2. 把一条一条的预编译sql, 重写为1条sql, 数据批量插入性能最好

  1. 先清除数据再测试: delete from user where id > 5;
  2. 耗时

标签:用户,private,public,详解,Plus,User,MyBatis,id,user
From: https://blog.csdn.net/CSDN20221005/article/details/142998640

相关文章

  • Linux nohup 命令详解
    文章目录Linux`nohup`命令详解基本语法`nohup`工作原理实用示例示例1:运行一个脚本并保持后台执行示例2:指定输出文件示例3:结合`sleep`命令使用`jobs`和`bg`管理后台进程使用`ps`和`kill`管理进程常见的`nohup`参数结合`nohup`和`cron`注意事项结论......
  • Linux rm命令详解
    文章目录Linux`rm`命令详解基本语法常用参数详解常见用法和示例注意事项实践示例结论参数表格Linuxrm命令详解rm(remove)命令用于删除文件或目录,是Linux系统中最常用的文件管理命令之一。rm命令具有强大的功能,可以删除单个文件、多个文件,甚至递归删除整个目录......
  • ThreeJS入门(123):THREE.Skeleton 知识详解,示例代码
    作者:还是大剑师兰特,曾为美国某知名大学计算机专业研究生,现为国内GIS领域高级前端工程师,CSDN知名博主,深耕openlayers、leaflet、mapbox、cesium,webgl,ThreeJS,canvas,echarts等技术开发,欢迎加微信(gis-dajianshi),一起交流。查看本专栏目录-本文是第123篇入门文章......
  • element-plus框架样式设置不生效
    问题:在element-plus的菜单组件中,二级菜单折叠,然后鼠标悬浮的时候,出现的内容是有内边距,我想去掉,如图:但是在控制台找到了相应的类,需要把padding设置为0。我通过如下代码设置不生效,原因:可能是生成的二级菜单样式里面没有带特定的hash属性而vue代码里面样式里带了scoped生成的样......
  • C++ [NOIP1999 提高组] 邮票面值设计 详解
    C++[NOIP1999提高组]邮票面值设计详解题目背景题目描述输入格式输出格式样例#1样例输入#1样例输出#1完整代码(你们最想要的):[NOIP1999提高组]邮票面值设计题目背景除直接打表外,本题不保证存在正确且时间复杂度可以通过全部数据做法。由于测试数据过水,部......
  • MyBatis基本使用(数据输出)
    导言    上期讲了一部分关于mybatis的基本使用知识点,本期继续整理mybatis基本使用知识点(数据输出)上期内容:MyBatis基本使用(上)_mybatis的配置文件使用**settings标签**设置-CSDN博客输出概述数据输出总体上有两种形式:-增删改操作返回的受影响行数:直接使用int或lo......
  • logstash详解
    logstash详解文章目录Logstash官网核心概念数据传输原理安装Logstash配置文件结构InputPluginsOutputPluginsCodecPluginsFilterPluginsLogstashQueue案例Logstash导入csv数据到ES案例同步数据库数据到ElasticsearchLogstash官网Logstash是免费且开放的服......
  • Sharding-JDBC标准模式详解
    Sharding-JDBC标准模式详解一.为什么要使用标准模式?Sharding-JDBC的标准模式就配置而言比inline模式繁琐多了,那为什么要使用标准模式呢Sharding-JDBC作为ApacheShardingSphere生态中的一款轻量级Java框架,提供了数据分片、读写分离、分布式事务和数据库治理等核心功......
  • 【Golang】Go 语言中的 time 包详解:全面掌握时间处理与应用
    在Go语言中,time包提供了强大的时间处理功能,适用于各种场景:获取当前时间、格式化和解析时间、计算时间间隔、设置定时器、处理超时等。在开发过程中,熟练掌握time包能够帮助我们轻松处理时间相关的操作,尤其是定时任务、超时控制等非常常见的功能。文章目录一、`time.......
  • 强大灵活的文件上传库:FilePond 详解
    文件上传是Web开发中常见的功能,尤其是对于图片、视频、文档等大文件的处理,如何既保证用户体验,又兼顾安全和性能,是每位开发者关心的问题。在这样的背景下,FilePond作为一款灵活强大的文件上传库,逐渐在前端开发者中脱颖而出。FilePond提供了优雅的用户界面和全面的功能,同时兼容多......