news 2026/10/10 3:32:56

SpringBoot3+MyBatis-Plus快速搭建CRUD工程:版本升级与配置避坑实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot3+MyBatis-Plus快速搭建CRUD工程:版本升级与配置避坑实践

先说明一个现实问题:五分钟只是一个理想化目标,真正卡时间的地方往往不是创建项目,而是版本不对、依赖冲突、扫描路径漏配这三个深坑。我自己是从 SpringBoot2 升级上来的,当时被javax换jakarta的改动坑了一整个下午;后来在新项目里直接上 SpringBoot3 + MyBatis-Plus,顺手把遇到的坑、合理的配置、以及实际跑通的步骤全整理了一遍。这篇文章就是那套方案的完整沉淀,适合刚接触 SpringBoot3 的新手,也适合正在做版本升级的老手——照着走一遍,五分钟搭出一个能跑 CRUD 的基础工程,是完全可以做到的。

1. 搭建前的准备:SpringBoot3 到底改了什么

1.1 版本硬门槛:Java 17 起步,Jakarta 命名空间

SpringBoot3 第一个要命的改动就是把基线提升到了 Java 17。这意味着如果你本机还在用 JDK8 或者 JDK11,IDE 里直接新建 SpringBoot3 项目都会报错,更别说编译运行了。我在升级时遇到的第一问题就是本机同时装了好几个 JDK,而 IDE 默认用的是老版本,结果启动类一跑就提示UnsupportedClassVersionError。解决办法很简单:确认 IDE 里 Project SDK 选的是 17+,File -> Project Structure -> SDK里不能只看 Platform Settings,还要看 Modules 的 Language Level。

第二个改动是javax.*全面迁移到jakarta.*。这是 Java EE 改名 Jakarta EE 之后的必然结果,SpringBoot3 底层完全切换到 Jakarta EE 9 规范。实际影响就是你代码里凡是 importjavax.servlet、javax.annotation这类包的地方,都要改成jakarta.servlet、jakarta.annotation。如果是新项目问题不大,毕竟不会主动引这些老包;但如果是从 SpringBoot2 升上来的老项目,这一步会牵扯到很多第三方库的兼容性,比如老的 Shiro、老的 Redis 客户端、某些内部封装框架,都得逐个验证是否支持 Jakarta。

1.2 MyBatis-Plus 的“3”后缀为什么这么关键

MyBatis-Plus 对 SpringBoot3 的支持是单独发的 starter,artifact 是mybatis-plus-spring-boot3-starter,不是以前那个mybatis-plus-boot-starter。这个区分很重要,因为 3.5.3 之前 MyBatis-Plus 只基于 SpringBoot2 的自动配置机制编写,直接硬搬进 SpringBoot3 会出现两个典型症状:一是启动时提示找不到SqlSessionFactory,二是@MapperScan扫描到了接口但注入时提示没有 Bean。我当时在网上搜到一堆答案,大部分都在说加@MapperScan注解或者检查 Mapper 接口路径,其实根子上就是 starter 版本不对。

我建议新项目直接选 MyBatis-Plus 3.5.5 或更高版本,比如 3.5.7。3.5.3.x 虽然也支持 SpringBoot3,但有些边缘配置还需要手工处理;3.5.5 开始整体稳定了很多,分页插件、乐观锁插件、逻辑删除这些常用功能都没再出过幺蛾子。另外要提醒一句:MyBatis-Plus 的版本号和 MyBatis 的版本号不是强绑定的,但如果你单独引了mybatis或者mybatis-spring,一定要和 MyBatis-Plus 内部依赖的版本保持兼容,最稳妥的做法就是只引 MyBatis-Plus 全家桶,不要自己额外加 MyBatis 依赖。

2. 实操核心:从空目录到能跑通 CRUD 的完整流程

2.1 创建项目与依赖选型

