牛客网第二章

仿牛客网
内容部分引用至 https://blog.csdn.net/weixin_44406146

@

一、发送邮件

  • 邮箱设置

    • 启用客户端SMTP服务
  • Spring Email

    • 导入 jar 包
    • 邮箱参数配置
    • 使用 JavaMailSender 发送邮件
  • 模板引擎

    • 使用 Thymeleaf 发送 HTML 邮件

邮箱设置

跟教程一致选用新浪邮箱,开启SMTP服务

image

Spring Email

1.导入jar包

mavenrepository中搜索Spring mail的相关jar包

image

image

image

把上述红框内容复制到pom文件中

2.邮箱参数设置

此项目配置如下

spring:
  mail:
    host: smtp.163.com
    username: xxxx@163.com
#
    password: xxx(此处写你的授权码)
    protocol: smtps

注意

password设置的值是授权码,可以参考此链接说明

3.使用JavaMailSender发送邮件

建立一个工具包util,写一个工具类MailClient

@Component
public class MailClient {
    private static final Logger logger = LoggerFactory.getLogger(MailClient.class);
    @Autowired
    private JavaMailSender mailSender;
    //直接使用配置文件中的用户名
    @Value("${spring.mail.username}")
    private String from;
    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);
            //不加true说明支持字符文本,加true说明支持html文本
            helper.setText(content,true);
            mailSender.send(helper.getMimeMessage());
        } catch (MessagingException e) {
            logger.error("发送邮件失败"+e.getMessage());
        }
    }
}

测试一波

@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class MailTest {
    @Autowired
    private MailClient client;
    @Autowired
    private TemplateEngine templateEngine;
    //测试发送普通邮件
    @Test
    public void testMail(){
        client.sendMail("xxxx@qq.com","Test","hello world");
    }
    //测试发送html文件,这里想要根据用户名设置对应的html所以用了Thymeleaf
    @Test
    public void testHtmlMail(){
        Context context = new Context();
        context.setVariable("username","hsw");
        String html = templateEngine.process("mail/demo", context);
        //System.out.println(html);
        client.sendMail("xxxx@qq.com","TestHtml",html);
    }
}

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}"/></p>
</body>
</html>

二、开发注册功能

  • 访问注册页面

    • 点击顶部区域内的链接,打开注册页面。
  • 提交注册数据

    • 通过表单提交数据。
    • 服务端验证账号是否已存在、邮箱是否已注册。
    • 服务端发送激活邮件。
  • 激活注册账号

    • 点击邮件中的链接,访问服务端的激活服务

个人思想

  1. 点击注册页面,然后打开注册页面

    1. 在首页模板中设置跳转,因为通过th:fragment共用了模板,所以只需要修改首页模板
    2. 通过lang3工具包,检测前台传过来的数据是否为空
      1. 如果为空或者邮箱等已被占用返回前台消息错误提醒
    1. 在service中完成注册功能,并通过service进行加盐
    2. 通过templateEngine和Email发送激活邮件
      1. /activation/{userId}/{code} 通过后面两个属性,我们可以得到userId和激活码,我们通过方法找到userId,并校验数据库中的code是否正确(激活状态),如果是则会校验status值是否已被修改为1,不是则修改成功。通过templateEngine我们可以实现动态的注入返回页面

image.png

访问注册页面

开发顺序一般为:数据访问层、业务层、视图层。

1.共用头部

一般头部的代码都是一样的

image可以用Thymeleaf提供的th:fragment标签来共用这部分代码,如下index.html中部分内容

image

register.html中部分头部内容

image

2.把纯html的注册页面修改成Thymeleaf的格式

先修改index.html中内容

image

因为共用头部了,index.html修改了这部分内容之后register.html就不用修改了

提交注册数据

首先导入一个常用的包 commons lang

主要是字符串判空等功能

image

pom中添加如下

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.9</version>
</dependency>

添加配置信息

