第一章 开发社区登录模块

第一章 开发社区登录模块

链接:https://gitee.com/xu3619/community

整合邮件发送功能

Untitled

发送者邮箱中打开SMTP服务

  • 首先在自己的邮箱(网易、QQ…均可)设置中开启 SMTP 服务

Untitled

导入相关依赖

  • pom.xml 文件中引入相关依赖
<!-- 引入邮件发送依赖 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-mail</artifactId>
   <version>2.7.0</version>
</dependency>

邮箱参数配置

  • 这里我使用的是网易邮箱的相关配置参数,其他邮箱可以自行百度查看配置参数
spring:
  # 发送者邮箱相关配置
  mail:
    # SMTP服务器域名
    host: smtp.163.com
    # 编码集
    default-encoding: UTF-8
    # 邮箱用户名
    username: 15814963053@163.com
    # 授权码(注意不是邮箱密码!)
    password: XDK********ONREJ
    # 协议:smtps
    protocol: smtps
    # 详细配置
    properties:
      mail:
        smtp:
          # 设置是否需要认证,如果为true,那么用户名和密码就必须的,
          # 如果设置false,可以不设置用户名和密码 (前提要知道对接的平台是否支持无密码进行访问的)
          auth: true
          # STARTTLS[1] 是对纯文本通信协议的扩展;
          # 它提供一种方式将纯文本连接升级为加密连接(TLS或SSL),而不是另外使用一个端口作加密通信。
          starttls:
            enable: true
            required: true

邮件发送工具类

/**
 * 邮件发送客户端
 *
 * @author xiexu
 * @create 2022-06-03 11:20
 */
@Component
public class MailClient {

    private static final Logger logger = LoggerFactory.getLogger(MailClient.class);

    @Autowired
    private JavaMailSender mailSender;

    @Value("${spring.mail.username}")
    private String from;

    /**
     * @param to      收件人
     * @param subject 邮件主题
     * @param content 邮件内容
     */
    public void sendMail(String to, String subject, String content) {
        try {
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message);

            helper.setFrom(from); // 发件人
            helper.setTo(to); // 收件人
            helper.setSubject(subject); // 邮件主题
            helper.setText(content, true); // 邮件内容,第二个参数true表示支持html格式

            mailSender.send(helper.getMimeMessage());
        } catch (MessagingException e) {
            logger.error("邮件发送失败:" + e.getMessage());
        }
    }

}

测试邮件发送

@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class MailTests {

    @Autowired
    private MailClient mailClient;

    @Test
    public void testTextMail() {
        mailClient.sendMail("1193499619@qq.com", "TEST", "测试邮件发送!");
    }

}
  • 测试发送邮件成功!

Untitled

使用 Thymleaf 模板引擎发送 HTML 格式的邮件

  • demo.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>邮件示例</title>
</head>
<body>
<p>欢迎您, <span style="color:red;" th:text="${username}"></span>!</p>
</body>
</html>
  • 测试类
		@Autowired
    private TemplateEngine templateEngine;

		@Test
    public void testHtmlMail() {
        // 激活邮件发送
        Context context = new Context(); // org.thymeleaf.context.Context 包下
        context.setVariable("username", "xxx同学");
        String content = templateEngine.process("/mail/demo", context);
        System.out.println(content);
        mailClient.sendMail("1193499619@qq.com", "HTML", content);
    }

Untitled

开发注册功能

Untitled

导入相关依赖

<!-- apache下字符串工具类 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

参数配置

# community
community
  path
    domain: http://localhost:8080

配置工具类

  • CommunityUtil.java
public class CommunityUtil {

    // 生成随机字符串
    public static String generateUUID() {
        // 将所有的"-"替换成空字符串
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    // MD5加密
    // hello -> abc123def456
    // hello + 3e4a8 -> abc123def456abc
    // 这样可以增加安全性
    public static String md5(String key) {
        if (StringUtils.isBlank(key)) { // 判空操作
            return null;
        }
        return DigestUtils.md5DigestAsHex(key.getBytes());
    }

}
  • CommunityConstant.java
public interface CommunityConstant {

    /**
     * 激活成功
     */
    int ACTIVATION_SUCCESS = 0;

    /**
     * 重复激活
     */
    int ACTIVATION_REPEAT = 1;

