Tenny's Blog


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 搜索

spring项目properties属性加密解密处理

发表于 2017-12-25 | 分类于 properties

前言

出于安全考虑,项目的properties文件中某些属性需要加密存放,使用时解密。

解决方案1.0

首先我们自定义一个属性加载器,继承自spring的PropertyPlaceholderConfigurer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 解密自定义加密属性
*
* @author tenny
*
*/
public class EncryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
private List<String> encryptPropNames;
// 覆写转换方法,对自定义属性解密
@Override
protected String convertProperty(String propertyName, String propertyValue) {
if (encryptPropNames.contains(propertyName)) {
return AESUtil.decrypt(propertyValue);
}
return propertyValue;
}
public void setEncryptPropNames(List<String> encryptPropNames) {
this.encryptPropNames = encryptPropNames;
}
}

然后修改配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 自定义属性加载器,解密某些属性 -->
<bean id="placeholderConfig" class="com.tenny.common.EncryptPropertyPlaceholderConfigurer">
<!-- 配置需解密的属性 -->
<property name="encryptPropNames">
<list>
<value>jdbc.password</value>
<value>redis.password</value>
</list>
</property>
<property name="locations">
<list>
<value>classpath:conf/jdbc.properties</value>
<value>classpath:conf/redis.properties</value>
</list>
</property>
</bean>

其中jdbc.properties中password属性加密存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/news
jdbc.username=root
jdbc.password=3hj83jsf234hp3jd
#定义初始连接数
jdbc.initialSize=0
#定义最大连接数
jdbc.maxActive=20
#定义最大空闲
jdbc.maxIdle=20
#定义最小空闲
jdbc.minIdle=1
#定义最长等待时间
jdbc.maxWait=60000

redis.properties同样

1
2
3
4
5
6
7
8
9
# Redis settings
redis.host=localhost
redis.port=6379
redis.pass=4jd89jkn68943gf1
redis.maxTotal=200
redis.maxIdle=50
redis.minIdle=300
redis.maxWaitMillis=1000
redis.testOnBorrow=true

至于加密解密工具AESUtil网上很多,这里就不贴了。

项目启动时,由于覆写了convertProperty方法,而每个属性都会过一遍该方法,通过配置encryptPropNames的值,就可以灵活选择需要解密的字段了。

新的问题

按理说到这里就满足需求了,然而tenny又遇到了一个问题。我们知道,加密解密工具AESUtil是需要key的,如果这个key也存在properties文件中呢。按上述方法,假如先读取到key再读取properties文件加密属性,是可以正常解析的。但假如先读取到需要解密的属性,这时候AESUtil所需key还是空,就会造成解密错误了。

由于properties继承自Hashtable,key/value不保证进出顺序,故加载properties属性顺序不保证与文件所写一致,所以想通过把key属性写在最前面来解决是不行的。

有一个解决方案是自定义一个OrderedProperties类继承自Properties,然后使用这个OrderedProperties来存放属性,保证读取顺序与写入顺序一致即可。我这里使用的是另外一种办法。

回顾之前方案

一路查看PropertyPlaceholderConfigurer继承链到PropertyResourceConfigurer,查看所有方法
PropertyResourceConfigurer所有方法

查看原convertProperty(String, String)方法,可以看到原方法只是将值原样返回了。
convertProperty原方法

那么为什么覆写convertProperty方法改写了值后就能得到改变后的值呢,我们再看convertProperties(Properties)方法
convertProperty原方法

也就是说先获取值,再经convertProperty方法获取另一个值,如果两者不同则以后者为准。所以我们之前覆写convertProperty(String, String)方法就可以自定义解密属性值了。

再回到新的问题上,properties属性解密需要AESUtil,而AESUtil先需要key属性,怎么解决这个”死锁”问题呢。

解决方案2.0

看了上面的源码我们自然就想到了另一个办法,就是覆写convertProperties(Properties)方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 解密自定义加密属性
*
* @author tenny
*
*/
public class EncryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
private List<String> encryptPropNames;
@Override
protected void convertProperties(Properties props) {
// 1.找到key属性值以初始化AESUtil
AESUtil.key = props.getProperty("key");
// 2.回调原遍历方法
super.convertProperties(props);
}
// 3.覆写转换方法,对自定义属性解密
@Override
protected String convertProperty(String propertyName, String propertyValue) {
if (encryptPropNames.contains(propertyName)) {
return AESUtil.decrypt(propertyValue);
}
return propertyValue;
}
public void setEncryptPropNames(List<String> encryptPropNames) {
this.encryptPropNames = encryptPropNames;
}
}

其中的props.getProperty(String)方法可以根据键直接获取对应值
getProperty方法

核心思路还是先拿到key,再处理其他属性。如此一来,就解决了解密属性要AESUtil而AESUtil要key属性的”死锁”问题。

总结

一般来说,大部分情况下解决方案1.0足矣。

不过我这里比较特别一点,场景稍微有点麻烦。先是折腾了半天想办法如何按写入顺序读出属性,但是感觉把事情搞得更麻烦了,又得引入新的东西,最后还是放弃了这种方案。后来在上级老大的提示下尝试重写convertProperty更上层的convertProperties方法,结合Properties.getProperty(String key)方法,对数据有了更灵活的控制,问题也就解决了。

Markdown行首缩进方法

发表于 2017-12-04 | 分类于 markdown

因为markdown的段落定义是由一个或多个连续的文本组成,中间的多个空格和tab会被认为是一个空格。
但是有时候确实需要这样的空格或tab缩进格式时怎么办?

1.全角空格

将输入法由半角改为全角。两次空格之后就能够有两个汉字的缩进。
例如:

1
2
3
测试的三句话:1.这是第一句话 //末尾两空格以换行
       2.这是第二句话 //开头7个全角空格以缩进,末尾两空格以换行
       3.这是第三句话 //开头7个全角空格以缩进

测试的三句话:1.这是第一句话
       2.这是第二句话
       3.这是第三句话

2.直接写

1
2
3
不断行的空白格&nbsp;或&#160;
半方大的空白&ensp;或&#8194;
全方大的空白&emsp;或&#8195;

例如:

1
2
3
4
5
测试的四句话:
&nbsp;&nbsp;&nbsp;&nbsp;1.这是第一句话 //末尾两空格以换行
&ensp;&ensp;2.这是第二句话 //末尾两空格以换行
&emsp;3.这是第三句话 //末尾两空格以换行
 4.这是第四句话 //开头1个全角空格以缩进

测试的四句话:
    1.这是第一句话
  2.这是第二句话
 3.这是第三句话
 4.这是第四句话

由此可知,各个空白的缩进比例为 4 : 2 : 1: 1。

个人感觉,在中文环境下,行首缩进对齐使用全角空格最方便。

使用Freemarker模板自动生成代码

发表于 2017-12-04 | 分类于 Freemarker

前言

因感概业务代码存在大量的增删改查功能,只是针对不同的表数据而已。故想有没有办法,在重复的相同代码中替换某些内容,于是按此思路搜寻,了解到有freemarker这个东西,一番学习后,特记录于此。

Freemarker简介

FreeMarker是一款模板引擎: 一种基于模板和要改变的数据,并用来生成输出文本(HTML网页、电子邮件、配置文件、源代码等)的通用工具。即:输出=模板+数据。简单来说,其用法原理类似String的replace方法,或MessageFormat的format方法,都是在一定的代码中改变(替换)某些内容。不过FreeMarker更加强大,模板来源可以是外部文件或字符串格式,替换的数据格式多样,而且支持逻辑判断,如此替换的内容将更加灵活。

小例子

比如我们要产生实体类,我们将模板定义如下,模板文件名为entity.flt(flt是freemarker模板的后缀名)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.tenny.${interfaceName?lower_case}.entity;
import java.util.Date;
public class ${entityName} {
<#list params as param>
// ${param.fieldNote}
private ${param.fieldType} ${param.fieldName};
</#list>
<#list params as param>
public void set${param.fieldName?cap_first}(${param.fieldType} ${param.fieldName}){
this.${param.fieldName} = ${param.fieldName};
}
public ${param.fieldType} get${param.fieldName?cap_first}(){
return this.${param.fieldName};
}
</#list>
}

其中的${xxx}就是变量了,由外部传入。可以看到模板还有类似java for each循环的语法。

接下来我们要填充数据了。数据来源后面详细说,先假定我们有了数据。其格式为Map,内容为:

1
2
3
4
5
6
7
8
9
10
11
12
Map<String, Object> beanMap = new HashMap<String, Object>();
beanMap.put("beanName", "User");// 实体类名
beanMap.put("interfaceName", "User");// 接口名
List<Map<String, String>> paramsList = new ArrayList<Map<String, String>>();
for (int i = 0; i < 4; i++) {
Map<String, String> tmpParamMap = new HashMap<String, String>();
tmpParamMap.put("fieldNote", "fieldNote" + i);
tmpParamMap.put("fieldType", "String");
tmpParamMap.put("fieldName", "fieldName" + i);
paramsList.add(tmpParamMap);
}
beanMap.put("params", paramsList);

最后我们使用模板替换数据(这里我的模板放在resources/model/下,目标文件放在resources/class/下):

1
2
3
4
5
6
7
Configuration config = new Configuration();
config.setObjectWrapper(new DefaultObjectWrapper());
Template template = config.getTemplate("src/main/resources/model/entity.ftl", "UTF-8");
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("src/main/resources/class/User.java"), "UTF-8"));
template.process(rootMap, out);
out.flush();
out.close();

