3、springmvc入门

3.1、原始的方式处理请求与响应

  1. @RequestMapping("/http")
  2. public void http(HttpServletRequest request, HttpServletResponse response){
  3. //1、获取请求数据
  4. //获取请求方法
  5. System.out.println(request.getMethod());
  6. //获取请求路径
  7. System.out.println(request.getServletPath());
  8. //获取请求体
  9. Enumeration<String> names = request.getHeaderNames();
  10. while(names.hasMoreElements()){
  11. String name = names.nextElement();
  12. String value=request.getHeader(name);
  13. System.out.println(name+":"+value);
  14. }
  15. //
  16. System.out.println(request.getParameter("code"));
  17. //2、返回相应数据
  18. response.setContentType("text/html;charset=utf-8");
  19. try (PrintWriter writer = response.getWriter();){
  20. writer.write("<h1>牛客网</h1>");
  21. } catch (IOException e) {
  22. e.printStackTrace();
  23. }
  24. }

3.2、GET请求->@RequestParam

  1. //students?current=1&limit=5
  2. @GetMapping("/students")
  3. @ResponseBody
  4. public String selectStu(
  5. @RequestParam(name = "current",required = false,defaultValue = "1")int current,
  6. @RequestParam(name = "limit",required = false,defaultValue = "10")int limit
  7. ){
  8. System.out.println(current);
  9. System.out.println(limit);
  10. return "student";
  11. }

3.3、GET请求->@PathVariable

  1. //students/id
  2. @GetMapping("/students/{id}")
  3. @ResponseBody
  4. public String selectStu(@PathVariable("id") int id){
  5. System.out.println(id);
  6. return "id";
  7. }

3.4、表单提交数据

访问静态资源路径:localhost:8080/community/static/student.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>增加学生</title>
  6. </head>
  7. <body>
  8. <form method="post" action="/community/student">
  9. <p>
  10. 姓名:<input type="text" name="name">
  11. </p>
  12. <p>
  13. 年龄:<input type="text" name="age">
  14. </p>
  15. <p>
  16. <input type="submit" value="保存">
  17. </p>
  18. </form>
  19. </body>
  20. </html>
  1. @PostMapping("/student")
  2. @ResponseBody
  3. public String saveStudent(String name,int age){
  4. System.out.println(name);
  5. System.out.println(age);
  6. return "success";
  7. }

3.5、相应html方式一ModelAndView

  1. @GetMapping("/teacher")
  2. public ModelAndView getTeacher(){
  3. ModelAndView mav = new ModelAndView();
  4. mav.addObject("name","李四");
  5. mav.addObject("age",15);
  6. //该路径相当于html/demo/teacher
  7. mav.setViewName("/demo/teaacher");
  8. return mav;
  9. }
  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <p th:text="${name}"></p>
  9. <p th:text="${age}"></p>
  10. </body>
  11. </html>

3.6、相应html方式二Model

  1. @GetMapping("/school")
  2. public String getSchool(Model model){
  3. model.addAttribute("name","五邑大学");
  4. model.addAttribute("age",60);
  5. return "demo/teacher";
  6. }

3.7、相应json字符串(异步请求)

java对象->json字符串->js对象

异步请求示例:
image.png

  1. @GetMapping("/emp")
  2. @ResponseBody
  3. public Map<String,Object> getEmp(){
  4. Map<String,Object> map=new HashMap<>();
  5. map.put("name","张三");
  6. map.put("age",18);
  7. map.put("salary",15000);
  8. return map;
  9. }
  1. @GetMapping("/emps")
  2. @ResponseBody
  3. public List<Map<String,Object>> getEmps(){
  4. List<Map<String,Object>> list=new ArrayList<>();
  5. Map<String,Object> map=new HashMap<>();
  6. map.put("name","张三");
  7. map.put("age",18);
  8. map.put("salary",15000);
  9. list.add(map);
  10. map.put("name","李四");
  11. map.put("age",19);
  12. map.put("salary",14000);
  13. list.add(map);
  14. map.put("name","王五");
  15. map.put("age",20);
  16. map.put("salary",13000);
  17. list.add(map);
  18. return list;
  19. }

4、mybatis入门

4.1、mybatis简介

MyBatis
• 核心组件

  • SqlSessionFactory:用于创建SqlSession的工厂类。
  • SqlSession:MyBatis的核心组件,用于向数据库执行SQL。
  • 主配置文件:XML配置文件,可以对MyBatis的底层行为做出详细的配置。
  • Mapper接口:就是DAO接口,在MyBatis中习惯性的称之为Mapper。
  • Mapper映射器:用于编写SQL,并将SQL和实体类映射的组件,采用XML、注解均可实现。

