项目介绍
这是一个校园社区的后端实现。主要功能如下:
主要技术栈:
SpringBoot
MyBatisPlus
MySQL
Swagger
Websocket
前期工作
设计生成数据库表
建立数据库,数据库名scnu,
USE scnu,然后建表:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for billboard_info
-- ----------------------------
DROP TABLE IF EXISTS `billboard_info`;
CREATE TABLE `billboard_info` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '公告',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '公告时间',
`showed` tinyint(1) NULL DEFAULT NULL COMMENT '1:展示中,0:过期',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '全站公告表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for comment_info
-- ----------------------------
DROP TABLE IF EXISTS `comment_info`;
CREATE TABLE `comment_info` (
`id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键',
`content` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '内容',
`user_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '作者ID',
`topic_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'topic_id',
`create_time` datetime(0) NOT NULL COMMENT '发布时间',
`modify_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '评论表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for post_info
-- ----------------------------
DROP TABLE IF EXISTS `post_info`;
CREATE TABLE `post_info` (
`id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键',
`title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '标题',
`content` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT 'markdown内容',
`user_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '作者ID',
`comments` int(11) NOT NULL DEFAULT 0 COMMENT '评论统计',
`collects` int(11) NOT NULL DEFAULT 0 COMMENT '收藏统计',
`view` int(11) NOT NULL DEFAULT 0 COMMENT '浏览统计',
`top` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否置顶,1-是,0-否',
`essence` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否加精,1-是,0-否',
`section_id` int(11) NULL DEFAULT 0 COMMENT '专栏ID',
`create_time` datetime(0) NOT NULL COMMENT '发布时间',
`modify_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
UNIQUE INDEX `title`(`title`) USING BTREE,
INDEX `user_id`(`user_id`) USING BTREE,
INDEX `create_time`(`create_time`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '话题表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for post_tag
-- ----------------------------
DROP TABLE IF EXISTS `post_tag`;
CREATE TABLE `post_tag` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`tag_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '标签ID',
`topic_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '话题ID',
PRIMARY KEY (`id`) USING BTREE,
INDEX `tag_id`(`tag_id`) USING BTREE,
INDEX `topic_id`(`topic_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 52 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '话题-标签 中间表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for promotion_info
-- ----------------------------
DROP TABLE IF EXISTS `promotion_info`;
CREATE TABLE `promotion_info` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '广告标题',
`link` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '广告链接',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '说明',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '广告推广表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for tag_info
-- ----------------------------
DROP TABLE IF EXISTS `tag_info`;
CREATE TABLE `tag_info` (
`id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '标签ID',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '标签',
`topic_count` int(11) NOT NULL DEFAULT 0 COMMENT '关联话题',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `name`(`name`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '标签表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for tip_info
-- ----------------------------
DROP TABLE IF EXISTS `tip_info`;
CREATE TABLE `tip_info` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`content` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '内容',
`author` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '作者',
`type` tinyint(4) NOT NULL COMMENT '1:使用,0:过期',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 24864 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '每日赠言' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_collect
-- ----------------------------
DROP TABLE IF EXISTS `user_collect`;
CREATE TABLE `user_collect` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户id',
`topic_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '话题id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户收藏表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_follow
-- ----------------------------
DROP TABLE IF EXISTS `user_follow`;
CREATE TABLE `user_follow` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`parent_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '被关注人ID',
`follower_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '关注人ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 130 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户关注表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_info
-- ----------------------------
DROP TABLE IF EXISTS `user_info`;
CREATE TABLE `user_info` (
`id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户ID',
`username` varchar(15) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '用户名',
`alias` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户昵称',
`password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '密码',
`avatar` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '头像',
`email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
`mobile` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机',
`score` int(11) NOT NULL DEFAULT 0 COMMENT '积分',
`token` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT 'token',
`bio` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '个人简介',
`active` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否激活,1:是,0:否',
`status` bit(1) NULL DEFAULT b'1' COMMENT '状态,1:使用,0:停用',
`role_id` int(11) NULL DEFAULT NULL COMMENT '用户角色',
`create_time` datetime(0) NOT NULL COMMENT '加入时间',
`modify_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `user_name`(`username`) USING BTREE,
INDEX `user_email`(`email`) USING BTREE,
INDEX `user_create_time`(`create_time`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_like
-- ----------------------------
DROP TABLE IF EXISTS `user_like`;
CREATE TABLE `user_like` (
`id` int(11) UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户id',
`topic_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '点赞的话题id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户点赞表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_message
-- ----------------------------
DROP TABLE IF EXISTS `user_message`;
CREATE TABLE `user_message` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '被留言用户id',
`luser_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '留言用户id',
`content` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '留言内容',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户留言表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_chat
-- ----------------------------
DROP TABLE IF EXISTS `user_chat`;
CREATE TABLE `user_chat` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`send_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '发送用户id',
`to_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '接收用户id',
`content` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '消息内容',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3
不支持排序规则的话使用以下这版:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for billboard_info
-- ----------------------------
DROP TABLE IF EXISTS `billboard_info`;
CREATE TABLE `billboard_info` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`content` varchar(255) CHARACTER SET utf8mb4 NOT NULL COMMENT '公告',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '公告时间',
`showed` tinyint(1) NULL DEFAULT NULL COMMENT '1:展示中,0:过期',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COMMENT = '全站公告表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for comment_info
-- ----------------------------
DROP TABLE IF EXISTS `comment_info`;
CREATE TABLE `comment_info` (
`id` varchar(20) CHARACTER SET utf8 NOT NULL COMMENT '主键',
`content` varchar(1000) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '内容',
`user_id` varchar(20) CHARACTER SET utf8 NOT NULL COMMENT '作者ID',
`topic_id` varchar(20) CHARACTER SET utf8 NOT NULL COMMENT 'topic_id',
`create_time` datetime(0) NOT NULL COMMENT '发布时间',
`modify_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COMMENT = '评论表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for post_info
-- ----------------------------
DROP TABLE IF EXISTS `post_info`;
CREATE TABLE `post_info` (
`id` varchar(20) CHARACTER SET utf8 NOT NULL COMMENT '主键',
`title` varchar(255) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '标题',
`content` longtext CHARACTER SET utf8 NULL COMMENT 'markdown内容',
`user_id` varchar(20) CHARACTER SET utf8 NOT NULL COMMENT '作者ID',
`comments` int(11) NOT NULL DEFAULT 0 COMMENT '评论统计',
`collects` int(11) NOT NULL DEFAULT 0 COMMENT '收藏统计',
`view` int(11) NOT NULL DEFAULT 0 COMMENT '浏览统计',
`top` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否置顶,1-是,0-否',
`essence` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否加精,1-是,0-否',
`section_id` int(11) NULL DEFAULT 0 COMMENT '专栏ID',
`create_time` datetime(0) NOT NULL COMMENT '发布时间',
`modify_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
UNIQUE INDEX `title`(`title`) USING BTREE,
INDEX `user_id`(`user_id`) USING BTREE,
INDEX `create_time`(`create_time`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COMMENT = '话题表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for post_tag
-- ----------------------------
DROP TABLE IF EXISTS `post_tag`;
CREATE TABLE `post_tag` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`tag_id` varchar(20) CHARACTER SET utf8 NOT NULL COMMENT '标签ID',
`topic_id` varchar(20) CHARACTER SET utf8 NOT NULL COMMENT '话题ID',
PRIMARY KEY (`id`) USING BTREE,
INDEX `tag_id`(`tag_id`) USING BTREE,
INDEX `topic_id`(`topic_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 52 CHARACTER SET = utf8 COMMENT = '话题-标签 中间表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for promotion_info
-- ----------------------------
DROP TABLE IF EXISTS `promotion_info`;
CREATE TABLE `promotion_info` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`title` varchar(255) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '广告标题',
`link` varchar(255) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '广告链接',
`description` varchar(255) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '说明',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COMMENT = '广告推广表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for tag_info
-- ----------------------------
DROP TABLE IF EXISTS `tag_info`;
CREATE TABLE `tag_info` (
`id` varchar(20) CHARACTER SET utf8 NOT NULL COMMENT '标签ID',
`name` varchar(255) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '标签',
`topic_count` int(11) NOT NULL DEFAULT 0 COMMENT '关联话题',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `name`(`name`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COMMENT = '标签表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for tip_info
-- ----------------------------
DROP TABLE IF EXISTS `tip_info`;
CREATE TABLE `tip_info` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`content` varchar(1000) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '内容',
`author` varchar(50) CHARACTER SET utf8 NULL DEFAULT '' COMMENT '作者',
`type` tinyint(4) NOT NULL COMMENT '1:使用,0:过期',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 24864 CHARACTER SET = utf8 COMMENT = '每日赠言' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_collect
-- ----------------------------
DROP TABLE IF EXISTS `user_collect`;
CREATE TABLE `user_collect` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` varchar(20) CHARACTER SET utf8mb4 NOT NULL COMMENT '用户id',
`topic_id` varchar(20) CHARACTER SET utf8mb4 NOT NULL COMMENT '话题id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '用户收藏表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_follow
-- ----------------------------
DROP TABLE IF EXISTS `user_follow`;
CREATE TABLE `user_follow` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`parent_id` varchar(20) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '被关注人ID',
`follower_id` varchar(20) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '关注人ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 130 CHARACTER SET = utf8mb4 COMMENT = '用户关注表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_info
-- ----------------------------
DROP TABLE IF EXISTS `user_info`;
CREATE TABLE `user_info` (
`id` varchar(20) CHARACTER SET utf8 NOT NULL COMMENT '用户ID',
`username` varchar(15) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '用户名',
`alias` varchar(255) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '用户昵称',
`password` varchar(100) CHARACTER SET utf8 NULL DEFAULT '' COMMENT '密码',
`avatar` varchar(1000) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '头像',
`email` varchar(255) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '邮箱',
`mobile` varchar(255) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '手机',
`score` int(11) NOT NULL DEFAULT 0 COMMENT '积分',
`token` varchar(255) CHARACTER SET utf8 NULL DEFAULT '' COMMENT 'token',
`bio` varchar(255) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '个人简介',
`active` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否激活,1:是,0:否',
`status` bit(1) NULL DEFAULT b'1' COMMENT '状态,1:使用,0:停用',
`role_id` int(11) NULL DEFAULT NULL COMMENT '用户角色',
`create_time` datetime(0) NOT NULL COMMENT '加入时间',
`modify_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `user_name`(`username`) USING BTREE,
INDEX `user_email`(`email`) USING BTREE,
INDEX `user_create_time`(`create_time`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COMMENT = '用户表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_like
-- ----------------------------
DROP TABLE IF EXISTS `user_like`;
CREATE TABLE `user_like` (
`id` int(11) UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` varchar(20) CHARACTER SET utf8mb4 NOT NULL COMMENT '用户id',
`topic_id` varchar(20) CHARACTER SET utf8mb4 NOT NULL COMMENT '点赞的话题id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COMMENT = '用户点赞表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_message
-- ----------------------------
DROP TABLE IF EXISTS `user_message`;
CREATE TABLE `user_message` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` varchar(20) CHARACTER SET utf8mb4 NOT NULL COMMENT '被留言用户id',
`luser_id` varchar(20) CHARACTER SET utf8mb4 NOT NULL COMMENT '留言用户id',
`content` varchar(500) CHARACTER SET utf8mb4 NOT NULL COMMENT '留言内容',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COMMENT = '用户留言表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for user_chat
-- ----------------------------
DROP TABLE IF EXISTS `user_chat`;
CREATE TABLE `user_chat` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`send_id` varchar(20) CHARACTER SET utf8mb4 NOT NULL COMMENT '发送用户id',
`to_id` varchar(20) CHARACTER SET utf8mb4 NOT NULL COMMENT '接收用户id',
`content` varchar(500) CHARACTER SET utf8mb4 NOT NULL COMMENT '消息内容',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3
MyBatisPlus代码生成器
先Spring Initializr建一个工程,在pom中导入要用到的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.scnu</groupId>
<artifactId>community</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>community</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<mybatis-plus.version>3.4.1</mybatis-plus.version>
<velocity.version>2.0</velocity.version>
<swagger.version>2.9.2</swagger.version>
<swagger-bootstrap-ui.version>1.9.2</swagger-bootstrap-ui.version>
<commons-lang3.version>3.9</commons-lang3.version>
<commons-fileupload.version>1.3.1</commons-fileupload.version>
<commons-io.version>2.6</commons-io.version>
<fastjson.version>1.2.75</fastjson.version>
<hutool.version>5.5.7</hutool.version>
<jwt.version>0.9.1</jwt.version>
<emoji-java.version>5.1.1</emoji-java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--websocket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!--分页-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.13</version>
</dependency>
<!--jjwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<!--jwt类有用到这个包-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.1</version>
</dependency>
<!--emoji-java-->
<dependency>
<groupId>com.vdurmont</groupId>
<artifactId>emoji-java</artifactId>
<version>${emoji-java.version}</version>
</dependency>
<!-- lettuce pool 缓存连接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--HuTool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--mybatis-plus 代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- Mybatis Plus 代码生成器模板引擎, -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>${velocity.version}</version>
</dependency>
<!--swagger,生成文档-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<!--swagger ui,需要注释掉,不然无法使用下面的swagger-bootstrap-ui扩展-->
<!-- <dependency>-->
<!-- <groupId>io.springfox</groupId>-->
<!-- <artifactId>springfox-swagger-ui</artifactId>-->
<!-- <version>${swagger.version}</version>-->
<!-- </dependency>-->
<!--swagger-bootstrap-ui扩展-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>${swagger-bootstrap-ui.version}</version>
</dependency>
<!--commons-lang3-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons-fileupload.version}</version>
</dependency>
<!--commons-io-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!--yaml-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Maven下载刷新完毕后,在主目录下建个config.CodeGenerator类,写MyBatisPlus的代码生成器:
package com.scnu.community.config;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.FileOutConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.util.ArrayList;
import java.util.List;
/**
* 代码生成器
* 这里生成的代码没有主键
*/
public class CodeGenerator {
/**
* <p>
* 读取控制台内容
* </p>
*/
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("Rosemary");
gc.setOpen(false);
// gc.setSwagger2(true); 实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/scnu?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("RoseMary");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
//设置模块名
pc.setModuleName("community");
//设置父路径
pc.setParent("com.scnu");
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
// String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
// focList.add(new FileOutConfig(templatePath) {
// @Override
// public String outputFile(TableInfo tableInfo) {
// // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
// return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
// + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
// }
// });
/*
cfg.setFileCreate(new IFileCreate() {
@Override
public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
// 判断自定义文件夹是否需要创建
checkDir("调用默认方法创建的目录,自定义目录用");
if (fileType == FileType.MAPPER) {
// 已经生成 mapper 文件判断存在,不想重新生成返回 false
return !new File(filePath).exists();
}
// 允许生成模板文件
return true;
}
});
*/
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
// TemplateConfig templateConfig = new TemplateConfig();
// 配置自定义输出模板
//指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
// templateConfig.setEntity("templates/entity2.java");
// templateConfig.setService();
// templateConfig.setController();
// templateConfig.setXml(null);
// mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
// 公共父类
// strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
// 写于父类中的公共字段
strategy.setSuperEntityColumns("id");
// strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
// mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
注意调整数据库的url、用户名、密码,以及输出的路径等。
生成成功后,项目有了基本的结构,包括实体层、mapper、服务层和控制器:
后面需要自己手动实现的主要是控制器和服务层的实现。
项目配置
resource下的application的配置,这里使用的是yaml格式:
server:
port: 9030
web:
domain: http://localhost
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: RoseMary
url: jdbc:mysql://localhost:3306/scnu?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=GMT%2B8
type: com.zaxxer.hikari.HikariDataSource
logging:
level:
root: info
com.douyuehan.doubao: debug
注意调整数据库的配置。
调整Application主程序,注意加入mapper扫描:
package com.scnu.community;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
/**
* @author Rosemary
* @since 2022-09-18
*/
@MapperScan("com.scnu.community.mapper")
@SpringBootApplication
public class CommunityApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(CommunityApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(CommunityApplication.class, args);
}
}
Swagger配置
使用swagger2做API文档,写个配置文件config.Swagger2Config:
package com.scnu.community.config;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @author Rosemary
* @since 2022-09-18
* 文档分类管理
*/
@Configuration
@EnableSwagger2
public class Swagger2Config {
/**
* 后端接口配置
* 所有/.*的接口都展示在backApi下
*/
@Bean
public Docket adminApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("backApi")
.apiInfo(adminInfo())
.select()
.paths(Predicates.and(PathSelectors.regex("/.*")))
//不显示错误的接口地址
.paths(Predicates.not(PathSelectors.regex("/error.*")))//错误路径不监控
.build();
}
/**
* 定义后端adminInfo
*/
private ApiInfo adminInfo(){
return new ApiInfoBuilder()
.title("华师社区系统API文档")
.description("本文档描述了华师社区系统的各个模块的接口的调用方式")
.version("1.0")
.contact(new Contact("Rosemary", "http://scnu.com", "back@scnu.com"))
.build();
};
/**
* 前端接口配置
*/
@Bean
public Docket webApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")
.apiInfo(webApiInfo())
.select()
.paths(Predicates.and(PathSelectors.regex("/.*")))
.build();
}
/**
* 定义前端webApi
*/
private ApiInfo webApiInfo(){
return new ApiInfoBuilder()
.title("华师社区系统网站API文档")
.description("本文档描述了华师社区系统各个模块的接口的调用方式")
.version("1.0")
.contact(new Contact("Rosemary", "http://scnu.web.com", "web@scnu.com"))
.build();
}
}
配置完成后就可以进入http://localhost:9030/doc.html查看文档了。
此时还没写控制器,所以文档里什么都没有。
MyBatisPlus配置
common.mybatisplus下建立一个类:
package com.scnu.community.common.mybatisplus;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.scnu.community.mapper")
public class MybatisPlusConfig {
/**
* 新的分页插件,一缓和二缓遵循mybatis的规则,
* 需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.setUseDeprecatedExecutor(false);
}
}
业务实现
状态码与异常
建立result包和exception包,定义一些异常和返回给前端的错误码信息,包括:
result:
- ResponseEnum,定义错误码及其信息
- Res,返回给前端
exception:
- BusinessException,自定义异常类
- UnifiedExceptionHandler,统一异常处理
- Assert,断言类
ResponseEnum:
package com.scnu.community.result;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
/**
* @author Rosemary
* @since 2022-09-18
*/
@Getter
@ToString
@AllArgsConstructor
public enum ResponseEnum {
//成功信息
SUCCESS(200,"成功"),
//失败信息
ERROR(-1,"服务内部错误"),
//-1xx 服务器错误
BAD_SQL_GRAMMAR_ERROR(-101, "sql语法错误"),
SERVLET_ERROR(-102, "servlet请求异常"), //-2xx 参数校验
UPLOAD_ERROR(-103, "文件上传错误"),
EXPORT_DATA_ERROR(-104, "数据导出失败"),
//-2xx 参数校验
BORROW_AMOUNT_NULL_ERROR(-201, "借款额度不能为空"),
MOBILE_NULL_ERROR(-202, "手机号码不能为空"),
MOBILE_ERROR(-203, "手机号码不正确"),
PASSWORD_NULL_ERROR(-204, "密码不能为空"),
CODE_NULL_ERROR(-205, "验证码不能为空"),
CODE_ERROR(-206, "验证码错误"),
MOBILE_EXIST_ERROR(-207, "手机号已被注册"),
LOGIN_MOBILE_ERROR(-208, "用户不存在"),
LOGIN_PASSWORD_ERROR(-209, "密码错误"),
LOGIN_LOKED_ERROR(-210, "用户被锁定"),
LOGIN_AUTH_ERROR(-211, "未登录"),
EMAIL_NULL_ERROR(-212,"邮箱不能为空"),
PASSWORD_NOT_EQUALS(-213,"两次输入的密码不相同"),
USER_BIND_IDCARD_EXIST_ERROR(-301, "身份证号码已绑定"),
USER_NO_BIND_ERROR(-302, "用户未绑定"),
USER_NO_AMOUNT_ERROR(-303, "用户信息未审核"),
USER_AMOUNT_LESS_ERROR(-304, "您的借款额度不足"),
LEND_INVEST_ERROR(-305, "当前状态无法投标"),
LEND_FULL_SCALE_ERROR(-306, "已满标,无法投标"),
NOT_SUFFICIENT_FUNDS_ERROR(-307, "余额不足"),
PAY_UNIFIEDORDER_ERROR(-401, "统一下单错误"),
ALIYUN_RESPONSE_ERROR(-501, "阿里云短信服务响应失败"),
ALIYUN_SMS_LIMIT_CONTROL_ERROR(-502, "短信发送过于频繁"),//业务限流
ALIYUN_SMS_ERROR(-503, "短信发送失败"),//其他失败
WEIXIN_CALLBACK_PARAM_ERROR(-601, "回调参数不正确"),
WEIXIN_FETCH_ACCESSTOKEN_ERROR(-602, "获取access_token失败"),
WEIXIN_FETCH_USERINFO_ERROR(-603, "获取用户信息失败");
//响应状态码
private Integer code;
//响应信息
private String message;
}
Res:
package com.scnu.community.result;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* @author Rosemary
* @since 2022-09-18
* 统一类,返回给前端
* code 状态码
* message 状态码对应消息
* data 得到的数据
*/
@Data
@ApiModel(description = "返回结果对象")
public class Res {
@ApiModelProperty(value = "状态码")
private Integer code;
@ApiModelProperty(value = "状态码对应信息")
private String message;
@ApiModelProperty(value = "返回数据")
private Map<String,Object> data = new HashMap<>();
/**
* 构造函数私有化
*/
public Res(){};
/**
* 考虑到经常要调用,且都是返回R对象,故使用static方法
* @return result ok
*/
public static Res ok(){
Res r = new Res();
r.setCode(ResponseEnum.SUCCESS.getCode());
r.setMessage(ResponseEnum.SUCCESS.getMessage());
return r;
}
/**
* 考虑到经常要调用,故使用static方法
* @return result error
*/
public static Res error(){
Res r = new Res();
r.setCode(ResponseEnum.ERROR.getCode());
r.setMessage(ResponseEnum.ERROR.getMessage());
return r;
}
/**
* 设置特定结果
* @param responseEnum 响应枚举类
* @return result
*/
public static Res setResult(ResponseEnum responseEnum){
Res r = new Res();
r.setCode(responseEnum.getCode());
r.setMessage(responseEnum.getMessage());
return r;
}
/**
* 设置结果data
* @param key
* @param value
* @return
*/
public Res data(String key, Object value){
this.data.put(key, value);
return this;
}
/**
*参数是集合的情况
* @param map
* @return
*/
public Res data(Map<String,Object> map){
this.setData(map);
return this;
}
/**
* 设置特定的消息
* @param message
* @return
*/
public Res message(String message){
this.setMessage(message);
return this;
}
/**
* 方便扩展其他状态码
* @param Code
* @return
*/
public Res code(Integer Code){
this.setCode(code);
return this;
}
}
自定义异常类:
package com.scnu.community.exception;
import com.scnu.community.result.ResponseEnum;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Rosemary
* @since 2022-09-18
* 定义错误码和消息,而错误码和消息相对应(在ResponseEnum中定义)
* 自定义异常类,用运行时异常,否则在controller中写代码时
* 要么一直往上抛,要么就必须用try catch
*/
@Data
@NoArgsConstructor
public class BusinessException extends RuntimeException {
//状态码
private Integer code;
//消息
private String message;
/**
*
* @param message 错误消息
*/
public BusinessException(String message) {
this.message = message;
}
/**
* @param message 错误消息
* @param code 错误码
*/
public BusinessException(String message, Integer code) {
this.message = message;
this.code = code;
}
/**
*
* @param message 错误消息
* @param code 错误码
* @param cause 原始异常对象
*/
public BusinessException(String message, Integer code, Throwable cause) {
super(cause);
this.message = message;
this.code = code;
}
/**
*
* @param resultCodeEnum 接收枚举类型
*/
public BusinessException(ResponseEnum resultCodeEnum) {
this.message = resultCodeEnum.getMessage();
this.code = resultCodeEnum.getCode();
}
/**
*
* @param resultCodeEnum 接收枚举类型
* @param cause 原始异常对象
*/
public BusinessException(ResponseEnum resultCodeEnum, Throwable cause) {
super(cause);
this.message = resultCodeEnum.getMessage();
this.code = resultCodeEnum.getCode();
}
}
统一异常处理:
package com.scnu.community.exception;
import com.scnu.community.result.Res;
import com.scnu.community.result.ResponseEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;
/**
* @author Rosemary
* @since 2022-09-18
* 统一异常处理
* RestControllerAdvice : 统一异常处理,带有Rest则全部返回json
*/
@Slf4j
@RestControllerAdvice
public class UnifiedExceptionHandler{
@ExceptionHandler(value = Exception.class)
public Res handleException(Exception e){
log.error(e.getMessage(),e);
return Res.error();
}
@ExceptionHandler(value = BadSqlGrammarException.class)
public Res handleException(BadSqlGrammarException e){
log.error(e.getMessage(),e);
return Res.setResult(ResponseEnum.BAD_SQL_GRAMMAR_ERROR);
}
/**
* 处理自定义异常,当有新的异常时,只需要在controller中
* 设置状态码和消息,则其会被捕获到
* @param e
* @return
*/
@ExceptionHandler(value = BusinessException.class)
public Res handleException(BusinessException e){
log.error(e.getMessage(),e);
return Res.error().message(e.getMessage());
}
/**
* Controller上一层相关异常,即servlet请求异常
*/
@ExceptionHandler({
NoHandlerFoundException.class,
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
MethodArgumentNotValidException.class,
HttpMediaTypeNotAcceptableException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
MissingServletRequestPartException.class,
AsyncRequestTimeoutException.class
})
public Res handleServletException(Exception e) {
log.error(e.getMessage(), e);
//SERVLET_ERROR(-102, "servlet请求异常"),
return Res.error().message(ResponseEnum.SERVLET_ERROR.getMessage()).code(ResponseEnum.SERVLET_ERROR.getCode());
}
}
断言类:
package com.scnu.community.exception;
import com.scnu.community.result.ResponseEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
/**
* @author Rosemary
* @since 2022-09-18
*/
@Slf4j
public class Assert {
/**
* 断言对象不为空
* obj 为空则抛异常
* @param obj
* @param responseEnum
*/
public static void notNull(Object obj, ResponseEnum responseEnum){
if(obj == null){
log.info("obj is null.....................");
throw new BusinessException(responseEnum);
}
}
/**
* 断言对象为空
* 如果对象obj不为空,则抛出异常
* @param object
* @param responseEnum
*/
public static void isNull(Object object, ResponseEnum responseEnum) {
if (object != null) {
log.info("obj is not null......");
throw new BusinessException(responseEnum);
}
}
/**
* 断言表达式为真
* 如果不为真,则抛出异常
* @param expression 是否成功
*/
public static void isTrue(boolean expression, ResponseEnum responseEnum) {
if (!expression) {
log.info("fail...............");
throw new BusinessException(responseEnum);
}
}
/**
* 断言两个对象不相等
* 如果相等,则抛出异常
* @param m1
* @param m2
* @param responseEnum
*/
public static void notEquals(Object m1, Object m2, ResponseEnum responseEnum) {
if (m1.equals(m2)) {
log.info("equals...............");
throw new BusinessException(responseEnum);
}
}
/**
* 断言两个对象相等
* 如果不相等,则抛出异常
* @param m1
* @param m2
* @param responseEnum
*/
public static void equals(Object m1, Object m2, ResponseEnum responseEnum) {
if (!m1.equals(m2)) {
log.info("not equals...............");
throw new BusinessException(responseEnum);
}
}
/**
* 断言参数不为空
* 如果为空,则抛出异常
* @param s
* @param responseEnum
*/
public static void notEmpty(String s, ResponseEnum responseEnum) {
if (StringUtils.isEmpty(s)) {
log.info("is empty...............");
throw new BusinessException(responseEnum);
}
}
}
工具准备
建立一个utils包,准备几个工具,包括:
- JwtUtils
- MD5密码加密工具
- websocket聊天工具
JwtUtils:
package com.scnu.community.utils;
import com.scnu.community.exception.BusinessException;
import com.scnu.community.result.ResponseEnum;
import io.jsonwebtoken.*;
import org.springframework.util.StringUtils;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.util.Date;
/**
* @author Rosemary
* @since 2022-09-18
*/
public class JwtUtils {
private static final String tokenSignKey = "1234rosemary";
private static SecretKeySpec getKeyInstance(){
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
byte[] bytes = DatatypeConverter.parseBase64Binary(tokenSignKey);
return new SecretKeySpec(bytes,signatureAlgorithm.getJcaName());
}
public static String createToken(Long userId, String userName) {
//登录有效期是1天
long tokenExpiration = 24 * 60 * 60 * 1000;
String token = Jwts.builder()
.setSubject("SRB-USER")
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.claim("userId", userId)
.claim("userName", userName)
//服务器私钥
.signWith(SignatureAlgorithm.HS512, getKeyInstance())
.compressWith(CompressionCodecs.GZIP)
.compact();
return token;
}
/**
* 判断token是否有效
* @param token
* @return
*/
public static boolean checkToken(String token) {
if(StringUtils.isEmpty(token)) {
return false;
}
try {
Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
public static Long getUserId(String token) {
Claims claims = getClaims(token);
Long userId = (Long)claims.get("userId");
return userId;
// return userId.longValue();
}
public static String getUserName(String token) {
Claims claims = getClaims(token);
return (String)claims.get("userName");
}
public static void removeToken(String token) {
//jwttoken无需删除,客户端扔掉即可。
}
/**
* 校验token并返回Claims
* @param token
* @return
*/
private static Claims getClaims(String token) {
if(StringUtils.isEmpty(token)) {
// LOGIN_AUTH_ERROR(-211, "未登录"),
throw new BusinessException(ResponseEnum.LOGIN_AUTH_ERROR);
}
try {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return claims;
} catch (Exception e) {
throw new BusinessException(ResponseEnum.LOGIN_AUTH_ERROR);
}
}
}
MD5:
package com.scnu.community.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* @author Rosemary
* @since 2022-09-18
*/
public final class MD5Utils {
public static String encrypt(String strSrc) {
try {
char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f' };
byte[] bytes = strSrc.getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j = bytes.length;
char[] chars = new char[j * 2];
int k = 0;
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("MD5加密出错!!+" + e);
}
}
}
websocket:
package com.scnu.community.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Rosemary
* @since 2022-09-18
*/
@Component
@ServerEndpoint("/websocket/{username}")
public class WebSocket {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 在线人数
*/
public static int onlineNumber = 0;
/**
* 以用户的姓名为key,WebSocket为对象保存起来
*/
private static Map<String, WebSocket> clients = new ConcurrentHashMap<String, WebSocket>();
/**
* 会话
*/
private Session session;
/**
* 用户名称
*/
private String username;
/**
* 建立连接
*
* @param session
*/
@OnOpen
public void onOpen(@PathParam("username") String username, Session session)
{
onlineNumber++;
logger.info("现在来连接的客户id:"+session.getId()+"用户名:"+username);
this.username = username;
this.session = session;
logger.info("有新连接加入! 当前在线人数" + onlineNumber);
try {
//messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
//先给所有人发送通知,说我上线了
Map<String,Object> map1 = Maps.newHashMap();
map1.put("messageType",1);
map1.put("username",username);
sendMessageAll(JSON.toJSONString(map1),username);
//把自己的信息加入到map当中去
clients.put(username, this);
//给自己发一条消息:告诉自己现在都有谁在线
Map<String,Object> map2 = Maps.newHashMap();
map2.put("messageType",3);
//移除掉自己
Set<String> set = clients.keySet();
map2.put("onlineUsers",set);
sendMessageTo(JSON.toJSONString(map2),username);
}
catch (IOException e){
logger.info(username+"上线的时候通知所有人发生了错误");
}
}
@OnError
public void onError(Session session, Throwable error) {
logger.info("服务端发生了错误"+error.getMessage());
//error.printStackTrace();
}
/**
* 连接关闭
*/
@OnClose
public void onClose()
{
onlineNumber--;
//webSockets.remove(this);
clients.remove(username);
try {
//messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
Map<String,Object> map1 = Maps.newHashMap();
map1.put("messageType",2);
map1.put("onlineUsers",clients.keySet());
map1.put("username",username);
sendMessageAll(JSON.toJSONString(map1),username);
}
catch (IOException e){
logger.info(username+"下线的时候通知所有人发生了错误");
}
logger.info("有连接关闭! 当前在线人数" + onlineNumber);
}
/**
* 收到客户端的消息
*
* @param message 消息
* @param session 会话
*/
@OnMessage
public void onMessage(String message, Session session)
{
try {
logger.info("来自客户端消息:" + message+"客户端的id是:"+session.getId());
System.out.println("------------ :"+message);
JSONObject jsonObject = JSON.parseObject(message);
String textMessage = jsonObject.getString("message");
String fromusername = jsonObject.getString("username");
String tousername = jsonObject.getString("to");
//如果不是发给所有,那么就发给某一个人
//messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
Map<String,Object> map1 = Maps.newHashMap();
map1.put("messageType",4);
map1.put("textMessage",textMessage);
map1.put("fromusername",fromusername);
if(tousername.equals("All")){
map1.put("tousername","所有人");
sendMessageAll(JSON.toJSONString(map1),fromusername);
}
else{
map1.put("tousername",tousername);
sendMessageTo(JSON.toJSONString(map1),tousername);
}
}
catch (Exception e){
e.printStackTrace();
logger.info("发生了错误了");
}
}
public void sendMessageTo(String message, String ToUserName) throws IOException {
for (WebSocket item : clients.values()) {
// System.out.println("在线人员名单 :"+item.username.toString());
if (item.username.equals(ToUserName) ) {
item.session.getAsyncRemote().sendText(message);
break;
}
}
}
public void sendMessageAll(String message,String FromUserName) throws IOException {
for (WebSocket item : clients.values()) {
item.session.getAsyncRemote().sendText(message);
}
}
public static synchronized int getOnlineCount() {
return onlineNumber;
}
}
处理实体层
前面遗留了一个问题,生成代码的时候,strategy.setSuperEntityColumns(“id”)没有注掉,导致生成的实体类没有id。可以把id手动敲进去,类型为long。
在entity下建一个pojo包放生成的实体类,再建一个vo包写几个vo:
- 登陆vo
- 发帖vo
- 注册vo
- 用户信息vo(用来返回给前端)
登陆vo:
package com.scnu.community.entity.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author Rosemary
* @since 2022-09-18
*/
@Data
@ApiModel(description = "登录对象")
public class LoginVO {
@ApiModelProperty(value = "用户手机")
private String mobile;
@ApiModelProperty(value = "用户密码")
private String password;
}
发帖vo:
package com.scnu.community.entity.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* @author Rosemary
* @since 2022-09-18
*/
@Data
@ApiModel(description = "发帖对象")
public class PostVO {
/**
* 标题
*/
@ApiModelProperty(value = "帖子标题")
private String title;
/**
* 内容
*/
@ApiModelProperty(value = "帖子内容")
private String content;
/**
* 标签
*/
@ApiModelProperty(value = "帖子标签")
private List<String> tags;
}
注册vo:
package com.scnu.community.entity.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author Rosemary
* @since 2022-09-18
*/
@Data
@ApiModel(description = "注册对象")
public class RegisterVO {
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "确认密码")
private String rePassword;
@ApiModelProperty(value = "邮箱")
private String email;
}
用户信息vo:
package com.scnu.community.entity.vo;
import com.scnu.community.entity.pojo.PostInfo;
import com.scnu.community.entity.pojo.UserMessage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* @author Rosemary
* @since 2022-09-18
* 返回给前端
*/
@Data
@ApiModel(description="用户信息对象")
public class UserInfoVO {
@ApiModelProperty(value = "用户表id")
private Long id;
@ApiModelProperty(value = "用户姓名")
private String name;
@ApiModelProperty(value = "用户昵称")
private String alias;
@ApiModelProperty(value = "头像")
private String avatar;
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "邮箱")
private String email;
@ApiModelProperty(value = "个人简介")
private String bio;
@ApiModelProperty(value = "邮箱")
private Integer roleId;
@ApiModelProperty(value = "我的帖子")
private List<PostInfo> myPosts;
@ApiModelProperty(value = "我的点赞")
private List<PostInfo> myLikes;
@ApiModelProperty(value = "我的收藏")
private List<PostInfo> myCollects;
@ApiModelProperty(value = "给我的留言")
private List<UserMessage> messageList;
/**
* jwt 访问令牌
*/
@ApiModelProperty(value = "JWT访问令牌")
private String token;
}
服务层方法与实现
先以公告板功能为例,完成其服务层实现与控制器,测试能否在Api文档中找到他。
在IBillboardInfoService接口中,添加一个String getContent()方法,
代码会显示1 related problem,因为这个方法还没有在BillboardInfoServiceImpl中进行实现,
下面我们实现他:
@Resource
private BillboardInfoServiceImpl billboardInfoService;
@Transactional(rollbackFor = Exception.class)
@Override
public String getContent() {
//查出所有showed为1的信息
QueryWrapper<BillboardInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("showed", 1);
List<BillboardInfo> list = baseMapper.selectList(queryWrapper);
//random随机生成,范围[0,size-1]
int random = (new Random()).nextInt(list.size());
//随机返回一条信息
return list.get(random).getContent();
}
以上代码会在未过期的信息中随机返回一条。
控制器:
@Resource
private IBillboardInfoService iBillboardInfoService;
@ApiOperation("获取公告板信息")
@GetMapping("/billboardinfo")
public Res getBillboardInfo(){
//获取公告板信息
String content = iBillboardInfoService.getContent();
return Res.ok().data("billboardinfo",content);
}
打开http://localhost:9030/doc.html,可以看到这个控制器,点击调试,可以看到返回信息:
{
"code": 200,
"message": "成功",
"data": {
"billboardinfo": "R1.0 开始已实现护眼模式 ,妈妈再也不用担心我的眼睛了。"
}
}
其他控制器不一边写一边测试了,先将服务层方法与实现写完。
最核心的两个接口是帖子信息接口和用户信息接口,里面的方法比较多,需要先将其要调用的方法实现了,
其中用户信息方法需要调用帖子信息方法,所以最后实现。
在实现帖子信息的方法之前,需要先准备好标签、收藏、点赞的相关方法,
下面先来实现IPostTagService中需要的方法:
List<String> getPostByTag(Long tagId);
Impl:
package com.scnu.community.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.scnu.community.entity.pojo.PostTag;
import com.scnu.community.mapper.PostTagMapper;
import com.scnu.community.service.IPostTagService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 话题-标签 中间表 服务实现类
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Service
public class PostTagServiceImpl extends ServiceImpl<PostTagMapper, PostTag> implements IPostTagService {
@Transactional(rollbackFor = Exception.class)
@Override
public List<String> getPostByTag(Long tagId) {
QueryWrapper<PostTag> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("tag_id", tagId.toString());
List<PostTag> tagPosts = baseMapper.selectList(queryWrapper);
List<String> list = new ArrayList<>();
//从获取的记录中获取出postId,即topicId
for (PostTag u:tagPosts
) {
list.add(u.getTopicId());
}
return list;
}
}
实现IUserCollectService中的:
List<String> getCollectPosts(Long userId);
Impl:
package com.scnu.community.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.scnu.community.entity.pojo.UserCollect;
import com.scnu.community.mapper.UserCollectMapper;
import com.scnu.community.service.IUserCollectService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 用户收藏表 服务实现类
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Service
public class UserCollectServiceImpl extends ServiceImpl<UserCollectMapper, UserCollect> implements IUserCollectService {
/**
* 获取userId对应的收藏帖子的id
* @param userId
* @return
*/
@Transactional(rollbackFor = Exception.class)
@Override
public List<String> getCollectPosts(Long userId) {
QueryWrapper<UserCollect> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", userId.toString());
List<UserCollect> userCollects = baseMapper.selectList(queryWrapper);
List<String> list = new ArrayList<>();
//从获取的记录中获取出postId,即topicId
for (UserCollect u:userCollects
) {
list.add(u.getTopicId());
}
return list;
}
}
实现IUserLikeService中的:
List<String> getLikePosts(Long userId);
Impl:
package com.scnu.community.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.scnu.community.entity.pojo.UserLike;
import com.scnu.community.mapper.UserLikeMapper;
import com.scnu.community.service.IUserLikeService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 用户点赞表 服务实现类
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Service
public class UserLikeServiceImpl extends ServiceImpl<UserLikeMapper, UserLike> implements IUserLikeService {
/**
* 获取userId对应的点赞帖子的id
* @param userId
* @return list
*/
@Transactional(rollbackFor = Exception.class)
@Override
public List<String> getLikePosts(Long userId) {
QueryWrapper<UserLike> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", userId.toString());
List<UserLike> userCollects = baseMapper.selectList(queryWrapper);
List<String> list = new ArrayList<>();
//从获取的记录中获取出postId,即topicId
for (UserLike u:userCollects
) {
list.add(u.getTopicId());
}
return list;
}
}
下面实现帖子信息相关方法:
List<PostInfo> getHotPost();
List<PostInfo> getLastPost();
List<PostInfo> getUserPost(Long userId);
List<PostInfo> getCollectPost(Long userId);
List<PostInfo> getLikePost(Long userId);
List<PostInfo> searchPosts(String search);
Boolean deletePost(Long id, Long userId);
void doPost(Long userId, PostVO postVO);
void editPost(PostInfo postInfo,Long userId);
List<PostInfo> getPostByTag(Long tagId);
Impl:
package com.scnu.community.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.scnu.community.entity.pojo.PostInfo;
import com.scnu.community.entity.vo.PostVO;
import com.scnu.community.mapper.PostInfoMapper;
import com.scnu.community.service.IPostInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.scnu.community.service.IPostTagService;
import com.scnu.community.service.IUserCollectService;
import com.scnu.community.service.IUserLikeService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 话题表 服务实现类
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Service
public class PostInfoServiceImpl extends ServiceImpl<PostInfoMapper, PostInfo> implements IPostInfoService {
@Resource
private IUserCollectService iUserCollectService;
@Resource
private IUserLikeService iUserLikeService;
@Resource
private IPostTagService iPostTagService;
/**
* 根据标签获取帖子
* @return list
*/
@Transactional(rollbackFor = Exception.class)
@Override
public List<PostInfo> getPostByTag(Long tagId) {
List<PostInfo> list = new ArrayList<>();
List<String> tagPosts = iPostTagService.getPostByTag(tagId);
for (String s:tagPosts
) {
PostInfo postInfo = baseMapper.selectById(s);
list.add(postInfo);
}
return list;
}
/**
* 获取热门讨论贴
* @return list
*/
@Transactional(rollbackFor = Exception.class)
@Override
public List<PostInfo> getHotPost() {
//浏览量作为热门贴的依据
QueryWrapper<PostInfo> queryWrapper = new QueryWrapper<>();
//按浏览量逆序排序
queryWrapper.orderByDesc("view");
List<PostInfo> postInfoList = baseMapper.selectList(queryWrapper);
return postInfoList;
}
/**
* 获取最新讨论帖
* @return list
*/
@Transactional(rollbackFor = Exception.class)
@Override
public List<PostInfo> getLastPost() {
QueryWrapper<PostInfo> queryWrapper = new QueryWrapper<>();
//按时间逆序排序
queryWrapper.orderByDesc("create_time");
List<PostInfo> postInfoList = baseMapper.selectList(queryWrapper);
return postInfoList;
}
/**
* 通过userId获取对应的帖子
* @return list
*/
@Transactional(rollbackFor = Exception.class)
@Override
public List<PostInfo> getUserPost(Long userId) {
QueryWrapper<PostInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id",userId);
List<PostInfo> postInfoList = baseMapper.selectList(queryWrapper);
return postInfoList;
}
@Transactional(rollbackFor = Exception.class)
@Override
public List<PostInfo> getCollectPost(Long userId) {
//放置收藏的post
List<PostInfo> list = new ArrayList<>();
//获取用户收藏的post的id
List<String> collectPosts = iUserCollectService.getCollectPosts(userId);
for (String s:collectPosts
) {
PostInfo postInfo = baseMapper.selectById(s);
list.add(postInfo);
}
return list;
}
@Transactional(rollbackFor = Exception.class)
@Override
public List<PostInfo> getLikePost(Long userId) {
//放置点赞的post
List<PostInfo> list = new ArrayList<>();
//获取用户点赞的post的id
List<String> likePosts = iUserLikeService.getLikePosts(userId);
for (String s:likePosts
) {
PostInfo postInfo = baseMapper.selectById(s);
list.add(postInfo);
}
return list;
}
@Transactional(rollbackFor = Exception.class)
@Override
public List<PostInfo> searchPosts(String search) {
QueryWrapper<PostInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.like("title",search);
List<PostInfo> postInfoList = baseMapper.selectList(queryWrapper);
return postInfoList;
}
@Transactional(rollbackFor = Exception.class)
@Override
public Boolean deletePost(Long id, Long userId) {
QueryWrapper<PostInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id", id)
.eq("userId", userId);
int delete = baseMapper.delete(queryWrapper);
Boolean flag = true;
//为0说明没删除成功
if (delete==0){
flag = false;
}
return flag;
}
@Transactional(rollbackFor = Exception.class)
@Override
public void doPost(Long userId, PostVO postVO) {
PostInfo postInfo = new PostInfo();
postInfo.setTitle(postVO.getTitle());
postInfo.setUserId(userId.toString());
postInfo.setContent(postVO.getContent());
postInfo.setCreateTime(LocalDateTime.now());
baseMapper.insert(postInfo);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void editPost(PostInfo postInfo,Long userId) {
Long id = postInfo.getId();
postInfo.setUserId(userId.toString());
//修改一下更新时间
postInfo.setModifyTime(LocalDateTime.now());
//修改
baseMapper.updateById(postInfo);
}
}
现在实现IUserMessageService中的用户留言方法,他将被用户信息方法调用:
List<UserMessage> getUserMessage(Long userId);
Impl:
package com.scnu.community.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.scnu.community.entity.pojo.UserMessage;
import com.scnu.community.mapper.UserMessageMapper;
import com.scnu.community.service.IUserMessageService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
/**
* <p>
* 用户留言表 服务实现类
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Service
public class UserMessageServiceImpl extends ServiceImpl<UserMessageMapper, UserMessage> implements IUserMessageService {
@Resource
private IUserMessageService iUserMessageService;
@Transactional(rollbackFor = Exception.class)
@Override
public List<UserMessage> getUserMessage(Long userId) {
QueryWrapper<UserMessage> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", userId);
List<UserMessage> userMessages = baseMapper.selectList(queryWrapper);
return userMessages;
}
}
IUserInfoService中的用户信息方法:
void register(RegisterVO registerVO);
UserInfoVO login(LoginVO loginVO, String ip);
UserInfoVO getUserInfo(Long userId);
List<UserInfo> searchUsers(String search);
List<PostInfo> searchPosts(String search);
Impl:
package com.scnu.community.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.scnu.community.entity.pojo.PostInfo;
import com.scnu.community.entity.pojo.UserInfo;
import com.scnu.community.entity.pojo.UserMessage;
import com.scnu.community.entity.vo.LoginVO;
import com.scnu.community.entity.vo.RegisterVO;
import com.scnu.community.entity.vo.UserInfoVO;
import com.scnu.community.exception.Assert;
import com.scnu.community.mapper.UserInfoMapper;
import com.scnu.community.result.Res;
import com.scnu.community.result.ResponseEnum;
import com.scnu.community.service.IPostInfoService;
import com.scnu.community.service.IUserInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.scnu.community.service.IUserMessageService;
import com.scnu.community.utils.JwtUtils;
import com.scnu.community.utils.MD5Utils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
/**
* <p>
* 用户表 服务实现类
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements IUserInfoService {
@Resource
private IPostInfoService iPostInfoService;
@Resource
private IUserMessageService iUserMessageService;
@Transactional(rollbackFor = Exception.class)
@Override
public void register(RegisterVO registerVO) {
//判断用户是否被注册
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("mobile", registerVO.getMobile());
Integer count = baseMapper.selectCount(queryWrapper);
//手机号已被注册,会抛出异常
Assert.isTrue(count==0, ResponseEnum.MOBILE_EXIST_ERROR);
//执行到此,说明手机号未注册,则插入数据
UserInfo userInfo = new UserInfo();
userInfo.setMobile(registerVO.getMobile());
userInfo.setEmail(registerVO.getEmail());
userInfo.setPassword(MD5Utils.encrypt(registerVO.getPassword()));
userInfo.setCreateTime(LocalDateTime.now());
baseMapper.insert(userInfo);
}
@Transactional(rollbackFor = Exception.class)
@Override
public UserInfoVO login(LoginVO loginVO, String ip) {
String mobile = loginVO.getMobile();
String password = loginVO.getPassword();
System.out.println(mobile);
//用户是否存在
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper
.eq("mobile", mobile);
UserInfo userInfo = baseMapper.selectOne(queryWrapper);
Assert.notNull(userInfo, ResponseEnum.LOGIN_MOBILE_ERROR);
//密码是否正确
Assert.equals(MD5Utils.encrypt(password), userInfo.getPassword(), ResponseEnum.LOGIN_PASSWORD_ERROR);
//生成token
String token = JwtUtils.createToken(userInfo.getId(),userInfo.getUsername());
//组装UserInfoVO
UserInfoVO userInfoVO = new UserInfoVO();
userInfoVO.setToken(token);
userInfoVO.setName(userInfo.getUsername());
userInfoVO.setAlias(userInfo.getAlias());
userInfoVO.setAvatar(userInfo.getAvatar());
userInfoVO.setMobile(mobile);
//返回,此时token返回给前端
return userInfoVO;
}
/**
* 用于获取展示个人中心的信息
* @param userId 用户id
* @return UserInfoVO
*/
@Transactional(rollbackFor = Exception.class)
@Override
public UserInfoVO getUserInfo(Long userId) {
//获取用户信息
UserInfo userInfo = baseMapper.selectById(userId);
//为空则抛出用户不存在的异常
Assert.notNull(userInfo, ResponseEnum.LOGIN_MOBILE_ERROR);
//获取用户帖子列表
List<PostInfo> userPosts = iPostInfoService.getUserPost(userId);
UserInfoVO userInfoVO = new UserInfoVO();
//获取收藏的帖子
List<PostInfo> collectPosts = iPostInfoService.getCollectPost(userId);
//获取点赞的帖子
List<PostInfo> likePosts = iPostInfoService.getLikePost(userId);
//给我的留言
List<UserMessage> userMessages = iUserMessageService.getUserMessage(userId);
//组装userInfoVO
userInfoVO.setId(userInfo.getId());
userInfoVO.setAvatar(userInfo.getAvatar());
userInfoVO.setAlias(userInfo.getAlias());
userInfoVO.setMobile(userInfo.getMobile());
userInfoVO.setName(userInfo.getUsername());
userInfoVO.setBio(userInfo.getBio());
userInfoVO.setEmail(userInfo.getEmail());
userInfoVO.setMyPosts(userPosts);
userInfoVO.setMyCollects(collectPosts);
userInfoVO.setMyLikes(likePosts);
userInfoVO.setMessageList(userMessages);
return userInfoVO;
}
/**
* 搜索返回用户
* @param search
* @return
*/
@Override
public List<UserInfo> searchUsers(String search) {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.like("username", search);
List<UserInfo> userInfoList = baseMapper.selectList(queryWrapper);
return userInfoList;
}
/**
* 搜索返回帖子
* @param search
* @return
*/
@Override
public List<PostInfo> searchPosts(String search) {
List<PostInfo> postInfoList = iPostInfoService.searchPosts(search);
return postInfoList;
}
}
至此已经实现了项目服务层的主体方法,还剩几个小方法没有实现:
- 广告推送
- 每日赠言
- 用户聊天记录
- 用户关注
IPromotionInfoService广告推送:
List<PromotionInfo> geListPromotions();
Impl:
package com.scnu.community.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.scnu.community.entity.pojo.PromotionInfo;
import com.scnu.community.mapper.PromotionInfoMapper;
import com.scnu.community.service.IPromotionInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* <p>
* 广告推广表 服务实现类
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Service
public class PromotionInfoServiceImpl extends ServiceImpl<PromotionInfoMapper, PromotionInfo> implements IPromotionInfoService {
@Override
public List<PromotionInfo> geListPromotions() {
//最大的三个id对应的推广
QueryWrapper<PromotionInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id")
//表示在后面的sql语句中拼接上该字符串,即只获取三条
.last("limit 3");
List<PromotionInfo> promotionInfos = baseMapper.selectList(queryWrapper);
return promotionInfos;
}
}
ITipInfoService每日赠言:
TipInfo getContent();
Impl:
package com.scnu.community.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.scnu.community.entity.pojo.TipInfo;
import com.scnu.community.mapper.TipInfoMapper;
import com.scnu.community.service.ITipInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Random;
/**
* <p>
* 每日赠言 服务实现类
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Service
public class TipInfoServiceImpl extends ServiceImpl<TipInfoMapper, TipInfo> implements ITipInfoService {
@Transactional(rollbackFor = Exception.class)
@Override
public TipInfo getContent() {
//查出所有公告
QueryWrapper<TipInfo> queryWrapper = new QueryWrapper<>();
List<TipInfo> tipInfos = baseMapper.selectList(queryWrapper);
//random随机生成,范围[0,size-1]
Integer random = (new Random()).nextInt(tipInfos.size());
//随机返回一条信息
return tipInfos.get(random);
}
}
IUserChatService聊天记录:
List<UserChat> getUserChat(Long userId);
void postUserChat(Long userId, Long toId, String content);
Impl:
package com.scnu.community.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.scnu.community.entity.pojo.UserChat;
import com.scnu.community.mapper.UserChatMapper;
import com.scnu.community.service.IUserChatService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
/**
* <p>
* 服务实现类
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Service
public class UserChatServiceImpl extends ServiceImpl<UserChatMapper, UserChat> implements IUserChatService {
private Long chatId = 2L ;
@Resource
private IUserChatService iUserChatService;
@Transactional(rollbackFor = Exception.class)
@Override
public List<UserChat> getUserChat(Long userId) {
QueryWrapper<UserChat> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("to_id", userId.toString());
List<UserChat> userchat = baseMapper.selectList(queryWrapper);
return userchat;
}
@Transactional(rollbackFor = Exception.class)
@Override
public void postUserChat(Long userId, Long toId, String content){
UserChat userChat = new UserChat();
userChat.setSendId(userId.toString());
userChat.setToId(toId.toString());
userChat.setContent(content);
userChat.setId(chatId);
chatId++;
baseMapper.insert(userChat);
}
}
IUserFollowService用户关注:
Boolean doFollow(Long userId, String id);
List<UserInfoVO> getFollowUsers(String userId);
List<UserInfoVO> getFans(String id);
Impl:
package com.scnu.community.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.scnu.community.entity.pojo.UserFollow;
import com.scnu.community.entity.pojo.UserInfo;
import com.scnu.community.entity.vo.UserInfoVO;
import com.scnu.community.mapper.UserFollowMapper;
import com.scnu.community.service.IUserFollowService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.scnu.community.service.IUserInfoService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 用户关注表 服务实现类
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Service
public class UserFollowServiceImpl extends ServiceImpl<UserFollowMapper, UserFollow> implements IUserFollowService {
@Resource
private IUserInfoService iUserInfoService;
@Transactional(rollbackFor = Exception.class)
@Override
public Boolean doFollow(Long userId, String id) {
//表的关联
QueryWrapper<UserFollow> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("follower_id", userId.toString())
.eq("parent_id", id);
UserFollow uf = baseMapper.selectOne(queryWrapper);
//说明没关注过,此时进行关注的操作
if (uf==null){
UserFollow userFollow =new UserFollow();
userFollow.setFollowerId(userId.toString());
userFollow.setParentId(id);
baseMapper.insert(userFollow);
return true;
}
//执行到这说明已经关注了,故删除关注记录
baseMapper.delete(queryWrapper);
return false;
}
/**
* 获取userId的关注的人
* @param userId
* @return
*/
@Transactional(rollbackFor = Exception.class)
@Override
public List<UserInfoVO> getFollowUsers(String userId) {
QueryWrapper<UserFollow> queryWrapper = new QueryWrapper<>();
//只查出parent_id这个字段
// queryWrapper.select("parent_id").eq("user_id", userId);
queryWrapper.eq("follower_id", userId).select("distinct parent_id");
List<UserFollow> userFollows =baseMapper.selectList(queryWrapper);
List<UserInfoVO> userInfoVOList = new ArrayList<>();
for (UserFollow uf:userFollows
) {
//组装UserInfoVO
UserInfo userInfo = iUserInfoService.getById(uf.getParentId());
UserInfoVO userInfoVO = new UserInfoVO();
userInfoVO.setId(userInfo.getId());
userInfoVO.setAvatar(userInfo.getAvatar());
userInfoVO.setAlias(userInfo.getAlias());
userInfoVO.setMobile(userInfo.getMobile());
userInfoVO.setName(userInfo.getUsername());
userInfoVO.setBio(userInfo.getBio());
userInfoVO.setEmail(userInfo.getEmail());
userInfoVOList.add(userInfoVO);
}
return userInfoVOList;
}
/**
* 获取关注userId的人
* @param userId
* @return
*/
@Transactional(rollbackFor = Exception.class)
@Override
public List<UserInfoVO> getFans(String userId) {
QueryWrapper<UserFollow> queryWrapper = new QueryWrapper<>();
//只查出parent_id这个字段
// queryWrapper.select("parent_id").eq("user_id", userId);
queryWrapper.eq("parent_id", userId).select("distinct follower_id");
List<UserFollow> userFollows =baseMapper.selectList(queryWrapper);
List<UserInfoVO> userInfoVOList = new ArrayList<>();
for (UserFollow uf:userFollows
) {
//组装UserInfoVO
UserInfo userInfo = iUserInfoService.getById(uf.getFollowerId());
UserInfoVO userInfoVO = new UserInfoVO();
userInfoVO.setId(userInfo.getId());
userInfoVO.setAvatar(userInfo.getAvatar());
userInfoVO.setAlias(userInfo.getAlias());
userInfoVO.setMobile(userInfo.getMobile());
userInfoVO.setName(userInfo.getUsername());
userInfoVO.setBio(userInfo.getBio());
userInfoVO.setEmail(userInfo.getEmail());
userInfoVOList.add(userInfoVO);
}
return userInfoVOList;
}
}
至此,服务层接口与方法大体实现。
控制器实现与Api文档测试
前面已经实现了公告板控制器,本节要实现以下控制器:
- 帖子信息
- 用户信息
- 广告推送
- 每日赠言
- 用户聊天记录
- 用户关注
PostInfoController:
package com.scnu.community.controller;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.scnu.community.entity.pojo.PostInfo;
import com.scnu.community.entity.vo.PostVO;
import com.scnu.community.result.Res;
import com.scnu.community.service.IPostInfoService;
import com.scnu.community.utils.JwtUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* <p>
* 话题表 前端控制器
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Api(tags = "帖子接口")
@RestController
@RequestMapping("/community/scnu")
public class PostInfoController {
@Resource
private IPostInfoService iPostInfoService;
// @Resource
// private IPostTagService iPostTagService;
@ApiOperation("发布帖子")
@PostMapping("/postinfo/post")
public Res doPost(HttpServletRequest request, @RequestBody PostVO postVO){
//获取用户id
String token = request.getHeader("token");
Long userId = JwtUtils.getUserId(token);
//增加帖子
iPostInfoService.doPost(userId, postVO);
//传入所有blog,得到分页结果对象
return Res.ok().message("发布成功");
}
/**
* 编辑post
* @param request
* @param postInfo
* @return
*/
@ApiOperation("编辑修改帖子")
@PostMapping("/postinfo/edit")
public Res editPost(HttpServletRequest request,@RequestBody PostInfo postInfo){
String token = request.getHeader("token");
Long userId = JwtUtils.getUserId(token);
iPostInfoService.editPost(postInfo,userId);
return Res.ok().message("修改成功");
}
@ApiOperation("获取高数课主贴")
@GetMapping("/postinfo/math")
public Res getMathPost(@RequestParam(required = false,defaultValue = "1",value = "pageNum")
int pageNum){
PageHelper.startPage(pageNum,5);
List<PostInfo> postInfoLists = iPostInfoService.getPostByTag(111L);
PageInfo pageInfo = new PageInfo(postInfoLists);
return Res.ok().data("pageInfo",pageInfo);
}
@ApiOperation("获取专业课主贴")
@GetMapping("/postinfo/major")
public Res getMajorPost(@RequestParam(required = false,defaultValue = "1",value = "pageNum")
int pageNum){
PageHelper.startPage(pageNum,5);
List<PostInfo> postInfoLists = iPostInfoService.getPostByTag(112L);
PageInfo pageInfo = new PageInfo(postInfoLists);
return Res.ok().data("pageInfo",pageInfo);
}
@ApiOperation("获取公共课主贴")
@GetMapping("/postinfo/public")
public Res getPublicPost(@RequestParam(required = false,defaultValue = "1",value = "pageNum")
int pageNum){
PageHelper.startPage(pageNum,5);
List<PostInfo> postInfoLists = iPostInfoService.getPostByTag(113L);
PageInfo pageInfo = new PageInfo(postInfoLists);
return Res.ok().data("pageInfo",pageInfo);
}
@ApiOperation("获取女生专区主贴")
@GetMapping("/postinfo/girls")
public Res getGirlsPost(@RequestParam(required = false,defaultValue = "1",value = "pageNum")
int pageNum){
PageHelper.startPage(pageNum,5);
List<PostInfo> postInfoLists = iPostInfoService.getPostByTag(211L);
PageInfo pageInfo = new PageInfo(postInfoLists);
return Res.ok().data("pageInfo",pageInfo);
}
@ApiOperation("获取男生专区主贴")
@GetMapping("/postinfo/boys")
public Res getBoysPost(@RequestParam(required = false,defaultValue = "1",value = "pageNum")
int pageNum){
PageHelper.startPage(pageNum,5);
List<PostInfo> postInfoLists = iPostInfoService.getPostByTag(212L);
PageInfo pageInfo = new PageInfo(postInfoLists);
return Res.ok().data("pageInfo",pageInfo);
}
@ApiOperation("获取社团专区主贴")
@GetMapping("/postinfo/party")
public Res getPartyPost(@RequestParam(required = false,defaultValue = "1",value = "pageNum")
int pageNum){
PageHelper.startPage(pageNum,5);
List<PostInfo> postInfoLists = iPostInfoService.getPostByTag(213L);
PageInfo pageInfo = new PageInfo(postInfoLists);
return Res.ok().data("pageInfo",pageInfo);
}
@ApiOperation("获取生活论坛主贴")
@GetMapping("/postinfo/daily")
public Res getDailyPost(@RequestParam(required = false,defaultValue = "1",value = "pageNum")
int pageNum){
PageHelper.startPage(pageNum,5);
List<PostInfo> postInfoLists = iPostInfoService.getPostByTag(311L);
PageInfo pageInfo = new PageInfo(postInfoLists);
return Res.ok().data("pageInfo",pageInfo);
}
@ApiOperation("获取闲置出售主贴")
@GetMapping("/postinfo/unused")
public Res getUnusedPost(@RequestParam(required = false,defaultValue = "1",value = "pageNum")
int pageNum){
PageHelper.startPage(pageNum,5);
List<PostInfo> postInfoLists = iPostInfoService.getPostByTag(312L);
PageInfo pageInfo = new PageInfo(postInfoLists);
return Res.ok().data("pageInfo",pageInfo);
}
@ApiOperation("获取热门讨论贴")
@GetMapping("/postinfo/hot")
public Res getHotPost(@RequestParam(required = false,defaultValue = "1",value = "pageNum")
int pageNum){
//开启分页,pageNum相当于页数,pageSize为每页展现的帖子数
PageHelper.startPage(pageNum,5);
List<PostInfo> postInfoLists = iPostInfoService.getHotPost();
//传入所有blog,得到分页结果对象
PageInfo pageInfo = new PageInfo(postInfoLists);
return Res.ok().data("pageInfo",pageInfo);
}
@ApiOperation("获取最新讨论贴")
@GetMapping("/postinfo/last")
public Res getLastPost(@RequestParam(required = false,defaultValue = "1",value = "pageNum")
int pageNum){
PageHelper.startPage(pageNum,5);
List<PostInfo> postInfoLists = iPostInfoService.getLastPost();
PageInfo pageInfo = new PageInfo(postInfoLists);
return Res.ok().data("pageInfo",pageInfo);
}
@ApiOperation("删除帖子")
@PostMapping("/postinfo/delete")
public Res getLastPost(HttpServletRequest request, @RequestParam(required = true)Long id){
String token = request.getHeader("token");
//先获取用户id
Long userId = JwtUtils.getUserId(token);
//删除post
Boolean flag = iPostInfoService.deletePost(id,userId);
if(flag){
return Res.ok().message("删除成功");
}
return Res.error().message("删除失败,请刷新重试");
}
}
UserInfoController:
package com.scnu.community.controller;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.scnu.community.entity.pojo.PostInfo;
import com.scnu.community.entity.pojo.UserInfo;
import com.scnu.community.entity.vo.LoginVO;
import com.scnu.community.entity.vo.RegisterVO;
import com.scnu.community.entity.vo.UserInfoVO;
import com.scnu.community.exception.Assert;
import com.scnu.community.result.Res;
import com.scnu.community.result.ResponseEnum;
import com.scnu.community.service.IUserFollowService;
import com.scnu.community.service.IUserInfoService;
import com.scnu.community.utils.JwtUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
/**
* <p>
* 用户表 前端控制器
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Api(tags = "用户接口")
@RestController
@RequestMapping("/community/scnu/user")
public class UserInfoController {
@Resource
private IUserInfoService userInfoService;
@ApiOperation("用户注册")
@PostMapping("/register")
public Res register(@RequestBody RegisterVO registerVO){
//不能为空
Assert.notEmpty(registerVO.getMobile(), ResponseEnum.MOBILE_NULL_ERROR);
Assert.notEmpty(registerVO.getEmail(), ResponseEnum.EMAIL_NULL_ERROR);
Assert.notEmpty(registerVO.getPassword(), ResponseEnum.PASSWORD_NULL_ERROR);
Assert.notEmpty(registerVO.getRePassword(), ResponseEnum.PASSWORD_NULL_ERROR);
//密码与确认密码要相同,不相同则抛出异常
Assert.equals(registerVO.getPassword(), registerVO.getRePassword(),
ResponseEnum.PASSWORD_NOT_EQUALS);
//注册
userInfoService.register(registerVO);
return Res.ok().message("注册成功");
}
@ApiOperation("用户登录")
@PostMapping("/login")
public Res login(@RequestBody LoginVO loginVO, HttpServletRequest request){
//账号密码不能为空
Assert.notEmpty(loginVO.getMobile(), ResponseEnum.MOBILE_NULL_ERROR);
Assert.notEmpty(loginVO.getPassword(), ResponseEnum.PASSWORD_NULL_ERROR);
//获取客户端请求ip
String ip = request.getRemoteAddr();
//登录
UserInfoVO userInfoVo = userInfoService.login(loginVO,ip);
System.out.println("执行不到这说明有误");
return Res.ok().data("userInfo",userInfoVo);
}
@ApiOperation("个人中心")
@GetMapping("/")
public Res getUserInfo(HttpServletRequest request){
//从token中获取UserId
String token = request.getHeader("token");
Long userId = JwtUtils.getUserId(token);
//获取个人中心相应的信息,这些信息要用于展示页面
UserInfoVO userInfoVo = userInfoService.getUserInfo(userId);
return Res.ok().data("userInfo",userInfoVo);
}
/**
* @param search 要搜索的内容或者用户
* @return
*/
@ApiOperation("搜索内容或用户")
@PostMapping("/search")
public Res searchUserOrMessage(@RequestBody String search){
//先展示用户,再展示帖子标题含有search字符串的帖子
List<UserInfo> userInfoList = userInfoService.searchUsers(search);
List<PostInfo> postInfoList = userInfoService.searchPosts(search);
HashMap<String,List> map = new HashMap<>();
if (userInfoList.size()==0 && postInfoList.size()==0){
//前端展示时,先判断map是否为空,如果是则说明搜索不到
return Res.ok().data("map","无此用户或帖子");
}
map.put("users", userInfoList);
map.put("posts", postInfoList);
//返回map,k1对应users,k2对应posts
return Res.ok().data("map",map);
}
}
打开Api文档,测试注册:
{
"email": "920688354@qq.com",
"mobile": "13662464656",
"password": "123456",
"rePassword": "123456"
}
结果:
{
"code": 200,
"message": "注册成功",
"data": {}
}
测试登陆:
{
"mobile": "13662464656",
"password": "123456"
}
结果:
{
"code": 200,
"message": "成功",
"data": {
"userInfo": {
"id": null,
"name": "",
"alias": null,
"avatar": null,
"mobile": "13662464656",
"email": null,
"bio": null,
"roleId": null,
"myPosts": null,
"myLikes": null,
"myCollects": null,
"messageList": null,
"token": "eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAKtWKi5NUrJSCg5y0g0Ndg1S0lFKrShQsjI0MzM2MzM0NTPSUSotTi3yTAGKmZobmpsbWZqZmZpYGFmYm1gaQCT9EnNTgWYo1QIAR0LDQ04AAAA.YZUofoFIuDdwQCxCgN6sZgma1je1BUP0GHWYe-_rBbRY33aYuBG-KEk7DjiESV_AIm9S2S_KgL_C6km_e1wr_w"
}
}
}
对于需要登陆后操作的功能,需要先在全局参数中设置一个参数,
参数名称为token,参数值为登陆获得的token,参数类型为header。
添加全局参数后测试个人中心结果:
{
"code": 200,
"message": "成功",
"data": {
"userInfo": {
"id": 1571772966548287500,
"name": "",
"alias": null,
"avatar": null,
"mobile": "13662464656",
"email": "920688354@qq.com",
"bio": null,
"roleId": null,
"myPosts": [],
"myLikes": [],
"myCollects": [],
"messageList": [],
"token": null
}
}
}
发帖测试:
{
"content": "测试",
"tags": [],
"title": "111111"
}
结果:
{
"code": 200,
"message": "发布成功",
"data": {}
}
继续完成剩余四个控制器。
PromotionInfoController:
package com.scnu.community.controller;
import com.scnu.community.entity.pojo.PromotionInfo;
import com.scnu.community.result.Res;
import com.scnu.community.service.IPromotionInfoService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* <p>
* 广告推广表 前端控制器
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Api(tags = "推广链接")
@RestController
@RequestMapping("/community/scnu")
public class PromotionInfoController {
@Resource
private IPromotionInfoService iPromotionInfoService;
@ApiOperation("获取三条推广信息")
@GetMapping("/promotioninfo")
public Res getPromotionInfo(){
//随机获取公告板信息
List<PromotionInfo> promotionInfoList = iPromotionInfoService.geListPromotions();
return Res.ok().data("promotionInfo",promotionInfoList);
}
}
TipInfoController:
package com.scnu.community.controller;
import com.scnu.community.entity.pojo.TipInfo;
import com.scnu.community.result.Res;
import com.scnu.community.service.ITipInfoService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* <p>
* 每日赠言 前端控制器
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Api(tags = "每日赠言")
@RestController
@RequestMapping("/community/scnu")
public class TipInfoController {
@Resource
private ITipInfoService tipInfoService;
@ApiOperation("随机获取每日赠言记录")
@GetMapping("/tipinfo")
public Res getTipInfo(){
TipInfo tipInfo = tipInfoService.getContent();
//返回结果
return Res.ok().data("tipInfo",tipInfo);
}
}
UserChatController:
package com.scnu.community.controller;
import com.scnu.community.entity.pojo.PostInfo;
import com.scnu.community.entity.pojo.UserChat;
import com.scnu.community.entity.vo.PostVO;
import com.scnu.community.result.Res;
import com.scnu.community.service.IPostInfoService;
import com.scnu.community.service.IUserChatService;
import com.scnu.community.utils.JwtUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* <p>
* 用户私聊 前端控制器
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Api(tags = "私聊接口")
@RestController
@RequestMapping("/community/chat")
public class UserChatController {
@Resource
private IUserChatService iUserChatService;
@ApiOperation("接收消息")
@GetMapping("/get")
public Res getUserChat(Long sendId){
List<UserChat> userChatList = iUserChatService.getUserChat(sendId);
return Res.ok().data("chatContent",userChatList);
}
@ApiOperation("发送消息")
@PostMapping("/post")
public Res postUserChat(Long sendId, Long toId, String content){
iUserChatService.postUserChat(sendId, toId, content);
return Res.ok().message("发送成功");
}
}
UserFollowController:
package com.scnu.community.controller;
import com.scnu.community.entity.pojo.UserInfo;
import com.scnu.community.entity.vo.UserInfoVO;
import com.scnu.community.exception.Assert;
import com.scnu.community.result.Res;
import com.scnu.community.result.ResponseEnum;
import com.scnu.community.service.IUserFollowService;
import com.scnu.community.utils.JwtUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* <p>
* 用户关注表 前端控制器
* </p>
*
* @author Rosemary
* @since 2022-09-18
*/
@Api(tags = "用户关注表接口")
@RestController
@RequestMapping("/community/scnu/userfollow")
public class UserFollowController {
@Resource
private IUserFollowService userFollowService;
/**
* 这里设置为点击一下关注,再点击一下就取消关注
* @param id 需要关注的用户
* @return
*/
@ApiOperation("关注用户")
@PostMapping("/follow/{id}")
public Res followUser(HttpServletRequest request, @PathVariable @RequestBody String id){
//从请求中获取userId
String token = request.getHeader("token");
Long userId = JwtUtils.getUserId(token);
//关注者和被关注者不能相同
if (id.equals(userId)){
Res.ok().data("data","不能关注自己");
}
Assert.notNull(userId, ResponseEnum.ERROR);
//flag为true表示关注,false表示无关注
Boolean flag = userFollowService.doFollow(userId,id);
return Res.ok().data("flag",flag);
}
/**
*
* @param id 用户的id
* @return 用户id关注的用户列表
*/
@ApiOperation("获取关注用户")
@PostMapping("/getfollow/{id}")
public Res getfollowUsers(@PathVariable @RequestBody String id){
//用户id不存在
Assert.notNull(id, ResponseEnum.LOGIN_MOBILE_ERROR);
//flag为true表示关注,false表示无关注
List<UserInfoVO> userInfoVOList = userFollowService.getFollowUsers(id);
return Res.ok().data("userInfoVOList",userInfoVOList);
}
/**
*
* @param id 用户的id
* @return 关注用户id的用户列表
*/
@ApiOperation("获取粉丝")
@GetMapping("/getfan/{id}")
public Res getFans(@PathVariable @RequestBody String id){
//用户id不存在
Assert.notNull(id, ResponseEnum.LOGIN_MOBILE_ERROR);
//flag为true表示关注,false表示无关注
List<UserInfoVO> userInfoVOList = userFollowService.getFans(id);
return Res.ok().data("userInfoVOList",userInfoVOList);
}
}
可以调节数据库中的内容进行测试,不再赘述。
整合websocket
config:
package com.scnu.community.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketStompConfig {
//这个bean的注册,用于扫描带有@ServerEndpoint的注解成为websocket
@Bean
public ServerEndpointExporter serverEndpointExporter()
{
return new ServerEndpointExporter();
}
}
前面已经引入了websocketUtils。
controller:
package com.scnu.community.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class WebSocketController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@RequestMapping("/websocket/{name}")
public String webSocket(@PathVariable String name, Model model) {
try {
logger.info("跳转到websocket的页面上");
System.out.println(name);
model.addAttribute("username", name);
return "websocket";
} catch (Exception e) {
logger.info("跳转到websocket的页面上发生异常,异常信息是:" + e.getMessage());
return "error";
}
}
}
测试用的前端页面(templates/websocket.html):
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<title>websocket</title>
</head>
<body>
<div style="margin: auto;text-align:left">
<h1>Websocket测试</h1>
</div>
<br/>
<div style="margin: auto;text-align:left">
<select id="onLineUser">
<option>在线好友列表</option>
</select>
<input id="text" type="text" />
<input type="button" value="发送" onclick="send()" />
</div>
<br>
<div style="margin-right: 10px;text-align: left">
<input type="button" value="关闭连接" onclick="closeWebSocket()" />
</div>
<hr/>
<div id="message" style="text-align: left;"></div>
<input type="text" th:value="${username}" id="username" style="display: none" />
<script type="text/javascript">
var webSocket;
var commWebSocket;
//判断当前浏览器是否支持WebSocket
if ("WebSocket" in window)
{
websocket = new WebSocket("ws://localhost:9030/websocket/"+document.getElementById('username').value);
//连通之后的回调事件 连接成功
websocket.onopen = function()
{
// websocket.send( document.getElementById('username').value+"已经上线了");
console.log(document.getElementById('username').value+"已经连通了websocket");
setMessageInnerHTML(document.getElementById('username').value+"已经连通了websocket");
};
//接收后台服务端的消息 接收到消息
websocket.onmessage = function (evt)
{
var received_msg = evt.data;
console.log("数据已接收:" +received_msg);
var obj = JSON.parse(received_msg);
console.log("可以解析成json,消息类型(1代表上线 2代表下线 3代表在线名单 4代表普通消息)是:"+obj.messageType);
//1代表上线 2代表下线 3代表在线名单 4代表普通消息
if(obj.messageType==1){
//把名称放入到selection当中供选择
var onlineName = obj.username;
var option = "<option>"+onlineName+"</option>";
$("#onLineUser").append(option);
setMessageInnerHTML(onlineName+"上线了");
}
else if(obj.messageType==2){
$("#onLineUser").empty();
var onlineName = obj.onlineUsers;
var offlineName = obj.username;
var option = "<option>"+"--所有--"+"</option>";
for(var i=0;i<onlineName.length;i++){
if(!(onlineName[i]==document.getElementById('username').value)){
option+="<option>"+onlineName[i]+"</option>"
}
}
$("#onLineUser").append(option);
setMessageInnerHTML(offlineName+"下线了");
}
else if(obj.messageType==3){
var onlineName = obj.onlineUsers;
var option = null;
for(var i=0;i<onlineName.length;i++){
if(!(onlineName[i]==document.getElementById('username').value)){
option+="<option>"+onlineName[i]+"</option>"
}
}
$("#onLineUser").append(option);
console.log("获取了在线的名单"+onlineName.toString());
}
else{
setMessageInnerHTML(obj.fromusername+":"+obj.textMessage);
}
};
//连接关闭的回调事件 连接关闭
websocket.onclose = function()
{
console.log("连接已关闭");
setMessageInnerHTML("连接已经关闭");
};
}
else{
// 浏览器不支持 WebSocket
alert("您的浏览器不支持 WebSocket!");
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
function closeWebSocket() {
//直接关闭websocket的连接
websocket.close();
}
function send() {
var selectText = $("#onLineUser").find("option:selected").text();
if(selectText=="在线好友列表"){
selectText = "All";
}
else{
setMessageInnerHTML(document.getElementById('username').value+":"+ $("#text").val());
}
var message = {
"message":document.getElementById('text').value,
"username":document.getElementById('username').value,
"to":selectText
};
websocket.send(JSON.stringify(message));
$("#text").val("");
}
</script>
</body>
</html>
http://localhost:9030/websocket/{username},用两个不同的username登陆测试,互发消息即可。
补充
跨域问题
什么是跨域?
跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。
例如:a页面想获取b页面资源,如果a、b页面的协议、域名、端口、子域名不同,所进行的访问行动都是跨域的,而浏览器为了安全问题一般都限制了跨域访问,也就是不允许跨域请求资源。注意:跨域限制访问,其实是浏览器的限制。理解这一点很重要!!!
同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域:
解决跨域问题的几种方法:
- 返回新的CorsFilter
- 重写 WebMvcConfigurer
- 使用注解 @CrossOrigin
- 手动设置响应头 (HttpServletResponse)
- 自定web filter 实现跨域
这里使用重写WebMvcConfigurer的方法实现:
@Configuration
public class GlobalWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
}
即重写WebMvcConfigurer中的addCorsMappings方法,allowCredentials为true表示允许发送cookie。
并声明要放行的原始域。
MD5加密
MD5加密的特点:
1.长度固定
不管多长的字符串,加密后长度都是一样长
作用:方便平时信息的统计和管理
2.易计算
字符串和文件加密的过程是容易的.
作用: 开发者很容易理解和做出加密工具
3.细微性
一个文件,不管多大,小到几k,大到几G,你只要改变里面某个字符,那么都会导致MD5值改变.
作用:很多软件和应用在网站提供下载资源,其中包含了对文件的MD5码,用户下载后只需要用工具测一下下载好的文件,通过对比就知道该文件是否有过更改变动.
4.不可逆性
你明明知道密文和加密方式,你却无法反向计算出原密码.
作用:基于这个特点,很多安全的加密方式都是用到.大大提高了数据的安全性
为防止撞库,还可以在传入的字符串后面加一段固定字符串,即“加盐”。
项目流程梳理
- 确定需求。
- 设计生成数据库表。
- 使用MyBatisPlus代码生成器,生成entity、mapper、服务层和控制器的基本框架。
- application.yml的配置,包括连接数据库。
- 引入工具类,包括jwtutil、MD5加密、websocket私聊实现。
- 设计并实现服务层的接口。
- 引入Swagger生成Api文档,并实现控制器。
- 测试。