    /**
     * 激活失败
     */
    int ACTIVATION_FAILURE = 2;

    /**
     * 默认状态的登录凭证的超时时间
     */
    int DEFAULT_EXPIRED_SECONDS = 3600 * 12;

    /**
     * 记住状态的登录凭证超时时间
     */
    int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100;

}

业务层

@Service
public class UserService implements CommunityConstant {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private MailClient mailClient;

    @Autowired
    private TemplateEngine templateEngine;

    @Value("${community.path.domain}")
    private String domain; // 域名

    @Value("${server.servlet.context-path}")
    private String contextPath; // 项目名

    public User findUserById(int id) {
        return userMapper.selectById(id);
    }

    // 注册方法
    public Map<String, Object> register(User user) {
        Map<String, Object> map = new HashMap<>();

        // 空值处理
        if (user == null) {
            throw new IllegalArgumentException("参数不能为空!");
        }
        if (StringUtils.isBlank(user.getUsername())) {
            map.put("usernameMsg", "账号不能为空!");
            return map;
        }
        if (StringUtils.isBlank(user.getPassword())) {
            map.put("passwordMsg", "密码不能为空!");
            return map;
        }
        if (StringUtils.isBlank(user.getEmail())) {
            map.put("emailMsg", "邮箱不能为空!");
            return map;
        }

        // 验证账号
        User u = userMapper.selectByName(user.getUsername());
        if (u != null) {
            map.put("usernameMsg", "该账号已存在!");
            return map;
        }

        // 验证邮箱
        u = userMapper.selectByEmail(user.getEmail());
        if (u != null) {
            map.put("emailMsg", "该邮箱已被注册!");
            return map;
        }

        // 注册用户
        // 生成随机的盐值
        user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
        // 对密码加盐后再进行MD5加密
        user.setPassword(CommunityUtil.md5(user.getPassword() + user.getSalt()));
        // 设置用户类型为0-普通用户; 1-超级管理员; 2-版主;
        user.setType(0);
        // 设置用户状态为0-未激活; 1-已激活;
        user.setStatus(0);
        // 生成激活码
        user.setActivationCode(CommunityUtil.generateUUID());
        // 生成随机头像,牛客网有一千个随机头像
        user.setHeaderUrl(String.format("http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));
        // 设置用户的创建时间
        user.setCreateTime(new Date());
        // 添加用户
        // 当调用insert语句时,mybatis会自动回写user,所以执行完这个语句后user就有id了
        userMapper.insertUser(user);

        // 发送激活邮件
        Context context = new Context();
        context.setVariable("email", user.getEmail());
        /**
         * http://localhost:8080/community/activation/101/code
         * activation是一个激活功能,需要传入用户id和激活码
         */
        String url = domain + contextPath + "/activation/" + user.getId() + "/" + user.getActivationCode();
        context.setVariable("url", url);
        String content = templateEngine.process("/mail/activation", context);
        mailClient.sendMail(user.getEmail(), "激活账号", content);

        return map;
    }

    /**
     * 激活邮件的方法
     *
     * @param userId 用户id
     * @param code   激活码
     * @return
     */
    public int activation(int userId, String code) {
        User user = userMapper.selectById(userId);
        if (user.getStatus() == 1) { // 用户状态已激活:1,未激活:0
            return ACTIVATION_REPEAT; // 重复激活
        } else if (user.getActivationCode().equals(code)) { // 激活码匹配上了
            // 将用户的status状态修改为1
            userMapper.updateStatus(userId, 1);
            return ACTIVATION_SUCCESS; // 激活成功
        } else {
            return ACTIVATION_FAILURE; // 激活失败
        }
    }

}

控制层

@Controller
public class LoginController implements CommunityConstant {

    @Autowired
    private UserService userService;

    @RequestMapping(path = "/register", method = RequestMethod.GET)
    public String getRegisterPage() {
        // 跳转到注册页面
        return "/site/register";
    }

    @RequestMapping(path = "/login", method = RequestMethod.GET)
    public String getLoginPage() {
        // 跳转到登录页面
        return "/site/login";
    }

    /**
     * 注意一个问题:在springmvc中,只要调用这个方法,那么参数user就会自动保存到model里面
     *
     * @param model
     * @param user
     * @return
     */
    @RequestMapping(path = "/register", method = RequestMethod.POST)
    public String register(Model model, User user) {
        Map<String, Object> map = userService.register(user);
        // 如果map为空说明注册成功了
        if (map == null || map.isEmpty()) {
            model.addAttribute("msg", "注册成功,我们已经向您的邮箱发送了一封激活邮件,请尽快激活!");
            model.addAttribute("target", "/index");
            // 跳转到操作结果页面
            return "/site/operate-result";
        } else { // 注册失败,返回错误信息并重新跳转到注册页面
            model.addAttribute("usernameMsg", map.get("usernameMsg"));
            model.addAttribute("passwordMsg", map.get("passwordMsg"));
            model.addAttribute("emailMsg", map.get("emailMsg"));
            // 跳转到注册页面
            return "/site/register";
        }
    }

    // http://localhost:8080/community/activation/101/code
    @RequestMapping(path = "/activation/{userId}/{code}", method = RequestMethod.GET)
    public String activation(Model model, @PathVariable("userId") int userId, @PathVariable("code") String code) {
        int result = userService.activation(userId, code);
        if (result == ACTIVATION_SUCCESS) { // 激活成功
            model.addAttribute("msg", "激活成功,您的账号已经可以正常使用了!");
            model.addAttribute("target", "/login"); // 跳转到登录页面
        } else if (result == ACTIVATION_REPEAT) { // 重复激活
            model.addAttribute("msg", "无效操作,该账号已经激活过了!");
            model.addAttribute("target", "/index"); // 跳转到首页
        } else { // 激活失败
            model.addAttribute("msg", "激活失败,您提供的激活码不正确!");
            model.addAttribute("target", "/index"); // 跳转到首页
        }
        // 跳转到操作结果页面
        return "/site/operate-result";
    }

}

会话管理

Untitled

cookie

Untitled

session

Untitled

在分布式系统中 session 存在一定的问题

Untitled

  • 解决方式:采用 redis 保存一些重要的数据,前期先采用 session,后期再升级为 redis

Untitled

生成验证码

  • 使用 Kaptcha 来生成验证码

Untitled

导入相关依赖

<!-- 生成验证码,图片 -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

编写 Kaptcha 配置类

@Configuration
public class KaptchaConfig {

    @Bean
    public Producer kaptchaProducer() {
        Properties properties = new Properties();
        properties.setProperty("kaptcha.image.width", "100");
        properties.setProperty("kaptcha.image.height", "40");
        properties.setProperty("kaptcha.textproducer.font.size", "32");
        properties.setProperty("kaptcha.textproducer.font.color", "0,0,0");
        properties.setProperty("kaptcha.textproducer.char.string", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYAZ");
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");

        DefaultKaptcha kaptcha = new DefaultKaptcha();
        Config config = new Config(properties);
        kaptcha.setConfig(config);
        return kaptcha;
    }

}

控制层

  • LoginController.java
		// 生成验证码的方法
    @RequestMapping(path = "/kaptcha", method = RequestMethod.GET)
    public void getKaptcha(HttpServletResponse response, HttpSession session) { // 因为验证码属于敏感数据,所以存在session比较合适
        // 生成验证码文本
        String text = kaptchaProducer.createText();
        // 通过验证码文本生成验证码图片
        BufferedImage image = kaptchaProducer.createImage(text);

        // 将验证码存入session
        session.setAttribute("kaptcha", text);

        // 将验证码图片输出给浏览器
        response.setContentType("image/png"); // 设置传输的格式
        try {
            OutputStream os = response.getOutputStream(); // 获取输出字节流
            ImageIO.write(image, "png", os);
        } catch (IOException e) {
            logger.error("响应验证码失败:" + e.getMessage());
        }
    }

开发登录、退出功能

Untitled

实体类

public class LoginTicket {

    private int id;
    // 用户id
    private int userId;
    // 凭证,需要发送给客户端
    private String ticket;
    // 0表示有效,1表示无效
    private int status;
    // 登录凭证过期时间
    private Date expired;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getTicket() {
        return ticket;
    }

    public void setTicket(String ticket) {
        this.ticket = ticket;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public Date getExpired() {
        return expired;
    }

    public void setExpired(Date expired) {
        this.expired = expired;
    }

    @Override
    public String toString() {
        return "LoginTicket{" + "id=" + id + ", userId=" + userId + ", ticket='" + ticket + '\'' + ", status=" + status + ", expired=" + expired + '}';
    }
}

数据层

@Mapper
public interface LoginTicketMapper {

    // 插入一条凭证数据
    @Insert({
            "insert into login_ticket(user_id,ticket,status,expired) ",
            "values(#{userId},#{ticket},#{status},#{expired})"
    })
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insertLoginTicket(LoginTicket loginTicket);

    // 根据凭证查询用户
    @Select({
            "select id,user_id,ticket,status,expired ",
            "from login_ticket where ticket=#{ticket}"
    })
    LoginTicket selectByTicket(String ticket);

    // 更新凭证的状态
    @Update({
            "update login_ticket set status=#{status} where ticket=#{ticket} "
    })
    int updateStatus(String ticket, int status);

}

业务层

		/**
     * 登录方法
     *
     * @param username
     * @param password
     * @param expiredSeconds 单位是毫秒
     * @return
     */
    public Map<String, Object> login(String username, String password, int expiredSeconds) {
        Map<String, Object> map = new HashMap<>();

        // 空值处理
        if (StringUtils.isBlank(username)) {
            map.put("usernameMsg", "账号不能为空!");
            return map;
        }
        if (StringUtils.isBlank(password)) {
            map.put("passwordMsg", "密码不能为空!");
            return map;
        }

        // 验证账号
        User user = userMapper.selectByName(username);
        if (user == null) {
            map.put("usernameMsg", "该账号不存在!");
            return map;
        }
        // 验证状态
        if (user.getStatus() == 0) { // 等于0表示该账号没有激活
            map.put("usernameMsg", "该账号未激活!");
            return map;
        }
        // 验证密码
        password = CommunityUtil.md5(password + user.getSalt());
        if (!user.getPassword().equals(password)) {
            map.put("passwordMsg", "密码不正确!");
            return map;
        }

        // 登录成功,需要生成登录凭证
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(user.getId());
        loginTicket.setTicket(CommunityUtil.generateUUID());
        loginTicket.setStatus(0); // 0表示该凭证有效
        loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
        loginTicketMapper.insertLoginTicket(loginTicket);

        map.put("ticket", loginTicket.getTicket()); // 将ticket数据存放到map中
        return map;
    }

		/**
     * 退出登录的方法
     *
     * @param ticket
     */
    public void loginOut(String ticket) {
        loginTicketMapper.updateStatus(ticket, 1); // 1表示该凭证无效
    }

控制层

		// 登录
		@RequestMapping(path = "/login", method = RequestMethod.POST)
    public String login(String username, String password, String code, boolean rememberme, Model model, HttpSession session, HttpServletResponse response) {

        // 检查验证码
        String kaptcha = (String) session.getAttribute("kaptcha");
        // equalsIgnoreCase表示验证码忽略大小写
        if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {
            model.addAttribute("codeMsg", "验证码不正确!");
            // 跳转到登录页面
            return "/site/login";
        }

        // 检查账号和密码
        int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
        Map<String, Object> map = userService.login(username, password, expiredSeconds);
        if (map.containsKey("ticket")) {
            // cookie的key和value都必须是字符串类型
            Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
            // 设置cookie在什么范围内有效,这里设置整个项目的访问路径都有效
            cookie.setPath(contextPath);
            // 设置cookie有效时间
            cookie.setMaxAge(expiredSeconds);
            // 发送cookie到浏览器
            response.addCookie(cookie);

            // 拿到ticket,说明登录成功,重定向到首页
            return "redirect:/index";
        } else {
            model.addAttribute("usernameMsg", map.get("usernameMsg"));
            model.addAttribute("passwordMsg", map.get("passwordMsg"));

            // 登录失败,跳转到登录页面
            return "/site/login";
        }
    }

		// 退出登录
    @RequestMapping(path = "/logout", method = RequestMethod.GET)
    public String logOut(@CookieValue("ticket") String ticket) {
        userService.loginOut(ticket);
        // 重定向到登录页面,我们有两个/login请求,重定向默认就是GET请求
        return "redirect:/login";
    }

显示登录信息

Untitled

客户端通过 cookie 携带登录凭证向服务器换取 user 信息,流程如图:

Untitled

工具类

  • CookieUtil.java
public class CookieUtil {

    public static String getValue(HttpServletRequest request, String name) {
        if (request == null || name == null) {
            throw new IllegalArgumentException("参数为空!");
        }
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(name)) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

}
  • HostHolder.java
/**
 * 持有用户信息,用于代替session对象
 *
 * @author xiexu
 * @create 2022-06-04 09:19
 */
@Component
public class HostHolder {

    private ThreadLocal<User> users = new ThreadLocal<>();

    public void setUser(User user) {
        users.set(user);
    }

    public User getUser() {
        return users.get();
    }

    public void clear() {
        users.remove();
    }

}

拦截器类

LoginTicketInterceptor.java 登录凭证拦截器

@Component
public class LoginTicketInterceptor implements HandlerInterceptor {

    @Autowired
    private UserService userService;

    @Autowired
    private HostHolder hostHolder;

    // 请求开始前先获取user信息
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从cookie中获取凭证
        String ticket = CookieUtil.getValue(request, "ticket");
        if (ticket != null) {
            // 查询凭证
            LoginTicket loginTicket = userService.findLoginTicket(ticket);
            /**
             * 检查凭证是否有效
             * status等于0表示凭证有效
             * loginTicket.getExpired().after(new Date()) 表示超时时间 晚于 当前时间
             */
            if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) {
                // 根据凭证的用户id查询用户信息
                User user = userService.findUserById(loginTicket.getUserId());
                // 在本次请求中(当前线程)持有该用户信息(要考虑多线程并发的情况,所以借助ThreadLocal)
                hostHolder.setUser(user);
            }
        }

        return true;
    }

    // 调用模板引擎之前执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 从ThreadLocal 中得到当前线程持有的user
        User user = hostHolder.getUser();
        if (user != null && modelAndView != null) {
            // 登录的用户信息存入modelAndView
            modelAndView.addObject("loginUser", user);
        }
    }

    // 在模板引擎执行之后调用该方法
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 从ThreadLocal中清除数据
        hostHolder.clear();
    }

}

配置类

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private LoginTicketInterceptor loginTicketInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginTicketInterceptor).excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg"); // 哪些路径不进行拦截
    }
}