因为开发、上线域名不一样所以用户打开邮箱激活的链接也不一样所以给他弄成可变的

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

写个工具类

package com.hsw.community.util;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.DigestUtils;
import java.util.UUID;
public class CommunityUtil {
    // 生成随机字符串
    public static String generateUUID(){
        return UUID.randomUUID().toString().replace("-","");
    }
    //MD5加密
    //MD5只能加密不能解密,原串加salt拼接新串加密防破解
    public static String md5(String key){
        //null,空串,空格都会判空
        if(StringUtils.isBlank(key)){
            return null;
        }
        return DigestUtils.md5DigestAsHex(key.getBytes());
    }
}

正式开发功能

1.Dao已经写完不弄了

2.弄Service

在userService中添加如下代码

@Autowired
    private MailClient mailClient;
    @Autowired
    TemplateEngine templateEngine;
    @Value("${community.path.domain}")
    private String domain;
    @Value("${server.servlet.context-path}")
    private String contextPath;
    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;
        }
        //注册用户
        //1.随机生成盐
        user.setSalt(CommunityUtil.generateUUID().substring(0,5));
        //2.加盐并加密
        user.setPassword(CommunityUtil.md5(user.getPassword()+user.getSalt()));
        //'0-普通用户; 1-超级管理员; 2-版主;'
        user.setType(0);
        //'0-未激活; 1-已激活;'
        user.setStatus(0);
        user.setActivationCode(CommunityUtil.generateUUID());
        //牛客头像地址0-1000
        user.setHeaderUrl(String.format("http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));
        user.setCreateTime(new Date());
        //插入后user内会回填id 具体看user-mapper.xml
        userMapper.insertUser(user);
        //激活邮件
        Context context = new Context();
        context.setVariable("email",user.getEmail());
        //url规定这么搞:http://localhost:8080/community/activation/101/code    #101-用户id,#code-激活码
        String url = domain+contextPath+"/activation/"+user.getId()+"/"+user.getActivationCode();
        context.setVariable("url",url);
        String html = templateEngine.process("/mail/activation", context);
        mailClient.sendMail(user.getEmail(),"牛客网激活账号",html);
        return map;
    }

3.邮件模板内容

mail目录下activation.html

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
    <title>牛客网-激活账号</title>
</head>
<body>
    <div>
        <p>
            <b th:text="${email}">xxx@xxx.com</b>, 您好!
        </p>
        <p>
            您正在注册牛客网, 这是一封激活邮件, 请点击 
            <a th:href="${url}">此链接</a>,
            激活您的牛客账号!
        </p>
    </div>
</body>
</html>

4.写controller

在LoginController类中增加如下内容

@Autowired
    private UserService userService;
    @RequestMapping(value = "/register",method = RequestMethod.POST)
    public String register(Model model, User user){
        Map<String, Object> map = userService.register(user);
        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";
        }
    }

5.注册失败处理重新回到register.html页面

处理register.html页面

  • 为每个字段增加name属性便于springmvc封装进user

  • 注册过程中的错误处理

    • th:value添加默认值
      image
    • 输出错误信息
      image

6.注册成功的中间页面

site/operate-result.html

image

激活注册账号

1.util包中定义几个激活状态常量

让UserService实现此接口

package com.hsw.community.util;
public interface CommunityContant {
    //激活成功
    int ACTIVATION_SUCCESS = 0;
    //重复激活
    int ACTIVATION_REPEAT = 1;
    //激活失败
    int ACTIVATION_FAILURE = 2;
}

2.业务层中UserService类增加新方法

public int activion(int userId,String code){
        User user = userMapper.selectById(userId);
        if(user==null){
            return ACTIVATION_FAILURE;
        }else if(user.getStatus()==1){
            return ACTIVATION_REPEAT;
        }else if(!code.equals(user.getActivationCode())){
            return ACTIVATION_FAILURE;
        }else{
            //设置激活状态
            userMapper.updateStatus(userId,1);
            return ACTIVATION_SUCCESS;
        }
    }