这样最后生成的User.java的内容大概如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.tenny.user.entity;
import java.util.Date;
public class User {
// 主键id
private Integer id;
// 用户名
private String username;
// 密码
private String password;
// 用户类型#1、管理员;2、普通用户
private Integer type;
// 创建时间
private Date createtime;
public void setId(Integer id){
this.id = id;
}
public Integer getId(){
return this.id;
}
public void setUsername(String username){
this.username = username;
}
public String getUsername(){
return this.username;
}
public void setPassword(String password){
this.password = password;
}
public String getPassword(){
return this.password;
}
public void setType(Integer type){
this.type = type;
}
public Integer getType(){
return this.type;
}
public void setCreatetime(Date createtime){
this.createtime = createtime;
}
public Date getCreatetime(){
return this.createtime;
}
}

补充

其实上面已经基本将Freemarker的用法展现了。但是我们还可以做更多改进。

使用数据库作为数据来源

模板中要替换的数据,我们可以从外部手动输入,然后由程序拼装成模板引擎需要的格式。但因为我们最开始也说了,大部分相同的是增删改查,不同的是业务数据库表,所以我们可以直接将数据库表字段作为数据来源。

假如你使用的是mysql,可以写一个mysql工具类,获取数据库字段信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package utils;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MysqlUtil {
private static String DRIVER_CLASS = "com.mysql.jdbc.Driver";
private static String DATABASE_URL = "jdbc:mysql://localhost:3306/news";
private static String DATABASE_URL_PREFIX = "jdbc:mysql://";
private static String DATABASE_USER = "root";
private static String DATABASE_PASSWORD = "tenny123";
private static Connection con = null;
private static DatabaseMetaData dbmd = null;
/**
* 初始化数据库链接
* @param db_url 数据库地址(ex: localhost:3306/news)
* @param db_user 用户名
* @param db_pw 密码
*/
public static void init(String db_url, String db_user, String db_pw){
try {
DATABASE_URL = DATABASE_URL_PREFIX + db_url;
DATABASE_USER = db_user;
DATABASE_PASSWORD = db_pw;
Class.forName(DRIVER_CLASS);
con = DriverManager.getConnection(DATABASE_URL, DATABASE_USER, DATABASE_PASSWORD);
dbmd = con.getMetaData();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取当前用户所有表
* @return
*/
public static List<String> getTables(){
List<String> tables = new ArrayList<String>();
try {
ResultSet rs = dbmd.getTables(null, DATABASE_USER, null, new String[] { "TABLE" });
while (rs.next()) {
tables.add(rs.getString("TABLE_NAME"));
}
} catch (SQLException e) {
e.printStackTrace();
}
return tables;
}
/**
* 获取表字段信息
* @param tableName
* @return
*/
public static List<Map<String,String>> getTableCloumns(String tableName){
List<Map<String,String>> columns = new ArrayList<Map<String,String>>();
try{
Statement stmt = con.createStatement();
String sql = "select column_name, data_type, column_key, is_nullable, column_comment from information_schema.columns where table_name='" + tableName + "'and table_schema='" + DATABASE_URL.substring(DATABASE_URL.lastIndexOf("/") + 1, DATABASE_URL.length()) + "'";
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()){
HashMap<String,String> map = new HashMap<String,String>();
map.put("columnName", rs.getString("column_name"));
map.put("dataType", rs.getString("data_type"));
map.put("isKey", ParamUtil.isEmpty(rs.getString("column_key"))?"false":"true");
map.put("notNull", rs.getString("is_nullable").equals("YES")?"false":"true");
map.put("comment", rs.getString("column_comment"));
columns.add(map);
}
}
catch (SQLException e){
e.printStackTrace();
}finally{
if(null != con){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return columns;
}
}

获取了数据表字段信息,再加上自己需要的一些属性,就可以给模板引擎生成最终代码文件了。

将freemarker转为字符串模板

还可以在一开始就将模板数据读取出来,存放在内存中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 将模板内容转成字符串
* @param modle 模板名【eg:entity.ftl】
* @return
*/
public static String file2Str(String modle){
StringBuffer buffer = new StringBuffer();
try {
BufferedReader br = new BufferedReader(new FileReader("/WEB-INF/templates/" + model));
String line = "";
while((line = br.readLine()) != null){
buffer.append(line).append(System.getProperty("line.separator"));// 保持原有换行格式
}
br.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return buffer.toString();
}

当然,利用字符串模板填充数据的写法有些不同

1
2
3
4
5
6
7
8
9
10
11
12
Configuration config = new Configuration();
StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
stringTemplateLoader.putTemplate("entity", "this is a template of ${entityName}");
config.setTemplateLoader(stringTemplateLoader);
StringWriter stringWriter = new StringWriter();
Template template = config.getTemplate("entity", "UTF-8");
Writer out = new BufferedWriter(stringWriter);
template.process(rootMap, out);
out.flush();
out.close();
return stringWriter.toString();

这样可以将其输入到html页面观看。如果需要的话,还可以随时修改模板,后台接收前端的字符串模板。如此,模板和数据都可以随意定制,岂不是更加灵活方便。

其他

有了以上的认知,我们可以做一个页面,输入数据库参数后,选中某个表,就可以生成关于改表的增删改查代码。或者在页面上添加字段,用这些字段来生成代码。这个就可以自由发挥了~

帖几张我自己的页面截图(没啥样式,吃藕勿喷)
首页
我已经将模板转换成字符串输出了,这样方便查看(其实直接看模板文件也一样= =!)
模板字符串
点击获取表,后台就根据数据库属性查询列表,然后我们选择某一个表,前端自动填充”实体名”和”接口名”属性,一般情况无需修改,点击提交,当当~我们的代码就出来了
代码

当然还可以自定义字段,比如我们不选表,也就是说不用数据库作为来源,在页面上添加几个字段
添加字段1

添加字段2

如果你也使用spring mvc,可以和我一样,设定好多个模板后,一次性将controller-service-serviceImpl-dao-entity-mapping-test全部产生。后面甚至可以继续设定目录或包,产生的代码就可以直接使用啦。

总结

嗯,一句话,模板+数据=最终代码。

模板根据目标代码制定,数据来源我们根据实际情况获取,如此便可diy自己的autocode。

我的autocode地址:https://github.com/tenny-peng/autocode

参考链接:http://www.cnblogs.com/yejg1212/p/4322452.html
     http://blog.csdn.net/xiekuntarena/article/details/53032907
     http://blog.csdn.net/5iasp/article/details/27181365

rsa之js加密java解密

发表于 2017-07-14 | 分类于 rsa

需求

有一个需求,登录密码不能明文传输,需要前端加密,后端接收后再解密。这里使用rsa加密,在此记录简单的demo。

RSA简介

RSA算法是一种非对称密码算法,所谓非对称,就是指该算法需要一对密钥,使用其中一个加密,则需要用另一个才能解密。使用时,先生成一对密钥——公钥+私钥,公钥对外公开,供加密使用,私钥则需妥善保存,用于解密已加密数据。

使用

在这个demo中,最终实现前端加密密码,后端解密密码。

  1. 引入依赖包,,除spring mvc等常用包外,这里需要commons-codec

    1
    2
    3
    4
    5
    6
    <!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
    <dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.10</version>
    </dependency>
  2. 编写RSAUtils,工具类,实现生成密钥,加密解密等方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    package com.rsa.utils;
    import java.security.Key;
    import java.security.KeyFactory;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.NoSuchAlgorithmException;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    import java.util.HashMap;
    import java.util.Map;
    import javax.crypto.Cipher;
    import org.apache.commons.codec.binary.Base64;
    public class RSAUtils {
    public static final String KEY_ALGORITHM = "RSA";
    public static final String PUBLIC_KEY = "RSAPublicKey";
    public static final String PRIVATE_KEY = "RSAPrivateKey";
    public static byte[] decryptBASE64(String key) {
    return Base64.decodeBase64(key);
    }
    public static String encryptBASE64(byte[] bytes) {
    return Base64.encodeBase64String(bytes);
    }
    /**
    * 生成密钥
    * @return
    * @throws Exception
    */
    public static Map<String, Key> generateKey(){
    Map<String, Key> keyMap = new HashMap<String,Key>(2);
    try {
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
    keyPairGenerator.initialize(1024);
    KeyPair keyPair = keyPairGenerator.generateKeyPair();
    keyMap.put(PUBLIC_KEY, keyPair.getPublic());// 公钥
    keyMap.put(PRIVATE_KEY, keyPair.getPrivate());// 私钥
    }catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
    }
    return keyMap;
    }
    /**
    * 取得公钥
    * @param keyMap
    * @return
    * @throws Exception
    */
    public static String getPublicKey(Map<String, Key> keyMap) {
    Key key = keyMap.get(PUBLIC_KEY);
    return encryptBASE64(key.getEncoded());
    }
    /**
    * 取得私钥
    * @param keyMap
    * @return
    * @throws Exception
    */
    public static String getPrivateKey(Map<String, Key> keyMap) {
    Key key = (Key) keyMap.get(PRIVATE_KEY);
    return encryptBASE64(key.getEncoded());
    }
    /**
    * 解密<br>
    * @param data
    * @param key
    * @return
    * @throws Exception
    */
    public static String decrypt(String data, String key) {
    return decrypt(decryptBASE64(data), key);
    }
    /**
    * 解密
    * @param data
    * @param key
    * @return
    * @throws Exception
    */
    public static String decrypt(byte[] data, String key){
    String result = null;
    byte[] keyBytes = decryptBASE64(key);
    try {
    PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
    KeyFactory keyFactory= KeyFactory.getInstance(KEY_ALGORITHM);
    Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
    Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    byte[] decryptData = cipher.doFinal(data);
    result = new String(decryptData);
    } catch (Exception e) {
    e.printStackTrace();
    }
    return result;
    }
    /**
    * 加密
    * @param data
    * @param key
    * @return
    * @throws Exception
    */
    public static byte[] encrypt(String data, String key) {
    byte[] result = null;
    byte[] keyBytes = decryptBASE64(key);
    try{
    X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
    Key publicKey = keyFactory.generatePublic(x509KeySpec);
    Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    result = cipher.doFinal(data.getBytes());
    } catch(Exception e){
    e.printStackTrace();
    }
    return result;
    }
    }
  3. 后端测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import java.security.Key;
    import java.util.Map;
    import com.rsa.utils.RSAUtils;
    public class Test {
    public static void main(String[] args) {
    Map<String, Key> keyMap = RSAUtils.generateKey();
    String publicKey = RSAUtils.getPublicKey(keyMap);
    String privateKey = RSAUtils.getPrivateKey(keyMap);
    System.err.println("公钥: \n" + publicKey);
    System.err.println("私钥: \n" + privateKey);
    String data = "123456";
    byte[] decryptDate = RSAUtils.encrypt(data, publicKey);
    String decryptData = RSAUtils.decrypt(decryptDate, privateKey);
    System.out.println("解密数据: \n" + decryptData);
    }
    }
  4. 后端测试结果,验证后端工具类正确。

    1
    2
    3
    4
    5
    6
    公钥:
    MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCV38Y7LUhAjrE2zdXlPhtvGdqeiZXKLQwkjEyO3xH/jIUy+6NgXAgam99HtMpUQATbvsVT1oRAIkVFkqiDgIZDxLrlKdESoRLyaThGsUlmRLkUTT1Ofuzc8l8FEYGonb9A0uHAmqsXrwSZjNG0M812cbvt4Hjts6TgbzoYLsLHDQIDAQAB
    私钥:
    MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAJXfxjstSECOsTbN1eU+G28Z2p6JlcotDCSMTI7fEf+MhTL7o2BcCBqb30e0ylRABNu+xVPWhEAiRUWSqIOAhkPEuuUp0RKhEvJpOEaxSWZEuRRNPU5+7NzyXwURgaidv0DS4cCaqxevBJmM0bQzzXZxu+3geO2zpOBvOhguwscNAgMBAAECgYBFgvAx6iKkronK3VTjahbXRKp89Vsf1hzXpqqraRKz77ynlMaFnqmzja/VViixQq/+K1DiPZBBHqP6TLcTpryeZBM5OeDoA3eYawOFuM/lyUe6H91mCygkM2FDWQGxEiXkgpyui786MtyWeI53xHjSWOPa5zQYWuH9fHG3mivOAQJBAMjN2d1AFPNe/La73DHi7RcA6CK4eZmoKZQqawYZ2oWd1MMOFRl8dkmneOfFaGDUZ2mShXzFZk7oKFdTJG1FB2ECQQC/Ehc3MJd/awZZ4qejxTe2LetaQnuMclqreZK3ClXTwFe+Sk/ETwXFQHsfblPRN1NFScAoA3Eq6GBhnp9MoVstAkAI/7iowqttsK8QnWCj17CaXE8K50uDyFZ8rl33ewcg/86+Iw5tAvfmGxw+/sjLthkgURGsYshP9vV/3FkAkJxhAkEAqWBwFAyPP/Sv/J5f3V3GtUifibPFsgrtNXTgCkKvMrcfESDu9SbYBrPScVpsEtrohlOKc+4ZM+ArEF58+IFRQQJBAMKBOO2cUWDwmanz26KEUAxPNi5PsvQcZIvreBbRdNsC3zRsNJwaF3WVe9K/Yn53jkrxhj5UC8sk6/A6JsNehkk=
    解密数据:
    123456
  5. 前端加密,依赖jsencrypt.js。这里登录时才获取公钥,实际可以在访问此页面就获取公钥,并且密码改变时实时加密

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    function login() {
    $.ajax({
    type: "post",
    url: "/rsa-demo/user/getkey",
    data: {},
    dataType: 'json',
    success: function(result) {
    var publicKey = result.key;
    console.log(publicKey);
    var username = $('#username').val();
    var password = $('#password').val();
    var encrypt = new JSEncrypt();
    encrypt.setPublicKey(publicKey);
    var encrypted = encrypt.encrypt(password);
    $.ajax({
    type: "post",
    url: "/rsa-demo/user/login",
    data: {'username': username, 'password': encrypted},
    dataType: 'json',
    success: function(result) {
    alert(result.message);
    }
    });
    }
    });
    }
  6. 前端ajax请求对应的后端控制器,返回公钥和验证密码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    package com.rsa.controller;
    import java.security.Key;
    import java.util.HashMap;
    import java.util.Map;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    import com.rsa.utils.RSAUtils;
    @RestController
    @RequestMapping("/user")
    public class UserController {
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public Map<String, Object> login(HttpServletRequest requset, HttpServletResponse response){
    Map<String, Object> result = new HashMap<String, Object>();
    HttpSession session = requset.getSession();
    // 没有密钥非法登录
    if(session.getAttribute(RSAUtils.PUBLIC_KEY) == null || session.getAttribute(RSAUtils.PRIVATE_KEY) == null){
    result.put("message", "登录状态异常,请刷新重试");
    return result;
    }
    String username = requset.getParameter("username");
    String password = requset.getParameter("password");
    String privateKey = (String) session.getAttribute(RSAUtils.PRIVATE_KEY);
    long startTime = System.currentTimeMillis();
    String decryptData = RSAUtils.decrypt(password, privateKey);
    long endTime = System.currentTimeMillis();
    System.err.println("解密所用时间:" + (endTime - startTime) + "ms");
    // 模拟数据验证
    if(username.equals("test") && decryptData.equals("123456")){
    // 登录成功删除密钥
    session.removeAttribute(RSAUtils.PUBLIC_KEY);
    session.removeAttribute(RSAUtils.PRIVATE_KEY);
    result.put("message", "登录成功");
    }else{
    result.put("message", "用户名或密码错误");
    }
    return result;
    };
    @RequestMapping(value = "/getkey")
    public Map<String, Object> getKey(HttpServletRequest requset){
    Map<String, Object> result = new HashMap<String, Object>();
    HttpSession session = requset.getSession();
    long startTime = System.currentTimeMillis();
    // 访问当前页面设置密钥
    Map<String, Key> keyMap = RSAUtils.generateKey();
    String publicKey = RSAUtils.getPublicKey(keyMap);
    String privateKey = RSAUtils.getPrivateKey(keyMap);
    long endTime = System.currentTimeMillis();
    System.err.println("生成key所用时间:" + (endTime - startTime) + "ms");
    session.setAttribute(RSAUtils.PUBLIC_KEY, publicKey);
    session.setAttribute(RSAUtils.PRIVATE_KEY, privateKey);
    result.put("key", publicKey);
    return result;
    }
    }

小结

按照正常流程,用户访问到登陆页面,后端给前端发送一个公钥,在用户输入密码登录时,需要先对密码加密,再传输。后端接受到已加密数据后,使用此公钥对应的私钥解密数据。后续验证数据库密码等省略。

需要注意的是,为保证安全,避免重放攻击和直接post请求,在登录前生成新的密钥对,登录后销毁该密钥对。在用户访问登录页面时,生成密钥对放入session,验证登录成功后,销毁该密钥对。因为每次生成新的公钥,所以重复发送同一请求无法解密通过。直接使用post请求也会因为密钥过期或不存在不予通过。

另外,RSA还可以生成数字签名。上述加密数据是用公钥加密,私钥解密。而数字签名用私钥加密,公钥解密,用于验证消息发送来源及消息完整性。因为私钥只有我(服务端)知道,假如客户端收到数字签名后用公钥无法解开,说明此服务端不可信。

参考链接:http://blog.csdn.net/cut001/article/details/53189645
     https://baike.baidu.com/item/RSA%E7%AE%97%E6%B3%95

HttpClient模拟post请求

发表于 2017-06-30

因项目需要,使用HttpClient模拟请求,故了解学习,记录在此。

简介

HttpClient 是 Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。

使用

使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可:

  1. 创建CloseableHttpClient对象。
  2. 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。
  3. 如果需要发送请求参数,可调用setEntity(HttpEntity entity)方法来设置请求参数。setParams方法已过时(4.4.1版本)。
  4. 调用HttpGet、HttpPost对象的setHeader(String name, String value)方法设置header信息,或者调用setHeaders(Header[] headers)设置一组header信息。
  5. 调用CloseableHttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个CloseableHttpResponse。
  6. 调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容;调用CloseableHttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头。
  7. 释放连接。无论执行方法是否成功,都必须释放连接。

示例

先下载最新的jar包或者maven引入

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>

用HttpClient编写自己的请求工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package com.demo.utils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
public class HttpClientUtils {
/**
* 通过httpClient发送post请求
* @param url 请求地址,非空
* @param map 请求参数
* @param encoding 编码格式,为空则默认UTF-8
*/
public static String sendPost(String url, Map<String,String> map, String encoding){
String result = "";
if(url == null){
result = "url不能为空";
return result;
}
if(encoding == null || encoding.isEmpty()){
encoding = "UTF-8";
}
// 创建httpClient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
// 创建post方式请求对象
HttpPost httpPost = new HttpPost(url);
// 装填参数
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
if(map != null){
for(Entry<String, String> entry : map.entrySet()){
nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
}
try {
// 参数转码
UrlEncodedFormEntity uefEntity = new UrlEncodedFormEntity(nvps, encoding);
//设置参数到请求对象中
httpPost.setEntity(uefEntity);
System.out.println("--------------------------------------");
System.out.println("请求地址: " + httpPost.getURI());
System.out.println("请求参数: " + nvps.toString());
System.out.println("--------------------------------------");
//设置header信息
//指定报文头【Content-type】、【User-Agent】
httpPost.setHeader("Content-type", "application/x-www-form-urlencoded");
httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
//执行请求操作,并拿到结果(同步阻塞)
CloseableHttpResponse response = httpclient.execute(httpPost);
HttpEntity entity = response.getEntity();
if (entity != null) {
//按指定编码转换结果实体为String类型
result = EntityUtils.toString(entity, encoding);
System.out.println("返回内容:");
System.out.println("--------------------------------------");
System.out.println(result);
System.out.println("--------------------------------------");
}
EntityUtils.consume(entity);
response.close();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
try {
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
}

编写测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.demo;
import java.util.HashMap;
import java.util.Map;
import com.demo.utils.HttpClientUtils;
public class Main {
public static void main(String[] args) {
String url = "http://php.weather.sina.com.cn/iframe/index/w_cl.php";
Map<String, String> map = new HashMap<String, String>();
map.put("city", "深圳");
map.put("charset", "utf-8");
HttpClientUtils.sendPost(url, map, null);
}
}

控制台查看结果

1
2
3
4
5
6
7
8
9
--------------------------------------
请求地址: http://php.weather.sina.com.cn/iframe/index/w_cl.php
请求参数: [charset=utf-8, city=深圳]
--------------------------------------
返回内容:
--------------------------------------
(function(){var w=[];w['深圳']=[{s1:'阵雨',s2:'多云',f1:'zhenyu',f2:'duoyun',t1:'32',t2:'27',p1:'≤3',p2:'≤3',d1:'无持续风向',d2:'无持续风向'}];var add={now:'2017-06-30 14:45:59',time:'1498805159',update:'北京时间06月30日12:10更新',error:'0',total:'1'};window.SWther={w:w,add:add};})();//0
--------------------------------------

附录

顺便记录一下java直接发送http请求的例子。

请求工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package com.demo.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
public class HttpRequest {
/**
* 向指定URL发送GET方法的请求
*
* @param url 发送请求的URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return URL 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param) {
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString);
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.demo;
import com.demo.utils.HttpRequest;
public class Main {
public static void main(String[] args) {
String url = "http://php.weather.sina.com.cn/iframe/index/w_cl.php";
String result = HttpRequest.sendPost(url, "city=深圳&charset=utf-8");
System.out.println(result);
}
}

控制台输出

1
(function(){var w=[];w['深圳']=[{s1:'阵雨',s2:'多云',f1:'zhenyu',f2:'duoyun',t1:'32',t2:'27',p1:'≤3',p2:'≤3',d1:'无持续风向',d2:'无持续风向'}];var add={now:'2017-06-30 14:58:03',time:'1498805883',update:'北京时间06月30日12:10更新',error:'0',total:'1'};window.SWther={w:w,add:add};})();//0

参考

http://www.cnblogs.com/zhuawang/archive/2012/12/08/2809380.html
http://blog.csdn.net/xiaoxian8023/article/details/49863967

mysql远程连接错误10060

发表于 2017-06-26 | 分类于 mysql

在公司协同开发时,同事需要在局域网内访问我的mysql数据库。但是连接超时,并返回错误代码10060。特此记录一下解决过程。

先找到数据库mysql中的user表,将对应访问的用户的Host由localhost改为%(允许任何IP地址访问)

user表Host

然后关闭防火墙即可。

但是为了安全着想,不想关闭防火墙,我们可以在防火墙中添加新的规则。

  1. 控制面板->系统和安全->防火墙
  2. 点击高级设置->入站规则,新建规则,选择端口,下一步
  3. TCP 端口,特定本地端口(S):填写80,3306,下一步
  4. 允许连接,下一步
  5. 默认全选,下一步
  6. 填写配置名称,如”mysql局域网访问策略”,完成。

配置范例见:http://maximumpcguides.com/windows-7/open-a-port-in-windows-7s-firewall/

这样你的同事就可以在局域网内访问你的数据库了。

参考链接:http://www.cnblogs.com/meetweb/p/3701443.html

web安全入门

发表于 2017-06-21 | 分类于 web安全

最近项目涉及到安全方面,自己特意了解了一下,记录在此,共同学习。

常见的web安全有以下几个方面

  • 同源策略(Same Origin Policy)
  • 跨站脚本攻击XSS(Cross Site Scripting)
  • 跨站请求伪造CSRF(Cross-site Request Forgery)
  • 点击劫持(Click Jacking)
  • SQL注入(SQL Injection)

同源策略

含义

所谓同源策略,指的是浏览器对不同源的脚本或者文本的访问方式进行的限制。比如源a的js不能读取或设置引入的源b的元素属性。

所谓”同源”指的是”三个相同”

  • 协议相同
  • 域名相同
  • 端口相同

举例来说,http://www.example.com/dir/page.html 这个网址,协议是 http:// ,域名是 www.example.com ,端口是 80(默认端口可以省略)。它的同源情况如下

1
2
3
4
http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)

目的

同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。

设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,Cookie 包含的信息(比如存款总额)就会泄漏。而Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。

由此可见,”同源政策”是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。

限制

随着互联网的发展,”同源政策”越来越严格。目前,如果非同源,共有三种行为受到限制

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM 无法获得
  • AJAX 请求不能发送

Ajax应用

我们可以在本地模拟非同源的ajax请求。

先写一个简单的web应用作为服务方,提供一个登陆服务接口,部署运行在tomcat上,此tomcat端口为8080。

再写一个小工程,只有一个简单的登陆页面。部署在另一个tomcat上,此tomcat端口设定为8081。

如此一来,两个工程就不是同源了,因为端口不同。我们在登陆页面发送ajax请求,出现错误:

1
XMLHttpRequest cannot load http://192.168.2.82:8080/Demo/user/login. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8081' is therefore not allowed access. The response had HTTP status code 403.

要解决这个问题,可以在服务器方设定

1
response.setHeader("Access-Control-Allow-Origin", "*");

服务方就可以允许其他域名访问。

要设定允许某个域名或某几个域名如下

1
2
3
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8081");
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8081,http://localhost:8082");

在spring mvc中,有一个注解@CrossOrigin,在需要提供跨域访问的方法上添加即可

1
@CrossOrigin(origins = {"http://localhost:8081", "http://192.168.2.99:8080"})

这样就可以成功跨域登陆了。

实际应用中,也可以让ajax访问同源服务接口,再由此服务去访问其他服务。

跨站脚本攻击

简介

跨站脚本攻击(Cross Site Scripting),为了不和层叠样式表(Cascading Style Sheets,CSS)缩写混淆,所以在安全领域叫做XSS。

XSS攻击,是指黑客通过”HTML注入”篡改了网页,插入了恶意的脚本,在用户浏览网页时,代码执行,从而实现用户浏览器。对受害用户可能采取Cookie资料窃取、会话劫持、钓鱼欺骗等各种攻击。

XSS有如下几种类型

  1. 反射型XSS

    反射型XSS只是简单地把用户输入的数据”反射”给浏览器。也就是说,黑客需要诱使用户点击一个恶意链接,才能攻击成功。反射型XSS也叫非持久型XSS。

    比如

    1
    <div>${message}</div>

    正常情况用户提交信息

    1
    http://xxx.com/test.html?param=hello

    那么浏览器正常输出”hello”信息

    但是如果用户输入了一段HTML代码

    1
    http://xxx.com/test.html?param=<script>alert("xss")</script>

    那么页面弹框,显示”XSS”

  2. 存储型XSS

    存储型XSS会把用户输入的数据”存储”在服务器。这种XSS具有很强的稳定性。

    比较常见的场景是,黑客写了一遍包含有恶意JavaScript的博客文章,文章发表后,所有访问该博客文章的用户,都会在他们的浏览器中执行这段恶意的JavaScript代码。黑客把恶意脚本保存到服务器端,所以这种XSS攻击就叫做”存储型XSS”。

  3. DOM Based XSS

    通过修改页面的DOM节点形成的XSS称之为DOM Based XSS。实际上,这种类型的XSS并非按照”数据是否保存在服务器端”来划分,DOM Based XSS从效果上来说也是反射型XSS。

    假设xss.html页面代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!DOCTYPE HTML>
    <html>
    <head>
    <title>xss</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    </head>
    <body>
    <div id="t"></div>
    <input type="text" id="text" value="" />
    <input type="button" id="s" value="write" onclick="test()" />
    </body>
    <script>
    function test(){
    var str = document.getElementById("text").value;
    document.getElementById("t").innerHTML = "<a href='" + str + "'>testLink</a>";
    }
    </script>
    </html>

    文本框输入内容并点击write按钮后,会在当前页面插入一个超链接,其地址为输入的内容。
    正常输入
    假设用户输入为

    1
    ' onclick=alert(/xss/) //

    那么页面代码变为

    1
    <a href'' onclick=alert(/xss/)//'>testLink</a>

    首先用第一个单引号闭合掉href的第一个单引号,然后插入一个onclick事件,最后在用//注释掉第二个单引号。

    点击链接,脚本执行
    脚本输入

防御

XSS攻击成功的根本原因是将输入的数据当成了代码来执行,从而违背了原来的语义。所以要在输入的时候要严格过滤,输出的时候也要进行检查。

输入检查

常见的XSS攻击,SQL注入,都要求攻击者构造一些特殊字符,这些特殊字符是正常用户不会用到的,所以输入检查很有必要。

只接受指定长度范围和期望格式的的内容提交,阻止或者忽略除此外的其他任何数据。比如:用户名只能是字母加数字,手机不长于16位,且大陆手机必须以13x,15x开头,否则非法。过滤一些些常见的敏感字符,例如:

1
< > ‘ “ & # \ javascript expression "onclick=" "onfocus"

过滤或移除特殊的Html标签, 例如:

1
<script>, <iframe> , &lt; for <, &gt; for >, &quot for

过滤JavaScript 事件的标签,例如 “onclick=”, “onfocus” 等等。

输出检查

一般来说,除了富文本的输出外,在变量输出到HTML页面时,可以使用编码(HtmlEncode)或转义的方式来防御XSS攻击。

DOM型的防御

把变量输出到页面时要做好相关的编码转义工作,如要输出到 <script>中,可以进行JS编码;要输出到HTML内容或属性,则进行HTML编码处理。根据不同的语境采用不同的编码处理方式。

HttpOnly

将重要的cookie标记为http only, 这样的话当浏览器向Web服务器发起请求的时就会带上cookie字段,但是在脚本中却不能访问这个cookie,这样就避免了XSS攻击利用JavaScript的document.cookie获取cookie:

跨站请求伪造

原理

CSRF原理比较简单,如图
csrf原理图

  1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;

  2. 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;

  3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;

  4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;

  5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。

假设用户在A网站可以删除自己的文章,其请求地址类似http://xxx.com/del.do?id=101。并且已经登录。

攻击者先构造一个自己的网页csrf.html,其内容为

1
<img src="http://xxx.com/del.do?id=102">

攻击者诱使目标用户C访问该页面,之后再回去查看自己的文章,发现id为102的文章被删除了。

原来在刚才访问csfr.html时,图片标签发送了一次请求,导致该文章被删除。

防御

验证码

因为csrf攻击是在用户不知情的情况下发起请求。验证码则强制用户与应用交互。

但是出于用户体验考虑,网站不能给所有操作都加验证码。所以验证码只能作为一种辅助手段。

Referer Check

根据HTTP协议,在HTTP头中有一个字段叫Referer,它记录了该HTTP请求的来源地址。在通常情况下,访问一个安全受限页面的请求必须来自于同一个网站。

在互联网中,Referer Check的常见用法是防止图片盗链。同理也可以检查请求是否来自合法的源。

某银行的转账是通过用户访问 http://bank.test/test?page=10&userID=101&money=10000 页面完成,用户必须先登录bank.test,然后通过点击页面上的按钮来触发转账事件。当用户提交请求时,该转账请求的Referer值就会是转账按钮所在页面的URL(本例中,通常是以bank.test域名开头的地址)。而如果攻击者要对银行网站实施CSRF攻击,他只能在自己的网站构造请求,当用户通过攻击者的网站发送请求到银行时,该请求的Referer是指向攻击者的网站,则银行网站拒绝该请求。

添加Token

CSRF攻击之所以能够成功,是因为攻击者可以伪造用户的请求,该请求中所有的用户验证信息都存在于Cookie中,因此攻击者可以在不知道这些验证信息的情况下直接利用用户自己的Cookie来通过安全验证。

由此可知,抵御CSRF攻击的关键在于:在请求中放入攻击者所不能伪造的信息,并且该信息不存在于Cookie之中。鉴于此,系统开发者可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务器端建立一个拦截器来验证这个token,如果请求中没有token或者token内容不正确,则认为可能是CSRF攻击而拒绝该请求。

点击劫持

点击劫持,这个术语是Robert Hansen 和 Jeremiah Grossman这2位安全研究专家给出的;其实在2008年9月,Adobe公司就发表了一份公开演讲,关于点击劫持问题的,因为其Flash产品的缺陷可以被严重的恶意利用。

点击劫持/UI重定向,是指恶意网站伪造一个看似可信的元素(如PayPal的donate按钮,或一个Send按钮-by Gmail等你可能使用的邮箱网站),根据RSnake and Jeremiah的调查证明,用户点击这些貌似可信的Sites的任何元素都可能触发你的话筒或者摄像头,远程攻击者可同时立即对你进行监控。

更常用的方法是,攻击者在他控制的网站用框架包含一个可信Sites,剥除掉上下文或者透明化这个Sites,这样他就可以轻易的操控你,而你最后可能就是给他发送转账,或者给他任何特权,而这些操作用户是完全不知晓的,后台进行;更甚的,如果这个恶意站点允许使用JS,那么攻击者可以及其轻松的把隐藏的元素精确地放置在鼠标指针下,这样的话不管用户点哪儿,攻击者都赢了;更更有甚的,攻击者可以在JS被禁用的情况下进行欺骗,只需要骗取用户点击一个链接或者按钮。

注意:点击劫持在任何浏览器上都存在,因为点击劫持不是浏览器漏洞或者Bug造成的,不能一夜就打上补丁;相反,点击劫持是利用最基本的标准Web特点,在任何位置都能实现,而这种天然缺陷是无法在短时间内改善的。

点击劫持是一种视觉上的欺骗手段。大概有两种方式,一是攻击者使用一个透明的,不可见的iframe,覆盖在一个网页上,然后诱使用户在该页面上进行操作,此时用户将在不知情的情况下点击透明的iframe页面,可以诱使用户恰好点击在iframe页面的一些功能性按钮上;二是攻击者使用一张图片覆盖在网页,遮挡网页原有位置的含义。

iframe覆盖

先写一个src.html,模拟源网页。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE HTML>
<html>
<head>
<title>原页面</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
button{
position:absolute;
top: 315px;
left: 462px;
z-index: 1;
width: 72px;
height: 26px;
}
</style>
</head>
<body>
<button onclick="func()">加关注</button>
</body>
<script>
function func(){
alert("关注成功!");
}
</script>
</html>

这个页面有一个按钮,点击后触发事件。

src.html

再写一个jack.html,劫持页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE HTML>
<html>
<head>
<title>点击劫持</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
html,body,iframe{
display: block;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
border:none;
}
iframe{
opacity:0;
filter:alpha(opacity=0);
position:absolute;
z-index:2;
}
button{
position:absolute;
top: 335px;
left: 462px;
z-index: 1;
width: 72px;
height: 26px;
}
</style>
</head>
<body>
那些不能说的秘密
<button>查看详情</button>
<iframe src="src.html"></iframe>
</body>
</html>

这个页面用一个按钮覆盖了原网页的按钮,用户点击后,实际就点击到了原网页的按钮。

jack.html

将透明度调一下查看按钮布局

透明jack.html

图片覆盖

先在之前的src.html中加入一个logo图片

src_logo

在jack.html中同样添加一个图片

jack_logo

调解透明度可以看到背后实际情况

透明jack_logo

假如LOGO图片对应的链接是某个网站,那么用户点击该图片,就会被链接到假的网站上。

图片也可以伪装得像正常的链接,按钮;或者图片中构造文字,覆盖在关键位置,可能会改变原有的意思。这种情况下,不需要用户点击,也能达到欺骗的目的,比如覆盖了页面的联系电话。

防御

iframe防御

通常可以写一段JavaScript代码,以禁止iframe的嵌套。

1
2
3
if(top.location != location){
top.location = self.location;
}

在src中加入该代码,再访问jack.html,发现浏览器自动跳转到了src.html。

由于是用JavaScript写的,这样的控制能力不是特别强,有许多方法可以绕过它。一个更好的方案是使用一个HTTP头——X-Frame-Options。

X-Frame-Options可以说是为了解决ClickJacking而生的。

它有三个可选值

  • DENY:浏览器拒绝当前页面加载任何frame页面
  • SAMEORIGIN:frame页面的地址只能为同源域名下的页面
  • ALLOW-FROM origin:定义允许frame加载的页面地址

图片防御

由于<img>标签在很多系统中是对用户开放的,因此在现实中有非常多的站点存在被图片覆盖攻击的可能。在防御时,需要检查用户提交的html代码中,<img>标签的style属性是否可能导致浮出。

SQL注入

简介

通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。

具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。

场景

比如我们开发了一个登陆模块,查询语句是

1
select * from user where name = ${name} and password = ${password};

正常情况下,我们拿到用户输入的用户名和密码拼接出来的语句是

1
select * from user where name = "admin" and password = "123456";

如果用户输入用户名为 “xxx” or 1 #,密码随便。那么拼接的语句就变成了

1
select * from user where username = "xxx" or 1 # and password = "123456";

“#”会使后面的and password = “123456”变成注释,这个语句会查询出所有用户,所以验证通过,成功登陆了系统。

防范

一般来说,从以下几点防范sql注入。

  1. 校验用户的输入

    拿到用户数据后,可以通过正则表达式,限制长度,对引号和”-“,”#”进行转换或过滤等。

  2. 使用预处理语句

    不要使用动态拼装SQL,而是使用参数化的SQL或者直接使用存储过程进行数据查询存取。

    比如之前的例子,在mybatis中,如果使用#{}代替${},编写的sql如下

    1
    select * from user where name = #{name} and password = #{password};

    这句话会在程序运行时会先编辑成带参数的sql语句

    1
    select * from user where name = ? and password = ?;

    当用户输入 “xxx” or 1 # 时,实际语句

    1
    select * from user where name = "'xxx' or 1 #" and password = "123456";

    这样是查不到用户的,所以登陆失败。

    当然有些地方必须使用$,比如order by name desc,这里的name desc都是传参进来的,直接按照参数字符串本身的含义。

    1
    select * from gooods order by ${param};

    这种情况下,我们必须严格控制传进来的参数。后台最好写方法确保参数正常。比如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //检验排序字段
    private String orderColumn(String orderColumn){
    //定义或获取可用排序字段集
    //判定外部传进来的参数字段是否存在于字段集中
    //如果存在,返回正确结果字段,不存在可抛出错误。
    }
    //检验排序顺序
    private String orderStringtoOrder(String orderString){
    String order = "asc";
    if(!orderString.isEmpty() && orderString.equals("desc")){
    order = "desc";
    }
    return order;
    }

    严格控制排序参数的正确性。

  3. 最小权限策略

    不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接,保证其正常使用即可。

    这么做的好处是,即使当前用户被攻破了,入侵者只能获取极小一部分权限,防止危害扩大。

  4. 加密信息

    不要把密码等机密信息明文存放,加密或者hash掉密码和敏感的信息。

  5. 异常提醒

    有些应用直接返回了异常信息给用户页面,这是很不安全的,通过这些异常信息,使用者可以知道程序栈,数据库类型甚至版本号等信息,这将有利于其进一步攻击。

    应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装,把异常信息存放在独立的表中。

参考

参考书籍: 白帽子讲Web安全
参考链接: http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
      http://blog.csdn.net/baidu_24024601/article/details/51957270
      http://www.cnblogs.com/lovesong/p/5248483.html
      http://blog.csdn.net/stilling2006/article/details/8526458
      http://blog.csdn.net/baochao95/article/details/52025180

hessian入门使用

发表于 2017-06-19 | 分类于 hessian

简介

Hessian是caucho公司开发的一种基于二进制RPC协议(Remote Procedure Call protocol)的轻量级远程调用框架。

在Java中使用Hessian:

服务器端:

  • 包含Hessian.jar包
  • 设计一个接口,用来给客户端调用
  • 实现该接口的功能
  • 配置web.xml,配好相应的servlet
  • 由于使用二进制RPC协议传输数据,对象必须进行序列化,实现Serializable 接口
  • 对于复杂对象可以使用Map的方法传递

客户端:

  • 包含Hessian.jar包
  • 具有和服务器端结构一样的接口。包括命名空间都最好一样
  • 利用HessianProxyFactory调用远程接口

入门例子

到 http://hessian.caucho.com/ 下载hessian.jar包,我这里使用的是hessian-4.0.51.jar。

服务器端

  1. Eclipse新建一个Web工程,命名为HseeionService。将hessian.jar包放入WEB-INF/lib中,并引入之;
  2. 创建pojo类;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package app.demo;
    import java.io.Serializable;
    public class User implements Serializable {
    private static final long serialVersionUID = 153519254199840035L;
    String userName = "soopy";
    String password = "showme";
    public User(String user, String pwd){
    this.userName = user;
    this.password = pwd;
    }
    public String getUserName(){
    return userName;
    }
    public String getPassword(){
    return password;
    }
    }
  3. 创建接口

    1
    2
    3
    4
    5
    6
    7
    package app.demo;
    public interface BasicAPI {
    public void setGreeting(String greeting);
    public String hello();
    public User getUser();
    }
  4. 实现接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package app.demo;
    public class BasicService implements BasicAPI {
    private String _greeting = "Hello, world";
    @Override
    public void setGreeting(String greeting) {
    _greeting = greeting;
    }
    @Override
    public String hello() {
    return _greeting;
    }
    @Override
    public User getUser() {
    return new User("prance", "meshow");
    }
    }
  5. 配置web.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    id="WebApp_ID"
    version="3.0">
    <servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class>
    <init-param>
    <param-name>service-class</param-name>
    <param-value>app.demo.BasicService</param-value>
    </init-param>
    </servlet>
    <servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
    </servlet-mapping>
    </web-app>
  6. 服务器测试test.jsp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <%@ page import="com.caucho.hessian.client.HessianProxyFactory,app.demo.BasicAPI"%>
    <%@page language="java"%>
    <%
    HessianProxyFactory factory = new HessianProxyFactory();
    String url = ("http://" +request.getServerName() + ":" +request.getServerPort() +
    request.getContextPath() + "/hello");
    out.println(url);
    out.println("<br>");
    BasicAPI basic = (BasicAPI) factory.create(BasicAPI.class,url);
    out.println("Hello: " + basic.hello());
    out.println("<br>");
    out.println("Hello: " + basic.getUser() .getUserName() );
    out.println("<br>");
    out.println("Hello: " +basic.getUser().getPassword() );
    %>
  7. 将HessianService部署到Tomcat等服务器上,访问 http://localhost:8080/HessianService/test.jsp ,浏览器结果:

    1
    2
    3
    4
    http://localhost:8080/HessianService/hello
    Hello: Hello, world
    Hello: prance
    Hello: meshow

客户端

  1. 创建一个工程,命名为HessianClient,同样引入hessian.jar包;
  2. 创建和服务端一样的pojo类;
  3. 创建和服务端一样的接口;
  4. 创建客户端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package app.demo;
    import java.net.MalformedURLException;
    import com.caucho.hessian.client.HessianProxyFactory;
    public class BasicClient {
    public static void main(String[] args) throws MalformedURLException {
    String url ="http://127.0.0.1:8080/HessianService/hello";
    HessianProxyFactory factory = new HessianProxyFactory();
    BasicAPI basic = (BasicAPI) factory.create(BasicAPI.class, url);
    System.out.println("Hello:" + basic.hello());
    System.out.println("Hello:" + basic.getUser().getUserName());
    System.out.println("Hello:" + basic.getUser().getPassword());
    basic.setGreeting("HelloGreeting");
    System.out.println("Hello:" + basic.hello());
    }
    }
  5. 运行客户端代码,控制台结果:

    1
    2
    3
    4
    Hello:Hello, world
    Hello:prance
    Hello:meshow
    Hello:HelloGreeting

说明

这里服务端和客户端都编写了同样的接口和基础类,只是为了演示简单例子。在实际使用中,应该是服务端将自己的接口及所需类打成jar包给客户端引入调用。

Spring整合Hessian

服务端项目

  1. 新建一个Maven项目,编辑pom.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
    http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.tenny</groupId>
    <artifactId>SpringHessianService</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringHessianService Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.16.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>com.caucho</groupId>
    <artifactId>hessian</artifactId>
    <version>4.0.7</version>
    </dependency>
    </dependencies>
    <build>
    <finalName>SpringHessianService</finalName>
    <plugins>
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
    <source>1.8</source>
    <target>1.8</target>
    <encoding>UTF-8</encoding>
    </configuration>
    </plugin>
    </plugins>
    </build>
    </project>
  2. 编辑web.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
    <display-name>Spring Hessian Service</display-name>
    <servlet>
    <servlet-name>HessianServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:hessian-server.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>HessianServlet</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>
    </web-app>
  3. 编辑hessian-server.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.tenny" />
    <bean name="/hello" class="org.springframework.remoting.caucho.HessianServiceExporter">
    <property name="service" ref="helloImpl" />
    <property name="serviceInterface" value="com.tenny.IHello" />
    </bean>
    </beans>
  4. 接口代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.tenny;
    /**
    * @author tenny
    *
    */
    public interface IHello {
    public String hello(String name);
    }
  5. 接口实现代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package com.tenny;
    import org.springframework.stereotype.Service;
    /**
    * @author tenny
    *
    */
    @Service
    public class HelloImpl implements IHello {
    @Override
    public String hello(String name) {
    String result = "hello " + name;
    System.out.println("service result: " + result);
    return result;
    }
    }

客户端项目

  1. 新建Maven工程,编辑pom.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
    http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.tenny</groupId>
    <artifactId>SpringHessianClient</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringHessianClient Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.16.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>com.caucho</groupId>
    <artifactId>hessian</artifactId>
    <version>4.0.7</version>
    </dependency>
    </dependencies>
    <build>
    <finalName>SpringHessianClient</finalName>
    <plugins>
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
    <source>1.8</source>
    <target>1.8</target>
    <encoding>UTF-8</encoding>
    </configuration>
    </plugin>
    </plugins>
    </build>
    </project>
  2. 编辑web.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
    <display-name>Spring Hessian Client</display-name>
    <servlet>
    <servlet-name>HessianClient</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:hessian-client.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>HessianClient</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>
    </web-app>
  3. 编辑hessian-client.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    <mvc:annotation-driven />
    <context:component-scan base-package="com.tenny" />
    <bean id="hello" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
    <property name="serviceUrl" value="http://localhost:8080/SpringHessianService/hello"></property>
    <property name="serviceInterface" value="com.tenny.IHello"></property>
    </bean>
    </beans>
  4. 接口代码(实际项目中引入服务端的接口jar包)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.tenny;
    /**
    * @author tenny
    *
    */
    public interface IHello {
    public String hello(String name);
    }
  5. Controller代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    package com.tenny;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    /**
    * @author tenny
    *
    */
    @RestController
    @RequestMapping("/hello")
    public class HelloController {
    @Autowired
    private IHello helloHess;
    @RequestMapping(value = "/sayHello", method=RequestMethod.GET)
    public String sayHello(){
    String result = helloHess.hello("tenny");
    System.out.println("client result: " + result);
    return result;
    }
    }

运行

分别启动服务端(端口为8080)和客户端(端口为8081)项目,浏览器地址访问 http://localhost:8081/SpringHessianClient/hello/sayHello 就能看到结果了。
其中页面显示hello tenny,服务端控制台打印service result: hello tenny,客户端控制台打印client result: hello tenny。

附件

SpringHessianService项目下载:https://github.com/tenny-peng/SpringHessianService
SpringHessianClient项目下载:https://github.com/tenny-peng/SpringHessianClient

DataTables使用入门

发表于 2017-06-19 | 分类于 DataTables

简介

Datatables是一款jquery表格插件。它是一个高度灵活的工具,可以将任何HTML表格添加高级的交互功能。

  • 分页,即时搜索和排序
  • 几乎支持任何数据源:DOM, javascript, Ajax 和 服务器处理
  • 支持不同主题 DataTables, jQuery UI, Bootstrap, Foundation
  • 各式各样的扩展: Editor, TableTools, FixedColumns ……
  • 丰富多样的option和强大的API
  • 支持国际化
  • 超过2900+个单元测试
  • 免费开源 ( MIT license )! 商业支持
  • 更多特性请到DataTables中文网查看

下载

可以到DataTables中文网下载最新版本的文档。

下载完目录如下:
DataTables目录

我们将使用media目录下的文件。
media目录

简单使用

在项目中使用Datatables,只需要引用三个文件即可,jQuery库,一个DT的核心js文件和一个DT的css文件。

demo目录

demo目录

demo.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE HTML>
<html>
<head>
<title>DataTables Demo</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="css/jquery.dataTables.css"></link>
</head>
<body>
<table id="table_id_example" class="display">
<caption>最近交易记录</caption>
</table>
</body>
<script src="js/jquery.js"></script>
<script src="js/jquery.dataTables.js"></script>
<script src="js/demo.js"></script>
</html>

demo.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
$(document).ready( function () {
var data = [
[
"20170527150032890",
"-0.10 CNY",
"提现",
"失败",
"2017-05-27 15:00:32",
"提现",
""
],
[
"20170527145824609",
"-3.00 CNY",
"冻结",
"成功",
"2017-05-27 14:58:24",
"冻结金额",
""
],
[
"20170527145704263",
"-3.00 CNY",
"提现",
"失败",
"2017-05-27 14:57:04",
"提现",
""
],
[
"20170527145226988",
"-100.00 CNY",
"冻结",
"成功",
"2017-05-27 14:52:26",
"冻结金额",
""
],
[
"20170525121845479",
"-0.01 CNY",
"提现",
"已受理",
"2017-05-27 09:28:09",
"重新提现",
"<button class='btn btn-warning btn-block' onclick=alert('haha') >取消提现</button>"
],
[
"20170527144117493",
"-0.11 CNY",
"提现",
"成功",
"2017-05-27 00:00:00",
"虚拟提现(冻结金额)",
""
],
[
"20170526165926389",
"-12.00 CNY",
"提现",
"已受理",
"2017-05-26 16:59:26",
"提现",
"<button class='btn btn-warning btn-block' onclick=alert('haha') >取消提现</button>"
],
[
"20170526165802358",
"-2.00 CNY",
"提现",
"已受理",
"2017-05-26 16:58:02",
"提现",
""
],
[
"20170526165520190",
"-1.00 CNY",
"提现",
"已受理",
"2017-05-26 16:55:20",
"提现",
"<button class='btn btn-warning btn-block' onclick=alert('haha') >取消提现</button>"
],
[
"20170526161241519",
"-1.00 CNY",
"提现",
"已受理",
"2017-05-26 16:12:41",
"提现",
""
],
[
"20170526165802358",
"-99.00 CNY",
"提现",
"已受理",
"2017-05-26 16:58:02",
"提现",
""
],
[
"20170526165520190",
"-99.00 CNY",
"提现",
"已受理",
"2017-05-26 16:55:20",
"提现",
""
],
[
"20170526161241519",
"-99.00 CNY",
"提现",
"已受理",
"2017-05-26 16:12:41",
"提现",
""
]
];
//DataTables 初始化
$('#table_id_example').DataTable( {
data: data,
columns: [
{ title: '批次号' },
{ title: '金额' },
{ title: '交易类型' },
{ title: '交易状态' },
{ title: '交易时间' },
{ title: '交易信息' },
{ title: '操作' }
]
} );
} );

结果

简单使用结果

添加国际化

Datatables中所使用的语言选项是通过 language 来配置的。 这是一个对象字符串,通过一个参数来描述Datatables的每个部分。

Datatables默认的是英语,这里我们改成中文。在demo.js文件添加 language 配置项,使其DataTables初始化代码像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//DataTables 初始化
$('#table_id_example').DataTable( {
data: data,
columns: [
{ title: '批次号' },
{ title: '金额' },
{ title: '交易类型' },
{ title: '交易状态' },
{ title: '交易时间' },
{ title: '交易信息' },
{ title: '操作' }
],
// 国际化
language: {
"sProcessing": "处理中...",
"sLengthMenu": "显示 _MENU_ 项结果",
"sZeroRecords": "没有匹配结果",
"sInfo": "显示第 _START_ 至 _END_ 项结果,共 _TOTAL_ 项",
"sInfoEmpty": "显示第 0 至 0 项结果,共 0 项",
"sInfoFiltered": "(由 _MAX_ 项结果过滤)",
"sInfoPostFix": "",
"sSearch": "搜索:",
"sUrl": "",
"sEmptyTable": "表中数据为空",
"sLoadingRecords": "载入中...",
"sInfoThousands": ",",
"oPaginate": {
"sFirst": "首页",
"sPrevious": "上页",
"sNext": "下页",
"sLast": "末页"
},
"oAria": {
"sSortAscending": ": 以升序排列此列",
"sSortDescending": ": 以降序排列此列"
}
}
} );

结果如图
国际化

关于表列

上面我们的写法是动态添加title

1
2
3
4
5
6
7
8
9
columns: [
{ title: '批次号' },
{ title: '金额' },
{ title: '交易类型' },
{ title: '交易状态' },
{ title: '交易时间' },
{ title: '交易信息' },
{ title: '操作' }
]

官网是另一种写法,首先在table里写好表列字段名,如:

1
2
3
4
5
6
7
8
<thead>
<tr>
<th>name</th>
<th>position</th>
<th>salary</th>
<th>office</th>
</tr>
</thead>

然后上面columns写法稍微有点不同

1
2
3
4
5
6
columns: [
{ data: 'name' },
{ data: 'position' },
{ data: 'salary' },
{ data: 'office' }
]

这样也是完全OK的,具体怎么用看自己喜好和实际业务了。

服务器支持

一次性处理大量数据DataTables性能会下降,因为 DT 需要渲染,数据越多,速度就越慢。

为了解决这个问题, DataTables 提供了服务器模式,把本来客户端所做的事情交给服务器去处理, 比如排序(order)、分页(paging)、过滤(filter)。

启用服务器模式时,每次请求 DataTables 会向服务器发送一些参数(当前分页,排序,搜索参数等),服务器则返回组装好的数据。

demo.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE HTML>
<html>
<head>
<title>DataTables Demo</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="css/jquery.dataTables.css"></link>
</head>
<body>
<table id="city" class="display">
<caption>city</caption>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>countryCode</th>
<th>district</th>
<th>population</th>
</tr>
</thead>
</table>
</body>
<script src="js/jquery.js"></script>
<script src="js/jquery.dataTables.js"></script>
<script src="js/demo.js"></script>
</html>

demo.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
$(document).ready( function () {
//DataTables 初始化
$('#country').DataTable( {
"processing": true, //开启加载等待提示,提示信息是下面language中的sProcessing配置
"serverSide": true, //打开服务器模式
"ajax": {
"url": "city/getall",
"type": "POST",
"data": function(d){ //d包含了DataTables发送到服务器的参数,这里还可以根据自己的业务添加参数
//可以自定义一些业务参数
//d.xxx = "xxx";
}
},
"columns": [
{ "data": 'id' },
{ "data": 'name' },
{ "data": 'countryCode' },
{ "data": 'district' },
{ "data": 'population' }
],
// 国际化
language: {
"sProcessing": "处理中...",
"sLengthMenu": "显示 _MENU_ 项结果",
"sZeroRecords": "没有匹配结果",
"sInfo": "显示第 _START_ 至 _END_ 项结果,共 _TOTAL_ 项",
"sInfoEmpty": "显示第 0 至 0 项结果,共 0 项",
"sInfoFiltered": "(由 _MAX_ 项结果过滤)",
"sInfoPostFix": "",
"sSearch": "搜索:",
"sUrl": "",
"sEmptyTable": "表中数据为空",
"sLoadingRecords": "载入中...",
"sInfoThousands": ",",
"oPaginate": {
"sFirst": "首页",
"sPrevious": "上页",
"sNext": "下页",
"sLast": "末页"
},
"oAria": {
"sSortAscending": ": 以升序排列此列",
"sSortDescending": ": 以降序排列此列"
}
}
} );
} );

查看结果

服务器模式
在浏览器调试窗口,我们可以看到发送到后台的参数(即上面ajax请求中d的数据):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
draw:1 //确保Ajax从服务器返回的是对应的,服务器接收到此参数后再返回
columns[0][data]:id
columns[0][name]:
columns[0][searchable]:true
columns[0][orderable]:true
columns[0][search][value]:
columns[0][search][regex]:false
columns[1][data]:name
columns[1][name]:
columns[1][searchable]:true
columns[1][orderable]:true
columns[1][search][value]:
columns[1][search][regex]:false
columns[2][data]:countryCode
columns[2][name]:
columns[2][searchable]:true
columns[2][orderable]:true
columns[2][search][value]:
columns[2][search][regex]:false
columns[3][data]:district
columns[3][name]:
columns[3][searchable]:true
columns[3][orderable]:true
columns[3][search][value]:
columns[3][search][regex]:false
columns[4][data]:population
columns[4][name]:
columns[4][searchable]:true
columns[4][orderable]:true
columns[4][search][value]:
columns[4][search][regex]:false
order[0][column]:0 //告诉后台那些列是需要排序的
order[0][dir]:asc //告诉后台列排序的方式, desc 降序 asc 升序
start:0 //第一条数据的起始位置,比如0代表第一条数据
length:10 //告诉服务器每页显示的条数
search[value]: //全局的搜索条件,此demo为空
search[regex]:false //为true代表全局搜索的值是作为正则表达式处理

服务器返回的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
{
"recordsFiltered": 4079, //过滤后的记录数
"data": [ //表格中需要显示的数据。
{
"countryCode": "AFG",
"district": "Kabol",
"name": "Kabul",
"id": 1,
"population": 1780000
},
{
"countryCode": "AFG",
"district": "Qandahar",
"name": "Qandahar",
"id": 2,
"population": 237500
},
{
"countryCode": "AFG",
"district": "Herat",
"name": "Herat",
"id": 3,
"population": 186800
},
{
"countryCode": "AFG",
"district": "Balkh",
"name": "Mazar-e-Sharif",
"id": 4,
"population": 127800
},
{
"countryCode": "NLD",
"district": "Noord-Holland",
"name": "Amsterdam",
"id": 5,
"population": 731200
},
{
"countryCode": "NLD",
"district": "Zuid-Holland",
"name": "Rotterdam",
"id": 6,
"population": 593321
},
{
"countryCode": "NLD",
"district": "Zuid-Holland",
"name": "Haag",
"id": 7,
"population": 440900
},
{
"countryCode": "NLD",
"district": "Utrecht",
"name": "Utrecht",
"id": 8,
"population": 234323
},
{
"countryCode": "NLD",
"district": "Noord-Brabant",
"name": "Eindhoven",
"id": 9,
"population": 201843
},
{
"countryCode": "NLD",
"district": "Noord-Brabant",
"name": "Tilburg",
"id": 10,
"population": 193238
}
],
"draw": 1, //Datatables发送的draw是多少那么服务器就返回多少
"recordsTotal": 4079 //数据库里总共记录数
}

点此查看更多服务器处理参数。

小结

DataTables是一个表格插件。既可以一次性获取大量数据在前端做分页处理(一般不推荐,数据量较大时客户端压力大,处理慢),也可以使用服务器处理分页(只返回当前页面的数据)。

常用的参数可能就那么几个,我们可以直接使用它们,也可以自己封装参数,比如”search[value]”我们可以改成”keyword”再传给后台,”order[0][column]”可以先在前端转换成对应”orderColumn”再传入后台,还可以加上其它特定的业务参数等。

其他一些未提及的,比如服务器模式的自定义回调函数,勇敢的去官网或google探索吧。

总体感觉,使用简单,配置灵活。

MyBatis分页插件PageHelper

发表于 2017-06-19 | 分类于 PageHelper

简介

PageHelper是一个Mybatis的分页插件,可以方便地对查询结果进行分页和排序。

在spring + mybatis 中使用 pagehelper

1. maven添加依赖

1
2
3
4
5
6
<!-- mybatis 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.1.4</version>
</dependency>

2. 编写mybatis-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 配置管理器 -->
<configuration>
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 4.0.0以后版本可以不设置该参数 -->
<property name="dialect" value="mysql"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
<!-- 和startPage中的pageNum效果一样-->
<property name="offsetAsPageNum" value="true"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,使用RowBounds分页会进行count查询 -->
<property name="rowBoundsWithCount" value="true"/>
<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
<property name="pageSizeZero" value="true"/>
<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
<!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
<!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
<property name="reasonable" value="true"/>
<!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
<!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默认值 -->
<!-- 不理解该含义的前提下,不要随便复制该配置 -->
<property name="params" value="pageNum=start;pageSize=limit;"/>
<!-- 支持通过Mapper接口参数来传递分页参数 -->
<property name="supportMethodsArguments" value="true"/>
<!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
<property name="returnPageInfo" value="check"/>
</plugin>
</plugins>
</configuration>

3. 在spring-mybatis配置文件中引入上述配置

1
2
3
4
5
6
7
<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath:com/demo/mapping/*.xml"></property>
</bean>

4. 业务逻辑代码

  1. mapper.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <select id="getAll" resultMap="BaseResultMap">
    SELECT
    <include refid="Base_Column_List" />
    FROM city
    WHERE 1 = 1
    <if test="pattern != null and pattern != ''">
    AND (
    name like CONCAT('%', #{pattern}, '%')
    OR
    countrycode like CONCAT('%', #{pattern}, '%')
    OR
    district like CONCAT('%', #{pattern}, '%')
    OR
    population like CONCAT('%', #{pattern}, '%')
    )
    </if>
    </select>
  2. dao

    1
    public List<Map<String,Object>> getAll(Map<String,Object> params);
  3. service

    1
    public List<Map<String, Object>> getAll(Map<String,Object> params);
  4. serviceImpl

    1
    2
    3
    public List<Map<String, Object>> getAll(Map<String,Object> params) {
    return dao.getAll(params);
    }
  5. controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 设置分页及排序参数:pageNum,页数,从1开始;pageSize,页面大小,每页查询数据量,如10;orderBy,排序字段及顺序,如"name desc"
    PageHelper.startPage(pageNum, pageSize);
    PageHelper.orderBy(orderBy);
    // 业务模糊查询参数
    Map<String, Object> params = new HashMap<String, Object>();
    params.put("pattern", patternStr);
    // 查询
    List<Map<String, Object>> dataList = cityService.getAll(params);
    PageInfo<Map<String, Object>> pageInfo = new PageInfo<Map<String, Object>>(dataList);
    // 结果
    pageInfo.getTotal();// 总结果数,如4079
    pageInfo.getList();// 数据结果,本次查询返回结果,如10个
  6. ajax请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    "ajax": {
    "url": "city/getall",
    "type": "POST",
    "data": {
    pageNum: 1; //查询第一页
    pageSize: 10; //查询10条记录
    orderColumn: "name"; // 后台的orderBy通过这里的orderColumn + orderDir拼接而成。
    orderDir: "desc"; //但是为了防止sql注入及非法参数,最好后台增加方法判断参数合法性,并返回合法值。
    pattern: "tenny"; //业务相关参数,模糊查询
    }
    }

查看结果

通过日志可以看到,插件对sql做了处理:1.先查询一条总数,2.在原sql上加入分页条件进行查询。

1
2
3
4
5
6
2017-06-19 11:15:38,569 DEBUG [com.demo.dao.ICityDao.getAll_COUNT] - ==> Preparing: SELECT count(0) FROM city WHERE 1 = 1
2017-06-19 11:15:38,601 DEBUG [com.demo.dao.ICityDao.getAll_COUNT] - ==> Parameters:
2017-06-19 11:15:38,841 DEBUG [com.demo.dao.ICityDao.getAll_COUNT] - <== Total: 1
2017-06-19 11:15:38,850 DEBUG [com.demo.dao.ICityDao.getAll] - ==> Preparing: SELECT id, name, countrycode, district, population FROM city WHERE 1 = 1 order by id asc limit ?,?
2017-06-19 11:15:38,850 DEBUG [com.demo.dao.ICityDao.getAll] - ==> Parameters: 0(Integer), 10(Integer)
2017-06-19 11:15:38,854 DEBUG [com.demo.dao.ICityDao.getAll] - <== Total: 10

pageInfo封装了查询条件及结果集:

1
PageInfo{pageNum=1, pageSize=10, size=10, startRow=1, endRow=10, total=4079, pages=408, list=Page{count=true, pageNum=1, pageSize=10, startRow=0, endRow=10, total=4079, pages=408, countSignal=false, orderBy='id asc', orderByOnly=false, reasonable=true, pageSizeZero=true}, firstPage=1, prePage=0, nextPage=2, lastPage=8, isFirstPage=true, isLastPage=false, hasPreviousPage=false, hasNextPage=true, navigatePages=8, navigatepageNums=[1, 2, 3, 4, 5, 6, 7, 8]}

123
Tenny Peng

Tenny Peng

29 日志
20 分类
27 标签
RSS
© 2018 Tenny Peng
由 Hexo 强力驱动
主题 - NexT.Pisces