分类存档: 修炼之旅

android ant脚本批量打包-依赖别的项目

一个下午都在修改原来的android打包脚本,晚上仔细看了官网文档半小时搞完。╮(╯▽╰)╭

主要问题在于自己的android工程还依赖于第三方的一个工程library。网上不少例子都是完全重写apk的打包过程,不是不好。就是有点啰嗦不好维护。简单的对于依赖第三方的library工程也没说清除。google了这方面资料不多,我也傻劲东查查西找找折腾了一个下午。早应该想到老外如果没有专门讨论这块,就说明官网文档已经说的太明白了,不需要讨论了。

android ant打包说明:http://developer.android.com/tools/projects/projects-cmdline.html

简单说下,在贴自己的脚本。主工程(自己的工程)、第三方library工程这样有2个工程。意味着要写2个build.xml

1. library工程. 在当前路径上,创建一个TARGET为8的lib工程

android update lib-project --target 8 --path .

2. 主工程. 在当前路径上,创建一个名为XXXXXXBuilder,TARGET为8的工程(eg 可以用android list targets 命令查看所有的target),依赖的library相对或绝对路径

android update project --name XXXXXBuilder --target 8 --path . --library ../xxxx

另外,没必要像文档离说的那样local.properties和ant.properties要分开写。把所有配置文件写在一个里面也就够了。不然维护起来也不方便

解决了上面问题,多渠道打包就简单了。直接贴xml

 

<?xml version="1.0" encoding="UTF-8"?>
<project name="xxxxBuilder" default="deploy">

    <taskdef resource="net/sf/antcontrib/antlib.xml" >
        <classpath>
            <pathelement location="${basedir}/packages/antcontrib.jar" />
        </classpath>
    </taskdef>

   ......省略

    <target name="deploy" >
        <foreach
            delimiter=","
            list="${market_channels}"
            param="channel"
            target="modify_manifest" >
        </foreach>
    </target>

    <target name="modify_manifest" >

        <echo message="=============${channel}==============" />

        <!-- AndroidManifest.xml 's replace -->
        <replaceregexp byline="false" flags="g" encoding="UTF-8" >
            <regexp pattern='android:name="UMENG_CHANNEL".*android:value="(.*)"' />
            <substitution expression='android:name="UMENG_CHANNEL" android:value="${channel}_${app_version_name}"' />
            <fileset dir="" includes="AndroidManifest.xml" />
        </replaceregexp>
        <replaceregexp byline="false" flags="g" encoding="UTF-8" >
            <regexp pattern='android:versionCode="(\d+)"' />
            <substitution expression='android:versionCode="${app_vcode}"' />
            <fileset dir="" includes="AndroidManifest.xml" />
        </replaceregexp>
        <replaceregexp byline="false" flags="g" encoding="UTF-8" >
            <regexp pattern='android:versionName="(\d+)"' />
            <substitution expression='android:versionName="${app_version_name}"' />
            <fileset dir="" includes="AndroidManifest.xml" />
        </replaceregexp>  

        <!-- Constant.java 's replace -->
        <replaceregexp byline="false" flags="g" encoding="UTF-8" >
            <regexp pattern='APPVERVALUE.*=.*"(.*)";' />
            <substitution expression='APPVERVALUE = "${app_version}";' />
            <fileset dir="${basedir}/src/com/xxx/android/xxxx/tools" includes="Constant.java" />
        </replaceregexp>  
        <replaceregexp byline="false" flags="g" encoding="UTF-8" >
            <regexp pattern='CLIENT_VERVALUE.*=.*"(.*)";' />
            <substitution expression='CLIENT_VERVALUE = "${app_version}";' />
            <fileset dir="${basedir}/src/com/xxx/android/xxxx/tools" includes="Constant.java" />
        </replaceregexp>
        <replaceregexp byline="false" flags="g" encoding="UTF-8" >
            <regexp pattern='PROMOTION_IDV_ALUE.*=.*"(.*)";' />
            <substitution expression='PROMOTION_IDV_ALUE = "${channel}_${app_version_name}";' />
            <fileset dir="${basedir}/src/com/xxx/android/xxxx/tools" includes="Constant.java" />
        </replaceregexp>

        <antcall target="release" />

        <copy tofile="${gos.path}/${channel}_${app_version_name}.apk" >
            <fileset dir="${basedir}/bin"  includes="xxxxBuilder-release.apk" />
        </copy>

        <delete includeEmptyDirs="true" >
            <fileset dir="${basedir}/bin" includes="**/*" />
        </delete>

    </target>

