IDEA 创建 Gradle Module

以创建名为 SpringIntegration 的 Gradle Module 为例介绍在 IDEA 创建 Gradle Module 的步骤。

IDEA 的 Maven Module 里修改 pom.xml 中的依赖后会自动下载相关的 Jar 包和源码,并添加到 classpath 里。

但是 IDEA 的 Gradle Module 里修改 build.gradle 中的依赖后却不会自动下载相关的 Jar 包和源码,并添加到 classpath 里,需要我们手动的在 Gradle 的工具窗口里点击刷新 Gradle Module 才会执行这些操作。

Spring + Fastweixin 微信开发

微信有两种模式,编辑模式和开发者模式,有些功能是互斥的,不可以同时使用,微信开发需要在开发者模式下进行(开发者模式下仍然可以去微信的网页上群发消息)。下面介绍的功能能满足大部分的需求,响应文本消息,图文消息,创建菜单,响应菜单消息等。

我们给微信提供服务有两种消息模式,被动和主动

  • 被动: 例如用户输入文本,点击菜单,微信服务器会访问我们的 Web 服务对应的 URL,我们返回对应的消息给微信服务器
  • 主动: 例如创建菜单,群发消息,这种模式需要我们主动去触发,给微信服务器发送消息,可以是执行某个定时任务触发,或者我们访问某个 URL 然后在其响应的代码里触发

Atom 常用插件和快捷键

Atom 以前很慢,所以一直不想用,在 1.0 版本后启动差不多需要 1.5 秒,已经快了很多,尝试了下,感觉很好,插件更好用,例如格式化插件 atom-beautify,jshint 等、界面更舒服,现在已经从 SublimeText 替换到 Atom 了,以下为常用的几个插件

本地服务映射为外网可访问-Ngrok

例如我们开发了一个网站,运行在我们自己的电脑上,本地访问地址是 http://localhost.com:8080,但是只能在自己的电脑和局域网访问,外网访问不了,例如在做微信公众号开发时如果不能被外网访问就很不方便。如果想要外网能访问我们的网站,则需要:

  • 购买一个域名和空间,把我们的网站部署上去
  • 使用工具把本地的网站服务映射为外网可访问的,例如 Ngrok,可支持 Mac,Windows,Linux

Spring 中配置 CORS

Ajax 以前要实现跨域访问,可以通过 JSONP、Flash 或者服务器中转的方式来实现,现在可以使用 CORS

跨域资源共享(CORS )是一种网络浏览器的技术规范,它为 Web 服务器定义了一种方式,允许网页从不同的域访问其资源,而这种访问是被同源策略所禁止的。CORS 系统定义了一种浏览器和服务器交互的方式来确定是否允许跨域请求。 它是一个妥协,有更大的灵活性,但比起简单地允许所有这些的要求来说更加安全。

Sublimetext 安装 jshint

一、安装 jshint 的依赖

  1. 由于 jshint 是依赖 Node.js 的,所以要先安装 Node.js,安装了 brew 的同学可以直接在 Terminal 使用以下命令

    1
    brew install node
  2. 安装 jshint

    1
    npm install -g jslint
  3. 测试 jshint: 写个 js 文件,例如某些行不用分号结束,乱赋值等,然后用下面的命令测试,会输出不规范的提示

    1
    jshint test.js

二、安装 SublimeLinter 及 jshint 插件

  1. 安装 SublimeLinter
  2. 安装 SublimeLinter-jshint
  3. 安装成功后就可以到 Sublimetext 里测试了,右键 SublimeLinter->Show All Erroes,就会有提示了,也会有实时的错误提示

三、参考

HTML 树的实现

树形结构的使用很广泛,例如用来显示文件夹和文件,组织机构的表示等,可以使用 zTree 来实现,这里我们将自己实现树,了解其原理。

