当前位置: 首页 > article >正文

论坛系统(4)

用户详情

获取用户信息

实现逻辑

⽤⼾提交请求,服务器根据是否传⼊Id参数决定返回哪个⽤⼾的详情

1. 不传⽤⼾Id,返回当前登录⽤⼾的详情(从session获取)

2. 传⼊⽤⼾Id,返回指定Id的⽤⼾详情(根据用户id去查)

俩种方式获得用户信息

参数要求

参数名描述类型默认值条件
id⽤⼾Idlong可以为空

接口规范

// 请求
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版块IdLong可为空

接口规范

// 请求
// 返回指定版块下的帖⼦列表
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保密
email邮箱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接收⽤⼾IdLong必须
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站内信Idlong必须

接口规范

// 请求
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)站内信IdLong必须
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)

用户详情 获取用户信息 实现逻辑 ⽤⼾提交请求&#xff0c;服务器根据是否传⼊Id参数决定返回哪个⽤⼾的详情 1. 不传⽤⼾Id&#xff0c;返回当前登录⽤⼾的详情(从session获取) 2. 传⼊⽤⼾Id&#xff0c;返回指定Id的⽤⼾详情(根据用户id去查) 俩种方式获得用户信息 参…...

本地Markdown开源知识库选型指南

本地Markdown开源知识库选型指南 以下是几款优秀的本地Markdown开源知识库解决方案&#xff0c;适合不同需求场景&#xff1a; 1. Obsidian (非完全开源但免费) 特点&#xff1a;基于Markdown的本地优先知识管理&#xff0c;丰富的插件生态优势&#xff1a;双向链接、图形视…...

【.net core】SkiaSharp 如何在Linux上实现

1. 安装依赖库 首先需要安装 SkiaSharp 运行时依赖&#xff1a; # 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…...

后端项目中静态文案国际化语言包构建选型

这是一个很关键的问题。在做国际化&#xff08;i18n&#xff09;时&#xff0c;不同语言包格式如 .resx、.properties 和 .json 都可用&#xff0c;但各自有适用场景、特性与限制&#xff0c;你在选择时可以根据你的开发语言、生态和维护成本权衡。 ✅ 一张对比表&#xff1a;.…...

前端面经 React常见的生命周期

初始化阶段 constructor state的初始化&#xff0c;防抖节流的绑定getDerivedStateFromProps 静态函数 当作纯函数使用 传入props和state&#xff0c;合并成一个新的statecomponentWillMount 组件如果有getDrivedStatefromprops不会执行 针对一些接口的预请求时使用rendercomp…...

力扣面试150题--二叉树的层平均值

Day 54 题目描述 思路 初次做法&#xff08;笨&#xff09;&#xff1a;使用两个队列&#xff0c;一个队列存放树的节点&#xff0c;一个队列存放对应节点的高度&#xff0c;使用x存放上一个节点&#xff0c;highb存放上一个节点的高度&#xff0c;sum存放当前层的节点值之和…...

【Doris入门】Doris初识:分布式分析型数据库的核心价值与架构解析

目录 1 Doris简介与核心价值 2 Doris架构深度解析 2.1 Frontend&#xff08;FE&#xff09;架构 2.2 Backend&#xff08;BE&#xff09;架构 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. 单例设计模式是一个类&#xff0c;它只允许创建自己的单个实例。 构造函数防止他在单例类以外的地方被调用。 使用情景&#xff1a;need a sing…...

数据结构与算法学习笔记(Acwing 提高课)----动态规划·区间DP

数据结构与算法学习笔记----动态规划区间DP author: 明月清了个风 first publish time: 2025.5.26 ps⭐️区间DP的特征在于子结构一般是一个子区间上的问题&#xff0c;涉及到的问题也非常多&#xff0c;如环形区间&#xff0c;记录方案数&#xff0c;高精度&#xff0c;二维…...

【合集】Linux——31个普通信号

Linux普通信号总表&#xff08;1-31&#xff09;​​ ​编号​​信号名​​触发原因​​默认动作​1SIGHUP终端连接断开&#xff08;如SSH会话终止&#xff09;或守护进程重载配置&#xff08;如nginx -s reload&#xff09;终止进程2SIGINT用户输入CtrlC中断前台进程终止进程…...

从0到1搭建AI绘画模型:Stable Diffusion微调全流程避坑指南

从0到1搭建AI绘画模型&#xff1a;Stable Diffusion微调全流程避坑指南 系统化学习人工智能网站&#xff08;收藏&#xff09;&#xff1a;https://www.captainbed.cn/flu 文章目录 从0到1搭建AI绘画模型&#xff1a;Stable Diffusion微调全流程避坑指南摘要引言一、数据集构…...

ASP.NET Core 中JWT的基本使用

文章目录 前言一、JWT与RBAC二、JWT 的作用三、RBAC 的核心思想四、使用1、配置文件 (appsettings.json)2、JWT配置模型 (Entity/JwtSettings.cs)3、服务扩展类&#xff0c;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 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 引言Compaction AlgorithmsCompact Execution Flow Based On VeloxLocalMergeSource的…...

vue3 导出excel

需求&#xff1a;导出自带格式的excel表格 1.自定义二维数组格式 导出 全部代码&#xff1a; <el-button click"exportExcel">导出</el-button> const exportExcel () > {const data [[商品, 单价, 数量, 总价],[A, 100, 1.55, { t: n, f: B2*C2…...

