黑马旅游网(2):用户注册
项目课程链接:https://www.bilibili.com/video/BV1CE411E7h4
完整课程连接:https://www.bilibili.com/video/BV1uJ411k7wy
1 业务描述
注册业务旨在收集和管理用户的个人信息,是未来提供个性化服务的基础。相应的前端页面如下方静态H5页面图所示:
网页中提供多个输入框,并给出对应的提示信息,逐步引导用户根据提示向输入框中填写信息。最终点击下方的 注册
按钮完成用户注册。
进一步地,为了验证联系方式的有效性,在完成注册功能后,前端还会提示用户执行 激活
操作:通过向用户注册时填写的邮箱发送验证邮件。在邮件正文中引导用户点击相应的超链接完成账户激活,并在激活页面提供登录链接,引导用户登录网站。整个注册业务流程完毕。
2 业务分析
2.1 业务流程抽象
在本案例中,注册业务分为两阶段:
- 注册阶段:用户根据前端页面的提示信息填写相应内容,进而提交至后台处理,最终将信息保存至数据库。
- 激活阶段:系统后台自动向用户填写的邮件地址发送一封
激活邮件
,邮件中包含一个激活链接。用户通过点击激活链接向服务器发送一个请求,服务器在接收到该请求后,再向用户浏览器回写激活成功信息。
2.2 可能的技术难点与解决策略
-
避免机器人批量注册
添加验证码,优先核对,不满足直接跳出方法。(后续单独写一篇博客分析此功能) -
如何保证激活用户的唯一性?
在tab_user
表中添加字段:active_code
。它是一种保证唯一性的序列码,本案例中调用 Java 中的工具类UUID
生成序列并作为用户的激活码。在用户注册时生成并与用户信息一并写入数据库,同时向用户提交的邮件地址发送一封带有激活码链接的邮件。只有用户在邮件中点击了这个链接,使用带有激活码的URL向服务器发出请求,服务器才能收到用户“发来的”激活码,并在后端完成校验并向用户端浏览器回写响应信息。 -
如何定义和处理激活状态?
在tab_user
表中添加两个字段:status
。status
用于标注用户是否激活,它只有两种可能的取值:Y
(已激活)和N
(未激活)。在用户刚刚提交注册表单时,后端在数据库中添加用户信息时对该字段设为N
,当用户在激活邮件中点击激活链接向服务器发送激活请求时,服务器在核对通过的前提下将该字段修改为Y
。 -
前端校验用户填写的表单信息:正则表达式校验,这里参考了暮光乐鱼的博客,他的正则表达式写的要比我的好。
- 非空 :.+
- 字符集 :^\w{6,20}$
- 手机号 :1(3|4|5|7|8)\d{9}
- 邮箱 :^\w+(.\w+)*@\w+(.\w+)+$
3 代码实现
3.1 信息验证与提交
3.1.1 前端
-
表单数据验证与提交主函数
/*如果此方法: 无返回或返回true,则表单提交 返回false,则表单不提交 */ $(function () { // 当表单提交时调用所有的校验方法 $("#registerForm").submit(function () { // 1.发送数据到服务器 if ( checkUsername() && // 用户名校验 checkPassword() && // 密码校验 checkEmail() && // 邮箱校验 checkName() && // 姓名校验 checkTelephone() && // 手机号校验 checkSex() && // 性别校验 checkBirthday() && // 生日校验 checkCode() // 验证码校验 ) { // 校验成功 // 发送AJAX请求,提交表单数据 username=Alex&password=123 ... $.post("user/register", $(this).serialize(), function (data) { // 处理响应数据 data {flag:true/false, errorMsg:"..."} if (data["flag"]) { // 注册成功 // 跳转成功页面 location.href = "register_ok.html"; } else { // 注册失败 // 在注册页面添加提示信息 $("#errorMsg").html(data["errorMsg"]); } }); } else { // 校验失败 } // 2.跳转页面 return false; }); // 当某一个组件失去焦点时,调用对应的校验方法 $("#username").blur(checkUsername); $("#password").blur(checkPassword); $("#email").blur(checkEmail); $("#name").blur(checkName); $("#telephone").blur(checkTelephone); $("#sex").blur(checkSex); $("#birthday").blur(checkBirthday); $("#check").blur(checkCode); })
-
用户名校验
// 校验用户名 function checkUsername() { // 1.获取用户名 let username = $("#username").val(); // 2.定义正则 let reg_username = /^\w{8,20}$/; // 正则:单词字符,以单词开头,以单词结尾,长度8~20位,包含8和20 // 3.判断并给出提示信息 let flag = reg_username.test(username); if (flag) { // 校验合法 $("#username").css("border", ""); } else { // 校验非法 // 输入栏边框变红 $("#username").css("border", "2px solid red"); } return flag; }
-
密码校验
// 校验密码 function checkPassword() { // 1.获取密码 let password = $("#password").val(); // 2.定义正则 let reg_username = /^\w{8,20}$/; // 正则:单词字符,以单词开头,以单词结尾,长度8~20位,包含8和20 // 3.判断并给出提示信息 let flag = reg_username.test(password); if (flag) { // 校验合法 $("#password").css("border", ""); } else { // 校验非法 // 输入栏边框变红 $("#password").css("border", "2px solid red"); } return flag; }
校验工作重复性较高,其它校验就不再博客里写了。可以参考我的项目仓库,或者暮光乐鱼的博客。
3.1.2 Servlet
/**
* 用户注册方法
*/
public void register(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 0.验证码校验
HttpSession session = request.getSession();
// 获取客户端浏览器提交的验证码
String checkCode_browser = request.getParameter("check");
session.removeAttribute("check"); // 客户端验证码获取即从Session中移除,保证验证码的一次性
// 获取服务器程序 CheckCodeServlet 生成的验证码
String checkCode_server = (String) session.getAttribute("CheckCode_Server");
// 验证码比对
if (!checkCode_server.equalsIgnoreCase(checkCode_browser)) {
/*比对失败,向浏览器回写信息并退出方法*/
return;
}
// 1.获取用户提交数据
Map<String, String[]> parameterMap = request.getParameterMap();
// 2.封装对象
User user = new User();
/*利用BeanUtils.populate将表单封装至user对象中*/
// 3.调用service完成注册
boolean flag = service.register(user);
ResultInfo resultInfo = new ResultInfo();
// 4.响应结果
ResultInfo resultInfo = new ResultInfo();
/*向resultInfo填充信息,序列化为json,回写给客户端浏览器*/
}
3.1.3 Service
/**
* 注册用户
* @param user User Bean 对象
* @return boolean
* true:注册成功
* false:注册失败
*/
@Override
public boolean register(User user) {
// 1.根据用户名查询用户对象(存在false,不存在true)
User loginUser = userDao.findByUsername(user.getUsername());
// 判断loginUser是否为null
if (loginUser != null) { // 用户名存在,注册失败
return false;
} // 用户名不存在,注册成功
// 2.保存用户信息
// 2.1 设置唯一的激活码
user.setCode(UuidUtil.getUuid());
// 2.2 设置激活状态
user.setStatus("N"); // 初始状态:未激活
// 2.3 向数据库中写入用户信息
userDao.save(user);
// 3.发送激活邮件
// 3.1 定义邮件内容
String content = "<a href='http://localhost:80/travel/user/active?code=" +
user.getCode() + "'>点击激活【黑马旅游网】</a>";
// 3.2 发送邮件
MailUtils.sendMail(user.getEmail(), content, "激活邮件");
return true;
}
3.1.4 Dao
/**
* 根据用户名查询用户信息
* @param username String 用户名
* @return
* 查询成功:User Bean 对象
* 查询失败:null
*/
@Override
public User findByUsername(String username) {
User user = null;
try {
// 1.定义sql
String sql = "SELECT * FROM tab_user WHERE username = ?";
// 2.执行sql
user = template.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), username);
} catch (Exception e) {
e.printStackTrace();
}
return user;
}
/**
* 用户信息保存
* @param user User Bean 对象
*/
@Override
public void save(User user) {
// 1.定义sql
String sql = "INSERT INTO tab_user(" +
"username, password, name, birthday, sex, telephone, email, status, code) " +
"VALUES(?,?,?,?,?,?,?,?,?)";
// 2.执行sql
template.update(sql, user.getUsername(), user.getPassword(),
/*一系列getter方法获取fields*/
);
}
3.2 邮件激活
3.2.1 Servlet
/**
* 用户激活方法
*/
public void active(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 1.获取激活码
String code = request.getParameter("code");
if (code != null) { // 获取激活码
// 2.调用service完成激活操作
// UserService service = new UserServiceImpl();
boolean flag = service.active(code);
// 3.判断标记
String msg;
if (flag) { // 激活成功
msg = "激活成功,请<a href='../login.html'>登录</a>";
} else { // 激活失败
msg = "激活失败,请联系管理员";
}
// 4.回写响应内容
response.setContentType("text/html;charset=utf-8");
response.getWriter().write(msg);
}
}
3.2.2 Service
/**
* 激活用户
* @param code String 激活码
* @return
* true:激活成功
* false:激活失败
*/
@Override
public boolean active(String code) {
// 1.根据激活码查询用户对象
User user = userDao.findByCode(code);
if (user != null) {
// 2.调用userDao中的修改激活状态方法
userDao.updateStatus(user);
return true;
} else {
return false;
}
}
3.2.3 Dao
/**
* 根据激活码查询用户信息
* @param code String 激活码
* @return
* 查询成功:User Bean 对象
* 查询失败:null
*/
@Override
public User findByCode(String code) {
User user = null;
try {
String sql = "SELECT * FROM tab_user WHERE code = ?";
user = template.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), code);
} catch (DataAccessException e) {
e.printStackTrace();
}
return user;
}
/**
* 更新激活状态
* @param user User Bean 对象
*/
@Override
public void updateStatus(User user) {
String sql = "UPDATE tab_user SET status = 'Y' WHERE uid = ?";
template.update(sql, user.getUid());
}
部分代码并未完全展示,完整代码可以参考我的 GitHub 仓库