目录
一. MySQL 建表
二. MyBatis
三. dao层
四. service层
五. controller层
六 component层
七. configuration层
八. utils 包
九. common 包
十. Exception
十一. interceptor包
十二. 代码有使用什么SpringBoot注解
首先使用 MySQL 进行表的创建, 有以下几个表:
t_user: 用户信息表, 用来存储用户的姓名, 密码,昵称,电话号码,邮件,个人简介
t_board: 板块表, 存储每个板块的名字, 板块中的文章数
t_article: 帖子表, 存储帖子标题, 文章, 点击数, 评论数和喜欢数. 使用 userId 和 boardId 将帖子和发帖用户,板块关联起来
t_article_reply: 评论表, 存储评论, 使用 articleId, replyId 将评论和帖子,评论用户联系起来
t_message: 站内信表, 存储消息, 使用 postUserId 和 receivedUserId 将信息和发生用户,接受用户联系起来
使用 MyBatis 和数据库建立联系
1. 创建 model 到, 按照数据库的表来创建类
2. 创建 mybatisConfig.xml, 将对应的依赖写入
3. 创建 mapper 目录, 按照固定格式, 写入 SQL 语句
4. 创建 dao 包, 里面装接口类, 类中写调用 SQL 的方法
mapper
常用标签: insert select update
标签属性:
id: dao 中对应的方法名
parameterType: 导入参数类型
resultMap: 返回参数类型
useGenerateKeys: 是否自己创建主键
keyProperty: 主键是谁(和useGenerateKeys一起用)
常用标签: trim if
标签属性:
prefix: 前面添加字符串
suffix: 后面添加字符串
prefixOverrides: 省略最前面对应字符
suffixOverrides: 省略最后面对应字符
test: test 的条件为 true 就将 if 中的字符串添加进 SQL 语句
常用标签: set
会自动删除最后一个,
public interface UserMapper {
int insert(User row);
int insertSelective(User row);
User selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(User row);
int updateByPrimaryKey(User row);
User selectByUserName (@Param("username") String username);
}
public interface BoardMapper {
int insert(Board row);
int insertSelective(Board row);
Board selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(Board row);
int updateByPrimaryKey(Board row);
List selectByNum (@Param("num") Integer num);
}
public interface ArticleMapper {
int insert(Article row);
int insertSelective(Article row);
Article selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(Article row);
int updateByPrimaryKeyWithBLOBs(Article row);
int updateByPrimaryKey(Article row);
/**
* 查询所有帖子列表
* @return
*/
List selectAll ();
List selectAllByBoardId(Long boardId);
Article selectDetailById(Long articleId);
List selectByUserId(Long userId);
}
public interface ArticleReplyMapper {
int insert(ArticleReply row);
int insertSelective(ArticleReply row);
ArticleReply selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(ArticleReply row);
int updateByPrimaryKey(ArticleReply row);
List selectByArticleId(Long articleId);
}
public interface MessageMapper {
int insert(Message row);
int insertSelective(Message row);
Message selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(Message row);
int updateByPrimaryKey(Message row);
Integer selectUnreadCount(Long receiveUserId);
List selectByReceiveUserId(Long id);
}
service 层主要通过这些方法来和 dao层进行一个交互, 增删查改数据库中的数据
使用到的类:
用户 | 板块 | 文章 | 评论 | 消息 |
IUserService | IBoardService | IArticleService | ArticleReplyService | IMessageService |
UserService
创建用户 | 按用户名查询 | 登录 | 按 ID 查询 |
createNormalUser |
selectByUserName |
login |
selectById |
会进行 user 的非空校验, 然后查询该用户是否已经存在, 不存在则创建用户 | 对用户名非空校验, 然后返回查询结果 | 对用户名和密码进行非空校验, 然后查询该用户是否存在, 存在则校验密码 |
对 id 进行非空校验, 根据 id 查询用户 |
增加用户发帖数 | 减少用户发帖数 | 修改个人信息 | 修改密码 |
addOneArticleCountById | subOneArticleCountById |
modifyInfo |
modifyPassword |
对 ID 进行非空校验, 根据 ID 查询用户, 然后将发帖数 +1, 更新用户 | 对 ID 进行非空校验, 根据 ID 查询用户, 将发帖数 -1, 更新用户 | user和ID非空校验, 定义一个标志位, 如果有修改信息标志位为 true, 否则为 false, 修改不通过 | 对id, 老密码和新密码进行非空校验, 校验老密码, 比较新老密码是否相同, 进行修改 |
BoardService
拿到前num个板块 | 增加一个板块帖子数 | 通过id拿到板块 | 减少一个板块帖子数 |
selectByNum |
addOneArticleCountById |
selectById |
subOneArticleCountById |
对 num 非空校验, 拿出前 num 个板块 | 非空校验 id, 将 对应板块帖子数 +1, 修改数据库 | 非空校验 id, 拿到对应板块 | 非空校验 id, 减少对应板块一个帖子数, 修改数据库 |
ArticleService
发布帖子 | 拿到全部帖子 | 通过板块 id 拿到所有帖子 |
create | selectAll | selectAllByBoardId |
非空校验article, userId, boardId, title, content. 将 article 写入数据库, 更新用户发帖数, 板块帖子数 |
拿到全部帖子 | 非空校验 boardId, 校验板块是否存在, 拿到所有对应的帖子 |
访问帖子 | 通过userId拿到所有帖子 | 拿到帖子 |
selectDetailById |
selectByUserId |
selectById |
非空校验id, 拿到帖子, 将帖子访问数 +1, 更新数据库, 返回帖子 | 非空校验userId, 拿到该用户写的帖子, 返回所有帖子 | 非空校验id, 通过帖子id拿到帖子, 返回帖子 |
修改帖子 | 点赞帖子 | 删除帖子 | 增加一个评论数 |
modify |
thumbsUpById |
deleteById |
addOneReplyCountById |
非空校验id, title, content. 修改帖子 |
非空校验id, 将对应帖子点赞 | 非空校验id, 删除对应帖子 | 非空校验id, 将对应帖子评论数+1 |
ArticleReplyService
创建评论 | 读取某文章的全部评论 |
create |
selectByArticleId |
非空校验评论, id.将 articleReply 写入数据库, 将帖子的回复数+1 | 对 articleId 非空校验, 拿出所有 articleId 的评论 |
MessageService
构建一个消息 | 通过id拿到消息 | 没有被阅读的消息数 |
create |
selectById |
selectUnreadCount |
非空校验消息本体, 发送用户id, 接收用户id, 查看接收用户是否存在, 将消息写入数据库 | 非空校验id, 将对应消息拿出 | 非空校验用户id, 返回没有被阅读数 |
通过接收用户id拿到消息 | 更改消息状态 | 回复消息 |
selectByReceiveUserId |
updateStateById |
reply |
非空校验用户id, 返回所有接受的消息 | 非空校验id、state, state 分为 0未读、1已读、2已回复 | 非空校验接受到的消息id、要发送消息, 如果接受到的消息为空, 则抛出异常. 不为空就发送消息, 将接收消息的状态改为 2已回复. |
Controller层通过 http 来和客户端接受和发送请求, 然后调用 service层 的方法来对数据库进行增删查改
UserController
用户注册 | 用户登录 | 获取用户信息 |
register | login | getUserInfo |
客户端传来username, nickname, password, passwordRepeat. 校验密码和重复密码是否相同, 调用service层 createNormalUser | 客户端传来用户名和密码, 调用service层 login方法, 如果登录成功将 user 作为 session 设到 redis 中 | 客户端传id, 非空校验id, 如果 id 为 null, 则从 session 中取出 user, 如果不为 null, 则返回 userId 的 user |
退出登录 | 修改个人信息 | 修改密码 |
logout | modifyInfo | modifyPassword |
如果 session 不为 null, 就销毁 session | 将客户端传来的不为 null 的数据对数据库进行修改, 调用 service 层的 modifyInfo 方法 | 客户端传来原密码、新密码、确认密码, 校验新密码和确认密码是否相同, 调用 service层 modifyPassword 方法进行修改 |
BoardController
获取首页版块列表 |
通过 id 获取版块信息 |
topList |
getById |
调用 service 层 selectByNum, 将 前 num 个板块获取 | 调用 service层的 selectById, 获取 id 对应的板块信息 |
ArticleController
发布新帖 | 获取帖子列表 | 根据帖子Id获取详情 |
create |
getAllByBoardId |
getDetails |
客户端传来 boardId, title, content. 封装文章对象, 调用 service 层的 create 方法, 添加文章 | 客户端传来 boardId, 如果为 null 则返回所有文章, 不为 null 则返回对应对应板块的索引文章 | 客户端传来 id, 判断 id 是否为登录用户 id, 如果是将文章标记, 返回 id 对应文章 |
修改帖子 |
点赞 |
删除帖子 |
获取用户的帖子列表 |
modify |
thumbsUp |
deleteById |
getAllByUserId |
客户端传来 id, title, content. 根据 id 获取帖子, 校验是否为当前用户的帖子, 如果是调用 service 层 modify, 修改帖子 | 客户端传来 id, 判定用户是否被禁言, 没有则调用 service层 thumbUpById, 点赞数+1 | 客户端传来 id, 判断是否被禁言, 对应帖子是否被删除, 当前登录用户是否为作者, 都通过调用 service层 deleteById 删除帖子 | 客户端传来 userId, 传来 null则从 session 中获取 userId, 调用 service层 selectByUserId 获取所有该用户帖子 |
ArticleReplyController
评论 |
获取回复列表 |
ArticleReplyController |
getRepliesByArticleId |
客户端传来 id, content. 判定要回复帖子是否存在, 从 session 中拿到user, 构建评论, 调用 service层 create 方法, 返回结果 | 客户端传来 articleId, 判断该文章是否存在, 调用 service层 selectByArticleId 获取该文章所有评论 |
MessageController
发送站内信 | 查询用户所有站内信 |
MessageController |
getAll |
客户端传来 receivedUserId, content. 从 session 拿到 postUser, 校验用户是否被禁言, 是不是给自己发站内信, receiver 是否存在, 封装 message, 调用 Messageservice的create方法将 message 写入数据库, 返回结果 | 从 session 获取 user, 调用 meesageService 的 selectReceiverUserId 获取 该用户所有站内信, 返回站内信 |
更新为已读 | 回复站内信 |
markRead |
reply |
客户端传来 id, 根据 id 获取站内信, 校验站内信是否存在, 站内信是不是自己的, 调用 messageService层的 updateStateById, 将站内信 state 改为 1, 返回结果 | 客户端传来 repliedId, content. 校验当前登录用户状态, 要回复的站内信状态, 是否给自己回复, 构造 message 对象, 调用 messageService层 reply, 返回结果 |
@Component
public class BinaryTool implements Serializable {
public static byte[] toBinary(User user){
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream)){
outputStream.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
}
return byteArrayOutputStream.toByteArray();
}
public static User fromBinary(byte[] data){
User user = null;
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data)){
try (ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)){
user = (User) objectInputStream.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
return user;
}
}
使用 toBinary 对传进来的对象进行序列化, 返回序列化后的对象
使用 fromBinary 对传进来的二进制数据反序列化, 返回 Object 对象
使用这两个方法实现序列化和反序列化.
用处:
将 java 对象存储进 redis 中无法直接存储, 需要进行序列化后再存储.
需要从 redis 中拿取对象时, 再进行反序列化.
注: 被序列化的对象也要实现 Serializable 接口
这里面放一些我们会使用到的字符串常量.
1. AppConfig
public class AppConfig {
/**
* 用户Session中的Key值
*/
public static final String USER_SESSION = "USER_SESSION";
}
用作 session 中的 Key 值
2. MyBatisConfig
@Configuration
// 具体的配置
@MapperScan("com.example.forum.dao")
public class MybatisConfig {
}
配置 Mybatis 的扫描路径
这里面是放一些我们要使用到的工具方法, 来完成某个特定的任务
UUIDUtil
public class UUIDUtil {
/**
* 生成一个标准的UUID
*
* @return
*/
public static String UUID_36 () {
return UUID.randomUUID().toString();
}
/**
* 生成一个32位的UUID
* @return
*/
public static String UUID_32 () {
return UUID.randomUUID().toString().replace("-", "");
}
}
第一个方法是生成一个标准的UUID, 36 位大小, 第二个方法是将 UUID 的 "-" 去掉变成 32 位.
MD5Util
public class MD5Util {
/**
* 对字符串进行MD5加密
* @param str 明文
* @return 密文
*/
public static String md5 (String str) {
return DigestUtils.md5Hex(str);
}
/**
* 对用户密码进行加密
* @param str 密码明文
* @param salt 扰动字符
* @return 密文
*/
public static String md5Salt (String str, String salt) {
return md5(md5(str) + salt);
}
}
使用这个类来对对密码进行加密后再放进数据库, 这样能有效防止数据库泄漏后密码被知晓.
第一个方法是使用 DigestUtils 类的静态方法 md5Hex, 来对数据进行 md5 加密, 但这样的加密很容易被破解.
第二个方法时先将密码进行 md5 加密, 然后往密码后加上一个 salt, 再对整个字符串进行 md5 加密
salt 是由 UUIDUtil 的 UUID_32 生成.
这里面放我们要返回给前端的自定义状态码
采用枚举
public enum ResultCode {
SUCCESS (0, "操作成功"),
FAILED (1000, "操作失败"),
FAILED_UNAUTHORIZED (1001, "未授权"),
FAILED_PARAMS_VALIDATE (1002, "参数校验失败"),
FAILED_FORBIDDEN (1003, "禁止访问"),
FAILED_CREATE (1004, "新增失败"),
FAILED_NOT_EXISTS (1005, "资源不存在"),
// 关于用户的错误描述
FAILED_USER_EXISTS (1101, "用户已存在"),
FAILED_USER_NOT_EXISTS (1102, "用户不存在"),
FAILED_LOGIN (1103, "用户名或密码错误"),
FAILED_USER_BANNED (1104, "您已被禁言, 请联系管理员, 并重新登录."),
FAILED_USER_ARTICLE_COUNT (1105, "更新帖子数量失败"),
FAILED_TWO_PWD_NOT_SAME (1106, "两次输入的密码不一致"),
// 关于版块的错误描述
FAILED_BOARD_ARTICLE_COUNT (1201, "更新帖子数量失败"),
FAILED_BOARD_BANNED (1202, "版块状况异常"),
FAILED_BOARD_NOT_EXISTS (1203, "版块不存在"),
FAILED_ARTICLE_NOT_EXISTS (1301, "帖子不存在"),
FAILED_ARTICLE_BANNED (1302, "帖子状况异常"),
FAILED_MESSAGE_NOT_EXISTS (1401, "站内信不存在"),
ERROR_SERVICES (2000, "服务器内部错误"),
ERROR_IS_NULL (2001, "IS NULL.");
// 状态码
int code;
// 描述信息
String message;
ResultCode(int code, String message){
this.code = code;
this.message = message;
}
@Override
public String toString() {
return "code=" + code +
", message='" + message + '.';
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
然后我们调用 AppResult 类来将数据返回给前端, 这里采用 Json 的格式返回
public class AppResult {
// 状态码
@JsonInclude(JsonInclude.Include.ALWAYS) // 不论任何情况都参与JSON序列化
private int code;
// 描述信息
@JsonInclude(JsonInclude.Include.ALWAYS)
private String message;
// 具体的数据
@JsonInclude(JsonInclude.Include.ALWAYS)
private T data;
/**
* 构造方法
* @param code
* @param message
*/
public AppResult(int code, String message) {
this(code, message, null);
}
public AppResult(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
/**
* 成功
* @return
*/
public static AppResult success () {
return new AppResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage());
}
public static AppResult success (String message) {
return new AppResult(ResultCode.SUCCESS.getCode(), message);
}
public static AppResult success (T data) {
return new AppResult<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
}
public static AppResult success (String message, T data) {
return new AppResult<>(ResultCode.SUCCESS.getCode(), message, data);
}
/**
* 失败
* @return
*/
public static AppResult failed () {
return new AppResult(ResultCode.FAILED.getCode(), ResultCode.FAILED.getMessage());
}
public static AppResult failed (String message) {
return new AppResult(ResultCode.FAILED.getCode(), message);
}
public static AppResult failed (ResultCode resultCode) {
return new AppResult(resultCode.getCode(), resultCode.getMessage());
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
自定义异常
public class ApplicationException extends RuntimeException{
// 在异常中持有一个错误信息对象
protected AppResult errorResult;
public ApplicationException(AppResult errorResult) {
super(errorResult.getMessage());
this.errorResult = errorResult;
}
public ApplicationException(String message) {
super(message);
}
public ApplicationException(String message, Throwable cause) {
super(message, cause);
}
public ApplicationException(Throwable cause) {
super(cause);
}
public AppResult getErrorResult() {
return errorResult;
}
}
这里面用来实现拦截器
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Value("${bit-forum.login.url}")
private String defaultURL;
/**
* 前置处理(对请求的预处理)
* @return true : 继续流程
false : 流程中断
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取session 对象
HttpSession session = request.getSession(false);
// 判断session是否有效
if (session != null && session.getAttribute(AppConfig.USER_SESSION) != null) {
// 用户为已登录状,校验通过
return true;
}
// 校验URL是否正确
if (!defaultURL.startsWith("/")) {
defaultURL = "/" + defaultURL;
}
// 校验不通过, 跳转到登录页面
response.sendRedirect(defaultURL);
// 中断流程
return false;
}
}
@Configuration
public class AppInterceptorConfigure implements WebMvcConfigurer {
// 注入自定义的登录拦截器
@Resource
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry 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");
}
}
bean:
@Service @Controller @Component @Configuration @Resource
日志:
@Slf4j
访问路径:
@RestMapping @PostMapping @GetMapping
返回 Json:
@ResponseBody @RestController
注: @RestController = @Controller + @ResponseBody