3.controller层增加方法

LoginController类

//url规定这么搞:http://localhost:8080/community/activation/101/code    #101-用户id,#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.activion(userId, code);
        if(result==ACTIVATION_SUCCESS){
            model.addAttribute("msg","激活成功,您的账号可以使用");
            model.addAttribute("target","/login");
        }else if(result==ACTIVATION_REPEAT){
            model.addAttribute("msg","无效操作,该账号已经激活");
            model.addAttribute("target","/login");
        }else{
            model.addAttribute("msg","激活失败,激活码不正确请重新注册");
            model.addAttribute("target","/register");
        }
        return "site/operate-result";
    }
    @RequestMapping(value = "/login",method = RequestMethod.GET)
    public String getLoginPage(Model model){
        return "site/login";
    }

3.处理下site下的login页面

就是加入Thymeleaf中的东西。。

三、 会话管理

HTTP的基本性质

  • HTTP是简单的
  • HTTP是可扩展的
  • HTTP是无状态的,有会话的

Cookie

  • 是服务器发送到浏览器,并保存在浏览器端的一小块数据。
  • 浏览器下次访问该服务器时,会自动携带块该数据,将其发送给服务器。

Session

  • 是JavaEE的标准,用于在服务端记录客户端信息。
  • 数据存放在服务端更加安全,但是也会增加服务端的内存压力。
  • 服务器分布式部署的时候存放session并没有十分完美的解决方案,所以一般我们都把数据存放进数据库中(redis)解决此问题。

四、生成验证码

Kaptcha

  • 导入 jar 包
  • 编写 Kaptcha 配置类
  • 生成随机字符、生成图片

个人想法:

参考手册

Kaptcha核心接口

image

默认实现类

image

1.导包

<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

2.配置

因为并没有整合进spring所以我们需要自己做整合,写一个配置类

@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","0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
        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;
    }

3.LoginController增加新方法

@Autowired
    private Producer kaptchaProducer;
    @RequestMapping(path="/kaptcha",method = RequestMethod.GET)
    public void getKaptcha(HttpServletResponse response, HttpSession session){
        //生成验证码
        String text = kaptchaProducer.createText();
        BufferedImage image = kaptchaProducer.createImage(text);
        //将验证码存入session
        session.setAttribute("kaptcha",text);
        //将图片输出给浏览器
        response.setContentType("image/png");
        try {
            ServletOutputStream outputStream = response.getOutputStream();
            ImageIO.write(image,"png",outputStream);
        } catch (IOException e) {
            logger.error("响应验证码获取失败:"+e.getMessage());
        }
    }

4.修改login.html

imageimage

五、开发登陆、退出功能

  • 访问登录页面

    • 点击顶部区域内的链接,打开登录页面。
  • 登录

    • 验证账号、密码、验证码。
    • 成功时,生成登录凭证,发放给客户端。
    • 失败时,跳转回登录页。
  • 退出

    • 将登录凭证修改为失效状态。
    • 跳转至网站首页。

登陆凭证相关

现在先存到数据库中

image

访问登录页面

上边已经完成

登陆

1.写dao层LoginTicketMapper类

@Repository
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);
}

2.写service层

@Service
public class LoginService {
    @Autowired
    LoginTicketMapper loginTicketMapper;
    @Autowired
    UserMapper userMapper;
    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;
        }else 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){
            map.put("usernameMsg","该账号未激活");
            return map;
        }
        //验证密码
        password = CommunityUtil.md5(password+user.getSalt());
        if(!password.equals(user.getPassword())){
            map.put("passwordMsg","密码不正确");
            return map;
        }
        //生成登陆凭证
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(user.getId());
        loginTicket.setTicket(CommunityUtil.generateUUID());
        loginTicket.setStatus(user.getStatus());
        loginTicket.setExpired(new Date(System.currentTimeMillis()+expiredSeconds*1000));
        loginTicketMapper.insertLoginTicket(loginTicket);
        map.put("loginTicket",loginTicket.getTicket());
        return map;
    }
}

