企业管理系统定制开发从零开始—仿牛客网讨论社区项目(一)

企业管理系统定制开发主要技术架构:

SpringBoot Spring SpringMVC Redis Kakfa Elasticsearch Spring Security Spring Actator

1.企业管理系统定制开发配置项目环境

        在中或者Idea企业管理系统定制开发中初始化一个SpringBoot企业管理系统定制开发项目并导出

        使用Idea企业管理系统定制开发打开导出的项目

2.MyBatis配置

 企业管理系统定制开发各个层之间的关系如下

       在搜索MySql Maven配置文件,在resources企业管理系统定制开发文件包内的pom.xml企业管理系统定制开发文件中导入相关的配置文件依赖,并在application.properties企业管理系统定制开发文件中配置相关的参数。

  1. # ServerProperties
  2. server.port=8080
  3. server.servlet.context-path=/community
  4. # ThymeleafProperties
  5. spring.thymeleaf.cache=false
  6. # DataSourceProperties
  7. spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
  8. spring.datasource.url=jdbc:mysql://localhost:3306/community?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
  9. #企业管理系统定制开发数据库的名称、密码等
  10. spring.datasource.username=root
  11. spring.datasource.password=123456
  12. spring.datasource.type=com.zaxxer.hikari.HikariDataSource
  13. #企业管理系统定制开发最大连接数、企业管理系统定制开发超时时间等
  14. spring.datasource.hikari.maximum-pool-size=15
  15. spring.datasource.hikari.minimum-idle=5
  16. spring.datasource.hikari.idle-timeout=30000
  17. # MybatisProperties
  18. #mapper扫描路径
  19. mybatis.mapper-locations=classpath:mapper/*.xml
  20. #在communtiy企业管理系统定制开发下创建实体类
  21. mybatis.type-aliases-package=com.nowcoder.community.entity
  22. mybatis.configuration.useGeneratedKeys=true
  23. mybatis.configuration.mapUnderscoreToCamelCase=true

        在community企业管理系统定制开发企业管理系统定制开发文件下创建config entity文件包,在resources文件下创建mapper文件包

        在entity文件下创建User类

  1. public class User {
  2. private int id;
  3. private String username;
  4. private String password;
  5. private String salt;
  6. private String email;
  7. private int type;
  8. private int status;
  9. private String activationCode;
  10. private String headerUrl;
  11. private Date createTime;
  12. public int getId() {
  13. return id;
  14. }
  15. public void setId(int id) {
  16. this.id = id;
  17. }
  18. public String getUsername() {
  19. return username;
  20. }
  21. public void setUsername(String username) {
  22. this.username = username;
  23. }
  24. public String getPassword() {
  25. return password;
  26. }
  27. public void setPassword(String password) {
  28. this.password = password;
  29. }
  30. public String getSalt() {
  31. return salt;
  32. }
  33. public void setSalt(String salt) {
  34. this.salt = salt;
  35. }
  36. public String getEmail() {
  37. return email;
  38. }
  39. public void setEmail(String email) {
  40. this.email = email;
  41. }
  42. public int getType() {
  43. return type;
  44. }
  45. public void setType(int type) {
  46. this.type = type;
  47. }
  48. public int getStatus() {
  49. return status;
  50. }
  51. public void setStatus(int status) {
  52. this.status = status;
  53. }
  54. public String getActivationCode() {
  55. return activationCode;
  56. }
  57. public void setActivationCode(String activationCode) {
  58. this.activationCode = activationCode;
  59. }
  60. public String getHeaderUrl() {
  61. return headerUrl;
  62. }
  63. public void setHeaderUrl(String headerUrl) {
  64. this.headerUrl = headerUrl;
  65. }
  66. public Date getCreateTime() {
  67. return createTime;
  68. }
  69. public void setCreateTime(Date createTime) {
  70. this.createTime = createTime;
  71. }
  72. @Override
  73. public String toString() {
  74. return "User{" +
  75. "id=" + id +
  76. ", username='" + username + '\'' +
  77. ", password='" + password + '\'' +
  78. ", salt='" + salt + '\'' +
  79. ", email='" + email + '\'' +
  80. ", type=" + type +
  81. ", status=" + status +
  82. ", activationCode='" + activationCode + '\'' +
  83. ", headerUrl='" + headerUrl + '\'' +
  84. ", createTime=" + createTime +
  85. '}';
  86. }
  87. }

在dao文件下创建UserMapper接口访问数据库

  1. @Mapper
  2. public interface UserMapper {
  3. User selectById(int id);
  4. User selectByName(String username);
  5. User selectByEmail(String email);
  6. int insertUser(User user);
  7. int updateStatus(int id, int status);
  8. int updateHeader(int id, String headerUrl);
  9. int updatePassword(int id, String password);
  10. }

        使用Mapper注解,并在mapper文件下创建user-mapp.xml,使得方法与Sql语句相关联,Mybatis 的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.nowcoder.community.dao.UserMapper">
  6. <sql id="insertFields">
  7. username, password, salt, email, type, status, activation_code, header_url, create_time
  8. </sql>
  9. <sql id="selectFields">
  10. id, username, password, salt, email, type, status, activation_code, header_url, create_time
  11. </sql>
  12. <select id="selectById" resultType="User">
  13. select <include refid="selectFields"></include>
  14. from user
  15. where id = #{id}
  16. </select>
  17. <select id="selectByName" resultType="User">
  18. select <include refid="selectFields"></include>
  19. from user
  20. where username = #{username}
  21. </select>
  22. <select id="selectByEmail" resultType="User">
  23. select <include refid="selectFields"></include>
  24. from user
  25. where email = #{email}
  26. </select>
  27. <insert id="insertUser" parameterType="User" keyProperty="id">
  28. insert into user (<include refid="insertFields"></include>)
  29. values(#{username}, #{password}, #{salt}, #{email}, #{type}, #{status}, #{activationCode}, #{headerUrl}, #{createTime})
  30. </insert>
  31. <update id="updateStatus">
  32. update user set status = #{status} where id = #{id}
  33. </update>
  34. <update id="updateHeader">
  35. update user set header_url = #{headerUrl} where id = #{id}
  36. </update>
  37. <update id="updatePassword">
  38. update user set password = #{password} where id = #{id}
  39. </update>
  40. </mapper>

        可以使用@Test注解测试相关方法是否正常使用

3.开发社区首页功能:

3.1 开发社区首页显示前10个帖子

        在Entity创建DiscussPost类,用于表示发送的相关数据,并在dao文件中创建DiscussPostMapper接口

  1. public class DiscussPost {
  2. private int id;
  3. private int userId;
  4. private String title;
  5. private String content;
  6. private int type;
  7. private int status;
  8. private Date createTime;
  9. private int commentCount;
  10. private double score;
  11. public int getId() {
  12. return id;
  13. }
  14. public void setId(int id) {
  15. this.id = id;
  16. }
  17. public int getUserId() {
  18. return userId;
  19. }
  20. public void setUserId(int userId) {
  21. this.userId = userId;
  22. }
  23. public String getTitle() {
  24. return title;
  25. }
  26. public void setTitle(String title) {
  27. this.title = title;
  28. }
  29. public String getContent() {
  30. return content;
  31. }
  32. public void setContent(String content) {
  33. this.content = content;
  34. }
  35. public int getType() {
  36. return type;
  37. }
  38. public void setType(int type) {
  39. this.type = type;
  40. }
  41. public int getStatus() {
  42. return status;
  43. }
  44. public void setStatus(int status) {
  45. this.status = status;
  46. }
  47. public Date getCreateTime() {
  48. return createTime;
  49. }
  50. public void setCreateTime(Date createTime) {
  51. this.createTime = createTime;
  52. }
  53. public int getCommentCount() {
  54. return commentCount;
  55. }
  56. public void setCommentCount(int commentCount) {
  57. this.commentCount = commentCount;
  58. }
  59. public double getScore() {
  60. return score;
  61. }
  62. public void setScore(double score) {
  63. this.score = score;
  64. }
  65. @Override
  66. public String toString() {
  67. return "DiscussPost{" +
  68. "id=" + id +
  69. ", userId=" + userId +
  70. ", title='" + title + '\'' +
  71. ", content='" + content + '\'' +
  72. ", type=" + type +
  73. ", status=" + status +
  74. ", createTime=" + createTime +
  75. ", commentCount=" + commentCount +
  76. ", score=" + score +
  77. '}';
  78. }
  79. }
  1. @Mapper
  2. public interface DiscussPostMapper {
  3. // 考虑到后期分页功能加入offset 和 limit变量
  4. // @Param注解用于给参数取别名,
  5. // 如果只有一个参数,并且在<if>里使用,则必须加别名.
  6. List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit);
  7. int selectDiscussPostRows(@Param("userId") int userId);
  8. }

        在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.nowcoder.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 <include refid="selectFields"></include>
  11. from discuss_post
  12. where status != 2
  13. <if test="userId!=0">
  14. and user_id = #{userId}
  15. </if>
  16. order by type desc, create_time desc
  17. limit #{offset}, #{limit}
  18. </select>
  19. <select id="selectDiscussPostRows" resultType="int">
  20. select count(id)
  21. from discuss_post
  22. where status != 2
  23. <if test="userId!=0">
  24. and user_id = #{userId}
  25. </if>
  26. </select>
  27. </mapper>

        在service文件下创建DiscussPostService类,用于服务层使用(使用@Service注解)

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

        将静态资源(css image js等文件)放到static文件下,将模板文件(site index.html)放到templates文件下。

        接下来开发视图层,新建一个HomeController在controller文件下使用@Controller注解

  1. //Controller访问路径可以省略
  2. @Controller
  3. public class HomeController {
  4. //注入对象
  5. @Autowired
  6. private DiscussPostService discussPostService;
  7. @Autowired
  8. private UserService userService;
  9. //使用GET方法
  10. @RequestMapping(path = "/index", method = RequestMethod.GET)
  11. List<DiscussPost> list = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit());
  12. List<Map<String, Object>> discussPosts = new ArrayList<>();
  13. if (list != null) {
  14. for (DiscussPost post : list) {
  15. Map<String, Object> map = new HashMap<>();
  16. map.put("post", post);
  17. User user = userService.findUserById(post.getUserId());
  18. map.put("user", user);
  19. discussPosts.add(map);
  20. }
  21. }
  22. model.addAttribute("discussPosts", discussPosts);
  23. return "/index";
  24. }
  25. }

        更改Index.html文件,使用Thymeleaf对其中相对路径进行更改,并显示相关的帖子。

  1. <ul class="list-unstyled">
  2. <li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}">
  3. <a href="site/profile.html">
  4. <img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;">
  5. </a>
  6. <div class="media-body">
  7. <h6 class="mt-0 mb-3">
  8. <a href="#" th:utext="${map.post.title}">备战春招,面试刷题跟他复习,一个月全搞定!</a>
  9. <span class="badge badge-secondary bg-primary" th:if="${map.post.type==1}">置顶</span>
  10. <span class="badge badge-secondary bg-danger" th:if="${map.post.status==1}">精华</span>
  11. </h6>
  12. <div class="text-muted font-size-12">
  13. <u class="mr-3" th:utext="${map.user.username}">寒江雪</u> 发布于 <b th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</b>
  14. <ul class="d-inline float-right">
  15. <li class="d-inline ml-2">赞 11</li>
  16. <li class="d-inline ml-2">|</li>
  17. <li class="d-inline ml-2">回帖 7</li>
  18. </ul>
  19. </div>
  20. </div>
  21. </li>
  22. </ul>

        完整的HTML文件如下:

  1. <!doctype html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  6. <link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
  7. <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
  8. <link rel="stylesheet" th:href="@{/css/global.css}" />
  9. <title>牛客网-首页</title>
  10. </head>
  11. <body>
  12. <div class="nk-container">
  13. <!-- 头部 -->
  14. <header class="bg-dark sticky-top">
  15. <div class="container">
  16. <!-- 导航 -->
  17. <nav class="navbar navbar-expand-lg navbar-dark">
  18. <!-- logo -->
  19. <a class="navbar-brand" href="#"></a>
  20. <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
  21. <span class="navbar-toggler-icon"></span>
  22. </button>
  23. <!-- 功能 -->
  24. <div class="collapse navbar-collapse" id="navbarSupportedContent">
  25. <ul class="navbar-nav mr-auto">
  26. <li class="nav-item ml-3 btn-group-vertical">
  27. <a class="nav-link" href="index.html">首页</a>
  28. </li>
  29. <li class="nav-item ml-3 btn-group-vertical">
  30. <a class="nav-link position-relative" href="site/letter.html">消息<span class="badge badge-danger">12</span></a>
  31. </li>
  32. <li class="nav-item ml-3 btn-group-vertical">
  33. <a class="nav-link" href="site/register.html">注册</a>
  34. </li>
  35. <li class="nav-item ml-3 btn-group-vertical">
  36. <a class="nav-link" href="site/login.html">登录</a>
  37. </li>
  38. <li class="nav-item ml-3 btn-group-vertical dropdown">
  39. <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
  40. <img src="http://images.nowcoder.com/head/1t.png" class="rounded-circle" style="width:30px;"/>
  41. </a>
  42. <div class="dropdown-menu" aria-labelledby="navbarDropdown">
  43. <a class="dropdown-item text-center" href="site/profile.html">个人主页</a>
  44. <a class="dropdown-item text-center" href="site/setting.html">账号设置</a>
  45. <a class="dropdown-item text-center" href="site/login.html">退出登录</a>
  46. <div class="dropdown-divider"></div>
  47. <span class="dropdown-item text-center text-secondary">nowcoder</span>
  48. </div>
  49. </li>
  50. </ul>
  51. <!-- 搜索 -->
  52. <form class="form-inline my-2 my-lg-0" action="site/search.html">
  53. <input class="form-control mr-sm-2" type="search" aria-label="Search" />
  54. <button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button>
  55. </form>
  56. </div>
  57. </nav>
  58. </div>
  59. </header>
  60. <!-- 内容 -->
  61. <div class="main">
  62. <div class="container">
  63. <div class="position-relative">
  64. <!-- 筛选条件 -->
  65. <ul class="nav nav-tabs mb-3">
  66. <li class="nav-item">
  67. <a class="nav-link active" href="#">最新</a>
  68. </li>
  69. <li class="nav-item">
  70. <a class="nav-link" href="#">最热</a>
  71. </li>
  72. </ul>
  73. <button type="button" class="btn btn-primary btn-sm position-absolute rt-0" data-toggle="modal" data-target="#publishModal">我要发布</button>
  74. </div>
  75. <!-- 弹出框 -->
  76. <div class="modal fade" id="publishModal" tabindex="-1" role="dialog" aria-labelledby="publishModalLabel" aria-hidden="true">
  77. <div class="modal-dialog modal-lg" role="document">
  78. <div class="modal-content">
  79. <div class="modal-header">
  80. <h5 class="modal-title" id="publishModalLabel">新帖发布</h5>
  81. <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  82. <span aria-hidden="true">&times;</span>
  83. </button>
  84. </div>
  85. <div class="modal-body">
  86. <form>
  87. <div class="form-group">
  88. <label for="recipient-name" class="col-form-label">标题:</label>
  89. <input type="text" class="form-control" id="recipient-name">
  90. </div>
  91. <div class="form-group">
  92. <label for="message-text" class="col-form-label">正文:</label>
  93. <textarea class="form-control" id="message-text" rows="15"></textarea>
  94. </div>
  95. </form>
  96. </div>
  97. <div class="modal-footer">
  98. <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
  99. <button type="button" class="btn btn-primary" id="publishBtn">发布</button>
  100. </div>
  101. </div>
  102. </div>
  103. </div>
  104. <!-- 提示框 -->
  105. <div class="modal fade" id="hintModal" tabindex="-1" role="dialog" aria-labelledby="hintModalLabel" aria-hidden="true">
  106. <div class="modal-dialog modal-lg" role="document">
  107. <div class="modal-content">
  108. <div class="modal-header">
  109. <h5 class="modal-title" id="hintModalLabel">提示</h5>
  110. </div>
  111. <div class="modal-body" id="hintBody">
  112. 发布完毕!
  113. </div>
  114. </div>
  115. </div>
  116. </div>
  117. <!-- 帖子列表 -->
  118. <ul class="list-unstyled">
  119. <li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}">
  120. <a href="site/profile.html">
  121. <img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;">
  122. </a>
  123. <div class="media-body">
  124. <h6 class="mt-0 mb-3">
  125. <a href="#" th:utext="${map.post.title}">备战春招,面试刷题跟他复习,一个月全搞定!</a>
  126. <span class="badge badge-secondary bg-primary" th:if="${map.post.type==1}">置顶</span>
  127. <span class="badge badge-secondary bg-danger" th:if="${map.post.status==1}">精华</span>
  128. </h6>
  129. <div class="text-muted font-size-12">
  130. <u class="mr-3" th:utext="${map.user.username}">寒江雪</u> 发布于 <b th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</b>
  131. <ul class="d-inline float-right">
  132. <li class="d-inline ml-2">赞 11</li>
  133. <li class="d-inline ml-2">|</li>
  134. <li class="d-inline ml-2">回帖 7</li>
  135. </ul>
  136. </div>
  137. </div>
  138. </li>
  139. </ul>
  140. <!-- 分页 -->
  141. <nav class="mt-5" th:if="${page.rows>0}">
  142. <ul class="pagination justify-content-center">
  143. <li class="page-item">
  144. <a class="page-link" th:href="@{${page.path}(current=1)}">首页</a>
  145. </li>
  146. <li th:class="|page-item ${page.current==1?'disabled':''}|">
  147. <a class="page-link" th:href="@{${page.path}(current=${page.current-1})}">上一页</a></li>
  148. <li th:class="|page-item ${i==page.current?'active':''}|" th:each="i:${#numbers.sequence(page.from,page.to)}">
  149. <a class="page-link" href="#" th:text="${i}">1</a>
  150. </li>
  151. <li th:class="|page-item ${page.current==page.total?'disabled':''}|">
  152. <a class="page-link" th:href="@{${page.path}(current=${page.current+1})}">下一页</a>
  153. </li>
  154. <li class="page-item">
  155. <a class="page-link" th:href="@{${page.path}(current=${page.total})}">末页</a>
  156. </li>
  157. </ul>
  158. </nav>
  159. </div>
  160. </div>
  161. <!-- 尾部 -->
  162. <footer class="bg-dark">
  163. <div class="container">
  164. <div class="row">
  165. <!-- 二维码 -->
  166. <div class="col-4 qrcode">
  167. <img src="https://uploadfiles.nowcoder.com/app/app_download.png" class="img-thumbnail" style="width:136px;" />
  168. </div>
  169. <!-- 公司信息 -->
  170. <div class="col-8 detail-info">
  171. <div class="row">
  172. <div class="col">
  173. <ul class="nav">
  174. <li class="nav-item">
  175. <a class="nav-link text-light" href="#">关于我们</a>
  176. </li>
  177. <li class="nav-item">
  178. <a class="nav-link text-light" href="#">加入我们</a>
  179. </li>
  180. <li class="nav-item">
  181. <a class="nav-link text-light" href="#">意见反馈</a>
  182. </li>
  183. <li class="nav-item">
  184. <a class="nav-link text-light" href="#">企业服务</a>
  185. </li>
  186. <li class="nav-item">
  187. <a class="nav-link text-light" href="#">联系我们</a>
  188. </li>
  189. <li class="nav-item">
  190. <a class="nav-link text-light" href="#">免责声明</a>
  191. </li>
  192. <li class="nav-item">
  193. <a class="nav-link text-light" href="#">友情链接</a>
  194. </li>
  195. </ul>
  196. </div>
  197. </div>
  198. <div class="row">
  199. <div class="col">
  200. <ul class="nav btn-group-vertical company-info">
  201. <li class="nav-item text-white-50">
  202. 公司地址:北京市朝阳区大屯路东金泉时代3-2708北京牛客科技有限公司
  203. </li>
  204. <li class="nav-item text-white-50">
  205. 联系方式:010-60728802(电话)&nbsp;&nbsp;&nbsp;&nbsp;admin@nowcoder.com
  206. </li>
  207. <li class="nav-item text-white-50">
  208. 牛客科技©2018 All rights reserved
  209. </li>
  210. <li class="nav-item text-white-50">
  211. 京ICP备14055008号-4 &nbsp;&nbsp;&nbsp;&nbsp;
  212. <img src="http://static.nowcoder.com/company/images/res/ghs.png" style="width:18px;" />
  213. 京公网安备 11010502036488号
  214. </li>
  215. </ul>
  216. </div>
  217. </div>
  218. </div>
  219. </div>
  220. </div>
  221. </footer>
  222. </div>
  223. <script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
  224. <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
  225. <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
  226. <script th:src="@{/js/global.js}"></script>
  227. <script th:src="@{js/index.js}"></script>
  228. </body>
  229. </html>

3.2开发分页组件,分页显示所有帖子

        在entity文件下创建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. }

        更改HomeController,加入分页的方法。

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

        在更改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>
  20. </div>
  21. </div>

效果图如下:

4.开发社区登录模块

4.1邮件发送

           在搜索Spring Mail配置文件并加入到poml文件中,在application.properties文件中配置Mail的参数。

  1. # ServerProperties
  2. server.port=8080
  3. server.servlet.context-path=/community
  4. # ThymeleafProperties
  5. spring.thymeleaf.cache=false
  6. # DataSourceProperties
  7. spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
  8. spring.datasource.url=jdbc:mysql://localhost:3306/community?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
  9. spring.datasource.username=root
  10. spring.datasource.password=lihonghe
  11. spring.datasource.type=com.zaxxer.hikari.HikariDataSource
  12. spring.datasource.hikari.maximum-pool-size=15
  13. spring.datasource.hikari.minimum-idle=5
  14. spring.datasource.hikari.idle-timeout=30000
  15. # MybatisProperties
  16. mybatis.mapper-locations=classpath:mapper/*.xml
  17. mybatis.type-aliases-package=com.nowcoder.community.entity
  18. mybatis.configuration.useGeneratedKeys=true
  19. mybatis.configuration.mapUnderscoreToCamelCase=true
  20. # MailProperties
  21. spring.mail.host=smtp.sina.com
  22. spring.mail.port=465
  23. #自己的邮箱
  24. spring.mail.username=nowcoder@sina.com
  25. spring.mail.password=nowcoder123
  26. spring.mail.protocol=smtps
  27. spring.mail.properties.mail.smtp.ssl.enable=true

        如果发送不了邮件,需要在个人邮箱的网站设置启用授权码,验证手机,并修改properties文件中关于Email的配置

  1. # MailProperties
  2. spring.mail.host=smtp.sina.com
  3. #spring.mail.port=465
  4. spring.mail.username=nowcoder@sina.com
  5. spring.mail.password=3398c6c71399f9fe
  6. #spring.mail.protocol=smtps
  7. #spring.mail.properties.mail.smtp.ssl.enable=true
  8. spring.mail.properties.mail.smtl.auth=true

        在community文件下创建util工具文件包,并在util包中创建MailClient类使用@Component注解,并创建发送邮件的方法。

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

4.2注册功能

        在Controller层下创建LoginController类,实现登录界面跳转到注册页面,使用@Controller注解。

  1. @Controller
  2. public class LoginController implements CommunityConstant {
  3. @RequestMapping(path = "/register", method = RequestMethod.GET)
  4. public String getRegisterPage() {
  5. return "/site/register";
  6. }
  7. @RequestMapping(path = "/login", method = RequestMethod.GET)
  8. public String getLoginPage() {
  9. return "/site/login";
  10. }
  11. }

         使用模板引擎thymeleaf修改注册页面regist.html。

  1. <!doctype html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  6. <link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
  7. <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
  8. <link rel="stylesheet" th:href="@{/css/global.css}" />
  9. <link rel="stylesheet" th:href="@{/css/login.css}" />
  10. <title>牛客网-注册</title>
  11. </head>
  12. <body>
  13. <div class="nk-container">
  14. <!-- 头部 -->
  15. <header class="bg-dark sticky-top" th:replace="index::header">
  16. <div class="container">
  17. <!-- 导航 -->
  18. <nav class="navbar navbar-expand-lg navbar-dark">
  19. <!-- logo -->
  20. <a class="navbar-brand" href="#"></a>
  21. <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
  22. <span class="navbar-toggler-icon"></span>
  23. </button>
  24. <!-- 功能 -->
  25. <div class="collapse navbar-collapse" id="navbarSupportedContent">
  26. <ul class="navbar-nav mr-auto">
  27. <li class="nav-item ml-3 btn-group-vertical">
  28. <a class="nav-link" href="../index.html">首页</a>
  29. </li>
  30. <li class="nav-item ml-3 btn-group-vertical">
  31. <a class="nav-link position-relative" href="letter.html">消息<span class="badge badge-danger">12</span></a>
  32. </li>
  33. <li class="nav-item ml-3 btn-group-vertical">
  34. <a class="nav-link" href="register.html">注册</a>
  35. </li>
  36. <li class="nav-item ml-3 btn-group-vertical">
  37. <a class="nav-link" href="login.html">登录</a>
  38. </li>
  39. <li class="nav-item ml-3 btn-group-vertical dropdown">
  40. <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
  41. <img src="http://images.nowcoder.com/head/1t.png" class="rounded-circle" style="width:30px;"/>
  42. </a>
  43. <div class="dropdown-menu" aria-labelledby="navbarDropdown">
  44. <a class="dropdown-item text-center" href="profile.html">个人主页</a>
  45. <a class="dropdown-item text-center" href="setting.html">账号设置</a>
  46. <a class="dropdown-item text-center" href="login.html">退出登录</a>
  47. <div class="dropdown-divider"></div>
  48. <span class="dropdown-item text-center text-secondary">nowcoder</span>
  49. </div>
  50. </li>
  51. </ul>
  52. <!-- 搜索 -->
  53. <form class="form-inline my-2 my-lg-0" action="search.html">
  54. <input class="form-control mr-sm-2" type="search" aria-label="Search" />
  55. <button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button>
  56. </form>
  57. </div>
  58. </nav>
  59. </div>
  60. </header>
  61. <!-- 内容 -->
  62. <div class="main">
  63. <div class="container pl-5 pr-5 pt-3 pb-3 mt-3 mb-3">
  64. <h3 class="text-center text-info border-bottom pb-3">&nbsp;&nbsp;</h3>
  65. <form class="mt-5" method="post" th:action="@{/register}">
  66. <div class="form-group row">
  67. <label for="username" class="col-sm-2 col-form-label text-right">账号:</label>
  68. <div class="col-sm-10">
  69. <input type="text"
  70. th:class="|form-control ${usernameMsg!=null?'is-invalid':''}|"
  71. th:value="${user!=null?user.username:''}"
  72. id="username" name="username" placeholder="请输入您的账号!" required>
  73. <div class="invalid-feedback" th:text="${usernameMsg}">
  74. 该账号已存在!
  75. </div>
  76. </div>
  77. </div>
  78. <div class="form-group row mt-4">
  79. <label for="password" class="col-sm-2 col-form-label text-right">密码:</label>
  80. <div class="col-sm-10">
  81. <input type="password"
  82. th:class="|form-control ${passwordMsg!=null?'is-invalid':''}|"
  83. th:value="${user!=null?user.password:''}"
  84. id="password" name="password" placeholder="请输入您的密码!" required>
  85. <div class="invalid-feedback" th:text="${passwordMsg}">
  86. 密码长度不能小于8位!
  87. </div>
  88. </div>
  89. </div>
  90. <div class="form-group row mt-4">
  91. <label for="confirm-password" class="col-sm-2 col-form-label text-right">确认密码:</label>
  92. <div class="col-sm-10">
  93. <input type="password" class="form-control"
  94. th:value="${user!=null?user.password:''}"
  95. id="confirm-password" placeholder="请再次输入密码!" required>
  96. <div class="invalid-feedback">
  97. 两次输入的密码不一致!
  98. </div>
  99. </div>
  100. </div>
  101. <div class="form-group row">
  102. <label for="email" class="col-sm-2 col-form-label text-right">邮箱:</label>
  103. <div class="col-sm-10">
  104. <input type="email"
  105. th:class="|form-control ${emailMsg!=null?'is-invalid':''}|"
  106. th:value="${user!=null?user.email:''}"
  107. id="email" name="email" placeholder="请输入您的邮箱!" required>
  108. <div class="invalid-feedback" th:text="${emailMsg}">
  109. 该邮箱已注册!
  110. </div>
  111. </div>
  112. </div>
  113. <div class="form-group row mt-4">
  114. <div class="col-sm-2"></div>
  115. <div class="col-sm-10 text-center">
  116. <button type="submit" class="btn btn-info text-white form-control">立即注册</button>
  117. </div>
  118. </div>
  119. </form>
  120. </div>
  121. </div>
  122. <!-- 尾部 -->
  123. <footer class="bg-dark">
  124. <div class="container">
  125. <div class="row">
  126. <!-- 二维码 -->
  127. <div class="col-4 qrcode">
  128. <img src="https://uploadfiles.nowcoder.com/app/app_download.png" class="img-thumbnail" style="width:136px;" />
  129. </div>
  130. <!-- 公司信息 -->
  131. <div class="col-8 detail-info">
  132. <div class="row">
  133. <div class="col">
  134. <ul class="nav">
  135. <li class="nav-item">
  136. <a class="nav-link text-light" href="#">关于我们</a>
  137. </li>
  138. <li class="nav-item">
  139. <a class="nav-link text-light" href="#">加入我们</a>
  140. </li>
  141. <li class="nav-item">
  142. <a class="nav-link text-light" href="#">意见反馈</a>
  143. </li>
  144. <li class="nav-item">
  145. <a class="nav-link text-light" href="#">企业服务</a>
  146. </li>
  147. <li class="nav-item">
  148. <a class="nav-link text-light" href="#">联系我们</a>
  149. </li>
  150. <li class="nav-item">
  151. <a class="nav-link text-light" href="#">免责声明</a>
  152. </li>
  153. <li class="nav-item">
  154. <a class="nav-link text-light" href="#">友情链接</a>
  155. </li>
  156. </ul>
  157. </div>
  158. </div>
  159. <div class="row">
  160. <div class="col">
  161. <ul class="nav btn-group-vertical company-info">
  162. <li class="nav-item text-white-50">
  163. 公司地址:北京市朝阳区大屯路东金泉时代3-2708北京牛客科技有限公司
  164. </li>
  165. <li class="nav-item text-white-50">
  166. 联系方式:010-60728802(电话)&nbsp;&nbsp;&nbsp;&nbsp;admin@nowcoder.com
  167. </li>
  168. <li class="nav-item text-white-50">
  169. 牛客科技©2018 All rights reserved
  170. </li>
  171. <li class="nav-item text-white-50">
  172. 京ICP备14055008号-4 &nbsp;&nbsp;&nbsp;&nbsp;
  173. <img src="http://static.nowcoder.com/company/images/res/ghs.png" style="width:18px;" />
  174. 京公网安备 11010502036488号
  175. </li>
  176. </ul>
  177. </div>
  178. </div>
  179. </div>
  180. </div>
  181. </div>
  182. </footer>
  183. </div>
  184. <script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
  185. <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
  186. <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
  187. <script th:src="@{/js/global.js}"></script>
  188. <script th:src="@{/js/register.js}"></script>
  189. </body>
  190. </html>

         在搜索commons lang配置文件并加入到poml文件中。在properties文件中加入community的路径。

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

在util中添加CommunityUtil工具类,方便生成密码。

  1. public class CommunityUtil {
  2. // 生成随机字符串
  3. public static String generateUUID() {
  4. return UUID.randomUUID().toString().replaceAll("-", "");
  5. }
  6. // MD5加密
  7. // hello -> abc123def456
  8. // hello + 3e4a8 -> abc123def456abc
  9. public static String md5(String key) {
  10. if (StringUtils.isBlank(key)) {
  11. return null;
  12. }
  13. return DigestUtils.md5DigestAsHex(key.getBytes());
  14. }
  15. }

        在Service中更新UserService类,用于注册用户业务,并更新激活页面activation.html。

  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. @Autowired
  14. private LoginTicketMapper loginTicketMapper;
  15. public User findUserById(int id) {
  16. return userMapper.selectById(id);
  17. }
  18. public Map<String, Object> register(User user) {
  19. Map<String, Object> map = new HashMap<>();
  20. // 空值处理
  21. if (user == null) {
  22. throw new IllegalArgumentException("参数不能为空!");
  23. }
  24. if (StringUtils.isBlank(user.getUsername())) {
  25. map.put("usernameMsg", "账号不能为空!");
  26. return map;
  27. }
  28. if (StringUtils.isBlank(user.getPassword())) {
  29. map.put("passwordMsg", "密码不能为空!");
  30. return map;
  31. }
  32. if (StringUtils.isBlank(user.getEmail())) {
  33. map.put("emailMsg", "邮箱不能为空!");
  34. return map;
  35. }
  36. // 验证账号
  37. User u = userMapper.selectByName(user.getUsername());
  38. if (u != null) {
  39. map.put("usernameMsg", "该账号已存在!");
  40. return map;
  41. }
  42. // 验证邮箱
  43. u = userMapper.selectByEmail(user.getEmail());
  44. if (u != null) {
  45. map.put("emailMsg", "该邮箱已被注册!");
  46. return map;
  47. }
  48. // 注册用户
  49. user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
  50. user.setPassword(CommunityUtil.md5(user.getPassword() + user.getSalt()));
  51. user.setType(0);
  52. user.setStatus(0);
  53. user.setActivationCode(CommunityUtil.generateUUID());
  54. user.setHeaderUrl(String.format("http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));
  55. user.setCreateTime(new Date());
  56. userMapper.insertUser(user);
  57. // 激活邮件
  58. Context context = new Context();
  59. context.setVariable("email", user.getEmail());
  60. // http://localhost:8080/community/activation/101/code
  61. String url = domain + contextPath + "/activation/" + user.getId() + "/" + user.getActivationCode();
  62. context.setVariable("url", url);
  63. String content = templateEngine.process("/mail/activation", context);
  64. mailClient.sendMail(user.getEmail(), "激活账号", content);
  65. return map;
  66. }
  67. public int activation(int userId, String code) {
  68. User user = userMapper.selectById(userId);
  69. if (user.getStatus() == 1) {
  70. return ACTIVATION_REPEAT;
  71. } else if (user.getActivationCode().equals(code)) {
  72. userMapper.updateStatus(userId, 1);
  73. return ACTIVATION_SUCCESS;
  74. } else {
  75. return ACTIVATION_FAILURE;
  76. }
  77. }
  78. public Map<String, Object> login(String username, String password, int expiredSeconds) {
  79. Map<String, Object> map = new HashMap<>();
  80. // 空值处理
  81. if (StringUtils.isBlank(username)) {
  82. map.put("usernameMsg", "账号不能为空!");
  83. return map;
  84. }
  85. if (StringUtils.isBlank(password)) {
  86. map.put("passwordMsg", "密码不能为空!");
  87. return map;
  88. }
  89. // 验证账号
  90. User user = userMapper.selectByName(username);
  91. if (user == null) {
  92. map.put("usernameMsg", "该账号不存在!");
  93. return map;
  94. }
  95. // 验证状态
  96. if (user.getStatus() == 0) {
  97. map.put("usernameMsg", "该账号未激活!");
  98. return map;
  99. }
  100. // 验证密码
  101. password = CommunityUtil.md5(password + user.getSalt());
  102. if (!user.getPassword().equals(password)) {
  103. map.put("passwordMsg", "密码不正确!");
  104. return map;
  105. }
  106. // 生成登录凭证
  107. LoginTicket loginTicket = new LoginTicket();
  108. loginTicket.setUserId(user.getId());
  109. loginTicket.setTicket(CommunityUtil.generateUUID());
  110. loginTicket.setStatus(0);
  111. loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
  112. loginTicketMapper.insertLoginTicket(loginTicket);
  113. map.put("ticket", loginTicket.getTicket());
  114. return map;
  115. }
  116. }
  1. <!doctype html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="utf-8">
  5. <link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
  6. <title>牛客网-激活账号</title>
  7. </head>
  8. <body>
  9. <div>
  10. <p>
  11. <b th:text="${email}">xxx@xxx.com</b>, 您好!
  12. </p>
  13. <p>
  14. 您正在注册牛客网, 这是一封激活邮件, 请点击
  15. <a th:href="${url}">此链接</a>,
  16. 激活您的牛客账号!
  17. </p>
  18. </div>
  19. </body>
  20. </html>

        注册之后需要更新LoginController,处理注册完成的请求。

  1. @Controller
  2. public class LoginController implements CommunityConstant {
  3. private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
  4. @Autowired
  5. private UserService userService;
  6. @Autowired
  7. private Producer kaptchaProducer;
  8. @Value("${server.servlet.context-path}")
  9. private String contextPath;
  10. @RequestMapping(path = "/register", method = RequestMethod.GET)
  11. public String getRegisterPage() {
  12. return "/site/register";
  13. }
  14. @RequestMapping(path = "/login", method = RequestMethod.GET)
  15. public String getLoginPage() {
  16. return "/site/login";
  17. }
  18. @RequestMapping(path = "/register", method = RequestMethod.POST)
  19. public String register(Model model, User user) {
  20. Map<String, Object> map = userService.register(user);
  21. if (map == null || map.isEmpty()) {
  22. model.addAttribute("msg", "注册成功,我们已经向您的邮箱发送了一封激活邮件,请尽快激活!");
  23. model.addAttribute("target", "/index");
  24. return "/site/operate-result";
  25. } else {
  26. model.addAttribute("usernameMsg", map.get("usernameMsg"));
  27. model.addAttribute("passwordMsg", map.get("passwordMsg"));
  28. model.addAttribute("emailMsg", map.get("emailMsg"));
  29. return "/site/register";
  30. }
  31. }
  32. }

        更新operate-result页面。

  1. <!doctype html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  6. <link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
  7. <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
  8. <link rel="stylesheet" th:href="@{/css/global.css}" />
  9. <title>牛客网-操作结果</title>
  10. </head>
  11. <body class="bg-white">
  12. <div class="nk-container">
  13. <!-- 头部 -->
  14. <header class="bg-dark sticky-top" th:replace="index::header">
  15. <div class="container">
  16. <!-- 导航 -->
  17. <nav class="navbar navbar-expand-lg navbar-dark">
  18. <!-- logo -->
  19. <a class="navbar-brand" href="#"></a>
  20. <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
  21. <span class="navbar-toggler-icon"></span>
  22. </button>
  23. <!-- 功能 -->
  24. <div class="collapse navbar-collapse" id="navbarSupportedContent">
  25. <ul class="navbar-nav mr-auto">
  26. <li class="nav-item ml-3 btn-group-vertical">
  27. <a class="nav-link" href="../index.html">首页</a>
  28. </li>
  29. <li class="nav-item ml-3 btn-group-vertical">
  30. <a class="nav-link position-relative" href="letter.html">消息<span class="badge badge-danger">12</span></a>
  31. </li>
  32. <li class="nav-item ml-3 btn-group-vertical">
  33. <a class="nav-link" href="register.html">注册</a>
  34. </li>
  35. <li class="nav-item ml-3 btn-group-vertical">
  36. <a class="nav-link" href="login.html">登录</a>
  37. </li>
  38. <li class="nav-item ml-3 btn-group-vertical dropdown">
  39. <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
  40. <img src="http://images.nowcoder.com/head/1t.png" class="rounded-circle" style="width:30px;"/>
  41. </a>
  42. <div class="dropdown-menu" aria-labelledby="navbarDropdown">
  43. <a class="dropdown-item text-center" href="profile.html">个人主页</a>
  44. <a class="dropdown-item text-center" href="setting.html">账号设置</a>
  45. <a class="dropdown-item text-center" href="login.html">退出登录</a>
  46. <div class="dropdown-divider"></div>
  47. <span class="dropdown-item text-center text-secondary">nowcoder</span>
  48. </div>
  49. </li>
  50. </ul>
  51. <!-- 搜索 -->
  52. <form class="form-inline my-2 my-lg-0" action="search.html">
  53. <input class="form-control mr-sm-2" type="search" aria-label="Search" />
  54. <button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button>
  55. </form>
  56. </div>
  57. </nav>
  58. </div>
  59. </header>
  60. <!-- 内容 -->
  61. <div class="main">
  62. <div class="container mt-5">
  63. <div class="jumbotron">
  64. <p class="lead" th:text="${msg}">您的账号已经激活成功,可以正常使用了!</p>
  65. <hr class="my-4">
  66. <p>
  67. 系统会在 <span id="seconds" class="text-danger">8</span> 秒后自动跳转,
  68. 您也可以点此 <a id="target" th:href="@{${target}}" class="text-primary">链接</a>, 手动跳转!
  69. </p>
  70. </div>
  71. </div>
  72. </div>
  73. <!-- 尾部 -->
  74. <footer class="bg-dark">
  75. <div class="container">
  76. <div class="row">
  77. <!-- 二维码 -->
  78. <div class="col-4 qrcode">
  79. <img src="https://uploadfiles.nowcoder.com/app/app_download.png" class="img-thumbnail" style="width:136px;" />
  80. </div>
  81. <!-- 公司信息 -->
  82. <div class="col-8 detail-info">
  83. <div class="row">
  84. <div class="col">
  85. <ul class="nav">
  86. <li class="nav-item">
  87. <a class="nav-link text-light" href="#">关于我们</a>
  88. </li>
  89. <li class="nav-item">
  90. <a class="nav-link text-light" href="#">加入我们</a>
  91. </li>
  92. <li class="nav-item">
  93. <a class="nav-link text-light" href="#">意见反馈</a>
  94. </li>
  95. <li class="nav-item">
  96. <a class="nav-link text-light" href="#">企业服务</a>
  97. </li>
  98. <li class="nav-item">
  99. <a class="nav-link text-light" href="#">联系我们</a>
  100. </li>
  101. <li class="nav-item">
  102. <a class="nav-link text-light" href="#">免责声明</a>
  103. </li>
  104. <li class="nav-item">
  105. <a class="nav-link text-light" href="#">友情链接</a>
  106. </li>
  107. </ul>
  108. </div>
  109. </div>
  110. <div class="row">
  111. <div class="col">
  112. <ul class="nav btn-group-vertical company-info">
  113. <li class="nav-item text-white-50">
  114. 公司地址:北京市朝阳区大屯路东金泉时代3-2708北京牛客科技有限公司
  115. </li>
  116. <li class="nav-item text-white-50">
  117. 联系方式:010-60728802(电话)&nbsp;&nbsp;&nbsp;&nbsp;admin@nowcoder.com
  118. </li>
  119. <li class="nav-item text-white-50">
  120. 牛客科技©2018 All rights reserved
  121. </li>
  122. <li class="nav-item text-white-50">
  123. 京ICP备14055008号-4 &nbsp;&nbsp;&nbsp;&nbsp;
  124. <img src="http://static.nowcoder.com/company/images/res/ghs.png" style="width:18px;" />
  125. 京公网安备 11010502036488号
  126. </li>
  127. </ul>
  128. </div>
  129. </div>
  130. </div>
  131. </div>
  132. </div>
  133. </footer>
  134. </div>
  135. <script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
  136. <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
  137. <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
  138. <script>
  139. $(function(){
  140. setInterval(function(){
  141. var seconds = $("#seconds").text();
  142. $("#seconds").text(--seconds);
  143. if(seconds == 0) {
  144. location.href = $("#target").attr("href");
  145. }
  146. }, 1000);
  147. });
  148. </script>
  149. </body>
  150. </html>

在utill中创建一个CommunityConstant接口,用于表示注册状态码。

  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. /**
  15. * 默认状态的登录凭证的超时时间
  16. */
  17. int DEFAULT_EXPIRED_SECONDS = 3600 * 12;
  18. /**
  19. * 记住状态的登录凭证超时时间
  20. */
  21. int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100;
  22. }

        更新LoginController的功能,并更改Index.html文件相对应位置的模板参数。

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

