采集项目总结
一、项目简介
本采集系统的基本功能是通过TCP/IP协议长连接基站,将基站上传的字节流根据协议解析为相应的基站和定位卡信息,过滤后进行持久化,提供访问页面供用户查询定位信息;用户可通过采集系统须对基站、位置、卡等相关实体进行管理。
系统用户分为两类,应用系统用户和管理员。
二、项目架构图
三、Ehcache在项目中的应用
Ehcache缓存数据,在Mina handler类中收到卡定位信息后直接放到缓存中,不直接与数据库交互,与数据库的交互放在缓存监听器类中。这里有一个问题是ehcache与Spring 的整合中没有提供缓存与监听器的绑定。只能手工实现,例:
A、
<cache name="alarmrecordCache" maxElementsInMemory="200"
eternal="false"
timeToIdleSeconds="30" overflowToDisk="false"
memoryStoreEvictionPolicy="LFU">
<cacheEventListenerFactory
class="com.**.listener.CacheEventListenerFactory"
properties="bean=alarmRecordListener"/>
</cache>
B、
<bean id="alarmRecordListener"
class="com.**.listener.AlarmRecordListener" lazy-init="false">
<property name="alarminfoService" ref="alarminfoService">
</property>
</bean>
C、
public class CacheEventListenerFactory extends
net.sf.ehcache.event.CacheEventListenerFactory{
@Override
public CacheEventListener createCacheEventListene (Properties properties) {
String beanName = properties.getProperty( "bean" );
if ( beanName == null ) {
throw new IllegalArgumentException( "缓存监听器名字未定义" );
}
return (CacheEventListener)
SpringContextTool.getApplicationContext().getBean( beanName );
}
}
四、基站分2种:小区与CDMA ,小区基站有固定ip ,CDMA基站无固定ip,通过发送消息通过sim卡->移动->互联网,所以CDMA的ip地址是变动的。业务中有要求给CDMA下发指令,因为没有固定ip ,所以只能广播。这样就有一个问题,广播时会有很多“远程主机已经关闭”错误,因为该ip的连接已经没有了。
解决:将所有下发的命令先存放在一个HashMap(Integer,List),integer 为基站编号,List 中存放命令,命令可能有多条。当CDMA基站向服务器发送信息,SessionOpened 时间中增加代码:根据基站编号查询命令并下发。即:CDMA打开连接时下发命令,基本上这个时候连接还在的。
A、
public class CdmaSendCommandSupport {
/**
* 数据存放格式 integer -> 基站号 List<AbstractCommond> -> 命令LIST
*/
public static HashMap<Integer, List<AbstractCommond>> cdmaCommandMap = new HashMap<Integer, List<AbstractCommond>>();
/**
* 把命令放到 Map 中去
*/
public static void putCommandToQueue(int readerid, AbstractCommond c) {
if (readerid == 0) {
ApplicationContext appContext = SpringContextTool.getApplicationContext();
ReaderService readerService = (ReaderService) appContext.getBean("readerService");
@SuppressWarnings("rawtypes")
List idList = readerService.loadAllCdmaReaderid();
for (int i = 0; i < idList.size(); i++) {
List<AbstractCommond> cList = CdmaSendCommandSupport.cdmaCommandMap
.get((Integer) idList.get(i));
if (cList == null) {
cList = new ArrayList<AbstractCommond>();
}
cList.add(c);
CdmaSendCommandSupport.cdmaCommandMap.put((Integer) idList.get(i), cList);
}
} else {
List<AbstractCommond> cList = CdmaSendCommandSupport.cdmaCommandMap
.get(new Integer(readerid));
if (cList == null) {
cList = new ArrayList<AbstractCommond>();
}
cList.add(c);
CdmaSendCommandSupport.cdmaCommandMap.put(new Integer(readerid),cList);
}
}
}
B、
// 1、处理未发送的命令
List<AbstractCommond> cList = CdmaSendCommandSupport.cdmaCommandMap
.get(readerState.getReaderId());
if (cList != null) {
for (AbstractCommond c : cList)
session.write(c);
CdmaSendCommandSupport.cdmaCommandMap.remove(readerState
.getReaderId());
}
五、枚举的应用
业务中遇到这样的问题,卡的类型,基站的类型,报警的类型都是使用的固定值,程序中要定义常量,数据库中也存在记录。容易造成不一致,最后采用了枚举,hibernate中有一个UserType接口用用来自定义与数据库的映射,注意这样修改后,相关的前台显示与查询均要修改。
public class EnumUserType<E extends Enum<E>> implements UserType {
private static final int[] sqlTypes = {Types.INTEGER };
private Class<E> clazz = null;
protected EnumUserType(Class<E> c) {
clazz = c;
if(!clazz.isEnum()){//检查是否是枚举类
throw new IllegalArgumentException(c+"不是枚举类");
}
if(!Identifiable.class.isAssignableFrom(clazz)){//检查是否实现Identifiable接口
throw new IllegalArgumentException(c+"未实现Identifiable接口");
}
}
@Override
public int[] sqlTypes() {
return sqlTypes;
}
@Override
public Class<E> returnedClass() {
return clazz;
}
@Override
public boolean equals(Object x, Object y){
if (x == y)
return true;
if (x == null || y == null)
return false;
else
return x.equals(y);
}
@Override
public int hashCode(Object x) {
return x.hashCode();
}
/**
* 把数据库中的查询结果集转换成相应的枚举类
*/
@Override
public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
throws HibernateException, SQLException {
int id = rs.getInt(names[0]);
Object result = null;
if (!rs.wasNull())
result = getEnumByID(id);
return result;
}
/**
* 私有方法,配合Identifiable接口通过ID返回相应的枚举值
*/
private E getEnumByID(int id) {
E[] values = clazz.getEnumConstants();
E type = null;
for(E t : values){
if(id==((Identifiable)t).getID()){
type = t;
break;
}
}
if(type==null){
throw new HibernateException("ID为"+id+"的枚举类"+clazz.getName()+"不存在");
}
return type;
}
/**
* 配合Identifiable接口将枚举值转成int类型以存入数据库
*/
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index)
throws HibernateException, SQLException {
if (null == value)
st.setNull(index, Types.INTEGER);
else
st.setInt(index, ((Identifiable) value).getID());//必须实现Identifiable接口
}
@Override
public Object deepCopy(Object value) throws HibernateException {
return value;
}
@Override
public boolean isMutable() {
return false;
}
@Override
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) value;
}
@Override
public Object assemble(Serializable cached, Object owner)
throws HibernateException {
return cached;
}
@Override
public Object replace(Object original, Object targer, Object owner)
throws HibernateException {
return original;
}
六、帮助类的应用
使用帮助类能更好的减低程序的耦合, 并且这些帮助类和方法一般定义为static,能减少内存占用,但是帮助类中经常会需要用到其他类,直接声明显然不太好。Spring有获取上下文,进而获取bean的方法。正好能用。
A、/**
* 持有Spring上下文
*/
public class SpringContextTool implements ApplicationContextAware {
private static ApplicationContext context;
public void setApplicationContext(ApplicationContext acx) {
context = acx;
}
public static ApplicationContext getApplicationContext() {
return context;
}
}
B、<bean name="springContextTool" class="com.yixun.middleware.util.SpringContextTool" lazy-init="false"></bean>
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" depends-on="springContextTool">
<property name="location">
<value>classpath:system.properties</value>
</property>
</bean>
C、public static NioSocketAcceptor getNioSocketAcceptor() {
ApplicationContext context = SpringContextTool.getApplicationContext();
return (NioSocketAcceptor) context.getBean("areaNioAcceptor");
}
七、关于Mina解析
.....