</project>

结束

mac码农新机攻略

1. 终端与VIM
http://equation85.github.com/blog/customize-terminal-on-mac/

2. 快速安装工具macport
http://www.macports.org/

3. git安装

$ sudo port selfupdate
$ sudo port install git-core

或者参考:http://blog.maxiang.net/install-git-on-mac/63/

必备软件:

  1. QQ
  2. 迅雷
  3. Xcode4
  4. eclipse
  5. jdk1.6
  6. iterm2

 

项目小结:”逛”频道

guang.mbaobao.com 上线

麦包包2012年新增板块之一

一些好玩的地方:

  1.  流式布局, 感谢@麦包包莴苣 同学努力
  2.  爱搭配页面,采用了@麦包包蜂虎 的标签系统,为精确选包提供保证

一些不足之处:

  1. 开发过程中界面上太多的细节被忽略,最不可饶恕的是,在代码截止前,vm模版里面还有mbaobao.test这种css,没被变量替换。不及时发现,上线后样式丢失就糗大了。
  2. 一开始使用fastjson,中途为了统一其他项目风格,使用gson。但因为对fastjson和gson序列化的区别不是太了解,切换的适合时候有造成了点小问题。
    fastjson依赖method序列化。gosn则是filed序列化。即用gson的适合,get方法里面玩些东西不起啥作用 

感谢 @麦朵儿 的需求,让我有幸开发这个精美的web , 感谢@麦包包-双竹 @麦包包包罗 提供百分点推荐插件 感谢@麦包包-角蒿 提供公共头尾部  , 感谢@麦包包-尖苏 的信任 , 好基友就不写了。

项目小结:会员俱乐部

vip.mbaobao.com 上线

一些好玩的地方:

  1.  非埋点。不埋点理由是因为vip将来会有很多小的任务,遍布麦包包网站各个角落,如果每个角落里都写段代码不说管理,自己看着也变扭。
  2. 向运营的@青木同志学到不少会员等级的东西。

一些不足之处:

  1. 老问题,项目有延期。原计划6月18日上线。一直拖到7月3日。虽然其中更重要的事情填入,但超时已经是事实。
  2. 较大失误一枚。用户等级数据重新刷了2遍。虽然没有损失,但毕竟客户资料比较重要。事先可以想的更齐全一点。
  3. 用户签到增加M值,存在bug。已修正
有成长,有未来

Java RSA加密

	private String encrypt(String encodeString) {
		//空字符串不加密
		if (StringUtil.isEmpty(encodeString)) {
			return "";
		}
		try {
			byte[] modulusBytes = new BASE64Decoder().decodeBuffer(PropertiesHelp.getProperty(
				configPath, "encrypt.modulus"));
			byte[] exponentBytes = new BASE64Decoder().decodeBuffer(PropertiesHelp.getProperty(
				configPath, "encrypt.exponent"));
			BigInteger modulus = new BigInteger(1, modulusBytes);
			BigInteger exponent = new BigInteger(1, exponentBytes);

			RSAPublicKeySpec rsaPubKey = new RSAPublicKeySpec(modulus, exponent);
			KeyFactory fact = KeyFactory.getInstance("RSA");
			PublicKey pubKey = fact.generatePublic(rsaPubKey);

			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.ENCRYPT_MODE, pubKey);

			byte[] bytes = encodeString.getBytes();
			byte[] encodedByteArray = new byte[] {};
			for (int i = 0; i < bytes.length; i += 102) {
				byte[] subarray = ArrayUtils.subarray(bytes, i, i + 102);
				byte[] doFinal = cipher.doFinal(subarray);
				encodedByteArray = ArrayUtils.addAll(encodedByteArray, doFinal);
			}

			return new BASE64Encoder().encode(encodedByteArray);
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
		return null;
	}

 

Java WebSocket 开发 Webbit