4.3会话管理

         使用Cookie保存一些信息,可以使用浏览器插件查看Cookie(F12控制台)

         有关Cookie的一个小示例:

  1. // cookie示例
  2. @RequestMapping(path = "/cookie/set", method = RequestMethod.GET)
  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. @RequestMapping(path = "/cookie/get", method = RequestMethod.GET)
  16. @ResponseBody
  17. public String getCookie(@CookieValue("code") String code) {
  18. System.out.println(code);
  19. return "get cookie";
  20. }
  21. // session示例
  22. @RequestMapping(path = "/session/set", method = RequestMethod.GET)
  23. @ResponseBody
  24. public String setSession(HttpSession session) {
  25. session.setAttribute("id", 1);
  26. session.setAttribute("name", "Test");
  27. return "set session";
  28. }
  29. @RequestMapping(path = "/session/get", method = RequestMethod.GET)
  30. @ResponseBody
  31. public String getSession(HttpSession session) {
  32. System.out.println(session.getAttribute("id"));
  33. System.out.println(session.getAttribute("name"));
  34. return "get session";
  35. }

cookie和session的区别

后期可以考虑将Session数据传输到redis数据库中,用于保存一些登陆凭证

4.4生成验证码

        验证码使用Kaptcha jar包用于随机生成字符和图片。 在搜索Kaptcha配置文件,在resources文件包内的pom.xml文件中导入相关的配置文件依赖。在community目录下创建config文件包并创建KaptchaConfig配置类,使用@Configuration注解。

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

        在LoginController文件中完善验证码功能,并完善登陆页面.html文件中验证码的模板,并实现刷新验证码的功能。

  1. @RequestMapping(path = "/kaptcha", method = RequestMethod.GET)
  2. public void getKaptcha(HttpServletResponse response, HttpSession session) {
  3. // 生成验证码
  4. String text = kaptchaProducer.createText();
  5. BufferedImage image = kaptchaProducer.createImage(text);
  6. // 将验证码存入session
  7. session.setAttribute("kaptcha", text);
  8. // 将突图片输出给浏览器
  9. response.setContentType("image/png");
  10. try {
  11. OutputStream os = response.getOutputStream();
  12. ImageIO.write(image, "png", os);
  13. } catch (IOException e) {
  14. logger.error("响应验证码失败:" + e.getMessage());
  15. }
  16. }
  17. @RequestMapping(path = "/login", method = RequestMethod.POST)
  18. public String login(String username, String password, String code, boolean rememberme,
  19. Model model, HttpSession session, HttpServletResponse response) {
  20. // 检查验证码
  21. String kaptcha = (String) session.getAttribute("kaptcha");
  22. if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {
  23. model.addAttribute("codeMsg", "验证码不正确!");
  24. return "/site/login";
  25. }

        刷新验证码方法:

  1. <script>
  2. function refresh_kaptcha() {
  3. var path = CONTEXT_PATH + "/kaptcha?p=" + Math.random();
  4. $("#kaptcha").attr("src", path);
  5. }
  6. </script>

4.5登录、退出功能

密码使用MD5加密

         数据库中有关于登录凭证的ticket,使用这个ticket作为登陆凭证。涉及到数据库的操作,就要处理LoginTicketMapper。

LoginTicketMapper(这里是mapper的另外一种写法,不用再resource里面创建mapper文件):

  1. @Mapper
  2. public interface LoginTicketMapper {
  3. @Insert({
  4. "insert into login_ticket(user_id,ticket,status,expired) ",
  5. "values(#{userId},#{ticket},#{status},#{expired})"
  6. })
  7. @Options(useGeneratedKeys = true, keyProperty = "id")
  8. int insertLoginTicket(LoginTicket loginTicket);
  9. @Select({
  10. "select id,user_id,ticket,status,expired ",
  11. "from login_ticket where ticket=#{ticket}"
  12. })
  13. LoginTicket selectByTicket(String ticket);
  14. @Update({
  15. "<script>",
  16. "update login_ticket set status=#{status} where ticket=#{ticket} ",
  17. "<if test=\"ticket!=null\"> ",
  18. "and 1=1 ",
  19. "</if>",
  20. "</script>"
  21. })
  22. int updateStatus(String ticket, int status);
  23. }

        在UserService中增加登陆的方法,并在login.html中增加相应的修改:

  1. public Map<String, Object> login(String username, String password, int expiredSeconds) {
  2. Map<String, Object> map = new HashMap<>();
  3. // 空值处理
  4. if (StringUtils.isBlank(username)) {
  5. map.put("usernameMsg", "账号不能为空!");
  6. return map;
  7. }
  8. if (StringUtils.isBlank(password)) {
  9. map.put("passwordMsg", "密码不能为空!");
  10. return map;
  11. }
  12. // 验证账号
  13. User user = userMapper.selectByName(username);
  14. if (user == null) {
  15. map.put("usernameMsg", "该账号不存在!");
  16. return map;
  17. }
  18. // 验证状态
  19. if (user.getStatus() == 0) {
  20. map.put("usernameMsg", "该账号未激活!");
  21. return map;
  22. }
  23. // 验证密码
  24. password = CommunityUtil.md5(password + user.getSalt());
  25. if (!user.getPassword().equals(password)) {
  26. map.put("passwordMsg", "密码不正确!");
  27. return map;
  28. }
  29. // 生成登录凭证
  30. LoginTicket loginTicket = new LoginTicket();
  31. loginTicket.setUserId(user.getId());
  32. loginTicket.setTicket(CommunityUtil.generateUUID());
  33. loginTicket.setStatus(0);
  34. loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
  35. loginTicketMapper.insertLoginTicket(loginTicket);
  36. map.put("ticket", loginTicket.getTicket());
  37. return map;
  38. }

        增加Controller层中的登录方法:

  1. @RequestMapping(path = "/login", method = RequestMethod.POST)
  2. public String login(String username, String password, String code, boolean rememberme,
  3. Model model, HttpSession session, HttpServletResponse response) {
  4. // 检查验证码
  5. String kaptcha = (String) session.getAttribute("kaptcha");
  6. if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {
  7. model.addAttribute("codeMsg", "验证码不正确!");
  8. return "/site/login";
  9. }
  10. // 检查账号,密码
  11. int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
  12. Map<String, Object> map = userService.login(username, password, expiredSeconds);
  13. if (map.containsKey("ticket")) {
  14. Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
  15. cookie.setPath(contextPath);
  16. cookie.setMaxAge(expiredSeconds);
  17. response.addCookie(cookie);
  18. return "redirect:/index";
  19. } else {
  20. model.addAttribute("usernameMsg", map.get("usernameMsg"));
  21. model.addAttribute("passwordMsg", map.get("passwordMsg"));
  22. return "/site/login";
  23. }
  24. }
  25. @RequestMapping(path = "/logout", method = RequestMethod.GET)
  26. public String logout(@CookieValue("ticket") String ticket) {
  27. userService.logout(ticket);
  28. return "redirect:/login";
  29. }

4.6显示登陆信息

        使用拦截器实现        

在controller层下创建Interceptor文件包,

拦截器的方法

  1. // 在Controller之前执行
  2. @Override
  3. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  4. logger.debug("preHandle: " + handler.toString());
  5. return true;
  6. }
  7. // 在Controller之后执行
  8. @Override
  9. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  10. logger.debug("postHandle: " + handler.toString());
  11. }
  12. // 在TemplateEngine之后执行
  13. @Override
  14. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  15. logger.debug("afterCompletion: " + handler.toString());
  16. }