• 示例

  • 使用MyBatis对用户表进行CRUD操作。
    http://www.mybatis.org/mybatis-3
    http://www.mybatis.org/spring

    4.2、导入mysql、mybatis依赖

    1. <dependency>
    2. <groupId>mysql</groupId>
    3. <artifactId>mysql-connector-java</artifactId>
    4. </dependency>
    5. <dependency>
    6. <groupId>org.mybatis.spring.boot</groupId>
    7. <artifactId>mybatis-spring-boot-starter</artifactId>
    8. <version>1.3.0</version>
    9. </dependency>

    4.3、application.properties配置

    ```properties

    DataSourceProperties

    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/community?characterEncoding=utf-8&sueSSL=false&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=123456

    连接池的配置

    spring.datasource.type=com.zaxxer.hikari.HikariDataSource

    最大连接数

    spring.datasource.hikari.maximum-pool-size=15

    最小空闲连接

    spring.datasource.hikari.minimum-idle=5

    超时时间30000毫秒

    spring.datasource.hikari.idle-timeout=30000

MybatisProperties

映射文件存放位置

mybatis.mapper-locations=classpath:mapper/*.xml

resultType可以省略全限定名

mybatis.type-aliases-package=com.guang.community.entity

自动生成组件

mybatis.configuration.use-generated-keys=true

数据库字段与实体类字段匹配,例如create_time与createTime的转换

mybatis.configuration.map-underscore-to-camel-case=true

  1. <a name="LGHy4"></a>
  2. ## 4.4、mybatis代码实现
  3. <a name="tnR8y"></a>
  4. ### 4.4.1、创建实体类
  5. ```java
  6. @Data
  7. @AllArgsConstructor
  8. @NoArgsConstructor
  9. @ToString
  10. public class User {
  11. private int id;
  12. private String userName;
  13. private String password;
  14. private String salt;
  15. private String email;
  16. private int type;
  17. private int status;
  18. private String activationCode;
  19. private String headerUrl;
  20. private Date createTime;
  21. }

4.4.2、创建实体类对应的mapper接口

  1. @Mapper
  2. public interface UserMapper {
  3. User selectUserById(int id);
  4. User selectUserByName(String name);
  5. User selectUserByEmail(String email);
  6. int insertUser(User user);
  7. int updateUserStatus(int id, int status);
  8. int updateUserHeader(int id, String headerUrl);
  9. int updateUserPassword(int id, String password);
  10. }

4.4.3、创建对应的mapper.xml文件

