您的位置 首页 java

Java Web轻松学61 – 实现用户注册功能

目录

  1. 介绍
  2. 四大架构(技术、业务、应用、数据)
  3. 思路
  4. 展示层 – 注册页面 register.html
  5. User实体类
  6. DAO层 – UserMapper接口和元数据
  7. 服务层 – UserService
  8. 控制器层 – 添加Handler
  9. 总结

介绍

到现在为止,我们的租房网应用只是实现了简单的用户登录(实际上仅有前端页面,后台还没有实现登录验证的业务逻辑)、查看自己感兴趣的房源(也是仅有一个接口)、查看某个房源的详情、编辑某个房源的信息等功能。

本篇文章将为我们的租房网应用实现一个简单的用户注册功能。

四大架构(技术、业务、应用、数据)

前面的工作只是一直不断的在使用新技术改造我们的项目,从最开始的Servlet,经过使用JSP和JSTL、Spring MVC和Spring IoC、关系数据库H2Database和JDBC、 Maven 、Spring JDBC,到目前采用ORM框架MyBatis、连接池框架Druid等技术,我们的项目在开发效率、可维护性、性能等方面不敢说达到完美,但也算很不错了。

所以,既然我们的 技术架构 搭建的也还够用了,现在暂时让我们的重心转移到 业务架构 上来。当然,还有 应用架构 数据架构

应用架构 我们就暂时采取独立的 单块应用架构 吧,就是说把所有业务功能都放在一个应用中。目前很流行 微服务架构 ,但那适合于业务功能很复杂,需要拆分为成百上千个应用,数据库也动辄上百个库,上万张表,我们的租房网应用现在还早着呢,全部功能放在一起又快又简单。

数据架构 上目前也仅仅使用了房源数据,而房源数据里面也只是模拟了若干个字符串类型的数据,并没有涉及到文档、图片、视频、音频等其他类型的数据,我们就先用关系数据库吧。

业务架构 我们也不作深入分析和设计,我们就怎么简单怎么来吧,现在缺用户注册功能,那我们就实现一个用户注册功能,还是赶紧把租房网应用改造成至少像模像样要紧。

思路

首先从租房网平台的最终用户的角度来看,我们应该有一个注册页面。

这个注册页面跟登录页面类似,不涉及任何其他业务的数据,因此可以采用 静态页面 的方式来实现。

有了这个注册页面,我们就可以直接打开这个页面进行注册。当然,也可以从其他页面比如登录页面链接到注册页面。

用户填写注册信息完毕之后,需要将它们提交到我们的租房网应用的后台。首先到达的是我们的控制器。因此,我们需要为控制器设计一个Handler,采用POST方法映射到 register.action 。

注册信息至少应该包含用户名和密码,当然实际应用中还应该包含更多用户信息,所以,我们应该设计一个用户实体类,类名就叫 User 吧。

根据分层的思维,控制器的Handler需要调用一个服务来实现用户注册的业务逻辑,我们就设计一个 UserService 吧,它专门用来处理用户相关的业务,比如注册、登录等等。

现在我们已经使用了MyBatis作为DAO层,所以还需要设计一个 UserMapper 。

最后,我们需要把用户数据保存在数据库中,所以要建立一个 user 表。

展示层 – 注册页面 register.html

注册页面的内容很简单,主要是使用表单元素<form>(HTML的基础知识大家可以参考 ):

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>租房网 - 注册</title>
</head>
<body>
<form action="register.action" method="post">
<h2>用户注册</h2>
<label for="user_name">请输入用户名</label><input type="text" id="user_name" name="userName" />
<label for="password">请输入密码</label><input type="password" id="password" name="password" />
<label for="password_confirmed">请再次输入密码</label><input type="password" id="password_confirmed" name="passwordConfirmed" />
<input type="submit" value="注册•" />
</form>
<p><a href="login.html">已经注册,直接登录!</a></p>
</body>
</html> 

这里重点关注的就是:

  • 表单元素的action属性和method属性;
  • <input>元素的name属性;

它们的值都需要与后台代码一致。

为了方便用户使用注册功能,一般的Web应用都会在登录页面设计一个链接跳转到注册页面,于是我们的登录页面 login.html 变为:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>租房网 - 登录•</title>
</head>
<body>
<form action="login.action" method="post">
<label for="user_name">用户名</label><input type="text" id="user_name" name="userName" />
<label for="password">密码</label><input type="password" id="password" name="password" />
<input type="submit" value="登录•" />
</form>
<p><a href="register.html">还没有注册?</a></p>
</body>
</html> 