在cpnfig文件下创建配置类WebMvcConfig,配置拦截器。

  1. @Configuration
  2. public class WebMvcConfig implements WebMvcConfigurer {
  3. //测试拦截器方法
  4. @Autowired
  5. private AlphaInterceptor alphaInterceptor;
  6. @Autowired
  7. private LoginTicketInterceptor loginTicketInterceptor;
  8. @Autowired
  9. private LoginRequiredInterceptor loginRequiredInterceptor;
  10. @Override
  11. public void addInterceptors(InterceptorRegistry registry) {
  12. registry.addInterceptor(alphaInterceptor)
  13. .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg")
  14. .addPathPatterns("/register", "/login");
  15. registry.addInterceptor(loginTicketInterceptor)
  16. .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
  17. }
  18. }

在请求开始之前查询登录用户:

在Interceptor文件下创建LoginTicketInterceptor,实现拦截器的方法。

  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. // 从cookie中获取凭证
  10. String ticket = CookieUtil.getValue(request, "ticket");
  11. if (ticket != null) {
  12. // 查询凭证
  13. LoginTicket loginTicket = userService.findLoginTicket(ticket);
  14. // 检查凭证是否有效
  15. if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) {
  16. // 根据凭证查询用户
  17. User user = userService.findUserById(loginTicket.getUserId());
  18. // 在本次请求中持有用户
  19. hostHolder.setUser(user);
  20. }
  21. }
  22. return true;
  23. }
  24. @Override
  25. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  26. User user = hostHolder.getUser();
  27. if (user != null && modelAndView != null) {
  28. modelAndView.addObject("loginUser", user);
  29. }
  30. }
  31. @Override
  32. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  33. hostHolder.clear();
  34. }
  35. }

        在Util文件中创建Cookie工具,以及HostHolder工具用于代替session对象。完成过后修改相应的html文件。

  1. public class CookieUtil {
  2. public static String getValue(HttpServletRequest request, String name) {
  3. if (request == null || name == null) {
  4. throw new IllegalArgumentException("参数为空!");
  5. }
  6. Cookie[] cookies = request.getCookies();
  7. if (cookies != null) {
  8. for (Cookie cookie : cookies) {
  9. if (cookie.getName().equals(name)) {
  10. return cookie.getValue();
  11. }
  12. }
  13. }
  14. return null;
  15. }
  16. }
  1. /**
  2. * 持有用户信息,用于代替session对象.
  3. */
  4. @Component
  5. public class HostHolder {
  6. private ThreadLocal<User> users = new ThreadLocal<>();
  7. public void setUser(User user) {
  8. users.set(user);
  9. }
  10. public User getUser() {
  11. return users.get();
  12. }
  13. public void clear() {
  14. users.remove();
  15. }
  16. }

