一些好玩的地方:
- 非埋点。不埋点理由是因为vip将来会有很多小的任务,遍布麦包包网站各个角落,如果每个角落里都写段代码不说管理,自己看着也变扭。
- 向运营的@青木同志学到不少会员等级的东西。
一些不足之处:
- 老问题,项目有延期。原计划6月18日上线。一直拖到7月3日。虽然其中更重要的事情填入,但超时已经是事实。
- 较大失误一枚。用户等级数据重新刷了2遍。虽然没有损失,但毕竟客户资料比较重要。事先可以想的更齐全一点。
- 用户签到增加M值,存在bug。已修正
一些好玩的地方:
一些不足之处:
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; }
今天终于把AC权限管理系统“上线”了
内部项目,取名AC,即access control 一个权限授权及管理服务。
webbit是基于netty扩展的websocket工具。可以大大简化websocket开发。
项目地址:https://github.com/webbit/webbit
使用说明:https://github.com/webbit/webbit/blob/master/README.md
本文权当翻译,高手直接进上面链接。
一些题外话:
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基本功能都已经实现。
这几天公司做了个简单的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协议。
上行通道:
下行通道:
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() { } }
逻辑处理:
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; } }
总结下遇到的问题:
<cross-domain-policy> <allow-access-from domain="*" to-ports="80-9000" /> </cross-domain-policy>
这几天看了下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命令行的功能。
文档是最佳的知识沉淀呀~
总结下这几天初步学习mock的心得. 文中大部分内容来自官网文档.
Mockery context = new Mockery(); Turtle turtle = context.mock(Turtle.class); Turtle turtle2 = context.mock(Turtle.class, "turtle2");
... create mock objects ... public void testSomeAction() { ... set up ... context.checking(new Expectations() {{ ... <expections block> ... }}); ... call code being tested ... }
expections 这里指的是jmock在不同情况下的预期值。expections 是jmcok的最重要部分之一。可以模拟用户在不同情况下返回不同的结果。
<expections block>这里是可以设置多个expections 。
expections的结构如下:
invocation-count (mock-object).method(argument-constraints); inSequence(sequence-name); when(state-machine.is(state-name)); will(action); then(state-machine.is(new-state-name));
上面是一个通用的结构。所有expecions都采用相同或更简洁的结构。
invocation-count | mock调用频率。oneOf表示调用一次后这个expections即失效。 |
mock-object | mock对象。即上面Mockery创建的对象。 |
method | 用mock代替实现接口的一个方法。所以mock必须使用接口编程。 |
argument-constraints | mock参数条件。即mock指定方法的参数。 |
when … will…then … | 类似于if .. else… 这样的语句。简单的条件表达式 |
action | mock符合条件后,执行操作。一般是返回结果。或者抛出异常 |
state-machine | 条件表达式。 |
下面详细讲解下expectations各个部分。
oneOf | 只执行一次 |
exactly(n).of | 执行指定n次 |
atLeast(n).of | 至少执行n次 |
atMost(n).of | 最多执行n次 |
between(min,max).of | 可以执行min-max次 |
allowing | 允许执行,不受限制 |
ignoring | 功能同allowing类似。这里主要字面上区分allowing |
never | 不允许执行 |
will(returnValue(v)) | 返回一个值。Object类型任意。集合类型不建议这里返回。虽然也可以 |
will(returnIterator(c)) | 返回一个集合类型的值。 |
will(returnIterator(v1, v2, …, vn)) | 返回一个集合类型的值。可以用多个,来分隔。 |
will(throwException(e)) | 抛出一个异常。 |
will(doAll(a1, a2, …, an)) | 嵌套执行多个actoin。不常用。 |
设定mock方法调用的顺序。简单的看下面代码片段。
定义了先save后countUser。调用的时候也必须找这个顺序。否则用例失败。
public void testCountUserSequences() { final Sequence testSequence = mockery.sequence("testSequence"); final User _user = genUser(); mockery.checking(new Expectations() { { oneOf(userDao).save(_user); inSequence(testSequence); will(returnValue(_user)); oneOf(userDao).countUser(); inSequence(testSequence); will(returnValue(1)); } }); assertEquals(userService.save(_user).getUsername(), "fengsage"); assertEquals(userService.countUser(), 1); }
目前晚上学习jmock。
Issue: spring+annotations使用jmock
遇到了这个场景。stackoverflow上有人问过这个问题,这里贴一下。
http://stackoverflow.com/questions/1638911/mock-object-and-spring-annotations
关键是使用ReflectionTestUtils的方法。这个方法在spring-test工程的相关jar包。
... @Autowired private UserService userService; private UserDao userDao = null; ... ReflectionTestUtils.setField(userService, "userDao", userDao, UserDao.class); ...
Issue:入参对象时。mock表达式传入对象必须和实际传入对象为同一句柄。
final User _user = genUser(); mockery.checking(new Expectations() { { one(userDao).queryUser(_user); List<User> userList = new ArrayList<User>(); userList.add(genUser()); userList.add(genUser()); will(returnValue(userList)); } }); List<User> users = userService.queryUser(_user);
共用一个_user句柄。否则报错。
Issues:jmock一个异常错误
will(throwException(new RuntimeException("test exception")));
推荐链接:http://superleo.iteye.com/blog/143493
相关:jmock2学习笔记-续
android自带的日期组件是通过TimeDialog,让用户上下调整来输入.但这样势必让用户需要点击某个按钮,弹出对话框后在选择.感觉不是非常的友好.
自己写了一个日期组件,可以通过手势在时间上拨动,达到调整数字的目的.
代码如下:
package com.zjhcsoft.mobi.android.widget; import java.util.Calendar; import java.util.GregorianCalendar; import com.zjhcsoft.mobi.android.ui.R; import android.app.DatePickerDialog; import android.app.Dialog; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.widget.DatePicker; import android.widget.TextView; /** * 日期滚动组件 * @author Fred * */ public class RotateTimeInput extends TextView { private static final String TAG = "RotateTimeInput"; public static final String UP = "up"; public static final String DOWN = "down"; private int year; private int month; private int days; private Calendar currentDate;//当前时间 private float fontSize = 13L; private GestureDetector ges;//手势接口实现 private RotateTimeListener rotateTimeListener; private Dialog dataDialog;//日期选择器 private boolean isMove = false; private boolean isFling = false; private boolean isScroll = false; //细粒度属性设置 private float longClickFix = 0;//防止滑动和长按有冲突 private float longClickNorm = 50;//区分滑动和长按标准差值,差值大于改值时,放弃长按事件 private int scrollNorm;//设置scroll因子 ,scroll数大于默认值时,时间触发 public RotateTimeInput(Context context, AttributeSet attrs) { super(context, attrs); initRotateWidget(context,attrs); } /** * 初始化控件时间 * @param year 年 * @param month 月-1 * @param days 日 */ public void initByGivenDate(int year,int month,int days){ this.year = year; this.month = month-1; this.days = days; currentDate = null; this.setText(formatTime());//设置默认值显示值 } /** * 初始化RotateTime组件 * @param context 应用上下文 * @param attrs 配置属性 */ private void initRotateWidget(Context context,AttributeSet attrs){ TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.RotateTimeInput); scrollNorm = a.getInt(R.styleable.RotateTimeInput_scrollNorm,8); a.recycle(); //设置字体大小 setTextSize(fontSize); //初始化当前时间 this.setText(formatTime());//设置默认值显示值 //增加轨迹事件 ges = new GestureDetector(context,new TouchGesture(context)); //鼠标手势触发 this.setOnTouchListener(new OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { if(event.getAction()==MotionEvent.ACTION_UP){ if(isMove&&isScroll&&!isFling){ longClickFix = 0; rotateTimeListener(); isScroll = false; isMove = false; } } return ges.onTouchEvent(event); } }); } /** * 时间格式化 * @return */ private String formatTime(){ calculate(); return new StringBuffer().append(year).append("年").append(month+1).append("月").append(days).append("日").toString(); } /** * 日期递增 */ private void upDays(){ currentDate.add(Calendar.DATE, 1); setText(formatTime()); } /** * 日期减少 */ private void downDays(){ currentDate.add(Calendar.DATE, -1); setText(formatTime()); } /** * 调整时间 * @param way 调整时间方向(RotateTimeInput.UP向前一天,RotateTimeInput.DOWN回退一天) */ public void adjustDay(String way){ if(way==UP){ upDays(); }else{ downDays(); } rotateTimeListener(); } /** * 重新计算当前年月日天数 */ private void calculate(){ if(currentDate==null){ if(year!=0&&month!=0&&days!=0){ this.currentDate = new GregorianCalendar(year, month, days); }else{ this.currentDate = GregorianCalendar.getInstance(); } } if (currentDate!=null){ this.year = currentDate.get(Calendar.YEAR); this.month = currentDate.get(Calendar.MONTH);//Java中,月份从0开始 this.days = currentDate.get(Calendar.DATE); } } /** * 创建Dialog对象,此处只有一个DataPick对象 * @param context 应用上下文 * @return */ private Dialog initDialog(Context context){ dataDialog = new DatePickerDialog(context,new DatePickerDialog.OnDateSetListener() { public void onDateSet(DatePicker view, int years, int monthOfYear, int dayOfMonth) { //DataPick用户锁定后 currentDate.set(years, monthOfYear, dayOfMonth); setText(formatTime()); rotateTimeListener(); } },currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)); dataDialog.show(); longClickFix = 0;//释放差值 return dataDialog; } /** * Touch接口实现,监听用户鼠标轨迹 * @author Fred * */ class TouchGesture extends GestureDetector.SimpleOnGestureListener{ private double scrollDistence=0; Context dialogContext; public TouchGesture(Context dialogContext){ this.dialogContext = dialogContext; } @Override public void onLongPress(MotionEvent e) { if(longClickFix scrollNorm) { scrollDistence=0; if (distanceX<0){ upDays(); }else{ downDays(); } } longClickFix = Math.abs(e2.getRawX() - e1.getRawX()); return false; } } /** * 拨动事件 * @param listener */ public void setOnRotateTimeListener(RotateTimeListener listener){ this.rotateTimeListener = listener; } private void rotateTimeListener(){ calculate(); if (rotateTimeListener!=null){ rotateTimeListener.onRotateTime(this.year, this.month+1, this.days); } } class RotateFlyingThread extends Thread { private int ri; private int rp; private float af; private boolean stop = false; private int ic; public RotateFlyingThread(int rotateInterval, int rotatePeriod, float attenuateFactor, int increment) { ri = rotateInterval; rp = rotatePeriod; af = attenuateFactor; ic = increment; } public void stopRotate() { stop = true; } public void run() { int p = 0; Log.i("Rotating", String.format( "rotateInterval: %s, rotatePeriod: %s, attenuateFactor: %s, increment: %s,", ri, rp, af, ic)); while ((p < rp) && !stop) { Log.i("Rotating", " ..." + RotateTimeInput.this.getText()); RotateTimeInput.this.post(new Runnable() { public void run() { if (ic>0){ upDays(); }else{ downDays(); } } }); try { Thread.sleep(ri); } catch (InterruptedException e) { e.printStackTrace(); } ri += (int) (ri * (af / 10)); p += ri; } rotateTimeListener(); isFling = false; isMove = false; Log.i("Rotating", "stopped"); } } }
事件接口:
package com.fengsage.mobi.android.widget; /** * 拨动时间空间 * @author Fred * */ public interface RotateTimeListener { /** * 当时间停下来的时候,开始触发事件 * @param year * @param month 1--12 * @param days */ public void onRotateTime(int year,int month,int days); }
最终效果如下:
在数字上面,用手左右来回拖动,时间也会跟着变动. 如果长按,则会弹出android标准的日期控件.
最近项目中用到异步任务功能,网上搜罗了下资料,奈何国内的质量实在太低,依靠伟大的谷歌搜到很多国外的文章,写的非常的好,更好的在于没有copy paste的成分.
本文内容转自:http://www.brighthub.com/mobile/google-android/articles/82805.aspx
英语好的朋友请直接阅读原文.小弟不才,做个苦力当翻译
介绍
我们的Android应用越来越复杂,连接服务端,和web交互数据,存储大文件在android数据库中的同时显示进度条或者在通知栏显示通知. 我们如何能在抛开UI线程的情况下,边接受处理数据边用进度条展示呢?开始之前,必须知道什么是"UI线程"?
如果你熟悉"Thread"的概念. 那么你应该很容易明白异步任务. Android应用在处理的时候只有一个main主线程在运行.这个看上去像在任何地方只有一个任务. 如果你只有一个UI线程在工作,你就不能做一些复杂的事情,比如同时存储10000条记录,这个时候应用会停住,直到1000条记录存储完毕. 这不是一个好的方式. 在android你可以在一个应用里,同一时间运行多个线程. 举例:一个后台任务可以从服务端接受数据并且存储到本地数据库中.
如何对"Thread"概念有足够的了解,那么我们继续
现在我们可以开始了吗? 开始在后台执行任务? 这里我们还有几步工作要做.
一个古老的路线方法是使用Thread线程类,使用Handlers 和Runnables. 但是我们有更好的方式。使用AsyncTask 类
AsyncTask 类
让我来看看AsyncTask类的结构
private class myBrightHubTask extends AsyncTask protected void onPreExecute(){ } 这个方式是在执行新线程前运行. 这里没有输入和输出的参数,所以这个可以初始化一些你认为必要的参数. protected Z doInBackground(X...x){ } 这是个非常重要的方法. 你可以在这里定义你需要的在后台执行的方法.这里开始是一个独立的线程在运行,而不是main线程. 这个方法接收一组对象集合,对象可以是在前继承AsyncTask的时候定义的X类型(你看到这个类的一开始我们继承的AsyncTask类了吗?这里的X就是这里的类型),默认是Object集合. protected void onProgressUpdate(Y y){ } 这个方法是在使用publishProgress()方法时触发,这个方法常用于显示一些在主线程中执行的进度条或者一些信息展示.比如当在执行一个后台任务的时候,前台做一个滚动条在滚动 , protected void onPostExecute(Z z){ }
这个方法在后台任务完成后执行,传入一个参数Z,也是前面定义的那个,你可以使用这个参数做一些结束任务时的处理,比如跳转intent或者其他的事情.
X,Y,Z的是什么类型?
我想你也可以从上面的结构中得出结论:
X – 运行后台任务的时候,需要传入的参数类型. 可以是一个对象的集合.
Y – 当你需要使用onProgressUpdate方法时,传入的参数类型
Z – 当后台任务完成时,需要传入数据处理的数据类型
如何使用这个异步任务呢?执行要执行下面2行代码.
myBrightHubTask brightHubTask = new myBrightHubTask(); brightHubTask.execute(x);
这里的输入参数是前面讲的X类型
一旦我们运行异步任务.你可以使用getStatus()方法获取它的状态
brightHubTask.getStatus();
我们可以接受下面几个状态:
RUNNING – 任务处理中
PENDING – 任务还没有完成.
FINISHED – 任务结束.
提醒
需要注意的是:
不用主动去调用onPreExecute, doInBackground and onPostExecute这个三个方法.这些方法会自动执行.
你不能在另外一个AsyncTask或者线程中使用异步任务. 调用异步任务必须在UI主线程中使用.
onPostExecute 这个方法时在UI主线程中执行.这里你可以调用另外一个异步任务.
输入参数可以设置一个Object集合,这样你就可以传入任意类型的对象
近期评论