# poseidon-boot-starter **Repository Path**: wang_lintao/poseidon-boot-starter ## Basic Information - **Project Name**: poseidon-boot-starter - **Description**: No description available - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-06-12 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ![poseidon](https://github.com/muggle0/poseidon-cloud/blob/master/project-document/png/factory.jpg?raw=true) # poseidon-boot-starter 使用说明 很nice 的 springboot 项目的脚手架,请路过的朋友点一个star。 对 springboot 项目感兴趣的小伙伴可以关注 [poseidon](https://github.com/muggle0/poseidon) 对 springCloud 感兴趣的小伙伴可以关注 [poseidon-cloud](https://github.com/muggle0/poseidon-cloud) 本人的书:[muggle的书](https://muggle-book.gitee.io/) 博客:muggle.javaboy.org 框架集成功能: - 异常报警 - 权限动态配置 - 幂等锁 - 日志分组 - 用户操作日志记录 - 查询接口通用化。 ## 开发日志 - 2020.3.9 1.0.0.Beta 发布。多方测试bug - 2020.4.5 项目从cloud-starter 分裂出来,补充部分不成熟地方。 - 2020.4.16 发布 `0.0.1.Beta` 版 - 2020.4.17 开始开发webflux版,提供对webflux的支持(webflux 分支 版本号0.0.1-webflux.Alpha)。 - 2020.6.1 `0.0.1.Beta` 添加查询组件功能,并计划发布`0.0.1.release`。 ## 快速开始 具体使用案例可参考 [sofia](https://github.com/fighting-v/sofia) 小伙伴喜欢的可以关注下这个项目嗷。 第一步拉取项目 并且使用 maven 安装到本地。 拉取项目: ``` git clone https://github.com/muggle0/poseidon-boot-starter.git ``` 安装到本地仓库: ``` cd poseidon-boot-starter mvn install ``` 第二步 创建 spring boot工程 并引入依赖: ```xml com.muggle poseidon-boot-starter 0.0.1.Alpha ``` 第三步开启自动化配置并注册 `tokenService` 和 `securityStore` appplication.properties: ```properties spring.profiles.active=dev # 使用内置logback配置代替spring logback配置 logging.config=classpath:poseidon-logback.xml # 配置内置logback参数 log文件位置 log.dir=logs #是否开启自动化配置,开启自动化配置后会注入权限管理,幂等锁,统一异常处理功能 poseidon.auto=true # 权限管理配置忽略的 url 使用ant匹配符。 poseidon.ignore-path=/** ``` 接下来往spring容器注册 ```java /** * muggle */ @Service public class SofiaSecurityStore implements SecurityStore { private static String publicKey="test"; @Override public UserDetails getUserdetail(String token) throws BasePoseidonCheckException { String role = JwtTokenUtils.getBody(token, publicKey, "role"); String username = JwtTokenUtils.getBody(token, publicKey, "username"); SofiaUserDO sofiaUserDO = new SofiaUserDO(); sofiaUserDO.setAccountNonExpired(true); sofiaUserDO.setAccountNonLocked(true); SimpleGrantedAuthority admin = new SimpleGrantedAuthority(role); sofiaUserDO.setAuthorities(Arrays.asList(admin)); sofiaUserDO.setEnabled(true); sofiaUserDO.setUsername(username); return sofiaUserDO; } @Override public String signUserMessage(UserDetails userDetails) { Map roleMap=new HashMap<>(); roleMap.put("username",userDetails.getUsername()); roleMap.put("role","/admin/**"); String token = JwtTokenUtils.createToken(roleMap, publicKey, 12L); return token; } @Override public Boolean cleanToken(String s) { return null; } } @Component public class SofiaTokenService implements TokenService { private AntPathMatcher antPathMatcher=new AntPathMatcher(); @Override public UserDetails getUserById(Long aLong) { return null; } @Override public boolean rooleMatch(Collection collection, String s) { boolean match=false; for (GrantedAuthority grantedAuthority : collection) { String authority = grantedAuthority.getAuthority(); match = antPathMatcher.match(authority, s); } return match; } @Override public void saveUrlInfo(List list) { } @Override public UserDetails login(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws SimplePoseidonCheckException { return loadUserByUsername("admin"); } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SofiaUserDO sofiaUserDO = new SofiaUserDO(); sofiaUserDO.setAccountNonExpired(true); sofiaUserDO.setAccountNonLocked(true); sofiaUserDO.setEnabled(true); sofiaUserDO.setUsername(username); return sofiaUserDO; } } ``` 访问自定义的接口,我们就能看到权限拦截成功了。 ## 接口说明: ### 权限相关配置 `SecurityStore.getUserdetail` 方法 根据token获取用户信息,当请求头 `header` 里面 有 `token` 如: ``` POST http://localhost:8080/test Content-Type:application/json token:eyJhbGciOiJIUzUxMiJ9.eyJyb2xlIjoiYWRtaW4iLCJ1c2VybmFtZSI6Ii9hZG1pbi8qKiJ9.8FOhRpN7DDii2YuuuXdcOU2BofwoEJ6YxCBb4k69sPCGxs9vpH9nd_cTjhvfilvS8itbiUJfgOAT3P9DtDvmiQ ``` 这时框架会调用调用该方法来获取用户信息 `userDetails`, 接下来会调用 `userDetails.getAuthorities()` 获取角色集合并获取请求的 uri 一并传递给 `TokenService.rooleMatch` 让实现者自己做权限判断。 登录请求的url 为 `/sign_in` 访问该 url 的时候会调用 `TokenService.login` 方法来获取一个token返回给前端。`TokenService.loadUserByUsername` 为 security 框架默认的方法,这里不会去使用它,所以可以只实现该方法,不必去实现其逻辑 当 spring 的 `profiles` 也就是配置 `spring.profiles.active` 为 "uat","sit","online","refresh" 时 会获取 swagger 接口注解上的信息并调用: `TokenService.saveUrlInfo()` 的方法,你可以选择直接return 也可以 将这些 url 存到数据库 用于做权限控制 ### 日志切面相关配置 当使用者实现接口 `DistributedLocker` 并注册到spring容器的时候会激活日志切面和幂等拦截。幂等拦截和日志切面使用示例: ```java @RestController @RequestMapping("/admin") public class TestController { @GetMapping("/test") @InterfaceAction(Idempotent = true,expertime = 4L) public ResultBean test(){ return ResultBean.success(); } @PostMapping("/test0") @InterfaceAction public ResultBean test0(){ return ResultBean.success(); } } ``` `@InterfaceAction` 是切面注解,当使用该注解的时候 会拦截用户请求 和请求信息打印 info 日志: ``` POSEIDON---- 2020-04-06 12:15:06 [http-nio-8080-exec-3] INFO com.muggle.poseidon.aop.RequestAspect - 请求日志 username=admin url=/admin/test0 method=POST ip=0:0:0:0:0:0:0:1 classMethod=com.fight.controller.TestController.test0 paramters=[] ``` 注解默认不开启幂等拦截,如果想开启幂等拦截需要将 `Idempotent`设置为 true,其他的幂等参数设置 `expertime` 为接口上锁时间, `message` 为幂等拦截后返回给前端的提示信息。 可能有部分开发者对用户行为日志写库的需求,我这里未做支持,如果有该需求的开发者可以自己修改 `RequestAspect` 源码。 ### 框架的基础设施 框架收录了平时使用的 utlis 类在 `com.muggle.poseidon.util` 包下,使用者可以按需修改调整。`com.muggle.poseidon.base` 包下提供了基类,基础异常和 `ResultBean` 使用者请根据实际情况按需调整 ### 统一异常处理相关配置 `com.muggle.poseidon.handler.web.WebResultHandler.WebResultHandler` 是统一异常处理类,该类定义了部分异常捕获后返回给前端的json信息,用户根据实际情况按需调整。 这里需要注意一个 异常报警功能的使用: ```java @ExceptionHandler(value = {Exception.class}) public ResultBean exceptionHandler(Exception e, HttpServletRequest req) { try { UserDetails userInfo = UserInfoUtils.getUserInfo(); ExceptionEvent exceptionEvent = new ExceptionEvent(String.format("系统异常: [ %s ] 时间戳: [%d] ", e.getMessage(),System.currentTimeMillis()), this); applicationContext.publishEvent(exceptionEvent); log.error("系统异常:" + req.getMethod() + req.getRequestURI()+" user: "+userInfo.toString() , e); return ResultBean.error("系统异常"); }catch (Exception err){ log.error("紧急!!! 严重的异常",err); return ResultBean.error("系统发生严重的错误"); } } ``` 当系统抛出无法处理的异常的时候,会发布一个事件 `ExceptionEvent` ,我们可以通过监听这个事件来实现系统报警: ```java @Component public class ExceptionListener implements ApplicationListener { @Override public void onApplicationEvent(ExceptionEvent event) { String message = event.getMessage(); // TODO 将异常信息投递到邮箱等,通知开发人员系统异常,尽快处理。 } } ``` 我们还可以添加我们自定义的异常拦截,我们需要,继承 `com.muggle.poseidon.handler.web.WebResultHandler` 然后添加我们需要的处理方法, 示例: ```java @RestControllerAdvice @Configuration public class MyWebResultHandler extends WebResultHandler { private static final Logger log = LoggerFactory.getLogger(OAwebResultHandler.class); @ExceptionHandler({ConstraintViolationException.class}) public ResultBean methodArgumentNotValidException(ConstraintViolationException e, HttpServletRequest req) { log.error("参数未通过校验", e); ResultBean error = ResultBean.error(e.getConstraintViolations().iterator().next().getMessage()); return error; } } ``` 示例中添加了一个参数校验异常处理方法。 对于其他的异常处理逻辑请阅读源码 `com.muggle.poseidon.handler.web.WebResultHandler` 类。 ### 查询组件的使用。 为了减少开发者对查询接口开发的开发时间,该框架设计了一个简单的配合 `PageHelper` 使用的插件,该功能由于构思不是很成熟, 所以其部分实现需要框架的使用者自己去完成。下面介绍该功能的使用方式和原理: 第一步注册查询拦截切面: ```java @Bean QueryAspect getQueryAspect(){ return new QueryAspect(); } ``` 第二步定义查询类: ```java public class MyQuery extends BaseQuery { @ApiModelProperty(value = "是否有效") private Boolean enable; @ApiModelProperty(value = "请求类型") private String requestType; @ApiModelProperty(value = "类名") private String className; @ApiModelProperty(value = "方法名") private String methodName; @ApiModelProperty(value = "请求路径") private String url; @ApiModelProperty(value = "描述") private String description; private String finalSql; @Override public void processSql() { Map operatorMap = this.getOperatorMap(); StringBuilder builder=new StringBuilder(); if (operatorMap!=null){ Iterator iterator = operatorMap.keySet().iterator(); while (iterator.hasNext()) { String next = iterator.next(); try { Object field=getFieldValue(next); if ((field instanceof Number)){ builder.append(next+operatorMap.get(next).getValue()+field); }else { builder.append(next+operatorMap.get(next).getValue()+"'"+field+"'"); } } catch (NoSuchFieldException | IllegalAccessException e) { throw new OAException("查询参数异常:"+next); } } } List groupBy = this.getGroupBy(); if (!CollectionUtils.isEmpty(groupBy)){ builder.append(" group by"); for (int i = 0; i < groupBy.size(); i++) { if (i==groupBy.size()-1){ builder.append(groupBy.get(i)); }else { builder.append(groupBy.get(i)+","); } } } this.finalSql=builder.toString(); } private Object getFieldValue(String next) throws NoSuchFieldException, IllegalAccessException { Field field = this.getClass().getDeclaredField(next); //打开私有访问 field.setAccessible(true); //获取属性值 return field.get(this); } @Override public String getFinalSql() { return finalSql; } public void setFinalSql(String finalSql) { this.finalSql = finalSql; } } ``` 然后在 `mybatis` 的xml中使用 `finalSql` : ```xml ``` 这个功能的设计思路是 在 `BaseQuery` 的 `orderBy` 字段为排序的list,`groupBy` 是 排序的list, `operatorMap` 是字段的运算符。 在 `QueryAspect` 的切面中它做的事情很简单,调用 `processSql()` 方法和 `init()` 方法,同时会调用 `QuerySqlProcessor` 的方法进行返回值和查询参数的自定义处理。 所以这个功能只定义最基础的骨架,其内部的具体实现还是要使用者自己去完成。 ### 日志配置 使用框架logback配置:`logging.config=classpath:poseidon-logback.xml` 具体配置信息信息在源码中有注释。 ## 框架使用及其二次开发建议 1. 权限控制: 因为不同项目对权限的管理粒度不一样,所以框架将这一部分暴露给使用者实现;关于权限的管控思路——粗粒度权限管控的可以以url的命名来简单管控如 `/admin/**` 的url 只能 admin角色访问,以此类推。 对于粒度再细一点的,可以将需要进行权限管控的url 缓存到内存,然后通过用户角色来判断是否有权限访问该url。 对于粒度更加细一点的权限控制,可以结合上面两种方法做权限管控,从url命名上约束接口是否需要权限控制,然后再将角色的url权限保存到数据库中。 2. 自动化配置扩展: 部分使用者可能希望诸如自定义的序列化器,拦截器,过滤器,监听器等也能根据项目需要实现自动化配置,我们可以在框架中加入我们自己的bean 假如我现在想自动化配置一个监听器: ```java public class ExceptionListener implements ApplicationListener { @Override public void onApplicationEvent(ExceptionEvent event) { } } ``` 我们在 `com.muggle.poseidon.auto.ExpansibilityConfig` 类中注册bean: ```java @Bean ExceptionListener listener(){ return new ExceptionListener(); } ``` 就可以让引用该starter包的spring容器中注册该监听器,或者你也可以在 `spring.factories` 加上你的类限定名: ```properties org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.muggle.poseidon.handler.web.WebResultHandler,\ com.username.lestener.ExceptionListener ``` ## 交流 框架涉及到的知识点可以上 [muggle-book](https://muggle-book.gitee.io/) 去查找,对于框架使用过程中遇到的问题或者bug 可以加作者微信反馈: 微信: b3duZXJhbmRzZWxm(base64解码后便是) 喜欢的朋友 starter 一下吧,撸码不容易