手机短信验证

手机的普及程度在中国远高于邮箱,以手机作为密码找回也非常的简单和易用,尤其是手机应用更是可以直接读取用户的手机短信实现免输入就可以完成验证,所以现在的应用中越来越多的使用到了短信验证。

当用户注册成大概的流程如下:

  1. 输入手机号,点击发送验证码
  2. 用户接收到验证码后输入到验证框

用户要做的事情就是输入手机号、点击发送验证码按钮、输入验证码,就完成了手机绑定。

那么在短信验证中服务器做了什么呢?

  1. 向服务器发送Ajax请求,把用户输入的手机号发送回服务器(用户点击发送验证码后)
  2. 服务器验证手机号的合法性
  3. 生成随机码、同时将随机码以手机号为键保存到服务器中,并设置过期时间
  4. 发送短信给用户
  5. 用户收到短信后会输入到验证框
  6. 用户提交表单时验证随机码是否正确

向服务器发送Ajax请求

发送Ajax请求这步比较简单,直接直接用Jquery发送请求并取得结果,代码样例如下:

$.ajax('/auth/verify/13888888888').done(function (code) {
    alert(code);
});

服务器验证手机号的合法性

这一步通常使用正则表达式验证是否是有效的11位的手机号:

/1[3458]\d{9}/g

上面的规则是第一位必需是1,第二位必需在3、4、5、8中,后面带9位数字,这样就确认用户输入一定是11位的手机号了。这些验证码一般都是根据国情而定的,例如4G手机就开始启用170的号段,那这样的验证就不合适了,解决办法:放松验证条件或与时俱进保持更新

生成随机码、同时将随机码以手机号为键保存到服务器中,并设置过期时间

生成的随机码一般为了对用户友好可以直接生成四位的数字随机数,例如:1234等。我使用的是Apachecommons.lang包进行生成:

RandomStringUtils.random(4, false, true);

将随机码以手机号为键保存到服务器中,并设置过期时间这个看个人喜好,推荐直接存入缓存服务中,项目中的内存缓存除外,如ehcahe。不推荐把随机码直接存到项目内存中,如果期间进行服务器部署会导致验证码全部失效。

我使用的方法是使用Redis进行保存

String phone = "138123456789";
Jedis jedis = new Jedis("localhost");

String key = "verifies:" + phone;

String verifyCode = RandomStringUtils.random(4, false, true);

jedis.set(key, verifyCode);
jedis.expire(key, 60 * 5); // 五分钟内有效

##发送短信给用户

如何在程序中发送短信给用户呢?我们的程序并没有发送短信到用户手机中的能力,通过需要接入第三方的短信服务提供商,通过调用第三方接口来实现发送短信的功能:

首先要找到短信服务提供商,接入短信服务

开发人员通过短信服务提供商提供的接口调通调用服务

发送验证码的过程如下:

  1. 网站(手机)请求发送信息
  2. 服务器向短信服务提供商通信,提交发送请求
  3. 短信服务提供商通过运营商将信息发送到用户的手机中

##用户收到短信后会输入到验证框

以下就没有什么难度了,用户收到短信后会输入到验证框,这时可以等待用户提交表单后验证

需要注意的问题

  1. 手机正则过期,不能配置最新的手机号码。保证正则一直可用
  2. 生成的随机码不要直接在请求时返回到浏览器中,这样会导致非普通用户可以简单获取到验证码批量注册用户
  3. 发短信的频率一定要做限制,服务提供商通常都会每条信息收取几分钱

java敏捷数据库迁移框架——flyway

看看自己的项目的那些SQL文件或者干脆连个建表语句都没有的同学是否会有想法把他们管理起来呢?向大家推荐一款非常轻量级的敏捷数据库迁移框架——Flyway。想知道她有什么魅力吗?

Flyway为大家提供了如下的实现方式:

  • Java API
  • 命令行
  • Maven
  • Gradle
  • Ant
  • SBT

为了减少描述难度在这里使用了Java API,项目构建方式为Maven,数据库为MySQL

需要环境

创建项目

首先我们要在命令行中使用Maven原型插件执行如下命令

mvn archetype:generate -B \
            -DarchetypeGroupId=org.apache.maven.archetypes \
            -DarchetypeArtifactId=maven-archetype-quickstart \
            -DarchetypeVersion=1.1 \
            -DgroupId=foo \
            -DartifactId=bar \
            -Dversion=1.0-SNAPSHOT \
            -Dpackage=foobar