4.7账号设置

        用户自己上传头像,请求必须是POST请求,表单:enctype = "multipart/form-data",SpringMVC通过MutipartFile上传文件。

        创建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 contextPath;
  11. @Autowired
  12. private UserService userService;
  13. @Autowired
  14. private HostHolder hostHolder;
  15. @LoginRequired
  16. @RequestMapping(path = "/setting", method = RequestMethod.GET)
  17. public String getSettingPage() {
  18. return "/site/setting";
  19. }
  20. @LoginRequired
  21. @RequestMapping(path = "/upload", method = RequestMethod.POST)
  22. public String uploadHeader(MultipartFile headerImage, Model model) {
  23. if (headerImage == null) {
  24. model.addAttribute("error", "您还没有选择图片!");
  25. return "/site/setting";
  26. }
  27. String fileName = headerImage.getOriginalFilename();
  28. String suffix = fileName.substring(fileName.lastIndexOf("."));
  29. if (StringUtils.isBlank(suffix)) {
  30. model.addAttribute("error", "文件的格式不正确!");
  31. return "/site/setting";
  32. }
  33. // 生成随机文件名
  34. fileName = CommunityUtil.generateUUID() + suffix;
  35. // 确定文件存放的路径
  36. File dest = new File(uploadPath + "/" + fileName);
  37. try {
  38. // 存储文件
  39. headerImage.transferTo(dest);
  40. } catch (IOException e) {
  41. logger.error("上传文件失败: " + e.getMessage());
  42. throw new RuntimeException("上传文件失败,服务器发生异常!", e);
  43. }
  44. // 更新当前用户的头像的路径(web访问路径)
  45. // http://localhost:8080/community/user/header/xxx.png
  46. User user = hostHolder.getUser();
  47. String headerUrl = domain + contextPath + "/user/header/" + fileName;
  48. userService.updateHeader(user.getId(), headerUrl);
  49. return "redirect:/index";
  50. }
  51. @RequestMapping(path = "/header/{fileName}", method = RequestMethod.GET)
  52. public void getHeader(@PathVariable("fileName") String fileName, HttpServletResponse response) {
  53. // 服务器存放路径
  54. fileName = uploadPath + "/" + fileName;
  55. // 文件后缀
  56. String suffix = fileName.substring(fileName.lastIndexOf("."));
  57. // 响应图片
  58. response.setContentType("image/" + suffix);
  59. try (
  60. FileInputStream fis = new FileInputStream(fileName);
  61. OutputStream os = response.getOutputStream();
  62. ) {
  63. byte[] buffer = new byte[1024];
  64. int b = 0;
  65. while ((b = fis.read(buffer)) != -1) {
  66. os.write(buffer, 0, b);
  67. }
  68. } catch (IOException e) {
  69. logger.error("读取头像失败: " + e.getMessage());
  70. }
  71. }
  72. }