1、引入mapper头部

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.guang.community.dao.UserMapper">
  6. </mapper>
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.guang.community.dao.UserMapper">
  6. <sql id="selectFields">
  7. id,username,password,salt,email,type,status,activation_code,header_url,create_time
  8. </sql>
  9. <sql id="insertFields">
  10. username,password,salt,email,type,status,activation_code,header_url,create_time
  11. </sql>
  12. <select id="selectUserById" resultType="User">
  13. select <include refid="selectFields"></include>
  14. from user
  15. where id=#{id}
  16. </select>
  17. <select id="selectUserByName" resultType="User">
  18. select <include refid="selectFields"></include>
  19. from user
  20. where username=#{userName}
  21. </select>
  22. <select id="selectUserByEmail" resultType="User">
  23. select <include refid="selectFields"></include>
  24. from user
  25. where email=#{email}
  26. </select>
  27. <!-- keyProperty="id"声明主键是什么-->
  28. <insert id="insertUser" parameterType="User" keyProperty="id">
  29. insert into user (<include refid="insertFields"></include>)
  30. values (#{userName},#{password},#{salt},#{email},#{type},#{status},#{activationCode},#{headerUrl},#{createTime})
  31. </insert>
  32. <!-- 基本数据类型返回值不需要生命-->
  33. <update id="updateUserStatus">
  34. update user set status=#{status} where id=#{id}
  35. </update>
  36. <update id="updateUserHeader">
  37. update user set header_url=#{headerUrl} where id=#{id}
  38. </update>
  39. <update id="updateUserPassword">
  40. update user set password=#{password} where id=#{id}
  41. </update>
  42. </mapper>

4.5、设置日志级别便于排查sql错误

  1. #logger设置全局日志级别为debug方便排查错误
  2. logging.level.com.guang.community=debug

5、开发社区首页

5.1、sql脚本创建数据表并导入数据

5.2、实体类DiscussPost

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. @ToString
  5. public class DiscussPost {
  6. private int id;
  7. private int userId;
  8. private String title;
  9. private String content;
  10. private int type;//0普通1置顶
  11. private int status;//0正常1精华2拉黑
  12. private Date createTime;
  13. private int commentCount;
  14. private double score;
  15. }

5.3、DiscussMapper

  1. @Mapper
  2. public interface DiscussPostMapper {
  3. /**
  4. *
  5. * @param userId 用于个人主页查询个人全部帖子
  6. * @param offset 起始页数
  7. * @param limit 每页展示的数据条数
  8. * @return
  9. */
  10. public List<DiscussPost> selectDiscussPosts(int userId,int offset,int limit);
  11. /**
  12. * @Param:用于给参数取别名,当需要动态拼接sql且参数只有一个时,必须给参数取别名
  13. * @param userId
  14. * @return
  15. */
  16. public int selectDiscussPostRowsTotal(@Param("userId") int userId);
  17. }

5.4、discusspost-mapper.xml

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.guang.community.dao.DiscussPostMapper">
  6. <sql id="selectFields">
  7. id,user_id,title,content,type,status,create_time,comment_count,score
  8. </sql>
  9. <select id="selectDiscussPosts" resultType="DiscussPost">
  10. select
  11. <include refid="selectFields"></include>
  12. from discuss_post
  13. where status !=2
  14. <if test="userId!=0">
  15. and user_id=#{userId}
  16. </if>
  17. order by type desc ,create_time desc
  18. limit #{offset},#{limit}
  19. </select>
  20. <select id="selectDiscussPostRowsTotal">
  21. select count(id)
  22. from discuss_post
  23. where status !=2
  24. <if test="userId!=0">
  25. and user_id=#{userId}
  26. </if>
  27. </select>
  28. </mapper>

5.5、DiscussPostService

  1. @Service
  2. public class DiscussPostService {
  3. @Autowired
  4. private DiscussPostMapper discussPostMapper;
  5. public List<DiscussPost> selectDiscussPosts(int userId,int offset,int limit){
  6. return discussPostMapper.selectDiscussPosts(userId,offset,limit);
  7. }
  8. public int selectDiscussPostRowsTotal(int userId){
  9. return discussPostMapper.selectDiscussPostRowsTotal(userId);
  10. }
  11. }

5.6、UserService

  1. @Service
  2. public class UserService {
  3. @Autowired
  4. private UserMapper userMapper;
  5. public User selectUserById(int id){
  6. return userMapper.selectUserById(id);
  7. }
  8. }

5.7、DiscussPostController

  1. @Controller
  2. public class DiscussPostController {
  3. @Autowired
  4. private DiscussPostService discussPostService;
  5. @Autowired
  6. private UserService userService;
  7. @GetMapping("/index")
  8. public String getIndexPage(Model model, Page page) {
  9. // 方法调用前,SpringMVC会自动实例化Model和Page,并将Page注入Model.
  10. // 所以,在thymeleaf中可以直接访问Page对象中的数据.
  11. page.setRows(discussPostService.selectDiscussPostRowsTotal(0));
  12. page.setPath("/index");
  13. List<DiscussPost> list = discussPostService.selectDiscussPosts(0, page.getOffset(), page.getLimit());
  14. List<Map<String, Object>> discussPosts = new ArrayList<>();
  15. if (list != null) {
  16. for (DiscussPost post : list) {
  17. Map<String, Object> map = new HashMap<>();
  18. map.put("post", post);
  19. User user = userService.selectUserById(post.getUserId());
  20. map.put("user", user);
  21. discussPosts.add(map);
  22. }
  23. }
  24. model.addAttribute("discussPosts", discussPosts);
  25. return "/index";
  26. }
  27. }

5.8、分页封装类Page

  1. /**
  2. * 封装分页相关的信息.
  3. */
  4. public class Page {
  5. // 当前页码
  6. private int current = 1;
  7. // 显示上限
  8. private int limit = 10;
  9. // 数据总数(用于计算总页数)
  10. private int rows;
  11. // 查询路径(用于复用分页链接)
  12. private String path;
  13. public int getCurrent() {
  14. return current;
  15. }
  16. public void setCurrent(int current) {
  17. if (current >= 1) {
  18. this.current = current;
  19. }
  20. }
  21. public int getLimit() {
  22. return limit;
  23. }
  24. public void setLimit(int limit) {
  25. if (limit >= 1 && limit <= 100) {
  26. this.limit = limit;
  27. }
  28. }
  29. public int getRows() {
  30. return rows;
  31. }
  32. public void setRows(int rows) {
  33. if (rows >= 0) {
  34. this.rows = rows;
  35. }
  36. }
  37. public String getPath() {
  38. return path;
  39. }
  40. public void setPath(String path) {
  41. this.path = path;
  42. }
  43. /**
  44. * 获取当前页的起始行
  45. *
  46. * @return
  47. */
  48. public int getOffset() {
  49. // current * limit - limit
  50. return (current - 1) * limit;
  51. }
  52. /**
  53. * 获取总页数
  54. *
  55. * @return
  56. */
  57. public int getTotal() {
  58. // rows / limit [+1]
  59. if (rows % limit == 0) {
  60. return rows / limit;
  61. } else {
  62. return rows / limit + 1;
  63. }
  64. }
  65. /**
  66. * 获取起始页码
  67. *
  68. * @return
  69. */
  70. public int getFrom() {
  71. int from = current - 2;
  72. return from < 1 ? 1 : from;
  73. }
  74. /**
  75. * 获取结束页码
  76. *
  77. * @return
  78. */
  79. public int getTo() {
  80. int to = current + 2;
  81. int total = getTotal();
  82. return to > total ? total : to;
  83. }
  84. }

5.9、index.html

  1. <!-- 分页 -->
  2. <nav class="mt-5" th:if="${page.rows>0}">
  3. <ul class="pagination justify-content-center">
  4. <li class="page-item">
  5. <a class="page-link" th:href="@{${page.path}(current=1)}">首页</a>
  6. </li>
  7. <li th:class="|page-item ${page.current==1?'disabled':''}|">
  8. <a class="page-link" th:href="@{${page.path}(current=${(page.current)-1})}">上一页</a></li>
  9. <li th:class="|page-item ${i==page.current?'active':''}|" th:each="i:${#numbers.sequence(page.from,page.to)}">
  10. <a class="page-link" href="#" th:text="${i}">1</a>
  11. </li>
  12. <li th:class="|page-item ${page.current==page.total?'disabled':''}|">
  13. <a class="page-link" th:href="@{${page.path}(current=${page.current+1})}">下一页</a>
  14. </li>
  15. <li class="page-item">
  16. <a class="page-link" th:href="@{${page.path}(current=${page.total})}">末页</a>
  17. </li>
  18. </ul>
  19. </nav>

6、git、github、smartGit代码管理

6.1、git配置

  1. 1、下载安装好git
  2. git-scm.com/download
  3. 2、配置git的全局用户名和邮箱
  4. git config --list 查看当前配置信息
  5. git config --grobal user.name "lirongguang" 设置用户名
  6. git config --grobal user.email "758679586@qq.com"
  7. 3、创建ssh密钥并且完成github功能的第二步
  8. ssh-keygen -t rsa -C "758679586@qq.com" 回车.....
  9. 4、cd 到项目的目录下。例如demo01项目
  10. git init 该命令后该项目目录下会产生.git文件
  11. 4、将生成的ssh密钥到远程仓库中配置
  12. git remote add origin 远程仓库链接+.git 远程仓库的别名为origin

6.2、github配置

  1. 1、注册并登录github账号
  2. 2、将git生成的ssh密钥在github的settings->SSh and GPG keys中添加
  3. 复制ssh密钥的的命令:clip < ~/.ssh/id_rsa.pub
  4. 3、创建仓库并且将其ssh地址复制完成git功能的第四步

image.png

7、发送邮件功能

7.1、新浪邮箱开启smtp服务

image.png

7.2、maven仓库搜索spring mail并导入pom.xml

image.png

7.3、application.properties配置

  1. #mailProperties
  2. spring.mail.host=smtp.sina.com
  3. spring.mail.port=465
  4. spring.mail.username=lirong_guang@sina.com
  5. #该项密码为smtp服务的授权码
  6. spring.mail.password=7f3585c4744806e1
  7. spring.mail.protocol=smtps
  8. spring.mail.properties.mail.smtp.ssl.enable=true

7.4、创建邮件发送client

  1. private static final Logger logger = LoggerFactory.getLogger(MailClient.class);
  2. @Autowired
  3. private JavaMailSender mailSender;
  4. @Value("${spring.mail.username}")
  5. private String from;
  6. public void sendMail(String to, String subject, String text) {
  7. try {
  8. MimeMessage message = mailSender.createMimeMessage();
  9. MimeMessageHelper helper = new MimeMessageHelper(message);
  10. helper.setFrom(from);
  11. helper.setTo(to);
  12. helper.setSubject(subject);
  13. //支持发生html模式
  14. helper.setText(text,true);
  15. mailSender.send(helper.getMimeMessage());
  16. } catch (MessagingException e) {
  17. logger.error("邮件发送失败:"+e.getMessage());
  18. }
  19. }

7.5、创建html模板

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>邮件示例</title>
  6. </head>
  7. <body>
  8. <p>欢迎你, <span style="color:red;" th:text="${username}"></span>!</p>
  9. </body>
  10. </html>

7.6、测试

  1. @Autowired
  2. private TemplateEngine templateEngine;
  3. @Test
  4. public void testHtmlMail() {
  5. Context context = new Context();
  6. context.setVariable("username", "sunday");
  7. String content = templateEngine.process("/mail/demo", context);
  8. System.out.println(content);
  9. mailClient.sendMail("lihonghe@nowcoder.com", "HTML", content);
  10. }

8、开发注册功能

8.1、commons lang数据校验

  1. <dependency>
  2. <groupId>org.apache.commons</groupId>
  3. <artifactId>commons-lang3</artifactId>
  4. <version>3.12.0</version>
  5. </dependency>

8.2、配置域名

  1. #community配置域名
  2. community.path.domain=http://localhost:8080

8.3、注册功能工具类

  1. public class CommunityUtils {
  2. //生成随机字符串
  3. public static String getUUID(){
  4. return UUID.randomUUID().toString().replaceAll("-","");
  5. }
  6. //md5加密
  7. public static String md5(String key){
  8. if(StringUtils.isEmpty(key)){
  9. return null;
  10. }
  11. return DigestUtils.md5DigestAsHex(key.getBytes());
  12. }
  13. }

8.4、注册工具类接口

  1. public interface CommunityConstant {
  2. /**
  3. * 激活成功
  4. */
  5. int ACTIVATION_SUCCESS = 0;
  6. /**
  7. * 重复激活
  8. */
  9. int ACTIVATION_REPEAT = 1;
  10. /**
  11. * 激活失败
  12. */
  13. int ACTIVATION_FAILURE = 2;
  14. }

8.5、UserService注册业务层

  1. @Service
  2. public class UserService implements CommunityConstant {
  3. @Autowired
  4. private UserMapper userMapper;
  5. @Autowired
  6. private MailClient mailClient;
  7. @Autowired
  8. private TemplateEngine templateEngine;
  9. @Value("${community.path.domain}")
  10. private String domain;
  11. @Value("${server.servlet.context-path}")
  12. private String contextPath;
  13. public User selectUserById(int id) {
  14. return userMapper.selectUserById(id);
  15. }
  16. public Map<String, Object> register(User user) {
  17. Map<String, Object> map = new HashMap<>();
  18. // 空值处理
  19. if (user == null) {
  20. throw new IllegalArgumentException("参数不能为空!");
  21. }
  22. if (StringUtils.isBlank(user.getUsername())) {
  23. map.put("usernameMsg", "账号不能为空!");
  24. return map;
  25. }
  26. if (StringUtils.isBlank(user.getPassword())) {
  27. map.put("passwordMsg", "密码不能为空!");
  28. return map;
  29. }
  30. if (StringUtils.isBlank(user.getEmail())) {
  31. map.put("emailMsg", "邮箱不能为空!");
  32. return map;
  33. }
  34. // 验证账号
  35. User u = userMapper.selectUserByName(user.getUsername());
  36. if (u != null) {
  37. map.put("usernameMsg", "该账号已存在!");
  38. return map;
  39. }
  40. // 验证邮箱
  41. u = userMapper.selectUserByEmail(user.getEmail());
  42. if (u != null) {
  43. map.put("emailMsg", "该邮箱已被注册!");
  44. return map;
  45. }
  46. // 注册用户
  47. user.setSalt(CommunityUtil.getUUID().substring(0, 5));
  48. user.setPassword(CommunityUtil.md5(user.getPassword() + user.getSalt()));
  49. user.setType(0);
  50. user.setStatus(0);
  51. user.setActivationCode(CommunityUtil.getUUID());
  52. user.setHeaderUrl(String.format("http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));
  53. user.setCreateTime(new Date());
  54. userMapper.insertUser(user);
  55. // 激活邮件
  56. Context context = new Context();
  57. context.setVariable("email", user.getEmail());
  58. // http://localhost:8080/community/activation/101/code
  59. String url = domain + contextPath + "/activation/" + user.getId() + "/" + user.getActivationCode();
  60. context.setVariable("url", url);
  61. String content = templateEngine.process("/mail/activation", context);
  62. mailClient.sendMail(user.getEmail(), "激活账号", content);
  63. return map;
  64. }
  65. public int activation(int userId, String code) {
  66. User user = userMapper.selectUserById(userId);
  67. if (user.getStatus() == 1) {
  68. return ACTIVATION_REPEAT;
  69. } else if (user.getActivationCode().equals(code)) {
  70. userMapper.updateUserStatus(userId, 1);
  71. return ACTIVATION_SUCCESS;
  72. } else {
  73. return ACTIVATION_FAILURE;
  74. }
  75. }
  76. }

8.6、注册接口

  1. @Controller
  2. public class LoginController implements CommunityConstant {
  3. @Autowired
  4. private UserService userService;
  5. @GetMapping("/register")
  6. public String getRegisterPage() {
  7. return "/site/register";
  8. }
  9. @GetMapping("/login")
  10. public String getLoginPage() {
  11. return "/site/login";
  12. }
  13. @PostMapping("/register")
  14. public String register(Model model, User user) {
  15. Map<String, Object> map = userService.register(user);
  16. if (map == null || map.isEmpty()) {
  17. model.addAttribute("msg", "注册成功,我们已经向您的邮箱发送了一封激活邮件,请尽快激活!");
  18. model.addAttribute("target", "/index");
  19. return "/site/operate-result";
  20. } else {
  21. model.addAttribute("usernameMsg", map.get("usernameMsg"));
  22. model.addAttribute("passwordMsg", map.get("passwordMsg"));
  23. model.addAttribute("emailMsg", map.get("emailMsg"));
  24. return "/site/register";
  25. }
  26. }
  27. // http://localhost:8080/community/activation/101/code
  28. @GetMapping("/activation/{userId}/{code}")
  29. public String activation(Model model, @PathVariable("userId") int userId, @PathVariable("code") String code) {
  30. int result = userService.activation(userId, code);
  31. if (result == ACTIVATION_SUCCESS) {
  32. model.addAttribute("msg", "激活成功,您的账号已经可以正常使用了!");
  33. model.addAttribute("target", "/login");
  34. } else if (result == ACTIVATION_REPEAT) {
  35. model.addAttribute("msg", "无效操作,该账号已经激活过了!");
  36. model.addAttribute("target", "/index");
  37. } else {
  38. model.addAttribute("msg", "激活失败,您提供的激活码不正确!");
  39. model.addAttribute("target", "/index");
  40. }
  41. return "/site/operate-result";
  42. }
  43. }

8.7、页面(略)

9、会话管理

• HTTP的基本性质

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

• Cookie

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

• Session

  • 是JavaEE的标准,用于在服务端记录客户端信息。
  • 数据存放在服务端更加安全,但是也会增加服务端的内存压力。

image.png

9.1、Cookie

存放在浏览器,对于密码等个人信息不适合进行存储,不安全但减少服务器压力。
image.png

  1. // cookie示例
  2. @GetMapping("/cookie/set")
  3. @ResponseBody
  4. public String setCookie(HttpServletResponse response) {
  5. // 创建cookie
  6. Cookie cookie = new Cookie("code", CommunityUtil.generateUUID());
  7. // 设置cookie生效的范围
  8. cookie.setPath("/community/alpha");
  9. // 设置cookie的生存时间
  10. cookie.setMaxAge(60 * 10);
  11. // 发送cookie
  12. response.addCookie(cookie);
  13. return "set cookie";
  14. }
  15. @GetMapping("/cookie/get")
  16. @ResponseBody
  17. public String getCookie(@CookieValue("code") String code) {
  18. System.out.println(code);
  19. return "get cookie";
  20. }

9.2、Session(依赖于Cookie)

存放于服务器Session对象存放于服务器,携带sessionId的Cookie对象存放在浏览器,安全但增加服务器压力。
image.png

  1. // session示例
  2. @GetMapping("/session/set")
  3. @ResponseBody
  4. public String setSession(HttpSession session) {
  5. session.setAttribute("id", 1);
  6. session.setAttribute("name", "Test");
  7. return "set session";
  8. }
  9. @GetMapping("/session/get")
  10. @ResponseBody
  11. public String getSession(HttpSession session) {
  12. System.out.println(session.getAttribute("id"));
  13. System.out.println(session.getAttribute("name"));
  14. return "get session";
  15. }

9.2.1、Session分布式部署问题:

image.png
解决方案:
1、粘性session
浏览器只访问一台服务器,再次访问也访问该台服务器
2、同步session
当服务器创建session时,同步给其他的服务器,增加服务器压力,服务器间产生耦合
3、共享session
腾出一台服务器专门用来存储session,单体服务器存在安全隐患
4、存储到数据库redis..

10、生成验证码( Kaptcha )

1、导入依赖

  1. <!-- https://mvnrepository.com/artifact/com.github.penggle/kaptcha -->
  2. <dependency>
  3. <groupId>com.github.penggle</groupId>
  4. <artifactId>kaptcha</artifactId>
  5. <version>2.3.2</version>
  6. </dependency>

2、配置类

  1. @Configuration
  2. public class KaptchaConfig {
  3. @Bean
  4. public Producer kaptchaProducer() {
  5. Properties properties = new Properties();
  6. properties.setProperty("kaptcha.image.width", "100");
  7. properties.setProperty("kaptcha.image.height", "40");
  8. properties.setProperty("kaptcha.textproducer.font.size", "32");
  9. properties.setProperty("kaptcha.textproducer.font.color", "0,0,0");
  10. properties.setProperty("kaptcha.textproducer.char.string", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYAZ");
  11. properties.setProperty("kaptcha.textproducer.char.length", "4");
  12. properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
  13. DefaultKaptcha kaptcha = new DefaultKaptcha();
  14. Config config = new Config(properties);
  15. kaptcha.setConfig(config);
  16. return kaptcha;
  17. }
  18. }

3、生成随机验证码接口

  1. private final Logger logger= LoggerFactory.getLogger(LoginController.class);
  2. @Autowired
  3. private Producer kaptchaProducer;
  4. @GetMapping("/kaptcha")
  5. public void getKaptcha(HttpServletResponse response, HttpSession session){
  6. //生成随机验证码,并且将验证码制作成图片
  7. String text = kaptchaProducer.createText();
  8. BufferedImage image = kaptchaProducer.createImage(text);
  9. //将验证码放入session,后面进行验证
  10. session.setAttribute("kaptcha",text);
  11. //将图片输出给浏览器
  12. response.setContentType("image/png");
  13. try {
  14. ServletOutputStream os = response.getOutputStream();
  15. ImageIO.write(image,"png",os);
  16. } catch (IOException e) {
  17. logger.error("获取验证码失败"+e.getMessage());
  18. }
  19. }

11、登录、退出功能

12、显示登录信息

拦截器

  1. @Component
  2. public class LoginTicketInterceptor implements HandlerInterceptor {
  3. @Autowired
  4. private UserService userService;
  5. @Autowired
  6. private HostHolder hostHolder;
  7. @Override
  8. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  9. String ticket = CookieUtil.getValue(request, "ticket");
  10. if (ticket != null) {
  11. LoginTicket loginTicket = userService.findLoginTicket(ticket);
  12. //检查凭证是否有效
  13. if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) {
  14. User user = userService.selectUserById(loginTicket.getUserId());
  15. hostHolder.setUsers(user);
  16. }
  17. }
  18. return true;
  19. }
  20. @Override
  21. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  22. User user = hostHolder.getUser();
  23. if(user!=null&&modelAndView!=null){
  24. modelAndView.addObject("loginUser",user);
  25. }
  26. }
  27. @Override
  28. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  29. hostHolder.clear();
  30. }
  31. }