主要是在表单元素<form>之后添加了一个<a>元素。

现在我们可以发布一下应用并启动Tomcat,验证一下我们的页面是否有错误,除了最后提交注册请求时会出现404的错误之外,显示上应该没什么问题,登录页面变成这样:

Java Web轻松学61 - 实现用户注册功能

注册页面是这样的:

看到上面的用户注册页面,我们很容易想到这里还有一个要考虑的问题,就是用户一旦点击注册按钮提交了注册请求之后,如何知道自己是否注册成功呢?如果不成功,那是因为什么导致不成功呢?即我们的系统应该给用户返回何种响应呢?

我这里的设计是提供一个注册结果的响应页面,它显然是动态的,它要么提示注册成功,要么提示导致注册失败的原因。所以,我设计了两个页面,一个是静态页面,一个是JSP页面。

register-success.html:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>租房网 - 注册成功!</title>
</head>
<body>
<h1>注册成功!请<a href="login.html">登录</a>!</h1>
</body>
</html> 

register-failure.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="#34; prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>租房网 - 注册失败!</title>
</head>
<body>
<h2>注册失败!请重新<a href="register.html">注册</a>!</h2>
<h3>失败原因:${errorMessage}</h3>
</body>
</html> 

注意,这里使用EL表达式来访问数据,所以转发给此页面的时候必须附加上数据 errorMessage

User实体类

虽然我们的思路中是按照前端页面、控制器层、服务层、数据访问层(DAO层)来分析的,但是由于我们的控制器层依赖于服务层,而服务层又依赖于数据访问层,所以开发的时候我们可以自下而上,这样的话代码层面不会出现错误提示。

我们先来考虑User实体类,前面已经提到过用户提交的注册信息至少包含用户名和密码,所以User实体类也必须有这两项。

难道这样就够了吗?我们再以用户的角度思考一下,假如用户有一天突然觉得自己的用户名不好,希望修改为另外一个用户名怎么办?是不是需要将关联到该用户名的其他记录都需要修改为新的用户名?这种方案实际上也可以,不过就会产生牵一发而动全身的不良效果。

所以,为了灵活考虑,我们应该为每个用户赋予一个全局唯一的且不可变的用户ID,然后跟该用户有关的记录都关联到这个用户ID。从这里我们可以看出,这个用户ID是由我们的系统自动生成的 (那该如何生成呢?下面介绍) ,对用户是不可见的。

然后,需要考虑用户ID的数据类型 (实际上它与如何生成也有一定关系) ,我们这里仍然选择字符串。

综合起来我们的User实体类有如下特点:

  • 用户ID由系统自动生成,唯一且不可变;
  • 用户名也唯一,但是可以被用户修改;
  • 用户密码的安全性问题,暂且不考虑。
package houserenter.entity;

public class User {

private String id;
private String name;
private String password;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [name=" + name + ", password=" + password + "]";
}

}
 

实体类中的getter方法、setter方法、toString()方法可以使用IDE提供的功能快速添加。

DAO层 – UserMapper接口和元数据

我们很容易根据业务功能设计出 UserMapper 包含哪些接口。

因为用户注册功能相当于是新增一个用户,所以需要一个插入用户的接口。

又因为用户注册时需要判断用户名是否已经被注册,所以需要一个根据用户名查找用户的接口。

不过,由于我们采用的是H2Database的嵌入式模式,所以必须由应用自己来创建用户表(实际生产环境中一般是由DBA来建表),所以还需要一个创建用户表的接口。但是用户表不能每次都重复创建,所以使用了 if not exists 语法。

UserMapper.java:

package houserenter.mapper;

import houserenter.entity.User;

public interface UserMapper {

int cteateTable();

int insert(User user);

User selectByName(String name);
}
 

UserMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "#34;>
<mapper namespace="houserenter.mapper.UserMapper">

<update id="cteateTable">
create table if not exists user(id varchar(36) primary key, name varchar(32), password varchar(16))
</update>

