
make it possible
概述 本文是紧接着上文安装好单机版的redis和rediscluster的之后需要去验证是否安装成功,以及如何和Spring集成到一起。 Jedis Jedis是是Redis官方推荐的Java客户端开发包,用于处理redis服务上的缓存数据。笔者用的是Jedis-2.7.2.jar Redis安装测试 接着这上文,我们先简单的写一个测试类,测试一下单机版的redis是否已经可用 [java] view plain copy print? @Test public void testJedisSingle() { // 创建一个jedis的对象。 Jedisjedis= newJedis("192.168.21.225", 6379); // 调用jedis对象的方法,方法名称和redis的命令一致。 jedis.set("key1", "jedis test"); Stringstring= jedis.get("key1"); System.out.println(string); // 关闭jedis。每次用完之后都应该关闭jedis jedis.close(); } 测试结果入下: 在实际的运用过程中,我们基本上不会这样每连接一次redis就去创建一个Jedis对象,而是会在程序加载的时候直接创建一个连接池。测试方法如下 [java] view plain copy print? /** * 使用连接池 */ @Test public void testJedisPool() { // 创建jedis连接池 JedisPoolpool= newJedisPool("192.168.21.225", 6379); // 从连接池中获得Jedis对象 Jedisjedis= pool.getResource(); Stringstring= jedis.get("key1"); System.out.println(string); // 关闭jedis对象 jedis.close(); pool.close(); } 运行结果入下: 测试完单机版之后,我们接下来看看如何使用Jedis操作redis集群(本文使用的是伪集群) [java] view plain copy print? @Test public void testJedisCluster() { HashSet<HostAndPort>nodes= newHashSet<>(); nodes.add(new HostAndPort("192.168.21.225",7001)); nodes.add(new HostAndPort("192.168.21.225",7002)); nodes.add(new HostAndPort("192.168.21.225",7003)); nodes.add(new HostAndPort("192.168.21.225",7004)); nodes.add(new HostAndPort("192.168.21.225",7005)); nodes.add(new HostAndPort("192.168.21.225",7006)); JedisClustercluster= newJedisCluster(nodes); cluster.set("key1", "1000"); Stringstring= cluster.get("key1"); System.out.println(string); cluster.close(); } 测试结果入下: 通过上面的测试一方面我们知道了如何简单的去操作redis,另一方面也验证在之前介绍的redis的按照是没有问题的。 与Spring的集成 下面我们看看在spring的配置文件中如何配置redis的参数和Jedis相关的东西。 连接池配置 首先我们看看redis连接池的配置 [html] view plain copy print? <!-- 连接池配置 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <!-- 最大连接数 --> <property name="maxTotal" value="30" /> <!-- 最大空闲连接数 --> <property name="maxIdle" value="10" /> <!-- 每次释放连接的最大数目 --> <property name="numTestsPerEvictionRun" value="1024" /> <!-- 释放连接的扫描间隔(毫秒) --> <property name="timeBetweenEvictionRunsMillis" value="30000" /> <!-- 连接最小空闲时间 --> <property name="minEvictableIdleTimeMillis" value="1800000" /> <!-- 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 --> <property name="softMinEvictableIdleTimeMillis" value="10000" /> <!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 --> <property name="maxWaitMillis" value="1500" /> <!-- 在获取连接的时候检查有效性, 默认false --> <property name="testOnBorrow" value="true" /> <!-- 在空闲时检查有效性, 默认false --> <property name="testWhileIdle" value="true" /> <!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true --> <property name="blockWhenExhausted" value="false" /> </bean> 单机配置 [html] view plain copy print? <bean id="redisClient" class="redis.clients.jedis.JedisPool"> <constructor-arg name="host" value="192.168.21.225"></constructor-arg> <constructor-arg name="port" value="6379"></constructor-arg> <constructor-arg name="poolConfig" ref="jedisPoolConfig"></constructor-arg> <bean id="jedisClient" class="com.taotao.rest.dao.impl.JedisClientSingle" /> </bean> 集群配置 [html] view plain copy print? <bean id="redisClient" class="redis.clients.jedis.JedisCluster"> <constructor-arg name="nodes"> <set> <bean class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="192.168.21.225"></constructor-arg> <constructor-arg name="port" value="7001"></constructor-arg> </bean> <bean class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="192.168.21.225"></constructor-arg> <constructor-arg name="port" value="7002"></constructor-arg> </bean> <bean class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="192.168.21.225"></constructor-arg> <constructor-arg name="port" value="7003"></constructor-arg> </bean> <bean class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="192.168.21.225"></constructor-arg> <constructor-arg name="port" value="7004"></constructor-arg> </bean> <bean class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="192.168.21.225"></constructor-arg> <constructor-arg name="port" value="7005"></constructor-arg> </bean> <bean class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="192.168.21.225"></constructor-arg> <constructor-arg name="port" value="7006"></constructor-arg> </bean> </set> </constructor-arg> <constructor-arg name="poolConfig" ref="jedisPoolConfig"></constructor-arg> </bean> <bean id="jedisClientCluster"class="com.taotao.rest.dao.impl.JedisClientCluster"></bean> 工具类 为了方便笔者直接调用Jedis去操作redis,所以笔者写了一个JedisClient工具类,如下 [java] view plain copy print? public interfaceJedisClient { Stringget(String key); Stringset(String key,String value); Stringhget(String hkey,String key); long hset(String hkey, String key, String value); long incr(String key); long expire(String key, int second); long ttl(String key); long del(String key); long hdel(String hkey, String key); } 由于对单机和集群的redis操作时不一样的,所以实现工具类的时候单机和集群的实现方式是不一样的。 单机实现 [java] view plain copy print? public classJedisClientSingle implements JedisClient{ @Autowired private JedisPool jedisPool; @Override public String get(String key) { Jedisjedis= jedisPool.getResource(); Stringstring= jedis.get(key); jedis.close(); return string; } @Override public String set(String key, String value) { Jedisjedis= jedisPool.getResource(); Stringstring= jedis.set(key, value); jedis.close(); return string; } @Override public String hget(String hkey, String key) { Jedisjedis= jedisPool.getResource(); Stringstring= jedis.hget(hkey, key); jedis.close(); return string; } @Override public long hset(String hkey, String key, String value) { Jedisjedis= jedisPool.getResource(); Longresult= jedis.hset(hkey, key, value); jedis.close(); return result; } @Override public long incr(String key) { Jedisjedis= jedisPool.getResource(); Longresult= jedis.incr(key); jedis.close(); return result; } @Override public long expire(String key, int second) { Jedisjedis= jedisPool.getResource(); Longresult= jedis.expire(key, second); jedis.close(); return result; } @Override public long ttl(String key) { Jedisjedis= jedisPool.getResource(); Longresult= jedis.ttl(key); jedis.close(); return result; } @Override public long del(String key) { Jedis jedis = jedisPool.getResource(); Longresult= jedis.del(key); jedis.close(); return result; } @Override public long hdel(String hkey, String key) { Jedisjedis= jedisPool.getResource(); Longresult= jedis.hdel(hkey, key); jedis.close(); return result; } } 集群实现 [java] view plain copy print? public classJedisClientCluster implements JedisClient { @Autowired private JedisCluster jedisCluster; @Override public String get(String key) { return jedisCluster.get(key); } @Override public String set(String key, String value) { return jedisCluster.set(key, value); } @Override public String hget(String hkey, String key) { return jedisCluster.hget(hkey, key); } @Override public long hset(String hkey, String key, String value) { return jedisCluster.hset(hkey, key, value); } @Override public long incr(String key) { return jedisCluster.incr(key); } @Override public long expire(String key, int second) { return jedisCluster.expire(key, second); } @Override public long ttl(String key) { return jedisCluster.ttl(key); } @Override public long del(String key) { return jedisCluster.del(key); } @Override public long hdel(String hkey, String key) { return jedisCluster.hdel(hkey, key); } } 测试 下面是测试配置以及工具类的方法 单机测试 [java] view plain copy print? @Test public void testSpringJedisSingle(){ ApplicationContextapplicationContext = newClassPathXmlApplicationContext( "classpath:spring/applicationContext-*.xml"); JedisPoolpool= (JedisPool) applicationContext.getBean("redisClient"); Jedisjedis= pool.getResource(); Stringstring= jedis.get("key1"); System.out.println(string); jedis.close(); pool.close(); } 集群测试 [html] view plain copy print? @Test public voidtestSpringJedisCluster() { ApplicationContextapplicationContext = newClassPathXmlApplicationContext( "classpath:spring/applicationContext-*.xml"); JedisClusterjedisCluster= (JedisCluster) applicationContext .getBean("redisClient"); Stringstring= jedisCluster.get("key1"); System.out.println(string); jedisCluster.close(); } 总结 至此,本文的的写作目的——测试集群,以及redis配置与Spring集成就达到了。其实在笔者在写本文的时候,一方面是对之前项目中redis运用的优化,另一方面在实际的其实本文在实现JedisClient的时候,所有的方法中在笔者的项目中是添加有日志的。至于为什么添加日志本文就不介绍了。
之前已经在博文中介绍了redis以及redis的简单实用,但是在实际的项目中用单机版redis还是很少的基本上都是实用redis集群。 RedisCluster概念 RedisCluster架构 架构细节: (1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽. (2)节点的fail是通过集群中超过半数的节点检测失效时才生效. (3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可 (4) Redis Cluster 是Redis的集群实现,内置数据自动分片机制,集群内部将所有的key映射到16384个Slot中,集群中的每个Redis Instance负责其中的一部分的Slot的读写。集群客户端连接集群中任一Redis Instance即可发送命令,当Redis Instance收到自己不负责的Slot的请求时,会将负责请求Key所在Slot的Redis Instance地址返回给客户端,客户端收到后自动将原请求重新发往这个地址,对外部透明。一个Key到底属于哪个Slot由crc16(key) % 16384 决定。 例如下图 Key:a 计算a的hash值,例如值为100,100这个slot在server1上,所以a应该放到server1. Key:hello Hash值:10032,此slot在server3上。Hell可以应该存在server3. redis-cluster投票:容错 (1)领着投票过程是集群中所有master参与,如果半数以上master节点与master节点通信超过(cluster-node-timeout),认为当前master节点挂掉. (2):什么时候整个集群不可用(cluster_state:fail)? a:如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完成时进入fail状态. ps : redis-3.0.0.rc1加入cluster-require-full-coverage参数,默认关闭,打开集群兼容部分失败. b:如果集群超过半数以上master挂掉,无论是否有slave集群进入fail状态. ps:当集群不可用时,所有对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误 RedisCluster安装 安装redis 版本说明 本教程使用redis3.0版本。3.0版本主要增加了redis集群功能。同时由于笔者实用的是一台虚拟机所以只是伪集群模式,但是和真集群的安装方法本质上还是类似的。 安装的前提条件: 需要安装gcc:yum install gcc-c++ 下载redis的源码包,把源码包上传到linux服务器 解压源码包 [plain] view plain copy print? tar -zxvf redis-3.0.0.tar.gz Make Make install make installPREFIX=/usr/local/redis-cluster/redis01 ps:PREFIX必须大写表示redis的安装路径,执行上面几步之后单机版的redis已经安装成功。 集群环境安装 搭建集群需要使用到官方提供的ruby脚本,需要安装ruby的环境。 安装ruby [plain] view plain copy print? yum install ruby yum install rubygems redis集群管理工具redis-trib.rb输入如下依次命令可以找到 [plain] view plain copy print? cd redis-3.0.0/src ll*.rb 脚本需要的ruby包: 下载地址:http://download.csdn.net/detail/senior_lee/9502524 上传到Linux,并安装ruby包 geminstall redis-3.0.0.gem 集群搭建 第一步:创建6另外5个redis实例,在/usr/local/redis-cluster/文件夹下,复制redis01即可,分别命名为redis02~redis06。 注意:需要分别删除每个redis安装目录里面的dum.rdb文件。 第二步:修改redis配置文件 1将port分别改成7001~7006 2打开cluster-enable前面的注释 3把创建集群的ruby脚本复制到redis-cluster目录下。 4启动6个redis实例 5创建集群 ./redis-trib.rb create --replicas 1 192.168.21.225:7001192.168.21.225:7002 192.168.21.225:7003 192.168.21.225:7004 192.168.21.225:7005192.168.21.225:7006 ps:创建了三个节点主节点,三个从节点。其中—replicas1 表示每个主节点下面有1个从节点,从节点可以是任意多个。 窗口输出结果入下: >>> Creating cluster Connecting to node 192.168.21.225:7001: OK Connecting to node 192.168.21.225:7002: OK Connecting to node 192.168.21.225:7003: OK Connecting to node 192.168.21.225:7004: OK Connecting to node 192.168.21.225:7005: OK Connecting to node 192.168.21.225:7006: OK >>> Performing hash slotsallocation on 6 nodes... Using 3 masters: 192.168.21.225:7001 192.168.21.225:7002 192.168.21.225:7003 Adding replica 192.168.21.225:7004 to 192.168.21.225:7001 Adding replica 192.168.21.225:7005 to 192.168.21.225:7002 Adding replica 192.168.21.225:7006 to 192.168.21.225:7003 M: 5a8523db7e12ca600dc82901ced06741b3010076192.168.21.225:7001 slots:0-5460 (5461 slots) master M: bf6f0929044db485dea9b565bb51e0c917d20a53192.168.21.225:7002 slots:5461-10922 (5462 slots) master M: c5e334dc4a53f655cb98fa3c3bdef8a808a693ca192.168.21.225:7003 slots:10923-16383 (5461 slots) master S: 2a61b87b49e5b1c84092918fa2467dd70fec115f192.168.21.225:7004 replicates 5a8523db7e12ca600dc82901ced06741b3010076 S: 14848b8c813766387cfd77229bd2d1ffd6ac8d65192.168.21.225:7005 replicates bf6f0929044db485dea9b565bb51e0c917d20a53 S: 3192cbe437fe67bbde9062f59d5a77dabcd0d632192.168.21.225:7006 replicates c5e334dc4a53f655cb98fa3c3bdef8a808a693ca Can I set the above configuration? (type'yes' to accept): yes >>> Nodes configuration updated >>> Assign a different configepoch to each node >>> Sending CLUSTER MEET messagesto join the cluster Waiting for the cluster to join..... >>> Performing Cluster Check(using node 192.168.21.225:7001) M: 5a8523db7e12ca600dc82901ced06741b3010076192.168.21.225:7001 slots:0-5460 (5461 slots) master M: bf6f0929044db485dea9b565bb51e0c917d20a53192.168.21.225:7002 slots:5461-10922 (5462 slots) master M: c5e334dc4a53f655cb98fa3c3bdef8a808a693ca192.168.21.225:7003 slots:10923-16383 (5461 slots) master M: 2a61b87b49e5b1c84092918fa2467dd70fec115f192.168.21.225:7004 slots: (0 slots) master replicates 5a8523db7e12ca600dc82901ced06741b3010076 M: 14848b8c813766387cfd77229bd2d1ffd6ac8d65192.168.21.225:7005 slots: (0 slots) master replicates bf6f0929044db485dea9b565bb51e0c917d20a53 M: 3192cbe437fe67bbde9062f59d5a77dabcd0d632192.168.21.225:7006 slots: (0 slots) master replicates c5e334dc4a53f655cb98fa3c3bdef8a808a693ca [OK] All nodes agree about slotsconfiguration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. 测试集群 依次输入如下命令 cd/usr/local/redis-cluster/ redis01/bin/redis-cli-h 192.168.21.225 -p 7002 –c ps:-c表示连接集群 集群就这样愉快的搭建完了,接下的文章里面会依次介绍如何使用jedis操作单机和集群版redis,jedis如何同spring集成以及如何编写jedis的工具类。
今天同事在调ajax的时候遇到了一个问题,明明ajax成功的返回了数据,但是每次执行的时候都进入error方法。 那么如何才能找到问题的原因呢?如下 [javascript] view plain copy print? function loadPic(){ $.ajax({ url: "courseRotation/loadPic", async: false, dataType: "json", data: { courseId:$("#courseId").val(), }, success: function (data) { alert(data); error:function(data, XMLHttpRequest, textStatus, errorThrown){ alert(data); alert(XMLHttpRequest.status); alert(XMLHttpRequest.readyState); alert(textStatus); } }); } 通过error里面的处理能够将错误信息,以及传回的信息都显示出来。如下图 同时错误原因是数据类型错误,改成“text”之后就没有问题了。写这篇博客主要是想告诉大家以后出现类似的错误如何快速的定位错误,并有针对的去解决错误。
概述 JNDI(JavaNaming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。目录服务是命名服务的一种自然扩展。两者之间的关键差别是目录服务中对象不但可以有名称还可以有属性(例如,用户有email地址),而命名服务中对象没有属性。 集群JNDI实现了高可靠性JNDI,通过服务器的集群,保证了JNDI的负载平衡和错误恢复。在全局共享的方式下,集群中的一个应用服务器保证本地JNDI树的独立性,并拥有全局的JNDI树。每个应用服务器在把部署的服务对象绑定到自己本地的JNDI树的同时,还绑定到一个共享的全局JNDI树,实现全局JNDI和自身JNDI的联系。 JNDI(JavaNaming and Directory Interface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似JDBC都是构建在抽象层上。现在JNDI已经成为J2EE的标准之一,所有的J2EE容器都必须提供一个JNDI的服务。 JNDI可访问的现有的目录及服务有: DNS、XNam 、Novell目录服务、LDAP(Lightweight Directory Access Protocol轻型目录访问协议)、 CORBA对象服务、文件系统、Windows XP/2000/NT/Me/9x的注册表、RMI、DSML v1&v2、NIS。 何时使用JNDI JNDI是一种查找服务,用于查找: Web应用环境变量 EJBs和他们的环境变量 通过DataSources的数据库连接池 JMS目标和连接工厂 备注:不要讲JNDI当做数据库使用,因为JDNI对象存储在内存中,访问JDNI对象与网络性能有关(网络好的时候查找性能高)。 JNDI必备知识 JNDI树 JNDI环境属性 在EJB中使用properties文件使用JNDI jndi.properties文件内容 [plain] view plain copy print? java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces java.naming.provider.url=localhost 备注:jndi.properties文件为所有的InitialContexts设置默认的属性,jndi.properties文件的搜索次序 ·CLASSPATH ·$JAVA_HOME/lib 从JNDI查找 ·Lookup()从JNDI树获取对象 [java] view plain copy print? //实例化一个InitialContext对象 InitialContext ctx = new InitialContext(); //在JNDI树上查找“UserManagerBean对象 UserManager userManager =(UserManager)ctx.lookup("UserManagerBean/remote"); User user = new User(); user.setUserName("cody"); user.setPossword("0909"); //使用查找到对象的方法 userManager.addUser(user); //连接完成之后关闭访问资源 ctx.close(); 谈谈远程绑定对象 ·绑定到远程命名服务的对象必须是序列化的 ·访问命名服务时,对象是采用复制机制 EJB是如何绑定远程访问对象的? EJB通过@Remote注解将对象绑定到JNDI树上 [java] view plain copy print? @Stateless @Remote(UserManager.class) public classUserManagerBean implements UserManager { @Override public void addUser(User user) { System.out.println("User[userName="+user.getUserName()+"]已经被成功保存"); user.setId(10); } } JNDI可能出现的异常 ·AuthenticationException 没有提供认证信息,或者提供的认证信息有误 ·CommunicationException 通信异常 ·InvalidNameException 非法命名之类的异常 ·NameNotFoundException 没有找到相应名称的资源 ·NoInitialContextException 没有初始化InitialContext对象 总结 J2EE规范要求所有 J2EE 容器都要提供 JNDI 规范的实现。JNDI 在 J2EE 中的角色就是“交换机” —— J2EE 组件在运行时间接地查找其他组件、资源或服务的通用机制。在多数情况下,提供 JNDI 供应者的容器可以充当有限的数据存储,这样管理员就可以设置应用程序的执行属性,并让其他应用程序引用这些属性(Java 管理扩展(Java ManagementExtensions,JMX)也可以用作这个目的)。JNDI 在 J2EE 应用程序中的主要角色就是提供间接层,这样组件就可以发现所需要的资源,而不用了解这些间接性。 在 J2EE 中,JNDI 是把 J2EE 应用程序合在一起的粘合剂,JNDI 提供的间接寻址允许跨企业交付可伸缩的、功能强大且很灵活的应用程序。这是 J2EE 的承诺,而且经过一些计划和预先考虑,这个承诺是完全可以实现的。
zookeeper(二) --- 基本概念 当我们学习一个新的东西的时候,首先得知道它是个什么东西能做什么,其次,我们得知道它的一些基本的概念,那么本文笔者会介绍一些zookeeper的基本概念。包括:集群角色、火花、数据节点、版本、watcher、ACL权限控制。 集群角色 Zookeeper集群中一共有三种角色,分别是:Leader、Follower,Observer。Leader服务器是整个zookeeper集群工作机制中的核心,Follower服务器是Zookeeper集群状态的跟随着,Observer服务器充当一个观察这的角色。 会话 会话是指客户端和Zookeeper服务器的连接,Zookeeper中会话叫Session,客户端靠与服务端建立一个TCP的长连接来维持一个Session,客户端在启动的时候首先会与服务器建立一个TCP连接,通过这个链接,客户端能够通过心跳检测与服务器保持有效的会话,也能向Zookeeper服务器发送请求并获得响应。 数据节点 Zookeeper中的节点有两类:一,急群众的一台机器成为一个节点;2、数据模型中的数据单元Z弄得,分为持久节点和临时节点。Zookeeper的数据模型是一颗树,树的节点就是Znode,Znode中可以保存信息。 版本 如下表: 版本类型 说明 version 当前数据节点数据内容的版本号 cversion 当前数据节点子节点的版本号 aversion 当前数据节点ACL变更版本号 悲观锁和乐观锁 悲观锁又叫悲观并发锁,是数据库中一种非常严格的锁策略,具有强烈的排他性,能够避免不同事务对同一数据并发更新造成的数据不一致性,在上一个事务没有完成之前,下一个事务不能访问相同的资源,适合数据更新竞争非常激烈的场景。相比悲观锁,乐观锁使用的场景会更多,悲观锁认为事务访问相同数据的时候一定会出现相互干扰,所以简单粗暴的使用排他的方式,而乐观锁认为不同事务访问相同资源是很少出现相互干扰的情况,因此在事务处理期间不需要进行并发控制,当然乐观锁也是锁,它还是会有并发控制的。对于与数据库我们通常的做法是在每个表中增加一个version字段,事务修改数据之前先读出数据(带上版本号),然后把这个数据度出现的版本号加入到更新语句的条件中,比如读出来的版本号是1,我们修改数据的语句可以这样写,update XXX set 字段=值 where id=1 按到version=1,如果更新失败说明其他事务更新过数据(每次修改数据版本号都会变),这个时候系统就会抛出异常给客户端,让客户端处理。 Watcher 时间监听器,Zookeeper允许用户在指定节点上注册一些Watcher,当数据节点发生变化的时候,Zookeeper服务器会把这个变化的通知发送给感兴趣的客户端。 ACL权限控制 ACL是AccessControl Lists的缩写,Zookeeper采用ACL策略来进行权限控制,有一下权限: CREATE:穿件子节点的权限 READ:获取节点数据和子节点列表的权限 WRITE:更新节点数据的权限 DELETE:删除子节点的权限 ADMIN:设置节点ACL的权限 这篇博客还是一些基础的理论知识,下一篇就是搭建Zookeeper集群了,在后面的博客可能就是solr知识了。
zookeeper(一) --- zookeeper概述 写文章之前笔者先说明一下笔者最近了解zookeeper的原因,最近笔者所在的项目中需要用全文索引,而架构师和项目经理决定使用比较成熟的solr+zookeeper的模式。这个任务比较幸运的落到了笔者的肩上,以上就是文章的背景了,进入主题。 什么是Zookeeper? Apache ZooKeeper是Apache软件基金会的一个软件项目,他为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。由雅虎创建,是Google Chubby的开源项目实现。 ZooKeeper曾经是Hadoop的一个子项目,但现在是一个独立的顶级项目。 ZooKeeper的架构通过冗余服务实现高可用性。因此,如果第一次无应答,客户端就可以询问另一台ZooKeeper主机。ZooKeeper节点将它们的数据存储于一个分层的命名空间,非常类似于一个文件系统或一个前缀树结构。客户端可以在节点读写,从而以这种方式拥有一个共享的配置服务。更新是全序的。 应用场景 1、 数据发布/订阅 数据发布/订阅 顾名思义就是一方把数据发布出来,另一方通过某种手段可以得到这些数据。 通常数据订阅有两种方式:推模式和拉模式,推模式一般是服务器制动向客户端发送消息,拉模式是客户端主动去服务器获取数据(通常采用定时轮询的方式),而zookeeper采用两种方式相结合。发布者将数据发布到zookeeper集群节点上,订阅者通过一定得方法告诉服务器,我对那个节点的数据感兴趣,那服务器在这些节点的数据发生变化时,就通知客户端客户端得到通知后可以去服务器获取数据信息。 2、 负载均衡 a. 首先DB再启动的时候先在zookeeper上注册一个临时节点(服务器出现问题的时候,节点会自动从zookeeper上删除) b. 客户端需要低些数据库的时候首先它去zookeeper得到所有可用的DB的连接信息(一张表) c. 客户端随机选择一个与之建立连接 d. 当客户端发现连接不可用的时候可再次从zookeeper上获取可用的DB连接信息,当然也可以在刚获取的那个列表里移除不可用的连接后再随机选择一个DB与之连接。 3、 命名服务 顾名思义,就是提供名称的服务,,例如数据库表格ID,串通的开发中,可以通过ping某个主机来实现,ping得通说明对方是可用的,相反是不可用的。Zookeeper中我们让所有的机器都注册一个临时的节点,我们判断一个机器是否可用,我们只需要判断这个节点在zookeeper中是否存在就可以了,不需要直接去连接诶需要检查的机器,降低系统的复杂度。 Zookeeper的优势? 1、 源代码开放 2、 已经被证实是高性能,易用的工业级产品 3、 有广泛的应用:Solr、Hadoop、Storm
我的成长(七)--- 矛盾,才是智慧 这几年,每隔半年或者一年的总会写点东西,记记流水账,写写想说的,展望一下未来的事却是很少做的。 过去的这几年一直在坚持着技术开发,但是却不知道自己真正的想要的是什么?自己能做什么,自己前进的方向在哪里?自己的进步也总是达不到自己的预期。 去年8月份有幸带了“jrkj”那个项目,认识了该项目的技术支持(武老师)。让我更加清楚了一个产品是怎么从无到有,如何去规划一个项目,如何去更好的进行项目管理。项目开发的过程中虽然遇到了很多意料之外的问题,但是最终还是在年前上线了。 其中的过程我已经不想再去描述了,在这个过程中我第一次在项目开始透彻的去理解需求去评审、去设计;开始真正意义上的去使用业务驱动而不是页面驱动。 我开始去权衡每一个功能的必要性,开始去使用一个个业务场景去验证数据库设计是否合理,开始制作详细到每半天的工作计划并严格按照计划实施,同时从每天的工作反馈中去修改计划使其趋于合理。 我开始去重视团队建设,去了解团队中每个成员需要什么,尽量的通过项目中让其去获得他们想要的技能。 在这半年里,我也发现要让一个人去相信你是多么的困难,口头上无论如何去描述如果没有让人看到结果,真正能够相信你的就只有亲人和自己了。所以我希望如果您能看到这篇文章,请您记住一句话“不要因为别人不相信你而懊恼,别人并没有做错什么。在你没有做出让人信服的东西之前,任何的不信任都是最正常的表现”。信任与否不重要,重要的是您是否能够让客户,让领导满意的东西。 在这半年里,我最大的收获其实并不是以上的那些。我开始用方法论去解决我遇到的问题,我知道了思考其实是有技术的,我明白了少有人走的路才是成功的路,我了解了我想做什么能做什么,我决定了去珍惜身边的每一个人,去爱我的每一位亲人。 在这半年里,我开始去权衡我做的每一件事是否有意义,有人说我积极主动少了,我想说的是我只是更加理智了。从一开始就想着去影响更多的人更多的事,终究是效果不好,倒不如从身边的人开始影响,慢慢的你会发现你能影响的东西越来越多。 在这半年里,我终于彻底的承认,最难改变的是人的思想。尤其是在自己还不足以影响别人的时候就更不要去尝试用你的观点去影响别人了,再说谁又能保证不成熟的我们,目前的观点能够让今后的自己认同呢? 最后,永远不要高估自己,小瞧他人;永远也不要高估他人,小瞧自己。矛盾,才是智慧。
vagrant --- vagrant部署环境 你是否也想在自己的笔记本上搭个集群做一系列的练习,你是不是经常为开发环境没问题的代码在测试环境下都出现一堆问题而苦恼,你是不是还因为虚拟机太占用笔记本资源而郁闷。 今天通通解决!下面就看看笔者的详细配置,通过这个运维人员可以将最终生产环境的镜像交给每个开发这样就很方便的统一了开发、测试、生产三套环境了。 同时vagrant占用内存较少,能够很方便的启动多个虚拟机,这样也满足了我们在自己笔记本上搭建集群的需求。 详细的配置 1. 安装virtualbox,安装好了就ok了,我们所有的操作不需要图形化页面 2. 安装vagrant,安装完成后执行 vagrant -v 如果可以正确输出版本信息,说明安装成功 3. 假设 centos***.box位于u盘上,位置为f:\setupall\centos***.box a. 将虚拟机文件,加入到镜像列表中,centosMix为我们要通过虚拟机文件,生成的镜像的名字 vagrant box addcentosMix f:\setupall\centos***.box 备注:镜像文件放哪个盘就切换到哪个盘下面操作 b. 查看目前的镜像列表, vagrant box list 如果可以看到 centosMix ,则正确安装了虚拟机镜像 c. 创建一个空的文件夹,比如d:\vagrant_workspace\testVagrant cd d:\vagrant_workspace\testVagrant vagrant init centosMix 初始化一个虚拟机实例 vagrant up 启动虚拟机 注意:启动之前先进入testVagrant文件加重,你会看到一个Vagranfile的文件,打开之后设置IP与你的笔记本在同一个局域网中(以便连接) 在mac系统中,可以直接执行 vagrant ssh 登陆虚拟机 在windows系统中,则需要使用securecrt,或者xshell之类的工具,登陆, ip:127.0.0.1 port:2222 (vagrant up过程中,会打印出端口) user:vagrant pwd:vagrant 注意:连接之前需要先进入设定 进入到虚拟机后,就可以按照实际需求配置您需要的环境了 资源的下载地址: 链接:http://pan.baidu.com/s/1ntYUJgP密码:zz5w 希望本文对能够对读者您起到一定得帮助作用,在安装此虚拟前先记得查看自己笔记本主板是否支持虚拟化并且是否已经打开此功能(笔者刚开始安装的时候就遇到了这种问题)。
NoSQL之Redis(三) --- Redis在项目中的运用 又是一个喧闹的新年,少了的不是年味,变了的是人们的内心。难得的假期静下心来回顾上一年,展望下一年。思考,总结,陪陪家人是否更值得去做?养精蓄锐,是不是又能更好迎接来年的挑战?年轻不是放纵的资本,需要的事更好的珍惜眼前的时光。 本文笔者会简单的描述一下Redis在“jrkj”这个项目中的哪些场景中使用的,以及是如何使用的。 应用场景一 首页 寻找讲师 讲师入门 我们知道在每一个项目中都会存在一些不会经常变动的数据,如果每次需要这些数据的时候都从数据库中读取的话,一方面是效率问题,另一方面也会增加数据库中的压力。 在上述三个地方系统都会默认的先从redis中读取数据,只要将redis中没有数据或者数据不全时才会从数据库中读取,并同时向Redis中备份一份(这样下一个请求来了又会从redis中读取)。 应用场景二 查看更多讲师(功能未上线) 在系统中我们还会遇到一些这样的情况,分页查询查询数据,默认我会将第一页的数据存入缓存中,第一次查询第二页的时候我会将第二页的数据也存入缓存中,以此类推。同样是减少直接对数据库的操作。 应用场景三 个人中心 当用户登录的时候,我会将其个人信息存入redis中,如果用户登录之后在其个人中心的相关页面需要用的其个人信息的地方直接从redis中读取。当用户修改个人信息的时候同时会更新redis中的数据,这样就保证了redis中的数据和数据库中是同步的。 应用场景四 分布式问题 该系统属于分布式系统,不可避免的就有这么一个问题需要解决——如何保证session共享的问题。我使用的是在用户登录的时候生成一个唯一标识(UUID)保证用户的唯一性,将其如用户信息一同放入缓存中,登录之后用户访问任何页面都会带着一个UUID通过这个UUID作为Key可以在缓存中获取该用户的个人信息。 总结 产品年前刚上线,并没有正真的运行起来,需要优化的地方还有很多,年后融资推广什么的希望一切都顺利。其实,刚才在场景一种笔者并没有说明如何将那些静态数据存入redis以及什么时候去更新这些数据,怎么更新这些数据然后存入缓存中,笔者都没有没有详细的写明,欲知原因请阅读下一篇博客。
NoSQL之Redis(二)---Java操作Redis存储自定义类型数据 Redis简介 Redis是一个开源,先进的key-value存储,并用于构建高性能,可扩展的Web应用程序的完美解决方案。 Redis从它的许多竞争继承来的三个主要特点: Redis数据库完全在内存中,使用磁盘仅用于持久性。 相比许多键值数据存储,Redis拥有一套较为丰富的数据类型。 Redis可以将数据复制到任意数量的从服务器。 Redis操作 使用java语言操作redis需要引用jedis的jar包,我用的版本是2.8.0【点击下载】。Redis支持存储的数据类型有String,List,Map,Set,Hash及Zset。 由于简单的操作Redis的基本类型在网上很容易就找到一堆资料,所以今天笔者主要写如何使用redis存储自定义类型。 Redis中支持的基本类型String,而且在我试验的过程中发现无论是List还是Map也都只能放String类型的数据。所以如果我们想要存储自定义类型的数据的时候,不可避免的会有两个过程——序列化与反序列化。 代码 //定义需要存储的数据 StudentVo studentVo = newStudentVo(); studentVo.setId(student.getId()); studentVo.setApplyTeacherState(student.getApplyTeacherState()); studentVo.setBornDate(student.getBornDate()); studentVo.setHeadPic(student.getHeadPic()); studentVo.setIntroduce(student.getIntroduce()); studentVo.setIsTeacher(student.getIsTeacher()); studentVo.setRealName(student.getRealName()); studentVo.setNickName(student.getNickName()); studentVo.setPhoNum(student.getPhoNum()); jedis = new Jedis("XXX.56. XXX.XXX ", 6379); //实例化一个新的jedis对象 UUID uuid = UUID.randomUUID(); String jSession = uuid.toString(); studentVo.setSessionId(jSession); //jSession是用户登录过程中产生的唯一标识 jedis.set(jSession.getBytes(),SerializationUtil.serialize(studentVo)); //SerializationUtil负责序列化与反序列化的类 jedis.expire(jSession, 3600); // 设置过期时间 //上面描述的是如何存储自定义类型,下面是如何使用了 //如果登录系统之后,系统访问链接后面都会带着一个UUID作为唯一标识, //例如:http://www.jrkj.org/itoo-jrkj-homepageset-web/index/63db86a4-12de-443d-bff0-23d4f6ab67c0 byte[] bSession= jedis.get(sessionId.getBytes()); //sessionId是用户的唯一标识, StudentVo student = (StudentVo)SerializationUtil.deserialize(bSession); //通过反序列化就能够获取存储的数据 序列化与反序列化代码 public classSerializationUtil { /** * 序列化 * * @param object * @return */ publicstatic byte[] serialize(Object object) { ObjectOutputStream oos = null; ByteArrayOutputStream baos = null; try { baos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(baos); oos.writeObject(object); byte[] bytes = baos.toByteArray(); return bytes; } catch (Exception e) { } return null; } /** * 反序列化 * * @param bytes * @return */ publicstatic Object deserialize(byte[] bytes) { ByteArrayInputStream bais = null; try { bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais); return ois.readObject(); } catch (Exception e) { } return null; } } 小结 Jedis操作redis存储定义一类型主要就是一个序列化与反序列化的过程,这个之后你就可以存储任意类型的数据了。不妨自己写个demo试试,如何安装redis之前的博客中已经有介绍,也可以在网上找找相关的教程。 笔者在这里提前祝大家新春快乐,希望新的一年里,大家心想事成、阖家幸福!
我的成长(六)--- 对好程序员的思考 最近两个月项目急着上线,一直没有时间停下来记录一下自己的思考。正是由于近两个月的加班,让笔者开始思考一个问题,怎么样才能做一个好程序员?是技术一流,还是踏踏实实?责任心,出了问题的心态,主人公意识… 还是申明一下吧,本文只是笔者个人的一点思考,一点想法,如有雷同纯属巧合,如有不同也请包含。 首先,程序员肯定是公司的一员,所以市面上关于“好员工”的各种要求完全适用。对于技术这块,一个好的程序员肯定是要有一定得解决问题的能力,技术也应该是较好的,当然如果技术一流也会锦上添花。 今天笔者还想说一些其他想写的。 笔者想一个好的程序员应该是有成为产品经理的潜质的,我这里仅就对业务的理解,和对需要的理解方面。很多时候,我们经常会听到一些人感叹,我们不缺懂技术的,也不缺懂业务的,我们缺的是既懂技术又懂业务的。 目前大部分开发的人可能只埋头开发,而不去思考业务。导致很多时候项目出问题不是技术上出问题了,而是对需求的理解上出问题了。如果一个程序员能够在这方面想产品经理一样对自身,对项目都是有百利而无一害的。 笔者最近还有一个项目就是好的程序员应该具备项目经理的潜质,能够更好的去规划自己的任务,而不是被动的去领任务,好的程序员能够分担项目经理的肩上的担子而不是加重它。 所以要成为好的程序员,我们需要关注的可能不仅仅是技术方面的东西,很多时候,我们还需要关心技术之外的一些东西。多看书,多和别人交流,真的很重要。 笔者的这些想法都还不成熟,我会慢慢的去完善这些想法,说不定几年后市面上横空出世一本书《好程序员》,哈哈,希望还是要有的,万一实现了呢?
我的成长(五)--- Make it Possible 文章最开始我想介绍一下自己很喜欢的一家公司宣传视频《Makeit possible》 他们说 前面没有路了 我说,是吗? 路不在脚下,在心里 可能,路上漫步荆棘 可能,风暴随时来临 可能,我会迷失 但我的信仰,从不动摇 因为 在路的另一头 我看见梦想在闪光 一路上 引领我走过黑暗 创造由我定义的可能 在这条叫可能的路上 我早已做了决定 无视挑战和质疑 把难关,当做前进的动力 把逆境,化作开山劈道的勇气 一步一步,不惧不退 机会,只留给无畏的人 来吧,继续开道 将不可能,变作可能 最近两个月项目急着上线,很多时候它都激励着我,以行践言, 是视频想说的,也道出了笔者的心声。 项目背景 8月份开始接手jrkj这个项目,项目经理就一直没有太大的信心这个项目最后能够上线。一方面,项目的开发人员是一直跟着老板表哥(大学教授)做项目的还未有过正式工作的学生,而且这些人里面有涉及到麻烦就要毕业的和随时可能被外包处去的。另一方面,这个项目还是有一定难度的,开发人员并没有深刻认识到这一点。还有一点也是逼着现在觉得特别重要的一点,开发人员的职业态度不够好。 现在想想,可能最初还是太乐观,不光是刚才说的几点都出现了,技术这一点也是些许有些问题的(最要是前端这块)。 项目进展 正如项目经理预计的一样,由于学校里的一些事导致项目设计的过程中磕磕盼盼,并且大部分开发人员对于设计、需求这一块并不上心,一心想着开发。导致在开发的过程中经常会出现因为需求的原因导致的无用功,而且随着项目的进行项目的参与人在不断的流失,整个项目的气氛也不太好。 最后终于只剩下两个人了,项目经理和老板一商量别在学校这个开发了,来北京吧。于是乎又找了几人,最终一行4人就回公司开发了,去了之后测试有专人协助。 其中,一堆邪乎的事,这里不方便细讲。这里笔者着重讲讲最后一个月,4个人的开发状况吧。 当时的项目状态完成了70%业务逻辑,前端已经将需要需要的页面做好了,并且大部分页面数据的显示有30%的概率是没问题的,遗留的工作图片的处理,分布式的处理,多线程处理首页数据更新、自动结账、自动评分,支付,负载均衡,还有一个很重要的工作测试! Makeit possible 反正有过开发经验、项目管理经验的人似乎都不会认为这个项目能够正常的上线,一个月的时候可能连遗留下来的工作都无法完成。 产品经理(技术出身)每周来一次虽然每次来嘴里都说相信年前能做一个bate版的,但是笔者还是从他的言语中感受到了深深的不信任,而这种不信任干却又是笔者深恶痛绝的。 “我应该用行动来说明” 分工 建,进入项目组1个人,负责寻找讲师这个功能以及图片的处理;亮、杰,主要负责controller到页面成数据的处理,登录注册等非核心功能的修改完善,协助郝姐进行测试工作;笔者,由于对业务比较熟悉核心的业务如果有问题我来修复,同时负责每天的任务安排,以及分布式session共享(url后带UUID作为唯一标识+redis);多线程(最后使用的是quartz);负载均衡(nginx + nginx sticky);支付(使用聚合支付提供商BeeCloud). 其他数据整理(公司师资部),图片(美工)。 时间安排 最后一个月吃住就直接在公司了,早九晚十二中午休息45分钟左右,每天吃饭完的时候也会休息半个小时到一个小时。就这样二月二号我还是将其最终挂到了网上www.jrkj.org. 结果 项目按时挂到了网上,可能还有这样那样的问题,但是当初的目标是已经完成了。 感想 项目虽然现在问题还有很多,但是确是已经完成了,项目相关人员都认为完成不了的任务。在最后一个月我没有从领导那里感受到对完成这个任务所拥有的信心,有的只是深深的疑虑,深深的怀疑。 但是很多时候,作为项目经理,你会怎样去面对,作为一个初出茅庐的项目经理你会如何去面对外界的质疑,外界的压力。 没有人相信你,别忘了,有一个人永远可以毫无保留的相信你——你自己!你要对自己有信心,这样你的战友才会有信心,随着一个个问题的解决,这种信心会一步步的提升。才能让项目开发进入一个良性循环。作为项目经理需要给自己信心,需要给开发人员信心。 不要怪别人不相信你,也不要认为产品经理不相信你就去抱怨、去评判产品经理的优劣。你得用行动去证明你是可信的。成功了,你可以大胆的走到产品经理,老板面前去说一句,交给我的事就相信我,我会给你想要的结果(我是真说了,不知道他们会怎么想,哈哈)。 对于喜欢看故事的朋友,笔者这篇博文讲得并不精彩,有好多细节也没有讲明白(可能有些东西目前真不能说),但是笔者最想表达的是,有些东西是不会变得,有些东西是可以改变的。改变自己,相信自己,学会承受压力,学会化解压力。改变心态,工作是为了老板,更是为了自己,让你的价值体现出来。 相信自己,因为你跟着就不知道你有多大的能量。
quartz---任务调度小试 背景 笔者目前做的项目”jrkj“首页上的信息都是从redis中读取的,每小时更新一次存入redis中,那么问题来了怎么才能让系统每个小时执行一次存入数据的方法呢?这个我用到的事quartz任务调度框架。 配置 我的项目用的是springMVC,spring+Ejb,EclipseLink,服务器用的是Jboss。由于项目中用到的Ejb所以在写配置文件applicationContext-common.xml的时候还是需要注意一些东西的,详细见配置文件。 <?xml version="1.0"encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd"> <context:component-scanbase-package="com.tgb.itoo.jrkj.controller" /> <util:properties id="evn" location="classpath:config/jboss-ejb-client.properties"></util:properties> <!-- 启动触发器的配置开始 --> <bean name="startQuertz" lazy-init="false"autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="myJobTrigger" /> <refbean="autoJobTrigger" /> </list> </property> </bean> <!-- 启动触发器的配置结束 --> <!-- 调度的配置开始 --> <!-- quartz-2.x的配置 --> <bean id="myJobTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail"> <ref bean="myJobDetail" /> </property> <property name="cronExpression"> <value>0 0 0/1 * * ?</value> <!-- <value>0 0/1 * * *?</value> --> </property> </bean> <!-- 调度的配置结束 --> <!-- 调度的配置开始 --> <!-- quartz-2.x的配置 --> <bean id="autoJobTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail"> <ref bean="autoJobDetail" /> </property> <property name="cronExpression"> <value>0 0 3 * * ?</value> </property> </bean> <!-- 调度的配置结束 --> <!-- job的配置开始 --> <bean id="autoJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject"> <ref bean="homePageShowController" /> </property> <property name="targetMethod"> <value>autoClassEndOrderLog</value> </property> </bean> <!-- job的配置开始 --> <bean id="myJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject"> <ref bean="homePageShowController" /> </property> <property name="targetMethod"> <value>loadHomeData</value> <!-- <value>run</value>--> </property> </bean> <jee:local-slsb id="homePageShowBean" jndi-name="java:global/itoo-jrkj-homepageset-ear/itoo-jrkj-homepageset-core-0.0.1-SNAPSHOT/homePageShowBeanImpl!com.tgb.itoo.jrkj.service.HomePageShowBean" business-interface="com.tgb.itoo.jrkj.service.HomePageShowBean"/> <bean name="homePageShowController"class="com.tgb.itoo.jrkj.controller.HomePageShowController"> <property name="homePageShowBean"ref="homePageShowBean"></property> </bean> </beans> 配置完成一个之后发现其实还有一些其他的的地方需要配置,所以您会发现上面的配置文件中配置了两个计时器,以及两个任务,如果业务有需要的话,还可以配置更多。 Controller代码如下 @RequestMapping("/loadHomeData") public voidloadHomeData() { System.out.println("test"); List<FieldVo>listFieldVos = new ArrayList<FieldVo>(); …… } 别以为上面配置了,代码写了任务就完成了,jboss还需要配置依赖的jar包。 首先在jboss的modules\org\springframework\spring\snowdrop路径下添加quartz2.2.1.jar(目前我用的版本),然后在该路径下的module.xml中resources节点下添加<resource-rootpath="quartz2.2.1jar"/> <pre name="code" class="plain"><modulexmlns="urn:jboss:module:1.0"name="org.springframework.spring"slot="snowdrop"> <resources> <resource-root path="spring-aop-4.0.9.RELEASE.jar"/> …………… <resource-rootpath="spring-messaging-4.0.9.RELEASE.jar"/> <resource-rootpath="spring-security-config-3.0.2.RELEASE.jar"/> <resource-rootpath="commons-fileupload-1.3.1.jar"/> <resource-rootpath="quartz2.2.1jar"/> ……….. 结果 接下来看看我打印的信息 总之,这样下来在我的项目中是可以正常使用了,但是我想当执行任务调度的时候是不是能够单独的给它新起一个线程,这样会不会更好呢?这块由于这几天项目比较忙一直没弄,总之,看后续更新的博客吧。
运维小知识之nginx---..nginx-sticky-module-1.1ngx_http_sticky_misc.cIn function ‘ngx_http_sticky_misc_text_raw 背景 今天笔者在使用nginx做负载均衡的过程中遇到了一个问题,如何解决session共享的问题,稍一查找发现解决办法不少,笔者使用的是nginx的一直扩展模块(安装和配置已经在前两篇博客有简单的介绍)在安装的过程中居然出现了问题,咱也不是逃避的人,解决吧,见下文。 问题描述 make[1]: Entering directory`/usr/local/nginx-1.7.4' cc -c -pipe -O -W -Wall -Wpointer-arith-Wno-unused-parameter -Werror -g -I src/core-I src/event -I src/event/modules -I src/os/unix -I objs -I src/http -Isrc/http/modules -I src/mail \ -oobjs/addon/nginx-sticky-module-1.1/ngx_http_sticky_misc.o \ ../nginx-sticky-module-1.1/ngx_http_sticky_misc.c cc1: warnings being treated as errors ../nginx-sticky-module-1.1/ngx_http_sticky_misc.c:In function ‘ngx_http_sticky_misc_text_raw’: ../nginx-sticky-module-1.1/ngx_http_sticky_misc.c:281:error: passing argument 2 of ‘ngx_sock_ntop’makes integer from pointer without a cast src/core/ngx_inet.h:110: note: expected ‘socklen_t’ but argument is of type ‘u_char *’ ../nginx-sticky-module-1.1/ngx_http_sticky_misc.c:281:error: passing argument 3 of ‘ngx_sock_ntop’makes pointer from integer without a cast src/core/ngx_inet.h:110: note: expected ‘u_char*’ but argument is of type ‘size_t’ ../nginx-sticky-module-1.1/ngx_http_sticky_misc.c:281:error: too few arguments to function ‘ngx_sock_ntop’ make[1]: ***[objs/addon/nginx-sticky-module-1.1/ngx_http_sticky_misc.o] Error 1 make[1]: Leaving directory`/usr/local/nginx-1.7.4' make: *** [build] Error 2 解决方法 进入nginx-sticky-module-1.1解压之后的文件夹 cd /usr/local/nginx-sticky-module-1.1 编辑文件夹里面的ngx_http_sticky_module.c文件 vi ngx_http_sticky_module.c 找到文件的地281行,做出如下修改 原digest->len =ngx_sock_ntop(in,digest ->data, len, 1); 改后digest->len =ngx_sock_ntop(in,sizeof(struct sockaddr_in),digest ->data, len, 1); 后记 今天状态不做,便把遇到的问题以及解决方法都稍微整理了一下,希望对读者有帮助,其实自己忘了,也能回来看看,一举两得。
Http 400 --- The request sent by the client was syntactically incorrect 问题描述 "The request sent by the client was syntactically incorrect",意思是:客户端发送的请求是语法错误。 问题重现 原因及解决方法 其实这次错误写篇博客是完全没有意义的,因为笔者在下图中的两个参数写得不一致导致的,改成一样的就能解决这个问题。 写这篇博客我更多的是想说最近在测试的时候发现有些页面数据不稳定,有时候没有问题,有时候会报这个错误。经过测试发现竟然是ajax同步和异步执行造成的。先大概说说吧,之后会写详细的文章说明。 页面加载的时候会触发多个同一级别ajax,而这些又是异步提交的,由于执行的时间不稳定,会导致有的先执行完成有的后执行,一旦有已经执行完的过程,就会直接加载页面导致还未执行完成的就出错啦。
JSTL---Servlet.service() for servlet action threw exceptionjavax.el.MethodNotFoundException Method size 背景 最近在项目测试的时候,发现有的JSP页面有时会出现如下错误: JSTL--- Servlet.service() for servlet action threw exceptionjavax.el.MethodNotFoundExceptionMethod size 原因 EL表达式语法错误,页面中绑定数据使用的是JSTL,所以有时会出现判空的情况写法如下: <c:if test = “${list.size>0}”> …… </c:if> 而EL表达式并不支持${list.size>0}这种写法。 解决方法 其实,JSTL是有相关的函数支持获取集合的大小大。我们需要再引入一个标签 <%@taglib uri="http://java.sun.com/jsp/jstl/core"prefix="c"%> <%@taglib prefix="fn"uri="http://java.sun.com/jsp/jstl/functions"%> EL表达式的正确写法 <c:if test = “${fn:length(list)}”> …… </c:if> 更多的知识大家可以去下面的链接地址看看: JSTL官网:https://jstl.java.net/ 开源中国JSTL社区:http://www.oschina.net/question/tag/jstl JSTL标签库:http://www.runoob.com/jsp/jsp-jstl.html
Java---MD5Util加密 最近项目进入测试阶段,之前一些没有考虑到,或者说考虑到一直没有去处理的细节的地方就都得去改了。之前用户注册时密码是明文存储的,负责开发的开发经验比较少,刚刚让其采用java自带的MD5加密工具包简单的实现了一个加密算法。详情如下: import java.security.MessageDigest; public class MD5Util { public final static String MD5(String s) { char hexDigits[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; try { byte[] btInput = s.getBytes(); // 获得MD5摘要算法的 MessageDigest 对象 MessageDigest mdInst = MessageDigest.getInstance("MD5"); // 使用指定的字节更新摘要 mdInst.update(btInput); // 获得密文 byte[] md = mdInst.digest(); // 把密文转换成十六进制的字符串形式 int j = md.length; char str[] = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; str[k++] = hexDigits[byte0 >>> 4 & 0xf]; str[k++] = hexDigits[byte0 & 0xf]; } return new String(str); } catch (Exception e) { e.printStackTrace(); return null; } } } 测试及结果: MD5Util md5Util = new MD5Util(); password = md5Util.MD5(password); System.out.println(password); 当然如果项目安全性级别要求高的话可定是还得通过其他的方法了,本文只是对md5简单的使用。
NoSQL之Redis(一)---CentOS6.5安装Redis 上周由于项目的需要在Linux服务器上安装了一个Redis,这篇博文主要是介绍一下如何在CentOS下安装redis,当然安装之前我们还是先来看看什么事Redis吧。 简介 redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。 安装 一、准备工作 1.下载redis 我用的版本是redis-3.0.6.tar.gz,点击链接免积分下载。 2.上传到服务器上 上传的方式很多,可以通过命令行,也可以使用工具;我用的是SecureFXPortable,上传到了/usr/local文件夹下 3.解压 先使用命令,通过下图发现redis安装包已经上传 cd /user/local/ 进入该文件夹,在使用如下命令解压压缩包 tar zxvf redis-3.0.6.tar.gz 二、正式安装 1、 编译 进入解压后redis的目录使用make命令编译,如下 cd redis-3.0.6/ make 编译完成之后的截图 2.正式安装 进入src文件夹使用makeinstall命令安装如下图 此时redis已经安装成功。 三、启动并配置 1.启动redis,如下图使用命令 ./redis-server 2.修改配置文件 出现上图之后说明redis安装成功了,关闭redis之后(Ctrl + C)就该配置了,如下图。 vi redis.conf 找到如下图的地方修改 3.后台启动 使用如下图命令 ./redis-server /usr/local/redis-3.0.6/redis.conf 4.查看redis是否启动 ps –ef | grep redis 总结 安装好了之后肯定是要用的接下来的文章里,您就会知道如何去使用redis,为什么去用它。如果您也感兴趣不妨自己安装一个虚拟机也试试。
运维入门---CentOS6.5安装Nexus 背景 同上一篇博文 安装 1、解压nexus-2.11.1-01-bundle.tar.gz文件: cd /usr/local/software --进入软件包所在目录 tar zxvf nexus-2.11.1-01-bundle.tar.gz -- 解压文件 2、移动目录 #mv nexus-2.11.1-01 /usr/local/nexus 3、修改jdk的配置 打开 nexus\bin\jsw\conf下的配置文件wrapper.conf vi /usr/local/nexus/bin/jsw/conf/wrapper.conf 修改JDK的路径: wrapper.java.command={yourjdk path}\java 使用which java(找到java的执行路径) 例如 wrapper.java.command=/usr/local/jdk1.8/bin/java 保存退出 4、Nexus启动 nexus启动是在bin目录下,首先看一下启动/关闭/重启等命令,输入命令: #cd /usr/local/nexus/bin #ls 出现如下选项: 启动nexus: #./nexus start 关闭nexus: #./nexus stop 5、Nexus验证 启动nexus后,在本机浏览器输入地址:http://1**.*6.1**.***:8081/nexus 出现上述页面,说明配置nexus成功! 点击右上角“Log in”,输入用户名和密码(默认用户名:admin 密码: admin123)登录
运维入门---centOS6.5安装Jenkins 一、背景 当然是项目需要,将之前在公司内部使用的环境在云服务器上也搭建起来,公司继续集成使用的是Jenkins,下面就是安装Jenkins的方法。 二、先安装JAVA 安装jenkins之前先确保系统中已经安装JDK(本文不再介绍JDK的安装),使用命令 java –version 三、安装jenkins sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo sudo rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key yum install jenkins 四、设置jenkins 端口和用户 sudo vi /etc/sysconfig/jenkins 五、安装目录和日志 /usr/lib/jenkins #安装目录 /var/log/jenkins #日志目录 六、jenkins启动文件 sudo /sbin/service jenkins start 七、访问jenkins 访问:http://123.**.***.121:8080/ 当然,这种方式并不是唯一的,装完之后我又买了本持续集成的书(《持续集成》,博文视点出品的,个人觉得其出品的书含金量不高)看了看,发现其实还有更加简单的方法,安装一个tomcat,然后把Jenkins的war包放到tomcat的webapp文件加下,启动tomcat直接访问Jenkins就能够配置了。
运维入门---修改MySQL密码 背景 说起这篇博文,就不得不提笔者12月初的一次出差。一天今天找笔者交代“咱有个外包的项目下周四你可能得出一趟差”,然后我还是做我之前的开发,就在去的前一天,我终于见到了项目的开发者。了解了一下需求之后,感觉什么问题都没有就出发了。 哪知这次出差就是个坑啊!跟我说的环境都是好了,结果JDK版本不对,Mysql密码错了,IP设置不对,没有考虑到服务器的无法连接外网…… 这篇博客就先讲讲如何修改MySQL密码吧 MySQL密码修改 1. 首先确认服务器出于安全的状态, 即是是没有人能够任意地连接MySQL数据库。因为在重新设置MySQL的root密码的期间,MySQL数据库完全出于没有密码保护的状态下,其他的用户也可以任意地登录和修改MySQL的信息。可以采用将MySQL对外的端口封闭,并且停止Apache以及所有的用户进程的方法实现服务器的准安全状态。最安全的状态是到服务器的Console上面操作,并且拔掉网线。 2.修改MySQL的登录设置: # vi/etc/my.cnf 在[mysqld]的段中加上一句:skip-grant-tables 保存并且退出vi。 3.重新启动mysqld #/etc/init.d/mysqld restart 4.登录并修改MySQL的root密码 5.将MySQL的登录设置修改回来 # vi/etc/my.cnf 6.重新启动mysqld #/etc/init.d/mysqld restart 总之这次出差收获挺大的,虽然在培训的时候出现了这样那样的问题(毕竟之前一个没有看过产品,就在培训的当天早晨花了一个小时体验一下系统。中午吃饭的时候做了个PPT下午就直接演示+培训了。这样的出差虽然有点惊险,但是正是这些问题替公司检验了笔者的能力。
运维入门---Linux系统下启动SVN问题 最近开发由于公司业务的需要,将目前正在开发项目的svn迁移到云服务器(不是笔者安装的)上有一天重启服务器之后发现了SVN连接不上了(见下图)。输入命令:svnserver -d -r /home/lyh/svn/store,依然出现同样的问题。 最后发现是没有设置监听的IP,设置好监听的IP如下命令 svnserve -d -r /home/svn/repos --listen-host 123.**.120.** 这样的话就SVN就启动成功了。 这这个过程中,如果先错误的启动之后再使用正确的命令启动会提示如下信息 这个时候得先启动使用命令 ps –ef|grepsvnserve 查看正在运行的svn进程然后使用命令 kill -9 2235 (杀死2235进程) 最后使用正确的命令就Ok啦。 后来想想这样其实也挺麻烦的虽然服务器不常重启,但是每次重启都这样配置一遍也挺麻烦的,最后写了个脚本每次开机是直接启动就Ok啦。 设置开机启动 1、创建执行脚本svn.sh(/root路径下),内容如下: #!/bin/bash Svnserve –d –r /home/svnroot/repository 2、添加执行权限 # chmod ug+x/root/svn.sh 3、添加自动运行 # vi/etc/init.d/rc.d/rc.local 向其中添加/root/svn.sh 4、保存退出 按键盘上Esc然后“:wq”保存文件并退出vi 最后自己重启试试就OK啦,这样才能一劳永逸,刚开始写的几行命令可能让我们以后少去好多麻烦。
-bash: /usr/local/maven/apache-maven-3.2.3/bin/mvn: Permission denied 背景: 刚刚在Linux系统系执行mvn -v的时候出现了下面的错误(如下图),而这种错误一看就是权限的问题,只需设置好权限就OK。 解决方法: 输入:chmod a+x/opt/apache-maven-3.2.2/bin/mvn(如下图) 扩展: options: -c,--changes 只输出被改变文件的信息 -f,--silent,--quiet 当chmod不能改变文件模式时,不通知文件的用户 --help 输出帮助信息。 -R,--recursive 可递归遍历子目录,把修改应到目录下所有文件和子目录 --reference=filename 参照filename的权限来设置权限 -v,--verbose 无论修改是否成功,输出每个文件的信息 --version 输出版本信息。 who u用户 g组 o其它 a所有用户(默认) opcode +增加权限 -删除权限 =重新分配权限 permission r读 w写 x执行 s设置用户(或组)的ID号 t设置粘着位(sticky bit),防止文件或目录被非属主删除 u用户的当前权限 g组的当前权限 o其他用户的当前权限
If you insist running as root, then set theenvironment variable RUN_AS_USER=root before running this script. 背景 今天笔者在配置Nexus的时输入命令 ./nexus start后出现“ If you insist running as root, then set thee nvironment variable RUN_AS_USER=root before running this script. ”的警告信息。大概的意思是:如果你想使用root用户,那么在运行开始脚本之前应该设置环境变量”RUN_AS_USER=root"。 解决方法 关于这个问题共有两种解决方法,一种是临时解决,一种是永久的解决。 临时方法 输入:export RUN_AS_USER=root 后在执行 ./nexus start 运行结果如下图: 永久方法 在系统用配置即可,输入:vi /etc/profile向其中加入exportRUN_AS_USER=root,修改后保存退出,如下图 修改完之后,先关闭nexus,再启动,如下图 简简单单的几步就OK啦
我的成长(三)---沟通真的很重要 最近接触了一个项目组,项目在开发的过程中,发现项目组的开发人员有时比较迷茫;问其原因,项目的需求经常变,不明确。笔者心想,项目开发过程中遇到需求的变更不是很正常吗,随着问题的深入发现并不是需求不明确,而是项目经理和项目组长之间的交流出现了问题。 于是我想了想平时工作中的一些事,无论在什么时候,沟通无不重要的。遇到过技术很牛,表达不行的没拿到一份好的Offer的,也遇到过技术还行,沟通能力强的拿到一个好的工资的。 最近在沟通这块自己也小有感触,下面就说说我最近的一点点理解吧 言之有物---不要使用不定代词 说到这点,我最先想到的是刚刚接触电脑的时候,老师给我们讲解其相关的知识。 “大家按下这个地方的那个按钮,就是开机” “点屏幕上的这个图标,就能看到书上XX页的哪张图片” 可能一节课下来,我们只记住了这个那个,对计算机方面的知识的理解也仅仅停留在“这个”“那个”上,对于某些对新生事物接受起来慢的同学可能这辈子就和计算机相关的知识说拜拜了。 我们也可以试想一下,如果项目组正在梳理需求、设计流程等过程中全是这样的不定代词,会议的参与人员需要怎样的集中精力才能正确的理解需求啊。如果您参与过这样的会议,笔者相信您一定深有体会。 回答问题---一定要简单、直接 不知道大家有没有遇到过这样的问题,笔者在工作的过程中曾经遇到过这样的人,每次和他交流起来就感觉是一种挑战。 问:“1+1=?“ 答:”这个问题是不是太简单了,你是不是在怀疑我的智商?“ 问:”你直接跟我说答案就行“ 答:”&%¥#@“(反正就不回答问题) 这样的沟通往往会花费我们很多时间,而且沟通的结果也不好。最好的情况是什么? 问什么,答什么。 问:”1+1=?“ 答:”2“ 当项目经理,产品经理的人都很忙,替他们多想想吧,让彼此的沟通更加高效。 汇报情况---别只靠脑子 为什么说别只靠脑子,想想吧,记是记不住的。当你想向你的上级汇报什么东西的时候,能够使用邮件的就不要用脑子记,用嘴说。如果真的需要面对面的说,最好是能够有文字方面的东西,千万不要有个什么东西就直接说了,万一在汇报的时候忘了怎么办? 分配任务---坚定、干脆 作为一个分配任务的人,如果你在分配任务或者执行什么命令的时候有半点的犹豫都会让你手下的人不知所措。虽然在敏捷开发过程中强调每个人的主动性,但是在现实的开发过程中,如果项目组长不明确任务,开发人员往往会不知道做什么。 以上是最近的一点感受,笔者希望能够对看过此文的人有所帮助!最近的一个项目马上就进入实现阶段了,今后这个系列的博文应该会更多的涉及技术方面的知识,同时在文章最后都会附上我最近在看的书名。 附:最近我在看的书:《软件架构师的12项修炼》
SSH---Spring减少配置量将公共的配置进行抽象 最近做项目的过程中遇到一个关于Spring配置特别基础的问题——减少配置量将公共的配置进行抽象。为此特地翻看以前看过的视频刚好有类似的Demo,所以就借用一下分享给大家。 抽象前 配置文件大致如下(只将可以抽象的地方贴出来): <bean id="bean2" class="com.tgb.spring.Bean2"> <property name="id"value="100"/> <propertyname="name" value="zhangsan"/> <propertyname="sex" value="nan"/> </bean> <beanid="bean3" class="com.tgb.spring.Bean3"> <propertyname="id" value="100"/> <propertyname="name" value="zhangsan"/> <propertyname="sex" value="nan"/> <property name="age"> <value>90</value> </property> <property name="password" value="123"/> </bean> <beanid="bean4" class="com.tgb.spring.Bean4"> <propertyname="id" value="100"/> <propertyname="name" value="zhangsan"/> <propertyname="sex" value="nan"/> <propertyname="age"> <value>90</value> </property> </bean> <bean id="bean5" class="com.tgb.spring.Bean5"> <property name="password" value="123"/> </bean> 抽象后 抽象之后配置文件看起来就舒服多了,如下: <beanid="bean2" class="com.tgb.spring.Bean2"> <propertyname="bean3" ref="bean3"/> <propertyname="bean4"> <refbean="bean4"/> </property> <propertyname="bean5" ref="bean5"/> </bean> <bean id="bean3"class="com.tgb.spring.Bean3"> <property name="id"value="100"/> <property name="name"value="zhangsan"/> <property name="sex"value="nan"/> </bean> <bean id="bean4"class="com.tgb.spring.Bean4"> <property name="id"value="100"/> <property name="name"value="zhangsan"/> <property name="sex"value="nan"/> <propertyname="age"> <value>90</value> </property> </bean> <bean id="bean5"class="com.tgb.spring.Bean5"> <propertyname="password" value="123"/> </bean> 总结 其实挺简单的只是如果我们在写代码的时候如果能够多注意一些这样的细节我们能够让我们的代码看起来更加简洁,另外一方面,笔者觉得无论是写代码不光自己能看懂,也要方便其他人审阅。 笔者想做好每一件经过自己手的事,让每一个让笔者干活的人放心。 本文资源链接http://download.csdn.net/detail/senior_lee/9068837
SSH---集成Struts2+Spring+Hibernate(一) 本方案让Spring创建Struts2的Action,不让Spring完全管理Struts2的Action,Struts2Action中的依赖对象,默认会根据名称自动装配 1、创建web项目 2、引入Struts2的依赖包,将依赖包拷贝到WEB-INF/lib下 *commons-logging-1.0.4.jar *freemarker-2.3.15.jar *ognl-2.7.3.jar * struts2-core-2.1.8.1.jar *xwork-core-2.1.6.jar *commons-fileupload-1.2.1.jar 3、引入Spring的依赖包,将依赖包拷贝到WEB-INF/lib下 *spring.jar *lib/aspectj/*.jar 4、引入hibernate相关依赖包,将依赖包拷贝到WEB-INF/lib下 *hibernate3.jar *lib/*.jar 5、数据库驱动 *MySQl JDBC Driver 6、将Struts2和Spring集成的依赖包拷贝到WEB-INF/lib下 *struts2-spring-plugin-2.1.8.1.jar 7、在web.xml文件中配置StrutsPrepareAndExecuteFilter <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 8、提供struts.xml配置文件,提供必要属性的配置 *struts.i18n.encoding=GB18030 * struts.configuration.xml.reload=true *struts.devMode=true 9、提供Spring的配置文件 *applicationContext-service.xml *applicationContext-dao.xml *applicationContext-common.xml 10、提供hibernate.cfg.xml配置文件,提供log4j 11、在web.xml文件中配置Spring的ContextLoaderListener,创建BeanFactory <context-param> <param-name>contextConfigLocation</param-name> <!-- <param-value>classpath:applicationContext-*.xml</param-value> --> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> 12、在web.xml文件中配置OpenSessionInViewFilter(需要放到Struts2的Filter前面) <filter> <filter-name>OpenSessionInViewFilter</filter-name> <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class> </filter> <filter-mapping> <filter-name>OpenSessionInViewFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 13、提供用户添加表单和add_success.jsp 14、建立User实体类,编写hibernate映射文件,将映射文件加入到hibernate.cfg.xml中 <mapping resource="/com/tgb/usermgr/domain/User.hbm.xml"/> 15、建立UserDao和UserService,并进行实现 UserDao //UserDao接口 package com.tgb.usermgr.dao; import com.tgb.usermgr.domain.User; public interfaceUserDao { public void add(User user); } //UserDao实现 packagecom.tgb.usermgr.dao.impl; importorg.springframework.orm.hibernate3.support.HibernateDaoSupport; importcom.tgb.usermgr.dao.UserDao; importcom.tgb.usermgr.domain.User; publicclass UserDaoImpl extends HibernateDaoSupport implements UserDao { public void add(User user) { getHibernateTemplate().save(user); } } UserService //UserService接口 package com.tgb.usermgr.service; import com.tgb.usermgr.domain.User; public interfaceUserService { public void add(User user); } //UserService实现 packagecom.tgb.usermgr.service.impl; importcom.tgb.usermgr.dao.UserDao; importcom.tgb.usermgr.domain.User; importcom.tgb.usermgr.service.UserService; publicclass UserServiceImpl implements UserService { private UserDao userDao; public void add(User user) { userDao.add(user); } public void setUserDao(UserDao userDao) { this.userDao = userDao; } } 16、建立Struts2的Action,并配置到Struts2的配置文件中 <struts> <constant name="struts.i18n.encoding" value="GB18030"/> <constant name="struts.configuration.xml.reload" value="true"/> <constant name="struts.devMode" value="true"/> <!-- 默认根据名称自动装配Action中的依赖对象,现在修改为根据类型 --> <constant name="struts.objectFactory.spring.autoWire"value="type"/> <package name="user-package" extends="struts-default" namespace="/user"> <action name="add" class="com.tgb.usermgr.web.action.AddUserAction"> <result>/add_success.jsp</result> </action> </package> </struts> 17、在jsp中调用Action <body> <form action="user/add.action"> 用户代码:<input type="text" name="user.userCode"><br> 用户姓名:<input type="text" name="user.userName"><br> 年龄:<input type="text" name="user.age"><br> <input type="submit"value="添加"> </form> </body> 最后附上框架的下载链接:http://download.csdn.net/detail/senior_lee/9059887
SSH-Struts2简单的自定义拦截器MethodFilterInterceptor 最近业余时间工作之余也在学习SSH相关的知识,今天刚刚尝试写了一个基础的Struts2拦截器通过继承MethodFilterInterceptor方法。 一、什么是拦截器 拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行,同时也提供了一种可以提取action中可重用部分的方式。 二、本文的实现 1、定义一个拦截器 import org.apache.struts2.ServletActionContext; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor; /** *权限校验的拦截器 * @author CodyLee * */ public class PrivilegeInterceptor extends MethodFilterInterceptor{ @Override //执行拦截的方法 protected String doIntercept(ActionInvocation actionInvocation) throws Exception{ //判断是否登陆,如果登陆,放行;如果没有登陆,跳转到登陆页面 AdminUser adminUser = (AdminUser)ServletActionContext.getRequest().getSession().getAttribute("existAdminUser"); if(adminUser != null){ //已经登陆过 return actionInvocation.invoke(); }else{ //跳转到登陆页面 ActionSupport support = (ActionSupport)actionInvocation.getAction(); support.addActionError("您还没有登陆!没有权限访问!"); return ActionSupport.LOGIN; } } } 2、配置自定义拦截器 <!-- 配置自定义拦截器 --> <interceptors> <interceptor name="privilegeInterceptor" class="cn.itcast.shop.interceptor.PrivilegeInterceptor"/> </interceptors> 3、Action中添加拦截器 <action name="adminCategory_*" class="adminCategoryAction" method="{1}"> <result name="findAll">/admin/category/list.jsp</result> <interceptor-ref name="privilegeInterceptor"/> <interceptor-ref name="defaultStack"/> </action> 三、注意事项 在Struts2中拦截器的接口是Interceptor而其实现类AbstractInterceptor是Interceptor的实现类,接口中的一般方法都实现了;本文中所用到的MethodFilterInterceptor又继承了AbstractInterceptor。
新建网站本机能访问外网无法访问解决办法 之前带着几个人给某事业单位运用公司新近研发的的一套框架建了个网站,在局域网中试运行了20来天之后应客户的要求发布到公网上之后,出现了无法访问的问题。 (其他的像备案,域名,独立IP等之前已经全部都弄好了,通过外网访问的时候端口都对应的打开了,防火墙等因素也不予考虑) 服务器环境 WinServer2008 + IIS7 问题描述 能够Ping通,本机可以访问,外网无法访问。 解决方法 因为网站刚发布的时候使用的是80端口,通过网上了解相关的信息是:80端口默认是关闭的需要联系网络运营商打开80端口。于是我就用了其他端口测试,结果发现还是不行,服务端和客户端是分开部署的,发现服务端可以正常访问,其编辑网站绑定时“IP地址”选择的是全部未分配。 改完之后客户端就能够通过外网访问了。 疑问 为什么直接写IP地址不行,选择全部未分配就行呢?在选择“全部未分配”的时候会自动使用电脑的IP但是我直接写IP的时候也是写的电脑IP(由于服务器只是用了一个网卡因此可以认为两者所指向的IP地址相同),为什么前者行,后者不行呢? 下回分晓! 2016年1月31日21:30:30更新 最后发现还是因为接入商那边没有开80端口,可能电信那边负责这个是的工程师也不是很熟悉,最后将其请到单位机房现场给其展示才让其相信是因为80端口没有开。
网站建设---最近的一些收获 一直自认为自己是一个愿意去思考的人,也经常去想身边发生的事。只不过记录的比较少很多时候同样的事情都不知道自己是否已经思考了很多遍,是不是上次就已经思考到了问题的答案,而这次还在苦苦思索。 最近资历尚浅的我得到了一个单独带项目的机会,虽然项目很简单---给某事业单位建设门户网站。但是在带这个项目的过程中,还是深深的觉得事情远远不是自己想象的那么简单。 问题一:不断改变的需求 其实如果仅仅是做出一个大众化的门户网站出来,按照目前的编码能力一周左右就能完成,但是由于客户一直在改变页面样式,主要是新闻的具体显示样式(需要每一篇新闻都有独立的风格;字体、字号、颜色、背景、图文的环绕方式等等)和网页Header部分(虽然一直觉得客户的要求的很Ugly,但是只要给的钱足够,我都应该做)。 问题二:如何带团队 这次我们开发组算上我就四个人,其中两个算是新人吧,如何去安排每个人的任务,最大限度的去发挥每个人的价值就是一个问题了。一方面,通过每天的日报详细的了解大家的具体工作情况,另一方面通过每天下班前的代码审查进一步仔细的了解新人的状况。 由于需求比较简单所以在任务划分的时候结合的可能出现的难点,按模块去划分了每个人的职责。遇到难点了一般是我和另一个有一定开发经验的人(主要是他)去实现,做出demo后让新人直接模仿就行。 问题三:新人问题 我想这个每个项目组都会遇到的问题,一般新人加入的初期,总会出现这样那样的问题。 就像这次,在我不知情的情况下新人修改了客户提供的文字资料,这个对于正在给客户演示的我来说真是个惊喜。最后用了句“目前这些都是测试数据”蒙混过关了。 而且在团队开发过程中,由于不熟悉SVN的使用也出现过一点点小的问题。 最重要的一点就是新人的情绪问题,我认为作为一个项目组长这方面也是需要特别注意的不能因此而影响到了怎么团队的氛围。 总之,这次项目开发公司这边催着也不紧,客户那边也不着急,一定得抓住这个难得好机会需要我好好把握。
前言 富文本编辑器,就是除了能输入不同的文本之外,还可以之间粘贴图画等其他的多媒体信息。也可说是所见即所得的编辑器。 目前可以使用的编辑器有很多, 在网络上有找到这样一份比较表格: 编辑器 产地 稳定 是否 轻量 技术 支持 主要优点 主要不足 Ver 速度 肥瘦 (MB) CKEditor 国外老牌 稳定 否 团队 功能强大,稳定 臃肿,加载慢 3.6 4 0.90 KindEditor 国产(上海-浩跃软件) 轻量 插件扩展 4.0 2 0.24 xhEditor 国产(台州-[王一]) 差 轻量 个人 迷你高效,插件扩展 表格编辑,不稳定 1.1 1 0.49 UEditor 国产(百度) 轻量 百度 小巧,分层架构 1.1 3 0.44 TinyMCE 国外老牌 轻量 素雅清新,轻量级 3.4 6 0.99 FCKEditor 国外老牌[已经退役] --- --- --- ----- ----- 2.6 4 1.11 eWebEditor 国产(福州-极限软件) 功能齐全强大 收费,要插件 7.3 - * 肥瘦: 指体积大小,单位是MB, 此参数为经过本人处理修改过的目录大小, 如KindEditor的表情图片我移走了, 在此不计算体积; * 速度: 我比较测试,数字越小,速度越快; * eWebEditor: 商业化太浓, 未测试(也没有[得不到]它的最新版); 需要说明的是: CKEditor是由Fckeditor更名而来。 优缺点 1、ewebeditor(国产,http://www.ewebeditor.net/),优点,功能很强大;缺点:使用需要收费,笨重,速度慢。 2、xheditor(国产,http://xheditor.com/) ,优点:开源免费,轻量、快速、简单,用 JQuery 开发,尤其喜欢它的文件上传;缺点:当前版本不支持对表格的单元格的合并、拆分、单元格属性的修改。 3、CKEditor/FckEditor(国外,http://ckeditor.com),优点:开源免费,功能强大,完整的二次开发接口和文档,可以添加编辑 Form 表单元素(如按钮、输入框等),是开发自定义表单的不二选择;缺点,笨重,不常用的功能,如对 Form 表单元素的修改编辑 bug 稍多。 4、UEditor(国产,http://ueditor.baidu.com/),优点,开源免费,表格的编辑是我用过的编辑器中最灵活的,如支持单元格合并拆分等,二次加载速度快,有百度公司的支持,缺点,文档较少,文件上传部分找不到任何文档,虽然自称轻量,但其全功能、去除 ui 后、压缩后的js也将近300k,第一次加载速度慢。 5、kindEditor(国产,http://www.kindsoft.net/),优点,开源免费,轻量,加载速度快,文档齐全;缺点,不支持对表格的单元格的合并、拆分。 如果不需要表格合并和拆分功能、同时使用 jquery 的话,xheditor 是最好的选择;如果不想用 jquery,或对 jquery 比较陌生,可以考虑 kindeditor; 如果需要做自定义表单,比如,编辑页面上要添加表单功能,ckeditor 是最好的选择; 如果费用非常充足,客户端机器配置较高,并且在局域网使用,可以考虑 ewebeditor; 如果对表格编辑有较高的要求,尤其是需要单元格合并、拆分功能的话,Ueditor 是个不错的选择。 补充 CSDN 使用的就是 xheditor编辑器。 xheditor 结合另外一个组件SyntaxHighlighter(一个用于高亮显示代码文本的js组件)可以实现插入代码高亮的效果。
Oracle---使用PL/SQL Developer连接Oracle12C(64位)版本 1、安装Oracle 12c 64位 2、安装32位的Oracle客户端( instantclient-basic-nt-12.1.0.1.0) 下载instantclient-basic-nt-12.1.0.1.0.zip,将其解压至Oracle安装目录的Product下(本机命名为:instantclient_12 _1):C: \app\orcl\product\instantclient _12 _1 。 拷贝数据库安装根目录下的一个目录C: \app\orcl \product\12.1.0\dbhome_1\NETWORK到Oracle客户端目录下C: \app\orcl \product\instantclient_12_1(其实只需要 NETWORK\ADMIN\tnsnames.ora) 3、安装PL/SQL Developer 安装 PL/SQL Developer,在工具->首选项 连接里面设置OCI Library和Oracle_Home,例如本机设置为: OracleHome :C:\app\orcl\product\instantclient_12_1 OCILibrary :C:\app\orcl\product\instantclient_12_1\oci.dll 文中所有出现的路径均为自己所安装的具体路径,每个人装的过程中可能会有略微的不同,在安装Oracle12c的时候和安装客户端的时候一定都要注意设置环境变量。
SQLServer---查询过程中的数据类型转化 前两天在维护某市人才服务中心的人事档案管理系统的时候,发现了这个一个问题新的档案编号规则是日期+已有档案最大编号+1(六位,不足六位在中间补零)(((CONVERT([varchar](9),[createTime],(112))+'')+right((100000000000.)+[num],(6))))例如:20150511007841。说实话真的不清楚当时为什么会用最大编号,而不是用总记录数+1(不存在删除数据),接下来说说遇到的问题,以及解决方法吧。 问题再现 查询表中总记录数+1 select count(number)+1 from T_UniversityStudent 执行结果 查询表中最大数据+1 select MAX(number)+1fromT_UniversityStudent 执行结果 问题原因 为什么明明数据库中有了7840条数据,而数据记录是每次加1,那为什么会造成这中问题呢? 我想有经验的开发者,已经知道了,是不是数据类型有问题了。在字符串的大小比较中9>1000是成立的,也就是说999>7840也是正确的。 我们通过排序来验证是否上述的说法是正确的 select number from T_UniversityStudentorderbynumber asc 执行结果 问题解决 既然我们已经知道了是字段设计的过程中存在问题,那么我们最简单的就是讲字段的数据类型改了,但由于我们不了解修改数据类型之后会不会造成不良的后果,因此这种方法并不是最好的。 这时我们就能用到cast函数和convert函数了。 具体的用法如下 通过将varchar类型转化成int类型排序(使用convert函数) select number from T_UniversityStudentorderbyconvert(int,number)asc 执行结果 将查询字段中的varchar转换成int select MAX(cast(numberasint))+1fromT_UniversityStudent 执行结果 需要注意的是convert函数只能运用在SQLServer中而cast函数可以用在oracle和SQL Server中,至于其他数据库中是否支持还需要读者自己尝试了。
自建JS代码库(1)---添加用户的常用验证 大家都知道现在有许多比较成熟的javascript代码库,比如:JQuery,Prototype等,里面有许多经过验证的非常好用的函数.这些优秀的代码库能够提高我们的开发效率,但是我们在开发过程中同时还会遇到一些经常使用的方法,而这些可能写起来也比较的繁琐,这个时候我们有自己的代码库直接Copy代码是不是能够让你心情愉悦呢? 今天简单的整理了一下,在项目开发过程中添加系统用户的时候可能会使用到的一些常用的验证。 1、判断用户名只能是字母或数字,且长度为4~6位 //1、常规方法---判断用户名只能是字母或数据,且长度为4~6位 if (!(trim(userIdField.value).length >=4&& trim(userIdField.value).length <=6)) { alert("用户代码只能为4~6位!!"); userIdField.focus(); return; } for (var i=0;i<trim(userIdField.value).length; i++) { varc = trim(userIdField.value).charAt(i); if(!((c >= '0' && c <='9') || (c >='a' && c <='z') ||(c >='A' && c <='Z'))) { alert("用户代码必须为数字和字母!"); userIdField.focus(); return; } } //2、正则表达式---判断用户名只能是字母或数据,且长度为4~6位 var re = new RegExp(/^[a-zA-Z0-9]{4,6}$/); if (!re.test(trim(userIdField.value))) { alert("用户代码必须为数字或字母,只能为4~6位!"); userIdField.focus(); return; } 2、判断联系电话都是数字(不为空时) var contactTelField =document.getElementById("contactTel"); //不采用正则 if (trim(contactTelField.value) !="") { for(var i=0; i<trim(contactTelField.value).length; i++) { varc = trim(contactTelField.value).charAt(i); if(!(c >= '0' && c <= '9')) { alert("电话号码不合法!"); contactTelField.focus(); return; } } } //采用正则 if (trim(contactTelField.value) !="") { //采用正则 re.compile(/^[0-9]*$/); if(!re.test(trim(contactTelField.value))) { alert("电话号码不合法!"); contactTelField.focus(); return; } } 3、判断Email是否包含@(Email不为空,且@不再首尾处) var emailField =document.getElementById("email"); if (trim(emailField.value).length != 0) { varemailValue = trim(emailField.value); if((emailValue.indexOf("@") == 0) || (emailValue.indexOf("@")== (emailValue.length - 1))) { alert("email地址不正确!"); emailField.focus(); return; } if(emailValue.indexOf("@") < 0) { alert("email地址不正确!"); emailField.focus(); return; } } 曾经听人说过一个好的程序员不是你能够花多少时间写出一个东西,而是能用最短的时候做出某一个东西,这就需要我们有很好的积累,见识过很多不同的代码,并且在需要用的时候能够快速的将它运用到自己的项目中。 我想这就得从编写自己的代码库开始了。
Casewhen 用法简介 上一篇博客使用了casewhen解决了数据汇总的问题,那么这一篇博客我将简单的介绍一下关于case when的使用。 CASEWHEN的表达形式 1、简单的case函数 --简单Case函数 CASE letterType WHEN '干部介绍信' THEN '1' WHEN '转递档案通知单' THEN '2' ELSE '其他' END 2、case搜索函数 --Case搜索函数 CASE WHEN letterType='干部介绍信' THEN '1' WHEN letterType='转递档案通知单' THEN '2' ELSE '其他' END 简单Case函数的写法相对比较简洁,但是和Case搜索函数相比,功能方面会有些限制,比如写判断式。还有一个需要注意的问题,Case函数只返回第一个符合条件的值,剩下的Case部分将会被自动忽略。 CASEWHEN 在语句中不同位置的用法 1、 SELECT CASE WHEN用法 select userID , count(CASE WHEN letterType='干部介绍信' then '1' end)干部介绍信数, count(CASE WHEN letterType='转递档案通知单' then '1' end)转递档案通知单数 from T_LettersRecord GROUP BY userID 运行结果: 2、 WHERE CASE WHEN 用法 SELECT l.letterType, u.realName FROM T_LettersRecord as l, T_User as u WHERE (CASE WHEN l.letterType = '干部介绍信' AND u.userID = '1' THEN1 WHENl.letterType = '干部介绍信' AND u.userID <> '1' THEN1 ELSE0 END) = 1 运行结果: 3、 GROUP BY CASE WHEN 用法 SELECT CASE WHEN salary <= 3000 THEN 'T1' WHEN salary > 3000 AND salary <=8000 THEN'T2' WHEN salary > 8000 AND salary <=12000 THEN'T3' WHEN salary > 12000 AND salary <= 20000 THEN 'T4' ELSE NULL END 级别名称, -- 别名命名 COUNT(*) FROM t_userSalary GROUP BY CASE WHEN salary <= 3000 THEN 'T1' WHEN salary > 3000 AND salary <=8000 THEN'T2' WHEN salary > 8000 AND salary <=12000 THEN'T3' WHEN salary > 12000 AND salary <= 20000 THEN 'T4' ELSE NULL END; 运行结果: 本文简单的介绍了一下CASEWHEN的简单使用方法,希望能够对读者能够有所帮助。 参考文章: http://www.cnblogs.com/eshizhan/archive/2012/04/06/2435493.html(eshizhan) http://www.cnblogs.com/yazdao/archive/2009/12/09/1620482.html(影子网络科技有限公司)
编程中无法回避的基础知识---事务 进行软件开发已经有一段时间了,有些东西虽然一直在用但是并不是很理解为什么去用它,它的机制又是什么,是不是还有其他的用途?就像我们在对数据库进行一系列操作时,我们为了保证数据的一致性往往会用到事务。本文将简单的介绍一下事务的相关知识,和简单用法。 基本概念 定义 事务是将一系列 数据源更新分组或分批的方法,以便在回滚事务时同时提交所有事务或者不提交任何事务[MSDN]。 事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begintransaction和end transaction语句(或函数调用)来界定。事务由事务开始(begintransaction)和事务结束(end transaction)之间执行的全体操作组成。[百度百科] 特性 事务是恢复和并发控制的基本单位。具有4个属性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。 类型 按事务是否跨越多数据资源来分类: 1)本地事务:事务操作一个数据资源,如数据库和消息队列。在物理上表现为位于同一台计算机。 2)分布事务:事务跨越多个数据源,如操作两个服务器上的数据库。 按事务处理方式划分: 1)手动事务:使用显示指令来控制事务的开始和结束,这种方式可以处理嵌套事务。SQLServer,ADO.NET都提供手动事务处理。 2)自动事务:通过有组件声明事务特性,把组件自动置于事务环境中。使用自动事务不能处理嵌套事务。自动事务的本质是依托于COM+。 简单的实例 实例一 namespace 简单的事务 { class Program { private static Test test; static void Main(string[] args) { test=new Test(); test.Add(); } } public class Test { SqlConnection conn = new SqlConnection("datasource=.;Initial Catalog=Test;Integrated Security=SSPI;Persist SecurityInfo=False"); public void Add() { conn.Open(); SqlCommand command = new SqlCommand("insert intotest2 values(111)", conn); try { command.Transaction =conn.BeginTransaction(); command.ExecuteNonQuery(); command.CommandText = "insert intotest values(222)"; command.ExecuteNonQuery(); command.Transaction.Commit(); } catch (Exception err) { Console.WriteLine(err); Console.ReadKey(); command.Transaction.Rollback(); } } } } 实例二 这是一个简单三层的事务示例, namespace 事务 //U层的代码 { class Program { static void Main(string[] args) { bll sbll = new bll(); bool flag =sbll.MoveScore(); if (flag == true) { Console.WriteLine("事务执行成功"); } else { Console.WriteLine("事务执行失败"); } } } } namespace AddTestBll //BLL层 { public class bll { private dal dal; public Boolean MoveScore() { SqlConnection sqlCon = new SqlConnection("datasource=.;Initial Catalog=Test;Integrated Security=SSPI;Persist SecurityInfo=False"); //打开连接 sqlCon.Open() ; //定义事务 SqlTransaction sqlTran =sqlCon.BeginTransaction(IsolationLevel.ReadCommitted); //用try...Catch...finally保证事务在出错时会回滚 try { int intResult =dal.Add1(sqlTran); int intResult1 =dal.add2(sqlTran); if (1 == intResult&& 1 == intResult1) { //如果都为真,提交 sqlTran.Commit(); return true; //添加成功 } else { sqlTran.Rollback(); return false; //添加失败 } } catch (Exception e) { //出现异常时,事物回滚 sqlTran.Rollback(); return false; } finally { sqlCon.Close(); } } } } namespace AddTestDal //Dal层 { public class dal { test enTest = new test(); private SQLHelper sqlHelper; public int Add1(SqlTransaction sqlTran) { int intResult = 0; //enTest.Id ="222"; string strSql = "insert intotest values(222)"; intResult =sqlHelper.ExecuteNonQuery(strSql, CommandType.Text, sqlTran); return intResult; } public int add2(SqlTransaction sqlTran) { int intResult = 0; string strSql = "insert intotest values(111)"; intResult =sqlHelper.ExecuteNonQuery(strSql, CommandType.Text, sqlTran); return intResult; } } public class SQLHelper { private SqlConnection conn = null; private SqlCommand cmd = null; private SqlDataReader sdr = null; public SQLHelper() { string connStr = "datasource=.;Initial Catalog=Test;Integrated Security=SSPI"; conn = new SqlConnection(connStr); } private SqlConnection GetConn() { if (conn.State == ConnectionState.Closed) { conn.Open(); } return conn; } ///<summary> ///执行带参数的增删改SQL语句或存储过程 ///</summary> ///<paramname="cmdText">增删改SQL语句或存储过程</param> ///<paramname="paras">参数集合</param> ///<paramname="cType">命令类型</param> ///<returns>返回更新的记录条数</returns> public int ExecuteNonQuery(string cmdText, CommandType cType, SqlTransaction sqlTran) { try { //实例化数据库命令SqlCommand cmd = new SqlCommand(cmdText,GetConn()); //命令类型:存储过程or SQL语句 cmd.CommandType = cType; cmd.CommandTimeout = 180; //设置最大连接时间 //事务 cmd.Transaction = sqlTran; //定义事务执行结果 int intResult =cmd.ExecuteNonQuery(); //执行事务:大于0返回true,否则返回false。 if (intResult > 0) { //事务执行成功 return intResult; } else { //事务执行失败 return intResult; } } catch (Exception ex) { //抛出异常 throw ex; } } } } 上面是两个关于本地事务的小例子,最近项目中用到了分布式而底层还用到了EF,这样就要求运用到分布式事务了,研究中,如果读者有相关的资料希望能分享给大家,谢谢! 文中代码自建了一个测试库里面两张表的表结构如下(记得设置主键) 优秀的博客推荐: 1、.NET简谈事务、分布式事务处理:http://wangqingpei557.blog.51cto.com/1009349/748799/ 2、简述.net中有哪几种事务:http://wangzhiyu110.blog.sohu.com/156427139.html 3、.NET简谈事务本质论:http://wangqingpei557.blog.51cto.com/1009349/719056
JS实现是一个文本框(值为参数)输入另一个显示(查询结果) 最近在项目当中遇到了这么一个问题:“在一个文本框中输入编号,然后从数据库中查询对应的名称动态的显示在另一个文本框中。” 当一个文本框失去焦点的时候就动态的执行相应的方法,从后台查出数据然后显示在页面上。所以这个时候需要做的就是用JS写一个文本框触发事件。//W3School:http://www.w3school.com.cn/jquery/event_blur.asp(关于失去焦点事件详解) <scripttype="text/javascript"> $(document).ready(function(){ $("input").focus(function(){ //获得焦点事件 $("input").css("background-color","#FFFFCC"); }); $("input").blur(function(){ //失去焦点事件 $("input").css("background-color","#D6D6FF"); }); }); </script> 有了这个小Demo之后就开始着手实现自己需要的功能了,Demo中实现的只是样式的修改,而自己需要传参数调取Controller(前台使用的是MVC)并将返回值显示出来。在网上查到的最多的是如下方法(自己没有调通): <script type="text/javascript"src="jquery.js"$amp;>amp;$lt;/script> <script type="text/javascript"> $('#test1').blur(function(){ var parm = $('#test1').val().trim(); $.post("后台操作URL",{'val':parm},function(){ $('#test2').val(返回数据); },返回数据类型); }); </script> 最初没有调通是因为自己对JQuery的不理解,通过自己查了查资料发现网上找到的这段其实是我后来写的那段的简写(详细参考:http://www.w3school.com.cn/jquery/ajax_post.asp) 最后几经修改成功的实现了自己想要的功能,代码如下: //随教工号的变化得到相应的教师名称 $('#EmployeeNo').blur(function () { var strEmployeeNo = $('#EmployeeNo').val().trim(); $.ajax({ type: "post", async: true,//表示异步执行;这里同步异步都是没有问题的,关于同步和异步自己目前还不是很清楚。 url: "/OnClass/QueryTeacherNameByEmployeeNo", //Controller中的方法名 data: { "strEmployeeNo": strEmployeeNo }, //参数,从前台获取的教工号 success: function(data) { $('#TeacherName').val(data); //显示教师的名字,data为Json,里面只有教师名一个属性故可以直接使用。 //有时候我们需要将json转化成字符串,方法见文尾 }, error: function(err) { alert("输入的课程编码有误,请重新输入"); } }); }); 最后写完之后其实是一个很简单的东西,自己做的时候花了一些时间主要还是对JS这块不是很熟悉,同时在平时用得也比较少比较生疏,再者网上一些资料并没有很规范的注释(大家都得好好写注释啊)看起来也需要花时间。 希望大家都能用心分享! 附录: 简写实现: $('#CourseCode').blur(function() { var jsonStr = ""; var strCourseCode = $('#CourseCode').val().trim(); $.post("/OnClass/QueryCourseNameByCourseCode", { 'strCourseCode': strCourseCode }, function (data) { $('#CourseName').val(data); }); }); 关于Json和字符串的转换: 字符串转对象(strJSON代表json字符串) var obj = eval(strJSON); var obj = strJSON.parseJSON(); var obj = JSON.parse(strJSON); json对象转字符串(obj代表json对象) var str = obj.toJSONString(); var str = JSON.stringify(obj) 运用时候需要除了eval()以外需要json.js包(切记)
MVC3.0动态添加表格的行数并Controller中获取添加数据 最近由于项目的中的相关需求,需要在MVC的视图中动态的添加添加数据的行数,并将前台输入的多行数据在Controller中获得传回服务端。本文将介绍如何从MVC的View端动态添加数据行数并将输入的数据在Controller中获得。 问题分析 一、设计前台显示页面(View)。 视图中样式 @*添加上课班信息窗口*@ <div id="Addwin" class="easyui-window" title="添加教学班信息" style="width: 815px; height: 400px" data-options="iconCls:'icon-save',modal:true"> <div style="padding: 15px 15px 10px 60px;"> <form id="AddForm" method="post" style="width: 710px;height: 300px"> <table cellpadding="3" id="myTab"> @*<thead>*@ <tr align="center"> <td>教学班编号</td> <td>教学班名称</td> <td>课程名称</td> <td>教师名</td> <td>教工号</td> <td>选择学生</td> <td>人数</td> <td style="border:0"></td> </tr> <tr> <td> <input type='text' style='width:80px' name="OnClassNo1" id="OnClassNo1" /> </td> <td> <input type='text' style='width:130px' name="OnClassName1" id="OnClassName1" /> </td> <td> <input type='text' style='width:100px' name="CourseName1" id="CourseName1" /> </td> <td> <input type='text' style='width:50px' name="TeacherName1" id="TeacherName1" /> </td> <td> <input type='text' style='width:80px' name="EmployeeNo1" id="EmployeeNo1" /> </td> <td> <a id="selectStu1" href="#" onclick="addStudentWin();">选择学生</a> </td> <td> <input type='text' style='width:40px' name="TotalMember1" id="TotalMember1" /> </td> <td style="border:0"> <input style='width:40px; border:0' type='hidden' name="StudentArray1" id="StudentArray1" /> </td> <td> <div> <a onclick="addNewRow();" plain="true" iconcls="icon-add" class="easyui-linkbutton" href="#" id="add"></a> <a onclick="removeRow();" plain="true" iconcls="icon-remove" class="easyui-linkbutton" href="#" id="eidt"></a> </div> </td> </tr> </table> @*position:fixed;bottom:15px;left:250px;*@ </form> <div class="login" style="text-align: center;bottom:15px;margin-top: 5px " > <a href="#" class="easyui-linkbutton" data-options="iconCls:'icon-ok'" onclick="submitInfo();" align="center">确认</a> <a href="#" class="easyui-linkbutton" data-options="iconCls:'icon-cancel'" style="margin-left: 50px" onclick="javascript:$('#Addwin').window('close');">取消</a> </div> </div> </div> 二、如何动态的添加(删除)记录的行数。 在这里我尝试了两种方法,都能够实现动态的添加行数的作用下面是两种方法的JS文件内容。 方法一: <pre name="code" class="javascript">var i = 1; function addRow() { //用于表格id的自增长 ++i; if (i<5) { //克隆指定的行 var newTr =tr1.cloneNode(true); //指定新行的id newTr.id = "tr" + i; //指定新行的name newTr.name = "tr" + i; //在当前表格中插入行 tr1.parentNode.insertAdjacentElement("beforeEnd", newTr); } else { alert("提示消息","最多添加四行!"); } } function delRow() { var tab = document.getElementById('list'); if (tab.rows.length > 2) { tab.deleteRow(tab.rows.length - 1); } else { alert("已经剩下最后一行,不能删除!"); } } 方法二: //动态创建table function addNewRow() { var tabObj = document.getElementById("myTab");//获取添加数据的表格 var rowsNum = tabObj.rows.length; //获取当前行数 var colsNum = tabObj.rows[rowsNum - 1].cells.length;//获取行的列数 var myNewRow = tabObj.insertRow(rowsNum);//插入新行 if (rowsNum >= 9) { alert("警告,最多添加八行记录!"); } else { var newTdObj1 =myNewRow.insertCell(0); newTdObj1.innerHTML = "<input type='text' style='width:80px'name='OnClassNo" +rowsNum + "'id='OnClassNo" +rowsNum + "'/>"; var newTdObj2 =myNewRow.insertCell(1); newTdObj2.innerHTML = "<inputtype='text' style='width:130px' name='OnClassName" + rowsNum + "'id='OnClassName" +rowsNum + "'/>"; var newTdObj3 =myNewRow.insertCell(2); newTdObj3.innerHTML = "<inputtype='text' style='width:100px' name='CourseName" + rowsNum + "' id='CourseName" + rowsNum + "' />"; var newTdObj4 =myNewRow.insertCell(3); newTdObj4.innerHTML = "<input type='text' style='width:50px'name='TeacherName" + rowsNum + "' id='TeacherName" + rowsNum + "'/>"; var newTdObj5 = myNewRow.insertCell(4); newTdObj5.innerHTML = "<inputtype='text' style='width:80px' name='EmployeeNo" + rowsNum + "'id='EmployeeNo" +rowsNum + "'/>"; var newTdObj6 =myNewRow.insertCell(5); newTdObj6.innerHTML = "<inputtype='text' style='width:50px' name='selectStudent" + rowsNum + "'id='selectStudent" + rowsNum + "' value='选择学生'/>"; newTdObj6.innerHTML = "<aid='selectStu" +rowsNum + "'href='#' onclick='addStudentWin();'>选择学生</a>"; var newTdObj7 =myNewRow.insertCell(6); newTdObj7.innerHTML = "<inputtype='text' style='width:40px' name='TotalMember" + rowsNum + "'id='TotalMember" +rowsNum + "' />"; var newTdObj8 =myNewRow.insertCell(7); newTdObj8.innerHTML = "<inputtype='hidden' style='width:40px; border:0' name='StudentArray" + rowsNum + "'id='StudentArray" + rowsNum + "' />"; var newTdObj9 =myNewRow.insertCell(8); newTdObj9.innerHTML = "<div><aonclick='addNewRow();' plain='true' iconcls='icon-add'class='easyui-linkbutton' href='#' id='add'>加</a>" + "<aonclick='removeRow();' plain='true' iconcls='icon-remove'class='easyui-linkbutton' href='#' id='eidt'>删</a></div>"; } } //窗口表格删除一行 function removeRow() { var tab = document.getElementById('myTab'); var i = tab.rows.length; if (tab.rows.length > 2) { tab.deleteRow(tab.rows.length - 1); --i; } else { alert("已经剩下最后一行,不能删除!"); } } 最终我选择了第二种方案,因为相对于第一种方案第二种方案中的数据更容易获得。第一种方案不同的是行号,每行中相同字段的数据不易区分;第二种方案每行相同字段的ID是不一样的易于数据的获得。 三、如何提交输入的数据 JS实现如下: //提交表单 function submitInfo() { //提交表单 $.messager.progress(); // 显示进度条 $('#AddForm').form('submit', { url: "/OnClass/AddOnClassInfo", onSubmit: function () { }, success: function (data) { if (data > 0) { alert("保存成功"); } $('#dg').datagrid('reload'); // 重新载入当前页面数据 //$.messager.progress('close'); // 如果提交成功则隐藏进度条 $('#Addwin').window('close'); //关闭窗口 } }); } 四、在Controller中获得前台输入的数据 数据已经提交了如何去或者这些数据,就又是一个问题了,如何解决详见如下代码。 <pre name="code" class="csharp"> public void AddOnClassInfo(IList<OnClass> lstOnClass) { //1.获取行数 int rowNum = 0; string strOnClassName = ""; bool flag = true; do { rowNum++; //stringstrTeachingName = "teachingClassName" + rowNum; strOnClassName = Request["teachingClassName" + rowNum]; if (strOnClassName==null || strOnClassName=="") { flag = false; } } while (flag); //2.循环每一行,给实体对象分别赋值 List<OnClass> lstOnClassInfo= new List<OnClass>(); for (int i = 1; i <rowNum; i++) { OnClass onClass = new OnClass() { OnClassID = Guid.NewGuid(), OnClassName = Request["teachingClassName" + i], // for (int j = 0; j< rowNum; j++) //{ //} }; lstOnClassInfo.Add(onClass); } //调用服务端服,跟本文主题无关 onClassService.AddOnClassBatch(objOnClass); } 本分的思路想必读完文章之后大家也能有所了解,我将这个问题分解成了四部分,每一部分既独立又相互联系,最后达到了自己想要的结果。 我想,无论考虑什么问题我们都应该像这篇文章一样将问题分解,然后一个一个的小问题去解决,最终达到解决整个问题的目标。首先从简单的入手,从自己已有的积累出发。
ITOO---第一阶段小结 接触编程已经到了第三个年头了,从开始做的练习项目“机房收费系统”“新闻发布系统”,再到维护“永和收银系统”“人事档案管理系统”,到这次参加ITOO这个项目学到的知识越来越多遇到的困难也越来越多解决的问题也越来越多。 从面向过程的开发到面向对象的开发,从文档驱动到敏捷开发,从经典三层到分布式用到的技术一步步的提高,我们的成长也越来越快。 这次从项目开始到第一次验收——68天,这个过程更多的是对自己肯定的过程,刚开始感觉自己就像个无知的少年。突然被灌输了一大堆之前没有用过的东西(WCF、EF、LinQ、MCV、EasyUI等)这时的脑海中出现的第一句话就是“这两年都干嘛呢?”。于是呼,顿时就明白了一个“切肤之痛”这个成语的意思,也明白了“圈子决定眼界”是怎么回事。 项目中感触最深的不是会使用了什么技术而是交流。 一方面,自己表达的东西如何让别人能够理解;另一方面,如何向对你描述问题的人表达你已经知道她所说的事情是怎么回事。 自己表达的东西别人不理解,进一步的交流就没法开展。而这个时候我们大多数时候第一想法是:别人怎么那么的X这么简单的问题都不明白。通过这个项目我明白了,产生问题的原因大多是我们表达得有问题。别人不明白你是否已懂得他的想法,进一步的交流也没法进行。你无法正确的传达出“I know!”这个信息,会导致谈论无法推进。 当然,最严重的问题——不懂装懂!为什么说这事最严重的问题呢?因为出现这种问题后所要花费的成本是最高的,不正确的需求做出来的肯定是错误的东西,这个会严重的拖延项目的进度。这个时候你要改原型重新定需求。所以开发的过程中切记“不懂装懂”,不懂就说出来,即使是似懂非懂的时候也说自己不懂(人事维护的时候最大的体会)。 虽然交流很重要,涉及团队的开发进度;但是技术也是很重要的,毕竟这才是我们吃饭的家伙…… (未完待续)
数据录入问题分析(1) 从12月12日开始通知录入数据,今天开始评教已经半个月了。但是从今天评教过程中整理出的反馈信息来看,数据还是出现了一些问题。 每一次测试的时候,每一次出现有误数据的时候,我都在想为什么我们每学期都会进行数据采集,但是每学期都会产生许多的相同的问题。其实,这也是一个容易回答的问题,因为每次都是一个全新的开始。 本没打算做具体的分析,加上具体的数据也没有汇总出来,因此今天主要是从宏观的角度对目前出现的问题简单的分析一下。 全局观 包括:(1)通观全局“向前看”,用宏观战略眼光分析问题。(2)抓住“关键”集中力量解决主要问题。抓住关键,实际上就是要抓住主要矛盾。(3)跳出框框,用联系和发展的眼光分析问题。不能只在眼前的事务里打圈子;不要固守一成不变的框框;要看到事物都是相互关联的,不能只片面强调一方面。(4)知微见著,透过现象看本质。分析事物要看本质;要看事物的主流。(5)排除干扰,朝着既定的正确的方针和目标坚定不移干下去。 历年(可能我说得有误) 从师范学院用提高班的教务系统已经有好几年了,每学期都会有数据采集(我自己经历过两次)每次都是重新开始,并没有之前留下的什么东西给负责具体数据采集的人员,唯一可以利用一下的就是数据录入说明。 这说明从这件事一开始就没有人把它当成一个会一直延续的事情做下去,今年的弄完了就弄完了,明年呢?明年又不是我负责,我还是不操心了! 今年 从一开始说要采集数据,我们的出发点就仅仅是基础数据,没有想评教系统可能会用到,没有去想考试系统也需要用到。知道要评教之后,数据整理的时候就完全一评教为中心了。似乎我们真的忘了我们正在用的是教务系统,基础、评教、考试、权限只是它的几个子系统,无论是评教还是考试,都需要基础系统的数据来支撑。 也就是说我们在做这些是情之前不没有一开始就从全局去考虑这些问题,而我们发现问题越晚,我们解决这些问题就越是困难。在这件事情上我们至少违背了(1)和(3)。 责任心 责任心是指个人对自己和他人、对家庭和集体、对国家和社会所负责任的认识、情感和信念,以及与之相应的遵守规范、承担责任和履行义务的自觉态度。它是一个人应该具备的基本素养,是健全人格的基础,是家庭和睦,社会安定的保障。 具有责任心的员工,会认识到自己的工作在组织中的重要性,把实现组织的目标当成是自己的目标。 安排 前几天安排10期的进行整理,在整理到一半的时候突然让11期的接手,这期间两者的协调从目前的结果来看并不是很理想。10期是有义务一直协助11期的干好这件事的。 实施 实施过程中多次出现未跟负责人说明的情况下,就去忙其他事情的;也有弄到一半就不弄的。 我们发现了问题并不是说要去追究什么,而是需要在这个过程中,学习到一些对今后的工作和学习有帮助的东西。责任心体现在任何地方,它能决定一个人的成败。 总结 发现了问题的所在,我们就要从自身出发,去纠正这些问题。这不是一天两天能够做好的,但是我希望我们能够每天都好一点,下次我希望我们能做得更好,也相信!
合作开发---配置EA+SVN的协同设计环境 点击下载更加详细的文档 EA(EnterpriseArchitect)是功能最强大的CASE工具。SVN(SubVersion)是最常用的版本控制工具。两者结合在一起,可以搭建高效的团队协同设计工作环境。 EA本身是以*.eap的私有格式来存储设计图的,但同时它也支持将设计图的内容存储在SVN等版本库中。借助第三方版本控制工具,以实现版本控制和协同设计的目的。 当选择SVN作为EA的版本配置库时,需要在SVN上指定一个存储目录,EA将其的所有设计图都存放在这个目录中。为了达到协同设计的目的,EA将把每一个package作为一个单独的文件进行存储。也就是说,同一个时间内、对同一个package,最多只能有一个人进行编辑。具体的操作步骤如下: 所用工具: EnterpriseArchitect 8.0 Slik-Subversion-1.7.8-x64(Slik-Subversion-1.7.7-win32) VisualSvn Server2.5.9(~/bin/svn.exe,可以代替Slik-Subversion中的同名文件,客户端可以不用装SVN服务器) Tortoise Svn1.7.2-X64(X86) 注意:版本兼容性似乎对EA+SVN的协同使用有一定影响,以上是我测试成功的版本。 服务器配置 1、 Enterprise Architect 8.0,VisualSvn Server2.5.9,Tortoise Svn 1.7.2软件安装简单不在此赘述。 2、 在SVN服务器上建立项目版本配置库(结果如下图) 3、 将库签出到本地 4、 版本控制配置 5、增加分支到版本控制 6、 以模型分支导出 7、 检查库文件加查看是否操作成功 经过上面的步骤之后将生成的文件提交到SVN服务上之后服务器端的配置就完成了。 客户端配置: 1、 安装好相关软件 2、 将服务器中相应的库文件checkout到本地 3、 打开EA新建一个空的module(文件名、存放地址没有特殊要求) 4、 导入节点模型 导入后(如下图)就可以签出进行修改了,修改完成后再次签入即完成修改。至此客户端也算是配置成功了。 注意事项 养成“开始工作之前,先从版本库更新版本”的习惯。 养成“工作结束之后,及时提交版本”的习惯。 Checkout的范围越小越好、时间越短越好。对于自己不会进行修改的部分,就不要将其checkout。 工具只是起辅助作用,不要忽视和同伴的线下直接交流。 对EA+SVN来说,设计图版本管理操作,都应该只在EA中完成,而不要在本地文件夹中直接进行版本操作。 对EA+SVN来说,版本控制的最小单位就是package,而且是以文件的形式 之前在网上也看过其他版本的大多是从整体上介绍了一下用法及其好处 ,并没有详细的步骤去介绍。特别是各种软件之间的版本问题特别的需要重视,可能你搭了很长时间的环境就是因为版本问题导致最终达不到自己想要的结果就得不偿失了。此方法由本人实际操作成功并在运行当中。 积累从分享开始(文中工具下载地址:http://pan.baidu.com/s/1jG22t4E)
LinQ---扩展方法和Lambda表达式 扩展方法: 扩展方法使您能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。对于用 C# 和 Visual Basic 编写的客户端代码,调用扩展方法与调用在类型中实际定义的方法之间没有明显的差异。 格式: public static class classA { public staticvoid ExtraMethod(this string s){…} } 例如: public static class ExtraClass { //拓展方法,特殊的静态方法 public static string ToPascal(this string s) //this后带类型,表名为该类型添加特殊的方法 { return s.Substring(0,1).ToUpper() + s.Substring(1).ToLower(); } } 注意事项: 扩展方法是一种特殊的静态方法 扩展方法必须在静态类中定义 扩展方法的优先级低于同名的类方法 扩展方法只在特定的命名空间内有效 除非必须不要滥用扩展方法 Lambda表达式 C#Lambda基本的表达形式:(参数列表) => {方法体} 说明 参数列表中的参数类型可以是明确类型或者是推断类型 如果是推断类型,则参数的数据类型将由编译器根据上下文自动推断出来 Lambda 用在基于方法的 LINQ 查询中,作为诸如Where 和 Where 等标准查询运算符方法的参数。 详细介绍 实例分析 public partial class Form1 : Form { //定义一个委托 public delegate string deleTransfer(string s); public Form1() { InitializeComponent(); } private void btnTest_Click(object sender, EventArgs e) { //拓展方法---- string strTest = "asdsad"; Console.WriteLine(strTest.ToLower()); Console.WriteLine(strTest.ToUpper()); Console.WriteLine(strTest.ToPascal()); Console.WriteLine("-------------------------------------"); //Lambda 来源 //.Net FrameWork 1.0委托---函数指针 deleTransfer trans = new deleTransfer(ToPascal); //委托指向方法ToPascal Console.WriteLine(trans("abcdEFGH")); //.net 2.0 匿名方法 deleTransfer transs = delegate(string s) { return s.Substring(0,1).ToUpper() + s.Substring(1).ToLower(); }; Console.WriteLine(transs("abcdEFGH")); //.net 3.5 匿名方法 //deleTransfertransss = (s) => (s.Substring(0, 1).ToUpper() + s.Substring(1).ToLower()); deleTransfer transss = s =>s.Substring(0, 1).ToUpper() + s.Substring(1).ToLower(); Console.WriteLine(transss("abcdEFGH")); } //将字符串的首字母转化为大写字母的方法 public string ToPascal(string s) { return s.Substring(0,1).ToUpper() + s.Substring(1).ToLower(); } } public static class ExtraClass { //拓展方法,特殊的静态方法 public static string ToPascal(this string s) //this后带类型,表名为该类型添加特殊的方法 { return s.Substring(0,1).ToUpper() + s.Substring(1).ToLower(); } public static string ToPascal(this string s, int len) //this后带类型,表名为该类型添加特殊的方法 { return s.Substring(0,1).ToUpper() + s.Substring(1, len).ToLower() + s.Substring(len + 1); } } 学习从分享开始(分享链接:http://pan.baidu.com/s/1AQgHo)
LinQ从零开始---初体验 LinQ是什么? LINQ,语言集成查询(LanguageIntegrated Query)是一组用于c#和Visual Basic语言的扩展。它允许编写C#或者Visual Basic代码以查询数据库相同的方式操作内存数据。 LinQ要解决什么问题? 面向对象与数据访问两个领域长期分裂,各自为政, 编程语言中的数据类型与数据库中的数据类型形成两套体系。 C# 中字符串用 string 表示 SQL 中字符串用 NVarchar/Varchar/Char 表示 SQL 编码体验落后 没有智能感应 没有严格意义上的强类型和类型检查 SQL 和XML 都有各自的查询语言,而对象没有自己的查询语言 LinQ的组成 LINQ包括五个部分:LINQto Objects、LINQ to DataSets、LINQ to SQL、LINQ to Entities、LINQ to XML。 1.LINQ to Objects 主要负责对象的查询(是指直接对任意IEnumerable集合使用LINQ查询,无需使用中间LINQ程序或API。LINQ To Object 提供的是内存中集合数据的实体映射. 2.LINQ to XML 在System.Xml.LINQ命名空间下实现对XML的操作。采用高效、易用、内存中的XML工具在宿主编程语言中提供XPath/XQuery功能等 3.LINQ toADO.NET 主要负责数据库的查询 1)LINQ to SQL 全称基于关系数据的.NET语言集成查询,用于以对象形式管理关系数据,并提供了丰富的查询功能。其建立于公共语言类型系统中的基于SQL的模式定义的集成之上,当保持关系型模型表达能力和对底层存储的直接查询评测的性能时,这个集成在关系型数据之上提供强类型。 2)LINQ to DataSet LINQ to DataSet将LINQ和ADO.NET集成,它通过ADO.NET获取数据,然后通过LINQ进行数据查询,从而实现对数据集进行非常复杂查询。 3)LINQ to Entities 它让你可以使用标准的 C#对象与数据库的结构和数据打交道。使用 LINQ to Entities 时,LINQ 查询在后台转换为 SQL 查询并在需要数据的时候执行,即开始枚举结果的时候执行。LINQ to Entities 还为你获取的所有数据提供变化追踪,也就是说,可以修改查询获得的对象,然后整批同时把更新提交到数据库。 LINQ to Entities 是 Entity Framework 的一部分并且取代 LINQ to SQL 作为在数据库上使用 LINQ 的标准机制。Entity Framework 是行业领先的对象-关系映射(ORM)系统。可以和多种数据库一起使用,并支持各种灵活、复杂的数据模型。 说了这么多,我们还是用一个简单的实例说明一下微软LINQ to SQL框架为我们带来的体验。 LinQ实例 我们从一个数组中选出自己需要的数据,看看用普通方法和使用LinQ有什么区别。 不使用LinQ: int[] arr = { 123, 2, 22, 23, 15, 6, 8, 67, 887, 999 }; //获取大于50的数 //没有Linq我们怎么做、 ArrayList result = new ArrayList(); for (int i = 0; i < arr.Length; i++) { if (arr[i] > 50) { result.Add(arr[i]); } } //打印result for (int i = 0; i < result.Count; i++) { Console.WriteLine(result[i]); } 输出结果: 使用LinQ: //获取大于50的数 IEnumerable ie = arr.Select(p => p).Where(p => p > 50); //输出-----该部分可以重复 IEnumerator result = ie.GetEnumerator(); while (result.MoveNext()) { Console.WriteLine(result.Current); } 输出结果: 分析: 从上面的实例中我们可以发现相对于不使用LinQ,使用LinQ代码更加简洁;同时无需复杂学习过程即可上手。 关于LinQ的其他优点,更快开发错误更少的应用程序。无需求助奇怪的编程技巧就可合并数据源。能够大幅减少过程控制语句的代码块,使代码的可读性和可维护性大幅提高。任何对象或数据源都可以定制实现LinQ适配器,为数据交互带来真正方便。还有待我们探究! 文章中实例源码:http://pan.baidu.com/s/1AQaV8
计算机软考临考前十大注意事项 1、 考试用具早早准备。 考试前当晚准备好两支2B的铅笔,检查准考证、身份证等是否全部备齐,避免第二天早上慌慌张张。 2B铅笔,注意要把笔尖削成扁的,这样填涂的时候,只要画一道就能涂满整个框了,非常节省时间。 橡皮擦,买专用的绘图橡皮,擦的比较干净。 身份证、准考证 表,现在很多人都习惯使用手机当表用,而考场中手机必须关机,一旦关机以后就不知道时间了,无法合理分配自己的做题时间! 2、 晚上早入睡 考场上的正常发挥,与考试前晚上的休息有关。晚上早点睡觉、不要熬夜看书,以免第2天考试的时候无精打采。 3、 白天早起提前到考场 掌握好考试时间。第2天早点起来,打的去考场,以免路上颠簸,一年也就两次考试,打的吧,图个愉快轻松的心情,同时,早点出发避免塞车。早到后,在考场附近解决早餐问题。少喝水,早餐后上厕所,考试时想上厕所是一件非常痛苦的事情。 4、 试卷下发后第一件事 试卷发下来后,监考老师会要求考生检查卷面是否完整,清晰,要求考生填写姓名、准考证号等。所以,第一件事情不是看题做题,而是填写姓名、准考证等相关信息。并检查是否填涂正确(注意:填涂都是用2B铅笔,而需要写字的地方一定要用圆珠笔或钢笔,千万别弄混。),避免出错,而导致几个月的努力付之一炬。 5、 做题前先总览 一般来说,考试都给我们充分的时间,所以,考生拿到考卷后,不要急忙做卷,先总览一遍,然后动笔;尤其是有论文的题目,先构思,避免想到哪写到哪。 6、 答题 拿到试题,仔细看题,不要匆忙做答,尤其是有论文的,先要酝酿好,但是时间也要把握好。 答题卡一定要边答边填涂,不要等到最后一起涂!万一没时间了,上午题就没分了! 不会的问题不要总是在想,只需要在卷上做个记号,如果有时间的话再回头看!不要因为捡芝麻而丢西瓜! 下午主观题,卷面整洁、清晰是最基本要求。想想看,老师批改那么多的试卷都要晕头转向了,再揣摩、猜测考生试卷上写的什么字,怎会有好心情?字写的不好没关系,但总得让老师看起来不费劲。做题时,碰到不会做或没把握的简答题、问答题,我们建议在卷子上写一些相关文字,能得一分算一分,得不到也不会有什么损失。对非常确定的题目大可不必多此一举,这有可能让老师没发现关键答题点,出现失分现象。 7、 一遍搞定勿反复 上午题,争取一遍搞定,根据自己以往的经验和成功率来决定是否要检查一遍,因为有些考生第一遍准确率还高些,反到在第二遍检查的时候把对的改成错的,所以,自己要掂量好,是否要检查(对上午考试而言)。 8、 不对答案 上午考试完成后,不要对答案,找个地方吃饭,中午休息一下,然后轻松对付下午的考试;如果考的不好,也没有时间沮丧或埋怨,更不要跟同学对答案,或打开书本查答案,这除了自讨苦吃外,对整个考试没有任何好处。考完一门后,应及时调整心态,以更饱满的精神投入到下一门考试中。 9、 不交白卷,题题做答 下午考试题目可能有点难,不要急,你觉得难的地方,其它考生也觉得难,这个时候,不要放弃,或者交空白卷,运用自己的想像力,根据自己的理解来回答,尽量写满,阅卷老师会给你些辛苦分的。 10、 交卷前检查 做完后,检查是试卷中是否有遗漏的问题。有的考生碰到简单的题目心情高兴,往往答了题目的第一问,而忘记回答第二问。 最后,检查考号,姓名是否有误。交卷!
浅谈平衡二叉树 平衡二叉树(Balanced binarytree)是由阿德尔森-维尔斯和兰迪斯(Adelson-Velskii and Landis)于1962年首先提出的,所以又称为AVL树。 一、平衡二叉树的基本介绍 定义:平衡二叉树或为空树,或为如下性质的二叉排序树: (1)左右子树深度之差的绝对值不超过1; (2)左右子树仍然为平衡二叉树. 平衡因子BF=左子树深度-右子树深度. 平衡二叉树每个结点的平衡因子只能是1,0,-1。若其绝对值超过1,则该二叉排序树就是不平衡的。 查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。 如图所示为平衡树和非平衡树示意图: (图一左为非平衡二叉树,图一右图为平衡二叉树) 二、平衡二叉树算法思想 若向平衡二叉树中插入一个新结点后破坏了平衡二叉树的平衡性。首先要找出插入新结点后失去平衡的最小子树根结点的指针。然后再调整这个子树中有关结点之间的链接关系,使之成为新的平衡子树。当失去平衡的最小子树被调整为平衡子树后,原有其他所有不平衡子树无需调整,整个二叉排序树就又成为一棵平衡二叉树。 失去平衡的最小子树是指以离插入结点最近,且平衡因子绝对值大于1的结点作为根的子树。假设用A表示失去平衡的最小子树的根结点,则调整该子树的操作可归纳为下列四种情况。 (1)LL型平衡旋转法(右转) 由于在A的左孩子B的左子树上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行一次顺时针旋转操作。即将A的左孩子B向右上旋转代替A作为根结点,A向右下旋转成为B的右子树的根结点。而原来B的右子树则变成A的左子树。 图二(1) 图二(2) (2)RR型平衡旋转法(左转) 由于在A的右孩子C的右子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行一次逆时针旋转操作。即将A的右孩子C向左上旋转代替A作为根结点,A向左下旋转成为C的左子树的根结点。而原来C的左子树则变成A的右子树。 图三(1) 图三(2) (3)LR型平衡旋转法(先左后右) 由于在A的左孩子B的右子数上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行两次旋转操作(先逆时针,后顺时针)。即先将A结点的左孩子B的右子树的根结点D向左上旋转提升到B结点的位置,然后再把该D结点向右上旋转提升到A结点的位置。即先使之成为LL型,再按LL型处理。 图四(1) 如图四中所示,即先将圆圈部分先调整为平衡树,然后将其以根结点接到A的左子树上,此时成为LL型,再按LL型处理成平衡型。 图四(2) (4)RL型平衡旋转法(先右后左) 由的右孩子C的左子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行两次旋转操作(先顺时针,后逆时针),即先将A结点的右孩子C的左子树的根结点D向右上旋转提升到C结点的位置,然后再把该D结点向左上旋转提升到A结点的位置。即先使之成为RR型,再按RR型处理。 图五(1) 如图五中所示,即先将圆圈部分先调整为平衡树,然后将其以根结点接到A的左子树上,此时成为RR型,再按RR型处理成平衡型。 图五(2) (以上的四种节点包含了所有插入时导致不平衡的情况,图中所示仅仅是平衡树插入后产生的最小不平衡子树) 平衡化靠的是旋转。参与旋转的是3个节点(其中一个可能是外部节点NULL),旋转就是把这3个节点转个位置。注意的是,左旋的时候p->right一定不为空,右旋的时候p->left一定不为空,这是显而易见的。 如果从空树开始建立,并时刻保持平衡,那么不平衡只会发生在插入删除操作上,而不平衡的标志就是出现bf == 2或者bf == -2的节点。8 例:在平衡二叉树中插入一个结点后造成了不平衡,设最低的不平衡结点为A,并且A的左孩子的平衡因子为-1,右孩子的平衡因子为0,则使其平衡的调整方法为( ) A.LL型 B.LR型 C.RL型 D.RR型 解:由图四(1)可知答案选B (个人的一点理解,如有错误还请读者不吝赐教)
AJAX入门---点滴的积累 每次学习完一个内容总会写上几句话总结一下学习的内容。这不刚看完王兴魁老师讲的AJAX核心技术,现在回顾梳理一下。这套视频的内容不多,简单的讲解了XMLHttpRequest技术,DOM及其操作HTML、XML,一些Javascript的知识,再通过两个综合性的例子收尾。 XMLHttpRequest对象 关于该对象的具体含义,如何使用可以看看我之前写的博客——《AJAX入门--- XMLHttpRequest对象的属性和方法》和《AJAX入门---五步使用XMLHttpRequest对象》。这两篇博客有对XMLHttpRequest对象的详细介绍。 DOM基础及其操作HTML、XML 关于DOM的基础知识,DOM操作HTML在之前也有博文《AJAX入门---DOM操作HTML》中已经进行了简短的描述,大家感兴趣的看看。 DOM操作XML和其操作HTML相同,可以利用DOM的API进行XML的获取和修改(之后会附上练习源码)。需要注意document这个特殊的对象只对应于HTML的根节点。XML的根节点需要在XML的DOM对象后通过一定的方式获取。 在这部分视频中,解决了由于浏览器差异带来的空白信息问题,学习了将DOM对象序列化成表示XML的字符串知识,了解并初步使用XPATH技术(不同浏览器的插寻,表达式的编写)。 自己的练习源码:http://download.csdn.net/detail/senior_lee/7755381 JavaScript高级技术 主要讲解Javascript中的数组,对象的创建,JSON数据格式,Javascript面向对象以及XMLHttpRequest对象的封装。类,公有私有属性和方法,原型对象,父子继承,接口,反射和命名空间。设计到的知识多,需要自己下去之后进行相关的学习。 关于JSON数据格式的使用在之前博客《初识ASP.NET---一般处理程序》中又提到,该博文的链接中有相关的源码下载,需要的可以看看。 后记 看了这套视频并不是AJAX学习的结束,恰恰是学习的开始,视频容量有限有些没有讲到的地方需要再看看其他相关资料。如此同时,无论学习什么,最终的使用才是目的,期望自己尽快能在项目中运用到相关的知识。
永和维护---从问题中得到的一些感受 从师哥找我和晓春维护永和收银系统到现在已经两个多月了,之前一直没什么事,最大的感觉就是什么感觉都没有。 也许万事万物的规律都是差不多的,每当你对某件事思想上松懈了的时候,也是它最可能爆发的时候,每每这个时候总是让人感到猝不及防。 六月---机器有价数据无价 某用户一台POS机既当前台,又当后台和数据服务器。而且该POS机经常出问题,某天该POS机硬盘(后来找专业机构想恢复数据时,发现是磁道损坏,所要的数据无法恢复)出现故障后,换了硬盘后重新帮她还原了一份系统但是数据已经无法找回(维护的过程中,会时常给用户备份数据,但是还是丢失了2个月的数据)。 由于店中消费者基本上都是会员用户,具体损失就不得而知了。现在想想,用户单店每天的数据量并不是很大,因此一台最普通亦或者是淘汰了的台式机都能当作数据服务器,从而提高数据的安全性。当然最好的解决方法,还是看看下文提到的另外一用户的做法。 七月---电源损坏,换服务器!浪费? 7月6号,某用户说想换一台服务器(已经还原好之前的镜像)。问其原因,答曰“之前的服务器,用了四年,最近时常出现突然断电的情况,便换了台”,当时给我的想法是“妈的,最多就是个电源问题,换个服务器是不是有点浪费”。后来又想了想,确实换台服务器是最好的,对于我维护来说这样我的工作就少了,同时用户也因此能够不因某些原因影响服务器的正常使用。没多久就印证了我的想法,后来在旧系统中拷数据的时候,系统已经损坏无法启动。我只能“法克”,还好无论做什么之前第一件事都是备份数据。 最后,通过第三方(一般维护工作都是通过QQ远程)将数据取出,还原到数据库中完美解决问题。最后还想说一句,服务器是刀片式的基本上就不会出现数据丢失的问题了。给力!!! 八月---工欲善其事必先利其器 昨天上次换服务器的用户又换新机器了,原因:POS机主板坏了。(“土豪啊!!!”) 最近客户说前台POS机经常死机,想让我解决一下。我看看维修记录,好像八月后已经是第三次找我了。第一次重装了前台,没有解决问题(事后发现);第二次,换了网线没有解决问题(事后发现);这是第三次了,之前网上查了些资料,发现可能是硬件问题或是散热问题引起的,没有重视。通过和客户的交流中得知最近POS机开机都需要开几次才能启动,我再次想可能是硬件的问题。但是和客户交流的时候最先说的还是让他重装一下系统。至于为什么不说可能是硬件的问题,一方面是不确定,另一方面是我想如果说一句不肯定的话,可能会让客户质疑我的能力,同时我的这个想法也没有得到祥哥的肯定和晓春的肯定也让我没有了自信去向客户说这种可能性。 晚上,客户和我联系的时候,说了可以用了,主板坏了我换了一台新的POS机,让我陷入了思考。 一方面,数据量并不是太大但是前台,后台,数据库服务器样样具备。前台和后台都连了网(上文说过一般是通过QQ远程解决问题)方便了我的维护,刀片式服务器基本上杜绝了数据丢失。写到此处,不禁想起来米老师经常告诫我们寒暑假租房、吃饭别省那点钱,住好点、吃好点,最重要。 另一方面:之前一直酝酿的博客,今天也可以去完成它了,学习了,也成长了。 未完待续。。。
AJAX入门---DOM操作HTML 一边学习AJAX一边坐着总结加深印象,上次写的是如何简单的使用XMLHttpRequest对象,今天写的是DOM(文档对象模型(Document Object Model))操作HTML,希望我的博客对大家的学习能够起到帮助作用。 一.什么是DOM 文件对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展标志语言的标准编程接口。DOM可以以一种独立于平台和语言的方式访问和修改一个文档的内容和结构。换句话说,这是表示和处理一个HTML或XML文档的常用方法。 二.简单的操作HTML 今天的例子实现前我们先需要了解DOM的常用节点和常用API,例子中通过动态的向HTML中添加元素节点达到学习的目的。 最终效果 添加节点实现 //添加节点 var index = 0; function appendnode() { //找到body对应节点 var htmlNode = document.documentElement; var bodyNode = htmlNode.lastChild; //新建节点 var divNode = document.createElement("div"); var textNode = document.createTextNode("我是一个新加入的节点" +index++); //建立节点之间的关系 divNode.appendChild(textNode); bodyNode.appendChild(divNode); } 插入节点实现 //插入节点 function insertnode() { var removenode = document.getElementById("remove"); var firstdivnode = removenode.nextSibling; while (firstdivnode) { if (firstdivnode&& firstdivnode.nodeName == "DIV") { var newnode =document.createElement("div"); var textnode =document.createTextNode("我是一个新加入的节点" + index++); newnode.appendChild(textnode); document.documentElement.childNodes[2].insertBefore(newnode,firstdivnode); break; } firstdivnode =firstdivnode.nextSibling; } } 移除节点实现 function removenode() { //1.找到body //2.找到需要被移除的那个div //3.调用remove方法移除节点 var bodynode = document.getElementById("remove").parentNode; var removenode = document.getElementById("remove"); var firstdivnode = removenode.nextSibling; while (firstdivnode) { if (firstdivnode&& firstdivnode.nodeName == "DIV") { bodynode.removeChild(firstdivnode); break; } firstdivnode = firstdivnode.nextSibling; } } 总结 总记得以前有位老师总是在班里提醒“好记性不如烂笔头”,诚然编程有何尝不是这样,自己实现一遍抵得上自己看千万遍源码。需要完整的demo您可以通过下载免费源码获取(http://download.csdn.net/detail/senior_lee/7714311)
XMLHttpRequest简介: XMLHttpRequest对象可以在不向服务器提交整个页面的情况下,实现局部更新网页。当页面全部加载完毕后,客户端通过该对象向服务器请求数据,服务器端接受数据并处理后,向客户端反馈数据。XMLHttpRequest 对象提供了对 HTTP 协议的完全的访问,包括做出 POST 和 HEAD 请求以及普通的 GET 请求的能力。XMLHttpRequest 可以同步或异步返回 Web 服务器的响应,并且能以文本或者一个 DOM 文档形式返回内容。尽管名为 XMLHttpRequest,它并不限于和 XML 文档一起使用:它可以接收任何形式的文本文档。 五步使用XMLHttpRequest对象 1. 建立XMLHttpRequest对象如下: (不同浏览器中XMLHttpRequest对象建立的方式不同:IE7以上,FireFox,Safari,Opera等中直接newXMLHttpRequest();IE6,IE5.5等则需要通过用一个正确的ActiveXObject控件名称通过new ActiveXObject(控件名)的方式) if (window.XMLHttpRequest) { xmlhttp=new XMLHttpRequest(); if (xmlhttp.overrideMimeType) { xmlhttp.overrideMimeType("text/xml"); } }else if (window.ActiveXObject) { var activexName=["MSXML.2.XMLHTTP.6.0","MSXML.2.XMLHTTP.5.0", "MSXML.2.XMLHTTP.4.0","MSXML.2.XMLHTTP.3.0", "MSXML.2.XMLHTTP","Miscrosoft.XMLHTTP"]; for (var i = 0; i <activexName.length; i++) { try { xmlhttp=new ActiveXObject(activexName[i]); } catch (e) { } } } 2. 注册回调函数 (设置回调函数是,不要在函数名后面加括号。加括号表示将回调函数的返回值注册给onreadystatechange属性) xmlhttp.onreadystatechange=callback; 3. 使用open方法设置服务器端交互的基本信息 (open方法最多有五个参数局,其中头三个参数是必须的) //使用GET方式时,请求数据位于url链接中,后面的send方法的参数直接写null xmlhttp.open("GET","Ajax?name="+ userName,true ); //使用POST方式时,open方法后面需要先调用setRequestHeader方法,来设置Content-Type的值,然后调用send方法,send的参数就是请求的数据 xmlhttp.open("POST","Ajax", true); xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); 4. 设置发送的数据,开始和服务器端交互 //GET方式 xmlhttp.send(null); //POST方式 xmlhttp.send("name=" + userName); 5. 在回调函数中判断交互是否结束,响应是否正确,并根据需要获取服务器端返回的数据,更新页面的内容 (回调函数中,最好将判断readyState和status的两个if条件,分开来写) function callback(){ //5.判断和服务器端的交互是否完成,还要判断服务器端是否正确返回了数据 if (xmlhttp.readyState == 4) { //表示和服务器端的交互已经完成 if (xmlhttp.status==200) { //表示和服务器的响应代码是200,正确的返回了数据 //纯文本数据的接受方法 var message =xmlhttp.responseText; //Xml数据对应的Dom对象的接受方法 //使用的前提是,服务器端需要设置content-type为text/xml //var domXml=xmlhttp.responseXML; //向div标签中填充文本内容的方法 var div=document.getElementById("message"); div.innerHTML=message; } } } 总结: 当然通过这个小小的例子,我们只能说是经历了一个从不知道到知道的过程,初步了解了一下XMLHttpRequest对象的用法,更加深刻的理解还需要我们更加深入的学习和在实践中加以利用。 文中demo源码免费下载链接:http://download.csdn.net/detail/senior_lee/7707257
由于刚刚接触到Ajax对其比较陌生,而其中的XMLHttpRequest对象更是未曾听闻。开始学之前,了解一下它的属性和方法为它的使用做下铺垫。本文重点介绍XMLHttpRequest的属性和方法。 XMLHttpRequest对象的属性和事件 属性 描述 readyState 表示XMLHttpRequest对象的状态[1] responseText 包含客户端接收到的HTTP相应的文本内容[2] responseXML 服务器响应的XML内容对应的DOM对象[3] status 服务器返回http状态码[4] statusText 服务器返回状态码的文本信息[5] 事件 描述 onreadystatechange 当readyState属性发生变化时触发此事件,用于触发回调函数。 [1]: 状态 名称 描述 0 Uninitialized 初始化状态。XMLHttpRequest 对象已创建或已被 abort() 方法重置。 1 Open open() 方法已调用,但是 send() 方法未调用。请求还没有被发送。 2 Send Send() 方法已调用,HTTP 请求已发送到 Web 服务器。未接收到响应。 3 Receiving 所有响应头部都已经接收到。响应体开始接收但未完成。 4 Loaded HTTP 响应已经完全接收。 [2]:readyState=4时,responseText包含完整的响应信息。 readyState=3时,responseText包含未完整的响应信息。 readyState<3时,responseText为空字符串。 [3]:当readyState=4,并且响应头部的Content-Type的MIME类型为XML(text/xml或application/xml)时,该属性有值并且被解析成一个XML文档。其它情况为null,包括回传的XML文档不良或未完成响应回传。 [4]:如 200 表示成功,而 404 表示 "NotFound" 错误。当 readyState 小于 3 的时候读取这一属性会导致一个异常。 [5]:当状态为 200 的时候它是"OK",当状态为 404 的时候它是 "Not Found"。和 status 属性一样,当 readyState 小于 3 的时候读取这一属性会导致一个异常。 XMLHttprequest对象的方法 1.open方法 描述:制定和服务器交互的HTTP方法,URL地址及其他请求信息。 open(method,url, async, username, password) 用来进行初始化工作 返回值:得到一个包含send()方法的对象 method:必须。用于指定HTTP请求方法,支持所有HTTP的方法,如GET,POST,按规定 uri:请求的服务器的地址,自动解析成绝对地址。 async:请求是否异步,true表示你异步,false表示同步,默认为true。 username,password:可以不指定,分别表示用户名和密码,提供HTTP认证机制需要的用户名和密码。 调用open后,readystate状态为1. 2.send(content)方法 描述:向服务器发出请求,其内容可以是DOM对象,输入流或是字符串。 调用open 方法后,可以调用send()方法来发送请求。 当open 中async=true时,send()方法调用后立即返回,否则会中断直到请求返回。 3.abort()方法 该方法可以暂停一个HttpRequest请求或者HttpResponse的接收,并且将XMLHttpRequest的状态设置为初始化。 4.setRequestHeader(header,value)方法 该方法用来设置请求的头部信息。此方法需要在open方法之后调用。 5.getResponseHeader()方法 描述:返回包含HTTP的所有响应头信息,其中响应头包括Content-Length,Date,URI等内容。 当readystate>2时,该方法用来检索响应的头部信息。否则返回一个空字符串。 getAllResponseHeaders()方法返回所有的HttpResponse头部信息。 知道的XMLHttpRequest对象的相关知识之后,重点就是如何使用了,敬请关注我的下篇博客《AJAX入门---五步使用XMLHttpRequest对象》