2017.9.3 更新v2.1.0: 进度监控调整为整个导入过程的进度监控,并且更加精确
前些时日开发了一款用Java将Excel导入数据库的工具:EXCEL-UTIL4J(源码和文档在这里),觉得还是写一个使用示例的好,主要想用来记录使用方法。
因为时间有限,工具只实现了一对一关系数据导入,以及只能使用hibernate作为ORM框架进行导入,所以本示例将只做一对一关系数据导入,并且最大可能的利用到工具的所有功能,使用hibernate进行导入。
示例中只会简单的提示代码作用,建议先大概浏览一下工具文档,这样有利于理解示例的代码,本示例代码源码见 excel-util4j-sample
需求
1. Excel表如下:
2. 数据表结构如下:
3. 分析
字段对应
Excel中的账号
、密码
在数据表excel_util4j_user
(下简称user
)中,分别对应username
、password
;
Excel中的姓名
、性别
、生日
、电话
、地址
在excel_util4j_user_info
(下简称info
)中,分别对应name
、gender
、birthday
、phone
、address
;-
字段约束(下面有建表语句,展示更清晰)
- 唯一性
user
表中username
以及info
表中phone
字段为唯一的 - 非空
username
、password
、name
- 其他
username
最大20字、password
6~20字、name
最大20字、phone
最大20,并且需要验证是否为电话号码、address
最大50字
- 唯一性
常量值字段
user
表中除了excel中的字段外,还有enable
字段,该字段表示是否启用账号,值为0
或1
,分别表示禁用
或启用
,本例中所有账户设置为1
其他字段
每个用户还有角色,对应的表为
excel_util4j_user_role
(下简称user_role
)表,由于excel-util4j只能导入一对一关系的数据,所以这里仅仅设置角色为一个,假设role_id
为3
创建时间字段
create_time
,该字段不由excel导入,也不是常量字段,而是系统当前时间
-
密码处理
密码解析后,需要验证是否为6~20个字符,通过验证后需要转换为MD5+Base64加密的格式再存表
下面是建表语句,表结构和约束更清晰的展现:
-
user
表:
CREATE TABLE excel_util4j_user (
id bigint(20) NOT NULL AUTO_INCREMENT,
username varchar(20) NOT NULL,
password varchar(20) NOT NULL,
create_time datetime DEFAULT NULL,
enable int(1) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY username_unique (username) USING BTREE
);
-
info
表:
CREATE TABLE excel_util4j_user_info (
id bigint(20) NOT NULL AUTO_INCREMENT,
user_id bigint(20) NOT NULL,
name varchar(20) NOT NULL,
gender int(1) DEFAULT NULL,
birthday date DEFAULT NULL,
phone varchar(20) DEFAULT NULL,
address varchar(50) DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY phone_unique (phone) USING BTREE
);
-
user_role
表:
CREATE TABLE excel_util4j_user_role (
id bigint(20) NOT NULL AUTO_INCREMENT,
user_id bigint(20) NOT NULL,
role_id int(11) NOT NULL,
PRIMARY KEY (id)
);
编码
源码下载下来,然后
mvn install -Dmaven.test.skip=true
安装相应jar包,共4个-
配置POM文件,将包添加到依赖:
<!-- 读取excel的包 --> <dependency> <groupId>online.dinghuiye</groupId> <artifactId>poi-kit</artifactId> <version>1.0.1</version> </dependency> <!-- excel-util4j-api包 --> <dependency> <groupId>online.dinghuiye</groupId> <artifactId>excelutil-api</artifactId> <version>2.1.0</version> </dependency> <!-- excel-util4核心包 --> <dependency> <groupId>online.dinghuiye</groupId> <artifactId>excelutil</artifactId> <version>2.1.0</version> </dependency> <!-- ORM实现包 --> <dependency> <groupId>online.dinghuiye</groupId> <artifactId>persistence-hibernate-impl</artifactId> <version>2.1.0</version> </dependency> <!-- 数据库相关的包,使用Mysql数据库 --> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.42</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.31</version> </dependency> <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>4.3.11.Final</version> </dependency> <!-- 转换,验证等包 --> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.6</version> </dependency> <!-- https://mvnrepository.com/artifact/javax.validation/validation-api --> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.1.0.Final</version> </dependency> <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.4.1.Final</version> </dependency> <dependency> <groupId>org.glassfish</groupId> <artifactId>javax.el</artifactId> <version>3.0.1-b08</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.33</version> </dependency>
-
创建实体类,用
Intellij Idea
自动生成,下面列出3个pojo代码,关键部分都有注释。属性注解参看文档。值得注意的是:
UniqueValidator.class
(v2.0.0版本的示例)或UsernameUniqueValidator.class
和PhoneUniqueValidator.class
(v2.1.0版本示例)是自定义的判重验证器,并不是使用的hibernate validator
的自定义验证,是excel-util4j
中实现的验证器,使用前需要编写类并实现online.dinghuiye.api.validation.Validator
,代码见下文UniqueValidator验证器
,另外两个验证器相似password
字段从excel导入后,需要先验证长度在6 ~ 20个字符,然后通过PasswordRepairer
进行修正,即将POJO对象的password
属性值设置为MD5+Base64加密的字符串。加密后的字符串长度不一定再满足6 ~ 20个字符了,hibernate存表时还会再次按照POJO注解进行验证,此时就可能无法验证通过而报错,所以需要将因为属性修正而可能影响到的验证的注解加上groups = {Validator.class}
参数,Validator.class
是online.dinghuiye.api.validation.Validator.class
createTime
字段是系统时间字段,使用自定义转换器设值,使用谦虚编写类并实现online.dinghuiye.api.resolution.Convertor
接口,重写方法返回需要的特定值即可,代码见下文CurrentTimeConvertor转换器
(v2.1.0版本示例)
-
user
pojo
package online.dinghuiye.example.entity;
import online.dinghuiye.api.annotation.validate.Validate;
import online.dinghuiye.api.validation.Validator;
import online.dinghuiye.core.annotation.convert.ConstValue;
import online.dinghuiye.core.annotation.convert.ValueConvert;
import online.dinghuiye.core.annotation.excel.SheetTitleName;
import online.dinghuiye.example.convertor.CurrentTimeConvertor;
import online.dinghuiye.example.validator.UsernameUniqueValidator;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.validator.constraints.NotBlank;
import javax.persistence.*;
import javax.validation.Valid;
import javax.validation.constraints.Size;
import java.util.Date;
/**
* @author Strangeen on 2017/08/27
*
* @author Strangeen on 2017/9/3
* @version 2.1.0
*/
@Entity
@DynamicInsert(true)
@Table(name = "excel_util4j_user")
public class ExcelUtil4JUserEntity {
@Transient // 不需要执行转换和验证,但并不影响hibernate存表的操作
private Long id;
@SheetTitleName("账号") // excel表字段对应
@NotBlank
@Size(max = 20, message = "输入最大{max}个字")
@Validate(validator = UsernameUniqueValidator.class, message = "已被注册") // 自定义检验器,判断重复
private String username;
@SheetTitleName("密码")
// 如果后续需要repaire的属性,需要将repaire可能影响的验证加上groups={Validator.class}
// 否则可能会导致比如字符串长度改变而无法再存表时通过hibernate的验证
@NotBlank
@Size(max = 20, min = 6, message = "输入{min}~{max}个字", groups = {Validator.class})
private String password;
@ValueConvert(CurrentTimeConvertor.class) // 自定义转换器,存入当前时间
private Date createTime;
@ConstValue("1") // 常量值转换器,导入时会被设置为1
private Integer enable;
@Valid // 执行hibernate validator支持的对象属性检测,不注释@Valid则不会对info对象的属性进行检测
private ExcelUtil4JUserInfoEntity info;
private ExcelUtil4JUserRoleEntity userRole;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Basic
@Column(name = "username")
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Basic
@Column(name = "password")
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Basic
@Column(name = "create_time")
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Basic
@Column(name = "enable")
public Integer getEnable() {
return enable;
}
public void setEnable(Integer enable) {
this.enable = enable;
}
@OneToOne(mappedBy = "user", cascade = {CascadeType.ALL})
public ExcelUtil4JUserInfoEntity getInfo() {
return info;
}
public void setInfo(ExcelUtil4JUserInfoEntity info) {
this.info = info;
}
// 这里定义为OneToOne并不太合适,只是为了演示
// 常规应该使用OneToMany,现阶段无法实现OneToMany的导入,就只能使用RowRecordPerPersistentRepairer在存表前进行修正了
@OneToOne(mappedBy = "user", cascade = {CascadeType.ALL})
public ExcelUtil4JUserRoleEntity getUserRole() {
return userRole;
}
public void setUserRole(ExcelUtil4JUserRoleEntity userRole) {
this.userRole = userRole;
}
}
-
info
pojo
package online.dinghuiye.example.entity;
import online.dinghuiye.api.annotation.validate.Validate;
import online.dinghuiye.core.annotation.convert.BlankToNull;
import online.dinghuiye.core.annotation.convert.DateFormat;
import online.dinghuiye.core.annotation.convert.ValueMap;
import online.dinghuiye.core.annotation.excel.SheetTitleName;
import online.dinghuiye.example.validator.PhoneUniqueValidator;
import org.hibernate.validator.constraints.NotBlank;
import javax.persistence.*;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.util.Date;
/**
* @author Strangeen on 2017/08/27
*
* @author Strangeen on 2017/9/3
* @version 2.1.0
*/
@Entity
@Table(name = "excel_util4j_user_info")
public class ExcelUtil4JUserInfoEntity {
@Transient
private Long id;
@OneToOne // 必须使用hibernate的双向绑定,否则无法生成hibernate的POJO对象
@JoinColumn(name = "user_id")
private ExcelUtil4JUserEntity user;
@SheetTitleName("姓名")
@NotBlank
@Size(max = 20, message = "输入最大{max}个字")
private String name;
@SheetTitleName("性别")
@ValueMap("{'男':1,'女':0}") // Map值转换器,将excel的只按照Map映射进行转换
private Integer gender;
@SheetTitleName("生日")
@BlankToNull // 空串转NULL转换器,防止生日字段为空串转换为Date时报错
@DateFormat("yyyy-MM-dd") // 时间格式转换器,将时间转换为指定格式,如果单元格为“文本”就会使用
private Date birthday;
@SheetTitleName("电话")
// hibernate validator的正则验证,这里大概写一个电话的验证正则
@Pattern(regexp = "(^(\\+|0)[0-9]{2}[0-9]{11}$)|(^[0-9]{11}$)", message = "填写不正确")
@Validate(validator = PhoneUniqueValidator.class, message = "已被注册")
private String phone;
@SheetTitleName("地址")
@Size(max = 50, message = "输入最大{max}个字")
private String address;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Basic
@Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Basic
@Column(name = "gender")
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
@Basic
@Column(name = "birthday")
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Basic
@Column(name = "phone")
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Basic
@Column(name = "address")
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@OneToOne
@JoinColumn(name = "user_id")
public ExcelUtil4JUserEntity getUser() {
return user;
}
public void setUser(ExcelUtil4JUserEntity user) {
this.user = user;
}
}
-
user_role
pojo
package online.dinghuiye.example.entity;
import online.dinghuiye.core.annotation.convert.ConstValue;
import online.dinghuiye.core.annotation.excel.Transient;
import javax.persistence.*;
/**
* @author Strangeen on 2017/08/27
*/
@Entity
@Table(name = "excel_util4j_user_role")
public class ExcelUtil4JUserRoleEntity {
@Transient
private Long id;
private ExcelUtil4JUserEntity user;
@ConstValue("3") // 导入的用户角色均为3
private Integer roleId;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@OneToOne
@JoinColumn(name = "user_id")
public ExcelUtil4JUserEntity getUser() {
return user;
}
public void setUser(ExcelUtil4JUserEntity user) {
this.user = user;
}
@Basic
@Column(name = "role_id")
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
}
- 编写
CurrentTimeConvertor转换器
代码:
自定义转换器需要实现online.dinghuiye.api.resolution.Convertor
package online.dinghuiye.example.convertor;
import online.dinghuiye.api.resolution.Convertor;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.Map;
/**
* 当前时间转换器,该转换器为自定义转换器,用于适应字段为当前时间的情况
* 自定义转换器需要实现online.dinghuiye.api.resolution.Convertor
*
* @author Strangeen on 2017/09/04
* @version 2.1.0
*/
public class CurrentTimeConvertor implements Convertor {
// convet方法参数会传入所有可能用到的值
// obj 需要转换的值
// field pojo属性字段
// excelRecordMap excel数据map<表头名称, 单元格值>
@Override
public Object convert(Object obj, Field field, Map<String, Object> excelRecordMap) {
// 返回当前时间即可,自定义转换器也可以用于其他特定值得转换
return new Date();
}
}
- 编写
UniqueValidator验证器
代码(UsernameUniqueValidator
和PhoneUniqueValidator
验证器代码略,请查看示例代码v2.1.0版本):
自定义判重验证器需要实现online.dinghuiye.api.validation.Validator
package online.dinghuiye.example.validator;
import online.dinghuiye.api.validation.Validator;
import online.dinghuiye.example.util.SessionFactoryUtil;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 判重Validator
*
* @author Strangeen on 2017/08/16
*/
public class UniqueValidator implements Validator {
/*
实现原理是将数据库中唯一值全部读取出来缓存到cache中,
然后将导入的只和cache比对,如果重复则返回false,如果不重复则加入到cache,返回true
这样做可以提高检验效率,但是必须要考虑并发问题
*/
private static Set<Object> usernameCache = new HashSet<>();
private static Set<Object> phoneCache = new HashSet<>();
public UniqueValidator() {
// 设置username的cache
setCache(usernameCache, "excel_util4j_user", "username");
// 设置phone的cache
setCache(phoneCache, "excel_util4j_user_info", "phone");
}
public static void setCache(Set<Object> cache, String tableName, String columnName) {
SessionFactory factory = SessionFactoryUtil.getSessionFactory();
Session session = factory.openSession();
SQLQuery query = session.createSQLQuery("select " + columnName + " from " + tableName);
List<Object> list = query.list();
for (Object obj : list) {
cache.add(obj);
}
session.close();
}
@Override
public <User> boolean validate(Object fieldValue, Field field, User obj) {
// 判断是username还是phone,这里只是演示,所以将cache写在一起,常规思路应该是分开的2个类
if ("username".equals(field.getName())) {
if (usernameCache.contains(fieldValue)) return false;
usernameCache.add(fieldValue);
return true;
} else if ("phone".equals(field.getName())) {
if (phoneCache.contains(fieldValue)) return false;
phoneCache.add(fieldValue);
return true;
}
// 其他字段不用检测,直接返回true
return true;
}
}
- 编写
PasswordRepairer修正器
代码:
在存表前将密码设置为密码明文的加密字符串,MD5Util
的代码略
package online.dinghuiye.example.repairer;
import online.dinghuiye.api.entity.Process;
import online.dinghuiye.api.entity.ResultStatus;
import online.dinghuiye.api.entity.RowRecord;
import online.dinghuiye.api.persistence.RowRecordPerPersistentRepairer;
import online.dinghuiye.example.entity.ExcelUtil4JUserEntity;
import online.dinghuiye.example.util.MD5Util;
import java.util.List;
/**
* 对密码进行MD5加密处理
* 由于密码需要验证长度,所以不能在验证前就MD5加密,否则验证是不正确的
* 所以需要在存表前进行修正
*
* 通过实现RowRecordPerPersistentRepairer可以获得hibernate的POJO对象,从而进行修正
*
* v2.1.0 进度监控更佳精确,接口提供了进度对象
* 如果遍历了List<RowRecord> list,可以对每一次循环执行process.updateProcess(1)
需要注意的是,使用前必须判断`process`是否为`null`,如果入口方法出传入的`ProcessObserver`为`null`,那么`process`就会为`null`
* 如果没有遍历或者不执行上述方法,当repairer执行完毕,程序会自动修正进度,
* 进度展示效果会立即变更到repairer方法执行完毕的进度状态
*
* @author Strangeen on 2017/9/3
* @version 2.1.0
*/
public class PasswordRepairer implements RowRecordPerPersistentRepairer {
@Override
public void repaire(List<RowRecord> list, Process process) {
for (RowRecord rr : list) {
if (rr.getResult().getResult() != ResultStatus.SUCCESS) continue;
ExcelUtil4JUserEntity obj =
(ExcelUtil4JUserEntity) rr.getPojoRecordMap().get(ExcelUtil4JUserEntity.class);
obj.setPassword(MD5Util.encode(obj.getPassword()));
// 精确的进度展示,可以操作process对象
if (process != null)
process.updateProcess(1);
}
}
}
- 编写入口代码:
配置SessionFactory
的代码略,可以使用多种方式配置,如Spring等
package online.dinghuiye.example;
import online.dinghuiye.api.entity.Process;
import online.dinghuiye.api.entity.ResultStatus;
import online.dinghuiye.api.entity.RowRecord;
import online.dinghuiye.api.entity.TransactionMode;
import online.dinghuiye.core.ImportHandler;
import online.dinghuiye.core.persistence.RowRecordPersistencorHibernateImpl;
import online.dinghuiye.core.resolution.torowrecord.RowRecordHandlerImpl;
import online.dinghuiye.core.validation.RowRecordValidatorImpl;
import online.dinghuiye.example.entity.ExcelUtil4JUserEntity;
import online.dinghuiye.example.repairer.PasswordRepairer;
import online.dinghuiye.example.util.SessionFactoryUtil;
import online.dinghuiye.excel.ExcelFactory;
import org.hibernate.SessionFactory;
import java.io.File;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
/**
* @author Strangeen on 2017/08/30
*
* @author Strangeen on 2017/9/3
* @version 2.1.0
*/
public class ExcelImportor {
public static void main(String[] args) {
SessionFactory factory = null;
try {
// 获取SessionFactory
factory = SessionFactoryUtil.getSessionFactory();
// 设置mode:SINGLETON为单条存储事务,MULTIPLE为整体事务,详见文档
TransactionMode mode = TransactionMode.SINGLETON;
// 创建导入器handler
ImportHandler handler = new ImportHandler();
handler.setHandler(new RowRecordHandlerImpl()); // 一对一关系解析器
handler.setValidator(new RowRecordValidatorImpl()); // 验证器
handler.setPersistencor(new RowRecordPersistencorHibernateImpl(factory)); // 持久化器hibernate实现
handler.setRepairer(new PasswordRepairer()); // 密码存储修正器
handler.setMode(mode);
// 执行excel导入
List<RowRecord> resultList = handler.importExcel(
ExcelFactory.newExcel(new File("D:/test_template.xlsx")), // 创建AbstractExcel对象读取excle
0, // 读取sheet序号为0的sheet
new Observer() {
@Override
public void update(Observable o, Object arg) {
// 创建导入进度观察者,arg为导入进度百分数(没有%)
Process process = (Process) arg;
System.out.println("进度:" + process.getProcess() + ",当前阶段:" + process.getNode());
}
},
ExcelUtil4JUserEntity.class); // 传入POJO
// 打印结果,如果有错误可以在resultList中得到
int successCount = 0;
int errorCount = 0;
for (RowRecord rr : resultList) {
if (rr.getResult().getResult() != ResultStatus.SUCCESS) { // 导入不成功
System.out.println(rr.getRowNo() + "行 - " + rr.getResult().getMsg()); // 打印行号和错误信息
errorCount ++; // 记录错误数
} else
successCount ++; // 记录成功数
}
// 注意:MULTIPLE为整体事务,successCount依然可能不为0,仅作为标识,实际上没有任何数据存入数据库的
System.out.println("success " + successCount + ", error " + errorCount);
} catch (Exception e) {
e.printStackTrace();
} finally {
SessionFactoryUtil.closeSessionFactory(factory);
}
}
}
至此,代码全部编写完毕,执行导入后,控制台打印出来的内容类似如下(我导入了10条数据,有6条存在问题,使用SINGLETON
事务形式):
进度:2.5,当前阶段:RESOLUTION
进度:5.0,当前阶段:RESOLUTION
进度:7.5,当前阶段:RESOLUTION
进度:10.0,当前阶段:RESOLUTION
进度:12.5,当前阶段:RESOLUTION
进度:15.0,当前阶段:RESOLUTION
进度:17.5,当前阶段:RESOLUTION
进度:20.0,当前阶段:RESOLUTION
进度:22.5,当前阶段:RESOLUTION
进度:25.0,当前阶段:RESOLUTION
进度:27.500000000000004,当前阶段:VALIDATION
进度:30.0,当前阶段:VALIDATION
进度:32.5,当前阶段:VALIDATION
进度:35.0,当前阶段:VALIDATION
进度:37.5,当前阶段:VALIDATION
进度:40.0,当前阶段:VALIDATION
进度:42.5,当前阶段:VALIDATION
进度:45.0,当前阶段:VALIDATION
进度:47.5,当前阶段:VALIDATION
进度:50.0,当前阶段:VALIDATION
进度:52.5,当前阶段:REPAIRATION
进度:55.00000000000001,当前阶段:REPAIRATION
进度:57.49999999999999,当前阶段:REPAIRATION
进度:60.0,当前阶段:REPAIRATION
进度:77.5,当前阶段:PERSISTENCE
进度:80.0,当前阶段:PERSISTENCE
进度:82.5,当前阶段:PERSISTENCE
进度:85.0,当前阶段:PERSISTENCE
进度:87.5,当前阶段:PERSISTENCE
进度:90.0,当前阶段:PERSISTENCE
进度:92.5,当前阶段:PERSISTENCE
进度:95.0,当前阶段:PERSISTENCE
进度:97.5,当前阶段:PERSISTENCE
进度:100.0,当前阶段:PERSISTENCE
4行 - 账号不能为空;密码输入6~20个字;姓名不能为空;电话填写不正确;地址输入最大50个字;
5行 - 电话填写不正确;
7行 - 电话已被注册;账号已被注册;
8行 - 姓名不能为空;账号不能为空;
10行 - 密码不能为空;
11行 - 姓名不能为空;
success 4, error 6