我习惯直接用 Spring Initializr 生成骨架。打开 start.spring.io ,Project 选 Maven,Language 选 Java,Spring Boot 版本选 3.x 的最新稳定版(写这篇文章时 3.3.x 已经是 mainstream,3.4.x 也出了,不过我会选 3.3.x 做示范,因为它经过了大半年生产验证)。

关键项推荐值说明
JDK17 或 2117 是底线,21 是 LTS 但有些内网环境没装
依赖Spring Web提供 REST 接口能力,CRUD 演示必备
依赖Lombok简化实体类样板代码,注意 JDK17 要配新版本 Lombok
依赖MySQL Driver连数据库用的驱动,版本由 SpringBoot 统一管理
手动引入mybatis-plus-spring-boot3-starterInitializr 里没有,需要自己写进 pom.xml

我一般会在 Initializr 里先勾上 Spring Web 和 MySQL Driver,Lombok 也顺手勾上,然后生成项目压缩包下载解压。打开pom.xml加入核心依赖:

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.7</version> </dependency>

这里有个细节:为什么要单独手动引?因为 Spring Initializr 只收录官方和部分知名第三方依赖,MyBatis-Plus 不在初始列表里。版本号建议去 Maven Central 查最新稳定版,不要用RELEASE这种写法,构建才可复现。

2.2 数据源与 MyBatis-Plus 配置

配置这块我踩过一次比较隐蔽的坑:以为 SpringBoot3 用了新的配置机制,就把spring.datasource的配置项也改了,结果数据源初始化直接失败。实际上spring.datasource.url、username、password这些根本没变,变化的是连接池的默认选型。SpringBoot3 默认使用 HikariCP,这个池子性能好、出错少,我不建议新手为了“统一技术栈”特意换成 Druid——除非你确实需要 Druid 的监控面板,否则 Hikari 够用且少一个维护点。

在application.yml里写:

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: your_password mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml type-aliases-package: com.example.demo.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: assign_id logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

逐行解读一下:

driver-class-name必须是com.mysql.cj.jdbc.Driver,如果是旧项目残留的com.mysql.jdbc.Driver,在 MySQL 8+ 驱动下直接报 ClassNotFound。serverTimezone=Asia/Shanghai解决时区差 8 小时的问题,allowPublicKeyRetrieval=true解决 MySQL 8 的 caching_sha2_password 认证报错。map-underscore-to-camel-case默认值其实已经是 true,但我喜欢写出来,让后来接手的人一眼看得懂。log-impl配成StdOutImpl是演示阶段最好的选择,每个 SQL 都会打印在控制台;生产环境务必删掉或改成Slf4jImpl,否则日志噪音太大。

有人说type-aliases-package不配置也行,确实可以,但配了之后,Mapper XML 里写resultType和parameterType就不再需要写全限定类名,能省很多事。我建议项目一创建就配好,别等 XML 多了再补。

2.3 实体类与 Mapper:MyBatis-Plus 的省力核心

建一张最简单的用户表:

CREATE TABLE `user` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `name` VARCHAR(50) DEFAULT NULL, `age` INT DEFAULT NULL, `email` VARCHAR(100) DEFAULT NULL, `deleted` TINYINT NOT NULL DEFAULT 0, `create_time` DATETIME DEFAULT NULL, `update_time` DATETIME DEFAULT NULL, PRIMARY KEY (`id`) );

实体类写法:

@Data @TableName("user") public class User { @TableId(type = IdType.ASSIGN_ID) private Long id; private String name; private Integer age; private String email; @TableLogic private Integer deleted; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; }

几个要解释的设计点:

@TableName("user")不加的话默认会映射到user表,但加上的好处是表名如果带前缀,比如t_user、sys_user,你就不用去全局配置里改映射规则。@TableId(type = IdType.ASSIGN_ID)是 MyBatis-Plus 推荐的雪花算法主键,它不是数据库自增,而是应用层生成分布式 ID,在多库分表场景下不会撞号。如果表是单表单库、没有分库分表需求,用数据库自增配合IdType.AUTO更自然,可以按需选择,但要保证代码和表定义一致。

