訪問地址:http://8.130.142.126:18080/sign-in.html?
代碼獲取:基于 Spring 前后端分離版本的論壇系統(tǒng): 基于 Spring 前后端分離版本的論壇系統(tǒng)?
一.前置知識
1.軟件生命周期
- a. 可行性研究:通過分析軟件開發(fā)要求,確定軟件項目的性質(zhì)、目標和規(guī)模,得出可行性研究報告,如果可行性研究報告是可行的,就要制訂詳細的項目開發(fā)計劃。此階段交付成果為可行性研究報告;
- b. 需求分析:是軟件生命周期中重要的也是決定性的一步,通過需求分析才能把軟件功能和性能的總體概念描述為具體的軟件需求規(guī)格說明書,為軟件開發(fā)奠定基礎(chǔ),此階段交付成果為軟件需求規(guī)格說明書;
- c. 概要設(shè)計:根據(jù)軟件需求規(guī)格說明建立軟件系統(tǒng)的總體結(jié)構(gòu)和模塊間的關(guān)系,定義各功能模塊接口,設(shè)計全局數(shù)據(jù)庫或數(shù)據(jù)結(jié)構(gòu),規(guī)定設(shè)計約束,制定組裝(集成)測試計劃;
- d. 詳細設(shè)計:將各模塊要實現(xiàn)的功能用相應的設(shè)計工具詳細的描述出來。
- e. 實現(xiàn):寫出正確的、易理解的和易維護的程序模塊。程序員根據(jù)詳細設(shè)計文檔將詳細設(shè)計轉(zhuǎn)化為程序,完成單元測試;
- f. 組裝測試(集成測試):將經(jīng)過單元測試的模塊逐步進行組裝和測試;
- g. 確認測試:測試系統(tǒng)是否達到了系統(tǒng)要求,按照規(guī)格說明書的規(guī)定,由用戶(或在用戶積極參與下)對系統(tǒng)進行驗收。必要時,還可以再通過現(xiàn)場測試或并行運行等?法對系統(tǒng)進行進一步測試;
- ?h. 使用:將軟件安裝在用戶確定的運行環(huán)境中,測試通過后移交用戶使用。在軟件使用過程中,客戶和維護人員必須認真收集發(fā)現(xiàn)的軟件錯誤,定期或階段性的撰寫軟件問題報告和軟件修改報告;
- i. 維護:通過各種必要的維護活動使系統(tǒng)持久的滿足用戶需要;
- j. 退役:終止對軟件產(chǎn)品的支持,軟件停止使用
2.面向?qū)ο?/h3>
3.架構(gòu)
C/S架構(gòu):即客戶端 / 服務(wù)器架構(gòu)模式
B/S架構(gòu):即瀏覽器 / 服務(wù)器架構(gòu)模式
4.相關(guān)技術(shù)和工具
服務(wù)器端技術(shù)
- Spring
- Spring Boot
- Spring MVC
- MyBatis
瀏覽器端技術(shù)
- HTML, CSS, JavaScript
- jQuery
- Bootstrap
數(shù)據(jù)庫
- MySQL
項目構(gòu)建工具
- Maven
版本控制工具
- Git + GITEE
二.數(shù)據(jù)庫的設(shè)計
1.創(chuàng)建數(shù)據(jù)庫
-- ----------------------------
-- 創(chuàng)建數(shù)據(jù)庫,并指定字符集
-- ----------------------------
drop database if exists forum_db;
create database forum_db character set utf8mb4 collate utf8mb4_general_ci;
2.創(chuàng)建表
-- 選擇數(shù)據(jù)庫
use forum_db;
set names utf8mb4;
set foreign_key_checks = 0;
1.用戶表
-- ----------------------------
-- 創(chuàng)建用戶表 for t_user
-- ----------------------------
drop table if exists `t_user`;
create table `t_user` (
`id` bigint(20) not null auto_increment comment '用戶編號,主鍵,自增',
`username` varchar(20) character set utf8mb4 collate utf8mb4_general_ci not null comment '用戶名,非空,唯一',
`password` varchar(32) character set utf8mb4 collate utf8mb4_general_ci not null comment '加密后的密碼',
`nickname` varchar(50) character set utf8mb4 collate utf8mb4_general_ci not null comment '昵稱,非空',
`phonenum` varchar(20) character set utf8mb4 collate utf8mb4_general_ci null default null comment '手機號',
`email` varchar(50) character set utf8mb4 collate utf8mb4_general_ci null default null comment '郵箱地址',
`gender` tinyint(4) not null default 2 comment '0女 1男 2保密,非空,默認2',
`salt` varchar(32) character set utf8mb4 collate utf8mb4_general_ci not null comment '為密碼加鹽,非空',
`avatarurl` varchar(255) character set utf8mb4 collate utf8mb4_general_ci null default null comment '用戶頭像url,默認系統(tǒng)圖片',
`articlecount` int(11) not null default 0 comment '發(fā)帖數(shù)量,非空,默認0',
`isadmin` tinyint(4) not null default 0 comment '是否管理員,0否 1是,默認0',
`remark` varchar(1000) character set utf8mb4 collate utf8mb4_general_ci null default null comment '備注,自我介紹',
`state` tinyint(4) not null default 0 comment '狀態(tài) 0 正常,1 禁言,默認0',
`deletestate` tinyint(4) not null default 0 comment '是否刪除 0否 1是,默認0',
`createtime` datetime not null comment '創(chuàng)建時間,精確到秒',
`updatetime` datetime not null comment '更新時間,精確到秒',
primary key (`id`) using btree,
unique index `user_username_uindex`(`username`) using btree
) engine = innodb auto_increment = 2 character set = utf8mb4 collate = utf8mb4_general_ci comment = '用戶表' row_format = dynamic;
set foreign_key_checks = 1;
2.文章表
-- ----------------------------
-- 創(chuàng)建帖子表 t_article
-- ----------------------------
drop table if exists `t_article`;
create table `t_article` (
`id` bigint(20) not null auto_increment comment '帖子編號,主鍵,自增',
`boardid` bigint(20) not null comment '關(guān)聯(lián)板塊編號,非空',
`userid` bigint(20) not null comment '發(fā)帖人,非空,關(guān)聯(lián)用戶編號',
`title` varchar(100) character set utf8mb4 collate utf8mb4_general_ci not null comment '標題,非空,最大長度100個字符',
`content` text character set utf8mb4 collate utf8mb4_general_ci not null comment '帖子正文,非空',
`visitcount` int(11) not null default 0 comment '訪問量,默認0',
`replycount` int(11) not null default 0 comment '回復數(shù)據(jù),默認0',
`likecount` int(11) not null default 0 comment '點贊數(shù),默認0',
`state` tinyint(4) not null default 0 comment '狀態(tài) 0正常 1 禁用,默認0',
`deletestate` tinyint(4) not null default 0 comment '是否刪除 0 否 1 是,默認0',
`createtime` datetime not null comment '創(chuàng)建時間,精確到秒,非空',
`updatetime` datetime not null comment '修改時間,精確到秒,非空',
primary key (`id`) using btree
) engine = innodb auto_increment = 1 character set = utf8mb4 collate = utf8mb4_general_ci comment = '帖子表' row_format = dynamic;
3.文章回復表
-- ----------------------------
-- 創(chuàng)建帖子回復表 t_article_reply
-- ----------------------------
drop table if exists `t_article_reply`;
create table `t_article_reply` (
`id` bigint(20) not null auto_increment comment '編號,主鍵,自增',
`articleid` bigint(20) not null comment '關(guān)聯(lián)帖子編號,非空',
`postuserid` bigint(20) not null comment '樓主用戶,關(guān)聯(lián)用戶編號,非空',
`replyid` bigint(20) null default null comment '關(guān)聯(lián)回復編號,支持樓中樓',
`replyuserid` bigint(20) null default null comment '樓主下的回復用戶編號,支持樓中樓',
`content` varchar(500) character set utf8mb4 collate utf8mb4_general_ci not null comment '回貼內(nèi)容,長度500個字符,非空',
`likecount` int(11) not null default 0 comment '點贊數(shù),默認0',
`state` tinyint(4) not null default 0 comment '狀態(tài) 0 正常,1禁用,默認0',
`deletestate` tinyint(4) not null default 0 comment '是否刪除 0否 1是,默認0',
`createtime` datetime not null comment '創(chuàng)建時間,精確到秒,非空',
`updatetime` datetime not null comment '更新時間,精確到秒,非空',
primary key (`id`) using btree
) engine = innodb auto_increment = 1 character set = utf8mb4 collate = utf8mb4_general_ci comment = '帖子回復表' row_format = dynamic;
4.版塊表
-- ----------------------------
-- 創(chuàng)建版塊表 t_board
-- ----------------------------
drop table if exists `t_board`;
create table `t_board` (
`id` bigint(20) not null auto_increment comment '版塊編號,主鍵,自增',
`name` varchar(50) character set utf8mb4 collate utf8mb4_general_ci not null comment '版塊名,非空',
`articlecount` int(11) not null default 0 comment '帖子數(shù)量,默認0',
`sort` int(11) not null default 0 comment '排序優(yōu)先級,升序,默認0,',
`state` tinyint(4) not null default 0 comment '狀態(tài),0 正常,1禁用,默認0',
`deletestate` tinyint(4) not null default 0 comment '是否刪除 0否,1是,默認0',
`createtime` datetime not null comment '創(chuàng)建時間,精確到秒,非空',
`updatetime` datetime not null comment '更新時間,精確到秒,非空',
primary key (`id`) using btree
) engine = innodb auto_increment = 1 character set = utf8mb4 collate = utf8mb4_general_ci comment = '版塊表' row_format = dynamic;
5.站內(nèi)信表
-- ----------------------------
-- 創(chuàng)建站內(nèi)信表 for t_message
-- ----------------------------
drop table if exists `t_message`;
create table `t_message` (
`id` bigint(20) not null auto_increment comment '站內(nèi)信編號,主鍵,自增',
`postuserid` bigint(20) not null comment '發(fā)送者,并聯(lián)用戶編號',
`receiveuserid` bigint(20) not null comment '接收者,并聯(lián)用戶編號',
`content` varchar(255) character set utf8mb4 collate utf8mb4_general_ci not null comment '內(nèi)容,非空,長度255個字符',
`state` tinyint(4) not null default 0 comment '狀態(tài) 0未讀 1已讀,默認0',
`deletestate` tinyint(4) not null default 0 comment '是否刪除 0否,1是,默認0',
`createtime` datetime not null comment '創(chuàng)建時間,精確到秒,非空',
`updatetime` datetime not null comment '更新時間,精確到秒,非空',
primary key (`id`) using btree
) engine = innodb auto_increment = 1 character set = utf8mb4 collate = utf8mb4_general_ci comment = '站內(nèi)信表' row_format = dynamic;
三.環(huán)境的搭建
1.創(chuàng)建Spring項目
?項目創(chuàng)建完成如下:
2.開啟熱部署
<!-- 熱部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
3.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.7.14</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.javastudy</groupId>
<artifactId>forum</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>forum</name>
<description>forum</description>
<properties>
<java.version>1.8</java.version>
<mybatis-starter.version>2.3.0</mybatis-starter.version>
<druid-starter.version>1.2.16</druid-starter.version>
<mysql-connector.version>5.1.49</mysql-connector.version>
</properties>
<dependencies>
<!-- spring web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-starter.version}</version>
</dependency>
<!-- 熱部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- spring boot test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- mysql連接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector.version}</version>
<scope>runtime</scope>
</dependency>
<!-- 阿里巴巴druid數(shù)據(jù)源,如果使用SpringBoot默認的數(shù)據(jù)源,刪除或注釋這個依賴即可 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid-starter.version}</version>
</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>
4.application.yml文件
#配置數(shù)據(jù)源
spring:
application:
name: "forum" # 項目名
output:
ansi:
enabled: always # 控制臺輸出彩色日志
datasource:
url: jdbc:mysql://127.0.0.1:13306/forum_db?characterEncoding=utf8&useSSL=false # 數(shù)據(jù)庫連接串
username: root # 數(shù)據(jù)庫用戶名字\
password: woaini520 # 數(shù)據(jù)庫密碼
driver-class-name: com.mysql.jdbc.Driver # 數(shù)據(jù)庫連接驅(qū)動
# 服務(wù)器配置
server:
port: 8082 # 指定端口號
# mybatis 相關(guān)配置,單獨配置,頂格寫
mybatis:
mapper-locations: classpath:mapper/**/*.xml # 指定 xxxMapper.xml的掃描路徑
configuration: # 配置打印 MyBatis 執(zhí)行的 SQL
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 日志信息
logging:
pattern:
dateformat: yyyy-MM-dd HH:mm:ss # 日期格式
file:
path: logs/
level:
root: info
四.工程搭建?
1.項目結(jié)構(gòu)
?2.生成類的映射文件
版本統(tǒng)一管理?
<mybatis-generator-plugin-version>1.4.1</mybatis-generator-plugin-version>
在 build --> plugins 標簽中加入如下配置?
<build>
<plugins>
<!-- mybatis 生成器插件 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>${mybatis-generator-plugin-version}</version>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<phase>deploy</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<!-- 相關(guān)配置 -->
<configuration>
<!-- 打開日志 -->
<verbose>true</verbose>
<!-- 允許覆蓋 -->
<overwrite>true</overwrite>
<!-- 配置文件路徑 -->
<configurationFile>
src/main/resources/mybatis/generatorConfig.xml
</configurationFile>
</configuration>
</plugin>
</plugins>
</build>
generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- 驅(qū)動包路徑,location中路徑替換成自己本地路徑 -->
<classPathEntry location="D:\java cave\Maven\repository\mysql\mysql-connector-java\5.1.49\mysql-connector-java-5.1.49.jar"/>
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- 禁用自動生成的注釋 -->
<commentGenerator>
<property name="suppressAllComments" value="true"/>
<property name="suppressDate" value="true"/>
</commentGenerator>
<!-- 連接配置 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:13306/forum_db?characterEncoding=utf8&useSSL=false"
userId="root"
password="woaini520">
</jdbcConnection>
<javaTypeResolver>
<!-- 小數(shù)統(tǒng)一轉(zhuǎn)為BigDecimal -->
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- 實體類生成位置 -->
<javaModelGenerator targetPackage="com.javastudy.forum.model" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- mapper.xml生成位置 -->
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- DAO類生成位置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.javastudy.forum.dao" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!-- 配置生成表與實例, 只需要修改表名tableName, 與對應類名domainObjectName 即可-->
<table tableName="t_article" domainObjectName="Article" enableSelectByExample="false"
enableDeleteByExample="false" enableDeleteByPrimaryKey="false" enableCountByExample="false"
enableUpdateByExample="false">
<!-- 類的屬性用數(shù)據(jù)庫中的真實字段名做為屬性名, 不指定這個屬性會自動轉(zhuǎn)換 _ 為駝峰命名規(guī)則-->
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="t_article_reply" domainObjectName="ArticleReply" enableSelectByExample="false"
enableDeleteByExample="false" enableDeleteByPrimaryKey="false" enableCountByExample="false"
enableUpdateByExample="false">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="t_board" domainObjectName="Board" enableSelectByExample="false" enableDeleteByExample="false"
enableDeleteByPrimaryKey="false" enableCountByExample="false" enableUpdateByExample="false">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="t_message" domainObjectName="Message" enableSelectByExample="false"
enableDeleteByExample="false" enableDeleteByPrimaryKey="false" enableCountByExample="false"
enableUpdateByExample="false">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="t_user" domainObjectName="User" enableSelectByExample="false" enableDeleteByExample="false"
enableDeleteByPrimaryKey="false" enableCountByExample="false" enableUpdateByExample="false">
<property name="useActualColumnNames" value="true"/>
</table>
</context>
</generatorConfiguration>
下面就是創(chuàng)建成功的樣子?
?
?
之后不要忘記給所有的Mapper加上@Mapper注解
?3. 配置掃描配置
@Configuration
//指定mybatis的掃描路徑
@MapperScan("com.javastudy.forum.dao")
public class MybatisConfig {
}
4.測試
插入一條數(shù)據(jù)?
INSERT INTO `forum_db`.`t_user` (`id`, `username`, `password`, `nickname`,
`gender`, `salt`, `avatarurl`, `articlecount`, `isadmin`, `state`,
`deletestate`, `createtime`, `updatetime`) VALUES (1, 'joyboy', '123456', '路飛',
2, '123', 'avatar.png',
0, 1, 0, 0, '2022-12-13 22:30:10', '2022-12-13 22:30:13');
@SpringBootTest
@Slf4j
class UserMapperTest {
@Resource
UserMapper userMapper;
@Resource
ObjectMapper objectMapper;
@Test
void insert() {
}
@Test
void insertSelective() {
}
@Test
void selectByPrimaryKey() throws JsonProcessingException {
User user = userMapper.selectByPrimaryKey(1L);
log.info(objectMapper.writeValueAsString(user));
}
@Test
void updateByPrimaryKeySelective() {
}
@Test
void updateByPrimaryKey() {
}
}
五.準備公共代碼編寫
1.編寫公共代碼
1.定義狀態(tài)碼
定義的狀態(tài)碼如下:
public enum ResultCode {
SUCCESS(0, "操作成功"),
FAILED(1000, "操作失敗"),
FAILED_UNAUTHORIZED(1001, "未授權(quán)"),
FAILED_PARAMS_VALIDATE(1002, "參數(shù)校驗失敗"),
FAILED_FORBIDDEN(1003, "禁止訪問"),
FAILED_CREATE(1004, "新增失敗"),
FAILED_NOT_EXISTS(1005, "資源不存在"),
FAILED_USER_EXISTS(1101, "用戶已存在"),
FAILED_USER_NOT_EXISTS(1102, "用戶不存在"),
FAILED_LOGIN(1103, "用戶名或密碼錯誤"),
FAILED_USER_BANNED(1104, "您已被禁言, 請聯(lián)系管理員, 并重新登錄."),
FAILED_TWO_PWD_NOT_SAME(1105, "兩次輸入的密碼不一致"),
ERROR_SERVICES(2000, "服務(wù)器內(nèi)部錯誤"),
ERROR_IS_NULL(2001, "IS NULL.");
long code;
String message;
ResultCode(long code, String message) {
this.code = code;
this.message = message;
}
@Override
public String toString() {
return "ResultCode{" +
"code=" + code +
", message='" + message + '\'' +
'}';
}
public long getCode() {
return code;
}
public String getMessage() {
return message;
}
}
2.定義返回結(jié)果?
public class AppResult<T> {
private Long code;
private String message;
private T data;
public AppResult() {
}
public AppResult(Long code, String message) {
this.code = code;
this.message = message;
}
public AppResult(Long code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
/**
* 成功
*/
public static AppResult success () {
return new AppResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage());
}
public static AppResult success (String message) {
return new AppResult(ResultCode.SUCCESS.getCode(), message);
}
public static <T> AppResult<T> success (String message, T data) {
return new AppResult<>(ResultCode.SUCCESS.getCode(), message, data);
}
public static <T> AppResult<T> success (T data) {
return new AppResult<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
}
/**
* 失敗
*/
public static AppResult failed () {
return new AppResult(ResultCode.FAILED.getCode(), ResultCode.FAILED.getMessage());
}
public static AppResult failed (String message) {
return new AppResult(ResultCode.FAILED.getCode(), message);
}
public static AppResult failed (ResultCode resultCode) {
return new AppResult(resultCode.getCode(), resultCode.getMessage());
}
public long getCode() {
return code;
}
public void setCode(long code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
3.自定義異常?
public class ApplicationException extends RuntimeException {
private AppResult errorResult;
public ApplicationException(AppResult errorResult) {
// 加入if 報錯 ,待查
super(errorResult.getMessage());
this.errorResult = errorResult;
}
public ApplicationException(String message) {
super(message);
}
public ApplicationException(String message, Throwable cause) {
super(message, cause);
}
public ApplicationException(Throwable cause) {
super(cause);
}
public AppResult getErrorResult() {
return errorResult;
}
}
4.全局異常處理?
@Slf4j // 日志
@ControllerAdvice
public class GlobalExceptionHandler {
// 以JSON的形式返回BOYD中的數(shù)據(jù)
@ResponseBody
// 指定要處理的異常
@ExceptionHandler(ApplicationException.class)
public AppResult handleApplicationException(ApplicationException e) {
// 打印異常信息, 上線生產(chǎn)之前要刪除這個打印方式
e.printStackTrace();
// 打印日志
log.error(e.getMessage());
// 判斷自定義的異常信息是否為空
if (e.getErrorResult() != null) {
// 返回異常類中記錄的狀態(tài)
return e.getErrorResult();
}
// 根據(jù)異常信息,封裝AppResult
return AppResult.failed(e.getMessage());
}
@ResponseBody
@ExceptionHandler(Exception.class)
public AppResult handleException(Exception e) {
// 打印異常信息, 上線生產(chǎn)之前要刪除這個打印方式
e.printStackTrace();
// 打印日志
log.error(e.getMessage());
// 異常信息的非空校驗
if (e.getMessage() == null) {
// 默認異常信息
return AppResult.failed(ResultCode.ERROR_SERVICES.getMessage());
}
// 根據(jù)異常信息,封裝AppResult
return AppResult.failed(e.getMessage());
}
}
2.Swagger自動生成
1.引入版本號
<springfox-boot-starter.version>3.0.0</springfox-boot-starter.version>
2.引入相關(guān)依賴
<!-- API?檔?成,基于swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${springfox-boot-starter.version}</version>
</dependency>
<!-- SpringBoot健康監(jiān)控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
3.編寫配置類
package com.javastudy.forum.config;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.web.*;
import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author Chooker
* @create 2023-08-09 12:45
*/
// 配置類
@Configuration
// 開啟Springfox-Swagger
@EnableOpenApi
public class SwaggerConfig {
/**
* Springfox-Swagger基本配置
*
* @return
*/
@Bean
public Docket createApi() {
Docket docket = new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.javastudy.forum.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}
// 配置API基本信息
private ApiInfo apiInfo() {
ApiInfo apiInfo = new ApiInfoBuilder()
.title("論壇系統(tǒng)API")
.description("論壇系統(tǒng)前后端分離API測試")
.contact(new Contact("Chooker", "https://blog.csdn.net/qq_64580912", "ak1474502128@gmail.com"))
.version("1.0")
.build();
return apiInfo;
}
/**
* 解決SpringBoot 6.0以上與Swagger 3.0.0 不兼容的問題
* 復制即可
**/
@Bean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
ServletEndpointsSupplier servletEndpointsSupplier,
ControllerEndpointsSupplier controllerEndpointsSupplier,
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties, Environment environment) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList();
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
String basePath = webEndpointProperties.getBasePath();
EndpointMapping endpointMapping = new EndpointMapping(basePath);
boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment,
basePath);
return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes,
corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath),
shouldRegisterLinksMapping, null);
}
private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment,
String basePath) {
return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath)
|| ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
}
}
4.application.yml 中添加配置?
spring:
mvc:
pathmatch:
matching-strategy: ANT_PATH_MATCHER #Springfox-Swagger兼容性配置
5.具體API
API常用注解
- @Api: 作用在Controller上,對控制器類的說明
- ? ? ?tags="說明該類的作用,可以在前臺界面上看到的注解
- @ApiModel: 作用在響應的類上,對返回響應數(shù)據(jù)的說明
- @ApiModelProerty:作用在類的屬性上,對屬性的說明
- @ApiOperation: 作用在具體方法上,對API接口的說明
- @ApiParam: 作用在方法中的每?個參數(shù)上,對參數(shù)的屬性進行說明?
?訪問地址:http://127.0.0.1:18080/swagger-ui/index.html? ? 出現(xiàn)以下頁面
?導入postman
?
?這樣就可以在postman中進行測試
六.注冊功能的實現(xiàn)
1.在Mapper.xml中編寫SQL語句
1.寫入操作(注冊部分的代碼)
已經(jīng)自動生成這部分的代碼,在UserMapper.xml文件中
<insert id="insertSelective" parameterType="com.javastudy.forum.model.User">
insert into t_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="username != null">
username,
</if>
<if test="password != null">
password,
</if>
<if test="nickname != null">
nickname,
</if>
<if test="phonenum != null">
phonenum,
</if>
<if test="email != null">
email,
</if>
<if test="gender != null">
gender,
</if>
<if test="salt != null">
salt,
</if>
<if test="avatarurl != null">
avatarurl,
</if>
<if test="articlecount != null">
articlecount,
</if>
<if test="isadmin != null">
isadmin,
</if>
<if test="remark != null">
remark,
</if>
<if test="state != null">
state,
</if>
<if test="deletestate != null">
deletestate,
</if>
<if test="createtime != null">
createtime,
</if>
<if test="updatetime != null">
updatetime,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=BIGINT},
</if>
<if test="username != null">
#{username,jdbcType=VARCHAR},
</if>
<if test="password != null">
#{password,jdbcType=VARCHAR},
</if>
<if test="nickname != null">
#{nickname,jdbcType=VARCHAR},
</if>
<if test="phonenum != null">
#{phonenum,jdbcType=VARCHAR},
</if>
<if test="email != null">
#{email,jdbcType=VARCHAR},
</if>
<if test="gender != null">
#{gender,jdbcType=TINYINT},
</if>
<if test="salt != null">
#{salt,jdbcType=VARCHAR},
</if>
<if test="avatarurl != null">
#{avatarurl,jdbcType=VARCHAR},
</if>
<if test="articlecount != null">
#{articlecount,jdbcType=INTEGER},
</if>
<if test="isadmin != null">
#{isadmin,jdbcType=TINYINT},
</if>
<if test="remark != null">
#{remark,jdbcType=VARCHAR},
</if>
<if test="state != null">
#{state,jdbcType=TINYINT},
</if>
<if test="deletestate != null">
#{deletestate,jdbcType=TINYINT},
</if>
<if test="createtime != null">
#{createtime,jdbcType=TIMESTAMP},
</if>
<if test="updatetime != null">
#{updatetime,jdbcType=TIMESTAMP},
</if>
</trim>
</insert>
2.根據(jù)用戶名查詢用戶信息
創(chuàng)建如圖的目錄結(jié)構(gòu),并在extension目錄下創(chuàng)建UserExtMapper.xml文件,里面編寫自動生成以外的代碼.?
可以看出?UserExtMapper.xml與UserMapper.xml是共用resultMap和Base_Column_List的
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.javastudy.forum.dao.UserMapper">
<!--
1. 注意namespace表示命名空間,要與 UserMapper.xml中的namespace相同
2. 統(tǒng)一用com.javastudy.forum.dao.UserMapper, 也就是UserMapper的完全限定名(包名+類名)
3. 不同的映射文件指定了相同的namespace后,定義的所有用id或name標識的結(jié)果集映射都可以在不同的文件中共享
-->
<!-- 根據(jù)用戶名查詢用戶信息-->
<select id="selectByUsername" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from t_user where username=#{username,jdbcType=VARCHAR};
</select>
</mapper>
2.在Mapper.java中定義方法
@Mapper
public interface UserMapper {
int insert(User row);
int insertSelective(User row);
User selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(User row);
int updateByPrimaryKey(User row);
/**
* 根據(jù)用戶名查詢用戶信息
* @param username
* @return
*/
User selectByUsername(String username);
}
3.定義Service接口
public interface IUserService {
User selectByUsername(String username);
int createNormalUser(User user);
}
4.實現(xiàn)Serivce接口
@Slf4j //日志
@Service
public class UserServiceImpl implements IUserService {
@Resource
UserMapper userMapper;
@Override
public User selectByUsername(String username) {
//非空校驗
if (StringUtils.isEmpty(username)) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
//根據(jù)用戶名查詢用戶信息
User user = userMapper.selectByUsername(username);
return user;
}
@Override
public int createNormalUser(User user) {
//非空校驗
if (user == null || StringUtils.isEmpty(user.getUsername()) || StringUtils.isEmpty(user.getNickname()) ||
StringUtils.isEmpty(user.getPassword()) || StringUtils.isEmpty(user.getSalt())) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
//校驗用戶名是否存在
User exitsUser = userMapper.selectByUsername(user.getUsername());
if (exitsUser != null) {
//打印日志
log.warn(ResultCode.FAILED_USER_EXISTS.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_USER_EXISTS));
}
//為性別設(shè)置默認值
if (user.getGender() != null) {
if (user.getGender() < 0 || user.getGender() > 2) {
user.setGender((byte) 2);
}
} else {
user.setGender((byte) 2);
}
//為發(fā)帖數(shù)設(shè)置默認值
user.setArticlecount(0);
//設(shè)置是否管理員
user.setIsadmin((byte) 0);
//設(shè)置狀態(tài)
user.setState((byte) 0);
//設(shè)置是否刪除
user.setDeletestate((byte) 0);
//設(shè)置時間
Date date = new Date();
user.setCreatetime(date);
user.setUpdatetime(date);
//寫入數(shù)據(jù)庫
int row = userMapper.insertSelective(user);
if (row != 1) {
//打印日志
log.warn(ResultCode.FAILED_CREATE.toString() + " 注冊用戶失敗,username:" + user.getUsername());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_CREATE));
}
return row;
}
}
5.單元測試
?加入SpringBootTest注解
1.測試selectByUsername接口
@SpringBootTest
@Slf4j
class UserServiceImplTest {
@Resource
private IUserService userService;
@Resource
private ObjectMapper objectMapper;
@Test
void selectByUsername() throws JsonProcessingException {
User user = userService.selectByUsername("joyboy");
log.info(objectMapper.writeValueAsString(user));
log.info("============================================");
User joyboy222 = userService.selectByUsername("");
log.info(objectMapper.writeValueAsString(joyboy222));
}
@Test
void createNormalUser() {
}
}
?符合預期,校驗完成
2.測試createNormalUser接口
@Test
void createNormalUser() {
User user = new User();
user.setUsername("testuser");
user.setPassword("123456");
user.setNickname("測試用戶");
user.setSalt("123456");
userService.createNormalUser(user);
log.info("注冊成功");
log.info("=================================");
user.setUsername("joyboy");
userService.createNormalUser(user);
log.info("注冊成功");
}
6.Controller實現(xiàn)方法外提供API接口
@RestController
@Slf4j
@RequestMapping("/user")
@Api(tags = "用戶接口")
public class UserController {
@Resource
IUserService userService;
@ApiOperation("用戶注冊")
@PostMapping("/register")
public AppResult register(@ApiParam("用戶名") @RequestParam("username") @NonNull String username,
@ApiParam("昵稱") @RequestParam("nickname") @NonNull String nickname,
@ApiParam("密碼") @RequestParam("password") @NonNull String password,
@ApiParam("確定密碼") @RequestParam("passwordRepeat") @NonNull String passwordRepeat) {
if (!password.equals(passwordRepeat)) {
return AppResult.failed(ResultCode.FAILED_TWO_PWD_NOT_SAME);
}
User user = new User();
user.setUsername(username);
user.setNickname(nickname);
//對密碼進行處理
String salt = UUIDUtils.UUID_32();
password = MD5Utils.md5Salt(password, salt);
user.setPassword(password);
user.setSalt(salt);
userService.createNormalUser(user);
return AppResult.success("注冊成功");
}
}
7.測試API接口
打開swagger進行測試
?
8.實現(xiàn)前端邏輯,完成前后端交互
可以在以下地址下載:?forum_static.zip
// 構(gòu)造數(shù)據(jù)
var postData = {
username: $("#username").val(),
nickname: $("#nickname").val(),
password: $("#password").val(),
passwordRepeat: $("#passwordRepeat").val(),
}
// 發(fā)送AJAX請求
// contentType = application/x-www-form-urlencoded
// 成功后跳轉(zhuǎn)到 sign-in.html
$.ajax({
type: 'post',
url: '/user/register',
//數(shù)據(jù)類型
contentType: "application/x-www-form-urlencoded",
//要提交的數(shù)據(jù)
data: postData,
//成功的回調(diào)函數(shù)
success: function (respData) {
if (respData.code == 0) {
location.assign("sign-in.html");
} else {
// 失敗(服務(wù)器處理業(yè)務(wù)邏輯失敗), 提示用戶錯誤信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
//http請求的失敗
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
});
七.登錄功能實現(xiàn)
1.在Mapper.xml中編寫SQL語句
之前已經(jīng)定義過了selectByName
2.在Mapper.java中定義方法
之前已經(jīng)定義過了selectByName
3.定義Service接口
User login(String username,String password);
4.實現(xiàn)Serivce接口
@Override
public User login(String username, String password) {
User user = selectByUsername(username);
if (user == null) {
//打印日志
log.warn(ResultCode.FAILED_USER_NOT_EXISTS.toString());
//拋出異常 ---防止惡意猜測
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_LOGIN));
}
//密碼校驗
boolean flag = MD5Utils.verifyOriginalAndCiphertext(password, user.getSalt(), user.getPassword());
if (!flag) {
//打印日志
log.warn("密碼輸入錯誤: username: "+username+ " password: " + password);
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_LOGIN));
}
return user;
}
5.單元測試
@Test
void login() {
User user = userService.login("joyboy", "123456");
log.info(user.toString());
User user2 = userService.login("user", "123456");
log.info(user2.toString());
}
?
符合預期?
6.Controller實現(xiàn)方法外提供API接口
@ApiOperation("用戶登錄")
@PostMapping("/login")
public AppResult register(HttpServletRequest httpServletRequest,
@ApiParam("用戶名") @RequestParam("username") @NonNull String username,
@ApiParam("密碼") @RequestParam("password") @NonNull String password) {
User user = userService.login(username, password);
//1.獲取session對象
HttpSession session = httpServletRequest.getSession(true);
//2.用戶信息保存到session中
session.setAttribute(AppConfig.SESSION_USER_KEY, user);
return AppResult.success("登陸成功");
}
7.測試API接口
8.實現(xiàn)前端邏輯,完成前后端交互
// 構(gòu)造數(shù)據(jù)
var postData = {
username: $("#username").val(),
password: $("#password").val(),
}
// 發(fā)送AJAX請求,成功后跳轉(zhuǎn)到index.html
$.ajax({
type: "post",
url: "/user/login",
//數(shù)據(jù)類型
contentType: "application/x-www-form-urlencoded",
data: postData,
success: function (respData) {
if (respData.code == 0) {
location.assign("/index.html");
} else {
// 失敗(服務(wù)器處理業(yè)務(wù)邏輯失敗), 提示用戶錯誤信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
?八.退出功能實現(xiàn)
退出功能直接在session中刪除用戶信息即可,因此不需要前面的五步
1.Controller實現(xiàn)方法外提供API接口
@ApiOperation("用戶注銷")
@GetMapping("/logout")
public AppResult logout(HttpServletRequest httpServletRequest) {
//獲取Session對象
HttpSession session = httpServletRequest.getSession(false);
if (session != null) {
session.invalidate();
}
//返回結(jié)果
return AppResult.success("注銷成功");
}
2.測試API接口
需要先登錄,才能退出
3.實現(xiàn)前端邏輯,完成前后端交互
// 成功后,跳轉(zhuǎn)到sign-in.html
$('#index_user_logout').click(function () {
$.ajax({
type: "get",
url: "/user/logout",
success: function (respData) {
if (respData.code == 0) {
location.assign("/sign-in.html");
} else {
// 失敗(服務(wù)器處理業(yè)務(wù)邏輯失敗), 提示用戶錯誤信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
});
九.獲取用戶信息實現(xiàn)
1.Controller實現(xiàn)方法外提供API接口
@ApiOperation("獲取用戶信息")
@GetMapping("/info")
public AppResult<User> getInfo(HttpServletRequest httpServletRequest) {
//獲取Session對象
HttpSession session = httpServletRequest.getSession(false);
if (session == null || session.getAttribute(AppConfig.SESSION_USER_KEY) == null) {
return AppResult.failed("用戶未登錄");
}
User user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
return AppResult.success(user);
}
2.測試API接口
?此時我們進行測試可以得到以上的數(shù)據(jù),但是此時還是有一定的弊端的
- 密碼和鹽不能在網(wǎng)絡(luò)上進行傳輸,會有安全隱患
- 有些為空的數(shù)據(jù)沒有必要傳輸
- 日期信息的傳輸格式不是常規(guī)的
接下來一一來解決以上的問題.
1. 解決密碼和鹽傳輸?shù)膯栴}
在實體類屬性上面加入@JsonIgnore注解?
@ApiModelProperty("密碼")
@JsonIgnore
private String password;
@ApiModelProperty("性別")
private Byte gender;
@ApiModelProperty("鹽")
@JsonIgnore
private String salt;
@ApiModelProperty("刪除狀態(tài)")
@JsonIgnore
private Byte deletestate;
2.解決空數(shù)據(jù)傳輸?shù)膯栴}和解決日期格式的問題
# JSON序列化配置
jackson:
date-format: yyyy-MM-dd HH:mm:ss # 日期格式
default-property-inclusion: NON_NULL # 不為null時序列化
此時符合條件?
登錄的時候,發(fā)現(xiàn)data信息不見了,因為data信息為null,所以沒有進行序列化,但是實際上code,message,data信息就算是null也是要進行傳輸?shù)?
加入以下注解可以解決這個問題.
@ApiModelProperty("狀態(tài)碼")
@JsonInclude(JsonInclude.Include.ALWAYS) // 任何情況下都參與JSON序列化
private Long code;
@ApiModelProperty("錯誤信息")
@JsonInclude(JsonInclude.Include.ALWAYS) // 任何情況下都參與JSON序列化
private String message;
@ApiModelProperty("返回的數(shù)據(jù)")
@JsonInclude(JsonInclude.Include.ALWAYS) // 任何情況下都參與JSON序列化
private T data;
?頭像同樣也是需要的
@ApiModelProperty("頭像地址")
@JsonInclude(JsonInclude.Include.ALWAYS)
private String avatarurl;
3.實現(xiàn)前端邏輯,完成前后端交互
//========================= 獲取用戶信息 =======================
// 成功后,手動設(shè)置用戶信息
// $('#index_nav_avatar').css('background-image', 'url(' + user.avatarUrl + ')');
$.ajax({
type: "get",
url: "/user/info",
success: function (respData) {
if (respData.code == 0) {
//成功
var user = respData.data;
if (!user.avatarurl) {//設(shè)默認的頭像
user.avatarurl = 'image/avatar01.jpeg';
}
$('#index_nav_nickname').html(user.nickname);
$('#index_nav_avatar').css('background-image', 'url(' + user.avatarurl + ')');
let subName = user.isAdmin == 1 ? '管理員' : '普通用戶';
$('#index_nav_name_sub').html(subName);
} else {
// 失敗(服務(wù)器處理業(yè)務(wù)邏輯失敗), 提示用戶錯誤信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
十.攔截器的實現(xiàn)
論壇中的大部分接口都需要在登錄的情況下進行訪問,因此我們需要一個攔截器對訪問頁面的時候?qū)ξ吹卿涍M行校驗
1.LoginInterceptor
未登錄跳轉(zhuǎn)的頁面我們在application.yml中定義,這樣為了以后的維護更加方便.
forum:
login:
url: sign-in.html
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Value("${forum.login.url}")
private String defaultUrl;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute(AppConfig.SESSION_USER_KEY) != null) {
return true;
}
//采用重定向
response.sendRedirect(defaultUrl);
return true;
}
}
2.AppInterceptorConfigurer
@Configuration // 把當前配置類加入到Spring中
public class AppInterceptorConfigurer implements WebMvcConfigurer {
@Resource
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/sign-in.html") // 排除登錄HTML
.excludePathPatterns("/sign-up.html") // 排除注冊HTML
.excludePathPatterns("/user/login") // 排除登錄api接口
.excludePathPatterns("/user/register") // 排除注冊api接口
.excludePathPatterns("/user/logout") // 排除退出api接口
.excludePathPatterns("/swagger*/**") // 排除登錄swagger下所有
.excludePathPatterns("/v3*/**") // 排除登錄v3下所有,與swagger相關(guān)
.excludePathPatterns("/dist/**") // 排除所有靜態(tài)文件
.excludePathPatterns("/image/**")
.excludePathPatterns("/**.ico")
.excludePathPatterns("/js/**");
}
}
十一.獲取用戶信息實現(xiàn)2?
當我們進入一個帖子的時候,我們點擊發(fā)帖的用戶,可以看到用戶的信息,此時前端給我們一個id,我們需要從數(shù)據(jù)庫中根據(jù)id插敘到用戶的信息.
1.在Mapper.xml中編寫SQL語句
已經(jīng)自動生成了
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from t_user
where id = #{id,jdbcType=BIGINT}
</select>
2.在Mapper.java中定義方法
User selectByPrimaryKey(Long id);
3.定義Service接口
/**
* 根據(jù)id查詢用戶的信息
* @param id
* @return
*/
User selectById(Long id);
4.實現(xiàn)Serivce接口
public User selectById(Long id) {
if(id<0||id==null){
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
User user = userMapper.selectByPrimaryKey(id);
return user;
}
5.單元測試
@Test
void selectById() {
User user = userService.selectById(1L);
log.info(user.toString());
User user2 = userService.selectById(2L);
log.info(user2.toString());
}
6.Controller實現(xiàn)方法外提供API接口
@ApiOperation("獲取用戶信息")
@GetMapping("/info")
public AppResult<User> getInfo(HttpServletRequest httpServletRequest,
@ApiParam("用戶id") @RequestParam(value = "id", required = false) Long id) {
User user;
if (id == null) {
//獲取Session對象
HttpSession session = httpServletRequest.getSession(false);
user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
} else {
user = userService.selectById(id);
}
return AppResult.success(user);
}
7.測試API接口
測試成功.
十二.獲取版塊信息實現(xiàn)
先向數(shù)據(jù)庫中插入一些板塊的信息.
-- 寫入版塊信息數(shù)據(jù)
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`, `deleteState`, `createTime`, `updateTime`) VALUES (1, 'Java', 0, 1, 0, 0, '2023-01-14 19:02:18', '2023-01-14 19:02:18');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`, `deleteState`, `createTime`, `updateTime`) VALUES (2, 'C++', 0, 2, 0, 0, '2023-01-14 19:02:41', '2023-01-14 19:02:41');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`, `deleteState`, `createTime`, `updateTime`) VALUES (3, '前端技術(shù)', 0, 3, 0, 0, '2023-01-14 19:02:52', '2023-01-14 19:02:52');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`, `deleteState`, `createTime`, `updateTime`) VALUES (4, 'MySQL', 0, 4, 0, 0, '2023-01-14 19:03:02', '2023-01-14 19:03:02');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`, `deleteState`, `createTime`, `updateTime`) VALUES (5, '面試寶典', 0, 5, 0, 0, '2023-01-14 19:03:24', '2023-01-14 19:03:24');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`, `deleteState`, `createTime`, `updateTime`) VALUES (6, '經(jīng)驗分享', 0, 6, 0, 0, '2023-01-14 19:03:48', '2023-01-14 19:03:48');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`, `deleteState`, `createTime`, `updateTime`) VALUES (7, '招聘信息', 0, 7, 0, 0, '2023-01-25 21:25:33', '2023-01-25 21:25:33');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`, `deleteState`, `createTime`, `updateTime`) VALUES (8, '福利待遇', 0, 8, 0, 0, '2023-01-25 21:25:58', '2023-01-25 21:25:58');
INSERT INTO `t_board` (`id`, `name`, `articleCount`, `sort`, `state`, `deleteState`, `createTime`, `updateTime`) VALUES (9, '灌水區(qū)', 0, 9, 0, 0, '2023-01-25 21:26:12', '2023-01-25 21:26:12');
1.在Mapper.xml中編寫SQL語句
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.javastudy.forum.dao.BoardMapper">
<select id="selectByNum" parameterType="java.lang.Integer" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_board
WHERE state=0 AND deleteState=0
ORDER BY sort ASC
LIMIT 0,#{num,jdbcType=INTEGER};
</select>
</mapper>
2.在Mapper.java中定義方法
List<Board> selectByNum(@Param("num") Integer num);
3.定義Service接口
public interface IBoardService {
List<Board> selectByNum(Integer num);
}
4.實現(xiàn)Serivce接口
@Service
@Slf4j
public class BoardServiceImpl implements IBoardService {
@Resource
private BoardMapper boardMapper;
@Override
public List<Board> selectByNum(Integer num) {
if (num < 0 || num == null) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
List<Board> boards = boardMapper.selectByNum(num);
return boards;
}
}
5.單元測試
@SpringBootTest
@Slf4j
class BoardServiceImplTest {
@Resource
BoardServiceImpl boardService;
@Test
void selectByNum() {
List<Board> boards = boardService.selectByNum(9);
log.info(boards.toString());
}
}
6.Controller實現(xiàn)方法外提供API接口
@RestController
@Slf4j
@RequestMapping("/board")
@Api(tags = "板塊接口")
public class BoardController {
@Value("${forum.index.board-num}")
Integer num;
@Resource
private IBoardService boardService;
@GetMapping("/topList")
@ApiOperation("獲取板塊列表")
public AppResult<List<Board>> topList() {
List<Board> boards = boardService.selectByNum(num);
return AppResult.success(boards);
}
}
7.測試API接口
8.實現(xiàn)前端邏輯,完成前后端交互
// ========================= 獲取版塊信息 =======================
// 成功后,調(diào)用buildTopBoard()方法,構(gòu)建版塊列表
$.ajax({
type: "get",
url: "/board/topList",
success: function (respData) {
if (respData.code == 0) {
buildTopBoard(respData.data);
} else {
// 失敗(服務(wù)器處理業(yè)務(wù)邏輯失敗), 提示用戶錯誤信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
十三.獲得帖子信息實現(xiàn)?
下面是獲得所有帖子信息的實現(xiàn)
1.在Mapper.xml中編寫SQL語句
創(chuàng)建ArticleExtMapper.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.javastudy.forum.dao.ArticleMapper">
<resultMap id="AllInfoResultMap" type="com.javastudy.forum.model.Article" extends="ResultMapWithBLOBs">
<association property="user" resultMap="com.javastudy.forum.dao.UserMapper.BaseResultMap" columnPrefix="u_"/>
</resultMap>
<!-- 查詢所有的帖子集合-->
<select id="selectAll" resultMap="AllInfoResultMap">
select u.id as u_id,
u.nickname as u_nickname,
u.gender as u_gender,
u.avatarUrl as u_avatarUrl,
a.id,
a.boardId,
a.userId,
a.title,
a.visitCount,
a.replyCount,
a.likeCount,
a.state,
a.deleteState,
a.createTime,
a.updateTime
from t_article as a,
t_user as u
where a.userId = u.id
and a.deleteState = 0
order by a.createTime DESC
</select>
</mapper>
2.在Mapper.java中定義方法
List<Article> selectAll();
3.定義Service接口
public interface IArticleService {
List<Article> selectAll();
}
4.實現(xiàn)Serivce接口
@Slf4j
@Service
public class ArticleService implements IArticleService {
@Resource
ArticleMapper articleMapper;
@Override
public List<Article> selectAll() {
List<Article> articles = articleMapper.selectAll();
return articles;
}
}
5.單元測試
@SpringBootTest
@Slf4j
class ArticleServiceImplTest {
@Resource
IArticleService articleService;
@Test
void selectAll() {
List<Article> articles = articleService.selectAll();
log.info(articles.toString());
}
}
6.Controller實現(xiàn)方法外提供API接口
@RestController
@Slf4j
@RequestMapping("/article")
@Api(tags = "帖子接口")
public class ArticleController {
@Resource
private IArticleService articleService;
@GetMapping("/getAllByBoardId")
@ApiOperation("獲得所有的帖子")
public AppResult<List<Article>> getAllByBoardId() {
List<Article> articles = articleService.selectAll();
if (articles == null) {
articles = new ArrayList<>();
}
return AppResult.success(articles);
}
}
7.測試API接口
8.實現(xiàn)前端邏輯,完成前后端交互
// ========================= 獲取帖子列表 =======================
// 成功后,調(diào)用listBuildArticleList()方法,構(gòu)建帖子列表
$.ajax({
type: "get",
url: "/article/getAllByBoardId",
success: function (respData) {
if (respData.code == 0) {
listBuildArticleList(respData.data);
} else {
// 失敗(服務(wù)器處理業(yè)務(wù)邏輯失敗), 提示用戶錯誤信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
十四.獲得指定帖子信息實現(xiàn)??
1.在Mapper.xml中編寫SQL語句
<!-- 查詢所有的帖子集合-->
<select id="selectByBoardId" resultMap="AllInfoResultMap">
select
u.id as u_id,
u.nickname as u_nickname,
u.gender as u_gender,
u.avatarUrl as u_avatarUrl,
a.id,
a.boardId,
a.userId,
a.title,
a.visitCount,
a.replyCount,
a.likeCount,
a.state,
a.deleteState,
a.createTime,
a.updateTime
from t_article as a, t_user as u
where a.userId = u.id
and a.deleteState = 0
and a.boardId = #{boardId,jdbcType=BIGINT}
order by a.createTime DESC;
</select>
2.在Mapper.java中定義方法
List<Article> selectByBoardId();
3.定義Service接口
List<Article> selectByBoardId(Long boardId);
4.實現(xiàn)Serivce接口
@Override
public List<Article> selectByBoardId(Long boardId) {
if(boardId==null){
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
List<Article> articles = articleMapper.selectByBoardId(boardId);
return articles;
}
5.單元測試
@Test
void selectByBoardId() {
List<Article> articles = articleService.selectByBoardId(1L);
log.info(articles.toString());
}
6.Controller實現(xiàn)方法外提供API接口
@GetMapping("/getAllByBoardId")
@ApiOperation("獲得所有的帖子")
public AppResult<List<Article>> getAllByBoardId(@ApiParam("板塊id")
@RequestParam(value = "boardId", required = false) Long boardId) {
List<Article> articles;
if (boardId == null) {
articles = articleService.selectAll();
} else {
articles = articleService.selectByBoardId(boardId);
}
if (articles == null) {
articles = new ArrayList<>();
}
return AppResult.success(articles);
}
7.測試API接口
8.實現(xiàn)前端邏輯,完成前后端交互
// ========================= 獲取帖子列表 =======================
// 成功后,調(diào)用listBuildArticleList()方法,構(gòu)建帖子列表
$.ajax({
type: "get",
url: "/article/getAllByBoardId"+queryString,
success: function (respData) {
if (respData.code == 0) {
listBuildArticleList(respData.data);
} else {
// 失敗(服務(wù)器處理業(yè)務(wù)邏輯失敗), 提示用戶錯誤信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
十五.根據(jù)Id獲取板塊信息
1.在Mapper.xml中編寫SQL語句
已經(jīng)自動生成
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from t_board
where id = #{id,jdbcType=BIGINT}
</select>
2.在Mapper.java中定義方法
已經(jīng)自動生成
Board selectByPrimaryKey(Long id);
3.定義Service接口
Board selectById(Long id);
4.實現(xiàn)Serivce接口
@Override
public Board selectById(Long id) {
if (id < 0 || id == null) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
Board board = boardMapper.selectByPrimaryKey(id);
return board;
}
5.單元測試
@Test
void selectById() {
Board board = boardService.selectById(1L);
log.info(board.toString());
}
6.Controller實現(xiàn)方法外提供API接口
@GetMapping("/getById")
@ApiOperation("根據(jù)id獲取板塊信息")
public AppResult<Board> getById(@ApiParam("板塊id") @RequestParam("id") Long id) {
Board board = boardService.selectById(id);
return AppResult.success(board);
}
7.測試API接口
8.實現(xiàn)前端邏輯,完成前后端交互
// ========================= 獲取版塊信息 =======================
//
function getBoardInfo(boardId) {
if (!boardId) {
return;
}
// 發(fā)送請求, 成功后,顯示版塊相關(guān)信息
$.ajax({
type: "get",
url: "/board/getById?id=" + boardId,
success: function (respData) {
if (respData.code == 0) {
$('#article_list_board_title').html(respData.data.name);
$('#article_list_count_board').html('帖子數(shù)量: ' + respData.data.articleCount);
$('#article_list_count_board').show();
} else {
// 失敗(服務(wù)器處理業(yè)務(wù)邏輯失敗), 提示用戶錯誤信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
}
十六.發(fā)布新帖操作
思考一下發(fā)布帖子會經(jīng)歷什么樣的操作.首先最直觀的就是新增了一篇文章,涉及到的是文章表(article),其次需要更新用戶的文章數(shù),涉及用戶表(user),最后需要更新板塊的帖子數(shù),涉及到板塊表(board),并且這三個操作要么都成功,要么都失敗,因此我們需要給這三個操作加上事務(wù).
1.在Mapper.xml中編寫SQL語句
更新文章數(shù)也有兩種方式
第一種:重新定義一個update的語句將articleCount字段加一.
<update id="updateArticleCount" parameterType="java.lang.Long">
UPDATE t_user
SET articleCount=articleCount + 1
WHERE id = #{id,jdbcType=BIGINT};
</update>
第二種:使用生成的updateByPrimaryKeySelective的語句,在java程序查詢到對應的用戶信息,并將文章數(shù)加一之后調(diào)用更新的sql.(具體下面演示,下面采用這種方式)
1.用戶表更新文章數(shù)
使用生成的updateByPrimaryKeySelective
2.板塊表更新文章數(shù)
同樣的使用生成的updateByPrimaryKeySelective
3.文章表添加文章
使用生成的insertSelective
2.在Mapper.java中定義方法
已經(jīng)定義過了
3.定義Service接口
1.用戶表
void addOneArticleCountById(Long id);
2.板塊表
void addOneArticleCountById(Long id);
3.文章表
@Transactional
void create (Article article);
4.實現(xiàn)Serivce接口
1.用戶表
?
@Override
public void addOneArticleCountById(Long id) {
if (id < 0 || id == null) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
// 查詢現(xiàn)有的用戶信息
User user = selectById(id);
// 校驗用戶是否為空
if (user == null) {
// 打印日志
log.warn(ResultCode.FAILED_USER_NOT_EXISTS.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_USER_NOT_EXISTS));
}
user.setArticlecount(user.getArticlecount() + 1);
user.setUpdatetime(new Date());
int row = userMapper.updateByPrimaryKeySelective(user);
if (row != 1) {
// 打印日志
log.warn(ResultCode.ERROR_SERVICES.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
}
}
?
2.板塊表
@Override
public void addOneArticleCountById(Long id) {
if (id < 0 || id == null) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
Board board = selectById(id);
if (board == null) {
//打印日志
log.warn(ResultCode.FAILED_BOARD_EXISTS.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_BOARD_EXISTS));
}
board.setArticleCount(board.getArticleCount() + 1);
board.setUpdateTime(new Date());
int row = boardMapper.updateByPrimaryKeySelective(board);
if (row != 1) {
// 打印日志
log.warn(ResultCode.ERROR_SERVICES.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
}
}
3.文章表
@Override
public void create(Article article) {
if (article == null || article.getBoardId() == null || article.getUserId() == null ||
StringUtils.isEmpty(article.getTitle()) || StringUtils.isEmpty(article.getContent())) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
// 設(shè)置默認值
article.setVisitCount(0); // 訪問數(shù)量
article.setReplyCount(0); // 回復數(shù)量
article.setLikeCount(0); // 點贊數(shù)量
article.setState((byte) 0); // 狀態(tài)
article.setDeleteState((byte) 0); // 是否刪除
Date date = new Date();
article.setCreateTime(date); // 創(chuàng)建時間
article.setUpdateTime(date); // 更新時間
int row = articleMapper.insertSelective(article);
if (row != 1) {
// 打印日志
log.warn(ResultCode.FAILED_CREATE.toString() + " 新增帖子失敗");
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_CREATE));
}
userService.addOneArticleCountById(article.getUserId());
boardService.addOneArticleCountById(article.getBoardId());
// 打印日志 ---如何獲取文章的id,可以在mapper.xml進行設(shè)置
log.info(ResultCode.SUCCESS.toString() + " 帖子發(fā)布成功, articleId = " + article.getId());
}
為了插入文章之后獲取到文章的id,我們可以進行如下的操作.?
5.單元測試
1.用戶表
@Test
void addOneArticleCountById() {
userService.addOneArticleCountById(1L);
log.info("更新成功");
}
更新前?
?更新后?
2.板塊表
@Test
void addOneArticleCountById() {
boardService.addOneArticleCountById(9L);
log.info("更新成功");
}
更新前?
?更新后
3.文章表
@Test
void create() {
Article article = new Article();
article.setTitle("8.18測試的文章標題");
article.setContent("8.18測試的文章內(nèi)容");
article.setBoardId(9L);
article.setUserId(2L);
articleService.create(article);
log.info("創(chuàng)建成功");
}
?相應的文章數(shù)目也發(fā)生了改變
6.Controller實現(xiàn)方法外提供API接口
@ApiOperation("發(fā)布帖子")
@PostMapping("/create")
public AppResult create(@ApiParam("文章標題") @RequestParam("title") @NonNull String title,
@ApiParam("文章內(nèi)容") @RequestParam("content") @NonNull String content,
@ApiParam("板塊id") @RequestParam("boardId") @NonNull Long boardId,
HttpServletRequest httpServletRequest) {
//獲取用戶id
HttpSession session = httpServletRequest.getSession(false);
User user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
// 校驗用戶狀態(tài)
if (user.getState() == 1) {
// 用戶已禁言, 返回提示
return AppResult.failed(ResultCode.FAILED_USER_BANNED);
}
//設(shè)置文章信息
Article article = new Article();
article.setUserId(user.getId());
article.setTitle(title);
article.setBoardId(boardId);
article.setContent(content);
articleService.create(article);
return AppResult.success("新增文章成功").
}
7.測試API接口
8.實現(xiàn)前端邏輯,完成前后端交互
// 構(gòu)造帖子對象
var postData = {
title: titleEl.val(),
content: contentEl.val(),
boardId: boardIdEl.val(),
}
// 提交, 成功后調(diào)用changeNavActive($('#nav_board_index'));回到首頁并加載帖子列表
// contentType: 'application/x-www-form-urlencoded'
$.ajax({
type: "post",
url: "/article/create",
data: postData,
contentType: 'application/x-www-form-urlencoded',
success: function (respData) {
if (respData.code == 0) {
// 提示
$.toast({
heading: '成功',
text: respData.message,
icon: 'success'
});
// 成功后跳轉(zhuǎn)到首頁
changeNavActive($('#nav_board_index'));
} else {
// 失敗(服務(wù)器處理業(yè)務(wù)邏輯失敗), 提示用戶錯誤信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
十七.獲取文章詳情
獲取文章詳情的時候也需要獲取相關(guān)用戶的信息,板塊的信息
1.在Mapper.xml中編寫SQL語句
<mapper namespace="com.javastudy.forum.dao.ArticleMapper">
<resultMap id="AllInfoResultMap" type="com.javastudy.forum.model.Article" extends="ResultMapWithBLOBs">
<!-- 關(guān)聯(lián)User對象 -->
<association property="user" resultMap="com.javastudy.forum.dao.UserMapper.BaseResultMap" columnPrefix="u_"/>
<!-- 關(guān)聯(lián)Board對象-->
<association property="board" resultMap="com.javastudy.forum.dao.BoardMapper.BaseResultMap" columnPrefix="b_"/>
</resultMap>
<!-- 根據(jù)帖子Id查詢帖子詳情 -->
<select id="selectById" resultMap="AllInfoResultMap" parameterType="java.lang.Long">
select u.id as u_id,
u.nickname as u_nickname,
u.gender as u_gender,
u.avatarUrl as u_avatarUrl,
b.id as b_id,
b.name as b_name,
a.id,
a.boardId,
a.userId,
a.title,
a.visitCount,
a.replyCount,
a.likeCount,
a.state,
a.deleteState,
a.createTime,
a.updateTime,
a.content
from t_article as a,
t_user as u,
t_board b
where a.userId = u.id
and a.boardId = b.id
and a.id = #{id,jdbcType=BIGINT}
and a.deleteState = 0
</select>
</mapper>
2.在Mapper.java中定義方法
Article selectById(Long id);
3.定義Service接口
Article selectById(Long id);
4.實現(xiàn)Serivce接口
@Override
public Article selectById(Long id) {
if (id == null) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
Article article = articleMapper.selectById(id);
return article;
}
5.單元測試
@Test
void selectById() {
Article article = articleService.selectById(1L);
log.info(article.toString());
}
6.Controller實現(xiàn)方法外提供API接口
@ApiOperation("通過文章id獲取文章的詳細信息")
@GetMapping("/getById")
public AppResult getById(@ApiParam("文章id") @RequestParam("id") @NonNull Long id) {
Article article = articleService.selectById(id);
if (article == null) {
// 返回提示
return AppResult.failed("帖子不存在");
}
return AppResult.success(article);
}
7.測試API接口
8.實現(xiàn)前端邏輯,完成前后端交互
// ===================== 請求帖子詳情 =====================
$.ajax({
type: "get",
url: "/article/getById?id=" + currentArticle.id,
success: function (respData) {
if (respData.code == 0) {
initArticleDetails(respData.data);
} else {
// 失敗(服務(wù)器處理業(yè)務(wù)邏輯失敗), 提示用戶錯誤信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
十八.文章訪問數(shù)量的增加
1.在Mapper.xml中編寫SQL語句
<update id="updateVisitCountById" parameterType="java.lang.Long">
update t_article
set visitCount=visitCount + 1
where id = #{id,jdbcType=BIGINT}
</update>
2.在Mapper.java中定義方法
int updateVisitCountById(Long id);
3.定義Service接口
Integer updateVisitCountById(Long id);
4.實現(xiàn)Serivce接口
public Integer updateVisitCountById(Long id) {
if (id == null || id < 0) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
int row = articleMapper.updateVisitCountById(id);
if (row != 1) {
// 打印日志
log.warn(ResultCode.ERROR_SERVICES.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
}
return null;
}
5.單元測試
@Test
void updateVisitCountById() {
articleService.updateVisitCountById(1L);
log.info("更新成功");
}
更新前?
?更新后
6.Controller實現(xiàn)方法外提供API接口
更新查詢操作即可
@ApiOperation("通過文章id獲取文章的詳細信息")
@GetMapping("/getById")
public AppResult getById(@ApiParam("文章id") @RequestParam("id") @NonNull Long id) {
Article article = articleService.selectById(id);
if (article == null) {
// 返回提示
return AppResult.failed("帖子不存在");
}
articleService.updateVisitCountById(id);
article.setVisitCount(article.getVisitCount() + 1);
return AppResult.success(article);
}
十九.編輯文章操作
在編輯文章之前,我們需要在文章中增加一個屬性,來表示這篇文章是不是屬于作者的,前端進行判斷來選擇展示修改和刪除文章的按鈕
@ApiOperation("通過文章id獲取文章的詳細信息")
@GetMapping("/getById")
public AppResult getById(@ApiParam("文章id") @RequestParam("id") @NonNull Long id,
HttpServletRequest httpServletRequest) {
Article article = articleService.selectById(id);
if (article == null) {
// 返回提示
return AppResult.failed("帖子不存在");
}
articleService.updateVisitCountById(id);
article.setVisitCount(article.getVisitCount() + 1);
HttpSession session = httpServletRequest.getSession(false);
User user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
if (user.getId() == article.getUserId()) {
article.setOwn(true);
}
return AppResult.success(article);
}
編輯文章的時候,我們需要先查詢到需要修改文章的信息,然后再上一次內(nèi)容的基礎(chǔ)上進行修改,可以直接調(diào)用getById接口
article_edit.html頁面
// ========================== 獲取帖子詳情 ==========================
// 成功后,設(shè)置ID,版塊名,標題,并初始編輯區(qū)同時設(shè)置正文initEditor(edit_article.content);
$.ajax({
type: "get",
url: "/article/getById?id=" + currentArticle.id,
success: function (respData) {
var article = respData.data;
if (respData.code == 0) {
$("#edit_article_id").val(article.id);
$("#edit_article_title").val(article.title);
$('#edit_article_board_name').html(article.board.name);
initEditor(article.content);
} else {
// 失敗(服務(wù)器處理業(yè)務(wù)邏輯失敗), 提示用戶錯誤信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
1.在Mapper.xml中編寫SQL語句
之前已經(jīng)自動生成了
2.在Mapper.java中定義方法
之前已經(jīng)自動生成了
3.定義Service接口
void modify(Long id, String title, String content);
4.實現(xiàn)Serivce接口
@Override
public void modify(Long id, String title, String content) {
if (id == null || id < 0 || StringUtils.isEmpty(title) || StringUtils.isEmpty(content)) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
Article article = new Article();
article.setTitle(title);
article.setContent(content);
article.setId(id);
article.setUpdateTime(new Date()); //設(shè)置更新時間
int row = articleMapper.updateByPrimaryKeySelective(article);
if (row != 1) {
// 打印日志
log.warn(ResultCode.ERROR_SERVICES.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
}
}
5.單元測試
@Test
void modify() {
articleService.modify(1L, "測試數(shù)據(jù)-標題1.1","測試數(shù)據(jù)-內(nèi)容2.0");
log.info("更新成功");
}
?更新前
?更新后
6.Controller實現(xiàn)方法外提供API接口
@ApiOperation("更新帖子")
@PostMapping("/modify")
public AppResult modify(@ApiParam("文章標題") @RequestParam("title") @NonNull String title,
@ApiParam("文章內(nèi)容") @RequestParam("content") @NonNull String content,
@ApiParam("文章id") @RequestParam("id") @NonNull Long id,
HttpServletRequest httpServletRequest) {
// 從session中獲取當前登錄的用戶
HttpSession session = httpServletRequest.getSession(false);
User user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
// 校驗用戶信息
if (user.getState() == 1) {
// 返回提示信息
return AppResult.failed(ResultCode.FAILED_USER_BANNED);
}
// 查詢帖子信息
Article article = articleService.selectById(id);
// 校驗帖子是否存在
if (article == null) {
// 返回提示信息
return AppResult.failed(ResultCode.FAILED_ARTICLE_NOT_EXISTS);
}
// 判斷當前登錄用戶是不是作者
if (article.getUserId() != user.getId()) {
// 返回提示信息
return AppResult.failed("您不是帖子的作者,無權(quán)修改.");
}
// 校驗帖子狀態(tài)
if (article.getState() == 1) {
// 返回提示信息
return AppResult.failed("帖子狀態(tài)異常, 請聯(lián)系管理員");
}
articleService.modify(id, title, content);
return AppResult.success("更新成功");
}
7.測試API接口
更新前?
?更新后
8.實現(xiàn)前端邏輯,完成前后端交互
// 構(gòu)造修改對象
var postData = {
id: articleIdEl.val(),
title: articleTitleEl.val(),
content: articleContentEl.val()
}
// 發(fā)送修改請求, 成功后跳轉(zhuǎn)至首頁changeNavActive($('#nav_board_index'));
$.ajax({
type: "post",
url: "/article/modify",
contentType: 'application/x-www-form-urlencoded',
data: postData,
// 成功回調(diào)
success: function (respData) {
if (respData.code == 0) {
// 成功后提示消息
$.toast({
heading: '成功',
text: respData.message,
icon: 'success'
});
// 跳轉(zhuǎn)到首面
changeNavActive($('#nav_board_index'));
} else {
// 失敗
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
// 失敗 (HTTP)
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
二十.刪除文章操作
刪除文章的時候需要做以下三個操作
1.需要將刪除文章的deleteState置為1(article表)
2.需要將相應板塊的文章數(shù)減一(board表)
3.需要將相應用戶的文章數(shù)減一(user表)
1.在Mapper.xml中編寫SQL語句
1.user表
直接使用生成的updateByPrimaryKeySelective
2.board表
直接使用生成的updateByPrimaryKeySelective
3.article表
直接使用生成的updateByPrimaryKeySelective
2.在Mapper.java中定義方法
1.user表
直接使用生成的updateByPrimaryKeySelective
2.board表
直接使用生成的updateByPrimaryKeySelective
3.article表
直接使用生成的updateByPrimaryKeySelective
3.定義Service接口
1.user表
void subOneArticleCountById(Long id);
2.board表
void subOneArticleCountById(Long id);
3.article表
@Transactional
void delete(Long id);
4.實現(xiàn)Serivce接口
1.user表
@Override
public void subOneArticleCountById(Long id) {
if (id < 0 || id == null) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
// 查詢現(xiàn)有的用戶信息
User user = selectById(id);
// 校驗用戶是否為空
if (user == null) {
// 打印日志
log.warn(ResultCode.FAILED_USER_NOT_EXISTS.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_USER_NOT_EXISTS));
}
user.setArticlecount(user.getArticlecount() - 1);
// 判斷減1之后,用戶的發(fā)帖數(shù)是否小于0
if (user.getArticlecount() < 0) {
// 如果小于0,則設(shè)置為0
user.setArticlecount(0);
}
user.setUpdatetime(new Date());
int row = userMapper.updateByPrimaryKeySelective(user);
if (row != 1) {
// 打印日志
log.warn(ResultCode.ERROR_SERVICES.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
}
}
2.board表
@Override
public void subOneArticleCountById(Long id) {
if (id < 0 || id == null) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
Board board = selectById(id);
if (board == null) {
//打印日志
log.warn(ResultCode.FAILED_BOARD_EXISTS.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_BOARD_EXISTS));
}
board.setArticleCount(board.getArticleCount() - 1);
if (board.getArticleCount() < 0) {
board.setArticleCount(0);
}
board.setUpdateTime(new Date());
int row = boardMapper.updateByPrimaryKeySelective(board);
if (row != 1) {
// 打印日志
log.warn(ResultCode.ERROR_SERVICES.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
}
}
3.article表
public void delete(Long id) {
if (id == null || id < 0) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
Article article = selectById(id);
if (article == null || article.getUserId() == null || article.getBoardId() == null) {
//打印日志
log.warn(ResultCode.FAILED_ARTICLE_NOT_EXISTS.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_ARTICLE_NOT_EXISTS));
}
article.setDeleteState((byte) 1);
article.setUpdateTime(new Date());
int row = articleMapper.updateByPrimaryKeySelective(article);
if (row != 1) {
// 打印日志
log.warn(ResultCode.ERROR_SERVICES.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
}
userService.subOneArticleCountById(article.getUserId());
boardService.subOneArticleCountById(article.getBoardId());
log.info("刪除帖子成功, article id = " + article.getId() + ", user id = " + article.getUserId() + ".");
}
5.單元測試
1.user表
@Test
void subOneArticleCountById() {
userService.subOneArticleCountById(1L);
log.info("更新成功");
}
2.board表
@Test
void subOneArticleCountById() {
boardService.subOneArticleCountById(9L);
log.info("更新成功");
}
3.article表
@Test
void delete() {
articleService.delete(6L);
log.info("更新成功");
}
更新前
t_article
?t_board
?t_user
?t_article
?t_board
?t_user
6.Controller實現(xiàn)方法外提供API接口
@ApiOperation("刪除帖子")
@PostMapping("/delete")
public AppResult delete(@ApiParam("文章id") @RequestParam("id") @NonNull Long id,
HttpServletRequest httpServletRequest) {
// 從session中獲取當前登錄的用戶
HttpSession session = httpServletRequest.getSession(false);
User user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
// 校驗用戶信息
if (user.getState() == 1) {
// 返回提示信息
return AppResult.failed(ResultCode.FAILED_USER_BANNED);
}
// 查詢帖子信息
Article article = articleService.selectById(id);
// 校驗帖子是否存在
if (article == null) {
// 返回提示信息
return AppResult.failed(ResultCode.FAILED_ARTICLE_NOT_EXISTS);
}
// 判斷當前登錄用戶是不是作者
if (article.getUserId() != user.getId()) {
// 返回提示信息
return AppResult.failed("您不是帖子的作者,無權(quán)修改.");
}
// 校驗帖子狀態(tài)
if (article.getState() == 1) {
// 返回提示信息
return AppResult.failed("帖子狀態(tài)異常, 請聯(lián)系管理員");
}
articleService.delete(id);
return AppResult.success("刪除成功");
}
7.測試API接口
8.實現(xiàn)前端邏輯,完成前后端交互
// ====================== 處理刪除事件 ======================
$('#details_artile_delete').click(function () {
$.ajax({
type: "post",
url: "/article/delete?id=" + $('#details_article_id').val(),
success: function (respData) {
if (respData.code == 0) {
// 成功提示
$.toast({
heading: '成功',
text: '刪除成功',
icon: 'success'
});
// 跳轉(zhuǎn)到首頁帖子列表
changeNavActive($('#nav-link-title'));
} else {
// 失敗(服務(wù)器處理業(yè)務(wù)邏輯失敗), 提示用戶錯誤信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
});
二十一.點贊文章操作
1.在Mapper.xml中編寫SQL語句
直接使用生成的updateByPrimaryKeySelective
2.在Mapper.java中定義方法
直接使用生成的updateByPrimaryKeySelective
3.定義Service接口
void thumbsUpById(Long id);
4.實現(xiàn)Serivce接口
@Override
public void thumbsUpById(Long id) {
if (id == null || id < 0) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
Article article = selectById(id);
if (article == null || article.getLikeCount() == null) {
//打印日志
log.warn(ResultCode.FAILED_ARTICLE_NOT_EXISTS.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_ARTICLE_NOT_EXISTS));
}
article.setLikeCount(article.getLikeCount() + 1);
article.setUpdateTime(new Date());
int row = articleMapper.updateByPrimaryKeySelective(article);
if (row != 1) {
// 打印日志
log.warn(ResultCode.ERROR_SERVICES.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
}
}
5.單元測試
@Test
void thumbsUpById() {
articleService.thumbsUpById(1L);
log.info("更新成功");
}
更新前?
?更新后
6.Controller實現(xiàn)方法外提供API接口
@ApiOperation("點贊帖子")
@PostMapping("/thumbsUp")
public AppResult thumbsUp(@ApiParam("文章id") @RequestParam("id") @NonNull Long id,
HttpServletRequest httpServletRequest) {
// 從session中獲取當前登錄的用戶
HttpSession session = httpServletRequest.getSession(false);
User user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
// 校驗用戶信息
if (user.getState() == 1) {
// 返回提示信息
return AppResult.failed(ResultCode.FAILED_USER_BANNED);
}
// 查詢帖子信息
Article article = articleService.selectById(id);
// 校驗帖子是否存在
if (article == null) {
// 返回提示信息
return AppResult.failed(ResultCode.FAILED_ARTICLE_NOT_EXISTS);
}
// 判斷當前登錄用戶是不是作者
if (article.getUserId() != user.getId()) {
// 返回提示信息
return AppResult.failed("您不是帖子的作者,無權(quán)修改.");
}
// 校驗帖子狀態(tài)
if (article.getState() == 1) {
// 返回提示信息
return AppResult.failed("帖子狀態(tài)異常, 請聯(lián)系管理員");
}
articleService.thumbsUpById(id);
return AppResult.success("點贊成功");
}
7.測試API接口
8.實現(xiàn)前端邏輯,完成前后端
// ====================== 處理點贊 ======================
$('#details_btn_like_count').click(function () {
$.ajax({
type: "post",
url: "/article/thumbsUp?id=" + currentArticle.id,
success: function (respData) {
if (respData.code == 0) {
currentArticle.likeCount = currentArticle.likeCount + 1;
// 設(shè)置頁面的值
$('#details_article_likeCount').html(currentArticle.likeCount);
// 成功提示
$.toast({
heading: '成功',
text: '點贊成功',
icon: 'success'
});
} else {
// 失敗(服務(wù)器處理業(yè)務(wù)邏輯失敗), 提示用戶錯誤信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
});
二十二.根據(jù)文章Id查詢回復列表
先來進行插入數(shù)據(jù)
-- 寫入回復表數(shù)據(jù)
INSERT INTO t_article_reply VALUES (NULL, 1, 1, NULL, NULL, '回復內(nèi)容111', 0, 0, 0, '2023-08-14 16:52:00', '2023-08-14 16:52:00');
INSERT INTO t_article_reply VALUES (NULL, 1, 1, NULL, NULL, '回復內(nèi)容222', 0, 0, 0, '2023-08-14 16:53:00', '2023-08-14 16:53:00');
INSERT INTO t_article_reply VALUES (NULL, 1, 1, NULL, NULL, '回復內(nèi)容333', 0, 0, 0, '2023-08-14 16:54:00', '2023-08-14 16:54:00');
INSERT INTO t_article_reply VALUES (NULL, 1, 2, NULL, NULL, '回復內(nèi)容444', 0, 0, 0, '2023-08-14 16:55:00', '2023-08-14 16:55:00');
INSERT INTO t_article_reply VALUES (NULL, 1, 2, NULL, NULL, '回復內(nèi)容555', 0, 0, 0, '2023-08-14 16:56:00', '2023-08-14 16:56:00');
1.在Mapper.xml中編寫SQL語句
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.javastudy.forum.dao.ArticleReplyMapper">
<!-- 自定義表關(guān)聯(lián)的結(jié)果集映射 -->
<resultMap id="AllInfoResultMap" type="com.javastudy.forum.model.ArticleReply" extends="BaseResultMap">
<!-- 關(guān)聯(lián)用戶表 -->
<association property="user" resultMap="com.javastudy.forum.dao.UserMapper.BaseResultMap" columnPrefix="u_" />
</resultMap>
<!-- 根據(jù)帖子Id查詢回復列表 -->
<select id="selectByArticleId" resultMap="AllInfoResultMap" parameterType="java.lang.Long">
select
u.id as u_id,
u.nickname as u_nickname,
u.gender as u_gender,
u.avatarUrl as u_avatarUrl,
u.phoneNum as u_phoneNum,
u.email as u_email,
ar.id,
ar.articleId,
ar.postUserId,
ar.replyId,
ar.replyUserId,
ar.content,
ar.likeCount,
ar.state,
ar.deleteState,
ar.createTime,
ar.updateTime
from t_article_reply ar, t_user u
where ar.postUserId = u.id
and ar.articleId = #{articleId,jdbcType=BIGINT}
and ar.deleteState = 0
order by ar.createTime desc
</select>
</mapper>
2.在Mapper.java中定義方法
List<ArticleReply> selectByArticleId(Long articleId);
3.定義Service接口
public interface IArticleReplyService {
List<ArticleReply> selectByArticleId(Long articleId);
}
4.實現(xiàn)Serivce接口
@Slf4j
@Service
public class ArticleReplyService implements IArticleReplyService {
@Resource
private ArticleReplyMapper articleReplyMapper;
@Override
public List<ArticleReply> selectByArticleId(Long articleId) {
if (articleId == null || articleId < 0) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
List<ArticleReply> articleReplies = articleReplyMapper.selectByArticleId(articleId);
return articleReplies;
}
}
5.單元測試
@Slf4j
@SpringBootTest
class ArticleReplyServiceTest {
@Resource
private IArticleReplyService articleReplyService;
@Test
void selectByArticleId() {
List<ArticleReply> articleReplies = articleReplyService.selectByArticleId(1L);
log.info(articleReplies.toString());
}
}
6.Controller實現(xiàn)方法外提供API接口
@RestController
@Slf4j
@RequestMapping("/reply")
@Api(tags = "文章回復接口")
public class ArticleReplyController {
@Resource
private IArticleReplyService articleReplyService;
@Resource
private IArticleService articleService;
@GetMapping("/getReplies")
@ApiOperation("獲取回復列表")
public AppResult<List<ArticleReply>> getAllByBoardId(@ApiParam("板塊id") @RequestParam("articleId") @NonNull Long articleId) {
Article article = articleService.selectById(articleId);
//判斷文章是否刪除
if (article == null || article.getDeleteState() == 1) {
// 返回錯誤提示
return AppResult.failed(ResultCode.FAILED_ARTICLE_NOT_EXISTS);
}
//判斷文章狀態(tài)
if (article.getState() == 1) {
// 返回錯誤提示
return AppResult.failed("帖子已封帖");
}
List<ArticleReply> articleReplies = articleReplyService.selectByArticleId(articleId);
if (articleReplies == null) {
articleReplies = new ArrayList<>();
}
return AppResult.success(articleReplies);
}
}
7.測試API接口
8.實現(xiàn)前端邏輯,完成前后端
$.ajax({
type: "get",
url: "/reply/getReplies?articleId=" + currentArticle.id,
success: function (respData) {
if (respData.code == 0) {
buildArticleReply(respData.data);
} else {
// 失敗(服務(wù)器處理業(yè)務(wù)邏輯失敗), 提示用戶錯誤信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
二十三.回復文章
回復文章需要進行如下兩個操作
1.插入一個回復(ArticleReply表)
2.增加文章的回復數(shù)(Article表)
因為涉及到多次更新的操作,因此也需要事務(wù)操作
1.在Mapper.xml中編寫SQL語句
1.Article表
已經(jīng)自動生成updateByPrimaryKeySelective
2.ArticleReply表
已經(jīng)自動生成insertSelective
2.在Mapper.java中定義方法
1.Article表
已經(jīng)自動生成updateByPrimaryKeySelective
2.ArticleReply表
已經(jīng)自動生成insertSelective
3.定義Service接口
1.Article表
void updateArticleCountById(Long id);
2.ArticleReply表
@Transactional
void create(ArticleReply articleReply);
4.實現(xiàn)Serivce接口
1.Article表
@Override
public void updateArticleCountById(Long id) {
if (id == null) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
Article article = selectById(id);
if (article == null) {
//打印日志
log.warn(ResultCode.FAILED_ARTICLE_NOT_EXISTS.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_ARTICLE_NOT_EXISTS));
}
article.setReplyCount(article.getReplyCount() + 1);
article.setUpdateTime(new Date());
int row = articleMapper.updateByPrimaryKeySelective(article);
if (row != 1) {
// 打印日志
log.warn(ResultCode.ERROR_SERVICES.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
}
}
2.ArticleReply表
@Override
public void create(ArticleReply articleReply) {
if (articleReply == null || StringUtils.isEmpty(articleReply.getContent()) ||
articleReply.getPostUserId() == null || articleReply.getPostUserId() < 0 ||
articleReply.getArticleId() == null || articleReply.getArticleId() < 0) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
//設(shè)置默認值
articleReply.setLikeCount(0); // 點贊數(shù)
articleReply.setState((byte) 0); // 狀態(tài)
articleReply.setDeleteState((byte) 0); // 是否刪除
Date date = new Date();
articleReply.setCreateTime(date); // 創(chuàng)建時間
articleReply.setUpdateTime(date); // 更新時間
int row = articleReplyMapper.insertSelective(articleReply);
if (row != 1) {
// 打印日志
log.warn(ResultCode.ERROR_SERVICES.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
}
//更新文章數(shù)
articleService.updateArticleCountById(articleReply.getArticleId());
// 打印日志
log.info(ResultCode.SUCCESS.toString() + " 回復發(fā)布成功, articleId = " + articleReply.getArticleId());
}
5.單元測試
1.Article表
@Test
void updateArticleCountById() {
articleService.updateArticleCountById(1L);
log.info("更新成功");
}
2.ArticleReply表
@Test
void create() {
ArticleReply articleReply = new ArticleReply();
articleReply.setArticleId(3L);
articleReply.setPostUserId(2L);
articleReply.setContent("測試評論內(nèi)容");
articleReplyService.create(articleReply);
}
更新前
更新后?
6.Controller實現(xiàn)方法外提供API接口
@PostMapping("/create")
@ApiOperation("發(fā)布回復")
public AppResult<List<ArticleReply>> create(@ApiParam("文章id") @RequestParam("articleId") @NonNull Long articleId,
@ApiParam("文章內(nèi)容") @RequestParam("content") @NonNull String content,
HttpServletRequest httpServletRequest) {
HttpSession session = httpServletRequest.getSession(false);
User user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
if (user.getState() == 1) {
// 返回提示
return AppResult.failed(ResultCode.FAILED_USER_BANNED);
}
Article article = articleService.selectById(articleId);
//判斷文章是否刪除
if (article == null || article.getDeleteState() == 1) {
// 返回錯誤提示
return AppResult.failed(ResultCode.FAILED_ARTICLE_NOT_EXISTS);
}
//判斷文章狀態(tài)
if (article.getState() == 1) {
// 返回錯誤提示
return AppResult.failed("文章已封帖");
}
ArticleReply articleReply=new ArticleReply();
articleReply.setContent(content);
articleReply.setPostUserId(user.getId());
articleReply.setArticleId(articleId);
articleReplyService.create(articleReply);
return AppResult.success("發(fā)布回復成功");
}
7.測試API接口
8.實現(xiàn)前端邏輯,完成前后端
// 構(gòu)造帖子對象
var postData = {
articleId: articleIdEl.val(),
content: replyContentEl.val()
}
// 發(fā)送請求,成功后
// 1. 清空回復區(qū)域
// 2. 更新回貼數(shù) currentArticle.replyCount = currentArticle.replyCount + 1;
// 3. 調(diào)用loadArticleDetailsReply()方法,重新構(gòu)建回貼列表
$.ajax({
type: "post",
url: "/reply/create",
contentType: 'application/x-www-form-urlencoded',
data: postData,
// 成功回調(diào)
success: function (respData) {
if (respData.code == 0) {
// 提示
$.toast({
heading: '成功',
text: respData.message,
icon: 'success'
});
// 清空回復區(qū)域
editor.setValue('');
// 更新回貼數(shù)
// 1. 更新全局變量中的回貼數(shù)
currentArticle.replyCount = currentArticle.replyCount + 1;
// 2. 更新頁面的值
$('#details_article_replyCount').html(currentArticle.replyCount);
// 3. 重新加載回復列表
loadArticleDetailsReply();
} else {
// 失敗
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
// 失敗 (HTTP)
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
});
二十四.用戶個人信息和文章展示
已經(jīng)有根據(jù)id獲取用戶信息的方法,現(xiàn)在我們只需要根據(jù)userId獲取所有文章信息的接口即可
1.在Mapper.xml中編寫SQL語句
<!-- 根據(jù)用戶Id查詢帖子列表-->
<select id="selectByUserId" resultMap="AllInfoResultMap" parameterType="java.lang.Long">
select
b.id as b_id,
b.name as b_name,
a.id,
a.boardId,
a.userId,
a.title,
a.visitCount,
a.replyCount,
a.likeCount,
a.state,
a.deleteState,
a.createTime,
a.updateTime
from t_article as a, t_board b
where a.boardId = b.id
and a.userId = #{userId,jdbcType=BIGINT}
and a.deleteState = 0
order by a.createTime desc
</select>
2.在Mapper.java中定義方法
List<Article> selectByUserId(Long userId);
3.定義Service接口
List<Article> selectByUserId(Long userId);
4.實現(xiàn)Serivce接口
@Override
public List<Article> selectByUserId(Long userId) {
if (userId == null) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
List<Article> articles = articleMapper.selectByUserId(userId);
return articles;
}
5.單元測試
@Test
void selectByUserId() {
List<Article> articles = articleService.selectByUserId(1L);
log.info(articles.toString());
}
6.Controller實現(xiàn)方法外提供API接口
@ApiOperation("獲取用戶文章列表")
@GetMapping("/getAllByUserId")
public AppResult<List<Article>> getAllByUserId(@ApiParam("用戶id") @RequestParam(value = "userId", required = false) Long userId,
HttpServletRequest httpServletRequest) {
if (userId == null) {
HttpSession session = httpServletRequest.getSession(false);
User user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
userId = user.getId();
}
List<Article> articles = articleService.selectByUserId(userId);
if (articles == null) {
articles = new ArrayList<>();
}
return AppResult.success(articles);
}
7.測試API接口
8.實現(xiàn)前端邏輯,完成前后端
profile.html頁面
1.獲取用戶信息
$.ajax({
type: 'get',
url: '/user/info' + userInfoQueryString,
// 成功回調(diào)
success: function (respData) {
if (respData.code == 0) {
// 把查詢到的數(shù)據(jù)設(shè)置到頁面上
initProfileUserInfo(respData.data);
} else {
// 失敗
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
// 失敗 (HTTP)
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
2.獲取用戶發(fā)布的文章
$.ajax({
type: "get",
url: "/article/getAllByUserId" + articleListQueryString,
success: function (respData) {
if (respData.code == 0) {
buildProfileUserArticle(respData.data);
} else {
// 失敗(服務(wù)器處理業(yè)務(wù)邏輯失敗), 提示用戶錯誤信息
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
二十五.修改用戶信息?
修改用戶信息,首先需要獲取用戶的信息,前面的接口已經(jīng)實現(xiàn),我們只需要在前端根據(jù)接口獲取信息展示到頁面上即可
1.在Mapper.xml中編寫SQL語句
已經(jīng)自動生成了updateByPrimaryKeySelective
2.在Mapper.java中定義方法
已經(jīng)自動生成了updateByPrimaryKeySelective
3.定義Service接口
void modifyInfo(User user);
4.實現(xiàn)Serivce接口
@Override
public void modifyInfo(User user) {
if (user == null) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
// 定義一個標識
boolean checkParam = false;
//更新用戶
User updateUser = new User();
updateUser.setId(user.getId());
//用戶名
if (!StringUtils.isEmpty(user.getUsername())) {
User user1 = selectByUsername(user.getUsername());
if (user1 == null) {
updateUser.setUsername(user.getUsername());
checkParam = true;
} else {
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_USER_EXISTS));
}
}
//昵稱
if (!StringUtils.isEmpty(user.getNickname())) {
updateUser.setNickname(user.getNickname());
checkParam = true;
}
//性別
if (user.getGender() != null) {
// 設(shè)置性別
updateUser.setGender(user.getGender());
// 校驗有效性 0女 1男 2保密
if (updateUser.getGender() < 0 || updateUser.getGender() > 2) {
// 處理方式1:設(shè)置成默認值
// 處理方式2:設(shè)置為null, 表示保持原來的性別,不更新數(shù)據(jù)庫
updateUser.setGender((byte) 2);
}
// 更新標識
checkParam = true;
}
//電話號碼
if (!StringUtils.isEmpty(user.getPhonenum())) {
updateUser.setPhonenum(user.getPhonenum());
checkParam = true;
}
//郵箱
if (!StringUtils.isEmpty(user.getEmail())) {
updateUser.setEmail(user.getEmail());
checkParam = true;
}
//簡介
if (!StringUtils.isEmpty(user.getRemark())) {
updateUser.setRemark(user.getRemark());
checkParam = true;
}
if (checkParam == false) {
throw new ApplicationException("所有信息不能同時為空");
}
int row = userMapper.updateByPrimaryKeySelective(updateUser);
if (row != 1) {
// 打印日志
log.warn(ResultCode.ERROR_SERVICES.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
}
}
5.單元測試
@Test
void modifyInfo() {
User user=new User();
user.setId(1L);
user.setNickname("小張三");
user.setRemark("我是快樂小張三");
user.setGender((byte) 1);
user.setPhonenum("123456");
user.setEmail("159@qq.com");
userService.modifyInfo(user);
}
更新前
更新后?
6.Controller實現(xiàn)方法外提供API接口
@ApiOperation("修改個人信息")
@PostMapping("/modifyInfo")
public AppResult<User> modifyInfo(HttpServletRequest httpServletRequest,
@ApiParam("用戶名(用于登錄)") @RequestParam(value = "username", required = false) String username,
@ApiParam("昵稱") @RequestParam(value = "nickname", required = false) String nickname,
@ApiParam("性別") @RequestParam(value = "gender", required = false) Byte gender,
@ApiParam("郵箱") @RequestParam(value = "email", required = false) String email,
@ApiParam("電話") @RequestParam(value = "phoneNum", required = false) String phoneNum,
@ApiParam("個人簡介") @RequestParam(value = "remark", required = false) String remark) {
if (StringUtils.isEmpty(username) && StringUtils.isEmpty(nickname) && gender == null &&
StringUtils.isEmpty(email) && StringUtils.isEmpty(phoneNum) && StringUtils.isEmpty(remark)) {
return AppResult.failed("要修改的信息不能全為空");
}
HttpSession session = httpServletRequest.getSession(false);
User user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
User updateUser = new User();
updateUser.setId(user.getId());
updateUser.setUsername(username); // 用戶名
updateUser.setNickname(nickname); // 昵稱
updateUser.setGender(gender); // 性別
updateUser.setPhonenum(phoneNum); // 電話
updateUser.setEmail(email); // 郵箱
updateUser.setRemark(remark); // 個人簡介
userService.modifyInfo(updateUser);
user = userService.selectById(user.getId());
session.setAttribute(AppConfig.SESSION_USER_KEY, user);
return AppResult.success(user);
}
7.測試API接口
修改前
?修改后
8.實現(xiàn)前端邏輯,完成前后端
1.獲取用戶信息
$.ajax({
type: 'get',
url: '/user/info',
// 成功回調(diào)
success: function (respData) {
if (respData.code == 0) {
// 把查詢到的數(shù)據(jù)設(shè)置到頁面上
initUserInfo(respData.data);
} else {
// 失敗
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
// 失敗 (HTTP)
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
2.修改用戶信息
$.ajax({
type: "post",
url: userURL,
contentType: 'application/x-www-form-urlencoded',
data: userInfo,
// 成功回調(diào)
success: function (respData) {
if (respData.code == 0) {
// 提示
$.toast({
heading: '成功',
text: respData.message,
icon: 'success'
});
// 修改個人信息成功后,更新頁面的用戶昵稱
if (type == 1) {
let user = respData.data;
// 個人中心
$('#settings_nickname').html(user.nickname);
// 導航欄
$('#index_nav_nickname').html(user.nickname);
} else if (type == 2) {
// 跳轉(zhuǎn)到登錄頁面
location.assign("/sign-in.html");
return;
}
} else {
// 失敗
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
// 失敗 (HTTP)
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
二十六.修改密碼
1.在Mapper.xml中編寫SQL語句
已經(jīng)自動生成了updateByPrimaryKeySelective
2.在Mapper.java中定義方法
已經(jīng)自動生成了updateByPrimaryKeySelective
3.定義Service接口
void modifyPassword(Long id, String newPassword, String oldPassword);
4.實現(xiàn)Serivce接口
@Override
public void modifyPassword(Long id, String newPassword, String oldPassword) {
if (id < 0 || id == null || StringUtils.isEmpty(newPassword) || StringUtils.isEmpty(oldPassword)) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
User user = selectById(id);
if (user == null || user.getDeletestate() == 1) {
// 打印日志
log.warn(ResultCode.FAILED_USER_NOT_EXISTS.toString() + " user id = " + id);
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_USER_NOT_EXISTS));
}
if (!MD5Utils.verifyOriginalAndCiphertext(oldPassword, user.getSalt(), user.getPassword())) {
// 打印日志
log.warn(ResultCode.FAILED_PASSWORD_CHECK.toString() + " user id = " + id + ", old password = " + oldPassword);
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PASSWORD_CHECK));
}
String salt = UUIDUtils.UUID_32();
User updateUser = new User();
updateUser.setId(user.getId());
updateUser.setSalt(salt);
updateUser.setPassword(MD5Utils.md5Salt(newPassword, salt));
updateUser.setUpdatetime(new Date());
int row = userMapper.updateByPrimaryKeySelective(updateUser);
if (row != 1) {
// 打印日志
log.warn(ResultCode.ERROR_SERVICES.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
}
}
5.單元測試
@Test
void modifyPassword() {
userService.modifyPassword(2L, "654321","123456");
log.info("更新成功");
}
更新前
更新后?
6.Controller實現(xiàn)方法外提供API接口
@ApiOperation("修改密碼")
@PostMapping("/modifyPwd")
public AppResult modifyPassword(@ApiParam("原密碼") @RequestParam("oldPassword") @NonNull String oldPassword,
@ApiParam("新密碼") @RequestParam("newPassword") @NonNull String newPassword,
@ApiParam("確認密碼") @RequestParam("passwordRepeat") @NonNull String passwordRepeat,
HttpServletRequest httpServletRequest) {
//判斷兩次輸入的密碼
if (!newPassword.equals(passwordRepeat)) {
return AppResult.failed(ResultCode.FAILED_TWO_PWD_NOT_SAME);
}
//獲取當前登錄的用戶信息
HttpSession session = httpServletRequest.getSession(false);
User user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
//修改密碼
userService.modifyPassword(user.getId(), newPassword, oldPassword);
//銷毀當前的session
session.invalidate();
return AppResult.success("修改成功");
}
7.測試API接口
8.實現(xiàn)前端邏輯,完成前后端
// 發(fā)送請求,提示響應結(jié)果
$.ajax({
type: "post",
url: userURL,
contentType: 'application/x-www-form-urlencoded',
data: userInfo,
// 成功回調(diào)
success: function (respData) {
if (respData.code == 0) {
// 提示
$.toast({
heading: '成功',
text: respData.message,
icon: 'success'
});
// 修改個人信息成功后,更新頁面的用戶昵稱
if (type == 1) {
let user = respData.data;
// 個人中心
$('#settings_nickname').html(user.nickname);
// 導航欄
$('#index_nav_nickname').html(user.nickname);
} else if (type == 2) {
// 提示
$.toast({
heading: '成功',
text: respData.message,
icon: 'success'
});
// 跳轉(zhuǎn)到登錄頁面
location.assign("/sign-in.html");
return;
}
} else {
// 失敗
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
// 失敗 (HTTP)
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
二十七.站內(nèi)信功能
這個功能涉及到的是message表
1.在Mapper.xml中編寫SQL語句
直接使用z自動生成的
2.在Mapper.java中定義方法
3.定義Service接口
public interface IMessageService {
void create(Message message);
}
4.實現(xiàn)Serivce接口
@Slf4j
@Service
public class MessageServiceImpl implements IMessageService {
@Resource
private MessageMapper messageMapper;
@Resource
private IUserService userService;
@Override
public void create(Message message) {
if (message == null || StringUtils.isEmpty(message.getContent()) || message.getPostUserId() == null || message.getReceiveUserId() == null) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
//校驗接受者
User user = userService.selectById(message.getReceiveUserId());
if (user == null || user.getDeletestate() == 1) {
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
message.setCreateTime(new Date());
message.setUpdateTime(new Date());
message.setState((byte) 0);
//發(fā)送消息
int row = messageMapper.insertSelective(message);
if (row != 1) {
// 打印日志
log.warn(ResultCode.ERROR_SERVICES.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
}
}
}
5.單元測試
@SpringBootTest
@Slf4j
class MessageServiceImplTest {
@Resource
IMessageService messageService;
@Test
void create() {
Message message=new Message();
message.setPostUserId(1L);
message.setReceiveUserId(2L);
message.setContent("hello,路飛");
messageService.create(message);
log.info("插入成功");
}
}
更新前
更新后
6.Controller實現(xiàn)方法外提供API接口
@RestController
@RequestMapping("/message")
@Slf4j
@Api(tags = "消息接口")
public class MessageController {
@Resource
private IMessageService messageService;
@Resource
private IUserService userService;
@PostMapping("/create")
@ApiOperation("發(fā)送消息")
public AppResult create(@ApiParam("消息內(nèi)容") @RequestParam("content") @NonNull String content,
@ApiParam("消息接收方") @RequestParam("receiveUserId") @NonNull Long receiveUserId,
HttpServletRequest httpServletRequest) {
if (StringUtils.isEmpty(content) || receiveUserId == null) {
return AppResult.failed("內(nèi)容和接收者id不能為null");
}
HttpSession session = httpServletRequest.getSession(false);
User user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
//校驗用戶是否存在
if (user == null || user.getDeletestate() == null) {
return AppResult.failed(ResultCode.FAILED_USER_NOT_EXISTS);
}
//校驗用戶狀態(tài)
if (user.getState() == 1) {
return AppResult.failed(ResultCode.FAILED_USER_BANNED);
}
if (user.getId() == receiveUserId) {
return AppResult.failed("不能給自己發(fā)送消息");
}
//查詢接受者
User receiveUser = userService.selectById(receiveUserId);
//校驗接受者狀態(tài)
if (receiveUser == null || receiveUser.getDeletestate() == 1) {
return AppResult.failed("接收者狀態(tài)異常");
}
//構(gòu)建message對象
Message message = new Message();
message.setContent(content);
message.setPostUserId(user.getId());
message.setReceiveUserId(receiveUserId);
messageService.create(message);
return AppResult.success("發(fā)送成功");
}
}
7.測試API接口
發(fā)送前
?發(fā)送后
8.實現(xiàn)前端邏輯,完成前后端
在index.html頁面
// 發(fā)送站內(nèi)信請求 url = message/send, 成功與失敗都調(diào)用cleanMessageForm()方法,清空輸入框
$.ajax({
type: "post",
url: "/message/create",
contentType: 'application/x-www-form-urlencoded',
data: postData,
success: function (respData) {
if (respData.code == 0) {
//清空
cleanMessageForm()
// 提示
$.toast({
heading: '成功',
text: respData.message,
icon: 'success'
});
} else {
// 失敗
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
// 失敗 (HTTP)
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
二十八.查看未讀數(shù)
1.在Mapper.xml中編寫SQL語句
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.javastudy.forum.dao.MessageMapper">
<select id="selectByNum" resultType="java.lang.Long" parameterType="java.lang.Long">
select count(*)
from t_message
where state = 0
and deleteState = 0
and receiveUserId = #{userId,jdbcType=BIGINT};
</select>
</mapper>
2.在Mapper.java中定義方法
Long selectByNum(Long userId);
3.定義Service接口
Long selectUnreadCount(Long userId);
4.實現(xiàn)Serivce接口
@Override
public Long selectUnreadCount(Long userId) {
if (userId == null) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
User user = userService.selectById(userId);
if (user == null || user.getDeletestate() == 1) {
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
Long num = messageMapper.selectByNum(userId);
return num;
}
5.單元測試
@Test
void selectUnreadCount() {
Long aLong = messageService.selectUnreadCount(1L);
log.info("id=1的用戶未讀數(shù):"+aLong);
}
6.Controller實現(xiàn)方法外提供API接口
@GetMapping("/getUnreadCount")
@ApiOperation("獲取未讀數(shù)")
public AppResult<Long> getUnreadCount(HttpServletRequest httpServletRequest) {
HttpSession session = httpServletRequest.getSession(false);
User user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
//檢驗用戶
if (user == null || user.getDeletestate() == 1) {
return AppResult.failed(ResultCode.FAILED_USER_NOT_EXISTS);
}
Long num = messageService.selectUnreadCount(user.getId());
return AppResult.success(num);
}
7.測試API接口
8.實現(xiàn)前端邏輯,完成前后端
// ============ 獲取用戶未讀站內(nèi)信數(shù)量 ============
// 成功后,處理小紅點是否顯示 #index_nva_message_badge
function requestMessageUnreadCount() {
$.ajax({
type: "get",
url: "/message/getUnreadCount",
success: function (respData) {
if (respData.code == 0) {
var messageBadgeEl = $('#index_nva_message_badge');
if (respData.data > 0) {
messageBadgeEl.show();
} else {
messageBadgeEl.hide();
}
} else {
// 失敗
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
// 失敗 (HTTP)
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
}
二十九.獲取消息列表?
在獲取消息的時候,我們也需要發(fā)送者的用戶信息,因此在message實體類中加上一個屬性
private User postUser;
1.在Mapper.xml中編寫SQL語句
<resultMap id="AllInfoResultMap" type="com.javastudy.forum.model.Message" extends="BaseResultMap">
<!-- 擴展??信息結(jié)果, 注意查詢結(jié)果列名的前綴為 u_ -->
<association property="postUser" resultMap="com.javastudy.forum.dao.UserMapper.BaseResultMap"
columnPrefix="u_"></association>
</resultMap>
<select id="selectByReceiveUserId" resultMap="AllInfoResultMap" parameterType="java.lang.Long">
select u.id as u_id,
u.avatarurl as u_avatarurl,
u.nickname as u_nickname,
u.gender as u_gender,
m.id,
m.postUserId,
m.receiveUserId,
m.content,
m.state,
m.createTime,
m.updateTime
from t_message m,
t_user u
where m.postUserId = u.id
and m.deleteState = 0
and m.receiveUserId = #{receiveUserId,jdbcType=BIGINT}
order by m.createTime desc, m.state asc
</select>
2.在Mapper.java中定義方法
List<Message> selectByReceiveUserId(Long receiveUserId);
3.定義Service接口
List<Message> selectById(Long id);
4.實現(xiàn)Serivce接口
@Override
public List<Message> selectById(Long id) {
if (id == null || id < 0) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
List<Message> messages = messageMapper.selectByReceiveUserId(id);
return messages;
}
5.單元測試
@Test
void selectById() {
List<Message> messages = messageService.selectById(1L);
log.info(messages.toString());
}
6.Controller實現(xiàn)方法外提供API接口
@GetMapping("/getAll")
@ApiOperation("查詢用戶的所有站內(nèi)信")
public AppResult<List<Message>> getAll(HttpServletRequest httpServletRequest) {
HttpSession session = httpServletRequest.getSession(false);
User user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
//檢驗用戶
if (user == null || user.getDeletestate() == 1 || user.getId() == null) {
return AppResult.failed(ResultCode.FAILED_USER_NOT_EXISTS);
}
List<Message> messages = messageService.selectById(user.getId());
return AppResult.success(messages);
}
7.測試API接口
8.實現(xiàn)前端邏輯,完成前后端
// ============ 獲取用戶所有站內(nèi)信 ============
function requestMessageList() {
$.ajax({
type:"get",
url:"/message/getAll",
success: function (respData) {
if (respData.code == 0) {
buildMessageList(respData.data);
} else {
// 失敗
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
// 失敗 (HTTP)
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
}
三十.站內(nèi)信已讀狀態(tài)更新
1.在Mapper.xml中編寫SQL語句
使用已經(jīng)生成的updateByPrimaryKeySelective
2.在Mapper.java中定義方法
使用已經(jīng)生成的updateByPrimaryKeySelective
3.定義Service接口
void updateStateById(Long id,Byte state);
4.實現(xiàn)Serivce接口
@Override
public void updateStateById(Long id, Byte state) {
// 非空校驗, state : 0 未讀 1 已讀 2 已回復
if (id == null || state == null || id < 0 || state < 0 || state > 2) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
Message message = new Message();
message.setId(id);
message.setState(state);
message.setUpdateTime(new Date());
int row = messageMapper.updateByPrimaryKey(message);
if (row != 1) {
// 打印日志
log.warn(ResultCode.ERROR_SERVICES.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.ERROR_SERVICES));
}
}
5.單元測試
@Test
void updateStateById() {
messageService.updateStateById(1L, (byte) 1);
log.info("更新成功");
}
更新前
更新后?
6.Controller實現(xiàn)方法外提供API接口
@PostMapping("/markRead")
@ApiOperation("更新為已讀")
public AppResult markRead(HttpServletRequest httpServletRequest,
@ApiParam("消息id") @RequestParam("id") Long id) {
Message message = messageService.selectById(id);
//校驗消息的狀態(tài)
if (message == null || message.getDeleteState() == 1) {
return AppResult.failed(ResultCode.FAILED_MESSAGE_NOT_EXISTS);
}
HttpSession session = httpServletRequest.getSession(false);
User user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
//校驗接受者是否和當前用戶一致
if (user.getId() != message.getReceiveUserId()) {
return AppResult.failed(ResultCode.FAILED_FORBIDDEN);
}
messageService.updateStateById(id, (byte) 1);
return AppResult.success("更改狀態(tài)成功");
}
7.測試API接口
更新前
更新后?
8.實現(xiàn)前端邏輯,完成前后端
// 發(fā)送請求,更新狀態(tài)為已讀
if (messageItem.state == 0 && statusDotEl.hasClass('status-dot-animated bg-red')) {
$.ajax({
type: 'post',
url: '/message/markRead',
contentType: 'application/x-www-form-urlencoded',
data: { id: messageItem.id },
// 成功回調(diào)
success: function (respData) {
if (respData.code == 0) {
// 更新頁面顯示效果和messageItem.state
statusDotEl.removeClass('status-dot-animated bg-red');
// 修改未讀為已讀
statusDescEl.html('[已讀]');
// 修改本地的對象狀態(tài)屬性
messageItem.state = 1;
}
}
});
}
三十一.站內(nèi)信回復功能?
1.在Mapper.xml中編寫SQL語句
使用已經(jīng)生成的updateByPrimaryKeySelective
2.在Mapper.java中定義方法
使用已經(jīng)生成的updateByPrimaryKeySelective
3.定義Service接口
void reply(Long repliedId, Message message);
4.實現(xiàn)Serivce接口
@Override
public void reply(Long repliedId, Message message) {
if (repliedId == null || repliedId < 0 || message == null) {
//打印日志
log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
//拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
}
Message repliedMessage = selectById(repliedId);
if (repliedMessage == null || repliedMessage.getDeleteState() == 1) {
// 打印日志
log.warn(ResultCode.FAILED_MESSAGE_NOT_EXISTS.toString());
// 拋出異常
throw new ApplicationException(AppResult.failed(ResultCode.FAILED_MESSAGE_NOT_EXISTS));
}
//更新狀態(tài)
updateStateById(repliedId, (byte) 2);
//發(fā)送信息
create(message);
}
5.單元測試
@Test
void reply() {
Message message = new Message();
message.setContent("我也愛你");
message.setPostUserId(1L);
message.setReceiveUserId(2L);
messageService.reply(2L, message);
}
更新前?
?更新后?
6.Controller實現(xiàn)方法外提供API接口
@PostMapping("/reply")
@ApiOperation("回復站內(nèi)信")
public AppResult reply(HttpServletRequest httpServletRequest,
@ApiParam("要回復的站內(nèi)信Id") @RequestParam("repliedId") @NonNull Long repliedId,
@ApiParam("站內(nèi)信的內(nèi)容") @RequestParam("content") @NonNull String content) {
//校驗當前用戶狀態(tài)
HttpSession session = httpServletRequest.getSession(false);
User user = (User) session.getAttribute(AppConfig.SESSION_USER_KEY);
if (user.getState() == 1) {
// 返回錯誤描述
return AppResult.failed(ResultCode.FAILED_USER_BANNED);
}
//校驗要回復的站內(nèi)信狀態(tài)
Message message = messageService.selectById(repliedId);
if (message == null || message.getDeleteState() == 1) {
return AppResult.failed(ResultCode.FAILED_MESSAGE_NOT_EXISTS);
}
// 不能給自己回復
if (user.getId() == message.getPostUserId()) {
// 返回錯誤描述
return AppResult.failed("不能回復自己的站內(nèi)信");
}
//構(gòu)造對象
Message postMessage = new Message();
postMessage.setContent(content);
postMessage.setUpdateTime(new Date());
postMessage.setCreateTime(new Date());
postMessage.setPostUserId(user.getId());
postMessage.setReceiveUserId(message.getPostUserId());
messageService.reply(repliedId, postMessage);
return AppResult.success("回復成功");
}
7.測試API接口
?更新前?
?更新后?
8.實現(xiàn)前端邏輯,完成前后端
$.ajax({
type: "post",
url: "/message/reply",
contentType: 'application/x-www-form-urlencoded',
data: postData,
success: function (respData) {
if (respData.code == 0) {
// 回復成功后刷新未讀標識和站內(nèi)信列表
requestMessageUnreadCount();
requestMessageList();
// 清空輸入?yún)^(qū)
cleanMessageReplyForm();
// 提示
$.toast({
heading: '成功',
text: respData.message,
icon: 'success'
});
} else {
// 失敗
$.toast({
heading: '警告',
text: respData.message,
icon: 'warning'
});
}
},
// 失敗 (HTTP)
error: function () {
$.toast({
heading: '錯誤',
text: '訪問出現(xiàn)問題,請聯(lián)系管理員',
icon: 'error'
});
}
});
三十二.項目的部署與發(fā)布
1.部署linux服務(wù)器?
1.創(chuàng)建數(shù)據(jù)庫
可以將sql語句變成一個文件,然后執(zhí)行下面的
source /root/java78/table.sql
2.將代碼打包
打包完成之后,將jar包拖拽到linux服務(wù)器上
3.運行代碼?
?后臺啟動項目
nohup java -jar Blog_Spring-0.0.1-SNAPSHOT.jar &
?查看日志
cd logs/
tail -f spring.log
??????
?終止當前的服務(wù)
ps -ef | grep [ ]?
kill -9 pid
?注意:如果開啟多個服務(wù),需要開端口,給防火墻添加端口號
查看防火墻狀態(tài)(如果沒開啟,建議開啟,不開啟可以直接訪問,開啟了需要進行已下的操作訪問)
systemctl status firewalld
?啟動防火墻和關(guān)閉防火墻
systemctl start firewalld
systemctl stop firewalld
查看開放的端口號
firewall-cmd --list-ports
開啟8080端口
firewall-cmd --permanent --add-port=8080/tcp
重啟防火墻
firewall-cmd --reload
設(shè)置開機啟動
systemctl enable firewalld
添加安全組
2.訪問測試
訪問地址:比特論壇 - 用戶登錄
待拓展的功能
頭像上傳
分頁展示
記錄用戶點贊的帖子
回復樓中樓
回復站內(nèi)信樓中樓
熱帖+Redis緩存
站內(nèi)信及時通知
用戶權(quán)限管理
版塊管理
用戶管理
最后.項目描述文章來源:http://www.zghlxwxcb.cn/news/detail-695548.html
論壇項目圍繞帖子與用戶兩個核心角色進行業(yè)務(wù)處理,大家要能夠理清每個功能的業(yè)務(wù)流程,并正確的寫出實現(xiàn)代碼。
在描述項目時,建議先介紹一下基本功能,比如對帖子的操作(增刪改查),點贊,回復,對用戶信息的修改,個人中心等,再具體描述技術(shù)實現(xiàn)過程,一般采用的格式為:用XXX技術(shù)實現(xiàn)了XXX功能,達到了XXX效果,以下是幾個示例:文章來源地址http://www.zghlxwxcb.cn/news/detail-695548.html
- 使用統(tǒng)一返回格式+全局錯誤信息定義處理前后端交互時的返回結(jié)果
- 使用@ControllerAdvice+@ExceptionHandler實現(xiàn)全局異常處理
- 使用攔截器實現(xiàn)用戶登錄校驗
- 使用MybatisGeneratorConfig生成常的增刪改查方法
- 集成Swagger實現(xiàn)自動生成API測試接口
- 使用jQuery完成AJAX請求,并處理HTML頁面標簽
- 對數(shù)據(jù)庫中常用的查詢字段建立索引,并使用查詢計劃分析索引是否生效
?
到了這里,關(guān)于基于 Spring 前后端分離版本的論壇系統(tǒng)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!