【在线网页收藏夹】后端
文章目录
- 本项目使用的技术
- 后端框架:spring boot + JPA
- web框架:spring MVC(Model模型、View视图、Controller控制器)
- 前端框架:layUI
- 包目录结构
config(配置包)
WebMvcConfig.java (配置类,实现WebMvcConfigurer接口)
package com.favorites.config;
import com.favorites.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 登录拦截
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") //所有路径都被拦截
.excludePathPatterns("/login/**", "/register/**", //添加不被拦截的路径
"/login.html", "/layui/**", "/images/**",
"/favicon.ico");
}
}
关于WebMvcConfigurer接口
WebMvcConfigurer是spring boot内部的一种配置方式
实现WebMvcConfigurer接口(推荐)
继承WebMvcConfigurationSupport类。
接口常用的方法:
/* 拦截器配置 */ void addInterceptors(InterceptorRegistry var1); /* 视图跳转控制器 */ void addViewControllers(ViewControllerRegistry registry); /* 静态资源处理 */ void addResourceHandlers(ResourceHandlerRegistry registry);
代码分析
该类主要实现WebMvcConfigurer
接口的拦截器addInterceptors
方法,实现用户登录拦截。
public void addInterceptors(InterceptorRegistry registry) {
super.addInterceptors(registry);
registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**").excludePathPatterns("/emp/toLogin","/emp/login","/js/**","/css/**","/images/**");
}
addPathPatterns("/**")
:addPathPatterns()
用于设置拦截器的过滤路径规则,/**
表示对所有请求都拦截。excludePathPatterns()
:用于设置不需要拦截的过滤路径规则。
更多接口相关方法参考博客:https://blog.csdn.net/zhangpower1993/article/details/89016503#2.%20WebMvcConfigurer%E6%8E%A5%E5%8F%A3
interceptor(拦截器包)
LoginInterceptor.java(登录拦截类)
package com.favorites.interceptor;
import com.favorites.dao.UserRepository;
import com.favorites.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Base64;
@Component
public class LoginInterceptor implements HandlerInterceptor {
//base64解码器,解码使用基本型base64编码方案
private Base64.Decoder decoder = Base64.getDecoder();
@Autowired
private UserRepository userRepository;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//统一拦截(查询当前session是否存在user)
User user = (User) request.getSession().getAttribute("user");
if (user != null) {
return true;
} else {
// 查看cookie
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if (cookie.getName().equals("token")) {
String[] token = new String(decoder.decode(cookie.getValue())).split("&&");
User user1 = userRepository.findByUsername(token[0]);
if (user1 != null && user1.getPassword().equals(token[1])) {
request.getSession().setAttribute("user", user1);
return true;
}
}
}
}
response.sendRedirect("/login.html");
}
return false;
}
}
关于HandlerInterceptor
接口
HandlerInterceptor
是spring处理器拦截器,主要功能
- 用户是否登录及用户权限
- 根据用户选择决定用HTML或Excel作为View
- *blackboard building block的应用(简单理解)
接口常用方法:
public interface HandlerInterceptor { /** * 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器,自定义Controller * 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应; */ boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; /** * 后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。 */ void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception; /** * 整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中 */ void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception; }
代码分析
该类主要使用preHandle()
方法(预处理回调方法)。在进行登陆拦截前检查用户是否登录(检查session
和cookie
中用户登录信息),若未登录则进行拦截返回false
,response
返回为"/login.html"
登陆页面进行重新登录;若已登录,则返回true
,继续执行下一步。
关于判断用户是否已登录
- 首先统一拦截,查询session中是否有用户的账号,密码信息。(个人理解为已在登录框中输入信息)
- 若输入框中无信息,则查询cookie中是否有用户的token,cookie中可储存两周内登陆过的用户信息。
model(模型包/实体类)
当model作为包名时,一般存的是实体类的模型,一般是给前端用于显示实体的字段内容。
User.java(用户实体类)
package com.favorites.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Data
@Entity
@Table(name = "t_user")
@org.hibernate.annotations.Table(appliesTo = "t_user", comment = "用户表")
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(value = { "hibernateLazyInitializer"})
public class User {
@Id
@Column(name = "id", nullable = false, columnDefinition = "int(10) comment '主键ID(自增)'")
@GeneratedValue
private Integer id;
@Column(name = "username", unique = true, nullable = false, columnDefinition = "varchar(100) comment '用户名'")
private String username;
@Column(name = "password", nullable = false, columnDefinition = "varchar(100) comment '密码'")
private String password;
@Column(name = "email", unique = true, nullable = false, columnDefinition = "varchar(100) comment '邮箱'")
private String email;
@Column(name = "random_key", nullable = false, columnDefinition = "varchar(10) comment '随机key'")
private String randomKey;
}
代码分析
使用hibernate
框架对数据库进行操作,定义User模型时,建立用户表。
@Table(name="XX")
:指明Table的名字@Id
,@GeneratedValue
:指定主键@Column
:Table的列名
Dao(数据接口访问层)
UserRepository.java(用户接口类)
package com.favorites.dao;
import com.favorites.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
//通过用户名查询用户
User findByUsername(String userName);
//通过邮箱查询用户
User findByEmail(String email);
//通过用户名或邮箱查询用户
User findByUsernameOrEmail(String username, String email);
//通过用户名和邮箱查询用户
User findByUsernameAndEmail(String username, String email);
}
关于JpaRepository
接口
JPA是spring boot的数据库访问组件。
- 使用前需要配置application.yml文件的数据库。
- 使用
@Repository
注解- 继承
JPARepository
UserRepository
不需要编写任何代码,就可实现增删改查
service(数据服务接口层)
UserService.java(用户服务类)
package com.favorites.service;
import com.favorites.dao.UserRepository;
import com.favorites.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User findByUsername(String username) {
return userRepository.findByUsername(username);
}
public User findByUsernameAndEmail(String username, String email) {
return userRepository.findByUsernameAndEmail(username, email);
}
public User save(User user) {
return userRepository.save(user);
}
public User findByUsernameOrEmail(String username, String email) {
return userRepository.findByUsernameOrEmail(username, email);
}
public User findByEmail(String email) {
return userRepository.findByEmail(email);
}
}
代码分析
个人理解:UserService.java
用户服务类的功能是实现UserRepository.java
用户接口的方法。
Controller(前端控制层)
UserController.java(用户控制类)
package com.favorites.controller;
import com.favorites.model.User;
import com.favorites.service.UserService;
import com.favorites.util.ApiResponse;
import com.favorites.util.PasswordUtils;
import com.favorites.util.SpringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/info")
//获取登录用户信息
public ApiResponse info() {
User user = (User) SpringUtils.getRequest().getSession().getAttribute("user");
return ApiResponse.success(user);
}
@PostMapping("/password")
//修改密码
public ApiResponse password(String oldPassword, String newPassword) {
User user = (User) SpringUtils.getRequest().getSession().getAttribute("user");
if (user.getPassword().equals(DigestUtils.md5DigestAsHex((oldPassword + user.getRandomKey()).getBytes()))) {
user.setRandomKey(PasswordUtils.randomPassword(10));
user.setPassword(DigestUtils.md5DigestAsHex((newPassword + user.getRandomKey()).getBytes()));
userService.save(user);
return ApiResponse.success();
} else {
return ApiResponse.error("身份验证错误");
}
}
}
代码分析
@RequestMapping("/user")
:映射处理请求,为控制器制定可以处理的URL请求。
使用方法:
- 在类定义处,提供初步请求映射信息
- 在方法处,提供详细映射信息
LoginController.java(登录控制类)
package com.favorites.controller;
import com.favorites.model.User;
import com.favorites.service.UserService;
import com.favorites.util.ApiResponse;
import com.favorites.util.EmailUtils;
import com.favorites.util.PasswordUtils;
import com.favorites.util.SpringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.Base64;
@RestController
@RequestMapping("/login")
public class LoginController {
//base64编码器
private final Base64.Encoder encoder = Base64.getEncoder();
@Autowired
private UserService userService;
@Autowired
private EmailUtils emailUtils;
@PostMapping
public ApiResponse login(@RequestBody User user, @RequestParam(required = false) String remember) {
User user1 = userService.findByUsername(user.getUsername());
if (user1 != null && user1.getPassword().equals(DigestUtils.md5DigestAsHex((user.getPassword() + user1.getRandomKey()).getBytes()))) {
HttpServletRequest request = SpringUtils.getRequest();
request.getSession().setAttribute("user", user1);
//保存user1到session会话中,会话期间都有效
if ("1".equals(remember)) { //自动登录
Cookie token = new Cookie("token", encoder.encodeToString((user1.getUsername() + "&&" + user1.getPassword()).getBytes()));
token.setPath("/");
token.setMaxAge(60 * 60 * 24 * 14);
SpringUtils.getResponse().addCookie(token);
}
return ApiResponse.success();
} else {
return ApiResponse.error("用户名或密码错误");
}
}
@PostMapping("/forgot") //忘记密码
public ApiResponse forgot(@RequestBody User user) {
User user1 = userService.findByUsernameAndEmail(user.getUsername(), user.getEmail());
if (user1 != null) {
String tempPwd = PasswordUtils.randomPassword(8);
// 重置用户密码
user.setRandomKey(PasswordUtils.randomPassword(10));
user.setPassword(DigestUtils.md5DigestAsHex((tempPwd + user.getRandomKey()).getBytes()));
userService.save(user1);
// 将临时密码发送至用户邮箱
emailUtils.send(user1.getEmail(), "海星收藏夹|重置密码",
String.format("您的新密码:%s,请牢记。", tempPwd));
return ApiResponse.success();
} else {
return ApiResponse.error("账号或邮箱不存在");
}
}
@GetMapping("/out")
//退出系统,清除session和cookie中的记录
public ApiResponse logout() {
HttpServletRequest request = SpringUtils.getRequest();
request.getSession().removeAttribute("user");
// 清除cookie
if (request.getCookies() != null) {
for (Cookie c : request.getCookies()) {
Cookie cookie = new Cookie(c.getName(), null);
cookie.setPath("/");
cookie.setMaxAge(0);
SpringUtils.getResponse().addCookie(cookie);
}
}
return ApiResponse.success();
}
}
代码分析
- 登录功能:查询用户表,保存到session,自动登录
- 忘记密码:用户名和邮箱查找用户,发送新密码邮件
- 退出系统:清除session和cookie的数据
RegisterController.java(注册控制类)
package com.favorites.controller;
import com.favorites.model.Category;
import com.favorites.model.Favorites;
import com.favorites.model.User;
import com.favorites.service.CategoryService;
import com.favorites.service.FavoritesService;
import com.favorites.service.UserService;
import com.favorites.util.ApiResponse;
import com.favorites.util.PasswordUtils;
import com.favorites.util.PinYinUtils;
import com.favorites.util.SpringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/register")
public class RegisterController {
@Autowired
private UserService userService;
@Autowired
private CategoryService categoryService;
@Autowired
private FavoritesService favoritesService;
/**
* 注册
*
* @param user
* @return
*/
@PostMapping
public ApiResponse register(@RequestBody User user) {
String password = user.getPassword();
if (userService.findByUsernameOrEmail(user.getUsername(), user.getEmail()) == null) {
user.setRandomKey(PasswordUtils.randomPassword(10));
user.setPassword(DigestUtils.md5DigestAsHex((user.getPassword() + user.getRandomKey()).getBytes()));
User user1 = userService.save(user);
// 创建默认分类
Category category = new Category(null, "默认分类", user1.getId(), 1, 9999, null);
categoryService.save(category);
// 推荐收藏
List<Favorites> recommends = new ArrayList<>();
recommends.add(new Favorites(null, "百度搜索", "https://www.baidu.com/favicon.ico", "https://www.baidu.com/", category.getId(), user1.getId(), PinYinUtils.toPinyin("百度搜索")));
recommends.add(new Favorites(null, "谷歌翻译", "https://translate.google.cn/favicon.ico", "https://translate.google.cn/", category.getId(), user1.getId(), PinYinUtils.toPinyin("谷歌翻译")));
favoritesService.saveAll(recommends);
// 设置session
SpringUtils.getRequest().getSession().setAttribute("user", user1);
return ApiResponse.success();
}
return ApiResponse.error("用户名或邮箱已存在");
}
@GetMapping("/{username}")
public ApiResponse checkUsername(@PathVariable String username) {
User user = userService.findByUsername(username);
if (user == null) {
return ApiResponse.success();
}
return ApiResponse.error();
}
@GetMapping("/email/{email}")
public ApiResponse checkEmail(@PathVariable String email) {
User user = userService.findByEmail(email);
if (user == null) {
return ApiResponse.success();
}
return ApiResponse.error();
}
}
util(工具包)
SpringUtils.java(spring工具类 响应和请求)
package com.favorites.util;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SpringUtils {
public static HttpServletRequest getRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
public static HttpServletResponse getResponse() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
}
}
代码分析
-
HttpServletRequest对象
:代表客户端的请求,通过这个对象提供的方法,可以获得客户端请求的所有信息。 -
HttpServletResponse对象
:代表服务器的响应,这个对象中封装了向客户端发送数据、发送响应头,发送响应状态码的方法。
ApiResponse.java(接口调用响应,成功/失败)
package com.favorites.util;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class ApiResponse {
Integer code;
String msg;
Object data;
public static ApiResponse success() {
return new ApiResponse(0, "success", null);
}
public static ApiResponse success(Object data) {
return new ApiResponse(0, "success", data);
}
public static ApiResponse error() {
return new ApiResponse(-1, "error", null);
}
public static ApiResponse error(String msg) {
return new ApiResponse(-1, msg, null);
}
}
PasswordUtils.java(随机生成密码)
package com.favorites.util;
import java.util.Random;
public class PasswordUtils {
private static final String SCOPE = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890";
private static final Random R = new Random();
/**
* 生成随机密码
*
* @param length
* @return
*/
public static String randomPassword(int length) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(SCOPE.charAt(R.nextInt(SCOPE.length())));
}
return sb.toString();
}
}
EmailUtils.java(邮箱工具类)
package com.favorites.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class EmailUtils {
@Autowired
private JavaMailSender javaMailSender;
@Value("${spring.mail.username}")
private String from;
@Async
public void send(String to, String title, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setSubject(title);
message.setText(text);
message.setTo(to);
message.setFrom(from);
javaMailSender.send(message);
}
}
JavaMailSender接口
:实现邮件的发送