@TableLogic是逻辑删除注解,加了之后 MyBatis-Plus 的deleteById不会执行物理 DELETE,而是自动改成 UPDATE 把deleted置为 1。所有查询语句也会自动追加deleted = 0条件。前提是 yml 里logic-delete-field配好了,或者注解写在字段上,两者配一个就行。

Mapper 接口极简:

@Mapper public interface UserMapper extends BaseMapper<User> { }

BaseMapper是 MyBatis-Plus 打包好的万能实现,里面有selectById、selectList、insert、updateById、deleteById等十几个常用方法,你几乎不用写一行 SQL。如果条件查询复杂,可以用它内置的QueryWrapper/LambdaQueryWrapper,也可以用自定义 XML,两种都在这个结构里走得通。

这里有个注解选择的细节:可以在启动类上加@MapperScan("com.example.demo.mapper"),也可以在每个 Mapper 接口上单独加@Mapper。我习惯两种都写上:启动类扫描保证多模块项目不会漏,Mapper 上写@Mapper是为了让 IDE 和代码阅读者一眼识别这是 MyBatis 的 Mapper,而不是普通接口。

2.4 Service 与 Controller:标准三层直出

MyBatis-Plus 也提供了 Service 层的封装,继承ServiceImpl后可以少写大量模板代码:

public interface UserService extends IService<User> { } @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { }

这样list()、getById()、save()、updateById()、removeById()这些方法直接就有,不需要自己在 Service 里再包一层 Mapper 调用。有的团队认为 Service 必须自己写业务逻辑,不能完全依赖IService封装,这个观点我部分认同——复杂业务当然要写定制方法,但基础的 CRUD 入口没必要重复造轮子,留出鉴权、参数校验、业务规则校验的扩展点就够了。

Controller 示例:

@RestController @RequestMapping("/user") @RequiredArgsConstructor public class UserController { private final UserService userService; @GetMapping("/{id}") public User getById(@PathVariable Long id) { return userService.getById(id); } @GetMapping("/list") public List<User> list() { return userService.list(); } @PostMapping public User save(@RequestBody User user) { userService.save(user); return user; } @PutMapping public User update(@RequestBody User user) { userService.updateById(user); return user; } @DeleteMapping("/{id}") public boolean delete(@PathVariable Long id) { return userService.removeById(id); } }

@RequiredArgsConstructor来自 Lombok,配合private final做构造器注入。Spring 官方推荐构造器注入是有道理的:字段多了以后能避免@Autowired的循环依赖隐患,也方便写单元测试的时候手工 new 出来传 mock。

到这里,一个最简的 SpringBoot3 + MyBatis-Plus 工程已经能启动了。启动类默认长这样:

@SpringBootApplication @MapperScan("com.example.demo.mapper") public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

我在本地测试时用 Postman 模拟请求,访问GET /user/list会返回空数组(因为表里没数据),先 POST 一条数据,再 GET 一次,数据就出来了。整个链路:Controller -> Service -> Mapper -> MySQL,跑通即代表工程骨架正常。

3. 让工程真正“能上线”:分页、自动填充与逻辑删除

3.1 分页插件:不配置等于白搭

MyBatis-Plus 的分页能力不是内置默认开启的。很多新手在 SpringBoot2 时代可能用过PageHelper,切到 MyBatis-Plus 后发现selectPage方法返回的数据不分页、把全表都查回来了,原因就是没注册分页拦截器。

配置方式如下:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

DbType.MYSQL必须写对。因为分页插件要生成对应数据库的方言 SQL,比如 MySQL 用LIMIT ? OFFSET ?,PostgreSQL 用LIMIT ? OFFSET ?但语法细节不同,Oracle 还要包一层ROWNUM。写错数据库类型,分页 SQL 在特定场景下可能会报错。

分页方法示例:

@GetMapping("/page") public IPage<User> page(@RequestParam(defaultValue = "1") long current, @RequestParam(defaultValue = "10") long size) { return userService.page(new Page<>(current, size)); }

