SpringMVC 接口版本管理/IP访问控制/ANT打包发布到LINUX
2017-05-25 15:54
633 查看
前言
最近懒了很多也忙了很多,好多东西没办法分享到blog,因为知识点比较杂,没有时间整理。写这篇文章主要原因是,因为遇到了同样的问题,但是网上没有很好的解决方案于是自己解决后,分享给大家
源码在csdn download
文章尾部可以下载
IOC迭代版
SpringMVC IOC DI接口版本管理(迭代版)
http://blog.csdn.net/crazyzxljing0621/article/details/76677865
(2017-8-4更新 )
概述
1.springMVC 多版本接口
2.接口有IP访问控制
3.支持jsonp
4.log发送到email
5.springMVC+mybatis
6.ANT打包通过SSH发布到linux
7.springMVC @ResponseBody乱码问题
准备工作
Java8
安装java8环境java version "1.8.0_91" Java(TM) SE Runtime Environment (build 1.8.0_91-b15) Java HotSpot(TM) 64-Bit Server VM (build 25.91-b15, mixed mode)
Tomcat 7.x +
安装tomcat7.x或tomcat8 总之要支持java8Ant
下载ant,配置ant环境变量。设置ANT_HOME,然后path追加 %ANT_HOME%/bin;
Apache Ant(TM) version 1.10.1 compiled on February 2 2017
其他
Centos 64bit有安装ant插件的eclipse
mysql
开始
多版本接口到底有何问题?
我们想提供多版本接口正常来看,获取一个version参数,通过判断不同参数,指向不同的函数或classif(version == 1) { Controller.save(); } else if(version == 2) { Controller.save(name) }高耦合不易修改,因为你要为你每个版本都提供一套if else...
按照之前网上一些文章所说我们可以在@RequestMapping中配置
@RequestMapping("/v1/xxxx") public String save1() @RequestMapping("/v2/xxxx") public String save2()
耦合依然存在只不过是把if的操作交给了@requestMapping修改起来也不符合我们的最终目的
来看看我的思路
@requestMapping ("/{domain}/{version}") class CoreController{ @requestMapping ("/**") public Object execute(@PathVariable domain,@PathVariable String version) { return null; } }
通过domain和version我们告诉抽象工厂去调用谁的 Controller
/** * 获取class * * @param version * @param domain * @return * @throws Exception */ private Class<?> selectClass(String version, String domain) throws Exception { switch (domain) { case ROOM: return ControllerAbstractFactoryImpl.instance(version).iRoomController().Class(); case USER: return ControllerAbstractFactoryImpl.instance(version).iUserController().Class(); default: return null; } }
package com.api.controller.factory; import com.api.controller.factory.inf.IController; /** * controller层抽象工厂 * @author Allen 2017年5月24日 * */ public interface IControllerAbstractFactory { IController iController() throws Exception; }
通过反射来拼classPath并得到他的实例返回给使用者
package com.api.controller.factory; import com.api.controller.factory.inf.IController; import com.api.modules.Conf; import com.api.util.exception.ApiVersionException; /** * controller层抽象工厂 * * @author Allen 2017年5月24日 * */ public class ControllerAbstractFactoryImpl implements IControllerAbstractFactory { /** class Constant **/ final StringBuilder CONTROLLER_PATH = new StringBuilder("com.api.controller.factory.inf.Controller"); private String version; private ControllerAbstractFactoryImpl(String version) { this.version = version; } private ControllerAbstractFactoryImpl() { // TODO Auto-generated constructor stub } public static IController instance(String version) throws Exception { if (!version.matches(Conf.PATTERN_COMPLIE_VERSION)) throw new ApiVersionException(); else return new ControllerAbstractFactoryImpl(version).iController(); } @Override public IController iController() throws Exception { return (IController) Class.forName(CONTROLLER_PATH.append(version).toString()).newInstance(); } }在CoreController中得到了对应版本和domain的Controller后,执行函数调用
这里通过反射来遍历目标class中所有的methods以及父级methods,寻找与我们 自定义的注释@requestAlias匹配的method并调用他,当然也是要判断我们自定义的注释
@RequestStrategyUtils,确保访问IP时策略内
/** * 执行目标函数 * * @param clazz * @param method * @return * @throws NoSuchMethodException * @throws InstantiationException * @throws SecurityException * @throws InvocationTargetException * @throws IllegalArgumentException * @throws IllegalAccessException */ private int targetMethod(Class<?> clazz, HttpServletRequest req) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException, InstantiationException { Method[] methods = clazz.getMethods(); // 找到目标method并配置参数类型class for (Method m : methods) { // 通过注解判断是否匹配访问domain if (m.isAnnotationPresent(RequestAlias.class) && m.getAnnotation(RequestAlias.class).value().equals(methodName)) { if (!ipControl(getRemoteHost(req), clazz, m)) { // ip不匹配访问权限 return -1; } result = clazz.getMethod(m.getName(), m.getParameterTypes()).invoke(clazz.newInstance(), obParam); return 0; } } return -2; }
/** * IP访问控制 * * @param request * @param response * @param handler * @return */ private boolean ipControl(String requestIp, Class<?> clazz, Method m) { if (clazz != null && m != null) { boolean isClazz = clazz.isAnnotationPresent(RequestStrategyUtils.class); boolean isMethod = m.isAnnotationPresent(RequestStrategyUtils.class); RequestStrategyUtils rc = null; // 如果方法和类声明中同时存在这个注解,那么方法中的会覆盖类中的设定。 rc = isMethod ? m.getAnnotation(RequestStrategyUtils.class) : isClazz ? clazz.getAnnotation(RequestStrategyUtils.class) : null; if (rc == null) return false; String[] value = rc.ip(); // 包含则true return Arrays.asList(value).stream().anyMatch(s -> s.equals(Conf.All_IP) || ((s.equals(Conf.LOCAL_IP) || (s.equals(Conf.DEVELOP_IP))) && s.indexOf(requestIp) != -1)); } return false; }
这里是我们的UserController1_1版本,他的父级是1_0
import com.api.modules.Conf; import com.api.service.factory.ServiceAbstractFactoryImpl; import com.api.util.annotations.RequestAlias; import com.api.util.annotations.RequestStrategyUtils; /** * 用户 * * @author Allen 2017年5月25日 * */ @RequestStrategyUtils(ip = { Conf.LOCAL_IP, Conf.DEVELOP_IP }) public class UserController1_1 extends UserController1_0 { @RequestAlias("save") public Object save(String version, String name, String alias) throws Exception { // TODO Auto-generated method stub return ServiceAbstractFactoryImpl.instance(version).iUser().save(name, alias); } }通过以上步骤锁定目标Controller并进行调用成功
ResultValue是我们封装的针对返回值组装为Vo并利用spring jackson,自动返回为json格式
if (flag == -1) {// IP访问权限不足 return ResultValue.execute(State.IPPERMISSIONS, callback); } else if (flag == -2) {// 找不到的访问域 return ResultValue.execute(State.DOMAINNOTFOUND, callback); } else if (result == null) { // 返回值为null,controller业务参数判定有无 return ResultValue.execute(State.NPE, callback); } else { // success return ResultValue.execute(State.SUCCESS, result, callback); }
result JSON:
{ state: "100", stateValue: "成功", value: [ { id: 1, name: "若风" }, { id: 2, name: "Miss&7号" }, { id: 3, name: "55开" } ] }
至此Controller多版本问题解决,并解耦。
下面来看我们如果要增加一个新的版本就简单很多
1.新建UserController1_2
2.UserController1_2 extends UserController1_1
3.UserController1_2 编写新特性或重写老版本函数
4.然后在调用的时候 url version中传入1_2就可以了
5.无需修改任何IF ELSE
再看一下如何实现的多版本接口过程
1.用户发起请求 https://127.0.0.1/MyApi/room/1_1/save/李毅/国足大帝 2.CoreController是唯一添加了 @Controller的Controller类,所以他得到了请求
3.解析得到domain为room,version为1_1 ,请求method alias save ,传入参数 李毅,国足大帝
4.通过domain 选择不同的 具体Controller
switch (domain) { case ROOM: return ControllerAbstractFactoryImpl.instance(version).iRoomController().Class(); case USER: return ControllerAbstractFactoryImpl.instance(version).iUserController().Class(); }5.通过vesion在抽象工厂中通过反射得到目标版本的 Controller
6.回到CoreController。
7.拿着version版的Controller,去反射查alias匹配的method
8.查到method后再匹配ip访问权限
9.然后入参,调用
因为Controller提供的是访问控制,代码具体体现在参数和访问上,然而我们在具体的业务中,新版本接口可能差异从Dao就开始不同了。
我的做法是Service也做了抽象工厂的版本,当然Dao层我没有做,我认为没有必要,因为耦合永远都是存在的,无论是什么设计模式还是架构,只不过是把耦合从表面上放到了桌子下面,让更多的人无需了解桌子下面的细节罢了。
UML
其他
MySql
创建一个库myapiCREATE TABLE `room` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(25) COLLATE utf8_bin NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_bin; CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(25) COLLATE utf8_bin NOT NULL, `alias` varchar(25) COLLATE utf8_bin DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COLLATE=utf8_bin; insert into `room`(`id`,`name`) values (1,'若风'),(2,'Miss&7号'),(3,'55开');
乱码问题
按照我下面的web和springmvc的xml来配就不会有问题web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>liveApi</display-name> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <servlet> <servlet-name>springDispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置Spring mvc下的配置文件的位置和名称 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <!-- 防止Spring内存溢出监听器 --> <listener> <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class> </listener> <servlet-mapping> <servlet-name>springDispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 静态资源访问 --> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.css</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.gif</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.jpg</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.js</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> <!-- 异常页面捕获 --> <error-page> <exception-type>java.lang.Throwable</exception-type> <location>/500.html</location> </error-page> <error-page> <error-code>500</error-code> <location>/500.html</location> </error-page> <error-page> <error-code>404</error-code> <location>/404.html</location> </error-page> </web-app>
springMVC.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> <!-- ====================================================== --> <!-- 配置@ResponseBody 保证返回值为UTF-8 --> <!-- 因为StringHttpMessageConverter默认是ISO8859-1 --> <!-- 用于使用@ResponseBody后返回中文避免乱码 --> <bean id="utf8Charset" class="java.nio.charset.Charset" factory-method="forName"> <constructor-arg value="UTF-8" /> </bean> <mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <constructor-arg ref="utf8Charset" /> </bean> </mvc:message-converters> </mvc:annotation-driven> <!-- 启动SpringMVC的注解功能,完成请求和注解POJO的映射 --> <bean id="mappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> </list> </property> </bean> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="messageConverters"> <list> <ref bean="mappingJacksonHttpMessageConverter" /> <!-- JSON转换器 --> </list> </property> </bean> <!-- ====================================================== --> <!-- 配置自动扫描的包 --> <context:component-scan base-package="com"></context:component-scan> <!-- ====================================================== --> </beans>
配置conf.properties
properties调用很麻烦我们在一个conf.java中与之进行匹配和初始化
#========================= #======== Mybatis ======== #========================= url=jdbc:mysql://你的ip:3306/myapi?useUnicode=true&characterEncoding=utf-8&autoReconnect=true driver=com.mysql.jdbc.Driver username=root password=123456 #========================= #======== 通用 ======== #========================= #时间格式化标准(在不了解调用细节的情况下不建议修改) date_format=yyyy-MM-dd HH:mm:ss pattern_complie_version=^[0-9]\\d*_?[0-9]*$ #================================= #======== 邮件 && 日志 ======== #================================= #邮件标题(local) local_title=我是一个标题 #邮件标题(release) release_title=我是发布后的标题 #邮件头 mail_head_title=【异常】直播服务器 #错误日志收件人 notice_mail=123@qq.com #发件箱账户 from_account=123@163.com; #发件箱 from_password=123 #发件箱smtp smtp_host=smtp.163.com
这样以后在使用中就可以直接Conf.abc这样很方便,当然你还可以扩展他,改成debug模式,增加一个配置每次都去获取他的值,如果是true则重新获取properties数据
package com.api.modules; import java.util.Properties; import com.api.util.PropertiesUtil; /** * 全局公用常量 * * @author Allen 2017年5月18日 * */ public class Conf { /************************ * 内外网访问策略 Final不可配在conf.properties Controller注解中配置策略用 **********************/ /** 本地ip **/ public final static String LOCAL_IP = "127.0.0.1"; /** 白名单机 **/ public final static String DEVELOP_IP = "172.26.106.38"; /** 全域 **/ public final static String All_IP = "*"; /************************ * 通用 **********************/ public static String DATE_FORMAT = value("date_format"); public static String PATTERN_COMPLIE_VERSION = value("pattern_complie_version"); /************************ * Log && E-Mail **********************/ public static String LOCAL_TITLE = value("local_title"); public static String RELEASE_TITLE = value("release_title"); public static String MAIL_HEAD_TITLE = value("mail_head_title"); public static String NOTICE_MAIL = value("notice_mail"); public static String FROM_ACCOUNT = value("from_account"); public static String FROM_PASSWORD = value("from_password"); public static String SMTP_HOST = value("smtp_host"); static Properties properties; static { properties = PropertiesUtil.getProperties("conf.properties"); } private static String value(String key) { if (properties == null) properties = PropertiesUtil.getProperties("conf.properties"); return properties.getProperty(key); } }
Log&Mail
这里没什么好说的得到exception堆栈日志,发送到邮箱即可,注意的是邮件标题很有可能被邮箱拒收,被认作垃圾邮件,所以有条件还是用公司自己的smtp邮件服务器好Ant
ant环境变量安装好后,执行build.xml 进行发布。记得看好配置,这个ant支持多服务器发布,所以会弹出选框。jsch-0.1.54.jar
SSH2的jar,把这个添加到ant中,Windows->preferences->ant->runtime->classpath->ant home Entries 把这个jar添加进去
org.eclipse.jdt-4.6.3.zip
org.eclipse.jdt.compiler.tool_1.1.100.v20160418-1457.jar
org.eclipse.jdt.core_3.12.3.v20170228-1205.jar
org.eclipse.jdt.debug.ui_3.7.201.v20160811-0450.jar
把zip解压缩把以上3个jar放到 ant/lib中,然后在eclipse->选中build.xml->run as -> 第二个ant build -> JRE -> select " run in the same JRE as the workspace "
总结
最后你发现你不需要维护if else ,你只需要做一个新的controller来集成父级,来写新的业务即可,其他的都不需要你考虑的看看我们用抽象工厂和反射来解决了这个看似复杂的问题
我们不需要在if(version),我们只需要extends,其他任何细节都不需要考虑,是不是美妙了许多
下载地址
http://download.csdn.net/detail/crazyzxljing0621/9852818
相关文章推荐
- 让SpringMVC支持可版本管理的Restful接口
- 免费开源接口管理平台DOClever 4.1.0 版本发布 添加管理总后台!
- 让SpringMVC支持可版本管理的Restful接口
- 让SpringMVC支持可版本管理的Restful接口
- 使用DOClever接口管理平台,使用免费开源版本线下(linux(mac))该怎么安装部署?
- 让SpringMVC支持可版本管理的Restful接口
- SpringBoot系列三:SpringBoot基本概念(统一父 pom 管理、SpringBoot 代码测试、启动注解分析、配置访问路径、使用内置对象、项目打包发布)
- SpringMVC支持可版本管理的Restful接口
- git提取出两个版本之间的差异文件并打包 linux命令行
- 本地发布Web API数据服务接口时,无法访问服务接口问题的解决
- 禅道---Linux系统下安装禅道管理工具7.2版本
- Linux中对不同版本的软件进行管理(update-alternatives)
- git linux上自动化打包发布脚本
- 权限管理:RBAC(基于角色的访问控制)SpringMVC实现
- 单域名多版本app访问接口
- (最新)IIS Express发布网站,实现IP,域名访问站点
- Linux版本 网络调试助手 发布
- (转)如何在linux C/C++语言中调用 sqlite 的函数接口来实现对数据库的管理
- svn版本管理软件——svn发布补丁和打补丁
- windows/linux VPS云服务器限制IP访问,限制别人的IP访问网站方法