3.写controller层

    @Autowired
    LoginService loginService;
    @Value("${server.servlet.context-path}")
    String context_path;
    //用于处理表单数据
    @RequestMapping(value = "/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");
        //检查验证码 , 业务层只处理业务逻辑 这种验证码校验可在这里直接做
        if(StringUtils.isBlank(kaptcha)||StringUtils.isBlank(code)||!StringUtils.equalsIgnoreCase(code,kaptcha)){
            model.addAttribute("codeMsg","验证码不正确");
            return "site/login";
        }
        //检查账号,密码
        int expiredSeconds = rememberMe?REMEMBERME_SECONDS:DEFAULT_SECONDS;
        Map<String, Object> msg = loginService.login(username, password, expiredSeconds);
        if(msg.containsKey("loginTicket")){
            Cookie cookie = new Cookie("ticket",(String)msg.get("loginTicket"));
            cookie.setPath(context_path);
            cookie.setMaxAge(expiredSeconds);
            response.addCookie(cookie);
            //重定向到首页
            return "redirect:/index";
        }else{
            model.addAttribute("usernameMsg",msg.get("usernameMsg"));
            model.addAttribute("passwordMsg",msg.get("passwordMsg"));
            return "site/login";
        }
    }

4.login.html页面的更改

在这里插入图片描述

退出

1.将登录凭证修改为失效状态

在LoginService类中加一个方法

public void logout(String ticket){
        if(StringUtils.isBlank(ticket)){
            return;
        }
        //1表示无效
        loginTicketMapper.updateStatus(ticket,1);
    }

2.controller层增加一个跳转方法

@RequestMapping(path="/logout",method = RequestMethod.GET)
    public String logout(@CookieValue("ticket") String ticket){
        loginService.logout(ticket);
        return "redirect:/login";
    }

3.修改index页面

image

自我思考:这样搞每次登陆都会在login_ticket表中增加一条登陆记录啊,不知道后续功能咋实现先把问题搁这。

六、显示登陆信息

  • 拦截器示例

    • 定义拦截器,实现HandlerInterceptor
    • 配置拦截器,为它指定拦截、排除的路径
  • 拦截器应用

    • 在请求开始时查询登录用户
    • 在本次请求中持有用户数据
    • 在模板视图上显示用户数据
    • 在请求结束时清理用户数据

拦截器示例

1.定义一个拦截器类在interceptor包下

问题:参数中的handler是啥?

@Component
public class AlphaInterceptor implements HandlerInterceptor {
    private Logger logger = LoggerFactory.getLogger(AlphaInterceptor.class);
    //在controller之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.debug("preHander:"+handler.toString());
        return true;
    }
    //在controller之后执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.debug("postHander:"+handler.toString());
    }
    //在模板引擎之后执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        logger.debug("afterCompletion:"+handler.toString());
    }
}

2.写一个配置类

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private AlphaInterceptor alphaInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(alphaInterceptor)    //不写下几行会拦截一切请求
        .excludePathPatterns("/**/*.css","/**/*.js","/**/*.jpg","/**/*.jpeg")  //这么写会排除拦截一些静态资源
        .addPathPatterns("/register","/login")      //这么写会增加拦截的路径
        ;
    }

3.访问/login页面查看日志

image

真相大白:handler是指拦截的目标

拦截器应用

业务逻辑

image.png

1.新建一个拦截器LoginTicketInterceptor