webbit是基于netty扩展的websocket工具。可以大大简化websocket开发。

项目地址:https://github.com/webbit/webbit

使用说明:https://github.com/webbit/webbit/blob/master/README.md

本文权当翻译,高手直接进上面链接

一些题外话:

  1. websocket和我一开始想象的TCP应用不同。websocket和传统意义上的socket通信不一样。本质上还是HTTP的扩展。
  2. websocket协议目前还没有定稿。目前主要有3个版本的协议在使用。且都是草案。webbit都实现了3个草案。具体参阅维基http://en.wikipedia.org/wiki/WebSocket

快速开始

Maven配置

<dependency>
	<groupId>org.webbitserver</groupId>
	<artifactId>webbit</artifactId>
	<version>0.4.7</version>
</dependency>

 配置端口8080.并配置websocket路径/socket的handler

public class WebSocketServer{

	public static void main(String[] args) {
		WebServer webServer = WebServers.createWebServer(8080)
	    .add(new StaticFileHandler("/socket"));
		webServer.start();
	}

}

 编写/socket handler

public class WebSocketHandler  extends BaseWebSocketHandler{

    private int connectionCount;

    public void onOpen(WebSocketConnection connection) {
        connection.send("Hello! There are " + connectionCount + " other connections active");
        connectionCount++;
    }

    public void onClose(WebSocketConnection connection) {
        connectionCount--;
    }

    public void onMessage(WebSocketConnection connection, String message) {
        connection.send(message.toUpperCase()); // echo back message in upper case
    }

}

到此为止。基本代码已经都好了。感觉更写个servlet一样方便。

下面是客户端代码:

<html>
  <body>

    <!-- Send text to websocket -->
    <input id="userInput" type="text">
    <button onclick="ws.send(document.getElementById('userInput').value)">Send</button>

    <!-- Results -->
    <div id="message"></div>

    <script>
      function showMessage(text) {
        document.getElementById('message').innerHTML = text;
      }

      var ws = new WebSocket('ws://' + document.location.host + '/hellowebsocket');
      showMessage('Connecting...');
      ws.onopen = function() { showMessage('Connected!'); };
      ws.onclose = function() { showMessage('Lost connection'); };
      ws.onmessage = function(msg) { showMessage(msg.data); };
    </script>
  </body>
</html>

到此为止。websocket基本功能都已经实现。

 

 

netty+flash xmlsocket 在线聊天室

这几天公司做了个简单的web im聊天室。麦包包晒包频道 右下角的“包打听”。

采用netty 做socket server + flash client. 通讯采用自定义的JSON文本。

在线IM初步的探索,现阶段做的比较简单。

目前就部署1台服务器。没有考虑多机通信。没有涉及通信队列。

总共代码不到1000行。netty的确强大。

关键代码分享:

无话可说的代码,netty通用设置。

public class ChatRoomNioServer {

	private static final Logger		logger		= Logger.getLogger(ChatRoomNioServer.class);

	private static final Integer	SERVER_PORT	= Integer.parseInt(PropertiesHelp
													.getProperty("chat.server.port"));
	public void start() {
		logger.info("chatroom init...");
		ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
			Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
		bootstrap.setPipelineFactory(new ChatRoomServerPipelineFactory());

		bootstrap.setOption("child.tcpNoDelay", true);
		bootstrap.setOption("child.keepAlive", true);
		bootstrap.setOption("reuseAddress", true);
		bootstrap.bind(new InetSocketAddress(SERVER_PORT));
		logger.info("chatroom running...");
	}

}

由于采用JSON协议。

上行通道:

  1. DelimiterBasedFrameDecoder 读取缓存时,已\0\r\n 为中止符
  2. StringDecoder 二进制转字符串进行UTF-8解码
  3. StringEncoder 字符串UTF-8编码

下行通道:

  1. MessageHandler 业务逻辑处理
  2. MessageEncoder JSON转字节流打入下行通道
public class ChatRoomServerPipelineFactory implements ChannelPipelineFactory {