业务层

		/**
     * 通过ticket查询用户信息
     *
     * @param ticket
     * @return
     */
    public LoginTicket findLoginTicket(String ticket) {
        return loginTicketMapper.selectByTicket(ticket);
    }

账号设置

Untitled

修改配置文件

# community
community:
# 配置文件上传路径
    upload: /Users/xiexu/community/data

业务层

  • UserService.java
		/**
     * 更新用户头像的路径
     *
     * @param userId
     * @param headerUrl
     * @return
     */
    public int updateHeader(int userId, String headerUrl) {
        return userMapper.updateHeader(userId, headerUrl);
    }

    /**
     * 修改密码
     *
     * @param userId      要修改密码的用户id
     * @param oldPassword 原始密码
     * @param newPassword 新密码
     */
    public boolean updatePassword(int userId, String oldPassword, String newPassword) {
        User user = userMapper.selectById(userId);
        // 获取原密码的盐值
        String salt = user.getSalt();
        // 先校验原密码是否匹配
        if (CommunityUtil.md5(oldPassword + salt).equals(user.getPassword())) {
            // 设置新密码
            newPassword = CommunityUtil.md5(newPassword + salt);
            userMapper.updatePassword(userId, newPassword);
            return true;
        }
        return false;
    }

控制层

  • UserController.java
