软件开发
实现业务功能
个人中心
页面结构介绍
个人中心的页面结构分为三部分
1> 导航栏
2> 正文部分
3> 页脚部分
index.html 的页面结构
1> 导航栏
2> 正文部分
3> 页脚部分
获取用户信息
实现逻辑
⽤⼾提交请求,服务器根据是否传⼊Id参数决定返回哪个⽤⼾的详情
1. 不传⽤⼾Id,返回当前登录⽤⼾的详情 从session中获取
2. 传⼊⽤⼾Id,返回指定Id的⽤⼾详情 根据传入的ID从数据库中查询
参数要求
参数名 | 描述 | 类型 | 默认值 | 条件 |
id | ⽤⼾Id | long | 可以为空 |
接口规范
// 请求
GET /user/info HTTP/1.1
GET /user/info?id=1 HTTP/1.1
// 响应
HTTP/1.1 200
Content-type: applicatin/json// 下面返回的是具体的User对象
{
"code": 0,
"message": "成功",
"data": {
"id": 25,
"username": "user223",
"nickname": "user223",
"phoneNum": null,
"email": null,
"gender": 1,
"avatarUrl": null,
"articleCount": 0,
"isAdmin": 0,
"state": 0,
"createTime": "2023-04-08 15:06:10",
"updateTime": "2023-04-08 15:06:10"
}
}
编写后端代码
1. 在Mapper.xml中编写SQL语句
之前就生成好了

2. 在Mapper.java中定义方法
也是之前生成好的

3. 定义Service接口
在IUserService定义selectById⽅法,如下

4. 实现Service接口
1> 非空校验
2> 调用DAO查询数据库并且获取对象
具体代码
@Override
public User selectById(Long id) {
// 1. 非空校验
if (id == null) {
// 打印日志
// 参数校验失败
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
// 抛出异常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
// 2. 调用DAO查询数据库并获取对象
User user = userMapper.selectByPrimaryKey(id);
// 返回结果
return user;
}
5. 单元测试
查询成功
6. Controller实现方法并对外提供API接口
步骤
1> 根据id的值来判断是通过那种方式来获取User对象
2> 如果id为空就从session中进行获取用户信息
3> 如果id不为空, 就用id去调用service通过id来查询用户信息
4> 判断返回的用户对象是不是空的
5> 返回正确的结果
具体代码
@Operation(summary = "获取用户信息")
@GetMapping("/info")
public AppResult<User> getUserInfo(HttpServletRequest request,
@Parameter(description = "用户id") @RequestParam(value = "id", required = false) Long id) {
// 定义返回的User对象
User user = null;
// 根据Id的值判断User对象的获取
if (id == null) {
// 1. 如果id为空, 从session中获取当登录的信息
HttpSession session = request.getSession(false);// 如果session没有,不会创建新的session
// 判断session和用户信息是否有效
if (session == null || session.getAttribute(AppConfig.USER_SESSION) == null) {
// 用户没有登录, 直接返回错误信息
return AppResult.failed(ResultCode.FAILED_FORBIDDEN);// 禁止访问
}
// 有效, 就从session中获取当前登录的用户信息
user = (User) session.getAttribute(AppConfig.USER_SESSION);
} else {
// 2. 如果id不为空, 那么就从数据库中按照Id查询用户信息
user = userService.selectById(id);
}
// 判断返回的用户对象是不是空的
if (user == null) {
return AppResult.failed(ResultCode.FAILED_USER_NOT_EXISTS);//用户不存在
}
// 返回正确的结果
return AppResult.success(user);
}
}
7. 测试API接口
分别测试登录和没登陆带id的情况
我们观察返回结果,发现我们的用户名, 密码, 创建跟新事件, 删除字段都返回了, 这个是不对的, 会造成信息泄露, 因此,我们需要进行处理
8. 修复返回值存在的缺陷
通过观察登录成功的返回结果发现,⽤⼾信息中的password, salt, deletState不应该返回给前
台,在User类中的对应属性上加@JsonIgnore(指定类中的属性不参与JSON字符串)注解,可以使对应的字段不参与JSON的序列化
序列化: JAVA对象--> JSON字符串
反序列化: JSON字符串-->JAVA对象
把@JsonIgnore过滤字段
对于日期, 我们需要去yml来进行设置正确的日期格式
修改⽇期格式为yyyy-MM-dd HH:mm:ss, 在application.yml中添加配置
# 在spring下加⼊⼦节点
spring:
# JSON序列化配置
jackson:
default-property-inclusion: NON_NULL # 不为null时序列化
然后我们在对应字段上加上@JsonFormate注解

