Java EE 进阶:MyBatis案例练习

news2025/4/2 5:38:29

表白墙

首先我们先准备一下数据库的数据

创建一个信息表

DROP TABLE IF EXISTS message_info;
 CREATE TABLE `message_info` (
 `id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
 `from` VARCHAR ( 127 ) NOT NULL,
 `to` VARCHAR ( 127 ) NOT NULL,
 `message` VARCHAR ( 256 ) NOT NULL,
 `delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT
 `create_time` DATETIME DEFAULT now(),
 `update_time` DATETIME DEFAULT now() ON UPDATE now(),
 PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;

引⼊MyBatis和MySQL驱动依赖 

<dependency>
 <groupId>org.mybatis.spring.boot</groupId>
 <artifactId>mybatis-spring-boot-starter</artifactId>
 <version>3.0.3</version>
 </dependency>
 <dependency>
 <groupId>com.mysql</groupId>
 <artifactId>mysql-connector-j</artifactId>
 <scope>runtime</scope>
 </dependency>

配置yml文件

spring:
  application:
    name: springboot-demo
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true

先创建实体类

@Data
public class MessageInfo {
    private Integer id;
    private String from;
    private String to;
    private String message;
    private Integer deleteFlag;
    private Date creatTime;
    private Date updateTime;
}

创建MessageMapper

@Mapper
public interface MessageMapper {

    @Select("SELECT `id`, `from`, `to`, `message` FROM message_info WHERE delete_flag = 0")
    List<MessageInfo> findMessage();

    @Insert("insert into message_info (`from`,`to`,message) values (#{from},#{to},#{message})")
    Integer addMessage(MessageInfo messageInfo);
}

创建MessageService

@Service
public class MessageService {



    @Autowired
    private MessageMapper messageMapper;

    private MessageInfo messageInfo;


    public Integer insertMessage(MessageInfo messageInfo) {
       return messageMapper.addMessage(messageInfo);
    }

    public List<MessageInfo> queryMessage() {
        return messageMapper.findMessage();
    }
}

 测试

 

 

 

图书管理系统

准备数据库

DROP DATABASE IF EXISTS book_test;
 CREATE DATABASE book_test DEFAULT CHARACTER SET utf8mb4;-- 
⽤⼾表
 
DROP TABLE IF EXISTS user_info;
 CREATE TABLE user_info (
 `id` INT NOT NULL AUTO_INCREMENT,
 `user_name` VARCHAR ( 128 ) NOT NULL,
 `password` VARCHAR ( 128 ) NOT NULL,
 `delete_flag` TINYINT ( 4 ) NULL DEFAULT 0,
 `create_time` DATETIME DEFAULT now(),
 `update_time` DATETIME DEFAULT now() ON UPDATE now(),
PRIMARY KEY ( `id` ),
 UNIQUE INDEX `user_name_UNIQUE` ( `user_name` ASC )) ENGINE = INNODB DEFAULT 
CHARACTER 
SET = utf8mb4 COMMENT = '⽤⼾表';
DROP TABLE IF EXISTS book_info;
 CREATE TABLE `book_info` (
 `id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
 `book_name` VARCHAR ( 127 ) NOT NULL,
 `author` VARCHAR ( 127 ) NOT NULL,
 `count` INT ( 11 ) NOT NULL,
 `price` DECIMAL (7,2 ) NOT NULL,
 `publish` VARCHAR ( 256 ) NOT NULL,
 `status` TINYINT ( 4 ) DEFAULT 1 COMMENT '0⽆效, 1正常, 2不允许借阅',
 `create_time` DATETIME DEFAULT now(),
 `update_time` DATETIME DEFAULT now() ON UPDATE now(),
 PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;-- 
初始化数据
 
INSERT INTO user_info ( user_name, PASSWORD ) VALUES ( "admin", "admin" );
 INSERT INTO user_info ( user_name, PASSWORD ) VALUES ( "zhangsan", "123456" );
INSERT INTO book_info (id, book_name, author, count, price, publish, status, create_time, update_time) VALUES
(6, '解忧杂货店', '东野圭吾', 20, 45.50, '南海出版公司', 1, '2025-03-22 13:00:00', '2025-03-22 13:00:00'),
(7, '百年孤独', '加西亚·马尔克斯', 13, 88.00, '上海译文出版社', 1, '2025-03-22 13:05:00', '2025-03-22 13:05:00'),
(8, '追风筝的人', '卡勒德·胡赛尼', 25, 39.80, '上海人民出版社', 1, '2025-03-22 13:10:00', '2025-03-22 13:10:00'),
(9, '嫌疑人X的献身', '东野圭吾', 18, 42.00, '人民文学出版社', 1, '2025-03-22 13:15:00', '2025-03-22 13:15:00'),
(10, '围城', '钱钟书', 15, 36.00, '人民文学出版社', 1, '2025-03-22 13:20:00', '2025-03-22 13:20:00'),
(11, '牧羊少年奇幻之旅', '保罗·柯艾略', 12, 29.90, '作家出版社', 1, '2025-03-22 13:25:00', '2025-03-22 13:25:00'),
(12, '红楼梦', '曹雪芹', 30, 59.00, '中华书局', 1, '2025-03-22 13:30:00', '2025-03-22 13:30:00'),
(13, '西游记', '吴承恩', 28, 48.00, '人民文学出版社', 1, '2025-03-22 13:35:00', '2025-03-22 13:35:00'),
(14, '水浒传', '施耐庵', 22, 46.50, '人民文学出版社', 1, '2025-03-22 13:40:00', '2025-03-22 13:40:00'),
(15, '三国演义', '罗贯中', 27, 49.00, '人民文学出版社', 1, '2025-03-22 13:45:00', '2025-03-22 13:45:00'),
(16, '小王子', '圣埃克苏佩里', 17, 25.00, '新星出版社', 1, '2025-03-22 13:50:00', '2025-03-22 13:50:00'),
(17, '白夜行', '东野圭吾', 19, 55.00, '南海出版公司', 1, '2025-03-22 13:55:00', '2025-03-22 13:55:00'),
(18, '人类简史', '尤瓦尔·赫拉利', 14, 89.00, '中信出版社', 1, '2025-03-22 14:00:00', '2025-03-22 14:00:00'),
(19, '未来简史', '尤瓦尔·赫拉利', 10, 92.00, '中信出版社', 1, '2025-03-22 14:05:00', '2025-03-22 14:05:00'),
(20, '我们仨', '杨绛', 8, 28.00, '生活·读书·新知三联书店', 1, '2025-03-22 14:10:00', '2025-03-22 14:10:00'),
(21, '霍乱时期的爱情', '加西亚·马尔克斯', 9, 75.00, '上海译文出版社', 1, '2025-03-22 14:15:00', '2025-03-22 14:15:00'),
(22, '目送', '龙应台', 11, 34.50, '广西师范大学出版社', 1, '2025-03-22 14:20:00', '2025-03-22 14:20:00'),
(23, '文化苦旅', '余秋雨', 16, 32.00, '中华书局', 1, '2025-03-22 14:25:00', '2025-03-22 14:25:00'),
(24, '长安的荔枝', '马伯庸', 21, 40.00, '湖南文艺出版社', 1, '2025-03-22 14:30:00', '2025-03-22 14:30:00'),
(25, '解密', '麦家', 13, 37.50, '作家出版社', 1, '2025-03-22 14:35:00', '2025-03-22 14:35:00');

引⼊MyBatis和MySQL驱动依赖

<dependency>
 <groupId>org.mybatis.spring.boot</groupId>
 <artifactId>mybatis-spring-boot-starter</artifactId>
 <version>3.0.3</version>
 </dependency>
 <dependency>
 <groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
 <scope>runtime</scope>
 </dependency>

 配置yml文件

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/book_test?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/**.xml
logging:
  file:
    name: spring-book.log

UserInfo

@Data
public class UserInfo {
    private Integer id;
    private String userName;
    private String password;
    private Integer deleteFlag;
    private Data createTime;
    private Data updateTime;

}

BookInfo

@Data
public class BookInfo {
    private Integer Id;
    private String bookName;
    private String author;
    private Integer count;
    private Integer price;
    private String publish;
    private Integer status;
    private String statusCN;
    private Date createTime;
    private Date updateTime;
}

PageResult

@AllArgsConstructor
@NoArgsConstructor
@Data
public class PageResult<T> {
    private List<T> records;
    private int total;
    private PageRequest pageRequest;



    public PageResult(int total,List<T> records,PageRequest pageRequest) {
        this.records = records;
        this.total = total;
    }
}

PageRequest

@Data
public class PageRequest {
    private Integer currentPage=1;
    private Integer pageSize=10;
    private Integer offset;

    public Integer getOffset() {
        return (currentPage-1)*pageSize;
    }
}

Result

@Data
public class Result<T>  {
    private ResultCodeEnum code;
    private String errMes;
    private T data;


    public static <T> Result success(T data){
        Result result=new Result();
        result.setCode(ResultCodeEnum.SUCCESS);
        result.setErrMes("");
        result.setData(data);
        return result;
    }

    public static <T> Result fail(String errMes){
        Result result=new Result();
        result.setCode(ResultCodeEnum.UNLOGIN);
        result.setErrMes(errMes);
        result.setData(null);
        return result;
    }

    public static <T> Result fail(String errMes,T data){
        Result result=new Result();
        result.setCode(ResultCodeEnum.FAIL);
        result.setErrMes(errMes);
        result.setData(data);
        return result;
    }

    public static <T> Result unLogin(){
        Result result=new Result();
        result.setCode(ResultCodeEnum.UNLOGIN);
        result.setErrMes("登录失败");
        return result;
    }
}

UserController

@RequestMapping("/user")
@RestController
public class userController {

    @Autowired
    private UserService userService;

    @RequestMapping("/login")
    public boolean login(String name, String password, HttpSession session){

        if(!StringUtils.hasLength(name) || !StringUtils.hasLength(password)){
            return false;
        }
        UserInfo userInfo=userService.queryPasswordByUsername(name);
        if (userInfo==null){
            return false;
        }
        if(password.equals(userInfo.getPassword())){
            session.setAttribute(Constants.SESSION_USER_KEY,userInfo);
            return true;
        }

        return false;
    }
}

 

BookController

@RestController
@RequestMapping("/book")
public class bookController {

    private final BookService bookService;

    public bookController(BookService bookService) {
        this.bookService = bookService;
    }

    @RequestMapping("/getListByPage")
    public Result<PageResult<BookInfo>> getList(PageRequest pageRequest, HttpSession session){
        if(session.getAttribute(Constants.SESSION_USER_KEY)==null){
            return Result.unLogin();
        }
        UserInfo userinfo =(UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
        if(userinfo==null && userinfo.getId()<=0){
            return Result.unLogin();
        }
        PageResult<BookInfo> bookInfoPageResult = bookService.pageResult(pageRequest);
        return Result.success(bookInfoPageResult);


    }

    @RequestMapping("/addBook")
    public String addBook(@RequestBody BookInfo bookInfo){
        if (!StringUtils.hasLength(bookInfo.getBookName()) ||
                !StringUtils.hasLength(bookInfo.getAuthor()) ||
                bookInfo.getCount()==null ||
                bookInfo.getPrice()==null ||
                !StringUtils.hasLength(bookInfo.getPublish()) ||
                bookInfo.getStatus()==null
        ){
            return "输入不合法";
        }
        try {
            bookService.addBook(bookInfo);
            return "添加成功";
        } catch (Exception e){
            return "添加失败";
        }
    }

    @RequestMapping("/queryBookId")
    public BookInfo queryBookId(@RequestParam("bookId") Integer bookId){
        return bookService.queryBookId(bookId);
    }

    @RequestMapping ("/updateBook")
    public String updateBook(@RequestBody BookInfo bookInfo){
        bookService.update(bookInfo);
        return "更新成功";
    }

    @RequestMapping ("/deleteBook")
    public String deleteBook(@RequestBody Integer bookId){
        BookInfo bookInfo=new BookInfo();
        bookInfo.setId(bookId);
        bookInfo.setStatus(BookStatus.DELETED.getCode());
        bookService.update(bookInfo);
        return "删除成功";
    }
    @RequestMapping("batchDelete")
    public boolean batchDelete(@RequestBody List<Integer> ids){
        bookService.batchDelete(ids);
        return true;
    }
}

UserMapper

@Mapper
public interface UserMapper {

    @Select("select id, user_name,`password`, delete_flag, create_time,update_time from user_info where delete_flag=0 and user_name=#{name}")
    UserInfo queryPasswordByUsername(String name);
}

BookMapper 

@Mapper
public interface BookMapper {

    @Insert("insert into book_info (book_name,author,count,price,publish,status) " +
            "values (#{bookName},#{author},#{count},#{price},#{publish},#{status})")
    void insertBook(BookInfo bookInfo);

    @Select("select * from book_info where status!=0 order by id asc limit #{offset},#{pageSize}")
    List<BookInfo> selectByPage(PageRequest pageRequest);

    @Select("select count(1) from book_info where status !=0")
    Integer count();

    @Select("select * from book_info where status!=0 and id=#{id}")
    BookInfo selectById(Integer id);


    Integer updateBook(BookInfo bookInfo);

    Integer batchDelete(List<Integer> ids);
}

UserService

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;


    public UserInfo queryPasswordByUsername(String name) {
        return userMapper.queryPasswordByUsername(name);
    }
}

BookService

@Service
public class BookService {
    //bookDao是数据库访问中的一个对象,现在我们对status这个对象进行修改
    @Autowired
    private BookMapper bookMapper;

    public void addBook(BookInfo bookInfo) {
        bookMapper.insertBook(bookInfo);
    }

    public PageResult<BookInfo> pageResult(PageRequest pageRequest){
        int count=bookMapper.count();
        List<BookInfo> bookInfos = bookMapper.selectByPage(pageRequest);
        for (BookInfo bookInfo:bookInfos) {
            bookInfo.setStatusCN(BookStatus.getCodeByName(bookInfo.getStatus()).getName());
        }
        return new PageResult<>(count,bookInfos,pageRequest);

    }


    public BookInfo queryBookId(Integer id) {
        return bookMapper.selectById(id);

    }

    public void update(BookInfo bookInfo) {
        bookMapper.updateBook(bookInfo);
    }

    public  Integer batchDelete(List<Integer> ids) {
        return bookMapper.batchDelete(ids);
    }
}

BookStatus

@Getter
public enum BookStatus {
    DELETED(0,"无效"),
    NORMAL(1,"可借阅"),
    FORBIDDEN(2,"不可借阅");
    private Integer code;
    private String name;

    BookStatus(Integer code, String name) {
        this.code = code;
        this.name = name;
    }

    public static BookStatus getCodeByName(Integer code){
        return switch (code) {
            case 0 -> DELETED;
            case 1 -> NORMAL;
            case 2 -> FORBIDDEN;
            default -> null;
        };
    }

    @Override
    public String toString() {
        return "BookStatus{" +
                "code=" + code +
                ", name='" + name + '\'' +
                '}';
    }
}

ResultCodeEnum 

public enum ResultCodeEnum {
    UNLOGIN(-1),
    SUCCESS(200),
    FAIL(-2);

    private int code;

    ResultCodeEnum(int code){
        this.code=code;
    }
}

constants

public class Constants {
    public static final String SESSION_USER_KEY="session_user_info";
}

 

 希望能对大家有所帮助!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2323440.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

路由选型终极对决:直连/静态/动态三大类型+华为华三思科配置差异,一张表彻底讲透!

路由选型终极对决&#xff1a;直连/静态/动态三大类型华为华三思科配置差异&#xff0c;一张表彻底讲透&#xff01; 一、路由&#xff1a;互联网世界的导航系统二、路由类型深度解析三者的本质区别 三、 解密路由表——网络设备的GPS华为&#xff08;Huawei&#xff09;华三&a…

01 相机标定与相机模型介绍

学完本文,您将了解不同相机模型分类、内参意义,及对应的应用代码模型 标定的意义 建模三维世界点投影到二维图像平面的过程。标定输出的是相机模型。 相机模型 相机模型可以解理解为投影模型 +

SICAR标准 汽车焊装生产线触摸屏操作说明

目录 SIMATIC HMI 是西门子工业自动化解决方案的核心组件&#xff0c;支持实时设备监控与交互&#xff0c;文档中展示了其在焊装生产线中以SICAR标准为基础的具体应用&#xff0c;包括车型切换&#xff08;如 AY2/A26&#xff09;、KMC 夹具配置及能源效率分析&#xff0c;适用…

Selenium Web自动化如何快速又准确的定位元素路径,强调一遍是元素路径

如果文章对你有用&#xff0c;请给个赞&#xff01; 匹配的ChromeDriver和浏览器版本是更好完成自动化的基础&#xff0c;可以从这里去下载驱动程序&#xff1a; 最全ChromeDriver下载含win linux mac 最新版本134.0.6998.165 持续更新..._chromedriver 134-CSDN博客 如果你问…

鸿蒙-全屏播放页面(使用相对布局)---持续更新中

最终实现效果图&#xff1a; 实现步骤 创建FullScreenPlay.ets全品播放页面 并将其修改为启动页面。 全屏播放&#xff0c;屏幕必然横过来&#xff0c;所以要将窗口横过来。 编辑 src/main/ets/entryability/EntryAbility.ets 若写在/EntryAbility.ets中&#xff0c;则所有…

全面讲解python的uiautomation包

在常规的模拟鼠标和键盘操作&#xff0c;我们一般使用pyautogui&#xff0c;uiautomation模块不仅能直接支持这些操作&#xff0c;还能通过控件定位方式直接定位到目标控件的位置&#xff0c;而不需要自己去获取对应坐标位置。uiautomation模块不仅支持任意坐标位置截图&#x…

CentOS 7 源码安装libjsoncpp-1.9.5库

安装依赖工具 sudo yum install cmake make gcc cmake 需要升级至 3.8.0 以上可参考&#xff1a;CentOS安装CMakegcc 需要升级至9.0 以上可参考&#xff1a;CentOS 7升级gcc版本 下载源码 wget https://github.com/open-source-parsers/jsoncpp/archive/refs/tags/1.9.5.…

备赛蓝桥杯之第十六届模拟赛第1期职业院校组第五题:回忆画廊

提示&#xff1a;本篇文章仅仅是作者自己目前在备赛蓝桥杯中&#xff0c;自己学习与刷题的学习笔记&#xff0c;写的不好&#xff0c;欢迎大家批评与建议 由于个别题目代码量与题目量偏大&#xff0c;请大家自己去蓝桥杯官网【连接高校和企业 - 蓝桥云课】去寻找原题&#xff0…

Windows下docker使用教程

docker安装 镜像制作镜像加载容器创建更新镜像导出镜像 Windows10安装dockerdocker image制作docker 镜像加载docker 容器创建更新imageimage 导出为.tar文件 #以Windows10 、11为例 linux和Windows区别在于docker安装的程序是哪个操作系统的&#xff0c;后面的内容其实不变 …

Java项目生成接口文档的方案

文章目录 问题&#xff1a;Java项目生成接口文档的方案方案一&#xff1a;Swagger3.0方案二&#xff1a;Apipost两者对比 问题&#xff1a;Java项目生成接口文档的方案 需求 1、需要生成生成时间&#xff0c;作者名称&#xff0c;项目名称&#xff0c;接口名称&#xff0c;请…

案例实践 | 招商局集团以长安链构建“基于DID的航运贸易数据资产目录链”

概览 案例名称 基于DID的航运贸易数据资产目录链 业主单位 招商局集团 上线时间 2024年10月 用户群体 供数用数企业和个人 用户规模 集团内20企业 案例背景 招商局集团深入落实“促进数据高效流通使用、赋能实体经济”精神&#xff0c;深化集团数字化水平&#xff0c…

2025年移动端开发性能优化实践与趋势分析

启动速度优化 本质&#xff1a;缩短首次可见帧渲染时间。 方法&#xff1a; iOS&#xff1a;利用Core ML本地模型轻量化部署&#xff0c;减少云端等待。Android&#xff1a;强制启用SplashScreen API&#xff0c;通过setKeepOnScreenCondition控制动画时长。冷启动需将耗时操…

Docker Compose介绍

基本概念 Docker-Compose是Docker官方的开源项目&#xff0c;负责实现对docker容器集群的快速编排。 可以这么理解&#xff0c;docker compose是docker提出的一个工具软件&#xff0c;可以管理多个docker容器组成一个应用&#xff0c;只需要编写一个YAML格式的配置文件docker…

头歌实践教学平台--【数据库概论】--SQL

一、表结构与完整性约束的修改(ALTER) 1.修改表名 USE TestDb1; alter table your_table rename TO my_table; 2.添加与删除字段 #语句1&#xff1a;删除表orderDetail中的列orderDate alter table orderDetail drop orderDate; #语句2&#xff1a;添加列unitPrice alter t…

算法基础——模拟

目录 1 多项式输出 2.蛇形方阵 3.字符串的展开 模拟&#xff0c;顾名思义&#xff0c;就是题⽬让你做什么你就做什么&#xff0c;考察的是将思路转化成代码的代码能⼒。这类题⼀般较为简单&#xff0c;属于竞赛⾥⾯的签到题&#xff08;但是&#xff0c;万事⽆绝对&#xff…

【第30节】MFC编程:ListCtrl控件和TreeCtrl控件

目录 引言 一、高级控件ListCtrl 二、高级控件TreeCtrl 三、Shell控件 四、CImageList 五、综合代码示例 引言 在MFC编程里&#xff0c;高级控件能大幅提升应用程序的交互性与功能性。接下来&#xff0c;咱们会详细讲讲ListCtrl和TreeCtrl这两个高级控件。不仅会介绍它们…

JavaScript 手写 call、apply、bind 和 new

1. 手写 call 方法 核心思路&#xff1a;改变函数的 this 指向并立即执行&#xff0c;通过将函数临时挂载到目标对象上调用。 Function.prototype.myCall function (context, ...args) {// 如果 context 为 null 或 undefined&#xff0c;则默认为 windowcontext context |…

计算机网络基础:量子通信技术在网络中的应用前景

计算机网络基础:量子通信技术在网络中的应用前景 一、前言二、量子通信技术基础2.1 量子通信的基本概念2.2 量子通信的主要原理2.2.1 量子密钥分发(QKD)原理2.2.2 量子隐形传态原理三、量子通信技术的特点3.1 绝对安全性3.2 超高通信速率潜力3.3 抗干扰能力强四、量子通信技…

Postman 下载文件指南:如何请求 Excel/PDF 文件?

在 Postman 中进行 Excel/PDF 文件的请求下载和导出&#xff0c;以下是简明的步骤&#xff0c;帮助你轻松完成任务。首先&#xff0c;我们将从新建接口开始&#xff0c;逐步引导你完成整个过程。 Postman 请求下载/导出 excel/pdf 文件教程

Stereolabs ZED Box Mini:机器人与自动化领域的人工智能视觉新选择

在人工智能视觉技术快速发展的今天&#xff0c;其应用场景正在持续拓宽&#xff0c;从智能安防到工业自动化&#xff0c;从机器人技术到智能交通&#xff0c;各领域都在积极探索如何利用这一先进技术。而 Stereolabs 推出的ZED Box Mini&#xff0c;正是一款专为满足这些多样化…