4 设计与规范
4.1 实体设计
实体类
- 博客Blog
- 博客分类Type
- 博客标签Tag
- 博客评论Comment
- 用户User
5 后台管理
5.1 登录
1 构建登录页面和后台管理首页
2 UserService和UserDao
3 LoginController实现登录
4 MD5加密
5 登录拦截器
登录注销操作
@Controller
@RequestMapping("/admin")
public class LoginController {
@Autowired
private UserService userService;
//管理员登录的入口(跳转到登录页面)
@GetMapping()
public String loginPage(){
return "admin/login";
}
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
HttpSession session,
RedirectAttributes attributes){
User user = userService.checkUser(username, password);
if(user != null){
// 浏览器获取不到密码
user.setPassword(null);
session.setAttribute("user", user);
return "admin/index";
}else {
// 数据重定向需要用RedirectAttributes,用Model获取不到
attributes.addFlashAttribute("msg", "用户名或密码错误");
return "redirect:/admin";
}
}
@GetMapping("/logout")
public String logout(HttpSession session){
// 注销清空session域中信息
session.removeAttribute("user");
return "redirect:/admin";
}
加密类
public class MD5Utils {
/**
* MD5加密类
* @param str 要加密的字符串
* @return 加密后的字符串
*/
public static String code(String str){
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes());
byte[]byteDigest = md.digest();
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < byteDigest.length; offset++) {
i = byteDigest[offset];
if (i < 0)
i += 256;
if (i < 16)
buf.append("0");
buf.append(Integer.toHexString(i));
}
//32位加密
return buf.toString();
// 16位的加密
//return buf.toString().substring(8, 24);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
在service层将密码加密后再与数据库中已加密的密码比较
@Override
public User checkUser(String username, String password) {
User user = userDao.queryByUsernameAndPassword(username, MD5Utils.code(password));
return user;
}
加密的意义避免数据库存储明文用户密码,避免数据库泄露后造成用户密码泄露
拦截器类
//登录拦截器
//extends HandlerInterceptAdapter 已过时
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (request.getSession().getAttribute("user") == null){
response.sendRedirect("/admin");
return false;
}
return true;
}
}
配置拦截器配置类
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/admin/**")
// 不能拦截的路径
.excludePathPatterns("/admin")
.excludePathPatterns("/admin/login");
}
}
5.2 分类管理
- 分类管理页面
2. 分类列表分页
添加pagehelper依赖
<!-- pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.13</version>
</dependency>
通过导入pagehelper的pageInfo对象
将查询到的数据进行分页(属于逻辑分页)
@GetMapping("/types")
public String types(@RequestParam(required = false,defaultValue = "1",value = "pageNum")int pageNum,
Model model){
Integer p = (Integer) model.getAttribute("pageNum");
if (p!=null){
pageNum = p;
}
PageHelper.startPage(pageNum, 5);
List<Type> allType = typeService.getAllType();
//得到分页结果对象
PageInfo<Type> pageInfo = new PageInfo<>(allType);
model.addAttribute("pageInfo", pageInfo);
return "admin/types";
}
pageInfo有很多属性方法可以满足基本分页功能需求
<div class="ui success message" th:unless="${#strings.isEmpty(msg)}">
<i class="close icon"></i>
<div class="header">提示:</div>
<p th:text="${msg}">恭喜,操作成功!</p>
</div>
<tr th:each="type, iterStat : ${pageInfo.list}">
<td th:text="${iterStat.count}"></td>
<td th:text="${type.name}"></td>
<td>
<a href="#" th:href="@{/admin/types/{id}/input(id=${type.id})}" class="ui mini teal basic button">编辑</a>
<a href="#" th:href="@{/admin/types/{id}/{pageNum}/delete(id=${type.id},pageNum=${pageInfo.pageNum})}" class="ui mini red basic button">删除</a>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<th colspan="7">
<div class="ui mini pagination menu" th:if="${pageInfo.pages}">
<a th:href="@{/admin/types}" th:unless="${pageInfo.isIsFirstPage()}" class="item">首页</a>
<a th:href="@{/admin/types(pageNum=${pageInfo.hasPreviousPage}?${pageInfo.prePage}:1)}" th:unless="!${pageInfo.isHasPreviousPage()}" class="item">上一页</a>
<a th:href="@{/admin/types(pageNum=${pageInfo.hasNextPage}?${pageInfo.nextPage}:${pageInfo.pages})}" th:unless="!${pageInfo.isHasNextPage()} " class="item">下一页</a>
<a th:href="@{/admin/types(pageNum=${pageInfo.pages})}" th:unless="${pageInfo.isIsLastPage()}" class="item">尾页</a>
</div>
<a href="#" th:href="@{/admin/types/input}" class="ui mini right floated teal basic button">新增</a>
</th>
</tr>
</tfoot>
- 分类新增,修改,删除
新增删除,修改,删除操作较简单,在这里可以省略
主要注意点
如何实现一个页面完成增加修改操作
@GetMapping("/types/input")
public String toAddType(Model model){
model.addAttribute("type", new Type()); //返回一个type对象给前端th:object
return "admin/types-input";
}
@GetMapping("/types/{id}/input")
public String toEditType(@PathVariable Long id,
Model model){
model.addAttribute("type", typeService.getType(id));
return "admin/types-input";
}
通过返回一个空对象和一个从数据库获取的对象
由前端判断,对象为空?新增:修改
<form action="#" th:object="${type}" th:action="*{id}==null ? @{/admin/types} : @{/admin/types/{id}(id=*{id})}"
th:method="post" class="ui form" >
如何设计url与对应映射的controller地址
举个例子
GET /zoos:列出所有动物园
POST /zoos:新建一个动物园
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物