# BMS **Repository Path**: feng01000/bms ## Basic Information - **Project Name**: BMS - **Description**: 图书馆管理系统 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-09-01 - **Last Updated**: 2021-09-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 微型图书馆管理系统 ## 程序启动 程序依赖中间件:`redis`,首先本地启动`redis`,然后再`application-dev.yml`配置`redis`信息 ``` spring: redis: database: 3 host: 127.0.0.1 port: 6379 password: chenfeng123 timeout: 5000ms jedis: pool: max-active: 100 max-idle: 10 max-wait: 100000ms ``` 依赖H2数据库,数据库初始信息在 `resources/sql/init.sql`,`resources/sql.data.sql` H2数据库查看地址:http://127.0.0.1:9012/bms/h2-console ``` spring: datasource: driver-class-name: org.h2.Driver url: jdbc:h2:mem:bms_h2_sql username: sa password: # 初始化数据表 schema: classpath:sql/init.sql # 初始化数据 data: classpath:sql/data.sql initialization-mode: always hikari: minimum-idle: 5 maximum-pool-size: 15 connection-test-query: SELECT 1 max-lifetime: 1800000 connection-timeout: 30000 pool-name: DatebookHikariCP h2: # 开启console 访问 默认false console: enabled: true settings: # 开启h2 console 跟踪 方便调试 默认 false trace: true # 允许console 远程访问 默认false web-allow-others: true # h2 访问路径上下文 path: /h2-console ``` ![image-20210903062630381](.\h2-console.png) 初始3条用户记录,密码均为 ***123456qaz*** ``` -- 123456qaz insert into bookmanager.user values (1,'zhangSan','zhangSan','{bcrypt}$2a$10$1oDBO5YS4YyztoYmHUOHCuzJoxt5AkdGs3Kf7YSBVGlIOxc31SIX2',1,20,1630424608,1630424608,1); insert into bookmanager.user values (2,'liSi','liSi','{bcrypt}$2a$10$1oDBO5YS4YyztoYmHUOHCuzJoxt5AkdGs3Kf7YSBVGlIOxc31SIX2',1,21,1630424608,1630424608,1); insert into bookmanager.user values (3,'wangWu','wangWu','{bcrypt}$2a$10$1oDBO5YS4YyztoYmHUOHCuzJoxt5AkdGs3Kf7YSBVGlIOxc31SIX2',1,22,1630424608,1630424608,1); ``` 直接运行程序,打开swagger地址 http://127.0.0.1:9012/bms/doc.html ![image-20210903062821996](.\bms-swagger.png) ## 图书馆功能 - 账号登录 /bms/login/loginByAccount - 借书 book/borrowBookList` - 还书 /bms/book/backBookList - 查看书籍详情 /bms/book/getBookDetailWithBookId - 当前书籍借出详情 /bms/book/listBookBorrowDetailWithBookId - 本人所借出图书详情 /bms/book/listBookBorrowDetailWithMe - 分页查询书籍信息 /bms/book/listBookWithPageKeyMsg ## 缓存设计 目前使用2级缓存 一级缓存是依托`guava`提供的`jvm`内存缓存 二级缓存使用的是`redis` ![image-20210903063228866](.\cache-redis.png) 热点数据重建 在程序启动时,将热点数据(本例中是加载所有图书信息-图书名称,ISBN,出版日期等)加载到 redis 缓存。 ``` @Service public class LoadBookCache implements InitializingBean { @Autowired private BookMapper dao; @Autowired private RedisTemplate redisTemplate; @Override public void afterPropertiesSet() throws Exception { loadHotBook2Cache(); } public void loadHotBook2Cache() { if (!redisTemplate.hasKey(RedisKeyPrefixConst.FLASH_PROMOTION_PRODUCT_KEY)) { List promotion = dao.listAllBook(); if (!ObjectUtils.isEmpty(promotion)) { promotion.stream().forEach(item -> { redisTemplate.opsForValue().set(RedisKeyPrefixConst.FLASH_PROMOTION_PRODUCT_KEY + String.valueOf(item.getBookId()), item); }); } } } } ``` ## 权限控制 本例使用 security+oauth2 实现权限控制。 本例中接口权限由menu表管理 权限表关系是:user --> rel_user_role --> role --> rel_role_menu -->menu ![image-20210903064420543](.\init_role_detail.png) USER 和 ADMIN 角色的用户都访问 `getBookDetailWithBookId`[查看书籍详情] 接口返回结果 用户 `liSi`,是 `USER` 角色,没有 `getBookDetailWithBookId` 访问权限 用户 `zhangSan`,是 `ADMIN` 角色,有 `getBookDetailWithBookId` 访问权限。 ![image-20210903064947404](.\getBookDetailWithBookId.png) ## 日志打印 本例使用 logback+aop,打印接口访问日志,方便查看用户接口请求参数及结果处理 ``` 2021-09-03 06:57:47,517 [http-nio-9012-exec-6] INFO c.b.m.config.RequestParameterAop 97 - REQUEST/RESPONSE IP【127.0.0.1】 -> URL【http://127.0.0.1:9012/bms/book/getBookDetailWithBookId】BookController[书籍管理] -> getBookDetailWithBookId[查看书籍详情] -> 请求耗时:[8]ms REQUEST:["req"] -> ["BooKIdReq(bookId=1)"] RESPONSE:{"code":0,"data":{"volume":10,"createTime":1630424608,"allVolume":10,"isValid":1,"isbn":"9787111213826","articleDate":"2007-06-01","updateTime":1630424608,"bookName":"Java编程思想(第4版)","bookId":1},"message":"成功"} ``` ## 全局异常 使用 @RestControllerAdvice + @ExceptionHandler 进行全局异常捕获 ``` @RestControllerAdvice public class ExceptionControllerAdvice { @ExceptionHandler(MyRuntimeException.class) public BaseResult APIExceptionHandler(MyRuntimeException e) { return Result.fail(e.getMessage()); } @ExceptionHandler(Exception.class) public BaseResult MethodArgumentNotValidExceptionHandler(Exception e) { // 从异常对象中拿到ObjectError对象 // ObjectError objectError = e.getMessage(); StringBuffer messsage = new StringBuffer(); if (e != null) { messsage.append(e.getClass()).append(": ").append(e.getMessage()).append("\n"); StackTraceElement[] elements = e.getStackTrace(); for (StackTraceElement stackTraceElement : elements) { messsage.append("\t").append(stackTraceElement.toString()).append("\n"); } } String errorMsg = messsage.toString(); // 然后提取错误提示信息进行返回 return Result.fail(errorMsg); } } ```