<insert id="insert" parameterType="houserenter.entity.User">
insert into user(id, name, password) values(#{id}, #{name}, #{password})
</insert>

<select id="selectByName" parameterType="java.lang.String" resultType="houserenter.entity.User">
select id,name,password from user where name = #{name}
</select>
</mapper> 

服务层 – UserService

服务层实现业务逻辑,所以就目前来说 UserService 最主要的一个方法是处理用户的注册请求。

用户注册的业务逻辑我这里设计的比较简单,仅仅包括判断两次密码是否一致和用户名是否已经被注册,实际上还有用户名和密码的长度、字符要求等限制:

Java Web轻松学61 - 实现用户注册功能

另外,由于我们采用的是H2Database的嵌入式模式,所以必须由应用自己来创建用户表(实际生产环境中一般是由DBA来建表),所以在UserService组件实例化之后首先需要判断用户表是否存在,如果不存在则创建。

package houserenter.service;

import java.util.UUID;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import houserenter.entity.User;
import houserenter.mapper.UserMapper;

@Service
public class UserService {

@Autowired
private UserMapper userMapper;

@PostConstruct
public void init() {
userMapper.cteateTable();
}

public User register(String userName, String password, String passwordConfirmed) throws Exception {

if (!passwordConfirmed.equals(password)) {
throw new Exception("两次输入的密码不一致,请重新输入!");
}

User user = userMapper.selectByName(userName);
if (user != null) {
throw new Exception("用户名 " + userName + " 已经注册过,请选择其他用户名!");
}

user = new User();
user.setId(UUID.randomUUID().toString());
user.setName(userName);
user.setPassword(password);
userMapper.insert(user);

return user;
}
}
 

这个UserService其实也没有太多可说的,它依赖于 UserMapper,然后依照业务流程来编写代码即可。

需要重点关注的是,我在这里使用了Java异常(可以参考 ),一旦两次密码不一致,或者用户名已经被注册过,就抛出异常。

还有一点是,我使用了 java.util.UUID 类来生成全局唯一的用户ID。

最后要提醒的是,register() 方法的返回值类型是 User 。这是因为在此业务逻辑中我们生成了用户ID,而此用户ID有可能被上层组件用到。一般情况下,方法一旦生成了新数据,则需要将该新数据返回给调用者。

还有一个比较容易忽略的问题是数据库操作的 事务 。因为我们的这段业务逻辑中访问数据库的地方有两处,一处是判断用户名是否已经被注册过,一处是插入新用户。如果有两个用户注册的请求到来,且它们要注册的用户名相同,那么很有可能一个请求刚刚判断完用户名不存在(尚未插入到数据库),另一个请求接着也判断该用户名是否已经被注册过(显然是没有),最后导致该用户名被注册两次。我们可以在数据库层面为user表的name列加上唯一性约束,也可以在应用层面将若干操作封装为事务。我们这里暂且忽略这个问题。

控制器层 – 添加Handler

控制器类 HouseRenterController 首先要注入 UserService :

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>租房网 - 注册</title>
</head>
<body>
<form action="register.action" method="post">
<h2>用户注册</h2>
<label for="user_name">请输入用户名</label><input type="text" id="user_name" name="userName" />
<label for="password">请输入密码</label><input type="password" id="password" name="password" />
<label for="password_confirmed">请再次输入密码</label><input type="password" id="password_confirmed" name="passwordConfirmed" />
<input type="submit" value="注册•" />
</form>
<p><a href="login.html">已经注册,直接登录!</a></p>
</body>
</html> 

添加处理注册请求的Handler也很简单:

@PostMapping("/register.action")
public ModelAndView postRegister(String userName, String password, String passwordConfirmed) {
System.out.println("userName: " + userName + ", password: " + password + ", passwordConfirmed: " + passwordConfirmed);
ModelAndView mv = new ModelAndView();
try {
userService.register(userName, password, passwordConfirmed);
mv.setViewName("register-success.html");
} catch (Exception e) {
mv.addObject("errorMessage", e.getMessage());
mv.setViewName("register-failure.jsp");
}
return mv;
} 

唯一要关注的是,注册成功和注册失败分别转发到了不同的页面。

总结

本篇文章简单实现了用户注册的功能,还有很多可以优化改进的地方:

  • 注册请求的参数绑定和校验还可以进一步简化,目前即便用户名为空也可以注册成功、长度和字符也没有限制等;
  • 注册结果的展示还不够友好,用户还需要点击一次才能继续登录或注册;
  • 不够安全,密码是明文存储、没有验证码等;
  • 用户ID的生成可以采用更好的方案;
  • 具有原子性的业务,访问数据库需要放到一个事务中;
  • 异常处理还不够完善;
  • 等等。

不管怎样,我们还是实现了一个基本可用的用户注册功能,而且开发起来还是相当快、相当清晰的,因为我们之前已经搭建好了整个技术框架啊,正所谓磨刀不误砍柴工!

文章来源:智云一二三科技

文章标题:Java Web轻松学61 – 实现用户注册功能

文章地址:https://www.zhihuclub.com/200800.shtml

关于作者: 智云科技

热门文章

网站地图