springboot bean管理,鉴权,异常统一处理、整合mybatis、单点登录,断言,统一http返回码,全局线程池接口字段校验,导出excel,google缓存结合stream判空,redis

📅 发布时间:2026/8/13 16:54:28
springboot bean管理,鉴权,异常统一处理、整合mybatis、单点登录,断言,统一http返回码,全局线程池接口字段校验,导出excel,google缓存结合stream判空,redis 目录1pom引入2整合mybatis2.1 mybaits执行过程2.2 sqlsession是线程不安全的。2.3 springboot整合mybatis3异常统一处理3.1 为什么要使用异常统一处理3.2 处理方式4接口鉴权5jwt 生成token6单点登录 token6.1 验证token过期的方式6.2 单点登录token在不同域之间怎么传递7, 断言8加密处理9统一http返回码10全局线程池11接口字段校验12导出excel13google缓存1pom引入通常引入spring-boot-starter 和 spring-boot-starter-web两个即可2整合mybatispom再引入mybatis-spring-boot-starter2.1 mybaits执行过程读取配置文件中数据库连接信息 -》创建一个sqlsessionFactory工厂 -》调用工厂中指的方法创建一个sqlsession会话实例-》sqlssesion.getMapper(.class)获取对应的mapper映射实例-》调用mapper中的方法执行sql - 提交、关闭。2.2 sqlsession是线程不安全的。因为connection是线程不安全的。为什么connection是线程不安全的呢如果多个线程共享一个connection实例。可能一个线程刚开启事con.setAutoCommit(true);而另一个线程直接提交事务con.commit();导致数据混乱。通常说的线程不安全指的是sqlsession的defaultSqlsession实现类这种方式需要手动提交sql。spring为我们提供了sqlsessionTemplate,sqlsessionManager 两种线程安全的实例。二者使用了ThreadLocal 和动态代理的方式处理线程安全的问题。这两种不用手动提交关注业务即可。使用原生的方式连接数据库在线程安全的情况下每次都要建立连接、销毁连接比较耗性能。所以使用数据库连接池在连接池中维持着多个连接。线程需要时从连接池中取即可使用完放回。一个线程中如果有多次跟数据库的交互没有事务时(通常都是查询)这些交互时的数据库连接可能不是同一个conncetion对象。如果是事务那就是一个连接对象。2.3 springboot整合mybatis简单理解引入mybatis-spring-boot-starter创建DataSource、sqlsessionFactory bean完成对数据库的连接。创建sqlsessionTemplate bean。3异常统一处理3.1 为什么要使用异常统一处理当前普遍的项目接口处理的方式是返回错误码和对应的错误说明。对于正常的返回信息是返回的实体字段信息这个没啥问题。但是对已运行时异常和我们抛出的异常不能按照我们的预想是返回错误码和错误说明。所以我们要对捕获的异常和运行时异常也能返回错误码和对应的异常说明处理。对于抛出的异常可以指定错误码。但是对于运行时异常则返回统一的系统异常在日志中打印真实的栈信息异常。3.2 处理方式参照如下博文https://www.cnblogs.com/kire-cat/p/15350364.html大致流程是添加配置类在配置类中采用aop的方式对运行时异常进行拦截获取到异常信息的错误码和错误信息使用ResponseBody注解返回给调用者。关键注解是类注解RestControllerAdvice。方法注解ExceptionHandler ResponsesBodyExceptionHandler是对controller任何一处异常做拦截。Responsebody这个注解表示你的返回值将存在responsebody中返回到前端RestControllerAdvice 注解是作用是拦截所有Controller注解进行增强处理。4接口鉴权给所有接口加鉴权处理。方式创建一个配置类也是一个bean (使用Configuration, Bean)。实现WebMvcConfigurer添加一个拦截器方法拦截器所有请求。并将拦截的请求转到我们自定义的处理器中出来。自定义拦截处理器需要实现HandlerInterceptor。自定义拦截处理器主要处理内容:如解析请求头中的token判断能否正常解析能否解析其中用户信息秘钥信息。在拿到用户秘钥信息与数据库中真实的用户信息对比一致则通过认证。5jwt 生成token原理大概是使用令牌即jwt,和加密算法以base64编码输出生成token。可以添加需要加密的内容如用户名密码。可以设置过期时间6单点登录 token关键点解决不同域名用户信息不能共享的问题。跨域先知道啥是跨域简而言之就是ip和端口或者域名是一样的。这样在浏览器中就能共享cookie了。单点登录分两种情况即浏览器访问时域名是否相同。相同域名这种情况一般是多系统后台服务有一个统一的转发入口服务类似于网关在网关中做token验证和登录。不同域名(自己项目的情况)这种就需要跨域访问的时候url携带上当前域cookie中的token然后在新的页面中先掉后端接口检验token是否正确。检验通过后前端再重定向刷新页面(使用当前token,访问新页面接口)去掉地址栏中的token正式访问。总结域名相同操作主要在后端域名不同操作主要在前端。6.0 怎么刷新token前端登录时把token放localstorage中每次请求时后台会重置token的过期时间并放到响应头中。这些操作都是在前后端各自的拦截器中实现。当然这种方式可能全新的太频繁了因为一次一般要请求很多接口。可以考虑加个时间判断快过期的时候后台再把新的token放到响应头中。6.1无论哪种登录方式都要带有用户信息。单纯一个地址是不行的。一般单点登录是不通系统间的跳转都能够拿到token的。7, 断言断言可以理解一个工具类通常用来做判空、是否等于等处理。断言和统一异常结合起来使用可以减少大量try catch的冗余处理。适合用于不满足条件时直接返回接口异常信息。8加密处理给数据库等密码做简单加密处理。springboot 提供了jasypt加密方式使用方便参照SpringBoot使用jasypt加解密密码8.2 登录密码加密处理使用RSA非对称加密处理后端可直接生成公钥密钥对(简单的形式不用jdk或申请证书)把公钥给前端。后端接收加密后的数据使用私钥解密即可。另md5加密是不可逆的所以无法解密。对于需要原字符串对比情况md5不适用。但是md5每次加密后的结果是一样的。9统一http返回码背景接口返回码分 业务状态码和http状态码。业务状态码可以随意定义。但是http状态码只有固定的一些如200,404500。有时候虽然接口状态码返回200但实际接口业务处理异常了。在浏览器控制台这种报错的接口是不爆红的给我们定位哪个接口出错带来不便。所以要求在接口业务处理异常时http状态码也返回异常。定义一个配置类实现ResponseBodyAdvice接口使用ControllerAdvice声明切面。注意也不是当自定义的业务码不在固定http状态码时会报错。所以要使用try catch捕获异常处理。10全局线程池使用类注解Configration方法注解Bean(asyncThreadPool),声明一个bean。然后ThreadPoolTaskExceutor exector new ThreadPoolTaskExcecutor声明一个线程池bean对象。在需要异步处理的方法上使用注解Async(asyncThreadPool 即可。这个方法的内容就可以异步操作了。11接口字段校验背景要对接口的字段判空、判断长度。1首先控制器层面Post 请求的参数要使用RequestBody 否则不识别。再者要声明Validated才会对参数对象中的字段判断。Length 限制长度 Notnull 不空Min 数值型最小值Max 数值型最大值2每个字段注解也可以使用groups分组使用如Length(max 20groups {Save.class, Update.class}3Get 请求可以使用RequestParam(defaultValue ) 声明默认值。12导出excel推荐使用alibaba.easyexcel。注解声明表头、列。可以自定义样式策略。其他导出excel的包容易卡死。不推荐。13google缓存google提供的loadingCache缓存包在查不到缓存时会抛出异常极不友好。所以建议结合stream流处理。// 声明缓存 private LoadingCacheString, OptionalString cache CacheBuilder.newBuilder().maximumSize(200) .expireAfterWrite(14400, TimeUnit.SECOND) .build( ListModel list service.queryData(); if(list null){ return Optional.empty(); } list.forEach(m- cache.put(key,value)); OptionalString value cache.getIfParent(key); if(value null){ return Optional.empty(); } return value; }); // 使用缓存 捕获异常查不到也影响业务 try{ return cache.get(key).orElse(null);\ }cache(Exception e){ return null; }14redis缓存和分布式锁、布隆过滤器1,redis 使用redisTemplate 操作redis2,缓存企业、部门、人员、字典数据过期时间半天、一天不等。3使用string格式即可key使用固定前缀加数据的键。值如果是复杂结构的则存储为json格式。4分布式锁:1用途 防止反复推送数据2项目启动时缓存数据到redis时先上锁再开启缓存因为缓存时间较长防止其他节点重复缓存相同的数据。 锁超期时间设置为100s。使用1加锁逻辑先获取key如果存在说明有其他节点在执行了放弃执行。如果没有则加缓存加锁。2删除锁任务执行完后删除锁删除前先判断锁的值是否一样。记住 锁的值是随机生成的。一样才能删除锁。否则可能出现任务执行的时间很长锁超时自动释放了。任务执行完了去释放锁可能把另外一个节点正在执行任务的锁给释放了。3,使用rediscallback 使加锁和设置过期时间在一个redis链接中保证事务原子性解决设置过期时间失败的问题。使用lua脚本解锁。if redis.call(get, keys) argv[1] then return redis.call(del, keys[1])。先判断锁的值是否一样一样才能删除。redis执行lua脚本能保证查询和删除在一个原子操作。redis序列化要注意一般有两种序列化方式一种是json一种是string。通常key是字符串value是json。存储时用的哪种方式取值时也要用哪种4.使用spring提供的redistemplate类获取锁执行execute方法返回成功就加锁成功。解锁使用lua脚本也是通过redistemplate执行。也可以使用setnx提供的原子性操作方法保证线程安全。集群使用主从复制做容灾。主节点写从节点读。5布隆过滤器原理是用来处理缓存穿透的。也就是越过缓存查数据库中不存在的数据。过滤器不存原始数据而是把数据映射成多个哈希函数然后查询的时候只要有一个值不存在就说明数据库中没这个值。当然了也有可能会误判即使不存在的数据也可能跟存在的数据的哈希值是一样的。但是没关系偶尔几个逃脱的去查库了也能接受。布隆过滤器Bloom Filter原理1️⃣ 它是什么结构布隆过滤器 一个很长的 bit 数组 多个哈希函数bit 数组 [0 0 0 0 0 0 0 0 0 0 ...] 哈希函数 h1(x), h2(x), h3(x) ...不存原始 key只把 key 映射到若干 bit 位置成 12️⃣ 插入元素比如插入user:1001用 3 个哈希函数h1(user:1001) % m 2 h2(user:1001) % m 5 h3(user:1001) % m 9把 bit[2]、bit[5]、bit[9] 置 1bit: [0 0 1 0 0 1 0 0 0 1 ...]3️⃣ 查询元素查user:9999h1 - 2 h2 - 7 h3 - 9检查bit[2] 1bit[7] 0 ❌只要有一个 bit 是 0 → 一定没插入过如果全是 1可能是真的插入过也可能是别的 key 把这几个 bit 碰巧置 1 了误判15幂等处理幂等的方式有很多种最简单的是主键(包括业务唯一索引)限制和前端防抖。2较复杂的是token去重需要前后端配合比如某个更新接口当进入到这个接口所在的页面时前端请求后端获得一个token然后拿这个token调更新接口。此时前后端各自保存一个相同的token,后端接口会先拿token去redis中找(要考虑到分布式redis结构是string,键可以是这个接口名值就是token。)如果token一致说明是首次操作然后更新后端的token, 如果前端再次发来请求两边token就会对不上说明请求就重复了。3悲观锁只适合更新操作对同一条数据使用fot update加锁。但这里的查询条件一定要是主键否则会锁全表。16, nginx转发分流17处理的问题。1项目启动找不到bean。处理发现在使用Async异步处理时在bean有循环依赖时会出现找不到bean的情况。解决使用lazy懒加载但保险起见还是不使用循环依赖的书写方式可增加业务处理层数。18, 查看服务所在目录属于哪个文件系统以及剩余磁盘空间命令: dh -lh 目录如 dh -lh /home20 redis删除策略定时删除给键设置过期时间。惰性删除如果没有设置过期时间就统一设置一个在请求时先看有没有过期。定期淘汰内存达到阈值就使用淘汰策略比如 最近不常用的使用最少的时间最长的随机删除等。21 kafka吞吐快的原因这个问题没思考过记一下。kafka是对文件做读写追加顺序操作反正这样就是快。2.零拷贝 正常写磁盘数据要经过缓冲区但人家不需要直接写。3分区分段可并行写。4可批量提交压缩传输。22 kafka消费者组的概念。就是多个消费者共享一个groupid共同消费。可以提升消费速度也能保证有序性也支持容灾有的消费者挂了也不影响。22. redis自动续期 策略。就使用redission提供的watch dog。首先要开启这个功能。然后会开启定时任务隔个十几秒扫一下锁还在被持有就继续续期。听说这个源码有用到可重入锁。