M SAAS权限管理系统| 从产品到微服务接口的设计与实现
2020-11-15 20:09:16 539 庶卒 SAAS权限管理系统 版本: 1 引用率 11.03%
# 从产品到微服务接口的设计与实现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. 菜单页面中包含多种不同的操作功能,即按钮功能;



通常产品应该画个产品效果图出来,便于直观的表达他期望的效果。

原始设计图太丑了,我这直接放出最终效果图

**产品树效果**

![产品树形结构.png](/ncimg/M00/00/07/rBIgzV_qggaAFP_pAAB2n5SXcxk462.png)

**产品菜单树效果**

![产品菜单树前端体现.png](/ncimg/M00/00/07/rBIgzV_qghWADz0VAAFFEDNHpKE408.png)



满足产品(・ω・)需求的产品效果有了,剩下的交给苦逼的程序员 {{{ (>_<) }}}



## 产品功能的设计与实现

针对需求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;
        
        Map handlerMethods = 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%