开发流程
一次请求过程
先开发DAO,再开发service,再开发controller
开发社区首页的分布实现
显示前10个帖子
创建帖子数据表
CREATE TABLE `discuss_post` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` varchar(45) DEFAULT NULL,
`title` varchar(100) DEFAULT NULL,
`content` text,
`type` int DEFAULT NULL COMMENT '0-普通; 1-置顶;',
`status` int DEFAULT NULL COMMENT '0-正常; 1-精华; 2-拉黑;',
`create_time` timestamp NULL DEFAULT NULL,
`comment_count` int DEFAULT NULL,
`score` double DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=281 DEFAULT CHARSET=utf8mb3
- AUTO_INCREMENT:自增id
开发DAO数据访问层
- 创建与表变量相同的实体类:
public class DiscussPost {
private int id;
private int userId;
private String title;
private String content;
private int type;
private int status;
private java.util.Date createTime;
private int commentCount;
private double score;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
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 Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public int getCommentCount() {
return commentCount;
}
public void setCommentCount(int commentCount) {
this.commentCount = commentCount;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String toString() {
return "DiscussPost{" +
"id=" + id +
", userId=" + userId +
", title='" + title + '\'' +
", content='" + content + '\'' +
", type=" + type +
", status=" + status +
", createTime=" + createTime +
", commentCount=" + commentCount +
", score=" + score +
'}';
}
}
(记得设置getter和setter和to_String方法。)
- 创建DiscussPostMapper接口:
@Mapper
public interface DiscussPostMapper {
//userId为0时,表示查询所有用户的帖子,如果不为0,表示查询指定用户的帖子
//offset表示起始行号,limit表示每页最多显示的行数
List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit);
//查询帖子的行数
//userId为0时,表示查询所有用户的帖子
int selectDiscussPostRows(@Param("userId") int userId);
//@param注解用于给参数取别名,拼到sql语句中,如果只有一个参数,并且在<if>标签里,则必须加别名
}
- 配置DiscussPost-Mapper.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.newcoder.community.dao.DiscussPostMapper">
<sql id="selectFields">
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>
- 用标签定义反复被复用的字段;
- resultType=“DiscussPost”,如果是自己定义的需要注明,像int之类的不需要;
- <if test = >标签标识满足条件则把其中的sql拼上;
- 编写测试类对我们之前配置的mapper进行测试:
@Test
public void testSelectPosts() {
List<DiscussPost> list = discussPostMapper.selectDiscussPosts(149, 0, 10);
for(DiscussPost post : list) {
System.out.println(post);
}
int rows = discussPostMapper.selectDiscussPostRows(149);
System.out.println(rows);
// System.out.println(discussPostMapper.selectDiscussPostRows(149));
}
开发Service业务层
- 创建DiscussPostService类:
@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);
}
}
两个方法,一个找帖子列表,一个找帖子数。
- 注意Autowared把对应的Mapper注入。
- 考虑我们的业务需求,最后id肯定不会呈现在界面上,因此需要通过user_id查找到名字。这里直接写进SQL里不容易封装,因此选择创建一个UserService组件对User进行操作。
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User findUserById(int id) {
return userMapper.selectById(id);
}
}
- 这里的selectById是我们之前写过的一个方法,通过id查User。
开发Controller层
- 创建HomeController返回索引index(html文件在templates/index.html下)
@Controller
public class HomeController {
@Autowired
private UserService userService;
@Autowired
private DiscussPostService discussPostService;
@RequestMapping(path = "/index", method = RequestMethod.GET)
public String getIndexPage(Model model) {
//先查前10个帖子
List<DiscussPost> list = discussPostService.findDiscussPosts(0,0,10);
List<Map<String, Object>> discussPosts = new ArrayList<>();
if(list != null) {
for (DiscussPost post : list) {
Map<String, Object> map = new java.util.HashMap<>();
map.put("post", post);
map.put("user", userService.findUserById(post.getUserId()));
discussPosts.add(map);
}
}
model.addAttribute("discussPosts", discussPosts);
return "/index";
}
- 修改index.html,将其中的静态文字修改为动态的ThymeLeaf:
<!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="/Users/iris/items/my_maven/community/src/main/resources/static/img/error.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">
<!-- 头部 -->
......
<!-- 内容 -->
<div class="main">
......
<!-- 帖子列表 -->
<ul class="list-unstyled">
<li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}">
<a href="site/profile.html">
<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 href="#" 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">赞 11</li>
<li class="d-inline ml-2">|</li>
<li class="d-inline ml-2">回帖 7</li>
</ul>
</div>
</div>
</li>
</ul>
</div>
</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>
- xmlns引入thymeleaf
- th:each=“map:{discussPosts}” 用循环显示discussPosts中的内容。
- utext:可以把转义字符正常显示。
- map.user.getHeaderUrl相当于:map.get(“user”)->User->user.getHeaderUrl()
实现分页功能
明确需求:在底端实现分页效果。
- 创建Page实体类:
package com.newcoder.community.entity;
//封装分页相关的信息
public class Page {
private int current = 1; //当前页码
private int limit = 10; //每页显示的数据条数
private int rows; //数据总数(用于计算总页数)
private String path; //查询路径(每一个页面都有一个独立的查询路径,用来复用分页的链接)
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) {
this.limit = limit;
}
}
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;
}
//获取当前页的起始行(用于数据库查询)
public int getOffset() {
//current * limit - limit
return (current - 1) * limit;
}
//用来获取总的页数
public int getTotal() {
//rows / limit [+1]
if (rows % limit == 0) {
return rows / limit;
} else {
return rows / limit + 1;
}
}
//获取起始页码
public int getFrom(){
int from = current - 2;
return from < 1? 1: from;
}
//获取终止页码
public int getTo(){
int from = current + 2;
return from > getTotal() ? getTotal(): from;
}
}
- 这里还需要一些方法得到起始页码和终止页码。
- 修改HomeController:
@RequestMapping(path = "/index", method = RequestMethod.GET)
public String getIndexPage(Model model, Page page) {
//方法调用前,Spring会自动把page注入给model,所以html中可以直接访问page的数据。
//先查前10个帖子
page.setRows(discussPostService.findDiscussPostRows(0));
page.setPath("/index");
List<DiscussPost> list = discussPostService.findDiscussPosts(0,page.getOffset(), page.getLimit());
List<Map<String, Object>> discussPosts = new ArrayList<>();
if(list != null) {
for (DiscussPost post : list) {
Map<String, Object> map = new java.util.HashMap<>();
map.put("post", post);
map.put("user", userService.findUserById(post.getUserId()));
discussPosts.add(map);
}
}
model.addAttribute("discussPosts", discussPosts);
return "/index";
- 在controller中给总帖子数和path赋值。
- 然后通过discussPostService对象取DiscussPost数组
- 遍历数组,找到post和user的hashmap
- 添加到model中
- 修改index.html
<!-- 分页 -->
<nav class="mt-5" th:if="${page.rows>0}">
<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" href="#" 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>
(太复杂了草,我服了)
项目的调试
响应状态码
Table
Status Code
Description
中文含义
200
OK
请求成功
201
Created
已创建
204
No Content
无内容
301
Moved Permanently
永久移动
302
Found
重定向
400
Bad Request
请求错误
401
Unauthorized
未授权
403
Forbidden
禁止
404
Not Found
未找到
500
Internal Server Error
内部服务器错误
- 重定向:服务器建议浏览器重新访问别的并给状态码302。(比如登陆和注册产生重定向)
断点调试-服务端
- debug模式,浏览器访问到才会卡住。
- F8逐行调试,F7进入内部,F9继续执行直到下一个断点
- shift + F7跳出。
断电调试-客户端
- 浏览器F12。
- 前端的,后面再说
Spring日志
- 默认工具:logback
- 设置日志级别,并将日志输出到不同的终端。(级别从小到大)
- 在application.propertities中:
# logger
logging.level.com.newcoder.community=debug
- 指定日志存放在哪个文件:
logging.file.name=community.log
- 实际开发:按照不同级别存放到不同的log,文件大小到一定程度时进行拆分。
使用Git进行版本控制
Git简介
# 账号配置
git config --list
git config --global user.name "lihonghe"
git config --global user.email "lihonghe@nowcoder.com"
# 本地仓库
git init
git status -s
git add *
git commit -m '...'
# 生成秘钥
ssh-keygen -t rsa -C "lihonghe@nowcoder.com"
# 推送已有项目
git remote add origin https://git.nowcoder.com/334190970/Test.git git push -u origin master
# 克隆已有仓库
git clone https://git.nowcoder.com/334190970/Test.git