一、MyBatis-Plus 核心架构
1.1 架构层次
text
应用程序
↓
MyBatis-Plus (增强层)
├── 通用 Mapper
├── 条件构造器
├── 代码生成器
├── 分页插件
├── 性能分析插件
└── 全局拦截插件
↓
MyBatis (核心层)
↓
JDBC
↓
数据库
1.2 核心特性
-
无侵入性:只做增强不做改变
-
强大的 CRUD:内置通用 Mapper、Service
-
Lambda 支持:类型安全的查询条件
-
多种主键策略:雪花算法、UUID、自增等
-
插件体系:分页、乐观锁、性能分析等
二、快速入门配置
2.1 依赖配置
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
2.2 基础配置
yaml
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
global-config:
db-config:
id-type: assign_id
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
mapper-locations: classpath*:/mapper/**/*.xml
三、核心注解详解
3.1 实体类注解
java
@Data
@TableName("sys_user") // 指定表名
public class User {
@TableId(type = IdType.ASSIGN_ID) // 主键策略
private Long id;
private String username;
@TableField("nick_name") // 字段映射
private String nickName;
@TableField(exist = false) // 非数据库字段
private String tempData;
@TableField(fill = FieldFill.INSERT) // 自动填充
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableLogic // 逻辑删除
private Integer deleted;
@Version // 乐观锁
private Integer version;
@EnumValue // 枚举值存储
private UserStatus status;
}
3.2 主键策略详解
java
public enum IdType {
AUTO(0), // 数据库自增
NONE(1), // 无状态
INPUT(2), // 手动输入
ASSIGN_ID(3), // 雪花算法(默认)
ASSIGN_UUID(4); // UUID
private final int key;
}
3.3 字段填充处理器
java
@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());
this.strictInsertFill(metaObject, "deleted", Integer.class, 0);
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
四、条件构造器深度解析
4.1 核心 Wrapper 类型
java
// 1. QueryWrapper - 普通条件构造器
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", "张三")
.like("email", "@gmail.com")
.between("age", 18, 30);
// 2. LambdaQueryWrapper - Lambda条件构造器(推荐)
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.eq(User::getUsername, "张三")
.like(User::getEmail, "@gmail.com")
.between(User::getAge, 18, 30);
// 3. UpdateWrapper - 更新条件构造器
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.set("email", "new@email.com")
.eq("username", "张三");
// 4. LambdaUpdateWrapper - Lambda更新构造器
LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.set(User::getEmail, "new@email.com")
.eq(User::getUsername, "张三");
4.2 常用条件方法
java
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 比较条件
wrapper.eq(User::getAge, 25) // age = 25
.ne(User::getAge, 25) // age != 25
.gt(User::getAge, 18) // age > 18
.ge(User::getAge, 18) // age >= 18
.lt(User::getAge, 60) // age < 60
.le(User::getAge, 60); // age <= 60
// 范围条件
wrapper.between(User::getAge, 18, 30) // age BETWEEN 18 AND 30
.notBetween(User::getAge, 18, 30)
.in(User::getAge, Arrays.asList(18, 20, 22)) // age IN (18,20,22)
.notIn(User::getAge, Arrays.asList(18, 20, 22));
// 模糊查询
wrapper.like(User::getUsername, "张") // username LIKE '%张%'
.likeLeft(User::getUsername, "张") // username LIKE '%张'
.likeRight(User::getUsername, "张") // username LIKE '张%'
.notLike(User::getUsername, "张");
// 空值判断
wrapper.isNull(User::getEmail) // email IS NULL
.isNotNull(User::getEmail); // email IS NOT NULL
// 分组排序
wrapper.groupBy(User::getDeptId) // GROUP BY dept_id
.orderByAsc(User::getAge) // ORDER BY age ASC
.orderByDesc(User::getCreateTime); // ORDER BY create_time DESC
// 链式调用
wrapper.eq(User::getStatus, 1)
.and(w -> w.gt(User::getAge, 18).or().lt(User::getAge, 60))
.having("count(*) > 1");
4.3 动态条件构建
java
public List<User> queryUsers(String username, Integer minAge, Integer maxAge, Integer status) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 动态条件
wrapper.eq(StringUtils.isNotBlank(username), User::getUsername, username)
.ge(minAge != null, User::getAge, minAge)
.le(maxAge != null, User::getAge, maxAge)
.eq(status != null, User::getStatus, status);
return userMapper.selectList(wrapper);
}
// 复杂动态条件
public List<User> complexQuery(UserQueryDTO query) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.nested(StringUtils.isNotBlank(query.getKeyword()), w ->
w.like(User::getUsername, query.getKeyword())
.or()
.like(User::getEmail, query.getKeyword()))
.between(query.getStartTime() != null && query.getEndTime() != null,
User::getCreateTime, query.getStartTime(), query.getEndTime())
.in(query.getStatusList() != null && !query.getStatusList().isEmpty(),
User::getStatus, query.getStatusList());
return userMapper.selectList(wrapper);
}
五、CRUD 操作详解
5.1 Mapper 层 CRUD
java
@Mapper
public interface UserMapper extends BaseMapper<User> {
// BaseMapper 提供的核心方法:
// 插入操作
int insert(User entity);
// 删除操作
int deleteById(Serializable id);
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
int delete(@Param(Constants.WRAPPER) Wrapper<User> queryWrapper);
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 更新操作
int updateById(@Param(Constants.ENTITY) User entity);
int update(@Param(Constants.ENTITY) User entity,
@Param(Constants.WRAPPER) Wrapper<User> updateWrapper);
// 查询操作
User selectById(Serializable id);
List<User> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
List<User> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
User selectOne(@Param(Constants.WRAPPER) Wrapper<User> queryWrapper);
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<User> queryWrapper);
List<User> selectList(@Param(Constants.WRAPPER) Wrapper<User> queryWrapper);
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<User> queryWrapper);
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<User> queryWrapper);
// 分页查询
<E extends IPage<User>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<User> queryWrapper);
<E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<User> queryWrapper);
}
5.2 Service 层 CRUD
java
public interface UserService extends IService<User> {
// 自定义业务方法
List<User> selectUsersByCondition(UserQueryDTO query);
Page<User> selectUserPage(Page<User> page, UserQueryDTO query);
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService {
@Override
public List<User> selectUsersByCondition(UserQueryDTO query) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 构建查询条件
return baseMapper.selectList(wrapper);
}
@Override
public Page<User> selectUserPage(Page<User> page, UserQueryDTO query) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 构建查询条件
return baseMapper.selectPage(page, wrapper);
}
// IService 提供的核心方法:
// boolean save(T entity);
// boolean saveBatch(Collection<T> entityList);
// boolean saveOrUpdate(T entity);
// boolean removeById(Serializable id);
// boolean removeByMap(Map<String, Object> columnMap);
// boolean remove(Wrapper<T> queryWrapper);
// boolean updateById(T entity);
// boolean update(T entity, Wrapper<T> updateWrapper);
// T getById(Serializable id);
// List<T> listByIds(Collection<? extends Serializable> idList);
// List<T> listByMap(Map<String, Object> columnMap);
// T getOne(Wrapper<T> queryWrapper);
// int count(Wrapper<T> queryWrapper);
// List<T> list(Wrapper<T> queryWrapper);
// Page<T> page(Page<T> page, Wrapper<T> queryWrapper);
}
5.3 批量操作优化
java
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 批量插入(性能优化)
public void batchInsertUsers(List<User> users) {
// 方式1:使用Service的saveBatch(推荐)
userService.saveBatch(users);
// 方式2:自定义批量插入(XML方式)
userMapper.batchInsert(users);
// 方式3:循环插入(不推荐)
for (User user : users) {
userMapper.insert(user);
}
}
// 批量更新
public void batchUpdateUsers(List<User> users) {
// 使用Service的updateBatchById
userService.updateBatchById(users);
}
}
六、插件系统深度解析
6.1 分页插件
java
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInterceptor.setMaxLimit(1000L); // 单页限制
paginationInterceptor.setOverflow(false); // 超出范围回到第一页
paginationInterceptor.setOptimizeJoin(true); // 优化联表查询
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}
// 分页使用
@Service
public class UserService {
public PageResult<User> getUserPage(Integer pageNum, Integer pageSize, UserQueryDTO query) {
// 创建分页对象
Page<User> page = new Page<>(pageNum, pageSize);
// 构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(query.getKeyword()),
User::getUsername, query.getKeyword())
.eq(query.getStatus() != null, User::getStatus, query.getStatus());
// 执行分页查询
Page<User> result = userMapper.selectPage(page, wrapper);
// 返回自定义分页结果
return new PageResult<>(
result.getRecords(),
result.getTotal(),
result.getCurrent(),
result.getSize(),
result.getPages()
);
}
}
6.2 乐观锁插件
java
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
// 乐观锁使用
@Service
public class ProductService {
@Transactional
public boolean updateProductStock(Long productId, Integer quantity) {
// 1. 查询商品
Product product = productMapper.selectById(productId);
// 2. 检查库存
if (product.getStock() < quantity) {
throw new BusinessException("库存不足");
}
// 3. 更新库存(自动处理版本号)
product.setStock(product.getStock() - quantity);
int result = productMapper.updateById(product);
// 4. 处理乐观锁冲突
if (result == 0) {
// 重试机制或抛出异常
throw new OptimisticLockException("数据已被其他用户修改,请重试");
}
return true;
}
}
6.3 防止全表操作插件
java
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 防止全表更新与删除
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}
6.4 性能分析插件
java
@Configuration
public class MybatisPlusConfig {
@Bean
@Profile({"dev", "test"}) // 只在开发测试环境使用
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 性能分析插件
PerformanceInnerInterceptor performanceInterceptor = new PerformanceInnerInterceptor();
performanceInterceptor.setMaxTime(1000L); // 最大执行时间(ms)
performanceInterceptor.setFormat(true); // 格式化SQL
interceptor.addInnerInterceptor(performanceInterceptor);
return interceptor;
}
}
七、高级查询技巧
7.1 查询投影
java
// 指定查询字段
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.select(User::getId, User::getUsername, User::getAge);
List<User> users = userMapper.selectList(wrapper);
// 聚合查询
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("COUNT(*) as count",
"AVG(age) as avgAge",
"MAX(age) as maxAge",
"MIN(age) as minAge");
List<Map<String, Object>> result = userMapper.selectMaps(queryWrapper);
// 分组查询
QueryWrapper<User> groupWrapper = new QueryWrapper<>();
groupWrapper.select("dept_id", "COUNT(*) as userCount")
.groupBy("dept_id")
.having("userCount > 5");
List<Map<String, Object>> groupResult = userMapper.selectMaps(groupWrapper);
7.2 联表查询
java
// 方式1:使用@TableField(select = false) + 自定义XML
@Data
public class UserVO {
private Long id;
private String username;
@TableField(select = false)
private List<Role> roles;
}
// 在Mapper中定义联表查询方法
public interface UserMapper extends BaseMapper<User> {
List<UserVO> selectUserWithRoles(@Param("userId") Long userId);
}
// XML配置联表查询
<!-- UserMapper.xml -->
<select id="selectUserWithRoles" resultMap="UserWithRoleResultMap">
SELECT u.*, r.id as role_id, r.role_name
FROM user u
LEFT JOIN user_role ur ON u.id = ur.user_id
LEFT JOIN role r ON ur.role_id = r.id
WHERE u.id = #{userId}
</select>
// 方式2:使用MP-Join(第三方扩展)
7.3 子查询
java
// 使用IN子查询
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.inSql(User::getDeptId, "SELECT id FROM dept WHERE status = 1");
// 使用EXISTS子查询
wrapper.exists("SELECT 1 FROM user_role ur WHERE ur.user_id = id AND ur.role_id = 1");
// 复杂子查询
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.in("dept_id",
new QueryWrapper<Dept>().select("id").eq("status", 1))
.eq("status", 1);
八、自定义SQL与XML映射
8.1 自定义Mapper方法
java
public interface UserMapper extends BaseMapper<User> {
// 自定义查询方法
List<User> selectByComplexCondition(@Param("query") UserQueryDTO query);
// 自定义更新方法
int updateStatusBatch(@Param("ids") List<Long> ids,
@Param("status") Integer status);
// 调用存储过程
void callUserProcedure(@Param("userId") Long userId);
// 复杂统计查询
UserStatisticsVO selectUserStatistics();
}
8.2 XML映射配置
xml
<?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.example.mapper.UserMapper">
<!-- 复杂查询 -->
<select id="selectByComplexCondition" resultType="com.example.entity.User">
SELECT * FROM user
<where>
<if test="query.username != null and query.username != ''">
AND username LIKE CONCAT('%', #{query.username}, '%')
</if>
<if test="query.minAge != null">
AND age >= #{query.minAge}
</if>
<if test="query.maxAge != null">
AND age <= #{query.maxAge}
</if>
<if test="query.statusList != null and query.statusList.size() > 0">
AND status IN
<foreach collection="query.statusList" item="status"
open="(" separator="," close=")">
#{status}
</foreach>
</if>
</where>
ORDER BY create_time DESC
</select>
<!-- 批量更新 -->
<update id="updateStatusBatch">
UPDATE user SET status = #{status}, update_time = NOW()
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<!-- 存储过程调用 -->
<update id="callUserProcedure" statementType="CALLABLE">
{call sp_update_user_status(#{userId, mode=IN})}
</update>
</mapper>
九、多数据源支持
9.1 多数据源配置
java
@Configuration
@MapperScan(basePackages = "com.example.mapper.primary",
sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public SqlSessionFactory primarySqlSessionFactory() throws Exception {
MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
sessionFactory.setDataSource(primaryDataSource());
return sessionFactory.getObject();
}
}
@Configuration
@MapperScan(basePackages = "com.example.mapper.secondary",
sqlSessionFactoryRef = "secondarySqlSessionFactory")
public class SecondaryDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory secondarySqlSessionFactory() throws Exception {
MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
sessionFactory.setDataSource(secondaryDataSource());
return sessionFactory.getObject();
}
}
十、代码生成器高级用法
10.1 自定义代码生成器
java
public class AdvancedCodeGenerator {
public static void main(String[] args) {
AutoGenerator generator = new AutoGenerator();
// 数据源配置
DataSourceConfig dataSource = new DataSourceConfig();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("123456");
generator.setDataSource(dataSource);
// 全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir") + "/src/main/java");
globalConfig.setAuthor("YourName");
globalConfig.setOpen(false);
globalConfig.setSwagger2(true); // 开启swagger注解
globalConfig.setBaseResultMap(true); // 生成BaseResultMap
globalConfig.setBaseColumnList(true); // 生成BaseColumnList
generator.setGlobalConfig(globalConfig);
// 包配置
PackageConfig packageConfig = new PackageConfig();
packageConfig.setParent("com.example");
packageConfig.setEntity("entity");
packageConfig.setMapper("mapper");
packageConfig.setService("service");
packageConfig.setServiceImpl("service.impl");
packageConfig.setController("controller");
generator.setPackageInfo(packageConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude("user", "role", "dept"); // 生成的表
strategy.setTablePrefix("tbl_"); // 表前缀
// 自定义实体类配置
strategy.setEntityTableFieldAnnotationEnable(true);
// 自定义Controller配置
strategy.setControllerMappingHyphenStyle(true);
// 自定义Service配置
strategy.setServiceName("%sService");
generator.setStrategy(strategy);
// 模板配置
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setController("templates/controller.java"); // 自定义模板
generator.setTemplate(templateConfig);
// 注入配置
InjectionConfig injectionConfig = new InjectionConfig() {
@Override
public void initMap() {
// 自定义参数
}
};
generator.setCfg(injectionConfig);
// 执行生成
generator.execute();
}
}
十一、性能优化与最佳实践
11.1 性能优化建议
java
// 1. 合理使用索引
// 为查询条件字段添加索引
// 2. 避免N+1查询
// 使用联表查询或批量查询
// 3. 分页优化
public Page<User> optimizePageQuery(Page<User> page, UserQueryDTO query) {
// 关闭count查询(适用于不需要总条数的场景)
page.setSearchCount(false);
// 或者使用游标分页(大数据量)
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getId, query.getLastId()) // 基于ID的游标分页
.last("LIMIT " + page.getSize());
return userMapper.selectPage(page, wrapper);
}
// 4. 批量操作优化
@Service
public class BatchOperationService {
// 使用批量插入(性能提升明显)
public void batchInsertUsers(List<User> users) {
userService.saveBatch(users, 1000); // 每批1000条
}
// 使用流式查询(大数据量)
public void processLargeData() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
try (Cursor<User> cursor = userMapper.selectCursor(wrapper)) {
cursor.forEach(user -> {
// 处理数据
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
11.2 最佳实践总结
-
实体类设计
-
使用Lombok简化代码
-
合理使用MP注解
-
统一命名规范
-
-
查询优化
-
优先使用LambdaQueryWrapper
-
合理使用索引
-
避免全表扫描
-
-
事务管理
-
合理使用@Transactional
-
注意事务传播行为
-
-
缓存策略
-
合理使用二级缓存
-
注意缓存一致性
-
-
监控与调优
-
使用性能分析插件
-
监控慢SQL
-
定期优化数据库
-
十二、常见问题与解决方案
12.1 主键冲突
java
// 解决方案:统一主键策略 @TableId(type = IdType.ASSIGN_ID) // 使用雪花算法 private Long id;
12.2 字段映射问题
java
// 解决方案:明确字段映射
@TableField("user_name")
private String userName;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
12.3 乐观锁冲突
java
// 解决方案:重试机制
@Transactional
public boolean updateWithRetry(User user, int maxRetries) {
for (int i = 0; i < maxRetries; i++) {
try {
User current = userMapper.selectById(user.getId());
user.setVersion(current.getVersion());
return userMapper.updateById(user) > 0;
} catch (Exception e) {
if (i == maxRetries - 1) {
throw new BusinessException("更新失败,请重试");
}
}
}
return false;
}
12.4 分页性能问题
java
// 解决方案:游标分页
public List<User> cursorPage(Long lastId, Integer size) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getId, lastId)
.orderByAsc(User::getId)
.last("LIMIT " + size);
return userMapper.selectList(wrapper);
}
通过掌握这些核心知识点,你可以高效地使用 MyBatis-Plus 进行开发,充分发挥其简化开发、提升效率的优势!
3万+

被折叠的 条评论
为什么被折叠?



