huangliuyu00 2019-06-27
源码链接: https://github.com/samt007/xy...
这是用Spring Cloud微服务架构搭建的一套基于EBS的API服务系统如对本文有任何的疑问,请联系我:[email protected]
这是一篇传统ERP系统和基于Java的微服务架构有效结合的技术文档。
传统ERP关注的是企业内部的信息化管理。当ERP系统能将其服务发布出去之后(结合微服务架构),就可以很好实现与第三方系统的无缝对接,同时也可以实现扩展ERP本身的功能。
目标是:让ERP的服务更开放!
简单来说,就是:
相当于做一个中间服务平台,把ERP的功能做成Web Service与其它系统集成。
下面具体说明它的作用。
如果没有一个统一的API对接平台,那么ERP和第三方系统做对接,那会是下图所示的结构:
从上图可以看出,各个第三方系统分别和ERP做对接,无论是DBLINK还是通过自己的Web Service, 都是杂乱的,没能统一管理的。简单来说就是各自为政,为对接而需要做的事情都是零碎的。
当第三方系统越来越多的时候,那对于日常的运维将会是一个灾难的问题。举个例子,就一个简单的运维:密码修改,需要调整的地方就会很多,也很容易遗漏。
特别指出的是,接口功能的复用方面也是个难题。假设一个查询库存的接口,CRM系统和在线下单系统都可以用的,需要开发2次。
有了这套统一的API系统之后,ERP系统和别的系统之间的对接就变成了这个结构:
所以,有了它,相当于ERP的API都可以通过这服务平台给开发出去,基本上所有的接口可以完成的业务,都可以通过这套服务平台来完成。
可以实现:
举个例子:
大概这个需求:
成品入库的时候,直接可以用条码枪扫条码或者二维码就可以入库;销售出库的时候,也可以通过刷成品的条码直接进行出库。JIT管理。
通过这个系统的实现逻辑是:
通过EBS的用户名和密码可以登入条码枪上的APP系统。然后,刷条码的时候,通过该Web Service可以产生对应的事务处理!例如完工入库,处理物料搬运单等。
下面是该系统的一些截图
注意:该功能后台API由该微服务提供,前台是安卓的APP
每个企业内部都有各种第三方系统,这些系统或多或少都需要和EBS进行集成。传统的集成方法是通过DBLINK。
但是有这套Web Service系统之后,就可以统一通过该Web Service作为中介,和EBS进行数据的集成交互!
关于它的解析,网上资料很多。
这里引用某位大神的总结(引用:http://blog.51cto.com/ityoukn...),解析得比较到位:
微服务的概念源于2014年3月Martin Fowler所写的一篇文章“Microservices”。微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful
API)。每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境、类生产环境等。另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。
微服务架构优势
复杂度可控:在将应用分解的同时,规避了原本复杂度无止境的积累。每一个微服务专注于单一功能,并通过定义良好的接口清晰表述服务边界。由于体积小、复杂度低,每个微服务可由一个小规模开发团队完全掌控,易于保持高可维护性和开发效率。
独立部署:由于微服务具备独立的运行进程,所以每个微服务也可以独立部署。当某个微服务发生变更时无需编译、部署整个应用。由微服务组成的应用相当于具备一系列可并行的发布流程,使得发布更加高效,同时降低对生产环境所造成的风险,最终缩短应用交付周期。
技术选型灵活:微服务架构下,技术选型是去中心化的。每个团队可以根据自身服务的需求和行业发展的现状,自由选择最适合的技术栈。由于每个微服务相对简单,故需要对技术栈进行升级时所面临的风险就较低,甚至完全重构一个微服务也是可行的。
容错:当某一组建发生故障时,在单一进程的传统架构下,故障很有可能在进程内扩散,形成应用全局性的不可用。在微服务架构下,故障会被隔离在单个服务中。若设计良好,其他服务可通过重试、平稳退化等机制实现应用层面的容错。
扩展:单块架构应用也可以实现横向扩展,就是将整个应用完整的复制到不同的节点。当应用的不同组件在扩展需求上存在差异时,微服务架构便体现出其灵活性,因为每个服务可以根据实际需求独立进行扩展。
从上面的解析得知:微服务是一种技术架构,将一个庞大的服务体系拆分为若干个子服务执行。
问题来了,应该如何拆分呢?就是服务的拆分原则是什么。
这个问题就像是一个大表如何进行分区一样,其实我觉得是具体问题具体分析。
由于我开发的是基于EBS的微服务系统,正常来说,比较合理的划分规则应该是以EBS的模块来分。
相当于每个模块都划分为一个单独的微服务。例如FND模块,INV模块,WIP模块等等。
有时候,为了某个目的,可能有些功能是定制的,需要提取几个模块的数据来用,而且被别的模块重用的概率很低。所以,实际上也可以以定制的功能来划分微服务。
目前来说,该系统包括2个子服务:
这个是整个微服务API的核心ald模块。这个模块的主要功能是验证用户的登录,为所有的api模块提供统一的token认证。相当于ebs的FND模块。
这个项目是属于微服务的API模块之一:条码管理系统提供数据以及数据处理的API。
主要是为条形码传输系统用。当然,未来可以添加若干个服务。微服务架构的优势是有很好的横向扩展能力!
该系统的架构图如下所示。
注意:
1.Spring Cloud模块中,实际上Spring Security并不是单独的一个模块,而是融入到每一个业务微服务模块中! 每个微服务都必须要有token认证才允许访问API,它非常重要! 所以我将它给列到Spring Cloud模块中。
2.图中有些模块目前还没有实现。
目前实现了架构整体,包括以下的服务(下一个章节会具体说明每个模块的用途):
xygerp-ald
xygerp-albc
xygerp-server-eureka
xygerp-server-zuul
注意: 以后会按需添加别的模块。
接下来是一步一步来开发一套这个基于Spring Cloud的微服务系统。
开发系统都必须要打好基础。所以,这里列出了开发基于Spring Cloud的微服务系统需要掌握的开发技术。
下面我不会具体解说每一个开发技术如何学习,因为这并不是本文的重点。工欲善其事必先利其器,基础还是必须要打好。
必须要熟悉java,否则基本不用看文档了。先打好基础吧!
系统开发的项目都是以maven做项目管理的,所以必须要先安装并掌握maven工具。
数据库端的开发技术。这里选用Oracle数据库,因为EBS就是基于Oracle数据库的ERP系统。
Java技术发展到现在,已经出现了许多非常优秀的开源框架,我们可以借助这些框架来快速开发系统。
Spring是目前开源的主流的技术包。
该系统主要用到的技术栈是:Spring boot,Spring Security以及Spring Cloud。
系统基于SpringBoot快速开发。选择目前热度很高的SpringBoot,最大限度地降低配置复杂度,把大量的精力投入到业务开发中来。
利用Spring MVC框架处理所有的url请求,简单易用。
Spring security是一个强大的和高度可定制的身份验证和访问控制框架。它是确保基于Spring的应用程序的标准。
这里主要是用Spring Security框架处理Token机制。
Spring Cloud是一系列框架的有序集合。
它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
注意:本系统目前使用Spring Cloud的2个模块
ORM框架选用MyBatis。
主要是考虑到它能够很好支持SQL语句:MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。
另外,还用到了MyBatis的一些提高开发效率的插件,特别是通用Mapper和PageHelper分页插件!
DRUID是阿里巴巴开源平台上一个数据库连接池实现。它结合了C3P0、DBCP、PROXOOL等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况。
前端和后端的唯一联系,变成了API接口。
API文档变成了前后端开发人员联系的纽带,变得越来越重要,swagger就是一款让你更好的书写API文档的框架。
目前用Redis的主要作用是存取token,以配合实现Spring Security完成api访问的安全机制。
以后可以考虑做缓存或者消息队列等高级功能。
基于Docker的容器化部署。
由于使用了微服务架构后,我们的系统将会由很多子系统构成。
为了达到多个系统之间环境隔离的目的,我们可以将它们部署在多台服务器上,可这样的成本会比较高,而且每台服务器的性能可能都没有充分利用起来。
所以我们很自然地想到了虚拟机,在同一台服务器上运行多个虚拟机,从而实现环境的隔离,每个虚拟机上运行独立的服务。
然而虚拟机的隔离成本依旧很高,因为它需要占用服务器较多的硬件资源和软件资源。
所以,在微服务结构下,要实现服务环境的隔离,Docker是最佳选择。它比虚拟机更加轻量级,占用资源较少,而且能够实现快速部署。
备注:后面有专题说明这个工具如何安装使用。由于篇幅原因,本文档暂时不讲解容器化部署。
Jenkins自动化构建工具。
当我们采用了微服务架构后,我们会发现这样一个问题。整个系统由许许多多的服务构成,这些服务都需要运行在单独的容器中,那么每次发布的复杂度将非常高。
首先你要搞清楚这些服务之间的依赖关系、启动的先后顺序,然后再将多个子系统挨个编译、打包、发布。这些操作技术难度低,却又容易出错。
那么有什么工具能够帮助我们解决这些问题呢?答案就是——Jenkins。
它是一款自动化构建的工具,简单的来说,就是我们只需要在它的界面上按一个按钮,就可以实现上述一系列复杂的过程。
备注:后面有专题说明这个工具如何安装使用。由于篇幅原因,本文档暂时不讲解自动化构建。
现在开始手把手来搭建一套这样子的系统。
先创建一个微服务系统的父级项目:xygerp-api
再在这个项目下面分别创建下面几个子项目:
项目名称 | 说明 |
---|---|
xygerp-ald | ald模块,端口:8180。这个是整个微服务API的核心ald模块。这个模块的主要功能是验证用户的登录,为所有的api模块提供统一的token认证。相当于ebs的FND模块。 |
xygerp-albc | albc子模块,端口:8181。这个项目是属于微服务的API模块之一:条码管理系统提供数据以及数据处理的API。主要是为条形码传输系统用。 |
xygerp-comm | comm模块这个项目是所有API项目的核心依赖项目。说白了就是将API微服务架构的所有项目的公用代码可以抽取在这里。 |
xygerp-basic-support | 核心基础支撑模块这个项目是所有API项目的基础数据支撑项目。这里统一归集了所有的Entity!因为对于Entity来说,应该是整个微服务都公用的。 |
xygerp-server-eureka | Spring Cloud的服务与发现的服务中心。端口:8101。这个模块是Spring cloud的最核心的模块了,用来处理各个微服务之间的服务调用的。 |
xygerp-server-zuul | Spring Cloud服务网关。端口:8102。在Spring Cloud架构体系内的所有微服务都通过Zuul来对外提供统一的访问入口,所有需要和微服务架构内部服务进行通讯的请求都走统一网关。 |
注意:
关于xygerp-basic-support:核心基础支撑模块可能您会有疑问:为什么不将entity归并在它所属的模块?其实是这样的,我主要是考虑到服务之间的互相调用的问题。
微服务虽然客观上是一个单独的服务,但是,实际上大部分的功能肯定是互相调用的。举个例子,销售订单模块调用库存模块的功能查询个库存是很正常的业务吧?
如果entity不共用的话,相当于销售模块得到的库存模块的结果无法归集为bean来处理,这样子对于后台的处理会带来极大的不便!
接着需要通过pom文件来指定它们之间的依赖关系,依赖关系如下图所示。
注意,上面的4个项目是有依赖关系的。
所以,xygerp-ald和xygerp-albc部署方式 改为war部署 (主要是为了利用jenkins进行自动化部署)。
而xygerp-comm和xygerp-basic-support只是为各个微服务提供基础代码支撑,所以是jar部署即可。
此外,为了简化各个模块的配置,我们将所有模块的通用依赖放在Project的pom文件中,然后让所有模块作为Project的子模块。
这样子模块就可以从父模块中继承所有的依赖,而不需要自己再配置了。
在父pom中指定子模块
modules标签指定了当前模块的子模块是谁,但是仅在父模块的pom文件中指定子模块还不够,还需要在子模块的pom文件中指定父模块是谁。
<modules> <module>xygerp-comm</module> <!--核心Comm模块 --> <module>xygerp-basic-support</module> <!--核心基础支撑模块 --> <module>xygerp-server-eureka</module> <!--Eureka服务治理模块 --> <module>xygerp-server-zuul</module> <!--zuul动态路由模块 --> <module>xygerp-ald</module> <!--核心ald模块 --> <module>xygerp-albc</module> <!--子模块:albc模块,提供条码对接API服务 --> </modules>
需要在子模块中指定父模块
<parent> <artifactId>xygerp</artifactId> <groupId>com.xygerp</groupId> <version>1.0-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent>
备注:具体代码直接看源码吧。这里只是提及了一些重点设置而已。
所以,到此为止,模块的依赖关系配置完毕!但要注意模块打包的顺序。
由于所有模块都依赖于xygerp-comm模块和xygerp-basic-support模块,因此在构建模块时,首先需要编译、打包、安装xygerp-comm模块和xygerp-basic-support模块,将它打包进本地仓库中,这样上层模块才能引用到。当该模块安装完毕后,再构建上层模块。
否则在构建上层模块的时候会出现找不到xygerp-comm模块中类库的问题。
Tips: 其实,如果是在父级目录直接用mvn package整体打包的话,那打包构建的顺序在父pom中是直接指定了!
xygerp-server-eureka:Spring Cloud服务注册和发现。就是处理服务之间的治理。
xygerp-server-zuul:Spring Cloud的统一API网关服务。
Tips: 这2个项目是为了实现微服务架构而用到的核心服务。所以,它们是相对独立的。不需要依赖父pom。
上面的项目都建立好之后,再添加所有项目都需要用到的依赖(具体代码可以参考我的源码)。
都没问题的话,就可以用mvn命令进行打包项目了:
mvn clean install -Dmaven.test.skip=true -P dev
这里简单解析一下指令:mvn:Maven的统一指令。
clean install:表示要构建该项目。
-Dmaven.test.skip=true:表示构建的时候要跳过测试模块。
-P dev:表示构建的时候,启用 dev 的Spring boot参数运行系统。
如果一切都OK,那正常的结果如下:
代码搞定了,接下来需要考虑的事情应该是如何测试。
毕竟所有的系统都必须要经过测试,特别是这种配置多,涉及范围广的系统。
这个就不得不说一下Spring boot的优势了。Spring boot的打包应用默认内置了tomcat服务。
换句话说,只需要java命令执行一下Spring boot打包的target结果,就可以启动一个tomcat服务啦!真挺方便测试的!
假设我的xygerp-api项目在这里:D:JSP_MyEclipsexygerp-api
然后分别打开4个cmd命令窗口,执行:
D:\JSP_MyEclipse\xygerp-api\xygerp-server-eureka\target>java -jar xygerp-server-eureka-1.0-SNAPSHOT.war D:\JSP_MyEclipse\xygerp-api\xygerp-server-zuul\target>java -jar xygerp-server-zuul-1.0-SNAPSHOT.war D:\JSP_MyEclipse\xygerp-api\xygerp-ald\target>java -jar xygerp-ald-1.0-SNAPSHOT.war D:\JSP_MyEclipse\xygerp-api\xygerp-albc\target>java -jar xygerp-albc-1.0-SNAPSHOT.war
如下图:
本地测试环境的服务启动起来了,接着就是进行具体的数据测试。
首先测试Eureka的服务注册以及发现,确认服务是否都已经注册到系统中:
然后,用swagger测试用户登录的功能:
http://127.0.0.1:8102/xygerp/ald/swagger-ui.html
目前是测试是否可以正常产生token。
说明已经登录成功,并且产生了本次访问的token!
将token记录下来,接着测试。
继续测试一个查询的功能:
http://127.0.0.1:8102/xygerp/albc/swagger-ui.html
注意,这里用了Spring Security框架,所以的API请求头都必须携带token信息。否则请求会返回401。
如果测试OK,那说明基本上系统已经成功搭建好了。
下一步就是如何在测试环境或者正式环境部署它,以及如何一键构建项目的问题了。
简单来说,系统的部署是用 docker工具 ,一键部署项目用的是 Jenkins工具。后面将会用专题来说明这2个工具的使用。
关于这个问题,目前我用的办法可能不一定是最优的,如果有别的兄台有更好的解决办法,请留言给我,十分感谢!
问题来源:
熟悉EBS开发的兄台都应该知道,登录ERP之后,我们每次打开Form,系统就会申请一个新的数据库Session,这时候,EBS系统会 自动帮我们初始化该Session的环境变量 :例如基本的语言环境,用户环境,业务实体等等。
这时候,我们在包里面可以直接用FND_GLOBAL.USER_ID之类的函数就可以非常方便获取环境变量的信息。
但是,在Java Web开发里面就不一样了!
在Java访问数据库的理念中,Session的申请是一个极耗资源的动作!所以,大部分连接数据库的Java软件都提出了一个 数据库连接池 的概念(例如DRUID数据库连接池)。简单来说就是session共用!
Session公用就会带来一个并发问题:A用户使用系统,并初始化了该Session的环境变量为A用户;当A用户不用系统的时候,Session会闲置并放回连接池里面等待别的用户使用。
这时候如果B的用户很可能会使用该Session,如果不重新初始化环境变量的话,那B用户使用系统的Session的环境变量还是A用户,就会导致数据的bug!
如何处理该问题是开发该系统碰到的一个难题。
问题解决:
我目前的处理办法是:在Service层,用AOP统一自动监控Service层的这个参数AuthUser user
只要在Service层将参数AuthUser user放在最后,AOP会自动初始化Session的环境变量。(需要注意的是,我这个系统的数据库Transaction在Service层启用!)
另外,语言环境变量,登录ID环境变量等,会一并自动初始化。因为AuthUser会携带该定义!
核心处理代码如下:
private static final String SQL_GLOBAL_INIT = " DECLARE " + " L_session_id NUMBER;L_user_id NUMBER;L_login_id NUMBER;L_LANG VARCHAR2(10); " + " BEGIN " + " L_user_id:=:P_USER_ID; L_login_id:=:P_LOGIN_ID; L_LANG:=:P_LANG;" + " APPS.fnd_global.INITIALIZE(" + " session_id=>L_session_id, user_id =>L_user_id, resp_id =>NULL, " + " resp_appl_id=>NULL, security_group_id=>NULL, site_id=>NULL, login_id =>L_login_id, " + " conc_login_id=>NULL, prog_appl_id=>NULL, conc_program_id=>NULL, conc_request_id=>NULL, " + " conc_priority_request=>NULL" + " ); " + " IF NVL(L_LANG,'US') <> USERENV('LANG') THEN " + " IF L_LANG='ZHS' THEN " + " APPS.fnd_global.set_nls_context(p_nls_language => 'SIMPLIFIED CHINESE'); " + " ELSE " + " APPS.fnd_global.set_nls_context(p_nls_language => 'AMERICAN'); " + " END IF;" + " END IF;" + " END; "; /*** * service层调用之前先自动初始化环境变量 * 需要注意的是,用户变量必须的参数放在最后! * 只要在Service层将参数AuthUser user放在最后,AOP会自动初始化Session的环境变量。 * @throws Exception */ @SuppressWarnings("static-access") @Before("execution(* com.jebms.*.service..*.*(..)) && args(..,user)") public void oracleDBInit(JoinPoint joinPoint,AuthUser user) throws Exception{ Long dbLoginId=devDao.getJdbcTemplate().queryForObject("SELECT FND_GLOBAL.LOGIN_ID FROM DUAL", Long.class); if(user.getLoginId()!=null&&user.getLoginId()>0&&!user.getLoginId().equals(dbLoginId)){ Map<String,Object> inParamMap=new HashMap<String,Object>(); inParamMap.put("P_USER_ID", user.getUserId()); inParamMap.put("P_LOGIN_ID", user.getLoginId()); inParamMap.put("P_LANG", user.getLanguage()); devDao.getDevJdbcTemplate().execute(this.SQL_GLOBAL_INIT, inParamMap); } }
源代码在:com.jebms.comm.utils. AopUtil
问题描述:
由于我这个是第三方的API系统,所以,用户名和密码信息实际上并不是该API系统需要管理的事情。
相当于说,API系统无法按照正常的流程来验证用户名和密码:输入用户名和密码,系统验证后台数据库的用户名和密码,再返回验证结果。
而是:输入用户名和密码,系统 调用ERP的用户名密码验证包 进行验证,再返回结果。
简单来说:需要添加自定义验证的逻辑。
还好Spring Security框架支持灵活的验证逻辑。
添加步骤:
首先,写一个自定义验证的类:MyAuthenticationProvider接着,在Spring Security框架的定义中,添加这个自定义的验证。
AbstractWebSecurityConfig
private MyAuthenticationProvider provider;//自定义验证
auth.authenticationProvider(provider);
即可以完美实现这个效果
核心代码:
/** * 自定义验证方式 */ @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = (String) authentication.getCredentials(); AuthUser user = (AuthUser) userService.loadUserByUsername(username); System.out.println("username:"+username+",password:"+password); if(user == null){ throw new BadCredentialsException("Username not found."); } //加密过程在这里体现 if (!sysService.xygErpValidateLogin(username, password)) { throw new BadCredentialsException("Wrong password."); } user.setPassword(passwordEncoder.encode(password)); Collection<? extends GrantedAuthority> authorities = user.getAuthorities(); return new UsernamePasswordAuthenticationToken(user, password, authorities); }
1.Entity基类封装。
封装了5who栏位,以及类似Form的FND_SET_WHO的方法,可以很方便进行开发。
另外,为了防止丢失更新,这边每次更新前实际上会先检测数据的一致性,对应的动作也有做了封装。
2.查询逻辑的封装。
查询功能相对来说还是会很多,对于复杂的查询条件如何传值是一个难题。
这里封装了一个SearchInfo积累,可以统一将所有的查询条件都放在这个类,然后在java的Controller层定义好查询条件对应匹配栏位,系统就可以自动产生对应的and条件。
例如:
@GetMapping(value = "/getPageLocator") @ApiOperation(value = "货位分页列表接口") public ResultEntity<PageInfo<EslipLocatorRE>> getPageLocator( @ApiParam(value = "库存组织ID",required = true) @RequestParam(required = true) int organizationId, @ApiParam(value = "库别代码",required = true) @RequestParam(required = true) String subinventoryCode, @ApiParam(value = "货位代码",required = false) @RequestParam(required = false) String locatorCode, SearchInfo searchInfo) throws Exception { searchInfo.getConditionMap().put("organizationId", organizationId); searchInfo.getConditionMap().put("subinventoryCode", subinventoryCode); searchInfo.getConditionMap().put("locatorCode", locatorCode); searchInfo.setAuthUser(this.authUser); searchInfo.initSqlCondition(); searchInfo.andSqlCondition("MIL.ORGANIZATION_ID","organizationId"); searchInfo.andSqlCondition("MIL.SUBINVENTORY_CODE","subinventoryCode"); searchInfo.andSqlCondition("MIL.SEGMENT1","locatorCode"); return eslipService.selectForPageLocator(searchInfo); }
3.统一的处理结果的封装。
基本上任何一个处理,要不成功,要不失败(警告其实也算失败)。
这里封装了一个返回结果的基类ResultEntity<T>,可以进行有效的应用端或者java端的交互。
@ApiModelProperty(value = "状态码,0表示成功 其他表示失败", example = "0",position = 1) private String code;
特别需要指出的是,前端获取或者处理数据,也是统一要用这个处理结果基类的返回。
简单来说,就是数据处理成功/失败,会有一个统一的返回结果标识。注意,这个标识和请求的响应结果标识(200)是有所不同的!
请求响应标识只是说明web服务器的响应是正常,但,具体的处理结果可能是处理失败。
下面是一个具体的例子(到时候实际开发处理的接口处理结果也是这样子):
{ "code": "0", "message": "", "description": "", "obj": [{ "createdBy": -1, "creationDate": "2017-10-10 09:37:03", "lastUpdatedBy": 10, "lastUpdateDate": "2017-11-16 14:47:48", "lastUpdateLogin": 96, "valueUUID": null, "id": 2, "applId": 1, "respCode": "BASIC_SET", "menuId": 2, "startDate": "2017-10-10 09:37:03", "endDate": null, "respName": "系统设置职责", "description": "系统设置职责", "menuCode": "SYSTEM_SET", "menuName": "系统设置菜单", "enabled": true }], "param1": null, "param2": null, "param3": null, "param4": null, "param5": null, "ok": true }
文档参考链接:
https://juejin.im/entry/5a781...
http://blog.51cto.com/ityoukn...