我们已经准备好开始了。当前项目的结构如下

.
|-- pom.xml
`-- src
    |-- main
    |   `-- java
    |       `-- foobar
    |           `-- App.java
    `-- test
        `-- java
            `-- foobar
                `-- AppTest.java

进入创建的项目

cd bar

增加Flyway依赖

编辑当前目录下的pom.xml,增加Flyway和MySQL的依赖

<project ...>
    ...
    <dependencies>
        <dependency>
            <groupId>com.googlecode.flyway</groupId>
            <artifactId>flyway-core</artifactId>
            <version>2.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        ...
    </dependencies>
    ...
</project>

整合Flyway

现在我们可以将Flyway的代码放入项目中,并配置数据库,例如增加到默认生成的:src/main/java/foobar/App.java

package foobar;

import com.googlecode.flyway.core.Flyway;

public class App {
    public static void main(String[] args) {
        // 创建Flyway实例
        Flyway flyway = new Flyway();

        // 设置数据库
        flyway.setDataSource("jdbc:mysql://localhost:3306/foobar", "user", "pass");

        // 开始迁移
        flyway.migrate();
    }
}

让我们创建第一个数据迁移吧

创建数据迁移目录src/main/resources/db/migration,执行命令

mkdir -p src/main/resources/db/migration

创建我们的第一个数据迁移src/main/resources/db/migration/V1__Create_person_table.sql

CRETE TABLE person (
    id INT,
    name VARCHAR(100)
);

执行程序

执行App.java(也可以直接在IDE中执行main方法)

mvn package exec:java -Dexec.mainClass=foobar.App -Dmaven.test.skip=true^1

如果你成功了,应该会得到如下信息

INFO: Creating Metadata table: `foobar`.`schema_version`
Feb 27, 2014 12:20:18 AM com.googlecode.flyway.core.command.DbMigrate migrate
INFO: Current version of schema `foobar`: << Empty Schema >>
Feb 27, 2014 12:20:18 AM com.googlecode.flyway.core.command.DbMigrate applyMigration
INFO: Migrating schema `foobar` to version 1
Feb 27, 2014 12:20:18 AM com.googlecode.flyway.core.command.DbMigrate logSummary
INFO: Successfully applied 1 migration to schema `foobar` (execution time 00:00.194s).

持续增加数据迁移吧

假如我们现在需要增加第二个数据迁移,命名为:src/main/resources/db/migration/V2__Add_people.sql

INSERT INTO person (id, name) VALUES (1, 'Axel');
INSERT INTO person (id, name) VALUES (2, 'Mr. Foo');
INSERT INTO person (id, name) VALUES (3, 'Ms. Bar');

执行命令

mvn package exec:java -Dexec.mainClass=foobar.App -Dmaven.test.skip=true

输出如下

Feb 27, 2014 12:25:00 AM com.googlecode.flyway.core.command.DbMigrate migrate
INFO: Current version of schema `foobar`: 1
Feb 27, 2014 12:25:00 AM com.googlecode.flyway.core.command.DbMigrate applyMigration
INFO: Migrating schema `foobar` to version 2
Feb 27, 2014 12:25:00 AM com.googlecode.flyway.core.command.DbMigrate logSummary
INFO: Successfully applied 1 migration to schema `foobar` (execution time 00:00.047s).

##总结
通过Flyway让我们能很方便的管理数据库文件,并进行版本控制。文档地址

Chrome Packaged App的一些坑

最近在写Http Craft——一个开源HTTP请求模拟的Chrome App过程中遇到了一个头疼的问题,作为一个CSS挫人,记录一下

无法对应用内的文本进行选择

无法对应用内的文本进行选择(inputtextarea可以选择)

解决方法:需要向css样式中添加如下样式

body {
    -webkit-touch-callout: all;
    -webkit-user-select: all;
    -khtml-user-select: all;
    -moz-user-select: all;
    -ms-user-select: all;
    user-select: all;
}

其中样式可选值:

  • auto——默认值,用户可以选中元素中的内容
  • none——用户不能选择元素中的任何内容
  • text——用户可以选择元素中的文本
  • element——文本可选,但仅限元素的边界内(只有IE和FF支持)
  • all——在编辑器内,如果双击/上下文点击发生在子元素上,改值的最高级祖先元素将被选中。
  • -moz-none——firefox私有,元素和子元素的文本将不可选,但是,子元素可以通过text重设回可选。

当内容过长时无法向下滚动

解决方法:

html {
    overflow: auto;
}