	@Override
	public ChannelPipeline getPipeline() throws Exception {
		ChannelPipeline pipeline = Channels.pipeline();
		pipeline
			.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.zeroDelimiter()));
		pipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8));
		pipeline.addLast("stringEncoder", new StringEncoder(CharsetUtil.UTF_8));
		pipeline.addLast("messageHandler", new MessageHandler());
		pipeline.addLast("encoder", new MessageEncoder());
		return pipeline;
	}

}

netty没有提供\0截取。不过重写部分代码即可。

public class Delimiters {

	public static ChannelBuffer[] zeroDelimiter() {
		return new ChannelBuffer[] { ChannelBuffers.wrappedBuffer(new byte[] { '\0' }),
				ChannelBuffers.wrappedBuffer(new byte[] { '\r', '\n' }) };
	}

	private Delimiters() {

	}
}

逻辑处理:

  1. 处理flash 的policy file request
  2. 处理业务逻辑
public class MessageHandler extends SimpleChannelUpstreamHandler {

	private static final Logger	logger			= Logger.getLogger(MessageHandler.class);

	private static final String	POLICY_REQUEST	= "<policy-file-request/>";

	private static final String	POLICY_XML		= "<?xml version=\"1.0\"?><cross-domain-policy><site-control permitted-cross-domain-policies=\"master-only\"/><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>";

	@Override
	public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		super.channelConnected(ctx, e);
		ChatService.initConnection(e.getChannel());
		logger.info("one user connection server, left " + ChatUserService.getOnlineUserCount()
					+ " users");
	}

	@Override
	public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		super.channelConnected(ctx, e);
		ChatService.closedConnection(e.getChannel());
		logger
			.info("one user left server, left " + ChatUserService.getOnlineUserCount() + " users");
	}

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
		String msg = (String) e.getMessage();
		if (msg.equalsIgnoreCase(POLICY_REQUEST)) {
			e.getChannel().write(POLICY_XML + "\0");
			e.getChannel().close();
		} else {
			try {
				MessageDispatchHandler messageDispatchHandler = new MessageDispatchHandler(e);
				messageDispatchHandler.setListener(new ChatRoomListenerImpl());
				messageDispatchHandler.dispatch();
			} catch (Exception ex) {
				logger.error("message handler error", ex);
			}
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
		logger.warn("Unexpected exception from downstream.", e.getCause());
		e.getChannel().close();
	}
}

具体业务在    MessageDispatchHandler 处理一些业务逻辑即可。

public class MessageDispatchHandler {

	private static final Logger	logger	= Logger.getLogger(MessageDispatchHandler.class);

	private ChatData			chatData;

	private ChatConnection		connection;

	//事件监听器
	private ChatRoomListener	listener;

	public MessageDispatchHandler(ChatConnection connection, Object message) {
		this.chatData = ChatPackageUtil.unpackage(message);
		this.connection = connection;
	}

	public void dispatch() {
		logger.debug("current chatdata :" + chatData);
		if (listener == null) {
			throw new RuntimeException("监听器不能为空!");
		}
		try {
			switch (chatData.getMethod()) {
				case C_CONN:
					listener.connChatRoom(connection, chatData);
					break;
				case C_JOIN:
					listener.loginChatRoom(connection, chatData);
					break;
				case C_PUBLISH_MSG:
					listener.broadcastMsg(connection, chatData);
					break;
				case C_SEND_ADMIN:
					listener.sendAdminMsg(connection, chatData);
					break;
				case C_SEND_NORMAL:
					listener.sendNormalMsg(connection, chatData);
					break;
				case C_GET_USERS:
					listener.getOnlineUserList(connection, chatData);
					break;
				default:
					throw new BusinessException("错误指令:" + chatData.getMethod());
			}
		} catch (BusinessException e) {
			logger.info("error msg:" + e.getMessage());
			ChatService.pushErrorMsg(e.getCode(), e.getMessage(), connection);
		}
	}

	public void setListener(ChatRoomListener listener) {
		this.listener = listener;
	}

}

 