@Component
public class LoginTicketInterceptor implements HandlerInterceptor {
    @Autowired
    UserService userService;
    @Autowired
    HostHolder hostHolder;
    //可以简单理解为controller之前通过ticket取用户登录信息
    @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);
            //检查凭证是够有效
            if(loginTicket!=null&&loginTicket.getStatus()==0&&loginTicket.getExpired().after(new Date())){
                //根据凭证查询用户
                User user = userService.findUserById(loginTicket.getUserId());
                //在本次请求中持有用户
                hostHolder.setUser(user);
            }
        }
        return true;
    }
    //在controller之后把用户信息传递给模板
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        User user = hostHolder.getUser();
        if(user!=null && modelAndView!=null){
            modelAndView.addObject("loginUser",user);
        }
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        hostHolder.clear();
    }
}

util包下HostHolder类可以模拟服务端的Session功能,存放是否有登陆的User

防止多线程出事用ThreadLocal

@Component
public class HostHolder {
    private ThreadLocal<User> users = new ThreadLocal<User>();
    public void setUser(User user){
        users.set(user);
    }
    public User getUser(){
        return users.get();
    }
    public void clear(){
        users.remove();
    }
}

CookieUtil根据key取cookie因为常用所以封装成一个工具类

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

2.在配置中加入拦截器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private AlphaInterceptor alphaInterceptor;
    @Autowired
    private LoginTicketInterceptor loginTicketInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(alphaInterceptor)    //不写下几行会拦截一切请求
        .excludePathPatterns("/**/*.css","/**/*.js","/**/*.jpg","/**/*.jpeg")  //这么写会排除拦截一些静态资源
        .addPathPatterns("/register","/login")      //这么写会增加拦截的路径
        ;
        registry.addInterceptor(loginTicketInterceptor)
                .excludePathPatterns("/**/*.css","/**/*.js","/**/*.jpg","/**/*.jpeg");
    }
}

3.模板中头部的处理

image

七、账号设置

  • 上传文件

    • 请求:必须是POST请求
    • 表单:enctype=“multipart/form-data”
    • Spring MVC:通过 MultipartFile 处理上传文件
  • 开发步骤

    • 访问账号设置页面
    • 上传头像
    • 获取头像

image.png

上传文件之访问账号设置页面

1.新建一个UserController类,增加一个方法,请求方式为get

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping(path="/setting",method = RequestMethod.GET)
    public String getSettingPage(){
        return "/site/setting";
    }
}

2.修改site/setting.html文件改成Thymeleaf的形式

image

3.修改index.html

image

上传头像和获取头像

1.在UserController类中完成

multipartFile 类可以控制从前台传入的文件。

@Controller
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserService userService;
    @Value("${server.servlet.context-path}")
    private String contextPath;
    @Value("${community.path.domain}")
    private String domain;
    @Value("${community.path.upload}")
    private String uploadPath;
    @Autowired
    private HostHolder hostHolder;
    private Logger logger = LoggerFactory.getLogger(UserController.class);
    @RequestMapping(path="/setting",method = RequestMethod.GET)
    public String getSettingPage(){
        return "/site/setting";
    }
    /**
     * 把文件存到服务器中
     * @param multipartFile
     * @param model
     * @return
     */
    @RequestMapping(path="/upload",method = RequestMethod.POST)
    public String uploadHeader(MultipartFile multipartFile, Model model){
        if(multipartFile==null){
            model.addAttribute("error","您没有上传任何图片");
            return "/site/setting";
        }
        String fileName = multipartFile.getOriginalFilename();
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        //System.out.println(suffix.equals(".jpg"));
        if(StringUtils.isBlank(suffix)||!(suffix.equals(".png")||suffix.equals(".jpg"))){
            model.addAttribute("error","文件格式错误,请重新上传");
            return "/site/setting";
        }
        fileName = CommunityUtil.generateUUID()+suffix;
        File dst = new File(uploadPath+"/"+fileName);
        try {
            multipartFile.transferTo(dst);
        } catch (IOException e) {
            logger.error("上传失败:"+e.getMessage());
            throw new RuntimeException("服务器发生失败,上传出现异常"+e);
        }
        //更新headerUrl这里规定格式张这样
        //http://localhost:8080/community/user/header/filename
        String headerUrl = domain+contextPath+"/user/header/"+fileName;
        User user = hostHolder.getUser();
        userService.uploadHeader(user.getId(),headerUrl);
        return "redirect:/user/setting";
    }
    /**
     * 当用户读取headerUrl时从本地读取后返回
     * @param filename
     * @param response
     */
    @RequestMapping(path = "header/{filename}",method = RequestMethod.GET)
    public void getImg(@PathVariable("filename")String filename, HttpServletResponse response){
        //服务器存放地址
        filename = uploadPath+"/"+filename;
        try (ServletOutputStream os = response.getOutputStream();
             InputStream is = new FileInputStream(filename);)
        {
            int len = 0;
            byte[] buffer = new byte[1024];
            while((len=is.read(buffer))!=-1){
                os.write(buffer,0,len);
            }
        } catch (IOException e) {
            logger.error("读取文件失败:"+e.getMessage());
        }
    }
}

