# oneboot-core **Repository Path**: codeaone/oneboot-core ## Basic Information - **Project Name**: oneboot-core - **Description**: OneBoot 框架核心组件,包括缓存,MVC,MyBatis等项目基础能力 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: https://codeaone.gitee.io/oneboot-site - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2021-04-24 - **Last Updated**: 2023-08-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 总览 这是一个说明。 ### 😶 结构说明 - oneboot-core: 新功能 - oneboot-mvc: 修复 Bug - oneboot-mybatis: 文档修改 - oneboot-cache: 文档修改 - oneboot-event: 文档修改 - oneboot-dubbo: 文档修改 ### 👻 设计指南 #### 异常设计 在业务代码中,统计使用异常来结束流程,不要使用 if(条件) return error 这种方式 ```java AlarmConfig alarmConfig = repository.findById(id); if (null == alarmConfig) { //使用异常来结束流程 throw new ObootException(CommonErrorCode.DATA_SELECT_FAIL); } //也可以使用断言,显的代码更为简洁 AssertUtil.isNotNull(fixedBill, CommonErrorCode.DATA_SELECT_FAIL, "票据号{} 查询失败", id); ``` 错误的方式: ```java //错误的方式 if (null == req) { return new Res<>(ResultCode.R900400.getCode(), ResultCode.R900400.getMsg(), null); } ``` CommonErrorCode 错误码是会在每个系统中独一份,如果需要前端感知为什么失败,则需要自行定义 通过以上代码可以看出,异常是有:ErrorLevel,ErrorType 错误级别与错误类型 在大行的系统里,合理的定义好错误级别与错误类型有利于系统异常日志的分析 ```java if (ex instanceof ObootException) { ObootException e = (ObootException) ex; buildFailResult(result, e); IErrorCode errorCode = e.getErrorCode(); event.setErrorCode(errorCode); // 这里需要看是系统异常还是业务异常 String errorLevel = errorCode.getErrorLevel(); if (StringUtils.equals(ErrorLevel.ERROR, errorLevel)) { LoggerUtil.error(e, "操作失败:{}", e.getMessage()); } else { // 不输出堆栈信息 LoggerUtil.error("操作失败:{}", e.getMessage()); } } ``` 通过这断代码可以看出来,可以通过错误类型,来区分是否输出异常堆栈信息,一些业务检查类,其实没有必要输出异常堆栈信息。这样我们通过 common-error.log 这个日志文档,才能看到真实有的效信息 错误码,需要:系统前缀+错误类型+错误级别+错误场景+具体错误码组成,这样在日志采集时,就能分析出具体的位置。 在微服务场景下,一个异常出现时,要把异常信息带上,在调用方能清楚知道是哪个系统出现问题了。 #### 日志设计 首先讲解一下日志的基本需求: 1、清晰记录业务过程数据 2、同一业务场景的日志,集中输出到同一文件中,方便查看 3、定义一些切面日志,自动记录,并归集到指定的摘要日志文件中(web,http,rpc,redis,mq,dao) 4、能比较方便的定义出新的日志切片文件 5、异常日志独出来,单独一个日志文件存放,方便日常检查服务是否正常 6、针对日志脱敏在代码层面有较好的支持。 以上的需求是比较基础,又满足了大部分的场景需要。 #### 事件机制设计 在核心业务流程中,合理的设计业务事件,对于业务的扩展性会有很好的支持。 那什么是事件? 事件处理可以是同步处理、异步处理。 本次重点来讲解异步处理方式,最简单的就是使用Spring Event来处理,可是他是本地队列的,在系统升级或者重启时,如果内存中还有未处理完的事情,肯定会对程序发生影响。 #### 表结构动态扩展方案 在项目开发过程中,总会遇到需要增加表字段的情况,而在有些时候,一些字段并不是常用字段,也不需要做查询操作,则可以使用JSON大字段的方案来存储扩展 在本框架中,定义了一种使用JSON大字段来动态扩展的方案 #### 缓存设计 缓存分为本地缓存、分布式缓存。在本框架中,大量采用了本地缓存机制 分布式缓存Redis不管性能再好,还是会有网络开销的,而使用本地内存缓存机制,则没有此问题。 那什么时候使用本地缓存,什么时候使用分布式缓存呢?其实很简单,就是系统重启后,缓存是否需要存在,如果需要则使用分布式缓存,比如 session 的会话状态,在服务重启后,不会导致在线用户会话丢失而重新登录。 在本地缓存设计,我们主要应用在如下几个场景: 1、针对那些高频使用表,如配置表,字典表等 2、针对那些需要做转换的(用户表,商品表) 3、针对在页面上需要下拉显示的数据 总数据量不超过5万条的,都是可以选择全量数据缓存在内存中。因为我们大多数系统,除了那些增量数据,基本上不会超过5万这个数据值。 拿城市级智慧停车解决方案来讲,那些平台用户,商家,停车场,停车点,收费员等都不会超过这数值。这些数据最方便的方式就是全量放在本地缓存中。这样的系统也就非常的简单,而且这种数据也是热点很高的数据。 分布式缓存,主要是使用在会临时使用的会变的业务数据,主要是强调临时性,都要求有过期时间。 ##### 本地缓存 本地缓存,主要针对是表的全量数据,要求使用起来非常灵活方法,定制化非常高。 ```java public class SystemConfigCache { //通过一个type来获取一组配置 public static List get(String type) {} //通过type+key,来获取最终的存储值 public static String getValueByName(String metaType, String metaKey) {} // ... 可以通过自己的业务需求,来定制不同的缓存数据获取方式,这样对业务使用非常友好 } ``` 那么本地缓存,在什么时候加载,什么时候更新,就是接下来的重点工作。 因为是全量加载,所以本地缓存应该是在系统启动时进行数据加载,而在数据变更时,做数据的全量or增量更新。如果是数据量比较小的,直接全量更新就行。 系统启动时加载,要求是在缓存加载完成后,外面的请求才能正常请求进来,所以需要设计一个缓存的加载组件,只要实现 CacheManager 接口,就能做到缓存自动加载。 WarpTheVoService 这个也是需要使用这种机制才行呀。已处理 页面数据转换过程缓存,包括 表依赖,枚举,数据字典 几个常用场景。我们在表依赖中,只存放ID,并不存放Name字段。当然在特姝情况,需要数据快照时,可直接存放Name字段。 那么我们常用的做法去数据库里做表的联合查询,或者做一个新的视图出来。这样做,其实是比较费时间的。而我们可以把依赖的这些数据全量加载起来,当然你可能会问,万一数据量大怎么办呢,如果是数据量比较大的情况下,那就做增量查询可以在数据库里做联合查询。但基本上是建议做增量查询操作。 ##### 表依赖 ##### 枚举 ##### 数据字典 #### 信息脱敏 信息脱敏必须是在后端做处理的,从接口返回的数据就应该做了信息脱敏处理。而不是在前端展示页面做。 信息脱敏的几个环节: - 接口响应字段 - 日志输出(关键信息需要被脱敏) 那如何优雅的处理这个问题呢? ## 代码提交 - feat: 新功能 - fix: 修复 Bug - docs: 文档修改 - perf: 性能优化 - revert: 版本回退 - ci: CICD 集成相关 - test: 添加测试代码 - refactor: 代码重构 - build: 影响项目构建或依赖修改 - style: 不影响程序逻辑的代码修改 - chore: 不属于以上类型的其他类型(日常事务) ## 交流