(仿牛客社区项目)Java开发笔记1.1:完成开发首页功能

47 篇文章 30 订阅
订阅专栏

完成仿牛客社区项目开发首页功能

项目按照MVC的编程思想,分为dao层,service层,controller层和view层进行开发,本章实现仿牛客社区的首页访问功能。

1.实体引入

这里对项目目前涉及到的实体类进行介绍,分别为用户User类,帖子DiscussPost类,用于给前端分页封装的bean:分页Page类。

1.1User类

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

1.2DiscussPost类

封装帖子的相关信息,在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;
}

1.3Page类

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

}

2.配置文件设定

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

3.dao层

3.1UserMapper的功能

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

3.2UserMapper的实现

在工程中创建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,代码见下。

<?xml version="1.0" encoding="UTF-8"?>
<!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>

3.3DiscussPostMapper的功能

DiscussPostMapper对DiscussPost MySQL表中的相关数据进行CRUD操作,这里先实现两个功能。

1.根据userId,offset,limit信息查询帖子集合【offset为当前页码第一条数据在MySQL数据库中的行数,limit为查询的消息记录数】,返回对应的实体类集合。

2.根据userId查询帖子数量。

3.4DiscussPostMapper的实现

在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注解用于给参数取别名,
    // 如果只有一个参数,并且在<if>里使用,则必须加别名.
    int selectDiscussPostRows(@Param("userId") int userId);

}

创建DiscussPostMapper.xml,代码见下。

<?xml version="1.0" encoding="UTF-8" ?>
<!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="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>

4.Service层

4.1UserService类

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

4.2DiscussPost类

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

}

5.HomeController层

用户操作浏览器的过程相当于一个”请求-响应“的过程,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";
    }

}

6.View层

View层用于发送Http get/post请求,Controller层对相应的请求捕获进行处理,与数据库交互运算后将结果传回给前端View层,View层根据后端传回的信息,将相关的信息使用动态网页技术展示在页面中。

这里使用前端模板引擎thymeleaf对数据进行动态展示,将传统的静态网页改造为动态thymeleaf网页一般需要如下几个步骤。以牛客社区首页index.html为例:

1.引入th库

NowCoder_1_1

2.相对路径需要和th绑定;

image-20220716210942628

NowCoder_1_5

3.业务修改相关标签,包含动态展示数据及开发分页功能。

NowCoder_1_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:18</b>
							<ul class="d-inline float-right">
                                <li class="d-inline ml-2"><span th:text="${map.likeCount}">11</span></li>
								<li class="d-inline ml-2">|</li>
								<li class="d-inline ml-2">回帖 <i th:text="${map.post.commentCount}">7</i></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}">1</a>
					</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">

    <!--访问该页面时,在此处生成CSRF令牌.-->
    <!--	<meta name="_csrf" th:content="${_csrf.token}">-->
    <!--	<meta name="_csrf_header" th:content="${_csrf.headerName}">-->

	<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">
				<!-- logo -->
				<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}">12</span></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}">nowcoder</span>
							</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">&times;</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:18</b>
							<ul class="d-inline float-right">
                                <li class="d-inline ml-2"><span th:text="${map.likeCount}">11</span></li>
								<li class="d-inline ml-2">|</li>
								<li class="d-inline ml-2">回帖 <i th:text="${map.post.commentCount}">7</i></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}">1</a>
					</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(电话)&nbsp;&nbsp;&nbsp;&nbsp;admin@nowcoder.com
								</li>
								<li class="nav-item text-white-50">
									牛客科技©2018 All rights reserved
								</li>
								<li class="nav-item text-white-50">
									京ICP备14055008号-4 &nbsp;&nbsp;&nbsp;&nbsp;
									<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>

7.功能演示

运行CommunityApplication.java,在浏览器中输入URL地址,可返回牛客网首页。

NowCoder_1_6

分页功能演示:

NowCoder_1_7

NowCoder_1_8