测试: 发现密码和盐过滤掉了, 然后创建和更新时间是正确的
但是上述的配置会影响我登录的返回结果, 我的data没有了, 此时我需要对统一返回结果进行强制序列化返回
加上@JsonInclude注解, 设置不管什么情况都会进行序列化返回
再进行登录测试: 发现data成功显示
编写前端代码
根据后端的响应数据, 来显示在前端
编写ajax请求
//========================= 获取用户信息 =======================
// 成功后,手动设置用户信息
// $('#index_nav_avatar').css('background-image', 'url(' + user.avatarUrl + ')');
$.ajax({
// 请求的方法
type: 'get',
// 没有参数,表示获取当前登录用户的信息
url: 'user/info',
// 成功回调
success: function (respData) {
// 判断响应的状态码
if (respData.code == 0) {
// 设置页面上用户的信息
let user = respData.data;
// 判断用户头像是否有效
if (!user.avatarUrl) {
// 设置默认的头像地址
user.avatarUrl = avatarUrl;
}
// 设置页面上的头像
$('#index_nav_avatar').css('background-image', 'url(' + user.avatarUrl + ')');
// 用户昵称
$('#index_nav_nickname').html(user.nickname);
// 设置用户组
let subName = user.isAdmin == 1 ? '管理员' : '普通用户';
$('#index_nav_name_sub').html(subName);
currentUserId = user.id;
} else {
// 提示信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
// 失败回调
error: function () {
// 提示信息
$.toast({
heading: '错误',
text: '访问出现问题,请与管理员联系.',
icon: 'error'
});
}
});
成功显示
退出功能
实现流程
• 流程⾮常简单,具体的实现逻辑如下:
1. ⽤⼾访问退出接⼝
2. 服务器注销Session
3. 返回成功或失败
4. 如果返回成功浏览器跳转到相应⻚⾯
5. 结束
• 退出后跳转到哪个⻚⾯交给前端⾃⼰处理,建议跳转到登录⻚⾯。
接口规范
// 请求
GET http://127.0.0.1:58080/user/logout HTTP/1.1
// 响应
HTTP/1.1 200
Content-Type: application/json
{"code":0,"message":"成功","data":null}
后端代码编写
销毁session对象并且解绑所有的用户数据
具体代码
@PostMapping("/logout")
@Operation(summary = "用户退出")
public AppResult logout (HttpServletRequest request){
// 获取session对象
HttpSession session = request.getSession();
if(session != null){
// 打印日志
log.info("退出成功");
// 注销session
session.invalidate();
}
// 退出成功响应
return AppResult.success();
}
前端代码编写
我们需要对a标签绑定事件
编写ajax请求
$('#index_user_logout').click(function () {
$.ajax({
type: 'get',
url: 'user/logout',
complete: function () {
// 当前请求完成时,不论成功与失败都跳转到登录页面
location.assign('/sign-in.html');
}
});
});
complete的解释
测试
配置拦截器
为什么要配置拦截器
为了达到封装复用的效果, 不每一个接口都进行判断session是否有效, 统一的校验工作抽取出来, 用拦截器对请求进行过滤(除了登录之外的的url都不准进行访问)
在interceptor包下创建LoginInterceptor
自定义登录拦截器
1> 类上实现HanlderInterceptor接口
2> 重写前置处理方法preHandle
3> 获取session对象, 并对session进行有效性验证
4> 验证不通过就跳转到登录页面, response.sendRedirect
package org.xiaobai.forum.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.xiaobai.forum.config.AppConfig;
/**
* 登录拦截器
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Value("${forum.login.url}")
private String defaultURL;
/**
* 前置处理(对请求进行预处理)
* @param request
* @param response
* @param handler
* @return true: 继续流程<br/> false: 流程终端
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取session 对象
HttpSession session = request.getSession(false);// 获取不到session也不创建新的session
// 判断session是否有效
if (session != null && session.getAttribute(AppConfig.USER_SESSION) != null) {
// 用户为已经登录, 校验通过
return true;
}
// 校验url是否正确
if (!defaultURL.startsWith("/")) {
defaultURL = "/" + defaultURL;
}
// 校验不通过, 跳转到登录页面
response.sendRedirect(defaultURL);
// 终止流程
return false;
}
}
注意
1> 对url进行有效性判断
2> 使用yml把目标页面放在配置文件中, 降低耦合性

修改application.yml配置⽂件,添加跳转页面
# 项⽬⾃定义相关配置
forum:
login:
url: sign-in.html # 未登录状况下强制跳转⻚⾯
在interceptor包下创建AppInterceptorConfigurer
步骤
1> 类上实现WebMvcConfigurer
2> 注入自定义的登录拦截器
3> 添加登录拦截器
4> 添加拦截的路径
具体代码
package org.xiaobai.forum.interceptor;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 注册拦截器
* 配置拦截规则
*
*/
// 表示一个配置类, 注入到spring
@Configuration
public class AppInterceptorConfigurer implements WebMvcConfigurer {
//注入自定义的登录拦截器
@Resource
private LoginInterceptor loginInterceptor;
// 配置拦截路径
@Override
public void addInterceptors(InterceptorRegistry registry) { WebMvcConfigurer.super.addInterceptors(registry);
// 添加登录拦截器
// 添加登录拦截器
registry.addInterceptor(loginInterceptor) // 添加用户登录拦截器
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/sign-in.html") // 排除登录HTML
.excludePathPatterns("/sign-up.html") // 排除注册HTML
.excludePathPatterns("/user/login") // 排除登录api接口
.excludePathPatterns("/user/register") // 排除注册api接口
.excludePathPatterns("/user/logout") // 排除退出api接口
.excludePathPatterns("/swagger*/**") // 排除登录swagger下所有
.excludePathPatterns("/v3*/**") // 排除登录v3下所有,与swagger相关
.excludePathPatterns("/dist/**") // 排除所有静态文件
.excludePathPatterns("/image/**")
.excludePathPatterns("/js/**")
.excludePathPatterns("/**.ico");
}
}
获取在首页中显示的板块
实现方式
• 在⾸⻚显⽰的版块信息,可以通过以下两种⽅式解决
• ⽅法⼀:单独提供查询前N条记录的接⼝,⽤来控制⾸⻚中版块的个数(按照sort进行升序排序)
• ⽅法⼆:通过配置表中state字段来实现,提供查询所有版块的接⼝
• 两种⽅式任选⼀个都可以,项⽬中使⽤⽅法⼀
实现逻辑
1. 用户访问首页
2. 服务器查询有效的版块并按排序字段排序
3. 返回版块集合
没有参数要求
接口规范
// 请求
GET http://127.0.0.1:58080/board/topList HTTP/1.1
// 响应
HTTP/1.1 200
Content-Type: application/json
{
"code": 0,
"message": "成功",
"data": [
{
"id": 1,
"name": "Java",
"articleCount": 5,
"sort": 1,
"state": 0,
"createTime": "2023-01-14 11:02:18",
"updateTime": "2023-01-14 11:02:18"
},
{
"id": 2,
"name": "C++",
"articleCount": 1,
"sort": 2,
"state": 0,
"createTime": "2023-01-14 11:02:41",
"updateTime": "2023-01-14 11:02:41"
},
{
"id": 3,
"name": "前端技术",
"articleCount": 0,
"sort": 3,
"state": 0,
"createTime": "2023-01-14 11:02:52",
"updateTime": "2023-01-14 11:02:52"
}....}]}
后端代码实现
把板块信息写入
-- 写⼊版块信息数据
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`,
`deleteState`, `createTime`, `updateTime`) VALUES (1, 'Java', 0, 1, 0, 0,
'2023-01-14 19:02:18', '2023-01-14 19:02:18');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`,
`deleteState`, `createTime`, `updateTime`) VALUES (2, 'C++', 0, 2, 0, 0, '2023-
01-14 19:02:41', '2023-01-14 19:02:41');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`,
`deleteState`, `createTime`, `updateTime`) VALUES (3, '前端技术', 0, 3, 0, 0,
'2023-01-14 19:02:52', '2023-01-14 19:02:52');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`,
`deleteState`, `createTime`, `updateTime`) VALUES (4, 'MySQL', 0, 4, 0, 0,
'2023-01-14 19:03:02', '2023-01-14 19:03:02');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`,
`deleteState`, `createTime`, `updateTime`) VALUES (5, '⾯试宝典', 0, 5, 0, 0,
'2023-01-14 19:03:24', '2023-01-14 19:03:24');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`,
`deleteState`, `createTime`, `updateTime`) VALUES (6, '经验分享', 0, 6, 0, 0,
'2023-01-14 19:03:48', '2023-01-14 19:03:48');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`,
`deleteState`, `createTime`, `updateTime`) VALUES (7, '招聘信息', 0, 7, 0, 0,
'2023-01-25 21:25:33', '2023-01-25 21:25:33');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`,
`deleteState`, `createTime`, `updateTime`) VALUES (8, '福利待遇', 0, 8, 0, 0,
'2023-01-25 21:25:58', '2023-01-25 21:25:58');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`,
`deleteState`, `createTime`, `updateTime`) VALUES (9, '灌⽔区', 0, 9, 0, 0,
'2023-01-25 21:26:12', '2023-01-25 21:26:12');
数据库显示插入成功
1. 在Mapper.xml中编写SQL语句
• 在src/main/resources/mapper/extension⽬录下新建 BoardExtMapper.xml( 注意命名空间和自动生成的.xml保持一致)
selectByNum 根据sort字段进行排序
编写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="org.xiaobai.forum.dao.BoardMapper">
<select id="selectByNum" resultMap="BaseResultMap" parameterType="java.lang.Integer">
select
<include refid="Base_Column_List"/>
from t_board
where state = 0 and deleteState = 0
order by sort asc
limit 0,#{num,jdbcType=INTEGER}
</select>
</mapper>
2. 在Mapper.java中定义方法
•org.xiaobai.forum.dao包下的BoardMapper中添加⽅法声明
3. 定义Service接口
• 在IBoradService定义⽅法,如下:
package org.xiaobai.forum.service;
import org.xiaobai.forum.model.Board;
import java.util.List;
public interface IBoradService {
/**
* 查询n挑记录
* @param num 要查询的条数
* @return
*/
List<Board> selectByNum(Integer num);
}
4. 实现Service接口
• 在BoardServiceImpl中实现IBoardService中新增的⽅法
实现步骤
1> 注入BoardMapper
2> 对num进行非空校验
3> 调用DAO查询数据库中的数据
4> 返回结果
package org.xiaobai.forum.service.impl;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.xiaobai.forum.common.AppResult;
import org.xiaobai.forum.common.ResultCode;
import org.xiaobai.forum.dao.BoardMapper;
import org.xiaobai.forum.exception.ApplicationException;
import org.xiaobai.forum.model.Board;
import org.xiaobai.forum.service.IBoradService;
import java.util.List;
@Slf4j
@Service
public class IBoardService implements IBoradService {
// 注入boardMapper
@Resource
private BoardMapper boardMapper;
@Override
public List<Board> selectByNum(Integer num) {
// 1. 进行非空校验
if (num <= 0) {
// 打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
// 抛出异常
throw new ApplicationException(AppResult.failed((ResultCode.FAILED_PARAMS_VALIDATE)));// 参数校验异常
}
// 2. 调用DAO查询数据库中的数据
List<Board> rs = boardMapper.selectByNum(num);
// 3. 返回结果
return rs;
}
}
5. 单元测试
查询成功
6. Controller实现方法并对外提供API接口
• 在BoradController中提供对外的API接⼝
application.yml中添加配置
# 项⽬⾃定义相关配置(顶格写)
forum: index: # ⾸⻚配置节点board-num: 9 # ⾸⻚中显⽰的版块个数
实现步骤
1> 注入IBoardService
2> 调用Service获取查询结果
3> 判断查询结果是否为空
4> 返回结果
具体代码
package org.xiaobai.forum.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.xiaobai.forum.common.AppResult;
import org.xiaobai.forum.model.Board;
import org.xiaobai.forum.service.IBoradService;
import java.util.ArrayList;
import java.util.List;
@Tag(name = "板块接口", description = "主页中展示有多少个板块")
@Slf4j
@RestController
@RequestMapping("/Borad")
public class BoradController {
// 从配置文件中读取值, 如果没有配置, 默认值为9
@Value("${form.index.board-num:9}")
private Integer indexBoardNum;
@Resource
private IBoradService boradService;
@Operation(summary = "获取首页板块列表")
@GetMapping("/topList")
public AppResult<List<Board>> topList() {
log.info("首页板块数为: " + indexBoardNum);
// 调用Service 获取查询结果
List<Board> boards = boradService.selectByNum(indexBoardNum);
// 判断是否为空
if (boards == null) {
boards = new ArrayList<>();
}
// 返回结果
return AppResult.success(boards);
}
}
7. 测试API接口
在登录条件下访问该接口
前端代码实现
根据后端响应值来构造板块信息
编写ajax
$.ajax({
type: 'get',
url: 'board/topList',
success : function (respData) {
if(respData.code == 0) {
// 构建版块列表
buildTopBoard(respData.data);
} else {
// 提示信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error : function () {
// 提示信息
$.toast({
heading: '错误',
text: '访问出现问题,请与管理员联系.',
icon: 'error'
});
}
});
测试: 成功显示
帖子操作
发布帖子
实现逻辑
1. ⽤⼾点击发新帖按钮,进⼊发帖⻚⾯
2. 选择版块,填⼊标题、正⽂后提交服务器
3. 服务器校验信息并写⼊数据库(文章表的insert)
4. 更新⽤⼾发帖数与版块帖⼦数(更新用户表和板块表)
5. 返回结果
3. 4要用数据库的事务
参数要求
参数名 | 描述 | 类型 | 默认值 | 条件 |
boardId | 版块Id | long | 必须 | |
title | ⽂章标题 | String | 必须 | |
content | 帖⼦内容 | String | 必须 |
接口规范
// 请求
POST http://127.0.0.1:58080/article/create HTTP/1.1
Content-Type: application/x-www-form-urlencoded
boardId=1&title=%E6%B5%8B%E8%AF%95%E6%96%B0%E5%B8%96%E5%AD%90%E6%A0%87%E9%A2%98
&content=%E6%B5%8B%E8%AF%95%E6%96%B0%E5%B8%96%E5%AD%90%E5%86%85%E5%AE%B9%E6%B5%
8B%E8%AF%95%E6%96%B0%E5%B8%96%E5%AD%90%E5%86%85%E5%AE%B9
// 响应
HTTP/1.1 200
Content-Type: application/json
{"code":0,"message":"成功","data":null}
实现后端代码
1. 在Mapper.xml中编写SQL语句
2. 在Mapper.java中定义方法
关于文章表的insert操作, 已经自动生成, 可以直接使用
关于更新用户表和文章表 update. 在更新用户和板块的帖子数的时候, 可以使用动态更新的方法只设置Id和要更新的帖子数量即可

3. 定义Service接口
对于update文章表在IArticleService定义⽅法
对于update用户表在IBoardService定义⽅法更新帖子数量
更具板块id查询板块信息
对于insert, 在IUserService定义⽅法
4. 实现Service接口
实现IBoardServiceImpl
实现步骤
1> 对id进行非空校验
2> 查询相应的板块
3> 更新帖子数量(此时要重新创建一个对象, 然后把要更新的字段进行设置
4> 调用DAO, 执行更新操作
5> 判断受影响行数
/**
* 更新板块中帖子的数量
*
* @param id 板块id
*/
@Override
public void addOneArticleCountById(Long id) {
// 对id进行非空校验
if (id == null || id <= 0) {
// 打印日志
log.warn(ResultCode.FAILED_BOARD_ARTICLE_COUNT.toString());// 更新帖子数失败
// 抛出异常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_BOARD_ARTICLE_COUNT));
}
// 查询对应的板块
Board board = boardMapper.selectByPrimaryKey(id);
if (board == null) {
// 打印日志
log.warn(ResultCode.ERROR_IS_NULL.toString());// 更新帖子数失败
// 抛出异常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_IS_NULL));
}
// 更新帖子数量
// 重新创建一个对象, 把要更新的字段设置进行
Board updateBoard = new Board();
updateBoard.setId(board.getId());
updateBoard.setArticleCount(board.getArticleCount() + 1);// 更新帖子数量
// 调用DAO, 执行更新操作
int row = boardMapper.updateByPrimaryKeySelective(updateBoard); // 调用mapper更新帖子
// 判断受影响行数
if (row != 1) {
log.warn(ResultCode.FAILED.toString() + ", 受影响行数不等于1 .");
throw new ApplicationException(AppResult.failed(ResultCode.FAILED));
}
}
根据板块id查询板块信息
@Override
public Board selectById(Long id) {
// 对id进行非空校验
if (id == null || id <= 0) {
// 打印日志
log.warn(ResultCode.FAILED_BOARD_ARTICLE_COUNT.toString());// 更新帖子数失败
// 抛出异常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_BOARD_ARTICLE_COUNT));
}
Board board = boardMapper.selectByPrimaryKey(id);
return board;
}
动态更新需要重新创建对象
实现UserServiceImpl
步骤
1> 对id进行非空校验
2> 查询用户信息
3> 更新用户的发帖数量
4> 动态更新sql
5> 调用DAO, 执行更新操作
具体代码
/**
* 更新当前用户帖子数量
* @param id 用户id
*/
@Override
public void addOneArticleCountById(Long id) {
// 对id进行非空校验
if (id == null || id <= 0) {
// 打印日志
log.warn(ResultCode.FAILED_BOARD_ARTICLE_COUNT.toString());// 更新帖子数失败
// 抛出异常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_BOARD_ARTICLE_COUNT));
}
// 查询用户信息
User user = userMapper.selectByPrimaryKey(id);
if (user == null) {
// 打印日志
log.warn(ResultCode.ERROR_IS_NULL.toString());// 更新用户数失败
// 抛出异常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_IS_NULL));
}
// 更新用户的发帖数量
User updateUser = new User();
// 动态更新sql, 只需要把更新的字段进行设置即可
updateUser.setId(user.getId());
updateUser.setArticleCount(user.getArticleCount() + 1);
// 调用DAO, 执行更新操作
int row = userMapper.updateByPrimaryKeySelective(updateUser);
if (row != 1) {
log.warn(ResultCode.FAILED.toString() + ", 受影响行数不等于1 .");
throw new ApplicationException(AppResult.failed(ResultCode.FAILED));
}
}
实现ArticleServiceImpl
步骤
1> 把和新增文章相关的mapper注入, 把和更新用户表和板块表的文章数的service进行注入
2> 对传入的Article 进行非空校验
3> 设置文章的默认值(访问数, 回复数, 点赞数...)
4> 把设置好默认值的article插入到数据库
5> 根据article的用户id获取用户的信息
6> 对用户进行非空校验(是否找到指定的用户)
7> 如果找到了, 那就更新用户的发帖数目
8> 根据article的板块id获取板块的信息
9> 对板块进行非空校验(是否找到指定的板块)
10> 如果找到了, 那就更新板块中的发帖数
具体代码
package org.xiaobai.forum.service.impl;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.xiaobai.forum.common.AppResult;
import org.xiaobai.forum.common.ResultCode;
import org.xiaobai.forum.dao.ArticleMapper;
import org.xiaobai.forum.exception.ApplicationException;
import org.xiaobai.forum.model.Article;
import org.xiaobai.forum.model.Board;
import org.xiaobai.forum.model.User;
import org.xiaobai.forum.service.IArticleService;
import org.xiaobai.forum.service.IBoardService;
import org.xiaobai.forum.service.IUserService;
import org.xiaobai.forum.utils.StringUtil;
import java.util.Date;
@Slf4j
@Service
public class ArticleServiceImpl implements IArticleService {
// 注入mapper, 注意service之间注入不能成环
@Resource
private ArticleMapper articleMapper;
@Resource
private IUserService userService;
@Resource
private IBoardService boardService;
@Override
public void create(Article article) {
// 进行非空校验
if (article == null || article.getUserId() == null || article.getBoardId() == null
|| StringUtil.isEmpty(article.getTitle())
|| StringUtil.isEmpty(article.getContent())) {
// 打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 参数校验失败
// 抛出异常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
// 设置文章的默认值
article.setVisitCount(0);// 访问数
article.setReplyCount(0);// 回复数
article.setLikeCount(0);// 点赞数
article.setDeleteState((byte) 0);
article.setState((byte) 0);
Date date = new Date();
article.setCreateTime(date);
article.setUpdateTime(date);
// 把新写的文章写入数据库
int articleRow = articleMapper.insertSelective(article);
if (articleRow <= 0) {
log.warn(ResultCode.FAILED_CREATE.toString());// 新增失败
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_CREATE));
}
// 获取用户信息
User user = userService.selectById(article.getUserId());
// 没有找到指定的用户信息
if (user == null) {
log.warn(ResultCode.FAILED_CREATE.toString() + ", 发帖失败, user id = " + article.getUserId());// 新增失败
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_CREATE));
}
// 更新用户的发帖数
userService.addOneArticleCountById(user.getId());
// 获取板块信息
Board board = boardService.selectById(article.getBoardId());
// 是否在数据库有对应的板块
if (board == null) {
log.warn(ResultCode.FAILED_CREATE.toString() + ", 发帖失败, board id = " + article.getUserId());// 新增失败
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_CREATE));
}
// 更新板块中的帖子数量
boardService.addOneArticleCountById(board.getId());
// 打印日志
log.info(ResultCode.SUCCESS.toString() + ", user id = " + article.getUserId()
+ ", board if = " + article.getBoardId() + ", article id = " + article.getId() + "发帖成功");
}
}
注意: service里不能出现service之间的循环调用
5. 单元测试
发现用户表里面的文章数量,板块表里面的文章数量都增加了
article表里面的数据增加了
同时用户表和板块表里面的文章数也增加了
测试代码
@Resource
private IArticleService articleService;
@Test
void create() {
Article article = new Article();
article.setUserId(1L);// asd
article.setBoardId(1L);// java板块
article.setTitle("测试新增文章接口");
article.setContent("文章的内容...");
articleService.create(article);
}
6. Controller实现方法并对外提供API接口
• 在 ArticleController 中提供对外的API接⼝
步骤
1> 根据session获取用户
2> 判断用户是否禁言
3> 对板块进行校验(通过板块id校验板块是不是存在)
4> 构造文章对象
5> 调用service, 进行插入操作(插入后文章, 板块,用户的文章数量都会增加,因为是一个事务的)
6> 响应
具体代码
package org.xiaobai.forum.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.xiaobai.forum.common.AppResult;
import org.xiaobai.forum.common.ResultCode;
import org.xiaobai.forum.config.AppConfig;
import org.xiaobai.forum.model.Article;
import org.xiaobai.forum.model.Board;
import org.xiaobai.forum.model.User;
import org.xiaobai.forum.service.IArticleService;
import org.xiaobai.forum.service.IBoardService;
@Tag(name = "文章接口", description = "对帖子进行发布,点赞,修改,删除操作...操作")
@Slf4j
@RestController
@RequestMapping("/article")
public class ArticleController {
// 注入service
@Resource
private IArticleService articleService;
@Resource
private IBoardService boardService;
@Operation(summary = "发布新帖")
@PostMapping("/create")
public AppResult create(HttpServletRequest request,
@Parameter(description = "板块id") @RequestParam("boardId") @NonNull Long boardId,
@Parameter(description = "文章标题") @RequestParam("title") @NonNull String title,
@Parameter(description = "文章内容") @RequestParam("content") @NonNull String content) {
// 获取用户
HttpSession session = request.getSession(false);
User user = (User) session.getAttribute(AppConfig.USER_SESSION);
// 判断用户是否禁言
if (user.getState() == 1) {
// 用户已禁言
return AppResult.failed(ResultCode.FAILED_USER_BANNED);// 禁言
}
// 板块校验
Board board = boardService.selectById(boardId);
if (board == null || board.getDeleteState() == 1 || board.getState() == 1) {
// 打印日志
log.warn(ResultCode.FAILED_BOARD_BANNED.toString());// 板块状况异常
// 返回响应
return AppResult.failed(ResultCode.FAILED_BOARD_BANNED);
}
// 封装文章对象
Article article = new Article();
article.setTitle(title); //标题
article.setContent(content); //正文
article.setBoardId(boardId); // 板块id
article.setUserId(user.getId());// 作者id
// 调用Service
articleService.create(article);
// 响应
return AppResult.success();
}
}
7. 测试API接口
插入成功
实现前端代码
editor.md实现输入文章标题内容的设置
editor.md的使用
获取表单数据
ajax请求
// 构造帖子对象
let postData = {
boardId : boardIdEl.val(),
title : titleEl.val(),
content : contentEl.val()
};
// 提交, 成功后调用changeNavActive($('#nav_board_index'));回到首页并加载帖子列表
// contentType: 'application/x-www-form-urlencoded'
$.ajax({
type: 'post',
url : 'article/create',
contentType : 'application/x-www-form-urlencoded',
data : postData,
// 回调
success : function (respData) {
if (respData.code == 0) {
// 提示信息
$.toast({
heading: '成功',
text: '发帖成功',
icon: 'success'
});
// 发布成功
changeNavActive($('#nav_board_index'));
} else {
// 提示信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error : function () {
// 提示信息
$.toast({
heading: '错误',
text: '访问出现问题,请与管理员联系.',
icon: 'error'
});
}
});
帖子列表
板块帖子列表
对应版块中显⽰的帖⼦列表以发布时间降序排列(点击不同的板块, 每个板块显示自己板块下所有的帖子)
不传⼊版块Id返回所有帖⼦
帖子列表中显示所有的帖子
实现逻辑
1. ⽤⼾点击某个版块或⾸⻚时,将版块Id做为参数向服务器发送请求
2. 服务器接收请求,并获取版块Id,查询对应版块下的所有帖⼦
3. 返回查询结果
参数要求
参数名 | 描述 | 类型 | 默认值 | 条件 |
boardId | 版块Id | Long | 可为空(根据传入的Board来执行不同的查询 selectAll(), selectByBoardId(Long boardId) |
接口规范
// 请求
// 返回指定版块下的帖⼦列表
GET http://127.0.0.1:58080/article/getAllByBoardId?boardId=1 HTTP/1.1
// 响应
HTTP/1.1 200
Content-Type: application/json
{
"code": 0,
"message": "成功",
"data": [
{
"id": 17,
"boardId": 1,
"userId": 1,
"title": "测试删除",
"visitCount": 8,
"replyCount": 1,
"likeCount": 1,
"state": 0,
"createTime": "2023-07-05 04:10:46",
"updateTime": "2023-07-05 11:22:43",
"board": {
"id": 1,
"name": "Java"
},
"user": {
"id": 1,
"nickname": "bitboy",
"phoneNum": null,
"email": null,
"gender": 1,
"avatarUrl": null
},
"own": false
},
{
"id": 15,
"boardId": 1,
"userId": 2,
"title": "单元测试",
"visitCount": 11,
"replyCount": 0,
"likeCount": 6,
"state": 0,
"createTime": "2023-07-03 11:30:36",
"updateTime": "2023-07-04 10:31:00",
"board": {
"id": 1,
"name": "Java"
},
"user": {
"id": 2,
"nickname": "bitgirl",
"phoneNum": null,
"email": null,
"gender": 2,
"avatarUrl": null
},
"own": false
},
{
"id": 11,
"boardId": 1,
"userId": 1,
"title": "testtest222",
"visitCount": 4,
"replyCount": 0,
"likeCount": 0,
"state": 0,
"createTime": "2023-07-02 09:19:00",
"updateTime": "2023-07-02 09:19:00",
"board": {
"id": 1,
"name": "Java"
},
"user": {
"id": 1,
"nickname": "bitboy",
"phoneNum": null,
"email": null,
"gender": 1,
"avatarUrl": null
},
"own": false
},
{
"id": 10,
"boardId": 1,
"userId": 1,
"title": "测试⻚⾯发帖",
"visitCount": 1,
"replyCount": 1,
"likeCount": 0,
"state": 0,
"createTime": "2023-07-02 09:17:47",
"updateTime": "2023-07-05 10:51:43",
"board": {
"id": 1,
"name": "Java"
},
"user": {
"id": 1,
"nickname": "bitboy",
"phoneNum": null,
"email": null,
"gender": 1,
"avatarUrl": null
},
"own": false
},
{
"id": 1,
"boardId": 1,
"userId": 1,
"title": "单元测试",
"visitCount": 13,
"replyCount": 2,
"likeCount": 3,
"state": 0,
"createTime": "2023-07-02 06:46:32",
"updateTime": "2023-07-05 10:16:43",
"board": {
"id": 1,
"name": "Java"
},
"user": {
"id": 1,
"nickname": "bitboy",
"phoneNum": null,
"email": null,
"gender": 1,
"avatarUrl": null
},
"own": false}]}
显示首页所有列表的后端代码实现
分析sql
结果必须包含作者信息, 帖子简要信息, 因此我们需要把俩个表关联起来
1. 在Mapper.xml中编写SQL语句
创建ArticleExtMapper.xml并写内容
当前这个结果集没有一个对应的ResultMap或者ResultType接收它, 所以要自定义一个拓展的ResultMap
不同map的关系分析
具体的实现(在Article实体类里面写User这个字段
2. 在Mapper.java中定义方法
在dao层定义方法
3. 定义Service接口
在 IArticleService 添加selectAll方法
4. 实现Service接口
在ArticleServiceImpl里面实现selectAll方法
直接查询mapper里面的数据,返回结果即可
/**
* 查询所有的帖子列表
* @return
*/
@Override
public List<Article> selectAll() {
// 调用DAO
List<Article> articles = articleMapper.selectAll();
// 返回结果
return articles;
}
注意, 为了防止我们的avatarUrl因为为null而被我们的设置的json序列化条件给过滤掉, 我们也要加上@JsonInclude,注解设置不管为不为null都要进行序列化
5. 单元测试
查询出来了所有的帖子信息
6. Controller实现方法并对外提供API接口
在ArticleController中提供对外的API接⼝
/**
* 通过查询板块id返回所有帖子信息
* @param boardId
* @return
*/
@Operation(summary = "获取帖子列表")
@GetMapping("/getAllByBoardId")
public AppResult<List<Article>> getAllByBoardId(@Parameter(description = "板块id") @RequestParam(value = "boardId",required = false)Long boardId){
// 查询所有的帖子
List<Article> articles = articleService.selectAll();
if(articles == null){
// 如果结果集为空, 那么就创建一个空集合
articles = new ArrayList<>();
}
// 返回响应结果
return AppResult.success(articles);
}
注意: article==null, 此时article是"null" articles = new ArrayList<>(); 里面的值是[]
7. 测试API接口
查询成功
前端代码实现
article_list.html
编写ajax请求
$.ajax({
type : 'get',
url : 'article/getAllByBoardId' + queryString,
// 回调
success : function (respData) {
if (respData.code == 0) {
// 成功
listBuildArticleList(respData.data);
} else {
// 失败
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error : function () {
// 提示信息
$.toast({
heading: '错误',
text: '访问出现问题,请与管理员联系.',
icon: 'error'
});
}
});
实现成功, 但是我们要的是不同板块显示该板块的帖子和板块信息, 需要把上面的前后端都进行小改一下
显示对应板块显示该板块下的所有帖子后端代码实现
1. 在Mapper.xml中编写SQL语句
在selectAll的基础上加上板块的条件
在AriticleExMapper.xml中添加下面代码
<!-- 根据板块ID查询所有未被删除的帖子列表, 不包含content-->
<select id="selectAllByBoardId" resultMap="AllInfoBaseResultMap" parameterType="java.lang.Long">
select
u.id u_id,
u.avatarUrl u_avatarUrl,
u.nickname u_nickname,
a.id,
a.boardId,
a.userId,
a.title,
a.visitCount,
a.replyCount,
a.likeCount,
a.state,
a.createTime,
a.updateTime
from t_article a,
t_user u
where a.userId = u.id
and a.deleteState = 0
and a.boardId = #{boardId,jdbcType=BIGINT}
order by a.createTime desc;
</select>
2. 在Mapper.java中定义方法

3. 定义Service接口
在IArticleService里面定义接口
4. 实现Service接口
在ArticleServiceImpl里面实现接口
实现步骤
1> 对boardId进行非空校验
2> 根据boardId查询板块信息
3> 对板块信息进行非空校验
4> 不为空就调用mapper, 通过boardId把所有和板块相关的帖子进行返回
具体代码
/**
* 根据板块id查询所有的帖子列表
* @param boardId 板块id
* @return
*/
@Override
public List<Article> selectAllByBoardId(Long boardId) {
// 非空校验
if(boardId == null || boardId <=0){
// 打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 参数校验失败
// 抛出异常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
// 校验板块是否存在
Board board = boardService.selectById(boardId);
// 非空校验
if(board == null){
// 打印日志
log.warn(ResultCode.FAILED_BOARD_NOT_EXISTS.toString()+", board id = "+boardId);
// 抛出异常
throw new ApplicationException(AppResult.failed((ResultCode.FAILED_BOARD_NOT_EXISTS)));
}
// 调用DAO, 进行查询
List<Article> articles = articleMapper.selectAllByBoardId(boardId);
// 返回结果
return articles;
}
5. 单元测试
成功返回了板块的文章信息
6. Controller实现方法并对外提供API接口
实现的具体步骤
1> 根据是否传入boardId来进行分情况讨论
用户如果传入boardId, 则根据boardId查询板块下的所有帖子
用户如果没有传入boardId, 查询的是所有帖子
2> 对帖子列表集合进行非空校验
3> 返回响应结果
代码
/**
* 通过查询板块id返回所有帖子信息
* @param boardId
* @return
*/
@Operation(summary = "获取帖子列表")
@GetMapping("/getAllByBoardId")
public AppResult<List<Article>> getAllByBoardId(@Parameter(description = "板块id") @RequestParam(value = "boardId",required = false)Long boardId){
List<Article> articles;
if(boardId == null){
// 查询所有查询所有的帖子
articles = articleService.selectAll();
}else {
// 有boardId就查询和boardId相关的帖子集合
articles = articleService.selectAllByBoardId(boardId);
}
// 判断结果是否为空
if(articles == null){
// 如果结果集为空, 那么就创建一个空集合
articles = new ArrayList<>();
}
// 返回响应结果
return AppResult.success(articles);
}
7. 测试API接口
成功返回指定的板块信息
编写前端代码
article_list.html
后端是根据是否传入boardId来进行返回不同的板块信息, 现在的问题是怎么把boardId加入到url, 并且确定这个参数值
实现流程
ajax请求
成功显示
获取指定板块信息
实现逻辑
客⼾端发送请求传⼊版块Id,服务器响应对应版本的详情
参数要求
参数名 | 描述 | 类型 | 默认值 | 条件 |
id | 版块Id | long | 必须 |
接口规范
// 请求
GET http://127.0.0.1:58080/board/getById?id=1 HTTP/1.1
// 响应
HTTP/1.1 200
Content-Type: application/json
{
"code": 0,
"message": "成功",
"data": {
"id": 1,
"name": "Java",
"articleCount": 5,
"sort": 1,
"state": 0,
"createTime": "2023-01-14 11:02:18",
"updateTime": "2023-01-14 11:02:18"
}
}
后端代码实现
1. 在Mapper.xml中编写SQL语句

2. 在Mapper.java中定义方法

3. 定义Service接口

4. 实现Service接口

5. 单元测试

6. Controller实现方法并对外提供API接口
实现步骤
1> 直接调用service
2> 对查看结果进行校验
3> 返回结果
具体代码
@Operation(summary = "获取板块信息")
@GetMapping("/getById")
public AppResult<Board> getById(@Parameter(description = "板块Id") @RequestParam("id") @NonNull Long id){
// 因为id的非空校验已经由lombook给校验过了, 因此直接调用service
Board board = boradService.selectById(id);
// 对查看结果进行校验
// 表示板块记录在数据库中不存在, 或者已删除状态
if(board == null || board.getDeleteState()==1){
// 打印日志
log.warn(ResultCode.FAILED_NOT_EXISTS.toString());
// 抛出异常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_NOT_EXISTS));
}
// 返回结果
return AppResult.success(board);
}
7. 测试API接口
成功返回结果
前端代码实现
在相应的板块上添加帖子的数量
编写ajax
$.ajax({
type : 'get',
url : 'board/getById?id=' + boardId,
// 回调
success : function (respData) {
if(respData.code == 0) {
let board = respData.data;
// 成功时,更新页面的内容
console.log(board)
console.log(board.name)
console.log(board.articleCount)
$('#article_list_board_title').html(board.name); // 版块名
$('#article_list_count_board').html('帖子数量: ' +board.articleCount);
} else {
// 失败
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error : function () {
// 提示信息
$.toast({
heading: '错误',
text: '访问出现问题,请与管理员联系.',
icon: 'error'
});
}
});
实现结果
获取帖子详情
实现逻辑
1. ⽤⼾点击帖⼦,将帖⼦Id做为参数向服务器发送请求
2. 服务器查询帖⼦信息
3. 帖⼦访问次数加1
4. 返回查询结果
参数要求
参数名 | 描述 | 类型 | 默认值 | 条件 |
id | 帖⼦Id | long | 必须 |
接口规范
// 请求
GET http://127.0.0.1:58080/article/getById?id=1 HTTP/1.1
// 响应
HTTP/1.1 200
Content-Type: application/json
{
"code": 0,
"message": "成功",
"data": {
"id": 1,
"boardId": 1,
"userId": 1,
"title": "单元测试",
"visitCount": 14,
"replyCount": 2,
"likeCount": 3,
"state": 0,
"createTime": "2023-07-02 06:46:32",
"updateTime": "2023-07-05 10:16:43",
"content": "测试内容",
"board": {
"id": 1,D
"name": "Java"
},
"user": {
"id": 1,
"nickname": "bitboy",
"phoneNum": null,
"email": null,
"gender": 1,
"avatarUrl": null
},
"own": true
}
}
帖子详情后端代码编写
1. 在Mapper.xml中编写SQL语句
根据帖子Id查询帖子详情
在selectAll的基础上加入过滤条件, 帖子的id是用户点击的板块的帖子id,也要把板块也进行显示,就可以满足需求
ArticleExtmapper.xml
<!-- 自定义结果集映射-->
<resultMap id="AllInfoBaseResultMap" type="org.xiaobai.forum.model.Article" extends="ResultMapWithBLOBs">
<!-- 关联的用户的映射-->
<association property="user" resultMap="org.xiaobai.forum.dao.UserMapper.BaseResultMap" columnPrefix="u_"/>
<!-- 关联板块的映射-->
<association property="board" resultMap="org.xiaobai.forum.dao.BoardMapper.BaseResultMap"
columnPrefix="b_">
</association>
</resultMap>
<select id="selectById" resultMap="AllInfoBaseResultMap">
SELECT
u.id AS u_id,
u.avatarUrl AS u_avatarUrl,
u.nickname AS u_nickname,
u.gender AS u_gender,
u.isAdmin AS u_isAdmin,
u.state AS u_state,
u.deleteState AS u_deleteState,
b.id AS b_id,
b.name AS b_name,
b.state As b_state,
b.deleteState AS b_deleteState,
a.id,
a.boardId,
a.userId,
a.title,
a.content,
a.visitCount,
a.replyCount,
a.likeCount,
a.state,
a.createTime,
a.updateTime
FROM
t_article a,
t_user u,
t_board b
WHERE
a.userId = u.id
AND a.deleteState = 0
AND a.id = #{id,jdbcType=BIGINT}
AND a.boardId = b.id
</select>
注意:
1> 根据帖子id查询帖子详情
2> 补充要返回的结果, 并且要返回的信息包含板块, 因此要进行三表关联
3> 定义关联对象

2. 在Mapper.java中定义方法
3. 定义Service接口
为了避免歧义,修改一下xml的id名字, selectDetailById
在IArticleService里面添加方法
4. 实现Service接口
实现步骤
1> 对id进行非空校验
2> 调用DAO, 根据用户id查询返回文章信息
3> 创建一个新的帖子对象, 然后更新里面的访问次数
4> 把新的帖子对象更新到数据库
5> 更新原始返回对象的访问次数
6> 返回原始对象
具体代码
/**
* 根据帖子Id查询详情
* @param id 帖子Id
* @return 帖子详情
*/
@Override
public Article selectDetailById(Long id) {
// 非空校验
if (id == null || id <= 0) {
// 打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 参数校验失败
// 抛出异常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
// 调用DAO
Article article = articleMapper.selectDetailById(id);
// 判断结果是否为空
if (article == null) {
// 打印日志
log.warn(ResultCode.FAILED_ARTICLE_NOT_EXISTS.toString());// 帖子不存在
// 抛出异常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_ARTICLE_NOT_EXISTS));
}
// 更新帖子的访问次数
Article updateArticle = new Article();
updateArticle.setId(article.getId());// 获取文章作者id
updateArticle.setVisitCount(article.getVisitCount() + 1);// 访问次数+1
// 保存到数据库
int row = articleMapper.updateByPrimaryKeySelective(updateArticle);
if (row != 1) {
// 打印日志
log.warn(ResultCode.ERROR_SERVICES.toString());// 服务器内部错误
// 抛出异常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
}
// 更新返回对象的访问次数
article.setVisitCount(article.getVisitCount() + 1);
// 返回帖子详情
return article;
}
5. 单元测试
返回了我们需要的信息
6. Controller实现方法并对外提供API接口
步骤
1> 调用Service, 获取帖子详情
2> 判断结果是否为空
3> 返回结果
具体代码
@Operation(summary = "根据帖子Id获取详情")
@GetMapping("/details")
public AppResult<Article> getDetails(@Parameter(description = "帖子Id") @RequestParam("id") @NonNull Long id){
// 调用Service, 获取帖子详情
Article article = articleService.selectDetailById(id);
// 判断结果是否为空
if(article == null){
// 返回错误信息
return AppResult.failed(ResultCode.FAILED_ARTICLE_NOT_EXISTS);
}
// 返回结果
return AppResult.success(article);
}
7. 测试API接口
前端代码编写
点击帖子的标题就会来到帖子的详细界面
构造ajax请求
$.ajax({
type : 'get',
url : 'article/details?id=' + currentArticle.id,
// 回调
success : function (respData) {
if (respData.code == 0) {
// 成功
initArticleDetails(respData.data);
} else {
// 提示信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error : function () {
// 提示信息
$.toast({
heading: '错误',
text: '访问出现问题,请与管理员联系.',
icon: 'error'
});
}
});
帖子详情页面
帖子详情显示编辑与删除按钮
能否删除(显示删除按钮)的判断条件: 当前登录的用户与帖子作者是不是为同一个用户
有俩种方法: 前端/后端
后端代码编写
在Article类里面添加一个isOwn属性来判断是否是作者
controller里面添加是否当前的帖子的id和作者id一样
测试