配置setting.html的静态资源路径

  1. <!doctype html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  6. <link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
  7. <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
  8. <link rel="stylesheet" th:href="@{/css/global.css}" />
  9. <link rel="stylesheet" th:href="@{/css/login.css}" />
  10. <title>牛客网-账号设置</title>
  11. </head>
  12. <body>
  13. <div class="nk-container">
  14. <!-- 头部 -->
  15. <header class="bg-dark sticky-top" th:replace="index::header">
  16. <div class="container">
  17. <!-- 导航 -->
  18. <nav class="navbar navbar-expand-lg navbar-dark">
  19. <!-- logo -->
  20. <a class="navbar-brand" href="#"></a>
  21. <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
  22. <span class="navbar-toggler-icon"></span>
  23. </button>
  24. <!-- 功能 -->
  25. <div class="collapse navbar-collapse" id="navbarSupportedContent">
  26. <ul class="navbar-nav mr-auto">
  27. <li class="nav-item ml-3 btn-group-vertical">
  28. <a class="nav-link" href="../index.html">首页</a>
  29. </li>
  30. <li class="nav-item ml-3 btn-group-vertical">
  31. <a class="nav-link position-relative" href="letter.html">消息<span class="badge badge-danger">12</span></a>
  32. </li>
  33. <li class="nav-item ml-3 btn-group-vertical">
  34. <a class="nav-link" href="register.html">注册</a>
  35. </li>
  36. <li class="nav-item ml-3 btn-group-vertical">
  37. <a class="nav-link" href="login.html">登录</a>
  38. </li>
  39. <li class="nav-item ml-3 btn-group-vertical dropdown">
  40. <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
  41. <img src="http://images.nowcoder.com/head/1t.png" class="rounded-circle" style="width:30px;"/>
  42. </a>
  43. <div class="dropdown-menu" aria-labelledby="navbarDropdown">
  44. <a class="dropdown-item text-center" href="profile.html">个人主页</a>
  45. <a class="dropdown-item text-center" href="setting.html">账号设置</a>
  46. <a class="dropdown-item text-center" href="login.html">退出登录</a>
  47. <div class="dropdown-divider"></div>
  48. <span class="dropdown-item text-center text-secondary">nowcoder</span>
  49. </div>
  50. </li>
  51. </ul>
  52. <!-- 搜索 -->
  53. <form class="form-inline my-2 my-lg-0" action="search.html">
  54. <input class="form-control mr-sm-2" type="search" aria-label="Search" />
  55. <button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button>
  56. </form>
  57. </div>
  58. </nav>
  59. </div>
  60. </header>
  61. <!-- 内容 -->
  62. <div class="main">
  63. <div class="container p-5 mt-3 mb-3">
  64. <!-- 上传头像 -->
  65. <h6 class="text-left text-info border-bottom pb-2">上传头像</h6>
  66. <form class="mt-5" method="post" enctype="multipart/form-data" th:action="@{/user/upload}">
  67. <div class="form-group row mt-4">
  68. <label for="head-image" class="col-sm-2 col-form-label text-right">选择头像:</label>
  69. <div class="col-sm-10">
  70. <div class="custom-file">
  71. <input type="file" th:class="|custom-file-input ${error!=null?'is-invalid':''}|"
  72. id="head-image" name="headerImage" lang="es" required="">
  73. <label class="custom-file-label" for="head-image" data-browse="文件">选择一张图片</label>
  74. <div class="invalid-feedback" th:text="${error}">
  75. 该账号不存在!
  76. </div>
  77. </div>
  78. </div>
  79. </div>
  80. <div class="form-group row mt-4">
  81. <div class="col-sm-2"></div>
  82. <div class="col-sm-10 text-center">
  83. <button type="submit" class="btn btn-info text-white form-control">立即上传</button>
  84. </div>
  85. </div>
  86. </form>
  87. <!-- 修改密码 -->
  88. <h6 class="text-left text-info border-bottom pb-2 mt-5">修改密码</h6>
  89. <form class="mt-5">
  90. <div class="form-group row mt-4">
  91. <label for="old-password" class="col-sm-2 col-form-label text-right">原密码:</label>
  92. <div class="col-sm-10">
  93. <input type="password" class="form-control" id="old-password" placeholder="请输入原始密码!" required>
  94. <div class="invalid-feedback">
  95. 密码长度不能小于8位!
  96. </div>
  97. </div>
  98. </div>
  99. <div class="form-group row mt-4">
  100. <label for="new-password" class="col-sm-2 col-form-label text-right">新密码:</label>
  101. <div class="col-sm-10">
  102. <input type="password" class="form-control" id="new-password" placeholder="请输入新的密码!" required>
  103. <div class="invalid-feedback">
  104. 密码长度不能小于8位!
  105. </div>
  106. </div>
  107. </div>
  108. <div class="form-group row mt-4">
  109. <label for="confirm-password" class="col-sm-2 col-form-label text-right">确认密码:</label>
  110. <div class="col-sm-10">
  111. <input type="password" class="form-control" id="confirm-password" placeholder="再次输入新密码!" required>
  112. <div class="invalid-feedback">
  113. 两次输入的密码不一致!
  114. </div>
  115. </div>
  116. </div>
  117. <div class="form-group row mt-4">
  118. <div class="col-sm-2"></div>
  119. <div class="col-sm-10 text-center">
  120. <button type="submit" class="btn btn-info text-white form-control">立即保存</button>
  121. </div>
  122. </div>
  123. </form>
  124. </div>
  125. </div>
  126. <!-- 尾部 -->
  127. <footer class="bg-dark">
  128. <div class="container">
  129. <div class="row">
  130. <!-- 二维码 -->
  131. <div class="col-4 qrcode">
  132. <img src="https://uploadfiles.nowcoder.com/app/app_download.png" class="img-thumbnail" style="width:136px;" />
  133. </div>
  134. <!-- 公司信息 -->
  135. <div class="col-8 detail-info">
  136. <div class="row">
  137. <div class="col">
  138. <ul class="nav">
  139. <li class="nav-item">
  140. <a class="nav-link text-light" href="#">关于我们</a>
  141. </li>
  142. <li class="nav-item">
  143. <a class="nav-link text-light" href="#">加入我们</a>
  144. </li>
  145. <li class="nav-item">
  146. <a class="nav-link text-light" href="#">意见反馈</a>
  147. </li>
  148. <li class="nav-item">
  149. <a class="nav-link text-light" href="#">企业服务</a>
  150. </li>
  151. <li class="nav-item">
  152. <a class="nav-link text-light" href="#">联系我们</a>
  153. </li>
  154. <li class="nav-item">
  155. <a class="nav-link text-light" href="#">免责声明</a>
  156. </li>
  157. <li class="nav-item">
  158. <a class="nav-link text-light" href="#">友情链接</a>
  159. </li>
  160. </ul>
  161. </div>
  162. </div>
  163. <div class="row">
  164. <div class="col">
  165. <ul class="nav btn-group-vertical company-info">
  166. <li class="nav-item text-white-50">
  167. 公司地址:北京市朝阳区大屯路东金泉时代3-2708北京牛客科技有限公司
  168. </li>
  169. <li class="nav-item text-white-50">
  170. 联系方式:010-60728802(电话)&nbsp;&nbsp;&nbsp;&nbsp;admin@nowcoder.com
  171. </li>
  172. <li class="nav-item text-white-50">
  173. 牛客科技©2018 All rights reserved
  174. </li>
  175. <li class="nav-item text-white-50">
  176. 京ICP备14055008号-4 &nbsp;&nbsp;&nbsp;&nbsp;
  177. <img src="http://static.nowcoder.com/company/images/res/ghs.png" style="width:18px;" />
  178. 京公网安备 11010502036488号
  179. </li>
  180. </ul>
  181. </div>
  182. </div>
  183. </div>
  184. </div>
  185. </div>
  186. </footer>
  187. </div>
  188. <script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
  189. <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
  190. <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
  191. <script src="https://cdn.jsdelivr.net/npm/bs-custom-file-input/dist/bs-custom-file-input.js" crossorigin="anonymous"></script>
  192. <script th:src="@{/js/global.js}"></script>
  193. <script>
  194. $(function(){
  195. bsCustomFileInput.init();
  196. });
  197. </script>
  198. </body>
  199. </html>

修改配置文件中,上传文件的保存位置。

community.path.upload=d:/work/data/upload

4.8检查登陆状态

        防止未登录访问某些资源,可以使用拦截器和注解。

       使用注解,在community创建annotation文件创建Login注解。

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

        给Usercontroller中的方法,加入@LoginRequire注解,并创建拦截器拦截带注解的方法。

创建一个新的拦截器LoginRequireInterceptor。

  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. //保证拦截的是方法
  8. if (handler instanceof HandlerMethod) {
  9. HandlerMethod handlerMethod = (HandlerMethod) handler;
  10. Method method = handlerMethod.getMethod();
  11. LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);
  12. if (loginRequired != null && hostHolder.getUser() == null) {
  13. response.sendRedirect(request.getContextPath() + "/login");
  14. return false;
  15. }
  16. }
  17. return true;
  18. }
  19. }

项目代码及相关资源:

麻烦点点小星星!!!!!!

CSDN下载需要积分

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发