总结下遇到的问题:

  1. flash socket通讯格式。每个协议请求都会\0作为消息结尾。因此netty接收消息也必须以\0为截止符读取消息。
  2. flash 安全策略。flash夸域访问服务器时,会自动发<policy-file-request/> ,服务端收到消息后,必须响应对应策略文件。
    <cross-domain-policy>
      <allow-access-from domain="*" to-ports="80-9000" />
    </cross-domain-policy>
  3. SQL注入。Socket和Http相比。只是通讯的层次发生了改变。应用本身的漏洞仍旧没变。处理SQL的时候需要特别注意。不过用一些成熟的ORM框架可以很好的避免这类问题。
    注:这里使用的是比较低级的JSON字符串作为消息载体,较为普遍和靠谱的做法是采用head+body的方式,但前提需要对格式有严格的约定。JSON灵活性较大,因此采用。

javascript实用模板引擎

John Resig 开发的一个简易模板引擎,配合json非常的实用。

// Simple JavaScript Templating
// John Resig - http://ejohn.org/ - MIT Licensed
(function(){
  var cache = {};
 
  this.tmpl = function tmpl(str, data){
    // Figure out if we're getting a template, or if we need to
    // load the template - and be sure to cache the result.
    var fn = !/\W/.test(str) ?
      cache[str] = cache[str] ||
        tmpl(document.getElementById(str).innerHTML) :
     
      // Generate a reusable function that will serve as a template
      // generator (and which will be cached).
      new Function("obj",
        "var p=[],print=function(){p.push.apply(p,arguments);};" +
       
        // Introduce the data as local variables using with(){}
        "with(obj){p.push('" +
       
        // Convert the template into pure JavaScript
        str
          .replace(/[\r\t\n]/g, " ")
          .split("<%").join("\t")
          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
          .replace(/\t=(.*?)%>/g, "',$1,'")
          .split("\t").join("');")
          .split("%>").join("p.push('")
          .split("\r").join("\\'")
      + "');}return p.join('');");
   
    // Provide some basic currying to the user
    return data ? fn( data ) : fn;
  };
})();

很少的一点代码。但功能非常实用。

准备json和一个模板即可

jquery simple template demo

zookeeper资料汇集

这几天看了下zookeeper的东西。收获多多。感谢国人的分享精神。使得这一产品能被更多人了解。

zookeeper基础配置与入门:http://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/

agapple学习笔记系列:http://agapple.iteye.com/blog/1292473

taobao通用产品团队:http://rdc.taobao.com/team/jm/archives/448

Paxos算法:http://zh.wikipedia.org/zh/Paxos%E7%AE%97%E6%B3%95

工具:https://github.com/killme2008/node-zk-browser 淘宝团队开发的一个zookeeper节点管理工具。nodejs开发。功能简单。基本实现了zookeeper命令行的功能。

文档是最佳的知识沉淀呀~

apache zookeeper

zookeeper

 

 

慎用gson

今天是辛苦1个半月代金券上线的日子。但很不幸。因为线上环境和预发布环境存在不一致问题。导致本次发布失败!

抓取到错误的日志。如下:

