M
SAAS权限管理系统| 从产品到微服务接口的设计与实现
# 从产品到微服务接口的设计与实现64.55% 先看涉及到的表吧,提提神 - `o_app_api`:微服务接口表 - `o_ui`:前端工程表 - `o_ui_page`:前端页面表 - `o_ui_page_button`:前端页面按钮表 - `o_product`:产品表 - `o_product_menu`:顶级/互斥式产品菜单表 - `o_product_menu_relation`:共存式产品菜单表 ## 产品的需求及效果图 1. 根据业务不同划分不同的产品线; 2. 产品线中包含该相关业务的所有功能; 3. 可以出售或试用产品线中的全部或部分功能;(Plus、Max、Pro、标准版、青春版、Mini) 4. 产品中的关联功能能够分类管理,即菜单分类; 5. 功能以网页页面的形式展示,即菜单页面; 6. 菜单页面中包含多种不同的操作功能,即按钮功能; 通常产品应该画个产品效果图出来,便于直观的表达他期望的效果。 原始设计图太丑了,我这直接放出最终效果图 **产品树效果**  **产品菜单树效果**  满足产品(・ω・)需求的产品效果有了,剩下的交给苦逼的程序员 {{{ (>_<) }}} ## 产品功能的设计与实现 针对需求1,根据产品单独建个表`o_product`,记录不同的产品数据。 针对需求2,用菜单来映射功能,建立产品菜单关联表。 针对需求3,产品根据产品类型划分顶级产品和子产品(共存产品、互斥产品),子产品还能继续挂接子产品形成树 针对需求4、5,菜单分为菜单分类和菜单页面,菜单分类下挂接菜单分类或菜单页面,形成菜单树66.73% 针对需求6,增加菜单按钮类型,挂接到菜单页面下 产品树很容易解决,产品表加个父级产品代码,返回树形的数据结构就OK了 下面主要讨论产品菜单树的设计和实现 根据需求和效果图,我们将菜单树分为为菜单分类、菜单页面、菜单查询按钮和菜单操作按钮这四种基本单元。 对于前端程序员来说,为了便于开发和前端渲染,菜单树数据应该类似如下机构 ```json [{ "menuName": "产品A", "menuType": "菜单分类", "children": [{ "menuName": "菜单分类A", "menuType": "菜单分类", "childern": [{ "menuName": "菜单页面A", "menuType": "菜单页面", "childern": [ { "menuName": "菜单按钮A", "menuType": "菜单查询按钮"}, { "menuName": "菜单按钮B", "menuType": "菜单操作按钮"} ] }] }] }] ``` 菜单分类和菜单页面作为顶部和左侧菜单树的数据来源,页面内部按钮的显示隐藏由菜单按钮是否存在来控制。顶部菜单和左侧导航菜单的显示是所有产品线都公用的部分,我会单独写一篇文章来说明前端如何进行控制。 前端需要的数据结构有了,现在开始讨论菜单树如何进行构建。 传统的方式是提供一个菜单管理界面,手动新增修改删除菜单,手动配置页面的跳转地址,至于页面内的接口的访问地址,基本上不进行存储,因为没有必要,至于接口的访问权限的控制,通常是通过设置一些标志来进行控制。 做过前端的同学应该都清楚,每个前端工程里每个页面都是硬编码写出来的,它是实际存在的,只要输对正确的地址就能打开,前端页面里调用的后端接口也是实际存在的,输对正确的参数,也能正常的调用通过。 对于产品线来说,所有的实际页面和接口,这就是他的全部功能,但是对于产品来说,他需要的只是现阶段满足需求的页面和功能按钮,一些已经过时、或者弃用、或者这个时间段内暂时不能使用的功能页面和按钮,他不需要,小手在菜单管理界面一点删除,就OK了。这样长期以往会导致一个后果,弃用的前端页面和后端接口越来越多,当有一天开始进行系统优化、功能改造时,对苦逼的换了不知几届的程序员来说,想要知道代码里的这些页面和接口是否还在使用,就只能自己翻产品线和翻日志了。 从这开始,产品经理和程序员的矛盾出现了。 为了解决这个矛盾,我设计了微服务接口管理、前端工程管理这两个个功能,这两个功能不是给产品用的,是给程序员自己用的。 **微服接口管理**: 管理所有微服务的接口信息。这些接口信息都是通过微服务启动后,程序自动获取当前微服务所拥有的接口信息,并自动同步到数据表中 `o_app_api`(微服务接口表) ```java100.00% @Order(-98) // 确保最先执行 @Component public class AppApiApplicationRunner implements ApplicationRunner { @Value("${spring.application.name:default}") private String applicationName; // 获取微服务名称,避免不同微服务相同接口的情况69.44% @Autowired100.00% private RequestMappingHandlerMapping requestMappingHandlerMapping; @Autowired100.00% private syncApiClient syncApiClient; @Override100.00% public void run(ApplicationArguments args) throws Exception { log.info("初始化查询api信息!"); if ("operate".equals(applicationName)) return; MaphandlerMethods = requestMappingHandlerMapping.getHandlerMethods(); List apis = new ArrayList<>((int) Math.ceil(handlerMethods.size() / 0.75) + 1); for (Map.Entry m : handlerMethods.entrySet()) { RequestMappingInfo info = m.getKey(); HandlerMethod method = m.getValue(); ApiAnnotation apiAnnotation = method.getMethodAnnotation(ApiAnnotation.class); OperateAppApi api = new OperateAppApi(); api.setApplicationName(applicationName); api.setControllerName(NCAop.getConciseMethodName(method.getMethod().getDeclaringClass().getName())); api.setApiUrl(info.getPatternsCondition().getPatterns().iterator().next()); if (apiAnnotation != null) { api.setModularName(apiAnnotation.modularName()); api.setDescription(apiAnnotation.description()); api.setState(apiAnnotation.state()); } else {100.00% api.setModularName(api.getControllerName()); api.setDescription(api.getApiUrl()); api.setState(1); } log.info("api信息 {}", api.toString()); api.setAccessAuth("none"); apis.add(api); } syncApiClient.initApiList(apis); // 新增修改入库,旧接口删除标记 log.info("同步接口完成"); } } ``` 这时候接口数据就有了,新增修改的接口自动入库和修改,已删除的接口,标记成可删除状态,可以在微服务接口管理界面进行删除。 同时,基于这些接口信息,实现了接口访问权限设置和接口访问统计管理功能,这两个功能的实现逻辑单独写文章进行介绍。61.87% **前端工程管理**: 管理前端工程及工程拥有的菜单和按钮。这个我之前也想弄成程序自动处理收集,方法就是在前端路由生成时,将页面及路由信息主动推送给后端接口,进行入库,但是呢,这样一点安全性都没有,而且没法添加接口信息,所以还是只能老老实实手动维护了。 前端工程和页面信息都手动进行录入,页面下的接口信息调用微服务接口信息,进行选取。 数据库表 67.08% - `o_ui`:前端工程表 - `o_ui_page`:前端页面表 - `o_ui_page_button`:前端页面按钮表 **产品管理**: 现在菜单页面和菜单按钮都有了,产品需要做的事就是,在产品管理页面,按照产品需求,自己定义菜单分类,自己从前端工程里选取前端页面挂接到自己想挂到地方,自己想删哪个删哪个。63.52%