SpringMVC 接口版本管理/IP访问控制/ANT打包发布到LINUX

来源:互联网 发布:手机淘宝店开店流程 编辑:程序博客网 时间:2024/06/16 14:05

前言

最近懒了很多也忙了很多,好多东西没办法分享到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 总之要支持java8

Ant

下载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参数,通过判断不同参数,指向不同的函数或class
if(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();elsereturn new ControllerAbstractFactoryImpl(version).iController();}@Overridepublic 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并配置参数类型classfor (Method m : methods) {// 通过注解判断是否匹配访问domainif (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();// 包含则truereturn 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 stubreturn 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 { // successreturn 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

创建一个库myapi
CREATE 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><beanclass="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=truedriver=com.mysql.jdbc.Driverusername=rootpassword=123456#=========================#========  通用   ========#=========================#时间格式化标准(在不了解调用细节的情况下不建议修改)date_format=yyyy-MM-dd HH:mm:sspattern_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

原创粉丝点击