2026/3/28 17:36:51
网站建设
项目流程
网站开发答辩知识点,如何提高网站访问速度,做网站的任务书,门户网站模板之家维度 2#xff1a;SQL 安全与审计 —— 防注入 全链路 SQL 监控
1. 条件构造器防注入最佳实践
避免使用字符串拼接条件#xff0c;优先使用 Lambda 表达式与参数绑定#xff1a;
java
运行
// 错误示例#xff1a;字符串拼接易引发SQL注入
QueryWrapperOrder…维度 2SQL 安全与审计 —— 防注入 全链路 SQL 监控1. 条件构造器防注入最佳实践避免使用字符串拼接条件优先使用 Lambda 表达式与参数绑定java运行// 错误示例字符串拼接易引发SQL注入 QueryWrapperOrder wrongWrapper new QueryWrapper(); String status 1 OR 11; // 恶意注入参数 wrongWrapper.eq(status, status); // 若直接拼接会执行恶意SQL // 正确示例1Lambda表达式自动参数绑定防注入 LambdaQueryWrapperOrder lambdaWrapper new LambdaQueryWrapper(); lambdaWrapper.eq(Order::getStatus, 1); // 自动生成参数化SQL // 正确示例2复杂条件用selectObjs/func方法避免直接拼接 QueryWrapperOrder rightWrapper new QueryWrapper(); rightWrapper.inSql(user_id, SELECT id FROM t_user WHERE role_id #{roleId}) .eq(is_deleted, 0); // 子查询也支持参数绑定核心原则禁止使用eq(column, 拼接字符串)复杂场景优先用 Lambda 或inSql依赖 MP 自动生成参数化 SQL?占位符从根源杜绝注入。2. 自定义 SQL 审计插件全链路 SQL 监控与拦截基于 MyBatis 拦截器机制开发自定义插件实现 SQL 执行前校验、执行后日志记录支持异常 SQL 拦截java运行package com.example.mp.plugin; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.util.Properties; /** * 自定义SQL审计插件监控SQL执行耗时、拦截风险SQL */ Component Intercepts({ Signature(type Executor.class, method query, args {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), Signature(type Executor.class, method update, args {MappedStatement.class, Object.class}) }) public class SqlAuditPlugin implements Interceptor { private static final Logger log LoggerFactory.getLogger(SqlAuditPlugin.class); // 慢SQL阈值毫秒 private static final long SLOW_SQL_THRESHOLD 500; Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement ms (MappedStatement) invocation.getArgs()[0]; Object parameter invocation.getArgs()[1]; BoundSql boundSql ms.getBoundSql(parameter); String sql boundSql.getSql().replaceAll(\\s, ); // 格式化SQL String method ms.getId(); // Mapper方法全路径 // 1. 风险SQL拦截示例拦截DELETE无WHERE条件的SQL if (ms.getSqlCommandType().name().equals(DELETE) !sql.contains(WHERE)) { log.error(拦截风险SQL无WHERE条件的DELETE操作method{}, sql{}, method, sql); throw new RuntimeException(禁止执行无WHERE条件的DELETE操作); } // 2. 统计SQL执行耗时 long start System.currentTimeMillis(); Object result invocation.proceed(); // 执行SQL long cost System.currentTimeMillis() - start; // 3. 慢SQL日志告警 if (cost SLOW_SQL_THRESHOLD) { log.warn(慢SQL告警method{}, cost{}ms, sql{}, parameter{}, method, cost, sql, parameter); } else { log.debug(SQL执行记录method{}, cost{}ms, sql{}, method, cost, sql); } return result; } Override public Object plugin(Object target) { return Plugin.wrap(target, this); // 生成代理对象 } Override public void setProperties(Properties properties) { // 可通过配置文件注入参数如慢SQL阈值 } }将插件注册到 MP 配置中与分页插件协同工作java运行// 补充MyBatisPlusConfig配置 Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 物理分页插件 PaginationInnerInterceptor paginationInterceptor new PaginationInnerInterceptor(DbType.MYSQL); paginationInterceptor.setOptimizeJoin(true); paginationInterceptor.setMaxLimit(1000L); interceptor.addInnerInterceptor(paginationInterceptor); // 注册SQL审计插件 interceptor.addInnerInterceptor(new SqlAuditPlugin()); return interceptor; }维度 3批量操作优化 —— 分片处理 连接池适配MP 默认批量操作saveBatch/updateBatchById采用单条 SQL 拼接大数据量万级以上时会导致 SQL 过长、数据库连接超时或 OOM需通过 “分片批量 连接池优化” 解决。1. 分片批量操作实现Service 层扩展基于 MP 原生方法封装分片逻辑将大批次数据拆分为小批次提交避免单次操作压力过大java运行package com.example.mp.service.impl; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.mp.mapper.OrderMapper; import com.example.mp.entity.Order; import com.example.mp.service.OrderService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.stream.Collectors; Service public class OrderServiceImpl extends ServiceImplOrderMapper, Order implements OrderService { // 分片批次大小根据数据库性能调整建议500-1000条/批 private static final int BATCH_SIZE 500; Override Transactional(rollbackFor Exception.class) public boolean saveBatchWithSharding(ListOrder orderList) { if (CollectionUtils.isEmpty(orderList)) { return false; } // 分片处理按BATCH_SIZE拆分列表 ListListOrder batches orderList.stream() .collect(Collectors.groupingBy(order - orderList.indexOf(order) / BATCH_SIZE)) .values().stream().toList(); // 逐批提交 for (ListOrder batch : batches) { super.saveBatch(batch, BATCH_SIZE); } return true; } }2. 数据库连接池适配批量操作需调整连接池参数避免连接耗尽yaml# application.yml 连接池配置HikariCP spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/mp_db?useUnicodetruecharacterEncodingutf8rewriteBatchedStatementstrue username: root password: 123456 hikari: maximum-pool-size: 20 # 最大连接数批量操作时需足够 minimum-idle: 5 # 最小空闲连接 connection-timeout: 30000 # 连接超时时间 idle-timeout: 600000 # 空闲连接超时时间关键配置开启rewriteBatchedStatementstrueMySQL 会将批量 SQL 转换为原生批量语句提升执行效率。维度 4数据权限管控 —— 插件化实现多租户 行级权限多租户、行级权限是企业级场景核心需求通过 MP 插件实现 “无侵入式权限管控”避免业务代码耦合权限逻辑。1. 多租户插件实现共享数据表方案基于 MP 租户插件自动为 SQL 添加租户 ID 条件实现数据隔离java运行package com.example.mp.config; import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.LongValue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 多租户配置共享数据表通过tenant_id字段隔离 */ Configuration public class TenantConfig { Bean public TenantLineInnerInterceptor tenantLineInnerInterceptor() { return new TenantLineInnerInterceptor(new TenantLineHandler() { // 获取当前租户ID实际场景从上下文/Token中获取 Override public Expression getTenantId() { Long tenantId getCurrentTenantId(); // 自定义方法从ThreadLocal获取租户ID return new LongValue(tenantId); } // 租户字段名数据库表中统一为tenant_id Override public String getTenantIdColumn() { return tenant_id; } // 忽略租户过滤的表如字典表、公共配置表 Override public boolean ignoreTable(String tableName) { return t_dict.equals(tableName) || t_config.equals(tableName); } }); } // 补充到MyBatisPlusInterceptor中 Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 多租户插件优先级高于分页插件 interceptor.addInnerInterceptor(tenantLineInnerInterceptor()); // 物理分页插件 PaginationInnerInterceptor paginationInterceptor new PaginationInnerInterceptor(DbType.MYSQL); paginationInterceptor.setOptimizeJoin(true); paginationInterceptor.setMaxLimit(1000L); interceptor.addInnerInterceptor(paginationInterceptor); // SQL审计插件 interceptor.addInnerInterceptor(new SqlAuditPlugin()); return interceptor; } // 模拟获取当前租户ID实际需结合权限框架实现 private Long getCurrentTenantId() { return ThreadLocalUtil.get(tenantId, Long.class); } }2. 行级权限插件实现数据范围过滤针对同一租户内不同角色的数据范围控制如管理员看全量、普通用户看自身数据扩展插件实现java运行// 行级权限插件核心逻辑拦截SQL添加数据范围条件 public class DataScopePlugin implements InnerInterceptor { Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { // 1. 获取当前用户角色与数据范围 DataScope dataScope getCurrentDataScope(); if (dataScope null) { return; } // 2. 拼接数据范围条件如user_id #{currentUserId} 或 dept_id IN (...) String scopeSql buildScopeSql(dataScope); // 3. 改写原SQL添加数据范围条件 String newSql boundSql.getSql() AND scopeSql; ReflectUtil.setFieldValue(boundSql, sql, newSql); } // 构建数据范围SQL根据角色动态生成 private String buildScopeSql(DataScope dataScope) { if (ADMIN.equals(dataScope.getRoleCode())) { return 11; // 管理员无限制 } else if (DEPT_MANAGER.equals(dataScope.getRoleCode())) { return dept_id IN ( String.join(,, dataScope.getDeptIds()) ); // 部门经理看本部门 } else { return user_id dataScope.getUserId(); // 普通用户看自身 } } }维度 5缓存体系设计 —— 二级缓存 分布式缓存联动MP 结合 MyBatis 二级缓存与 Redis 分布式缓存构建 “本地缓存 分布式缓存” 二级体系解决缓存穿透、击穿问题。1. 开启 MyBatis 二级缓存本地缓存在 MP 配置中开启二级缓存适配 BaseMapper 接口xml!-- mybatis-config.xml -- configuration settings !-- 开启二级缓存 -- setting namecacheEnabled valuetrue/ !-- 二级缓存序列化方式 -- setting namecacheProviderType valueorg.apache.ibatis.cache.impl.PerpetualCache/ /settings /configuration在 Mapper 接口添加缓存注解指定缓存策略java运行Mapper CacheNamespace(implementation RedisCache.class, // 自定义Redis缓存实现 eviction FifoCache.class, // 缓存淘汰策略 flushInterval 3600000, // 缓存过期时间1小时 size 1024, // 缓存最大条数 readWrite true) // 读写缓存 public interface OrderMapper extends BaseMapperOrder { // ... }2. 自定义 Redis 缓存实现分布式缓存集成 Redis 实现分布式缓存解决本地缓存集群不一致问题java运行package com.example.mp.cache; import org.apache.ibatis.cache.Cache; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; import javax.annotation.Resource; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * MyBatis二级缓存Redis实现分布式缓存 */ public class RedisCache implements Cache { private final String id; // 缓存ID对应Mapper接口全路径 private final ReadWriteLock readWriteLock new ReentrantReadWriteLock(); Resource private RedisTemplateString, Object redisTemplate; public RedisCache(String id) { this.id id; } Override public String getId() { return id; } Override public void putObject(Object key, Object value) { // 缓存Key前缀MapperID缓存Key String cacheKey mp:cache: id : key.toString(); redisTemplate.opsForValue().set(cacheKey, value, 3600, java.util.concurrent.TimeUnit.SECONDS); } Override public Object getObject(Object key) { String cacheKey mp:cache: id : key.toString(); return redisTemplate.opsForValue().get(cacheKey); } Override public Object removeObject(Object key) { String cacheKey mp:cache: id : key.toString(); redisTemplate.delete(cacheKey); return null; } Override public void clear() { // 批量删除该Mapper的所有缓存 String pattern mp:cache: id :*; redisTemplate.delete(redisTemplate.keys(pattern)); } Override public int getSize() { String pattern mp:cache: id :*; return redisTemplate.keys(pattern).size(); } Override public ReadWriteLock getReadWriteLock() { return readWriteLock; } }六、MyBatis-Plus 生产级优化总结性能层面物理分页解决大数据量查询瓶颈分片批量避免 OOM缓存体系降低数据库压力整体查询性能提升 60% 以上安全层面参数绑定防注入 SQL 审计插件实现全链路 SQL 监控杜绝注入风险与恶意操作扩展性层面插件化实现多租户、行级权限无侵入适配企业级权限需求降低业务代码耦合稳定性层面连接池优化 慢 SQL 告警 缓存防护确保高并发场景下的稳定运行。