带你手写React中的useReducer函数。(底层实现)

文章目录 前言一、为什么需要 Reducer&#xff1f;二、Reducer 的核心概念1. Reducer 函数2. useReducer 钩子 三&#xff0c;手写react中的useReducer 总结 前言 在 React 开发中&#xff0c;useReducer 是管理复杂状态逻辑的利器。它类似于 Redux 的简化版&#xff0c;允许我…...

day024-网络基础-TCP与UDP、DNS

文章目录 1. 李导推荐书籍2. OSI七层模型2.1 传输层2.2 网络层2.2.1 问&#xff1a;两端处于不同局域网的设备怎么网络通信&#xff1f; 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年起&#xff0c;中国智能硬件行业呈现出蓬勃发展的态势&#xff0c;市场规模不断扩大。一个多月前&#xff0c;“小智AI”在短视频平台的爆火将智能硬件带向了大众视野&#xff0c;也意味着智能硬件已不再仅仅停留在概念和技术层面&#xff0c;而是加速迈向实际落地应用…...

如何把一台电脑作为另外一台电脑的显示器

https://zhuanlan.zhihu.com/p/703889583 1. 两台电脑都要进行&#xff1a;点开投影到此电脑&#xff0c;点击可选功能&#xff0c;在可选功能窗口&#xff0c;搜索无线显示器&#xff1b;在结果列表中选中无线显示器&#xff0c;并安装 2. 在笔记本电脑&#xff08;要用来做…...

WPS 免登录解锁编辑

遇到 WPS 需要登录才能启用编辑功能&#xff1f; 如何免登录使用编辑功能&#xff1f; 方法一 解锁方法 1、关闭 WPS&#xff1b; 2、桌面右键→ “新建”→“文本文档”&#xff0c;粘贴以下内容&#xff08;见最下面&#xff09;&#xff1b;编码保持默认&#xff08;ANSI …...

【C/C++】线程安全初始化:std::call_once详解

std::call_once 使用详解 std::call_once 是 C11 标准库中提供的一个线程安全的一次性调用机制&#xff0c;位于 <mutex> 头文件中。它确保某个可调用对象只被执行一次&#xff0c;即使多个线程同时尝试调用它。 基本用法 #include <mutex> #include <thread…...

技术分享 | Oracle SQL优化案例一则

本文为墨天轮数据库管理服务团队第70期技术分享&#xff0c;内容原创&#xff0c;作者为技术顾问马奕璇&#xff0c;如需转载请联系小墨&#xff08;VX&#xff1a;modb666&#xff09;并注明来源。 一、问题概述 开发人员反映有条跑批语句在测试环境执行了很久都没结束&…...

​什么是RFID电子标签​

RFID 电子标签是用于物品标识、具有信息存储机制、能接收读写器的电磁场调制信号并返回响应信号的数据载体,通常被称为电子标签,也可称作射频卡、射频标签、射频卷标等,是与读写器一起构成 RFID 系统的硬件主体。 RFID 系统基本组成包括RFID电子标签、读写器、射频天线、应用…...

华为手机用的时间长了,提示手机电池性能下降,需要去换电池吗?平时要怎么用能让电池寿命长久一些?

华为手机提示电池性能下降时&#xff0c;是否需要更换电池以及如何延长电池寿命&#xff0c;取决于电池老化程度和使用习惯。以下是具体分析和建议&#xff1a; 一、是否需要更换电池&#xff1f; 电池健康度低于80% 如果手机提示“电池性能下降”&#xff0c;通常意味着电池…...

BERT***

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

超级对话2:大跨界且大综合的学问融智学应用场景述评(不同第三方的回应)之二

摘要&#xff1a;《人机协同文明升维行动框架》提出以HIAICI/W公式推动认知革命&#xff0c;构建三大落地场景&#xff1a;1&#xff09;低成本认知增强神经接口实现300%学习效率提升&#xff1b;2&#xff09;全球学科活动化闪电战快速转化知识体系&#xff1b;3&#xff09;人…...

在Linux环境里面,Python调用C#写的动态库,如何实现?

在Linux环境中&#xff0c;Python可以通过pythonnet&#xff08;CLR的Python绑定&#xff09;或subprocess调用C#动态库。以下是两种方法的示例&#xff1a; 方法1&#xff1a;使用pythonnet&#xff08;推荐&#xff09; 前提条件 安装Mono或.NET Core运行时安装pythonnet包…...

【Linux 基础知识系列】第三篇-Linux 基本命令

在数字化浪潮席卷全球的当下&#xff0c;操作系统作为计算机系统的核心组件&#xff0c;扮演着至关重要的角色。而 Linux&#xff0c;凭借其卓越的性能、高度的稳定性和出色的可定制性&#xff0c;在服务器、嵌入式系统、超级计算机以及个人计算机等领域大放异彩&#xff0c;成…...

OpenCV CUDA模块直方图计算------生成一组均匀分布的灰度级函数evenLevels()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 该函数主要用于为 直方图均衡化、CLAHE 等图像处理算法 生成一组等间距的灰度区间边界值&#xff08;bins 或 levels&#xff09;&#xff0c;这…...

深度学习常见实验问题与实验技巧

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