IPage里带了records、total、current、size几个字段,前端做表格分页组件时直接绑数据就行,非常省事。我实际项目中还会把分页结果统一封装成 ApiResponse,把records和total拆出来,这样前端拿数据结构的成本更低。

3.2 字段自动填充:创建时间和更新时间别再手动 set

我见过太多项目里每写一条 insert 就要手动setCreateTime(new Date()),漏一个就出现脏数据。MyBatis-Plus 的MetaObjectHandler就是干这个的:

@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } }

实体类上对应字段要有@TableField(fill = FieldFill.INSERT)或FieldFill.INSERT_UPDATE。这套组合的好处是:插入时自动写两个时间,更新时只更新updateTime,代码里彻底删掉手动 set 时间的逻辑。

这里注意一个容易搞混的点:strictInsertFill的第二个参数是实体字段名,不是数据库列名。如果你的实体字段叫createTime,数据库列叫create_time,这里照样写createTime。MyBatis-Plus 会按照驼峰映射规则转成列名,写错了就填充不进去,但不报错,非常隐蔽。

3.3 逻辑删除:不只删数据,查数据也要过滤

前文提到的@TableLogic已经在实体类里写了,这里再补充一个实际业务里的坑:逻辑删除字段如果参与查询,ID 也要考虑唯一性。比如订单表里逻辑删除了订单,但主键 ID 又复用了物理 ID 的生成策略,那历史数据和新数据就会出现主键冲突。我一般建议逻辑删除表的主键使用雪花 ID,或者业务上接受这种冲突,否则就要引入“唯一业务号”字段。

global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

只要配置了logic-delete-field,且实体中 naming 能对上,MyBatis-Plus 会自动对查询语句加过滤条件。我特意反复提醒:如果你在 XML 里写了自定义 SQL,比如<select id="selectUserList">SELECT * FROM user WHERE name = #{name}</select>,这个 SQL 不会自动追加逻辑删除条件。要么在 XML 里手动写AND deleted = 0,要么使用 MyBatis-Plus 的@Select注解并套用其条件构造器。这是一个很容易被忽略的“半自动”特性。

3.4 代码生成器:值不值得用

MyBatis-Plus 有一个代码生成器,可以一键生成实体、Mapper、Service、Controller。如果你看过官方文档,会发现配置它甚至比手写还累——需要写一大堆自定义模板策略。我的态度是:单表 CRUD 不值得为它搭一套环境,但如果你的项目里有二十张以上的表,并且团队里所有新人都按同一套模板生成代码,它能保证风格统一,这时候收益就起来了。

如果你真要用,注意生成器依赖版本的兼容性,尽可能与我们使用的 MyBatis-Plus 核心版本保持一致,避免生成的代码里引用了错的@TableId包名。我遇到过一次生成出来的实体 import 是com.baomidou.mybatisplus.annotation.IdType,但项目里依赖的版本把这个类移到别的包,编译直接报错。

4. 高频问题与排查思路速查

4.1 启动失败类

报错关键字常见原因解决方向
UnsupportedClassVersionErrorJDK 版本低于 17项目 SDK 和 Language Level 切到 17+
ClassNotFoundException: javax.servlet.*依赖还停留在 javax 时代找出旧包升级或替换为 jakarta 版本
Failed to configure a DataSource没配数据源或配置项拼错检查 spring.datasource 下四个核心配置
Invalid bound statement (not found)Mapper XML 没被扫描或 namespace 不对检查 mapper-locations 路径和 namespace
Error creating bean with name 'sqlSessionFactory'starter 版本不对换成 mybatis-plus-spring-boot3-starter
Cause: java.lang.IllegalArgumentException: jdbcUrl is requiredSpringBoot 数据源属性名写错确认是 url 不是 jdbcUrl,是 username 不是 user