2.修改setting.html文件

image

密码修改

1.增加UserService中的方法

public int updatePassword(int userId,String password){
        return userMapper.updatePassword(userId,password);
    }

2.增加UserController中的方法

@RequestMapping(path="password",method = RequestMethod.POST)
    public String changePassword(Model model,
            String oldPassword,
            String newPassword,
            String confirmPassword){
        if(StringUtils.isBlank(oldPassword)){
            model.addAttribute("olderror","请输入原密码");
            return "/site/setting";
        }
        if(StringUtils.isBlank(newPassword)){
            model.addAttribute("newerror","请输入新密码");
            return  "/site/setting";
        }
        if(StringUtils.isBlank(confirmPassword)){
            model.addAttribute("confirmerror","请输入确认密码");
            return "/site/setting";
        }
        if(!newPassword.equals(confirmPassword)){
            model.addAttribute("confirmerror","两次密码不一致请重新输入");
            return "/site/setting";
        }
        User user = hostHolder.getUser();
        String password = user.getPassword();
        if(!CommunityUtil.md5(oldPassword+user.getSalt()).equals(password)){
            model.addAttribute("olderror","原密码输入错误,请重新输入");
            return "/site/setting";
        }
        newPassword = CommunityUtil.md5(newPassword+user.getSalt());
        userService.updatePassword(user.getId(),newPassword);
        return "redirect:/logout";
    }

3.修改setting.html

image

八、检查登陆状态

  • 使用拦截器

    • 在方法前标注自定义注解
    • 拦截所有请求,只处理带有该注解的方法
  • 自定义注解

    • 常用的元注解:
      @Target、@Retention、@Document、@Inherited
    • 如何读取注解:
      Method.getDeclaredAnnotations()
      Method.getAnnotation(Class annotationClass)

一个问题

虽然没有登录用户看不到image

但是如果用户知道url也可直接到相关界面

image

自定义注解

image.png

常用的元注解

  • @Target:声明自定义注解可以作用在什么类型上,类上方法上等
  • @Retention:声明自定义注解保留的时间
  • @Document:声明自定义注解生成文档的时候要不要把注解也带上去
  • @Inherited:用于继承的,一个子类继承父类,父类有注解,子类要不要继承这个注解

读取注解的方法

  • Method.getDeclaredAnnotations()
  • Method.getAnnotation(Class< T > annotationClass)

使用拦截器解决问题

1.定义自定义注解,主要作用就是标识

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}

2.对需要拦截的方法上加此注解

image

3.新建拦截器进行处理

@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 = handlerMethod.getMethod();
            LoginRequired annotation = method.getAnnotation(LoginRequired.class);
            if(annotation!=null&&hostHolder.getUser()==null){
                response.sendRedirect(request.getContextPath()+"/login");
                //false表示不让继续执行
                return false;
            }
        }
        return true;
    }
}

4.WebMvcConfig类上加上这个拦截器

image

posted @ 2021-03-22 19:13  WonderC  阅读(1985)  评论(2编辑  收藏  举报