@Controller
@RequestMapping("/user")
public class UserController {

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @Value("${community.path.upload}")
    private String uploadPath;

    @Value("${community.path.domain}")
    private String domain; // 域名

    @Value("${server.servlet.context-path}")
    private String contextPath; // 项目访问路径

    @Autowired
    private UserService userService;

    @Autowired
    private HostHolder hostHolder;

    @RequestMapping(path = "/setting", method = RequestMethod.GET)
    public String getSettingPage() {
        // 跳转到账号设置页面
        return "/site/setting";
    }

    @RequestMapping(path = "/upload", method = RequestMethod.POST)
    public String uploadHeader(MultipartFile headerImage, Model model) {
        if (headerImage == null) {
            model.addAttribute("error", "您还没有选择图片!");
            return "/site/setting";
        }

        // 获取上传到服务器的文件名
        String filename = headerImage.getOriginalFilename();
        // 获取文件名最后一个"."的后缀(比如png,jpeg)
        String suffix = filename.substring(filename.lastIndexOf("."));
        if (StringUtils.isBlank(suffix)) {
            model.addAttribute("error", "文件格式不正确!");
            return "/site/setting";
        }

        // 生成随机文件名
        filename = CommunityUtil.generateUUID() + suffix;
        // 确定文件存放的路径
        File dest = new File(uploadPath + "/" + filename);
        try {
            // 将headerImage写入到文件中
            headerImage.transferTo(dest);
        } catch (IOException e) {
            logger.error("上传文件失败:" + e.getMessage());
            throw new RuntimeException("上传文件失败,服务器发生异常!", e);
        }

        // 更新当前用户头像的路径(web访问路径)
        // http://localhost:8080/community/user/header/xxx.png
        String headerUrl = domain + contextPath + "/user/header/" + filename;
        User user = hostHolder.getUser();
        userService.updateHeader(user.getId(), headerUrl);

        // 更新成功,重定向到首页
        return "redirect:/index";
    }