写在最后:页面中相关的前端代码是本项目最终的代码,前端的代码在项目整个开发过程中经历了好几轮重构与迭代,因此最好用git对本项目进行版本控制。但由于笔者当时开发时时间较为仓促,没有使用git对项目进行版本控制,前端部分的代码和界面演示这一环节采用的是项目完成后的代码,在后期有时间的情况下,笔者会将笔记涉及到这一部分的内容进行优化。

从零开始—仿牛客网讨论社区项目(一)
m0_67401545的博客
07-29 799
搜索MySqlMaven配置文件,在resources文件包内的pom.xml文件中导入相关的配置文件依赖,并在application.properties文件中配置相关的参数。使用Mapper注解,并在mapper文件下创建user-mapp.xml,使得方法与Sql语句相关联,Mybatis的xml配置可以在官网找到相关的配置。在community文件下创建util工具文件包,并在util包中创建MailClient类使用@Component注解,并创建发送邮件的方法。...
基于SpringBoot 仿牛客网讨论社区项目
04-03
仿牛客网讨论社区项目: 主要技术架构: SpringBoot Spring SpringMVC MyBatis Redis Kakfa Elasticsearch Spring Security Spring Actator 基于SpringBoot社区平台,实现了牛客网讨论区的功能。实现了邮箱注册...
仿牛客项目(五)
ahtohlove的博客
03-29 524
仿牛客项目,发送帖子、评论帖子、查看评论、查看私信、发送私信
仿牛客社区项目Java开发笔记6.1:Spring整合Elasticsearch
weixin_45700663的博客
07-24 717
仿牛客社区项目Java开发笔记6.1:Spring整合Elasticsearch
仿牛客社区项目笔记-项目发布与总结
qq_35484331的博客
05-24 1310
仿牛客社区项目笔记-项目发布与总结项目发布与总结1. 单元测试 项目发布与总结 分为。 1. 单元测试 引入Spring test 起步依赖。 保证测试方法的独立性。常用注解有: @BeforeClass:在测试类加载之前执行 @AfterClass: 在测试类销毁时执行 @Before: 在测试方法执行前执行(创建测试数据) @After: 在测试方法执行后执行(删除测试数据) @Test:测试方法(执行需要测试的方法) ...
仿牛客社区项目笔记
qq_35484331的博客
05-26 2677
仿牛客社区项目笔记 目录: 1. 首页模块 2. 注册登录模块 3. 帖子模块(核心) 4. 事务管理(服务于“添加评论”) 5. 统一处理异常和日志 6. 点赞关注模块(引入Redis) 7. 优化登陆模块(使用Redis) 8. 构建TB级异步消息系统(引入Kafka) 9. 分布式搜索引擎(引入Elasticsearch) 10. 构建安全高效的企业服务(引入Spring Security) 11. 项目发布与总结 ...
Java仿牛客系统源码.zip
06-06
Java仿牛客系统源码.zip
牛客网 牛客Java高级工程师 - 高薪求职项目课vol.4.zip
最新发布
01-12
软件开发设计:应用软件开发、系统软件开发、移动应用开发、网站开发C++、Java、python、web、C#等语言的项目开发与学习资料 硬件与设备:单片机、EDA、proteus、RTOS、包括计算机硬件、服务器、网络设备、存储设备...
基于SpringBoot仿牛客论坛源码.zip
05-21
开发环境 构建工具:Apache Maven 集成开发工具:IntelliJ IDEA 数据库:MySQL、Redis 应用服务器:Apache Tomcat 版本控制工具:Git 软件架构 基础功能 邮箱设置 启用客户端SMTP服务 Spring Email: ——导入jar包...
仿牛客博客项目源码(有部分功能没有实现) https://gitee.com/xudahu/community
05-25
仿牛客UI源码
01-26
仿牛客UI(张俊峰) 1.图标来自牛客app 2.大致实现UI效果 3.实现抽提 底部:RelativeLayout(学习、社区、消息、我的牛客) + 中间 自定义ViewPager(禁止滑动) 一、学习界面: (1) 标题模块:牛客 (2) 图片滑动模块:ViewPager+Pager (3) 签到模块:显示(已打卡、今日刷题、今日学习、共打卡) (4) 学习模块:Linearlayout(可用GridView)(专题练习、公司套题、错题练习、课程学习、大题查看、期末备考) ? 点击中任何一个LAYOUT,会显示一个由ExpandableList实现一个列表 ? 点击ExpandabList子标签中的练习字样,会自动跳转到另一个Activity,进行专项练习。 ? 可以进行考试,有倒计时,要求达到牛客网的效果,并能出考试结果。 (5) 参与模块:(文字(我参与的课程)+添加按钮) ? 点击添加按钮跳转到另一页面。由 ListView实现 二、 社区界面: 1. 标题模块:显示文字(最新),点击最新会弹出一个上下文菜单(最新、最热、精华)。 2. 滑动标题模块:ViewPager+PagerSlidingTabStrip 3. 内容模块:使用ListView显示用户内容。 三、 消息界面: 1、 菜单模块:(朋友私信、系统通知)使用ViewPager实现(可用Tabhost) 2、 朋友私信页面:显示一个私信图片 3、 系统通知页面:(由ListView实现)由于比较固定本人使用RelativeLayout实现 四、 我的牛客界面: 1. 头像显示模块:头像+用户名+用户信息 2. 内容显示模块 更多效果请试用,感谢支持!
community:牛客论坛
03-29
community:牛客论坛
仿牛客网第一篇(分页显示帖子,注册,登录,拦截器实现)
weixin_55347789的博客
07-13 302
首先在applciation.properties配置ServerProperties(包含端口和访问路径),ThymeleafProperties(缓存设置),数据库(数据源,路径等),还有mybatis配置。创建拦截器LoginTicketInterceptor,在请求开始之前查询用户信息,在controller层下创建Interceptor包,然后创建LoginTicketInterceptor拦截器。其中,最难的是page分页组件的创建,以及在controller的运用。
仿牛客笔记】初识Spring Boot开发社区首页-Spring MVC入门
xue_hua_c的博客
10-20 287
HTTP:全称 HyperText Transfer protocol 超文本传输协议,用于传输HTML等内容的应用协议,位于应用层,规定了浏览器和服务器之间如何通信,以及通信时的数据格式。分层的目的是为了解耦。服务端代码分三层:表现层,业务层,数据访问层。浏览器访问服务器首先访问表现层,期待表现层返回写数据,表现层调用业务层处理逻辑,业务层处理业务的过程会调用数据库进行数据访问,最后表现层得到业务层返回的数据,返回给浏览器,整个请求结束。MVC:是一种刚刚设计模式,将复杂的代码分为三个层次。
仿牛客网论坛项目
weixin_42157700的博客
06-12 2120
仿牛客网论坛项目
仿牛客社区项目笔记-帖子模块(核心)
qq_35484331的博客
04-17 2421
仿牛客社区项目笔记-帖子模块(核心)1. 帖子模块1.1 过滤敏感词1.2 发布帖子1.3 帖子详情1.4 显示评论1.5 添加评论1.6 私信列表1.7 发送私信 1. 帖子模块 分为 过滤敏感词,发布帖子,帖子详情,显示评论,添加评论,私信列表,发送私信。 1.1 过滤敏感词 位于 util 包下的 SensitiveFilter 。 略 1.2 发布帖子 使用 Ajax 在 index 页面发送异步请求(局部刷新)。在 Controller 层返回 Json 数据显示。 前提用户已经登录,否则不显示
仿牛客社区项目Java开发笔记4.4:实现关注、取消关注功能
weixin_45700663的博客
07-22 1422
仿牛客社区项目Java开发笔记4.4:实现关注、取消关注功能
仿牛客社区项目Java开发笔记8.2:项目监控
weixin_45700663的博客
07-26 279
仿牛客社区项目Java开发笔记8.2:项目监控
java web默认页面_Spring Boot 2.0 设置网站默认首页的实现代码
weixin_35159324的博客
02-21 419
Spring Boot设置默认首页,方法实验OK如下附上Application启动代码/*** @ClassName Application* @Description Spring-Boot website启动类* @author kevin.tian* @Date 2018-03* @version 1.0.0*/@SpringBootApplication@PropertySource(va...
仿牛客项目kafka
08-29
仿牛客项目中使用Kafka可以实现消息的异步处理和分布式架构。 使用Kafka的第一步是创建一个主题(topic),主题既是消息的类别,也是消息在Kafka中的存储位置。可以使用命令行工具kafka-topics.bat来创建主题。例如...

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
写文章

热门文章

  • 南邮MySQL实验报告一 3987
  • 南邮MySQL实验报告二 3139
  • 南邮Android实验报告一:安卓数据存取 2824
  • (硅谷课堂项目)Java开发笔记2:项目概述,搭建项目环境和开发讲师管理接口 2558
  • (仿牛客社区项目)Java开发笔记4.2:实现点赞功能 2170

分类专栏

  • 数据结构与算法
  • Java项目 47篇
  • Android 3篇
  • MySQL 2篇
  • 操作系统 1篇

最新评论

  • (硅谷课堂项目)Java开发笔记5:讲师管理模块前端

    SeeSeaSunStar: 大佬,总结真详细,后面是烂尾了吗?表情包

  • (仿牛客社区项目)Java开发笔记6.2:开发社区搜索功能

    ? vikki ?: 请问下如何把数据库中的数据全部都加到ES服务器中呢

  • (仿牛客社区项目)Java开发笔记6.2:开发社区搜索功能

    dmsWide: 请问在ElasticsearchService中定义的方法是: [code=java] public Map<String,Object> searchDiscussPost(String keyword, int current, int limit) throws Exception [/code]但是在SearchController中使用时[code=java] org.springframework.data.domain.Page<DiscussPost> searchResult = elasticsearchService.searchDiscussPost(keyword, page.getCurrent() - 1, page.getLimit()); [/code]前者得返回值和后者的类型定义不一致

  • (仿牛客社区项目)Java开发笔记6.2:开发社区搜索功能

    JIUYU_M: 引用「// 搜索帖子 org.springframework.data.domain.Pag」 这里会报类型无法转换的错,楼主是怎么解决的呢?

  • (仿牛客社区项目)Java开发笔记4.2:实现点赞功能

    人间老怪: 感觉挺不错的就是有点懵表情包

您愿意向朋友推荐“博客详情页”吗?

  • 强烈不推荐
  • 不推荐
  • 一般般
  • 推荐
  • 强烈推荐
提交

最新文章

  • (硅谷课堂项目)Java开发笔记5:讲师管理模块前端
  • (硅谷课堂项目)Java开发笔记4:前端基础知识(二)
  • (硅谷课堂项目)Java开发笔记3:前端基础知识
2022年52篇
2021年2篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

深圳SEO优化公司枣庄网站优化排名哪家好抚顺英文网站建设多少钱白城关键词排名多少钱玉林百度关键词包年推广吉祥百度关键词包年推广哪家好来宾推广网站报价临猗SEO按天收费价格双龙网站优化按天收费临猗建站哪家好德阳百姓网标王推广推荐河源seo网站优化公司恩施SEO按天扣费哪家好桐城seo推荐民治seo烟台百度关键词包年推广多少钱贺州网站优化软件哪家好乌海建站推荐三明关键词按天收费多少钱丽江网站优化按天计费价格赣州网站seo优化推荐丹东网站优化软件推荐海西网站关键词优化哪家好铜仁网站建设天门网站优化按天收费公司黄石营销网站价格邢台企业网站建设多少钱玉溪seo公司伊犁网站优化软件报价重庆网站改版聊城网站排名优化价格歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化