项目按照MVC的编程思想,分为dao层,service层,controller层和view层进行开发,本章实现仿牛客社区的首页访问功能。
这里对项目目前涉及到的实体类进行介绍,分别为用户User类,帖子DiscussPost类,用于给前端分页封装的bean:分页Page类。
User类用于对注册用户的相关属性进行描述,在工程中创建entity包,创建User类,相关代码见下。
package com.gerrard.community.entity;
import java.util.Date;
public class User {
private int id;
private String username;
private String password;
private String salt; //盐,添加在用户password字段后面对密码进行扰动,并将最终结果进行MD5加密,提升安全性
private String email; //用户注册的邮箱
private int type; //0-普通用户; 1-超级管理员; 2-版主;
private int status; //0-未激活; 1-已激活;
private String activationCode; //激活码,作用待阐述
private String headerUrl; //用户头像Url地址
private Date createTime; //用户注册时间
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getActivationCode() {
return activationCode;
}
public void setActivationCode(String activationCode) {
this.activationCode = activationCode;
}
public String getHeaderUrl() {
return headerUrl;
}
public void setHeaderUrl(String headerUrl) {
this.headerUrl = headerUrl;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", salt='" + salt + '\'' +
", email='" + email + '\'' +
", type=" + type +
", status=" + status +
", activationCode='" + activationCode + '\'' +
", headerUrl='" + headerUrl + '\'' +
", createTime=" + createTime +
'}';
}
}
封装帖子的相关信息,在entity包中创建DiscussPost类,相关代码见下【相关的get,set,toString方法这里省略,后同】。
package com.gerrard.community.entity;
import java.util.Date;
public class DiscussPost {
private int id;
private int userId;
private String title;
private String content;
private int type; //0-普通; 1-置顶;
private int status; //0-正常; 1-精华; 2-拉黑;
private Date createTime;
private int commentCount;
private double score;
}
Page类对分页信息进行封装,方便前端实现分页功能【帖子,评论等需要进行分页之初都可以借助此实体类完成】。
Page类中可以卸载部分计算,根据Page类中的current,limit,rows字段,提供getOffset方法返回当前页中第一条数据在数据表中的行号,提供getTotal方法返回总页数,提供getFrom方法返回"前端页面底部分页栏中多个小格格中第一个格格的序号”,提供getTo方法返回“前端页面底部分页栏中多个小格格中最后一格格的序号“。
在entity包中创建Page类,相关代码见下。
package com.gerrard.community.entity;
//封装分页相关信息
public class Page {
//当前页码,默认为第一页
private int current=1;
//显示上限,每页展示多少条记录,默认为10
private int limit=10;
//数据总数(用于计算总页数),数据总行数,一般从MySQL表中查找
private int rows;
//所有页码访问的根路径
private String path;
// public Page() {
// System.out.println("construct");
// }
public int getCurrent() {
return current;
}
public void setCurrent(int current) {
if(current>=1){
this.current = current;
}
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
if(limit>=1 && limit <=100) { //将合法性判断下沉至entity端
this.limit = limit; //超出范围则还是默认一页显示10条
}
}
public int getRows() {
return rows;
}
public void setRows(int rows) {
if(rows>=0){
this.rows = rows;
}
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
/**
* 获取当前页的起始行
*
* @return
*/
public int getOffset(){
//当前页的第一条数据记录在多少行
return (current-1)*limit;
}
/**
* 获取总页数
*
* @return
*/
public int getTotal(){
// |-- rows/limit--| 向上取整操作
if(rows % limit ==0){
return rows/limit;
}else{
return rows/limit+1;
}
}
/**
* 获取起始页码
*
* @return
*/
public int getFrom(){
int from=current-2;
return from<1?1:from;
}
/**
* 获取结束页码
*
* @return
*/
public int getTo(){
int to=current+2;
int total=getTotal();
return to>total?total:to;
}
}
application.properties内容见下。
# ServerProperties
server.port=8080
server.servlet.context-path=/community
# ThymeleafProperties
spring.thymeleaf.cache=false
# DataSourceProperties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/community?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=30000
# MybatisProperties
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.gerrard.community.entity
mybatis.configuration.useGeneratedKeys=true
mybatis.configuration.mapUnderscoreToCamelCase=true
#logger
#logging.level.com.gerrard.community=debug
#logging.file=d:/gerrardProjects/data/gerrard/community.log
dao层主要的功能为与MySQL进行交互,对User MySQL表中的用户信息进行CRUD(create,retrieve,update,delete)操作,
【值得注意的是,本项目不涉及到delete操作,因为这会最终损失用户相关的数据,如果未来开发出某种功能需要用到历史数据,则数据的缺失会影响功能的使用[比如统计用户最近一年来的访问帖子的热度排行]。综上考虑,本项目对删除功能所采取的措施为:修改实体的状态字段,比如用户退出后,将用户的登录凭证状态字段置为1,表示凭证已失效】
UserMapper需要完成如下功能。
1.根据用户的id查询到用户的实体类®;
2.根据用户的username查询到用户的实体类®;
3.根据用户的email查询到用户的实体类®;
4.将用户实体类插入到User MySQL表中©;
5.更新用户的登录状态status(U)【0表示用户账号未激活,1表示用户账号已激活,未来用户点击激活邮件的链接时会使用此方法将登录状态改为1】;
6.更新用户的头像地址headUrl(U);
7.更新用户的密码password(U);
在工程中创建dao包,创建UserMapper接口,代码见下。
package com.gerrard.community.dao;
import com.gerrard.community.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Mapper
//@Repository
public interface UserMapper {
User selectById(int id);
User selectByName(String username);
User selectByEmail(String email);
int insertUser(User user);
int updateStatus(@Param("id")int id, @Param("status")int status);
int updateHeader(@Param("id")int id,@Param("headerUrl")String headerUrl);
int updatePassword(@Param("id")int id,@Param("password")String password);
}
创建UserMapper.xml,代码见下。
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gerrard.community.dao.UserMapper">
<sql id="insertFields">
username,password,salt,email,type,status,activation_code,header_url,create_time
sql>
<sql id="selectFields">
id,username,password,salt,email,type,status,activation_code,header_url,create_time
sql>
<select id="selectById" resultType="User">
select<include refid="selectFields">include>
from user
where id=#{id}
select>
<select id="selectByName" resultType="User">
select <include refid="selectFields">include>
from user
where username = #{username}
select>
<select id="selectByEmail" resultType="User">
select <include refid="selectFields">include>
from user
where email = #{email}
select>
<insert id="insertUser" parameterType="User" keyProperty="id">
insert into user(<include refid="insertFields">include>)
values(#{username},#{password},#{salt},#{email},#{type},#{status},#{activationCode},#{headerUrl},#{createTime})
insert>
<update id="updateStatus">
update user set status=#{status} where id=#{id}
update>
<update id="updateHeader">
update user set header_url=#{headerUrl} where id=#{id}
update>
<update id="updatePassword">
update user set password=#{password} where id=#{id}
update>
mapper>
DiscussPostMapper对DiscussPost MySQL表中的相关数据进行CRUD操作,这里先实现两个功能。
1.根据userId,offset,limit信息查询帖子集合【offset为当前页码第一条数据在MySQL数据库中的行数,limit为查询的消息记录数】,返回对应的实体类集合。
2.根据userId查询帖子数量。
在dao包中添加DiscussPostMapper接口,相关代码见下。
package com.gerrard.community.dao;
import com.gerrard.community.entity.DiscussPost;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface DiscussPostMapper {
List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit);
// @Param注解用于给参数取别名,
// 如果只有一个参数,并且在里使用,则必须加别名.
int selectDiscussPostRows(@Param("userId") int userId);
}
创建DiscussPostMapper.xml,代码见下。
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gerrard.community.dao.DiscussPostMapper">
<sql id="selectFields">
id, user_id, title, content, type, status, create_time, comment_count, score
sql>
<sql id="insertFields">
user_id, title, content, type, status, create_time, comment_count, score
sql>
<select id="selectDiscussPosts" resultType="DiscussPost">
select <include refid="selectFields">include>
from discuss_post
where status !=2
<if test="userId!=0">
and user_id=#{userId}
if>
order by type desc,create_time desc
limit #{offset},#{limit}
select>
<select id="selectDiscussPostRows" resultType="int">
select count(id)
from discuss_post
where status!=2
<if test="userId!=0">
and user_id=#{userId}
if>
select>
mapper>
UserService主要对Mapper的方法进行排列组合,亦或自己提供额外方法来实现特定的功能,这里需要实现的服务为根据与User的id返回User实体类。在工程中创建service包,创建UserService类,代码见下。
package com.gerrard.community.service;
import com.gerrard.community.dao.UserMapper;
import com.gerrard.community.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService implements CommunityConstant {
@Autowired
private UserMapper userMapper;
public User findUserById(int id){
return userMapper.selectById(id);
}
DiscussPost类提供与帖子操作相关的服务,在controller层中进行调用,这里需要实现的服务是根据userId,offset,limit信息查询帖子集合;根据userId查询帖子集合总行数。在工程中创建DiscussPost类,相关代码见下。
package com.gerrard.community.service;
import com.gerrard.community.dao.DiscussPostMapper;
import com.gerrard.community.entity.DiscussPost;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DiscussPostService {
@Autowired
private DiscussPostMapper discussPostMapper;
public List<DiscussPost> findDiscussPosts(int userId, int offset, int limit) {
return discussPostMapper.selectDiscussPosts(userId, offset, limit);
}
public int findDiscussPostRows(int userId) {
return discussPostMapper.selectDiscussPostRows(userId);
}
}
用户操作浏览器的过程相当于一个”请求-响应“的过程,Controller层正好对应于此块,是连接项目前端和后端的纽带。
UserController这里完成的主要功能为接收浏览器发送的“/index” 的get请求,响应thymeleaf的模板页面index.html,即开发社区的首页页面。在工程中创建controller包,创建HomeController类,相关代码如下。
未登录状态UserId为0,代码将要返回未登陆状态查询到的所有帖子信息。
package com.gerrard.community.controller;
import com.gerrard.community.entity.DiscussPost;
import com.gerrard.community.entity.Page;
import com.gerrard.community.entity.User;
import com.gerrard.community.service.DiscussPostService;
import com.gerrard.community.service.LikeService;
import com.gerrard.community.service.MessageService;
import com.gerrard.community.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.gerrard.community.util.CommunityConstant.ENTITY_TYPE_POST;
@Controller
public class HomeController {
@Autowired
private DiscussPostService discussPostService;
@Autowired
private UserService userService;
@RequestMapping(path="/index",method= RequestMethod.GET)
public String getIndexPage(Model model, Page page){
// 方法调用钱,SpringMVC会自动实例化Model和Page,并将Page注入Model.
// 所以,在thymeleaf中可以直接访问Page对象中的数据.
// System.out.println(page.hashCode());
page.setRows(discussPostService.findDiscussPostRows(0));
page.setPath("/index");
List<DiscussPost> list=discussPostService.findDiscussPosts(0,page.getOffset(),page.getLimit());
//对list信息进行增益,可以想象对数据进行纵向扩展,一维->二维
List<Map<String,Object>> discussPosts=new ArrayList<>();
if(list!=null){
for(DiscussPost post:list){
Map<String,Object> map=new HashMap<>();
map.put("post",post);
User user=userService.findUserById(post.getUserId());
map.put("user",user);
discussPosts.add(map);
}
}
model.addAttribute("discussPosts",discussPosts);
return "/index";
}
}
View层用于发送Http get/post请求,Controller层对相应的请求捕获进行处理,与数据库交互运算后将结果传回给前端View层,View层根据后端传回的信息,将相关的信息使用动态网页技术展示在页面中。
这里使用前端模板引擎thymeleaf对数据进行动态展示,将传统的静态网页改造为动态thymeleaf网页一般需要如下几个步骤。以牛客社区首页index.html为例:
1.引入th库
2.相对路径需要和th绑定;
3.业务修改相关标签,包含动态展示数据及开发分页功能。
动态展示帖子数据的代码见下。
<ul class="list-unstyled">
<li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}">
<a th:href="@{|/user/profile/${map.user.id}|}">
<img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;">
a>
<div class="media-body">
<h6 class="mt-0 mb-3">
<a th:href="@{|/discuss/detail/${map.post.id}|}" th:utext="${map.post.title}">备战春招,面试刷题跟他复习,一个月全搞定!a>
<span class="badge badge-secondary bg-primary" th:if="${map.post.type==1}">置顶span>
<span class="badge badge-secondary bg-danger" th:if="${map.post.status==1}">精华span>
h6>
<div class="text-muted font-size-12">
<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:18b>
<ul class="d-inline float-right">
<li class="d-inline ml-2">赞 <span th:text="${map.likeCount}">11span>li>
<li class="d-inline ml-2">|li>
<li class="d-inline ml-2">回帖 <i th:text="${map.post.commentCount}">7i>li>
ul>
div>
div>
li>
ul>
分页功能的具体代码见下。
解释一下
th:href="@{${page.path}(current=${page.current-1})
浏览器输入/index,发送get请求,请求经过HomeController的getIndexPage方法后,将index.html动态页面返回至浏览器,这个动态页面的底部展示有分页信息,把每页数据请求的Url地址动态写死,下次点击访问别页时会再次经过HomeController的getIndexPage方法,创建Page对象时,会将current信息赋值给Page对象中的current字段,进而影响到查出的数据库帖子集合信息。
<nav class="mt-5" th:if="${page.rows>0}" th:fragment="pagination">
<ul class="pagination justify-content-center">
<li class="page-item">
<a class="page-link" th:href="@{${page.path}(current=1)}">首页a>
li>
<li th:class="|page-item ${page.current==1?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current-1})}">上一页a>li>
<li th:class="|page-item ${i==page.current?'active':''}|" th:each="i:${#numbers.sequence(page.from,page.to)}">
<a class="page-link" th:href="@{${page.path}(current=${i})}" th:text="${i}">1a>
li>
<li th:class="|page-item ${page.current==page.total?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current+1})}">下一页a>
li>
<li class="page-item">
<a class="page-link" th:href="@{${page.path}(current=${page.total})}">末页a>
li>
ul>
nav>
改为动态页面后的index.html完整代码见下【这里多添加了后续实现的功能】。
doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" th:href="@{/css/global.css}" />
<title>牛客网-首页title>
head>
<body>
<div class="nk-container">
<header class="bg-dark sticky-top" th:fragment="header">
<div class="container">
<nav class="navbar navbar-expand-lg navbar-dark">
<a class="navbar-brand" href="#">a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon">span>
button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" th:href="@{/index}">首页a>
li>
<li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser!=null}">
<a class="nav-link position-relative" th:href="@{/letter/list}">消息<span class="badge badge-danger" th:text="${messageUnRead}" th:if="${messageUnRead!=0}">12span>a>
li>
<li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser==null}">
<a class="nav-link" th:href="@{/register}">注册a>
li>
<li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser==null}">
<a class="nav-link" th:href="@{/login}">登录a>
li>
<li class="nav-item ml-3 btn-group-vertical dropdown" th:if="${loginUser!=null}">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img th:src="${loginUser.headerUrl}" class="rounded-circle" style="width:30px;"/>
a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item text-center" th:href="@{|/user/profile/${loginUser.id}|}">个人主页a>
<a class="dropdown-item text-center" th:href="@{/user/setting}">账号设置a>
<a class="dropdown-item text-center" th:href="@{/logout}">退出登录a>
<div class="dropdown-divider">div>
<span class="dropdown-item text-center text-secondary" th:utext="${loginUser.username}">nowcoderspan>
div>
li>
ul>
<form class="form-inline my-2 my-lg-0" method="get" th:action="@{/search}">
<input class="form-control mr-sm-2" type="search" aria-label="Search" name="keyword" th:value="${keyword}"/>
<button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索button>
form>
div>
nav>
div>
header>
<div class="main">
<div class="container">
<div class="position-relative">
<ul class="nav nav-tabs mb-3">
<li class="nav-item">
<a th:class="|nav-link ${orderMode==0?'active':''}|" th:href="@{/index(orderMode=0)}">最新a>
li>
<li class="nav-item">
<a th:class="|nav-link ${orderMode==1?'active':''}|" th:href="@{/index(orderMode=1)}">最热a>
li>
ul>
<button type="button" class="btn btn-primary btn-sm position-absolute rt-0" data-toggle="modal" data-target="#publishModal" th:if="${loginUser!=null}">我要发布button>
div>
<div class="modal fade" id="publishModal" tabindex="-1" role="dialog" aria-labelledby="publishModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="publishModalLabel">新帖发布h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×span>
button>
div>
<div class="modal-body">
<form>
<div class="form-group">
<label for="recipient-name" class="col-form-label">标题:label>
<input type="text" class="form-control" id="recipient-name">
div>
<div class="form-group">
<label for="message-text" class="col-form-label">正文:label>
<textarea class="form-control" id="message-text" rows="15">textarea>
div>
form>
div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消button>
<button type="button" class="btn btn-primary" id="publishBtn">发布button>
div>
div>
div>
div>
<div class="modal fade" id="hintModal" tabindex="-1" role="dialog" aria-labelledby="hintModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="hintModalLabel">提示h5>
div>
<div class="modal-body" id="hintBody">
发布完毕!
div>
div>
div>
div>
<ul class="list-unstyled">
<li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}">
<a th:href="@{|/user/profile/${map.user.id}|}">
<img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;">
a>
<div class="media-body">
<h6 class="mt-0 mb-3">
<a th:href="@{|/discuss/detail/${map.post.id}|}" th:utext="${map.post.title}">备战春招,面试刷题跟他复习,一个月全搞定!a>
<span class="badge badge-secondary bg-primary" th:if="${map.post.type==1}">置顶span>
<span class="badge badge-secondary bg-danger" th:if="${map.post.status==1}">精华span>
h6>
<div class="text-muted font-size-12">
<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:18b>
<ul class="d-inline float-right">
<li class="d-inline ml-2">赞 <span th:text="${map.likeCount}">11span>li>
<li class="d-inline ml-2">|li>
<li class="d-inline ml-2">回帖 <i th:text="${map.post.commentCount}">7i>li>
ul>
div>
div>
li>
ul>
<nav class="mt-5" th:if="${page.rows>0}" th:fragment="pagination">
<ul class="pagination justify-content-center">
<li class="page-item">
<a class="page-link" th:href="@{${page.path}(current=1)}">首页a>
li>
<li th:class="|page-item ${page.current==1?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current-1})}">上一页a>li>
<li th:class="|page-item ${i==page.current?'active':''}|" th:each="i:${#numbers.sequence(page.from,page.to)}">
<a class="page-link" th:href="@{${page.path}(current=${i})}" th:text="${i}">1a>
li>
<li th:class="|page-item ${page.current==page.total?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current+1})}">下一页a>
li>
<li class="page-item">
<a class="page-link" th:href="@{${page.path}(current=${page.total})}">末页a>
li>
ul>
nav>
div>
div>
<footer class="bg-dark">
<div class="container">
<div class="row">
<div class="col-4 qrcode">
<img src="https://uploadfiles.nowcoder.com/app/app_download.png" class="img-thumbnail" style="width:136px;" />
div>
<div class="col-8 detail-info">
<div class="row">
<div class="col">
<ul class="nav">
<li class="nav-item">
<a class="nav-link text-light" href="#">关于我们a>
li>
<li class="nav-item">
<a class="nav-link text-light" href="#">加入我们a>
li>
<li class="nav-item">
<a class="nav-link text-light" href="#">意见反馈a>
li>
<li class="nav-item">
<a class="nav-link text-light" href="#">企业服务a>
li>
<li class="nav-item">
<a class="nav-link text-light" href="#">联系我们a>
li>
<li class="nav-item">
<a class="nav-link text-light" href="#">免责声明a>
li>
<li class="nav-item">
<a class="nav-link text-light" href="#">友情链接a>
li>
ul>
div>
div>
<div class="row">
<div class="col">
<ul class="nav btn-group-vertical company-info">
<li class="nav-item text-white-50">
公司地址:北京市朝阳区大屯路东金泉时代3-2708北京牛客科技有限公司
li>
<li class="nav-item text-white-50">
联系方式:010-60728802(电话) [email protected]
li>
<li class="nav-item text-white-50">
牛客科技©2018 All rights reserved
li>
<li class="nav-item text-white-50">
京ICP备14055008号-4
<img src="http://static.nowcoder.com/company/images/res/ghs.png" style="width:18px;" />
京公网安备 11010502036488号
li>
ul>
div>
div>
div>
div>
div>
footer>
div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous">script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous">script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous">script>
<script th:src="@{/js/global.js}">script>
<script th:src="@{js/index.js}">script>
body>
html>
运行CommunityApplication.java,在浏览器中输入URL地址,可返回牛客网首页。
分页功能演示:
写在最后:页面中相关的前端代码是本项目最终的代码,前端的代码在项目整个开发过程中经历了好几轮重构与迭代,因此最好用git对本项目进行版本控制。但由于笔者当时开发时时间较为仓促,没有使用git对项目进行版本控制,前端部分的代码和界面演示这一环节采用的是项目完成后的代码,在后期有时间的情况下,笔者会将笔记涉及到这一部分的内容进行优化。