[01 17:03:48,567 DEBUG] ["http-bio-80"-exec-3] conn.SingleClientConnManager - Releasing connection org.apache.http.impl.conn.SingleClientConnManager$ConnAdapter@287b2e39
[01 17:03:48,568 ERROR] ["http-bio-80"-exec-3] impl.LotteryProcessServiceImpl - 处理异常[order=LotteryUseOrder [lotteryTypeCode=USE, accountId=5556067, cardNo=A00088754, passwd=null, skuId=null, origPayAmount=127.00, favorableAmount=20.00, orderId=1000000090, sceneType=MAYMAY]]:
com.google.gson.JsonSyntaxException: 2011-07-05 17:41:12
        at com.google.gson.DefaultTypeAdapters$DefaultDateTypeAdapter.deserializeToDate(DefaultTypeAdapters.java:376)
        at com.google.gson.DefaultTypeAdapters$DefaultDateTypeAdapter.deserialize(DefaultTypeAdapters.java:351)
        at com.google.gson.DefaultTypeAdapters$DefaultDateTypeAdapter.deserialize(DefaultTypeAdapters.java:307)
        at com.google.gson.JsonDeserializerExceptionWrapper.deserialize(JsonDeserializerExceptionWrapper.java:51)
        at com.google.gson.JsonDeserializationVisitor.invokeCustomDeserializer(JsonDeserializationVisitor.java:92)
        at com.google.gson.JsonObjectDeserializationVisitor.visitFieldUsingCustomHandler(JsonObjectDeserializationVisitor.java:117)
        at com.google.gson.ReflectingFieldNavigator.visitFieldsReflectively(ReflectingFieldNavigator.java:63)
        at com.google.gson.ObjectNavigator.accept(ObjectNavigator.java:120)
        at com.google.gson.JsonDeserializationContextDefault.fromJsonObject(JsonDeserializationContextDefault.java:76)
        at com.google.gson.JsonDeserializationContextDefault.deserialize(JsonDeserializationContextDefault.java:54)
        at com.google.gson.Gson.fromJson(Gson.java:551)
        at com.google.gson.Gson.fromJson(Gson.java:498)
        at com.google.gson.Gson.fromJson(Gson.java:467)
        at com.google.gson.Gson.fromJson(Gson.java:417)
        at com.google.gson.Gson.fromJson(Gson.java:389)
        at com.mbb.payengine.lottery.integration.AcctransMaymayQueryServiceClientImpl.genAccountMaymayInfo(AcctransMaymayQueryServiceClientImpl.java:146)
        at com.mbb.payengine.lottery.integration.AcctransMaymayQueryServiceClientImpl.getAccountByAccountId(AcctransMaymayQueryServiceClientImpl.java:72)
        at com.mbb.payengine.lottery.domainservice.process.impl.use.UseTransProcessor.process(UseTransProcessor.java:69)
        at com.mbb.payengine.lottery.domainservice.template.LotteryTemplate.process(LotteryTemplate.java:46)
        at com.mbb.payengine.lottery.domainservice.factory.impl.LotteryDomainFactoryImpl.processUseTemplate(LotteryDomainFactoryImpl.java:171)
        at com.mbb.payengine.lottery.domainservice.factory.impl.LotteryDomainFactoryImpl.compose(LotteryDomainFactoryImpl.java:104)
        at com.mbb.payengine.lottery.service.api.impl.LotteryProcessServiceImpl$1.doInTransaction(LotteryProcessServiceImpl.java:101)
        at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:130)
        at com.mbb.payengine.lottery.service.api.impl.LotteryProcessServiceImpl.process(LotteryProcessServiceImpl.java:85)
        at com.mbb.payengine.lottery.web.api.LotteryProcessServiceController.process(LotteryProcessServiceController.java:114)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:176)
        at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:426)
        at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:414)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:790)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:549)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:304)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:240)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:164)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:462)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
        at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:563)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:399)
        at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:317)
        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:204)
        at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:311)
        at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
        at java.lang.Thread.run(Thread.java:662)
Caused by: java.text.ParseException: Unparseable date: "2011-07-05 17:41:12"
        at java.text.DateFormat.parse(DateFormat.java:337)
        at com.google.gson.DefaultTypeAdapters$DefaultDateTypeAdapter.deserializeToDate(DefaultTypeAdapters.java:374)
        ... 57 more
[01 17:03:48,571 INFO ] ["http-bio-80"-exec-3] impl.LotteryProcessServiceImpl - 代金券使用失败[order=LotteryUseOrder [lotteryTypeCode=USE, accountId=5556067, cardNo=A00088754, passwd=null, skuId=null, origPayAmount=127.00, favorableAmount=20.00, orderId=1000000090, sceneType=MAYMAY],result=LotteryProcessResult [orgiAmount=0.00, cardNo=null, useFrequence=0, alreadyUseFrequence=0, validityBegin=null, validityEnd=null, lotteryDiscount=null, lotteryScope=null, lotteryUseCondition=null, lotteryFee=null, lotteryCarriage=null, activeId=null]]

问题在gson解析json时间时,出错。但日志显示。出差的地方是2011-07-05 17:41:12。

经过部门同事帮忙,找到原因:线上环境采用en_US.UTF-8编码。线下环境一直用的是zh_CN.UTF-8编码。

Gson默认会使用系统环境的时间解析器来解析时间.显然en_US.UTF-8和zh_CN.UTF-8的默认时间不一致。修改代码如下,可以修复问题

Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();

 

建议使用fastjson替代gson. 相关的问题会少很多~