这里单独拎出Invalid bound statement说一下。SpringBoot2 老项目里常见的是 mapper.xml 放在src/main/java目录下但没在 pom.xml 里加<resources>配置,导致编译后 XML 没有复制到 classpath。SpringBoot3 下我建议直接把 XML 放在src/main/resources/mapper/下,这样默认就打包进去了,省掉额外配置。

4.2 运行期逻辑类

现象常见原因解决方向
selectPage不过滤,全表返回没注册分页插件配置 MybatisPlusInterceptor + PaginationInnerInterceptor
insert报主键重复IdType.AUTO和数据表自增不一致统一主键策略
时间字段为空或都是当前时间自动填充注解和 Handler 没配全检查 fill 属性和 MetaObjectHandler 是否被扫描
JSON 返回的 LocalDateTime 格式怪异Jackson 默认序列化不是业务想要的格式配置spring.jackson.date-format或定义全局 ObjectMapper
逻辑删除后 count 计数不对XML 自定义 SQL 没加过滤条件手动补 deleted 条件或用 Wrapper

LocalDateTime序列化问题我多讲一句。SpringBoot 默认用 Jackson 序列化 Java 时间类型,输出是数组格式,比如[2025,1,10,14,30,0],前端拿到完全没法直接显示。最简单的处理是在 yml 里加:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

但实测发现date-format只对java.util.Date生效,对LocalDateTime无效。这里要么为LocalDateTime单独配jackson-datatype-jsr310和格式注解,要么在字段上加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss"),我推荐字段注解方案,因为它是显式且局部可控的,不会影响全局其他类型。后来项目里统一封装了LocalDateTimeSerializer/LocalDateTimeDeserializer并注册到 Jackson,但前期快速搭建阶段,字段注解已经够用。

4.3 我踩过的几个进阶坑

先说 Lombok 和 JDK17 的兼容性。Lombok 老版本比如 1.18.20 在 JDK17 下会直接报 “java.lang.ExceptionInInitializerError”,卡在编译阶段。SpringBoot3 的依赖管理里如果带了 Lombok 版本,基本是给 JDK17 适配过的 1.18.30 以上,一般不会有问题;但老项目升级时如果 pom 里锁了 Lombok 老版本,记得升级。我自己在新工程里显式写:

<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.32</version> <scope>provided</scope> </dependency>

scope=provided很重要,Lombok 是编译期注解处理器,不需要打包进最终 Jar。

再说 Mapper XML 的 namespace。MyBatis-Plus 的@Mapper注解已经帮忙注册了 Mapper 接口,但 XML 文件里的namespace必须和接口全限定名完全一致,否则运行时会报BindingException。这个错误经常在拷代码时发生,接口改名了 XML 忘记同步,排错 20 分钟是常有的事。

还有一个小众但典型的坑:SpringBoot3 + MyBatis-Plus 使用Page对象时,getTotal()有时候返回 0,原因是分页对象没在 Interceptor 里被正确拦截。排查方法很简单——看控制台日志里分页拦截器有没有打印==> Preparing: SELECT COUNT(*) FROM user。如果没有这行,说明项目里同名类冲突了,比如你自己从别的包 import 了一个Page,而不是com.baomidou.mybatisplus.extension.plugins.pagination.Page。IDE 自动 import 非常容易选错,这个问题我在团队里帮人看过不止三四次。

5. 后续还能怎么扩展

工程搭建只是第一步,实际项目里你可能马上要面对认证授权、统一异常处理、参数校验、多数据源这些问题。我个人的扩展顺序建议:先补统一返回体和全局异常,再上参数校验;然后根据需要接 Redis 做缓存,最后才是复杂权限框架。这些不是搭建阶段的事,但提前知道方向,搭数据库表结构时就能少走弯路。

把工具链做个收束:IDEA 装 MyBatisX 插件,它能从数据库表反向生成实体、Mapper 和 XML,效率比代码生成器高不少;再装一个 RestfulTool 或者直接用 Postman 测试接口。这两个辅助工具对新手非常友好,可以大大压缩调试时间。