提取cookie工具类

  1. public class CookieUtil {
  2. public static String getValue(HttpServletRequest request,String name){
  3. if(request==null||name==null)
  4. throw new IllegalArgumentException("参数为空!!!");
  5. Cookie[] cookies = request.getCookies();
  6. if(cookies!=null){
  7. for (Cookie cookie : cookies) {
  8. if(cookie.getName().equals(name)){
  9. return cookie.getValue();
  10. }
  11. }
  12. }
  13. return null;
  14. }
  15. }

存储对象ThreadLocal

  1. @Component
  2. public class HostHolder {
  3. ThreadLocal<User> users=new ThreadLocal<>();
  4. public void setUsers(User user){
  5. users.set(user);
  6. }
  7. public User getUser(){
  8. return users.get();
  9. }
  10. public void clear(){
  11. users.remove();
  12. }
  13. }

配置类添加拦截器

  1. @Configuration
  2. public class WebMvcConfig implements WebMvcConfigurer {
  3. @Autowired
  4. private LoginTicketInterceptor loginTicketInterceptor;
  5. @Override
  6. public void addInterceptors(InterceptorRegistry registry) {
  7. registry.addInterceptor(loginTicketInterceptor)
  8. .excludePathPatterns("/**/*.css","/**/*.js","/**/*.png","/**/*.jpg","/**/*.jpeg");
  9. }
  10. }