实现下图中表示文件的树,主要是使用 <ul><li> 组织结构、CSS 调整显示效果、jQuery 实现点击的动态效果和 jQuery UI 实现拖拽操作 (主要的代码都在 HTML 和 CSS 上,JS 的代码只有 20 行)。

jQuery 表单验证插件 validate

jQuery Validate 插件为表单提供了强大的验证功能,让客户端表单验证变得更简单,同时提供了大量的定制选项,满足应用程序各种需求。该插件捆绑了一套有用的验证方法,包括 URL 和电子邮件验证,同时提供了一个用来编写用户自定义方法的 API,以及使用 Ajax 进行服务器端验证。所有的捆绑方法默认使用英语作为错误信息,且已翻译成其他 37 种语言。
该插件是由 Jörn Zaefferer 编写和维护的,他是 jQuery 团队的一名成员,是 jQuery UI 团队的主要开发人员,是 QUnit 的维护人员。该插件在 2006 年 jQuery 早期的时候就已经开始出现,并一直更新至今。

Bootstrap Progress Bar

1
2
3
<div class="progress">
<div class="progress-bar" style="width: 2%;"><span>2% 是不是显示不完整</span></div>
</div>

上面的代码显示出的进度条中由于进度只有 2%,只显示出了 2%是不是显示不完整 没有显示出来

Commons IO 例子

可以使用 Apache Commons-IO 操作文件:

功能 代码
获取文件名的后缀 FilenameUtils.getExtension(path)
获取文件名不包含后缀 FilenameUtils.getBaseName(path)
获取文件所在目录的路径 FilenameUtils.getFullPath(path)
复制文件 FileUtils.copyFileToDirectory()
复制文件夹 FileUtils.copyDirectory()
移动文件 FileUtils.moveFileToDirectory()
计算文件的 check sum FileUtils.checksumCRC32()
递归的创建目录 FileUtils.forceMkdir(new File("/Users/Biao/Desktop/a/b/c"))
还有更多文件相关的操作 ……

Commons-Lang3 例子

可以使用 Apache Commons-Lang3:

  • 日期格式化
  • 序列化和反序列化
  • 生成随机字符串
  • 字符串 join, 包含, 空判断, 缩写, 补全输出指定长度的字符串 (leftPad, center, rightPad) 等
  • 获取系统信息
  • 获取 Class 的信息
  • HTML escape and unescape
  • 数字相关, 如求数组中的最大最小值

jQuery 中 fadeIn() 和 slideDown() 同时执行

jQuery 没有直接提供 fadeIn() 和 slideDown() 同时执行的函数,但是可以像下面这样实现:

1
$elem.stop(true, true).fadeIn({ duration: 300, queue: false }).css('display', 'none').slideDown(300);

The answer is that once either effects activate, it takes the inline css property “display=none” off of the element. These hide effects require the display property to be set to “none”. So, just rearrange the order of methods and add a css-modifier in the chain between fade and slide.

fadeOut() 和 slideUp() 同时执行的代码如下:

1
$elem.stop(true, true).fadeOut({ duration: 300, queue: false }).slideUp(300);

Bootstrap 模态对话框

Bootstrap 的对话框用起来比较繁复,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Modal title</h4>
</div>
<div class="modal-body">
<p>One fine body…</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->

使用 bootstrap-dialog 达到相同的效果只需要 BootstrapDialog.alert('I want banana!'),不用写 HTML 代码,插件里已经写了。

Apache Commons

commons-io

Class Name Description
FilenameUtils 文件名称一些操作,如获取文件名的后缀
FileUtils 文件工具类,内置提供了大量文件转换方法,如 readFileToString(File,Path)
IOUtils 主要提供了 IO 常见操作
Stream 转换,关闭 Stream 等操作
1
'commons-io:commons-io:2.5'

Java 使用 FTP

Java 使用 FTP 等用 Apache Commons Net 就可以了:

Apache Commons Net™ library implements the client side of many basic Internet protocols. The purpose of the library is to provide fundamental protocol access, not higher-level abstractions. Therefore, some of the design violates object-oriented design principles. Our philosophy is to make the global functionality of a protocol accessible (e.g., TFTP send file and receive file) when possible, but also provide access to the fundamental protocols where applicable so that the programmer may construct his own custom implementations (e.g, the TFTP packet classes and the TFTP packet send and receive methods are exposed).

微 Web 服务的 REST 框架 Spark Framework

需要点击网页上的一个按钮打开本机上的文件或者修改本地的配置文件,由于安全的限制浏览器不能直接访问本地文件系统。为了实现这个功能,可以本地启动一个 Web 服务,浏览器访问这个 Web 服务(localhost),然后使用 Web 服务中的代码来访问本地文件系统。为了启动一个 Web 服务,一般我们会选择 Tomcat,Jetty,Websphere 等作为 Web 服务器响应 HTTP 请求,对于我们这么小的一个需求,就有些重量级了,如果能像打开一个普通程序一样打开程序就能启动 Web 服务就好了。

jQuery 404 时调用的方法

jQuery 的 ajax 能给不同的响应状态码指定回调函数,例如当连不上服务器(服务器没启动,网络有问题等)时调用 statusCode0 的方法,连上了服务器,但是找不到要访问的 URL 则调用 statusCode404 的方法。

Future

Future 的作用

  • 作为 ExecutorService.submit(Callable|Runnable) 的返回结果
  • 得到了 Future,说明其相关的任务已经提交给线程池去执行了
  • 获取任务的结果(阻塞): Future.get()
  • 取消任务: Future.cancel()
  • 查看任务是否完成: Future.isDone()

如果线程池执行的任务没有返回结果,直接用 Runnable 就好了,不需要用 Callable (代码里加上无意义的返回语句有点奇怪),Callable 更多是任务执行后有结果返回。

jQuery ui 拖拽

jQuery ui 提供了拖拽元素,拖拽排序等功能,例如要让一个元素能够被拖拽,只要给它执行 draggable() 函数即可,要实现 ul li 拖拽排序,只要在 ul 上执行函数 sortable() 就能实现了,具体更多的例子请参考 jQuery UI 实例 - 拖动

Spring 异步调用

Spring 中让一个方法在新线程中运行,只需要给方法加上 @Async 注解就可以(当然也可以自己直接创建一个线程实现异步执行)。

典型的使用如用户注册后需要发送一封邮件进行验证,邮件发送完成的时间取决于很多因素,例如网络快的时候发的快一些,慢的时候需要多一些的时间。如果使用同步的方式发送邮件,有可能需要等很久邮件才发送完成然后用户才能得到注册成功的响应,体验不是很好,如果使用异步的方式发送邮件,在发邮件的同时用户就被告知注册成功,请去收件箱中查看邮件进行注册验证(异步的方式还可以使用 MQ)。

Spring Security Login Util

查看当前登录用户的信息,调用 SecurityUtils.getLoginUser() 即可:

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 com.xtuer.utils;

import com.xtuer.bean.User;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

public final class SecurityUtils {
/**
* 获取登陆用户的信息
*
* @return 返回登陆的用户,如果没有登陆则返回 null
*/
public static User getLoginUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();

// URL 没有经过 Spring Security 登陆验证的 filter 时 auth 为 null
if (auth == null) {
return null;
}

Object p = auth.getPrincipal();

return p instanceof User ? (User) p : null;
}
}

getAuthentication() 返回的是一个 Authentication 对象,未登陆时它的 getPrinciple() 返回的是字符串 anonymousUser,登陆后返回的是 UserDetailsService.loadUserByUsername() 返回的对象,也即是说,我们可以实现一个 User 类,继承自 org.springframework.security.core.userdetails.User,在我们实现的 User 类中保存用户数据,在 loadUserByUsername() 中返回此用户对象。

User 类的实现请参考用户信息数据源一章