提示:如果你用的是 IDEA 社区版,需要注意它没有自带 Spring Initializr 集成,可以手动去 start.spring.io 下载工程包再导入,也可以用 Maven 的archetype生成。两种方式效果一样,不用强求 IDE 版本。

最后分享一个我个人操作上的习惯:项目刚创建完,第一件事不是急着写业务代码,而是先启动一次空工程,确认 Boot 本身能起、数据库能连。这个“先冒烟再开发”的做法能帮你把环境问题聚焦在最早期,而不是等写完几十行代码后再在层层堆叠里找根因。我每次开新项目都这么做,团队里新人也沿用这个流程,被环境问题卡住的概率明显低很多。这套 SpringBoot3 + MyBatis-Plus 的架子,我在多个外包项目、公司内部中台项目中都完整跑过,稳定性和后续维护性都可以放心。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 3:32:50

FFmpeg+Nginx+SSM搭建RTSP转HLS网页直播系统实战

简介&#xff1a;这套以SSM架构整合Nginx与FFmpeg的流媒体方案&#xff0c;专门面向需要将RTSP实时流转为HLS并在网页上播放的开发者&#xff0c;尤其适合刚接触视频接入的小白和希望快速预览实时画面的朋友。压缩包共52个文件&#xff0c;约70.26MB&#xff0c;涵盖png界面图、…

作者头像 李华
网站建设 2026/10/10 3:32:47

栈:从数据结构到全栈开发的思维暗号

栈这东西&#xff0c;是我跟同行聊天时几乎绕不开的话题。数据结构课上说“Stack栈”是最基础的容器之一&#xff0c;算法竞赛里单调栈和表达式求值常年出镜&#xff0c;线上服务崩了抓调用栈&#xff08;backtrace&#xff09;定位问题&#xff0c;聊全栈开发时“技术栈”三个…

作者头像 李华
网站建设 2026/10/10 3:32:47

k3s轻量级Kubernetes实战:低配Ubuntu服务器部署微服务全指南

最近被问得最多的问题&#xff0c;不是“Kubernetes 怎么学”&#xff0c;而是“我就一台 2G 内存的 Ubuntu 服务器&#xff0c;能装 K8s 跑微服务吗”。直接上标准 K8s 集群当然费劲&#xff0c;但换用 k3s 这种轻量级 Kubernetes 发行版&#xff0c;完全可以在低配机器上把一…

作者头像 李华
网站建设 2026/10/10 3:32:46

folium地图离线部署:Leaflet资源本地化与CDN替换实战

简介&#xff1a;面向使用 Folium 进行 Web 地图可视化的 Python 开发者&#xff0c;这份本地化静态资源包能有效应对默认远程加载 CDN 缓慢、跨境访问不稳定等痛点&#xff0c;让地图打开速度不再受限于外网环境。资源将 Leaflet、Bootstrap、FontAwesome 等依赖的 JS、CSS、字…

作者头像 李华
网站建设 2026/10/10 3:32:37

Mac无法写入NTFS U盘?四种解决方案与避坑指南

1. 从一次深夜拷贝失败说起&#xff1a;Mac 写不进 U 盘的典型症状如果你用 Mac 时间够长&#xff0c;大概率遇到过这种场景&#xff1a;同事递过来一个 U 盘或者移动硬盘&#xff0c;说里面有几个 G 的设计稿或者视频素材&#xff0c;你插上 Mac&#xff0c;能看见文件&#x…

作者头像 李华
网站建设 2026/10/10 3:31:02

Linux终端效率进阶:从Readline快捷键到tmux复用实战

如果你和我一样&#xff0c;每天有大量时间泡在Linux终端里&#xff0c;那你一定有过这种体验&#xff1a;好不容易理清思路准备敲一条长命令&#xff0c;发现要修改中间一个参数&#xff0c;只能一下一下按方向键挪&#xff1b;或者刚想起昨天用过一条很长的命令&#xff0c;翻…

作者头像 李华