13、账号设置功能

UserController

  1. @Controller
  2. @RequestMapping("/user")
  3. public class UserController {
  4. private static final Logger logger = LoggerFactory.getLogger(UserController.class);
  5. @Value("${community.path.upload}")
  6. private String uploadPath;
  7. @Value("${community.path.domain}")
  8. private String domain;
  9. @Value("${server.servlet.context-path}")
  10. private String context;
  11. @Autowired
  12. private UserService userService;
  13. @Autowired
  14. private HostHolder hostHolder;
  15. /**
  16. * 去到登录页面
  17. *
  18. * @return
  19. */
  20. @GetMapping("/setting")
  21. public String getSettingPage() {
  22. return "/site/setting";
  23. }
  24. /**
  25. * 处理头像上传路径
  26. *
  27. * @param headerImage
  28. * @param model
  29. * @return
  30. */
  31. @PostMapping("/upload")
  32. public String uploadHeader(MultipartFile headerImage, Model model) {
  33. if (headerImage == null) {
  34. model.addAttribute("error", "您还没有上传图片");
  35. return "/site/setting";
  36. }
  37. String fileName = headerImage.getOriginalFilename();
  38. String suffix = fileName.substring(fileName.lastIndexOf("."));
  39. if (StringUtils.isBlank(suffix)) {
  40. model.addAttribute("error", "文件格式不正确");
  41. return "/site/setting";
  42. }
  43. //生成随机文件名
  44. fileName = CommunityUtil.getUUID() + suffix;
  45. File dest = new File(uploadPath + "/" + fileName);
  46. try {
  47. headerImage.transferTo(dest);
  48. } catch (IOException e) {
  49. logger.error("上传文件失败" + e.getMessage());
  50. throw new RuntimeException("上传文件失败" + e.getMessage());
  51. }
  52. //更新用户头像,外界可获取的连接地址
  53. //http://localhost:8080/community/user/header/xxx.png
  54. String headerUrl = domain + context + "/user/header/" + fileName;
  55. User user = hostHolder.getUser();
  56. userService.updateHeader(user.getId(),headerUrl);
  57. return "redirect:/index";
  58. }
  59. /**
  60. * 获取头像
  61. *
  62. * @param fileName
  63. * @param response
  64. */
  65. @GetMapping("/header/{fileName}")
  66. public void getHeader(@PathVariable("fileName") String fileName, HttpServletResponse response) {
  67. // 服务器存放路径
  68. fileName = uploadPath + "/" + fileName;
  69. String suffix = fileName.substring(fileName.lastIndexOf("."));
  70. // 响应图片
  71. response.setContentType("image/" + suffix);
  72. try (
  73. FileInputStream fis = new FileInputStream(fileName);
  74. OutputStream os = response.getOutputStream();
  75. ) {
  76. byte[] buffer = new byte[1024];
  77. int b = 0;
  78. while ((b = fis.read(buffer)) != -1) {
  79. os.write(buffer, 0, b);
  80. }
  81. } catch (IOException e) {
  82. logger.error("读取头像失败: " + e.getMessage());
  83. }
  84. }
  85. }