SpringMVC 处理 Ajax 映射

SpringMVC 返回 Json 数据给前端是件很简单的事,但是 SpringMVC 的 Controller 接收前端 Ajax传来的 Json 数据却不那么容易,前端和后端都要很小心,有一点不对就会出错,需要分为 2 种情况处理:

  • GET
  • PUT, POST, DELETE

How to change default Java version

1: First run /usr/libexec/java_home -V which will output something like the following:

Matching Java Virtual Machines (2):
    1.8.0_25, x86_64:   "Java SE 8" /Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home
    1.7.0_75, x86_64:   "Java SE 7" /Library/Java/JavaVirtualMachines/jdk1.7.0_75.jdk/Contents/Home

/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home

2: Pick the version you want to be the default (1.7.0_75 for arguments sake) then:

export JAVA_HOME=`/usr/libexec/java_home -v 1.7.0_75`

3: Now when you run java -version you will see:

java version "1.7.0_75"
Java(TM) SE Runtime Environment (build 1.7.0_75-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.75-b04, mixed mode)

4: Just add the above export JAVA_HOME … line to your shell’s init file(/.bash_profile).

Gradle 编码

Gradle 默认使用系统字符编码(Windows 为 GBK,Linux, Mac 为 UTF-8),很多程序员都是使用 Windows,但是 Java 文件以及其他资源文件大多数都会使用 UTF-8(因为要跨平台使用),在 Windows 开发时编译运行容易出现乱码,报错等。

Spring Security 加密密码

明文保存密码是不可取的,可以使用 SHABCrypt 等对密码进行加密。

BCrypt 算法与 MD5/SHA 算法有一个很大的区别,每次生成的 hash 值都是不同的,就可以免除存储 salt,暴力破解起来也更困难。BCrypt 加密后的字符长度比较长,有60位,所以用户表中密码字段的长度,如果打算采用 BCrypt 加密存储,字段长度不得低于 68(需要前缀 {bcrypt})。

下面的代码展示怎么使用 BCrypt 进行加密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.junit.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

public class EncryptPassword {
@Test
public void encrypt() {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

for (int i = 0; i < 5; ++i) {
// 每次生成的密码都不一样
String encryptedPassword = passwordEncoder.encode("Passw0rd");
System.out.println(encryptedPassword);
System.out.println(passwordEncoder.matches("Passw0rd", encryptedPassword)); // true
System.out.println(passwordEncoder.matches("Password", encryptedPassword)); // false
}
}
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$2a$10$l7vPVeqwb9GiVjURV5J2QO1CM5qxwk00/Ra5qEog0WgP7O5XV0Ble
true
false
$2a$10$jeyMfHF88mNJb9v.mQ7YiuZ8oTU.pHaiKdT1NLOM38eXj7heHZHg2
true
false
$2a$10$ux43/3JcHUC1hszyoJaH0eQhv7LkIVfL7p1cW80WxfxeTr2dUY6kO
true
false
$2a$10$KdUmhaJOJ30klEcKiYT25.fIRPrMs4xONHOQh4JvmpKSjJ8d9.QKG
true
false
$2a$10$gQKUOoFuevnCkoej3.AvAO9YzHKCKYmKuiSfEGHL22piY2FfNDQYu
true
false

随意取其中任意一个都可以,因为每次生成都是不一样的,所以取第一个就可以了。

Spring Security 用户信息数据源

前面章节中用户名、密码、权限都是写在配置文件里的,不能动态的管理用户的权限,大多数时候显然是不行的。这里介绍从其他数据源读取用户的信息,例如从数据库,LDAP 等。只需要给 authentication-provider 提供接口 UserDetailsService 的实现类即可,使用这个类获取用户的信息,涉及以下内容:

  • 修改 spring-security.xml 中的 authentication-provider
  • 类 UserDetailsService 实现了 Spring Security 的接口 UserDetailsService
  • 类 User
  • 类 UserService