牛客网项目第二章学习笔记

一、发送邮件

知识点:

  • 邮箱设置
    • 启用客户端SMTP服务
  • Spring Email
    • 导入 jar 包
    • 邮箱参数配置
  • 使用 JavaMailSender 发送邮件
    • 模板引擎
    • 使用 Thymeleaf 发送 HTML 邮件

1.启用客户端SMTP服务
  课程中选择的新浪,我选择的是网易邮箱。将服务开启。
图片说明

2.Spring Email
  导入相关依赖包。mavenrepository官网,用来查找相关包。在pom.xml添加配置。添加如下代码即可。(找包的意义是找到官方给出的配置代码,如果记得模板,也可以自己改,而不用上官网找包)

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
            <version>2.1.5.RELEASE</version>
        </dependency>

3.邮箱参数设置

# MailProperties
spring.mail.host=smtp.163.com
spring.mail.port=465
spring.mail.username=填邮箱名(用来给新注册用户发邮件的邮箱,根据个人情况修改)
spring.mail.password=密码(发邮件的邮箱的密码,有同学是授权码而不是登录密码)
spring.mail.protocol=smtps
spring.mail.properties.mail.smtp.ssl.enable=true

  到底是登录密码还是授权码得看具体使用邮箱的政策。比如新浪的就是需要授权码,而网易仍然是登录密码
4.使用JavaMailSender发送邮件
新建一个发邮件的工具类

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

在发邮箱时,使用一个模板,注册用户都用这个模板发。该模板放在resources下的templates中比较合适

<!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>

接下来就是写测试代码了。在专门的Test包下进行测试代码的书写。

  @Test
    public void testHtmlMail() {
        Context context = new Context();
        context.setVariable("username", "sunday");//设置收件人的用户名,注意上面的模板
        //找到模板路径
        String content = templateEngine.process("/mail/demo", context);
        //System.out.println(content);
        mailClient.sendMail("****@qq.com", "HTML", content);
    }

测试成功!


二、开发注册功能

课程内容:

  • 访问注册页面
    • 点击顶部区域内的链接,打开注册页面。
  • 提交注册数据
    • 通过表单提交数据。
    • 服务端验证账号是否已存在、邮箱是否已注册。
    • 服务端发送激活邮件。
  • 激活注册账号
    • 点击邮件中的链接,访问服务端的激活服务

1.从dao层开始开发

  首先是改静态模板,利用Thymeleaf将index.html和register.html中的静态改为动态,这里改动的是首页和注册两个页面。

2.提交注册数据

  首先导入一个常用的包 commons lang。主要是字符串判空等功能。在pom.xml添加依赖。

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

写一个生成随机字符串的类。因为开发当中总会涉及到随机,比如我有一群头像,注册用户的头像就是随机选取的。那么封装一个随机生成的类,便于复用。

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());
    }
}

3.service层

注册方法的核心代码,dao层之前实现了(user和userMapper),所以直接进入service层的开发。里面会调用dao层去查询数据,如是否用户名是否已存在。

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;
        }
        // 验证账号 userMapper是dao层接口
        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));//取5个随机生成的字符
        //加入到密码中,再一起加密
        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);

        // 激活邮件(上节课是在test中做测试,现在是在service层实现发邮件功能)
        Context context = new Context();
        context.setVariable("email", user.getEmail());
        // http://localhost:8080/community/activation/101/code
        //满足上面写法的url才允许激活。101是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;
    }

上面的激活邮件,是有一个激活模板的。activation.html,将对应的静态改为动态。

4.controller层开发

a.对应于注册功能的controller层需要添加的代码。

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

b.注册成功后,页面的处理
这里设计了一个中间页面site/operate-result.html过渡,然后可以跳转到首页进行登录。
c.注册失败时,页面的处理
重新回到register.html页面

5.激活注册账号

a.创建一个激活的接口,里面设置几个常量。并让UserService实现此接口

public interface CommunityContant {
    //激活成功
    int ACTIVATION_SUCCESS = 0;
    //重复激活
    int ACTIVATION_REPEAT = 1;
    //激活失败
    int ACTIVATION_FAILURE = 2;
}

