最后两课的内容是实践,结合前⾯课程的技术来做⼀个简单的⽤户管理系统,该系统包括以下功能:管理员
注册、注册验证、管理员登录、管理员退出、添加⽤户、修改⽤户、删除⽤户、浏览⽤户信息等功能。
技术选型,使⽤
MongoDB
存储系统数据、使⽤
Filter
检查⽤户的登录状态、使⽤
Redis
管理⽤户
Session\
数据缓存、使⽤
Spring Boot Mail
验证⽤户注册邮箱,使⽤
Hibernate-validator
做参数校验,使⽤
BootStrap
前端框架、
Thymeleaf
模板,并且使⽤
Thymeleaf
进⾏⻚⾯布局。
功能设计
⾸先看⼀下⽤户管理系统的业务流程图:
- 访问⾸⻚,需判断⽤户是否登录
- ⽤户登录时判断是否注册,提示⽤户去注册
- 注册成功后,发送验证邮件
- ⽤户登录邮箱,点击链接验证邮箱
- ⽤户登录成功后,进⼊⽤户管理⻚⾯GitChat
- ⽤户管理⻚⾯可以对⽤户进⾏浏览,增、删、改、查等操作
- ⽤户可以单击“退出”按钮进⾏退出操作
- 每次的请求都会验证⽤户是否登录,如果 session 失效或者未登录会⾃动跳转到登录⻚⾯
从以上的内容可以看出⽤户管理系统的主要功能,如果在⽇常的⼯作中接到这样的⼀个需求,会怎么设计或
者开发呢?
本节课程的开发步骤是:
- 开发数据库层的增、删、改功能
- 开发 Web 层代码,输出增、删、改、查的请求接⼝
- ⻚⾯布局、进⾏数据展示层的代码开发
- 结合上⾯前 3 步操作完成⽤户增、删、改、查功能
- 开发⽤户注册、登录、退出功能
- 注册成功发送验证邮件、单击邮件链接验证修改⽤户状态
- 进⾏ Session 管理,使⽤ Redis 管理⽤户的 Session 信息
- 添加⾃定义 Filter 对⽤户的请求进⾏验证
- 添加缓存、综合调试
前期准备
我们使⽤
MongoDB
做为数据库,需要有
MongoDB
数据库环境;使⽤了
Redis
管理
Session
,需要有
Redis
环境;⽤户注册之后会发送邮件验证,需要准备邮件账户。
添加相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
从以上配置可以看出,我们使⽤
spring-boot-starter-web
组件处理
Web
层业务,使⽤
spring-boot-starter
thymeleaf
组件作为前端⻚⾯模板引擎,使⽤
spring-boot-starter-data-mongodb
组件操作
MongoDB
数据
库,使⽤
spring-session-data-redis
组件和
Redis
交互,使⽤
spring-boot-starter-mail
组建处理邮件相关内 容!
配置信息
数据库、
Thymeleaf
、
Session
失效时间配置:
# mongodb 配置
spring.data.mongodb.uri=mongodb://localhost:27017/manage
# 测试环境取消 thymeleaf 缓存
spring.thymeleaf.cache=false
spring.session.store-type=redis
# 设置 session 失效时间
spring.session.timeout=3600
Redis
配置:
# Redis 服务器地址
spring.redis.host=192.168.0.xx
# Redis 服务器连接端⼝
spring.redis.port=6379
# Redis 服务器连接密码(默认为空)
spring.redis.password=
# 连接池最⼤连接数(使⽤负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最⼤阻塞等待时间(使⽤负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最⼤空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最⼩空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=0
邮件配置:
spring.mail.host=smtp.126.com
spring.mail.username=youremail@126.com
spring.mail.password=yourpass
spring.mail.default-encoding=UTF-8
开发数据库层代码
public interface UserRepository extends MongoRepository<User, String> {
Page<User> findAll(Pageable pageable);
Optional<User> findById(String id);
User findByUserNameOrEmail(String userName, String email);
User findByUserName(String userName);
User findByEmail(String email);
void deleteById(String id);
}
根据前期
MongoDB
的课程我们知道,集成
MongoRepository
会⾃动实现很多内置的数据库操作⽅法。
GitChat
Web 层⽤户管理
Param
设计,在项⽬中创建了
param
包⽤来存放所有请求过程中的参数处理,如登录、注册、⽤户等,根据
不容的请求来设计不同的请求对象。
分⻚展示⽤户列表信息
@RequestMapping("/list")
public String list(Model model,@RequestParam(value = "page", defaultValue = "0") I
nteger page,
@RequestParam(value = "size", defaultValue = "6") Integer size)
{
Sort sort = new Sort(Sort.Direction.DESC, "id");
Pageable pageable = PageRequest.of(page, size, sort);
Page<User> users=userRepository.findAll(pageable);
model.addAttribute("users", users);
logger.info("user list "+ users.getContent());
return "user/list";
}
添加⽤户
public String add(@Valid UserParam userParam,BindingResult result, ModelMap model)
{
String errorMsg="";
if(result.hasErrors()) {
List<ObjectError> list = result.getAllErrors();
for (ObjectError error : list) {
errorMsg=errorMsg + error.getCode() + "-" + error.getDefaultMessage()
+";";
}
model.addAttribute("errorMsg",errorMsg);
return "user/userAdd";
}
User u= userRepository.findByUserNameOrEmail(userParam.getUserName(),userParam
.getEmail());
if(u!=null){
model.addAttribute("errorMsg","⽤户已存在!");
return "user/userAdd";
}
User user=new User();
BeanUtils.copyProperties(userParam,user);
user.setRegTime(new Date());
user.setUserType("user");
userRepository.save(user);
return "redirect:/list";
}
⾸先验证参数是否正确,再次查询此⽤户名或者邮箱是否已经添加过,校验⽆误后将⽤户信息保存到数据库
中,⻚⾯跳转到⽤户列表⻚。
修改⽤户
public String edit(@Valid UserParam userParam, BindingResult result,ModelMap model
) {
String errorMsg="";
if(result.hasErrors()) {
List<ObjectError> list = result.getAllErrors();
for (ObjectError error : list) {
errorMsg=errorMsg + error.getCode() + "-" + error.getDefaultMessage()
+";";
}
model.addAttribute("errorMsg",errorMsg);
model.addAttribute("user", userParam);
return "user/userEdit";
}
User user=userRepository.findById(userParam.getId()).get();
BeanUtils.copyProperties(userParam,user);
user.setRegTime(new Date());
userRepository.save(user);
return "redirect:/list";
}
和添加⽤户的业务逻辑⼤体相同,最主要的是需要⾸先查询此⽤户信息,再根据前端内容进⾏修改。
删除⽤户
public String delete(String id) {
userRepository.deleteById(id);
return "redirect:/list";
}
删除⽤户⽐较简单,直接调⽤
Repository.delete()
⽅法即可。
前端⻚⾯
⽤户列表
<h1>⽤户列表</h1>
<br/><br/>
<div class="with:80%">
<table class="table table-hover">
<thead>
<tr>
<th>User Name</th>
<th>Email</th>
<th>User Type</th>
<th>Age</th>
<th>Reg Time</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td th:text="${user.userName}">neo</td>
<td th:text="${user.email}">neo@126.com</td>
<td th:text="${user.userType}">User</td>
<td th:text="${user.age}">6</td>
<td th:text="${#dates.format(user.regTime, 'yyyy-MM-dd HH:mm:ss')}"></
td>
<td><a th:href="@{/toEdit(id=${user.id})}" th:if="${user.userType !='
manage'}" >edit</a></td>
<td><a th:href="@{/delete(id=${user.id})}" onclick="return confirm('确
认是否删除此⽤户?')" th:if="${user.userType !='manage'}" >delete</a></td>
</tr>
</tbody>
</table>
<div th:include="page :: pager" th:remove="tag"></div>
</div>
<div class="form-group">
<div class="col-sm-2 control-label">
<a href="/toAdd" th:href="@{/toAdd}" class="btn btn-info">添加</a>
</div>
</div>
效果图如下:
可以看出此⻚⾯的功能主要使⽤
Thymeleaf
语法循环遍历展示⽤户信息,并且给出修改和删除的链接;使⽤
了
th:if
来根据⽤户的不同状态来选择是否显示编辑和删除链接;⻚⾯中有这么⼀句:
<div th:include="page ::
pager" th:remove="tag"></div>
,其实就是引⼊了封装好的分⻚信息
page.html
,前⾯课程已经介绍了分⻚信
息这⾥不再多说。
添加⽤户
<form class="form-horizontal" th:action="@{/add}" method="post">
<div class="form-group">
<label for="userName" class="col-sm-2 control-label">userName</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="userName" id="userName"
placeholder="userName"/>
</div>
</div>
<div class="form-group">
<label for="email" class="col-sm-2 control-label" >email</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="email" id="email" placeh
older="email"/>
</div>
</div>
...
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<input type="submit" value="Submit" class="btn btn-info" />
<input type="reset" value="Reset" class="btn btn-info" />
<a href="/toAdd" th:href="@{/list}" class="btn btn-info">Back</a>
</div>
</div>
</form>
主要是输⼊⽤户相关信息,并提供提交、重置、返回的功能。
效果图如下
:
修改⻚⾯和添加类似,⼤家可以参考示例代码。
注册、登录、退出
注册
注册的⽤户类型为
manage
,后台添加的⽤户类型为
user
,两者区分开来⽅便后续管理。
注册⻚⾯代码:
<div class="with:60%">
<div style="margin: 10px 90px;">已有账户?直接<a th:href="@{/toLogin}">登录</a><
/div>
<form class="form-horizontal" th:action="@{/register}" method="post" >
<div class="form-group">`
<label for="userName" class="col-sm-2 control-label">User Name</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="userName" id="userN
ame" placeholder="enter userName"/>
</div>
</div>
<div class="form-group">`
<label for="email" class="col-sm-2 control-label">email</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="email" id="email" p
laceholder="enter email"/>
</div>
</div>
<div class="form-group">
<label for="password" class="col-sm-2 control-label" >Password</label>
<div class="col-sm-6">
<input type="password" class="form-control" name="password" id="pa
ssword" placeholder="enter password"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label"></label>
<div class="col-sm-6">
<div th:if="${errorMsg != null}" class="alert alert-danger" role=
"alert" th:text="${errorMsg}">
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-6">
<input type="submit" value="Submit" class="btn btn-info" />
<input type="reset" value="Reset" class="btn btn-info" />
</div>
</div>
</form>
</div>
效果图如下:
后端处理逻辑如下:
@RequestMapping("/register")
public String register(@Valid RegisterParam registerParam, BindingResult result, M
odelMap model) {
logger.info("register param"+ registerParam.toString());
String errorMsg = "";
if (result.hasErrors()) {
List<ObjectError> list = result.getAllErrors();
for (ObjectError error : list) {
errorMsg = errorMsg + error.getCode() + "-" + error.getDefaultMessage(
) + ";";
}
model.addAttribute("errorMsg", errorMsg);
return "register";
}
UserEntity u = userRepository.findByUserNameOrEmail(registerParam.getUserName(
), registerParam.getEmail());
if (u != null) {
model.addAttribute("errorMsg", "⽤户已存在!");
return "register";
}
UserEntity user = new UserEntity();
BeanUtils.copyProperties(registerParam, user);
user.setRegTime(new Date());
user.setUserType("manage");
user.setState("unverified");
userRepository.save(user);
logger.info("register user "+ user.toString());
return "login";
}
判断参数输⼊是否正确,⽤户是否已经存在,验证完毕后将⽤户信息存⼊数据库,并跳转到登录⻚⾯。
GitChat
登录
登录⻚⾯代码:
<div class="with:60%">
<div style="margin: 10px 90px;">没有账户?先去<a th:href="@{/toRegister}">注册</
a></div>
<form class="form-horizontal" th:action="@{/login}" method="post" >
<div class="form-group">`
<label for="loginName" class="col-sm-2 control-label">Login Name</labe
l>
<div class="col-sm-6">
<input type="text" class="form-control" name="loginName" id="logi
nName" placeholder="enter userName or email"/>
</div>
</div>
<div class="form-group">
<label for="password" class="col-sm-2 control-label" >Password</label>
<div class="col-sm-6">
<input type="password" class="form-control" name="password" id="pa
ssword" placeholder="enter password"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label"></label>
<div class="col-sm-6">
<div th:if="${errorMsg != null}" class="alert alert-danger" role=
"alert" th:text="${errorMsg}">
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-6">
<input type="submit" value="Submit" class="btn btn-info" />
</div>
</div>
</form>
</div>
效果图如下:
后端处理逻辑如下:
@RequestMapping("/login")
public String login(@Valid LoginParam loginParam, BindingResult result, ModelMap m
odel, HttpServletRequest request) {
String errorMsg = "";
if (result.hasErrors()) {
List<ObjectError> list = result.getAllErrors();
for (ObjectError error : list) {
errorMsg = errorMsg + error.getCode() + "-" + error.getDefaultMessage(
) + ";";
}
model.addAttribute("errorMsg", errorMsg);
return "login";
}
UserEntity user = userRepository.findByUserName(loginParam.getLoginName());
if (user == null) {
user = userRepository.findByEmail(loginParam.getLoginName());
}
if (user == null) {
model.addAttribute("errorMsg", "⽤户名不存在!");
return "login";
} else if (!user.getPassword().equals(loginParam.getPassword())) {
model.addAttribute("errorMsg", "密码错误!");
return "login";
}
request.getSession().setAttribute(WebConfiguration.LOGIN_KEY, user.getId());
request.getSession().setAttribute(WebConfiguration.LOGIN_USER, user);
return "redirect:/list";
}
⾸先根据⽤户名或者⽤户邮件去查找此⽤户,如果⽤户不存在返回并给出提示;如果找到⽤户判断⽤户密码
是否正确,如正确,将⽤户信息存⼊
Session
中,并跳转到⽤户列表⻚。
退出
退出⽐较简单只需要清空
Session
中的⽤户信息即可:
@RequestMapping("/loginOut")
public String loginOut(HttpServletRequest request) {
request.getSession().removeAttribute(WebConfiguration.LOGIN_KEY);
request.getSession().removeAttribute(WebConfiguration.LOGIN_USER);
return "login";
}
下⼀课我们继续介绍⽤户管理系统的设计研发。