论坛系统(4)
用户详情
获取用户信息
实现逻辑
⽤⼾提交请求,服务器根据是否传⼊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
{
"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"}}
用户帖子列表
实现逻辑
对应版块中显⽰的帖⼦列表以发布时间降序排列不传⼊版块Id返回所有帖⼦
1. ⽤⼾点击某个版块或⾸⻚时,将版块Id做为参数向服务器发送请求
2. 服务器接收请求,并获取版块Id,查询对应版块下的所有帖⼦
3. 返回查询结果
参数要求
参数名 | 描述 | 类型 | 默认值 | 条件 |
boardId | 版块Id | Long | 可为空 |
接口规范
// 请求
// 返回指定版块下的帖⼦列表
GET http://127.0.0.1:58080/article/getAllByBoardId?boardId=1 HTTP/1.1(以传入的id为准)
// 返回所有的帖⼦列表
GET http://127.0.0.1:58080/article/getAllByBoardId HTTP/1.1 (从session中获取当前登录用户的Id)
// 响应
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
}}]
后端代码实现
1. 在Mapper.xml中编写SQL语句
需要定义一个根据用户Id查询的SQL
2. 在Mapper.java中定义方法
在ArticleMapper里面定义selectByUserId方法

3. 定义Service接口
在IArticleService里面定义selectByUserId接口