UserService

  1. public int updateHeader(int userId,String headerUrl){
  2. return userMapper.updateUserHeader(userId,headerUrl);
  3. }

application.properties

  1. #配置头像上传路径
  2. community.path.upload=d:/AAcommunity/data/upload

14、修改密码

15、未登录-拦截不可访问的请求

Annotation-LoginRequire

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface LoginRequire {
  4. }

LoginRequiredInterceptor

  1. @Component
  2. public class LoginRequiredInterceptor implements HandlerInterceptor {
  3. @Autowired
  4. private HostHolder hostHolder;
  5. @Override
  6. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  7. if(handler instanceof HandlerMethod){
  8. HandlerMethod handlerMethod=(HandlerMethod)handler;
  9. Method method = handlerMethod.getMethod();
  10. LoginRequire annotation = method.getAnnotation(LoginRequire.class);
  11. if(annotation!=null&&hostHolder.getUser()==null){
  12. response.sendRedirect(request.getContextPath()+"/login");
  13. return false;
  14. }
  15. }
  16. return true;
  17. }
  18. }

WebMvcConfig添加拦截器

  1. registry.addInterceptor(loginRequiredInterceptor)
  2. .excludePathPatterns("/**/*.css","/**/*.js","/**/*.png","/**/*.jpg","/**/*.jpeg");