b.serveice层实现接口后,传递给dao层(user)查询。

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;
        }
 }

c.controller层增加方法。查看是否激活成功,然后向前端返回结果

    @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";
    }

d.处理相关的前端页面。在activation页面进行激活,site/operate-result.html这个过渡页面告诉激活是否成功(模板页面,填充及激活成功或注册成功)。然后跳转至登录页面login。激活失败跳转到index主页。


三、 会话管理

课程内容:简介关于http回话的相关内容,为后续实现登录功能铺垫

  • HTTP的基本性质
    • HTTP是简单的
    • HTTP是可扩展的
    • HTTP是无状态的,有会话的
  • Cookie
    • 是服务器发送到浏览器,并保存在浏览器端的一小块数据。
    • 浏览器下次访问该服务器时,会自动携带块该数据,将其发送给服务器。
  • Session
    • 是JavaEE的标准,用于在服务端记录客户端信息。
    • 数据存放在服务端更加安全,但是也会增加服务端的内存压力。
    • 服务器分布式部署的时候存放session并没有十分完美的解决方案,所以一般我们都把数据存放进数据库中(redis)解决此问题。

四、生成验证码

课程内容:

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

Kaptcha是一个可高度配置的实用验证码生成工具。

1.pom.xml导入相关依赖

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

2.进行相关配置。

可以配置一个xml文件(spring-context-kaptcha.xml)。或者写一个配置类。这里是选择写配置类。

@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.Controller层增加新方法

  这个功能不涉及到数据库,所以不用修改dao和service层,在controller层修改即可。注意这个验证码要放在服务端的,以便用来验证用户输入的验证码,为了安全,因此使用session。

    @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)

验证码是动态了,因此要做出相关修改。(使用js和Thymeleaf)

五、开发登陆、退出功能

课程内容:

  • 访问登录页面
    • 点击顶部区域内的链接,打开登录页面。
  • 登录
    • 验证账号、密码、验证码。
    • 成功时,生成登录凭证,发放给客户端。
    • 失败时,跳转回登录页。
  • 退出
    • 将登录凭证修改为失效状态。
    • 跳转至网站首页。

1.登录凭证暂时放数据库中,后面将进行重构。

数据库建表,expired是过期时间。最重要的是凭证ticket

CREATE TABLE `login_ticket` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `ticket` varchar(45) NOT NULL,
  `status` int(11) DEFAULT '0' COMMENT '0-有效; 1-无效;',
  `expired` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `index_ticket` (`ticket`(20))
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;

下面是表的样子。

2.dao层开发

  先写一个基础类LoginTicket,再写一个配合使用的LoginTicketMapper的接口。二者共同完成dao层。(Mapper接口的注解和Mapper.xml中都是可以写sql语句的)基础类不实现业务。

//在Mapper接口的注解中写sql语句
@@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({
            "<script>",
            "update login_ticket set status=#{status} where ticket=#{ticket} ",
            "<if test=\"ticket!=null\"> ",
            "and 1=1 ",
            "</if>",
            "</script>"
    })
    int updateStatus(@Param("ticket")String ticket, @Param("status")int status);
}

3.service层开发登录

登录的过程可以参考注册的相关流程。(这里和之前都省略了service层类名,只记录核心的方法,可以放在一个service类也可以)

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) {
            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);
        loginTicket.setExpired(new Date
                        (System.currentTimeMillis() + expiredSeconds * 1000));
        loginTicketMapper.insertLoginTicket(loginTicket);

        map.put("ticket", loginTicket.getTicket());
        return map;
    }

4.controller层开发

  

全部评论

相关推荐

08-13 11:13
吉林大学 Java
等待中
投递荣耀等公司8个岗位
点赞 评论 收藏
分享
Lorn的意义:你这种岗位在中国现在要么牛马天天加班,要么关系户进去好吃好喝,8年时间,真的天翻地覆了,对于资本来说你就说一头体力更好的牛马,哎,退伍没有包分配你真的亏了。
点赞 评论 收藏
分享
投递OPPO等公司8个岗位
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务