4. 实现Service接口
实现步骤
1> 对用户id进行非空校验
2> 调用service, 根据userId校验用户是否存在
3> 调用DAO根据用户id查询用户的帖子
具体代码
@Overridepublic List<Article> selectByUserId(Long userId) {// 对userId进行非空校验if (userId == null || userId <= 0) {// 打印日志log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));}// 校验用户是否存在User user = userService.selectById(userId);if (user == null) {// 打印日志log.warn(ResultCode.FAILED_USER_NOT_EXISTS.toString() + ", user id = " + userId);// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_USER_NOT_EXISTS));}// 调用DAOList<Article> articles = articleMapper.selectByUserId(userId);// 返回结果return articles;}
5. 单元测试
返回帖子列表

6. Controller实现方法并对外提供API接口
实现步骤
1> 判断userId是否为空, 是空的就去获取登录用户的userId
2> 调用Service,通过userId查询对应的作者的帖子
具体代码
@Operation(summary = "获取用户的帖子列表")@GetMapping("/getAllByUserId")public AppResult<List<Article>> getAllByUserId(HttpServletRequest request,@Parameter(description = "用户Id") @RequestParam(value = "userId",required = false) Long userId){// 如果UserId为空, 那么就从sessionId获取当前登录的用户Idif(userId == null){// 获取SessionHttpSession session = request.getSession(false);// 获取User对象User user = (User) session.getAttribute(AppConfig.USER_SESSION);userId = user.getId();}// 调用ServiceList<Article> articles = articleService.selectByUserId(userId);// 返回结果return AppResult.success(articles);}
7. 测试API接口
不管传不传userId都能查询帖子信息
前端代码实现
构造ajax请求
实现结果
个人中心
修改个人信息
实现逻辑
只对⽤⼾的基本信息做修改,不包括密码与头像,修改密码与修改头像提供单独的修改接⼝
1. ⽤⼾打开找修改个⼈信息⻚⾯
2. 填⼊要修改的内容并提交服务器
3. 服务器获取登录⽤⼾的Id(从session中获取),并根据提交的内容修改数据
4. 返回成功或失败,如果成功返回更新后的个⼈信息
5. 浏览器刷新个⼈信息
参数要求
后端可以提供一个统一的接口, 更具用户提供参数来封装要修改的对象
参数名 | 描述 | 类型 | 默认值 | 条件 |
username | ⽤⼾名(⽤于登录) | String | 可选,唯⼀,校验是否存在 | |
nickname | 昵称 | String | 可选, | |
gender | 性别 | Byte | 可选,0⼥, 1男,2保密 | |
邮箱 | String | 可选 | ||
phoneNum | 电话 | String | 可选 | |
remark | 个⼈简介 | String | 可选 |
接口规范
// 请求
POST http://127.0.0.1:58080/user/modifyInfo HTTP/1.1
Content-Type: application/x-www-form-urlencoded
id=1&nickname=bitboy&gender=1&email=bitboy%40bit.com&phoneNum=18888888888&remar
k=%E6%88%91%E6%98%AF%E4%B8%80%E4%B8%AA%E4%BC%98%E7%A7%80%E7%9A%84%E5%A5%BD%E9%9
D%92%E5%B9%B4
// 响应
HTTP/1.1 200
Content-Type: application/json
{
"code": 0,
"message": "成功",
"data": {
"id": 1,
"username": "bitboy",
"nickname": "bitboy",
"phoneNum": "18888888888",
"email": "bitboy@bit.com",
"gender": 1,
"avatarUrl": null,
"articleCount": 1,
"isAdmin": 1,
"remark": "我是⼀个优秀的好⻘年",
"state": 0,
"createTime": "2023-06-24 11:59:15",
"updateTime": "2023-07-09 03:05:23"
}
}
后端代码编写
1. 在Mapper.xml中编写SQL语句
2. 在Mapper.java中定义方法
直接使用动态SQL,已经生成
3. 定义Service接口
IUserService定义modifyInfo接口

4. 实现Service接口
实现modifyInfo
实现步骤
1> 对用户进行非空校验
2> 根据用户id查询用户是否存在(不存在直接抛异常)
3> 定义一个标志位
4> 设置一个对象,用来动态更新sql的时候对修改的属性进行操作
5> 对用户名进行校验是否要更改(必须验证时唯一值)
6> 对其他参数校验是否要更改
7> geng'ju
具体代码
@Overridepublic void modifyInfo(User user) {// 1. 非空校验if (user == null || user.getId() == null || user.getId() <= 0) {// 打印日志log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));}// 2. 根据用户id查询校验用户是否存在User existsUser = userMapper.selectByPrimaryKey(user.getId());// 如果查不到, 就直接抛异常if (existsUser == null) {// 打印日志log.warn(ResultCode.FAILED_USER_NOT_EXISTS.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_USER_NOT_EXISTS));}// 3. 定义一个标志位boolean checkAttr = false;// false表示没有校验通过// 4. 定义一个专门用来更新的对象, 防止用户传入的User对象设置了其他属性// 当使用动态SQL进行更新的时候, 覆盖了没有经过校验的字段User updateUser = new User();// 5. 设置用户IdupdateUser.setId(user.getId());// 6. 对每一个参数进行校验并赋值if (!StringUtil.isEmpty(user.getUsername())&& !user.getUsername().equals(existsUser.getUsername())) {// 用户名不为空并且修改后的用户名和原先用户名不一样// 更新用户名之前, 需要进行唯一性校验, 去表里面找有没有修改后的用户User checkUser = userMapper.selectByUserName(user.getUsername());if (checkUser != null) {// 用户已存在log.warn(ResultCode.FAILED_USER_EXISTS.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_USER_EXISTS));}// 数据库中没有找到相应的用户, 表示可以修改用户名updateUser.setUsername(user.getUsername());// 更新标志位checkAttr = true;}// 7. 校验昵称if (!StringUtil.isEmpty(user.getNickname())&& !user.getNickname().equals(existsUser.getNickname())) {// 设置昵称updateUser.setNickname(user.getNickname());// 更新标志位checkAttr = true;}// 8. 校验性别if (user.getGender() != null && user.getGender() != existsUser.getGender()) {// 设置性别updateUser.setGender(user.getGender());// 合法性校验if (updateUser.getGender() > 2 || updateUser.getGender() < 0) {updateUser.setGender((byte) 2);}// 更新标志位checkAttr = true;}// 9. 校验邮箱if (!StringUtil.isEmpty(user.getEmail())&& !user.getEmail().equals(existsUser.getEmail())) {// 设置邮箱updateUser.setEmail(user.getEmail());// 更新标志位checkAttr = true;}// 9. 校验电话号码if (!StringUtil.isEmpty(user.getPhoneNum())&& !user.getPhoneNum().equals(existsUser.getPhoneNum())) {// 设置电话号码updateUser.setPhoneNum(user.getPhoneNum());// 更新标志位checkAttr = true;}// 10. 校验个人简介if (!StringUtil.isEmpty(user.getRemark())&& !user.getRemark().equals(existsUser.getRemark())) {// 设置电话号码updateUser.setRemark(user.getRemark());// 更新标志位checkAttr = true;}// 11. 根据标志位来决定是否可以执行更新if (checkAttr == false) {// 打印日志log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));}// 12. 调用DAOint row = userMapper.updateByPrimaryKeySelective(updateUser);if (row != 1) {log.warn(ResultCode.FAILED.toString() + ", 受影响的行数不等于 1 .");throw new ApplicationException(AppResult.failed(ResultCode.FAILED));}}
5. 单元测试
修改成功

6. Controller实现方法并对外提供API接口
实现步骤
1> 接受参数, 对参数进行非空校验
2> 从session中获取要修改的用户Id
3> 封装更新的User对象
4> 调用Service中的方法
5> 查询最新的用户信息
6> 把最新的用户信息设置到session中
7> 返回结果
具体代码
// 修改个人信息@Operation(summary = "修改个人信息")@GetMapping("/modifyInfo")public AppResult modifyInfo(HttpServletRequest request,@Parameter(description = "用户名") @RequestParam(value = "username", required = false) String username,@Parameter(description = "昵称") @RequestParam(value = "nickname", required = false) String nickname,@Parameter(description = "性别") @RequestParam(value = "gender", required = false) Byte gender,@Parameter(description = "邮箱") @RequestParam(value = "email", required = false) String email,@Parameter(description = "电话号") @RequestParam(value = "phoneNum", required = false) String phoneNum,@Parameter(description = "个人简介") @RequestParam(value = "remark", required = false) String remark) {// 1. 接受参数// 2. 对参数做非空校验(全部都为空,就返回错误信息)if (StringUtil.isEmpty(username) && StringUtil.isEmpty(nickname)&& StringUtil.isEmpty(email) && StringUtil.isEmpty(phoneNum)&& StringUtil.isEmpty(remark) && gender == null) {// 返回错误信息return AppResult.failed("请输入要修改的内容");}// 3. 从session中获取用户IdHttpSession session = request.getSession(false);User user = (User) session.getAttribute(AppConfig.USER_SESSION);// 4. 封装更新的User对象User updateUser = new User();updateUser.setId(user.getId()); // 用户IdupdateUser.setUsername(username); // 用户名updateUser.setNickname(nickname); // 昵称updateUser.setGender(gender); // 性别updateUser.setEmail(email); // 邮箱updateUser.setPhoneNum(phoneNum); // 电话updateUser.setRemark(remark); // 个人简介// 5. 调用Service中的方法userService.modifyInfo(updateUser);// 6. 查询最新的用户信息user = userService.selectById(user.getId());// 7. 把最新的用户信息设置到session中session.setAttribute(AppConfig.USER_SESSION,user);// 8. 返回结果return AppResult.success(user);}
7. 测试API接口
前端代码编写
编写ajax请求
最终的结果
修改密码
实现逻辑
• 为修改密码提供⼀个单独的接⼝及操作⻚⾯
1. ⽤⼾打开修改密码⻚⾯
2. 输⼊原密码、新密码、重复新密码并提交服务器
3. 服务器校验原密码是否正确
4. 原密码校验通过更新密码
5. 返回成功或失败
参数要求
参数名 | 描述 | 类型 | 默认值 | 条件 |
oldPassword | 原密码 | String | 必须 | |
newPassword | 新密码 | String | 必须 | |
passwordRepea t | 确认新密码 | String | 必须,与新密码相同 |
接口规范
// 请求
POST http://127.0.0.1:58080/user/modifyPwd HTTP/1.1
Content-Type: application/x-www-form-urlencoded
id=1&oldPassword=123456&newPassword=123456&passwordRepeat=123456
// 响应
HTTP/1.1 200
Content-Type: application/json
{
"code": 0,
"message": "成功",
"data": null
}
后端代码实现
1. 在Mapper.xml中编写SQL语句
2. 在Mapper.java中定义方法
1.2实现过
3. 定义Service接口
4. 实现Service接口
实现步骤
1> 对id进行非空校验
2> 根据id去数据库查询用户的信息, 并校验用户是否存在
3> 校验老密码是否正确(取出老盐, 然后和老密码进行md5加密)
4> 然后老密码和用户密码进行比对
5> 构造新的盐和新密码
6> 构造要更新的对象
7> 调用DAO
具体代码
/*** 修改密码* @param id 用户Id* @param newPassword 新密码* @param oldPassword 老密码*/@Overridepublic void modifyPassword(Long id, String newPassword, String oldPassword) {// 对id进行非空校验if (id == null || id <= 0 || StringUtil.isEmpty(newPassword) || StringUtil.isEmpty(oldPassword)) {// 打印日志log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));}// 查询用户的信息User user = userMapper.selectByPrimaryKey(id);// 校验用户是否存在if (user == null || user.getDeleteState() == 1) {// 打印日志log.warn(ResultCode.FAILED_USER_NOT_EXISTS.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_USER_NOT_EXISTS));}// 校验老密码是否正确// 对老密码进行加密, 获取密文: 获取旧的盐, 然后让旧的盐和旧的密码拼接, 然后用md5进行加密String oldEncryptPassword = MD5Util.md5Salt(oldPassword,user.getSalt());// 和用户当前的密码进行比较if(!oldEncryptPassword.equalsIgnoreCase(user.getPassword())){// 打印日志log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));}// 生成一个新的盐String salt = UUIDUtil.UUID_32();// 生成新密码的密文 ((明文)md5加密+扰动字符)md5加密String encryptPassword = MD5Util.md5Salt(newPassword,salt);// 构造要更新的对象User updateUser = new User();updateUser.setId(user.getId()); // 用户idupdateUser.setSalt(salt);// 新生成的盐updateUser.setPassword(encryptPassword); // 新密码对应的密文Date date = new Date();updateUser.setUpdateTime(date); // 更新时间// 调用DAOint row = userMapper.updateByPrimaryKeySelective(updateUser);if (row != 1) {log.warn(ResultCode.FAILED.toString() + ", 受影响的行数不等于 1 .");throw new ApplicationException(AppResult.failed(ResultCode.FAILED));}}
5. 单元测试

6. Controller实现方法并对外提供API接口
实现逻辑
1> 校验新密码和确认密码是否相同
2> 获取当前登录的用户信息
3> 调用Service
4> 销毁session 回到登录页面重新登录
5> 返回结果
具体代码
@Operation(summary = "修改密码")@PostMapping("/modifyPwd")public AppResult modifyPassword(HttpServletRequest request,@Parameter(description = "原密码") @RequestParam("oldPassword")@NonNull String oldPassword,@Parameter(description = "新密码") @RequestParam("newPassword")@NonNull String newPassword,@Parameter(description = "确认密码") @RequestParam("passwordRepeat")@NonNull String repeatPassword){// 1. 校验新密码和确认密码是否相同if(!newPassword.equalsIgnoreCase(repeatPassword)){// 返回错误描述return AppResult.failed(ResultCode.FAILED_TWO_PWD_NOT_SAME);}// 2. 获取当前登录的用户信息HttpSession session = request.getSession(false);User user =(User) session.getAttribute(AppConfig.USER_SESSION);// 3. 调用ServiceuserService.modifyPassword(user.getId(),newPassword,oldPassword);// 4. 销毁session 回到登录页面重新登录if (session != null) {session.invalidate();}// 5. 返回结果return AppResult.success();}
7. 测试API接口
站内信
发送
实现逻辑
1. 在⽬录⽤⼾详情界⾯,点击发送站内信接钮,进⼊编辑⻚⾯
2. 编写站内信内容,点击发送按钮
3. 提⽰发送结果
参数要求
参数名 | 描述 | 类型 | 条件 | 默认值 |
receiveUserId | 接收⽤⼾Id | Long | 必须 | |
content | 站内信内容 | String | 必须 |
接口规范
// 请求
POST http://127.0.0.1:58080/message/send HTTP/1.1
Content-Type: application/x-www-form-urlencoded
receiveUserId=2&content=%E4%BD%A0%E5%A5%BD%E5%95%8A
// 响应
HTTP/1.1 200
Content-Type: application/json
{
"code": 0,
"message": "成功",
"data": null
}
后端代码实现
1. 在Mapper.xml中编写SQL语句
2. 在Mapper.java中定义方法
实现过了
3. 定义Service接口
4. 实现Service接口
实现逻辑
1> 对Message进行非空校验
2> 根据message的id查询接收者是否存在
3> 设置默认值
4> 设置创建时间和更新时间
5> 调用DAO,插入message
具体代码
package org.xiaobai.forum.service.impl;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.MessageMapper;
import org.xiaobai.forum.exception.ApplicationException;
import org.xiaobai.forum.model.Message;
import org.xiaobai.forum.model.User;
import org.xiaobai.forum.service.IMessageService;
import org.xiaobai.forum.service.IUserService;
import org.xiaobai.forum.utils.StringUtil;import javax.annotation.Resource;
import java.util.Date;@Service
@Slf4j
public class MessageServiceImpl implements IMessageService {@Resourceprivate MessageMapper messageMapper;@Resourceprivate IUserService userService;@Overridepublic void create(Message message) {// 非空校验if (message == null || message.getPostUserId() == null || message.getReceiveUserId() == null|| StringUtil.isEmpty(message.getContent())) {// 打印日志log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));}// 校验接收者是否存在User user = userService.selectById(message.getReceiveUserId());if (user == null || user.getDeleteState() == 1) {// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));}// 设置默认值message.setState((byte) 0);message.setDeleteState((byte) 0);// 设置创建与更新时间Date date = new Date();message.setCreateTime(date);message.setUpdateTime(date);// 调用DAOint row = messageMapper.insertSelective(message);if (row != 1) {log.warn(ResultCode.FAILED_CREATE.toString());throw new ApplicationException(AppResult.failed(ResultCode.FAILED_CREATE));}}
}
5. 单元测试

6. Controller实现方法并对外提供API接口
实现逻辑
1> 获取当前登录的用户信息
2> 判断当前登录用户的状态, 如果是禁言是不能发送站内信
3> 判断发送人是不是作者(不能自己给自己发送站内信)
4> 根据接收者id校验接收者是否存在
5> 封装发送对象
6> 调用Service, 把message传进去
7> 返回结果
具体代码
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.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.Message;
import org.xiaobai.forum.model.User;
import org.xiaobai.forum.service.IMessageService;
import org.xiaobai.forum.service.IUserService;import javax.annotation.Resource;@Tag(name ="站内信接口",description = "给其他用户发送信息")
@RestController
@RequestMapping("/message")
@Slf4j
public class MessageController {@Resourceprivate IMessageService messageService;@Resourceprivate IUserService userService;@Operation(summary = "发送站内信")@PostMapping("/send")public AppResult send(HttpServletRequest request,@Parameter(description = "接收者Id")@RequestParam("receiveUserId") @NonNull Long receiveUserId,@Parameter(description = "内容")@RequestParam("content")@NonNull String content){// 获取当前登录的用户信息HttpSession session = request.getSession(false);User user = (User) session.getAttribute(AppConfig.USER_SESSION);// 1. 当前登录用户的状态, 如果是禁言状态是不能发送站内信if(user.getState() == 1){// 返回用户状态异常return AppResult.failed(ResultCode.FAILED_USER_BANNED);}// 2. 不能给自己发送站内信if(user.getId() == receiveUserId){return AppResult.failed("不能给自己发送站内信");}// 3. 校验接收者是否存在User receiveUser = userService.selectById(receiveUserId);// 判断接收者状态是否正常if (receiveUser == null || receiveUser.getDeleteState() == 1) {// 返回接收者状态不正常return AppResult.failed("接收者状态异常");}// 4. 封装发送对象Message message = new Message();message.setPostUserId(user.getId());// 发送者Idmessage.setReceiveUserId(receiveUserId);// 接收者Idmessage.setContent(content);// 站内信内容// 5.调用ServicemessageService.create(message);// 6. 返回结果return AppResult.success("发送成功");}
}
7. 测试API接口
未读数
实现逻辑
查询当前登录⽤⼾的未读站内信数量(直接从session中获取用户的Id)
接口规范
// 请求
GET http://127.0.0.1.41:58080/message/getUnreadCount HTTP/1.1
// 响应
HTTP/1.1 200
Content-Type: application/json
{"code":0,"message":"成功","data":1}
后端代码实现
1. 在Mapper.xml中编写SQL语句
根据用户Id查询该用户的未读数量 state = 0(0表示未读)
2. 在Mapper.java中定义方法

3. 定义Service接口

4. 实现Service接口
实现逻辑
1> 对receiverUserId进行参数非空校验
2> 直接调用DAO,通过receiverUserId查询该用户未读的信息数
3> 检查count
具体代码
/*** 根据用户Id查询该用户的未读数* @param receiveUserId 用户id* @return 未读数*/@Overridepublic Integer selectUnreadCount(Long receiveUserId) {// 参数非空校验if(receiveUserId == null || receiveUserId <=0){// 打印日志log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));}// 直接调用DAOInteger count = messageMapper.selectUnreadCount(receiveUserId);// 检查countif (count == null) {log.warn(ResultCode.ERROR_SERVICES.toString());throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));}// 返回结果return count;}
5. 单元测试

6. Controller实现方法并对外提供API接口
实现逻辑
1> 根据session获取当前登录的用户
2> 调用Service, 根据用户Id获取用户站内信未读数
3> 返回结果集
具体代码
@Operation(summary = "获取未读数")@GetMapping("/getUnreadCount")public AppResult<Integer> getUnreadCount(HttpServletRequest request){// 1. 获取当前登录的用户HttpSession session = request.getSession(false);User user = (User) session.getAttribute(AppConfig.USER_SESSION);// 2. 调用ServiceInteger count = messageService.selectUnreadCount(user.getId());// 当前登录的id就是接收者id// 3. 返回结果return AppResult.success(count);}
7. 测试API接口
列表
实现逻辑
⽤⼾访问API,服务器响应当前登录⽤⼾的站内信
接口规范
// 请求
GET http://127.0.0.1:58080/message/getAll HTTP/1.1
// 响应
HTTP/1.1 200
Content-Type: application/json
{
"code": 0,
"message": "成功",
"data": [{
"id": 11,
"postUserId": 32,
"receiveUserId": 3,
"content": "真的可以发出去吗\n",
"state": 2,
"createTime": "2023-06-20 11:21:09",
"updateTime": "2023-06-25 11:24:38",
"postUser": {
"id": 32,
"nickname": "ljl",
"phoneNum": null,
"email": null,
"gender": 2,
"avatarUrl": null
}
}]}
后端代码实现
1. 在Mapper.xml中编写SQL语句


2. 在Mapper.java中定义方法

3. 定义Service接口

4. 实现Service接口
实现逻辑
1> 参数非空校验
2> 调用DAO
3> 返回结果
具体代码
@Overridepublic List<Message> selectByReceiveUserId(Long receiveUserId) {// 参数非空校验if(receiveUserId == null || receiveUserId <=0){// 打印日志log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));}// 调用DAOList<Message> messages = messageMapper.selectByReceiveUserId(receiveUserId);// 返回结果return messages;}
5. 单元测试

6. Controller实现方法并对外提供API接口
具体逻辑
1> 获取当前登录的用户
2> 根据用户的id获取用户的站内信列表
3> 返回结果
代码编写
@Operation(summary = "查询用户的所有站内信")@GetMapping("/getAll")public AppResult<List<Message>> getAll(HttpServletRequest request){// 1. 获取当前登录的用户HttpSession session = request.getSession(false);User user = (User) session.getAttribute(AppConfig.USER_SESSION);// 2. 调用ServiceList<Message> messages = messageService.selectByReceiveUserId(user.getId());// 3. 返回结果return AppResult.success(messages);}
7. 测试API接口
更新状态
实现逻辑
1. ⽤⼾点击站内信,显⽰详情⻚⾯
2. 更新未读状态的站内信为已读
参数要求
参数名 | 描述 | 类型 | 条件 | 默认值 |
id | 站内信Id | long | 必须 |
接口规范
// 请求
POST http://127.0.0.1:58080/message/markRead HTTP/1.1
Content-Type: application/x-www-form-urlencoAded
id=1
// 响应
HTTP/1.1 200
Content-Type: application/json
{"code": 0,
"message": "成功",
"data": null
}
后端代码实现
1. 在Mapper.xml中编写SQL语句
2. 在Mapper.java中定义方法
之前写了
3. 定义Service接口

4. 实现Service接口
实现逻辑
1> 对id, state进行校验
2> 动态更新, 构造更新对象
3> 调用DAO,更新是否已读
具体代码
@Overridepublic void updateStateById(Long id, Byte state) {// 对id进行非空校验, state: 0 未读 1 已读 2 已回复if (id == null || id <= 0 || state < 0 || state > 2) {// 打印日志log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 更新帖子数失败// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));}// 构造更新对象Message updateMessage = new Message();updateMessage.setId(id);updateMessage.setState(state);Date date = new Date();updateMessage.setUpdateTime(date);// 调用DAOint row = messageMapper.updateByPrimaryKeySelective(updateMessage);if(row !=1){log.warn(ResultCode.ERROR_SERVICES.toString());throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));}}
5. 单元测试

6. Controller实现方法并对外提供API接口
实现逻辑
1> 根据Id查询站内信,并且查询站内信是否存在
2> 判断站内信是不是自己
3> 调用Service把id所对应的Message更新为已读状态
具体代码
@Operation(summary = "更新为已读")@PostMapping("/markedRead")public AppResult markedRead(HttpServletRequest request,@Parameter(description = "站内信Id") @RequestParam("id") @NonNull Long id) {//1. 根据Id查询站内信Message message = messageService.selectById(id);//2. 站内信是否存在if (message == null || message.getDeleteState() == 1) {// 返回错误信息return AppResult.failed(ResultCode.FAILED_MESSAGE_NOT_EXISTS);}//3. 站内信是不是自己的HttpSession session = request.getSession(false);User user = (User) session.getAttribute(AppConfig.USER_SESSION);// 判断站内信的收件人是不是当前的登录if (user.getId() != message.getReceiveUserId()) {// 返回错误信息return AppResult.failed(ResultCode.FAILED_FORBIDDEN);}// 调用Service, 把状态更新为已读messageService.updateStateById(id,(byte)1);// 返回结果return AppResult.success();}
7. 测试API接口
回复
实现逻辑
1. ⽤⼾在站内信详情⻚⾯点击回复按钮,显⽰回复区
2. 填写回复内容并提交到服务器
3. ⽤⼾不能回复接收者不是⾃⼰的站内信
4. 站内信状态置为已回复(并且要在数据库中新增一条站内信记录, 俩个数据的修改操作在一个业务逻辑中, 因此要用事务进行管理)
参数要求
参数名 | 描述 | 类型 | 条件 | 默认值 |
repliedId(要回复的站内信id) | 站内信Id | Long | 必须 | |
content | 内容 | String | 必须 |
接口规范
// 请求
POST http://127.0.0.1:58080/message/reply HTTP/1.1
Content-Type: application/x-www-form-urlencoded
repliedId=1&receiveUserId=2&content=%E4%BD%A0%E5%A5%BD%E5%95%8A
// 响应
HTTP/1.1 200
Content-Type: application/json
{
"code": 0,
"message": "成功",
"data": null
}
后端代码实现
1. 在Mapper.xml中编写SQL语句
2. 在Mapper.java中定义方法
3. 定义Service接口

4. 实现Service接口
实现步骤
1> 对repliedId进行非空校验
2> 校验repliedId是不是存在
3> 更新状态为已读
4> 回复的内容写入数据库
具体代码
/*** 回复站内信* @param repliedId 要回复的站内信Id* @param message 回复的对象*/@Overridepublic void reply(Long repliedId, Message message) {// 对repliedId进行非空校验if (repliedId == null || repliedId <= 0) {// 打印日志log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));}// 校验repliedId是不是存在Message existsMessage = messageMapper.selectByPrimaryKey(repliedId);// 信息是否存在if (existsMessage == null || existsMessage.getDeleteState() == 1) {// 打印日志log.warn(ResultCode.FAILED_MESSAGE_NOT_EXISTS.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_MESSAGE_NOT_EXISTS));}// 更新状态为已回复updateStateById(repliedId,(byte)2);// 回复的内容写入数据库create(message);}
5. 单元测试

6. Controller实现方法并对外提供API接口
实现步骤
1> 校验当前登录用户的状态(是否禁言)
2> 校验要恢复的站内信状态(是否有站内信)
3> 校验是不是给自己回复
4> 构造回复对象
5> 调用Service
6> 返回结果
具体代码
@Operation(summary = "回复站内信")@PostMapping("/reply")public AppResult reply(HttpServletRequest request,@Parameter(description = "要回复的站内信Id") @RequestParam("repliedId") @NonNull Long repliedId,@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);}// 校验要回复的站内信状态Message existsMessage = messageService.selectById(repliedId);if (existsMessage == null || existsMessage.getDeleteState() == 1) {// 返回错误描述return AppResult.failed(ResultCode.FAILED_MESSAGE_NOT_EXISTS);}// 不能给自己回复if (user.getId() == existsMessage.getPostUserId()) {// 返回错误描述return AppResult.failed("不能回复自己的站内信");}// 构造对象Message message = new Message();message.setPostUserId(user.getId()); // 发送者message.setReceiveUserId(existsMessage.getPostUserId()); // 接收者message.setContent(content); // 内容// 调用ServicemessageService.reply(repliedId, message);// 返回结果return AppResult.success();}
7. 测试API接口
相关文章:

论坛系统(4)
用户详情 获取用户信息 实现逻辑 ⽤⼾提交请求,服务器根据是否传⼊Id参数决定返回哪个⽤⼾的详情 1. 不传⽤⼾Id,返回当前登录⽤⼾的详情(从session获取) 2. 传⼊⽤⼾Id,返回指定Id的⽤⼾详情(根据用户id去查) 俩种方式获得用户信息 参…...
本地Markdown开源知识库选型指南
本地Markdown开源知识库选型指南 以下是几款优秀的本地Markdown开源知识库解决方案,适合不同需求场景: 1. Obsidian (非完全开源但免费) 特点:基于Markdown的本地优先知识管理,丰富的插件生态优势:双向链接、图形视…...
【.net core】SkiaSharp 如何在Linux上实现
1. 安装依赖库 首先需要安装 SkiaSharp 运行时依赖: # Ubuntu/Debian sudo apt-get update sudo apt-get install -y libfontconfig1 libfreetype6 libx11-6 libx11-xcb1 libxcb1 \libxcomposite1 libxcursor1 libxdamage1 libxi6 libxtst6 \libnss3 libcups2 lib…...
后端项目中静态文案国际化语言包构建选型
这是一个很关键的问题。在做国际化(i18n)时,不同语言包格式如 .resx、.properties 和 .json 都可用,但各自有适用场景、特性与限制,你在选择时可以根据你的开发语言、生态和维护成本权衡。 ✅ 一张对比表:.…...
前端面经 React常见的生命周期
初始化阶段 constructor state的初始化,防抖节流的绑定getDerivedStateFromProps 静态函数 当作纯函数使用 传入props和state,合并成一个新的statecomponentWillMount 组件如果有getDrivedStatefromprops不会执行 针对一些接口的预请求时使用rendercomp…...

力扣面试150题--二叉树的层平均值
Day 54 题目描述 思路 初次做法(笨):使用两个队列,一个队列存放树的节点,一个队列存放对应节点的高度,使用x存放上一个节点,highb存放上一个节点的高度,sum存放当前层的节点值之和…...

【Doris入门】Doris初识:分布式分析型数据库的核心价值与架构解析
目录 1 Doris简介与核心价值 2 Doris架构深度解析 2.1 Frontend(FE)架构 2.2 Backend(BE)架构 3 Doris核心概念详解 3.1 数据分布模型 3.2 Tablet与Replica 3.3 数据模型 4 Doris关键技术解析 4.1 存储引擎 4.2 查询执…...
C#面试问题41-60
41. What is the Singleton design pattern? Singleton is a class that only allows creating a single instance of itselt. 单例设计模式是一个类,它只允许创建自己的单个实例。 构造函数防止他在单例类以外的地方被调用。 使用情景:need a sing…...

数据结构与算法学习笔记(Acwing 提高课)----动态规划·区间DP
数据结构与算法学习笔记----动态规划区间DP author: 明月清了个风 first publish time: 2025.5.26 ps⭐️区间DP的特征在于子结构一般是一个子区间上的问题,涉及到的问题也非常多,如环形区间,记录方案数,高精度,二维…...
【合集】Linux——31个普通信号
Linux普通信号总表(1-31) 编号信号名触发原因默认动作1SIGHUP终端连接断开(如SSH会话终止)或守护进程重载配置(如nginx -s reload)终止进程2SIGINT用户输入CtrlC中断前台进程终止进程…...

从0到1搭建AI绘画模型:Stable Diffusion微调全流程避坑指南
从0到1搭建AI绘画模型:Stable Diffusion微调全流程避坑指南 系统化学习人工智能网站(收藏):https://www.captainbed.cn/flu 文章目录 从0到1搭建AI绘画模型:Stable Diffusion微调全流程避坑指南摘要引言一、数据集构…...
ASP.NET Core 中JWT的基本使用
文章目录 前言一、JWT与RBAC二、JWT 的作用三、RBAC 的核心思想四、使用1、配置文件 (appsettings.json)2、JWT配置模型 (Entity/JwtSettings.cs)3、服务扩展类,JWT配置 (Extensions/ServiceExtensions.cs)4、用户仓库接口服务5、认证服务 (Interface/IAuthService.…...
未来技术展望
应用场景:海量数据并行处理 技术融合: # 概念代码:量子加速的数据清洗 from quantum_processor import PhotonicProcessordef quantum_data_cleaning(data):# 使用光量子处理器并行处理千万级数据processor = PhotonicProcessor(model="Xanadu Borealis")return …...

从一到无穷大 #46:探讨时序数据库Deduplicate与Compaction的设计权衡
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作),由 李兆龙 确认,转载请注明版权。 文章目录 引言Compaction AlgorithmsCompact Execution Flow Based On VeloxLocalMergeSource的…...

vue3 导出excel
需求:导出自带格式的excel表格 1.自定义二维数组格式 导出 全部代码: <el-button click"exportExcel">导出</el-button> const exportExcel () > {const data [[商品, 单价, 数量, 总价],[A, 100, 1.55, { t: n, f: B2*C2…...
带你手写React中的useReducer函数。(底层实现)
文章目录 前言一、为什么需要 Reducer?二、Reducer 的核心概念1. Reducer 函数2. useReducer 钩子 三,手写react中的useReducer 总结 前言 在 React 开发中,useReducer 是管理复杂状态逻辑的利器。它类似于 Redux 的简化版,允许我…...

day024-网络基础-TCP与UDP、DNS
文章目录 1. 李导推荐书籍2. OSI七层模型2.1 传输层2.2 网络层2.2.1 问:两端处于不同局域网的设备怎么网络通信? 2.3 数据链路层2.4 物理层2.5 图解OSI七层模型 3. 数据传输模式3.1 全双工3.2 半双工3.3 单工 4. TCP 3次握手4.1 抓包 5. TCP 4次挥手5.1 …...

专场回顾 | 重新定义交互,智能硬件的未来设计
自2022年起,中国智能硬件行业呈现出蓬勃发展的态势,市场规模不断扩大。一个多月前,“小智AI”在短视频平台的爆火将智能硬件带向了大众视野,也意味着智能硬件已不再仅仅停留在概念和技术层面,而是加速迈向实际落地应用…...
如何把一台电脑作为另外一台电脑的显示器
https://zhuanlan.zhihu.com/p/703889583 1. 两台电脑都要进行:点开投影到此电脑,点击可选功能,在可选功能窗口,搜索无线显示器;在结果列表中选中无线显示器,并安装 2. 在笔记本电脑(要用来做…...

WPS 免登录解锁编辑
遇到 WPS 需要登录才能启用编辑功能? 如何免登录使用编辑功能? 方法一 解锁方法 1、关闭 WPS; 2、桌面右键→ “新建”→“文本文档”,粘贴以下内容(见最下面);编码保持默认(ANSI …...
【C/C++】线程安全初始化:std::call_once详解
std::call_once 使用详解 std::call_once 是 C11 标准库中提供的一个线程安全的一次性调用机制,位于 <mutex> 头文件中。它确保某个可调用对象只被执行一次,即使多个线程同时尝试调用它。 基本用法 #include <mutex> #include <thread…...

技术分享 | Oracle SQL优化案例一则
本文为墨天轮数据库管理服务团队第70期技术分享,内容原创,作者为技术顾问马奕璇,如需转载请联系小墨(VX:modb666)并注明来源。 一、问题概述 开发人员反映有条跑批语句在测试环境执行了很久都没结束&…...
什么是RFID电子标签
RFID 电子标签是用于物品标识、具有信息存储机制、能接收读写器的电磁场调制信号并返回响应信号的数据载体,通常被称为电子标签,也可称作射频卡、射频标签、射频卷标等,是与读写器一起构成 RFID 系统的硬件主体。 RFID 系统基本组成包括RFID电子标签、读写器、射频天线、应用…...

华为手机用的时间长了,提示手机电池性能下降,需要去换电池吗?平时要怎么用能让电池寿命长久一些?
华为手机提示电池性能下降时,是否需要更换电池以及如何延长电池寿命,取决于电池老化程度和使用习惯。以下是具体分析和建议: 一、是否需要更换电池? 电池健康度低于80% 如果手机提示“电池性能下降”,通常意味着电池…...

BERT***
1.预训练(Pre-training) 是深度学习中的一种训练策略,指在大规模无标注数据上预先训练模型,使其学习通用的特征表示,再通过微调(Fine-tuning) 适配到具体任务 2.sentence-lev…...

超级对话2:大跨界且大综合的学问融智学应用场景述评(不同第三方的回应)之二
摘要:《人机协同文明升维行动框架》提出以HIAICI/W公式推动认知革命,构建三大落地场景:1)低成本认知增强神经接口实现300%学习效率提升;2)全球学科活动化闪电战快速转化知识体系;3)人…...
在Linux环境里面,Python调用C#写的动态库,如何实现?
在Linux环境中,Python可以通过pythonnet(CLR的Python绑定)或subprocess调用C#动态库。以下是两种方法的示例: 方法1:使用pythonnet(推荐) 前提条件 安装Mono或.NET Core运行时安装pythonnet包…...
【Linux 基础知识系列】第三篇-Linux 基本命令
在数字化浪潮席卷全球的当下,操作系统作为计算机系统的核心组件,扮演着至关重要的角色。而 Linux,凭借其卓越的性能、高度的稳定性和出色的可定制性,在服务器、嵌入式系统、超级计算机以及个人计算机等领域大放异彩,成…...
OpenCV CUDA模块直方图计算------生成一组均匀分布的灰度级函数evenLevels()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 该函数主要用于为 直方图均衡化、CLAHE 等图像处理算法 生成一组等间距的灰度区间边界值(bins 或 levels),这…...

深度学习常见实验问题与实验技巧
深度学习常见实验问题与实验技巧 有一定的先后顺序的 还在迷茫深度学习中的改进实验应该从哪里开始改起的同学,一定要进来看看了!用自身经验给你推荐实验顺序! YOLOV8-硬塞注意力机制?这样做没创新!想知道注意力怎么…...