    @RequestMapping(path = "/header/{fileName}", method = RequestMethod.GET)
    public void getHeader(@PathVariable("fileName") String fileName, HttpServletResponse response) {
        // 服务器存放路径
        fileName = uploadPath + "/" + fileName;
        // 文件后缀
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        // 响应图片
        response.setContentType("image/" + suffix);
        ServletOutputStream os = null;
        FileInputStream fis = null;
        try {
            os = response.getOutputStream();
            fis = new FileInputStream(fileName);
            byte[] buffer = new byte[1024];
            int b = 0;
            while ((b = fis.read(buffer)) != -1) {
                os.write(buffer, 0, b);
            }
        } catch (IOException e) {
            logger.error("读取头像失败:" + e.getMessage());
        } finally {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 修改密码
    @RequestMapping(path = "/updatePassword", method = RequestMethod.POST)
    public String updatePassword(String oldPassword, String newPassword, Model model) {
        User user = hostHolder.getUser();
        if (!userService.updatePassword(user.getId(), oldPassword, newPassword)) {
            model.addAttribute("passwordMsg", "原密码错误,请重新输入!");
            // 密码修改失败,跳转到账号设置页面
            return "/site/setting";
        } else {
            // 密码修改成功,重定向到登录页面
            return "redirect:/logout";
        }
    }

}

检查登录状态

对需要登录的接口添加注解,并在拦截器中进行登录状态检查

Untitled

注解类

@Target(ElementType.METHOD) // 该注解可以写在方法上
@Retention(RetentionPolicy.RUNTIME) // 该注解在程序运行时有效
public @interface LoginRequired {
    

}

拦截器

/**
 * 拦截器
 *
 * @author xiexu
 * @create 2022-06-04 13:24
 */
@Component
public class LoginRequiredInterceptor implements HandlerInterceptor {

    @Autowired
    private HostHolder hostHolder;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 判断拦截到的是不是一个方法
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 获取method对象
            Method method = handlerMethod.getMethod();
            // 获取该方法上的 @LoginRequired 注解
            LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);
            // 当前方法需要登录,但是该用户没有登录
            if (loginRequired != null && hostHolder.getUser() == null) {
                // 强制重定向到登录页面
                response.sendRedirect(request.getContextPath() + "/login");
                // 拒绝访问
                return false;
            }
        }
        return true;
    }

}

配置类

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private AlphaInterceptor alphaInterceptor;

    @Autowired
    private LoginTicketInterceptor loginTicketInterceptor;

    @Autowired
    private LoginRequiredInterceptor loginRequiredInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(alphaInterceptor).excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg") // 哪些路径不进行拦截
                .addPathPatterns("/register", "/login"); // 明确拦截哪些路径

        registry.addInterceptor(loginTicketInterceptor).excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg"); // 哪些路径不进行拦截

        registry.addInterceptor(loginRequiredInterceptor).excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg"); // 哪些路径不进行拦截
    }
}

在需要拦截的方法上加上该注解

Untitled

Untitled

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿小羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值