Compare commits

...

29 Commits

Author SHA1 Message Date
qingfeng1121
15eca0d0b5 refactor(controller/service/repository): 重构分类属性、文章和消息相关功能
重构分类属性相关类名从Category_attribute改为Categoryattribute
优化文章和消息的分页查询功能,新增分页DTO类
移除旧的分页DTO类PageDto,新增ArriclePageDto和MessagePageDto
调整消息统计逻辑,区分文章评论和独立消息
更新安全配置,开放消息新增接口权限
2025-12-18 15:19:59 +08:00
qingfeng1121
f27be2130c refactor: 优化代码结构和配置
- 在PageDto中添加toString方法以便调试
- 关闭开发环境的SQL日志输出以减少日志噪音
- 允许匿名用户发表评论
- 调整安全配置,仅允许POST方法访问认证端点
- 修改文章状态分页接口,使用查询参数替代路径参数
2025-12-15 17:54:14 +08:00
qingfeng1121
9b01ee8889 refactor(项目结构): 重构项目包结构和异常处理
- 将ResponseMessage和GlobalExceptionHandler移动到exceptopn包
- 重构服务接口包结构,将接口从service.imp移动到service包
- 更新所有控制器中ResponseMessage的引用路径
- 统一服务接口命名规范,去除I前缀
- 调整application.properties配置,统一服务端口
- 优化SecurityConfig权限配置,简化API访问控制
- 清理旧的日志文件
- 更新开发环境配置,添加前端开发端口支持
2025-12-12 17:35:53 +08:00
qingfeng1121
505a7a0944 chore: 更新配置文件及pom.xml编码设置
修改开发环境jwt注释格式
调整生产环境jpa ddl-auto为create
添加maven资源插件UTF-8编码配置
2025-12-11 14:48:15 +08:00
qingfeng1121
51392bf807 feat: 添加用户数据初始化功能并优化配置管理
refactor: 重构CORS配置使其从配置文件读取
refactor: 移除无用分类和消息初始化代码

build: 更新pom.xml依赖项

docs: 拆分应用配置文件为环境特定配置
2025-12-11 12:42:53 +08:00
qingfeng1121
eb1f70d431 feat(文章): 添加根据状态分页查询文章功能
实现文章列表的分页查询功能,包括:
- 在ArticleRepository添加分页查询方法
- 在IArticleService和ArticleService添加分页接口和实现
- 新增PageDto用于分页参数校验
- 在ArticleController添加分页接口
2025-11-14 15:30:39 +08:00
qingfeng1121
47c357695b chore: 添加日志文件 web_project.log.2025-11-08.0.gz 2025-11-09 16:27:49 +08:00
qingfeng1121
d8c6c74de4 feat(文章/疯言疯语): 添加状态管理功能
实现文章和疯言疯语内容的状态管理,支持按状态查询和更新
允许公开访问文章查看接口
完善相关文档和日志记录
2025-11-08 11:16:14 +08:00
qingfeng1121
5136a3a78b feat: 添加随机内容模块并优化安全配置
新增Nonsense相关实体、DTO、Repository、Service和Controller,实现随机内容的CRUD功能
优化CORS和安全配置,增加更精细的权限控制和错误处理
移除Article和Message中不必要的验证注解,调整部分API的权限要求
2025-11-05 16:11:38 +08:00
qingfeng1121
25eeab4940 feat(security): 实现JWT认证并增强API安全控制
添加JWT依赖并实现token生成与验证功能
在控制器方法上添加权限注解保护API端点
更新安全配置以集成JWT过滤器
移除无用的编码测试工具类
修改JWT相关配置为更安全的设置
2025-11-03 16:14:53 +08:00
qingfeng1121
f6d1d719a9 feat: 添加UTF-8编码支持并优化DTO验证
refactor: 重构用户服务密码更新逻辑
fix: 删除不再使用的MarkdownDto类
style: 清理日志文件并优化日志配置
build: 更新pom.xml配置以支持UTF-8编码
docs: 更新application.properties配置文档
2025-10-30 19:00:47 +08:00
qingfeng1121
5803080352 feat(security): 重构安全配置并添加用户认证功能
refactor: 将ResponseMessage移动到config包并增强功能
feat: 添加用户管理相关功能及密码加密配置
fix: 修复HelpController中README文件路径问题
docs: 更新application.properties配置注释
style: 清理无用导入和日志文件
2025-10-28 12:47:02 +08:00
qingfeng1121
9132feb870 refactor(service): 重构服务接口和实现类结构
将服务接口从service包移动到service.imp包
修复ArticleRepository中viewCount的COALESCE处理
添加getPublishedArticles方法获取已发布文章
优化incrementViewCount方法使用仓库直接更新
修正HelpController中README_API.md路径
2025-10-26 20:18:35 +08:00
qingfeng1121
46be613f28 feat(消息): 添加消息点赞功能
- 在MessageController中添加点赞接口
- 在MessageRepository中添加点赞数更新方法
- 在IMessageService和MessageService中实现点赞逻辑
- 初始化测试数据时设置点赞数默认值为0
- 完善相关文档注释
2025-10-23 18:18:27 +08:00
qingfeng1121
f53e251d46 feat(消息): 添加回复ID字段支持消息回复功能
在Message和MessageDto中添加replyid字段,支持消息回复功能
添加删除所有评论的API端点
重构消息控制器方法顺序
```

```msg
feat(文章): 实现文章浏览量增加功能

添加incrementViewCount方法用于增加文章浏览量
在文章实体中添加likes字段记录点赞数
更新API文档说明新增字段
```

```msg
chore: 移除数据初始化类

注释掉CategoryDataInit和MessageDataInit类
这些初始化功能将由其他方式实现
2025-10-22 13:28:30 +08:00
qingfeng1121
848b13506c fix: 移除MessageDto中未使用的JPA注解并更新数据库配置
移除MessageDto中未使用的JPA注解以简化代码结构
在数据库连接URL中添加allowPublicKeyRetrieval参数以解决连接问题
清理过期的日志文件
2025-10-20 12:00:07 +08:00
qingfeng1121
effcc3838d refactor(pojo): 修正Article类中attributeid字段的列名拼写
feat(controller): 在ArticleController中添加根据属性ID获取文章的方法

style(repository): 在CategoryRepository方法上添加空行提高可读性

chore: 移除MyAfterProjecyApplication中多余的MapperScan注解
2025-10-19 11:11:56 +08:00
qingfeng1121
bd6b240f52 refactor: 删除未使用的Category_attribute接口
该接口未被项目使用且功能可由其他现有接口替代,移除以减少代码冗余
2025-10-18 10:29:23 +08:00
qingfeng1121
ffea3e85ae feat(分类属性): 实现分类属性管理功能
新增分类属性相关实体、DTO、仓库、服务及控制器
扩展文章服务以支持按属性查询文章
重构文章实体将typeid改为attributeid
添加按标题查询文章功能
2025-10-16 16:34:36 +08:00
qingfeng1121
8cc4c1da1d feat: 添加Category_attribute实体类和仓库接口
refactor(security): 限制文章更新仅限AUTHOR角色
修改ArticleController的updateArticle方法权限,移除ADMIN角色访问权限

chore: 更新application.properties中的Redis配置
取消注释Redis相关配置,包括缓存和连接池设置
2025-10-16 16:12:19 +08:00
qingfeng1121
2809837422 feat(分类模块): 实现分类管理功能
新增分类模块相关代码,包括实体类、DTO、Repository、Service和Controller
添加分类数据初始化逻辑和日志记录
实现分类的增删改查及搜索功能
2025-10-12 14:23:42 +08:00
qingfeng1121
299c9a57ec feat(API): 修改文章ID类型为String并添加CORS配置
将文章ID从Integer类型改为String类型以支持更灵活的ID格式
添加CORS配置类解决跨域问题,允许所有来源访问API
2025-10-11 13:32:41 +08:00
qingfeng1121
470cf71713 refactor(消息模块): 重构消息服务及控制器功能
重构消息模块,包括以下主要变更:
1. 将MessageRepository从CrudRepository扩展改为JpaRepository
2. 新增消息查询方法,支持按文章ID、父消息ID和昵称查询
3. 完善消息服务层逻辑,增加日志记录和错误处理
4. 扩展消息控制器API,新增获取根消息、回复消息等端点
5. 添加消息数据初始化组件和检查器
6. 优化全局异常处理,增加请求路径日志

同时调整文章模块:
1. 移除按作者查询文章功能
2. 统一分类查询参数命名
3. 优化文章服务层代码结构

配置变更:
1. 添加缓存相关依赖
2. 调整数据库连接配置
3. 暂时禁用Hibernate二级缓存
2025-10-11 11:17:12 +08:00
qingfeng1121
60f4752124 feat: 实现API文档支持与系统优化
refactor(ArticleRepository): 修正@Param注解导入错误并优化查询方法
fix(ArticleService): 解决事务回滚问题并优化日志配置
feat(SecurityConfig): 添加Spring Security配置禁用默认认证
docs: 添加详细API文档README_API.md
feat(HelpController): 实现Markdown文档渲染API
style: 清理无用注释和导入
build: 更新pom.xml依赖和插件配置
chore: 优化application.properties配置
2025-10-10 16:20:13 +08:00
qingfeng1121
fdb0608751 重构Article模块 新增Message模块 优化安全配置 2025-10-10 14:39:43 +08:00
qingfeng1121
92c604e1f5 新增全局报错处理统 优化代码结构 article基础代码完成 2025-10-09 17:22:00 +08:00
qingfeng1121
6841ba67f1 编写mvc基础代码添加ResponseMessage编写Message关键字 2025-10-09 12:43:41 +08:00
qingfeng1121
1d4dee573e 补全 2025-10-09 12:43:41 +08:00
qingfeng1121
f84c66b6db 补全 2025-10-09 12:43:41 +08:00
61 changed files with 10314 additions and 6 deletions

110
251010使用TRAEai.md Normal file
View File

@@ -0,0 +1,110 @@
# 技术问题总结文档
本文档总结了在项目开发和调试过程中遇到的主要技术问题、原因分析及解决方案。
## 一、事务回滚问题(核心问题)
### 问题描述
API接口调用时出现500错误响应信息为"Transaction silently rolled back because it has been marked as rollback-only"(事务被静默回滚,因为它已被标记为只能回滚)。
### 根本原因
通过详细分析,发现问题的根本原因是**@Param注解导入错误**
`ArticleRepository`接口中项目使用的是Spring Data JPA框架但错误地导入了MyBatis的`@Param`注解:
```java
import org.apache.ibatis.annotations.Param; // 错误的导入
```
这导致Spring Data JPA无法正确识别和绑定查询参数从而在执行数据库操作时抛出异常最终导致事务被标记为只能回滚。
### 解决方案
将MyBatis的`@Param`注解替换为Spring Data JPA正确的`@Param`注解:
```java
import org.springframework.data.repository.query.Param; // 正确的导入
```
## 二、方法参数与@Param注解不匹配问题
### 问题描述
`ArticleRepository`中存在多个查询方法的参数名称与`@Param`注解值不匹配的情况:
1. `findPublishedByAuthor`方法:
- 方法参数为`id`
- `@Param`注解值为`"authorId"`
-`Article`实体中不存在`authorId`字段
2. `findPublishedByCategory`方法:
- 方法参数为`typeid`
- `@Param`注解值为`"categoryId"`
- JPQL查询中使用`:categoryId`参数
### 解决方案
1. 对于`findPublishedByAuthor`方法:
- 由于`Article`实体中不存在`authorId`字段,移除了相关的查询条件和参数
- 仅保留状态筛选条件:`SELECT a FROM Article a WHERE a.status = 1`
- 修改方法签名为无参方法:`List<Article> findPublishedByAuthor()`
2. 对于`findPublishedByCategory`方法:
- 统一参数名称,修改方法参数名为`categoryId`
- 确保`@Param`注解值和JPQL查询参数名保持一致
## 三、事务配置问题
### 问题描述
`ArticleService``getArticleById`方法中,最初使用了`@Transactional(readOnly = true)`注解,但方法内部却调用了`incrementViewCount`写操作方法,导致事务冲突。
### 解决方案
尝试了两种方案:
1. 第一种方案:将`@Transactional(readOnly = true)`修改为普通的`@Transactional`注解,允许在事务内执行写操作
2. 第二种方案(最终采用):为了排查问题,暂时注释掉了`incrementViewCount`方法调用,将事务配置改回`@Transactional(readOnly = true)`
但根本问题解决后(修复了@Param注解导入错误),两种配置都可以正常工作。
## 四、配置文件优化问题
### 问题描述
项目的`application.properties`配置文件存在一些可以优化的地方,包括:
1. 数据库URL中的拼写错误
2. 未使用的MyBatis配置残留
3. 数据库连接池配置不完整
4. JPA性能配置缺失
5. Redis连接池配置不合理
6. 安全性配置不完善
### 解决方案
对配置文件进行了全面优化,主要包括:
1. **数据库配置优化**
- 修复了数据库URL拼写错误
- 添加了字符集、SSL、时区等连接参数
2. **移除不必要的配置**
- 删除了未使用的MyBatis配置
3. **性能优化配置**
- 完善了Hikari连接池配置
- 添加了JPA批量操作和缓存配置
- 优化了Redis连接池参数
4. **安全性增强**
- 限制了Actuator暴露的端点
- 完善了JWT和CORS配置
5. **添加必要的配置**
- 会话管理配置
- 国际化配置
- 响应编码配置
## 五、其他相关问题
### Article实体与查询方法不匹配
在排查过程中发现,`ArticleRepository`中的某些查询方法引用了`Article`实体中不存在的字段(如`authorId`),这表明在开发过程中可能存在实体设计与数据访问层不一致的情况。
### 日志级别配置
为了更好地排查问题调整了日志配置将核心包的日志级别设置为DEBUG同时限制了其他框架的日志输出避免日志信息过于冗长。
## 六、总结
本次问题排查和修复过程中,我们发现了多个相互关联的技术问题,其中最核心的问题是**@Param注解导入错误**。这一问题导致了一系列连锁反应,最终表现为事务回滚错误。
通过系统性地分析和解决这些问题我们不仅修复了API功能还优化了项目的整体配置和性能。这个过程也提醒我们在开发过程中要特别注意框架注解的正确使用以及保持代码各部分之间的一致性。

1191
README_API.md Normal file

File diff suppressed because it is too large Load Diff

3542
logs/web_project.log Normal file

File diff suppressed because it is too large Load Diff

69
pom.xml
View File

@@ -18,12 +18,24 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security 核心依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Security 配置 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<!-- AOP支持用于方法级安全控制 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
@@ -43,13 +55,36 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--参数校验-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Markdown解析库 -->
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.18.2</version>
</dependency>
<!-- JWT依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
@@ -61,6 +96,7 @@
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
@@ -68,7 +104,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
@@ -81,7 +117,8 @@
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.qf.myafterprojecy.MyAfterProjecyApplication</mainClass>
<skip>true</skip>
<skip>false</skip>
<jvmArguments>-Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8</jvmArguments>
</configuration>
<executions>
<execution>
@@ -92,7 +129,27 @@
</execution>
</executions>
</plugin>
<!-- 添加资源编码配置 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
<!-- 确保项目编译和资源处理使用UTF-8编码 -->
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
</build>
</project>

View File

@@ -0,0 +1,42 @@
package com.qf.myafterprojecy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.nio.charset.StandardCharsets;
/**
* 应用主类
* 设置系统编码并启动Spring Boot应用
*/
@SpringBootApplication
public class MyAfterProjecyApplication {
public static void main(String[] args) {
// 在应用启动前设置系统编码确保所有输出都使用UTF-8
setSystemEncoding();
// 启动Spring Boot应用
SpringApplication.run(MyAfterProjecyApplication.class, args);
}
/**
* 设置系统编码为UTF-8
* 解决控制台输出和日志中的中文乱码问题
*/
private static void setSystemEncoding() {
// 设置系统属性确保所有输出流都使用UTF-8编码
System.setProperty("file.encoding", StandardCharsets.UTF_8.name());
System.setProperty("sun.stdout.encoding", StandardCharsets.UTF_8.name());
System.setProperty("sun.stderr.encoding", StandardCharsets.UTF_8.name());
// 设置默认字符编码
try {
java.nio.charset.Charset.defaultCharset();
} catch (Exception e) {
// 记录编码设置异常
System.err.println("设置默认字符编码时发生异常: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,48 @@
package com.qf.myafterprojecy.config;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.filter.CharacterEncodingFilter;
import javax.servlet.Filter;
import java.nio.charset.StandardCharsets;
/**
* 字符编码配置类
* 确保所有HTTP请求和响应都使用UTF-8编码解决中文乱码问题
*/
@Configuration
public class CharacterEncodingConfig {
/**
* 创建字符编码过滤器
* 优先级设置为最高,确保在所有其他过滤器之前执行
*/
@Bean
public FilterRegistrationBean<Filter> characterEncodingFilter() {
// 创建字符编码过滤器
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
// 设置请求编码为UTF-8
encodingFilter.setEncoding(StandardCharsets.UTF_8.name());
// 强制请求使用UTF-8编码
encodingFilter.setForceRequestEncoding(true);
// 强制响应使用UTF-8编码
encodingFilter.setForceResponseEncoding(true);
// 创建过滤器注册Bean
FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>(encodingFilter);
// 设置过滤器顺序为最高优先级
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
// 为所有请求路径注册过滤器
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}

View File

@@ -0,0 +1,80 @@
package com.qf.myafterprojecy.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* CORS配置类用于解决跨域问题
*/
@Configuration
public class CorsConfig {
// 从配置文件中读取CORS配置
@Value("${cors.allowed-origins}")
private String allowedOrigins;
@Value("${cors.allowed-methods}")
private String allowedMethods;
@Value("${cors.allowed-headers}")
private String allowedHeaders;
@Value("${cors.allow-credentials}")
private Boolean allowCredentials;
@Value("${cors.max-age:3600}")
private Long maxAge;
/**
* 创建CORS过滤器配置跨域请求的规则
* 从配置文件中读取CORS配置实现配置的统一管理
*/
@Bean
public CorsFilter corsFilter() {
// 创建CORS配置对象
CorsConfiguration config = new CorsConfiguration();
// 允许的来源,从配置文件读取并分割
String[] originsArray = allowedOrigins.split(",");
for (String origin : originsArray) {
config.addAllowedOrigin(origin.trim());
}
// 允许携带凭证如Cookie
config.setAllowCredentials(allowCredentials);
// 允许的HTTP方法从配置文件读取并分割
String[] methodsArray = allowedMethods.split(",");
for (String method : methodsArray) {
config.addAllowedMethod(method.trim());
}
// 允许的请求头,从配置文件读取
config.addAllowedHeader(allowedHeaders);
// 明确暴露的响应头对于JWT认证很重要
config.addExposedHeader("Authorization");
config.addExposedHeader("Content-Type");
config.addExposedHeader("X-Requested-With");
config.addExposedHeader("Accept");
config.addExposedHeader("Access-Control-Allow-Origin");
config.addExposedHeader("Access-Control-Allow-Credentials");
// 设置预检请求的有效期(秒),从配置文件读取
config.setMaxAge(maxAge);
// 创建基于URL的CORS配置源
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 为所有路径应用CORS配置
source.registerCorsConfiguration("/**", config);
// 返回配置好的CORS过滤器
return new CorsFilter(source);
}
}

View File

@@ -0,0 +1,53 @@
package com.qf.myafterprojecy.config;
import com.qf.myafterprojecy.pojo.Users;
import com.qf.myafterprojecy.repository.UsersRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
/**
* 自定义的UserDetailsService实现
* 用于从数据库加载用户信息进行认证
*/
@Component
public class CustomUserDetailsService implements UserDetailsService {
private static final Logger logger = LoggerFactory.getLogger(CustomUserDetailsService.class);
@Autowired
private UsersRepository usersRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("用户登录认证: {}", username);
// 从数据库中查询用户
Users user = usersRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
// 转换用户角色为Spring Security的权限
// 根据role字段的值设置不同的角色权限
String role = "ROLE_USER"; // 默认角色
if (user.getRole() == 1) {
role = "ROLE_ADMIN"; // 管理员角色
}
List<SimpleGrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority(role));
// 返回Spring Security的User对象
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
authorities
);
}
}

View File

@@ -0,0 +1,96 @@
package com.qf.myafterprojecy.config;
import com.qf.myafterprojecy.utils.JwtUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* JWT认证过滤器用于验证token并授权用户
*/
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailsService userDetailsService;
@Value("${jwt.header:Authorization}")
private String tokenHeader;
@Value("${jwt.token-prefix:Bearer}")
private String tokenPrefix;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
// 获取token
String token = getTokenFromRequest(request);
System.out.println(token);
if (token != null && validateToken(token)) {
// 从token中获取用户名
String username = jwtUtils.getUsernameFromToken(token);
System.out.println("username: " + username);
// 加载用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 创建认证对象
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 设置认证信息到上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("无法设置用户认证: {}", e);
SecurityContextHolder.clearContext();
}
filterChain.doFilter(request, response);
}
/**
* 从请求头中获取token
*/
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader(tokenHeader);
if (bearerToken != null && bearerToken.startsWith(tokenPrefix + " ")) {
return bearerToken.substring(tokenPrefix.length() + 1);
}
return null;
}
/**
* 验证token
*/
private boolean validateToken(String token) {
try {
String username = jwtUtils.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return jwtUtils.validateToken(token, userDetails);
} catch (Exception e) {
logger.error("无效的token: {}", e);
return false;
}
}
}

View File

@@ -0,0 +1,26 @@
package com.qf.myafterprojecy.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* 密码编码器配置类
* 用于配置Spring Security使用的密码加密方式
*/
@Configuration
public class PasswordEncoderConfig {
/**
* 创建BCrypt密码编码器
* BCrypt是一种强哈希函数适合密码存储
* @return PasswordEncoder实例
*/
@Bean
public PasswordEncoder passwordEncoder() {
// 强度设置为10这是一个平衡安全性和性能的值
// 数值越高,计算成本越大,安全性越好
return new BCryptPasswordEncoder(10);
}
}

View File

@@ -0,0 +1,135 @@
package com.qf.myafterprojecy.config;
import org.springframework.http.HttpMethod;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.http.HttpServletResponse;
/**
* Spring Security配置类
* 配置权限管理功能
*/
@Configuration
@EnableWebSecurity
// 启用方法级别的安全控制
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
/**
* 配置AuthenticationManager Bean
* 使用AuthenticationConfiguration来获取认证管理器这是更现代的方式
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
/**
* 配置安全过滤器链
* @param http HttpSecurity对象用于配置HTTP安全策略
* @return 配置好的SecurityFilterChain对象
* @throws Exception 配置过程中可能出现的异常
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 启用CORS支持确保与CorsConfig中配置的过滤器配合工作
.cors().and()
// 禁用CSRF保护对于API服务通常不需要
.csrf().disable()
// 配置URL访问权限
.authorizeRequests()
// 允许公开访问的路径
// 登录和认证相关端点应该全部公开
.antMatchers(HttpMethod.POST,"/api/auth/**").permitAll()
// 文章浏览量增加接口公开
.antMatchers(HttpMethod.POST,"/api/articles/view/**").permitAll()
// 所有GET请求公开
.antMatchers(HttpMethod.GET,"/api/**").permitAll()
// 公开评论新增接口
.antMatchers(HttpMethod.POST,"/api/messages").permitAll()
// 新增、删除、修改操作需要管理员权限
.antMatchers(HttpMethod.POST,"/api/**").hasRole("ADMIN")
.antMatchers(HttpMethod.PUT,"/api/**").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE,"/api/**").hasRole("ADMIN")
// 管理员才能访问的路径
.antMatchers("/api/admin/**").hasRole("ADMIN")
// 其他所有请求都需要认证
.anyRequest().authenticated()
.and()
// 配置会话管理,使用无状态会话策略
// 这意味着每个请求都需要包含认证信息如JWT
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// 确保OPTIONS请求能够通过处理预检请求
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> {
// 设置CORS头信息
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Max-Age", "3600");
// 如果是预检请求直接返回200
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return;
}
// 未认证处理
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"message\": \"未授权访问,请先登录\"}");
});
// 添加JWT认证过滤器
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
// 确保Spring Security不会添加额外的CharacterEncodingFilter
// 因为我们在CharacterEncodingConfig中已经配置了自定义的过滤器
http.addFilterBefore((request, response, chain) -> {
// 确保响应使用UTF-8编码
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
chain.doFilter(request, response);
}, JwtAuthenticationFilter.class);
// 配置访问拒绝处理器
http.exceptionHandling()
.accessDeniedHandler((request, response, accessDeniedException) -> {
// 设置CORS头信息
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
// 无权限处理
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"message\": \"权限不足,无法访问\"}");
});
return http.build();
}
}

View File

@@ -0,0 +1,167 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Article;
import com.qf.myafterprojecy.pojo.dto.ArticleDto;
import com.qf.myafterprojecy.pojo.dto.ArriclePageDto;
import com.qf.myafterprojecy.service.IArticleService;
import org.springframework.data.domain.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 文章控制器类处理文章相关的HTTP请求
* 提供文章的增删改查功能,以及按作者、分类和浏览量获取文章的接口
*/
@RestController
@RequestMapping("/api/articles")
@Validated
public class ArticleController {
@Autowired
private IArticleService articleService; // 注入文章服务接口
/**
* 根据ID获取单个文章
* @param id 文章ID
* @return 返回包含文章信息的ResponseMessage对象
*/
@GetMapping("/{id}")
public ResponseMessage<Article> getArticle(@PathVariable String id) {
return articleService.getArticleById(id);
}
/**
* 获取已发布或未发布的文章列表
* @return 返回包含已发布文章列表的ResponseMessage对象
*/
@GetMapping("/published")
public ResponseMessage<List<Article>> getPublishedArticles() {
return articleService.getPublishedArticles();
}
/**
* 根据状态获取文章列表
* @param status 文章状态0未发表 1已发表 2已删除
* @return 返回包含文章列表的ResponseMessage对象
*/
@GetMapping("/status/{status}")
public ResponseMessage<List<Article>> getArticlesByStatus(@PathVariable Integer status) {
return articleService.getArticlesByStatus(status);
}
/**
* 根据状态分页获取文章列表
* @param status 文章状态0未发表 1已发表 2已删除
* @param page 页码从0开始可选默认为0
* @param size 每页大小可选默认为10最大为100
* @return 返回包含分页文章列表的ResponseMessage对象
*/
// api/articles/status/page?status=1&page=1&size=2
// get 只能这样不能传递json
@GetMapping("/status/page")
public ResponseMessage<Page<Article>> getArticlesByStatusWithPagination(ArriclePageDto pageDto) {
return articleService.getArticlesByStatusWithPagination(pageDto);
}
/**
* 获取所有文章列表
* @return 返回包含文章列表的ResponseMessage对象
*/
@GetMapping
public ResponseMessage<List<Article>> getAllArticles() {
return articleService.getAllArticles();
}
/**
* 获取文章数量
* @param status 文章状态0未发表 1已发表 2已删除
* @return 返回文章数量
*/
@GetMapping("/count/{status}")
public ResponseMessage<Integer> getArticleCount(@PathVariable Integer status) {
return articleService.getArticleCount(status);
}
/**
* 根据标题查询文章列表
* @param title 文章标题
* @return 返回包含文章列表的ResponseMessage对象
*/
@GetMapping("/title/{title}")
public ResponseMessage<List<Article>> getArticlesByTitle(@PathVariable String title) {
return articleService.getArticlesByTitle(title);
}
/**
* 根据属性ID获取该属性下的所有文章
* @param attributeId 属性ID
* @return 返回包含文章列表的ResponseMessage对象
*/
@GetMapping("/attribute/{attributeId}")
public ResponseMessage<List<Article>> getArticlesByAttribute(@PathVariable Integer attributeId) {
return articleService.getArticlesByAttribute(attributeId);
}
/**
* 根据属性ID获取最新文章按创建时间降序
* @param attributeId 属性ID
* @return 返回包含最新文章列表的ResponseMessage对象
*/
@GetMapping("/attribute/{attributeId}/latest")
public ResponseMessage<List<Article>> getLatestArticlesByAttribute(@PathVariable Integer attributeId) {
return articleService.getLatestArticlesByAttribute(attributeId);
}
/**
* 创建新文章
* 仅限AUTHOR角色用户访问
* @param articleDto 包含文章数据的DTO对象
* @return 返回包含新创建文章信息的ResponseMessage对象
*/
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Article> createArticle(@Valid @RequestBody ArticleDto articleDto) {
return articleService.saveArticle(articleDto);
}
/**文章浏览量
* 增加文章浏览量
* @param id 文章ID
* @return 返回包含更新后文章信息的ResponseMessage对象
*/
@PostMapping("/view/{id}")
public ResponseMessage<Article> incrementViewCount(@PathVariable Integer id) {
return articleService.incrementViewCount(id);
}
/**
* 更新现有文章
* 仅限AUTHOR角色用户访问
* @param id 要更新的文章ID
* @param articleDto 包含更新后文章数据的DTO对象
* @return 返回包含更新后文章信息的ResponseMessage对象
*/
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Article> updateArticle(
@PathVariable Integer id,
@Valid @RequestBody ArticleDto articleDto) {
return articleService.updateArticle(id, articleDto);
}
/**
* 删除文章
* 仅限AUTHOR或ADMIN角色用户访问
* @param id 要删除的文章ID
* @return 返回包含被删除文章信息的ResponseMessage对象
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Article> deleteArticle(@PathVariable Integer id) {
return articleService.deleteArticle(id);
}
}

View File

@@ -0,0 +1,130 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.utils.JwtUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 认证控制器
* 处理用户登录相关请求
*/
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private static final Logger logger = LoggerFactory.getLogger(AuthController.class);
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtils jwtUtils;
/**
* 用户登录请求体
*/
static class LoginRequest {
private String username;
private String password;
// getters and setters
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
/**
* 用户登录接口
* @param loginRequest 登录请求参数
* @return 登录结果
*/
@PostMapping("/login")
public ResponseMessage<Map<String, Object>> login(@RequestBody LoginRequest loginRequest) {
logger.info("用户登录请求: {}", loginRequest.getUsername());
try {
// 创建认证令牌
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
// 执行认证
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 将认证信息存入上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
// 获取认证后的用户信息
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
// 生成JWT token
String token = jwtUtils.generateToken(userDetails);
// 构建返回数据
Map<String, Object> data = new HashMap<>();
data.put("username", userDetails.getUsername());
data.put("authorities", userDetails.getAuthorities());
data.put("token", token);
data.put("tokenPrefix", jwtUtils.getTokenPrefix());
return ResponseMessage.success(data, "登录成功");
} catch (AuthenticationException e) {
logger.error("登录失败: {}", e.getMessage());
return ResponseMessage.error("用户名或密码错误");
}
}
/**
* 获取当前登录用户信息
* @return 当前用户信息
*/
@PostMapping("/info")
public ResponseMessage<Map<String, Object>> getCurrentUserInfo() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return ResponseMessage.error("未登录");
}
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
Map<String, Object> data = new HashMap<>();
data.put("username", userDetails.getUsername());
data.put("authorities", userDetails.getAuthorities());
return ResponseMessage.success(data, "获取用户信息成功");
}
/**
* 用户登出接口
* @return 登出结果
*/
@PostMapping("/logout")
public ResponseMessage<Void> logout() {
SecurityContextHolder.clearContext();
return ResponseMessage.successEmpty("登出成功");
}
}

View File

@@ -0,0 +1,109 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Categoryattribute;
import com.qf.myafterprojecy.pojo.dto.CategoryAttributeDto;
import com.qf.myafterprojecy.service.ICategoryAttributeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import javax.validation.Valid;
@RestController
@RequestMapping("/api/category-attributes")
@Validated
public class CategoryAttributeController {
private static final Logger log = LoggerFactory.getLogger(CategoryAttributeController.class);
@Autowired
private ICategoryAttributeService categoryAttributeService;
/**
* 根据ID获取分类属性
* @param id 属性ID
* @return 属性信息
*/
@GetMapping("/{id}")
public ResponseMessage<Categoryattribute> getAttributeById(@PathVariable Integer id) {
log.info("接收根据ID获取分类属性的请求: ID={}", id);
return categoryAttributeService.getCategoryAttributeById(id);
}
@GetMapping
public ResponseMessage<List<Categoryattribute>> getAttributeCount() {
log.info("接收获取分类属性数量的请求");
return categoryAttributeService.getAllCategoryAttributes();
}
/**
* 根据分类ID获取属性列表
* @param categoryId 分类ID
* @return 属性列表
*/
@GetMapping("/category/{categoryId}")
public ResponseMessage<List<Categoryattribute>> getAttributesByCategory(@PathVariable Integer categoryId) {
log.info("接收根据分类ID获取属性列表的请求: 分类ID={}", categoryId);
return categoryAttributeService.getAttributesByCategoryId(categoryId);
}
/**
* 创建新的分类属性
* @param dto 分类属性数据
* @return 创建结果
*/
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Categoryattribute> createAttribute(@Valid @RequestBody CategoryAttributeDto dto) {
log.info("接收创建分类属性的请求: 分类ID={}, 属性名称={}",
dto.getCategoryid(), dto.getAttributename());
return categoryAttributeService.saveCategoryAttribute(dto);
}
/**
* 更新分类属性
* @param id 属性ID
* @param dto 分类属性数据
* @return 更新结果
*/
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Categoryattribute> updateAttribute(
@PathVariable Integer id,
@Valid @RequestBody CategoryAttributeDto dto) {
log.info("接收更新分类属性的请求: ID={}, 分类ID={}, 属性名称={}",
id, dto.getCategoryid(), dto.getAttributename());
return categoryAttributeService.updateCategoryAttribute(id, dto);
}
/**
* 删除分类属性
* @param id 属性ID
* @return 删除结果
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Boolean> deleteAttribute(@PathVariable Integer id) {
log.info("接收删除分类属性的请求: ID={}", id);
return categoryAttributeService.deleteCategoryAttribute(id);
}
/**
* 检查分类下是否存在指定名称的属性
* @param categoryId 分类ID
* @param attributeName 属性名称
* @return 是否存在
*/
@GetMapping("/check-exists")
public ResponseMessage<Boolean> checkAttributeExists(
@RequestParam Integer categoryId,
@RequestParam String attributeName) {
log.info("接收检查分类属性是否存在的请求: 分类ID={}, 属性名称={}", categoryId, attributeName);
return categoryAttributeService.existsByCategoryAndName(categoryId, attributeName);
}
}

View File

@@ -0,0 +1,102 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Category;
import com.qf.myafterprojecy.pojo.dto.CategoryDto;
import com.qf.myafterprojecy.service.ICategoryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 分类控制器类处理分类相关的HTTP请求
* 提供分类的增删改查功能
*/
@RestController
@RequestMapping("/api/categories")
@Validated
public class CategoryController {
private static final Logger log = LoggerFactory.getLogger(CategoryController.class);
@Autowired
private ICategoryService categoryService;
/**
* 根据ID获取分类信息
* @param id 分类ID
* @return 返回分类信息
*/
@GetMapping("/{id}")
public ResponseMessage<Category> getCategoryById(@PathVariable Integer id) {
log.info("接收根据ID获取分类的请求: {}", id);
return categoryService.getCategoryById(id);
}
/**
* 获取所有分类列表
* @return 返回分类列表
*/
@GetMapping
public ResponseMessage<List<Category>> getAllCategories() {
log.info("接收获取所有分类列表的请求");
return categoryService.getAllCategories();
}
/**
* 创建新分类
* @param categoryDto 分类数据传输对象
* @return 返回创建结果
*/
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Category> createCategory(@Valid @RequestBody CategoryDto categoryDto) {
log.info("接收创建分类的请求: {}", categoryDto.getTypename());
return categoryService.saveCategory(categoryDto);
}
/**
* 更新分类信息
* @param id 分类ID
* @param categoryDto 分类数据传输对象
* @return 返回更新结果
*/
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Category> updateCategory(
@PathVariable Integer id,
@Valid @RequestBody CategoryDto categoryDto) {
log.info("接收更新分类的请求: ID={}, 分类名称={}", id, categoryDto.getTypename());
return categoryService.updateCategory(id, categoryDto);
}
/**
* 删除分类
* @param id 分类ID
* @return 返回删除结果
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Boolean> deleteCategory(@PathVariable Integer id) {
log.info("接收删除分类的请求: {}", id);
return categoryService.deleteCategory(id);
}
/**
* 根据分类名称搜索分类
* @param typename 分类名称
* @return 返回符合条件的分类列表
*/
@GetMapping("/search")
public ResponseMessage<List<Category>> searchCategoriesByTypename(@RequestParam String typename) {
log.info("接收根据名称搜索分类的请求: {}", typename);
return categoryService.searchCategoriesByTypename(typename);
}
}

View File

@@ -0,0 +1,74 @@
package com.qf.myafterprojecy.controller;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 帮助控制器类处理前端调用api/help请求
* 提供README_API.md文件的读取和返回功能
*/
@RestController
@RequestMapping("/api/help")
public class HelpController {
/**
* 获取README_API.md文件内容
* @return 返回包含README_API.md文件内容的ResponseMessage对象
*/
@GetMapping
public ResponseMessage<String> getReadmeApi() {
try {
// 获取项目根目录
String rootPath = System.getProperty("user.dir") ;
// 构建README_API.md文件路径
File readmeFile = new File(rootPath, "README_API.md");
// 检查文件是否存在
if (!readmeFile.exists() || !readmeFile.isFile()) {
// 如果不存在,尝试使用类路径资源加载
try {
ClassPathResource resource = new ClassPathResource("README_API.md");
String markdownContent = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()), StandardCharsets.UTF_8);
// 将Markdown转换为HTML
String htmlContent = convertMarkdownToHtml(markdownContent);
return ResponseMessage.success(htmlContent, "获取API文档成功");
} catch (IOException e) {
return ResponseMessage.error("未找到README_API.md文件");
}
}
// 读取文件内容
String markdownContent = new String(FileCopyUtils.copyToByteArray(new FileInputStream(readmeFile)), StandardCharsets.UTF_8);
// 将Markdown转换为HTML
String htmlContent = convertMarkdownToHtml(markdownContent);
return ResponseMessage.success(htmlContent, "获取API文档成功");
} catch (IOException e) {
return ResponseMessage.error("读取README_API.md文件失败: " + e.getMessage());
}
}
/**
* 将Markdown文本转换为HTML
* @param markdown 原始Markdown文本
* @return 转换后的HTML字符串
*/
private String convertMarkdownToHtml(String markdown) {
Parser parser = Parser.builder().build();
Node document = parser.parse(markdown);
HtmlRenderer renderer = HtmlRenderer.builder().build();
return renderer.render(document);
}
}

View File

@@ -0,0 +1,129 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Message;
import com.qf.myafterprojecy.pojo.dto.MessageDto;
import com.qf.myafterprojecy.pojo.dto.MessagePageDto;
import com.qf.myafterprojecy.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/messages")
public class MessageController {
private static final Logger logger = LoggerFactory.getLogger(MessageController.class);
@Autowired
private IMessageService messageService;
/**
* 获取所有消息
*/
@GetMapping
public ResponseMessage<Iterable<Message>> getAllMessages() {
logger.info("接收获取所有消息的请求");
return messageService.getAllMessages();
}
/**
* 分页查询消息
*/
@GetMapping("/page")
public ResponseMessage<List<Message>> getMessagesByPage(MessagePageDto messagePageDto) {
logger.info("接收分页查询消息的请求: {}", messagePageDto);
return messageService.getMessagesByPage(messagePageDto);
}
/**
* 获取消息数量
* @param articleId 文章ID (可选)
* @return 消息数量
* 文章ID为null时返回所有消息数量
*/
@GetMapping("/count")
public ResponseMessage<Integer> getMessageCount( Integer articleId) {
logger.info("接收获取消息数量的请求: {}", articleId);
return messageService.getMessageCountByArticleId(articleId);
}
/**
* 根据ID获取消息
*/
@GetMapping("/{id}")
public ResponseMessage<Message> getMessage(@PathVariable Integer id) {
logger.info("接收根据ID获取消息的请求: {}", id);
return messageService.getMessageById(id);
}
/**
* 根据文章ID获取消息列表
*/
@GetMapping("/article/{articleId}")
public ResponseMessage<List<Message>> getMessagesByArticleId(@PathVariable Integer articleId) {
logger.info("接收根据文章ID获取消息的请求: {}", articleId);
return messageService.getMessagesByArticleId(articleId);
}
/**
* 获取所有根消息(非回复的消息)
*/
@GetMapping("/root")
public ResponseMessage<List<Message>> getRootMessages() {
logger.info("接收获取所有根消息的请求");
return messageService.getRootMessages();
}
/**
* 根据父消息ID获取回复列表
*/
@GetMapping("/{parentId}/replies")
public ResponseMessage<List<Message>> getRepliesByParentId(@PathVariable Integer parentId) {
logger.info("接收根据父消息ID获取回复的请求: {}", parentId);
return messageService.getRepliesByParentId(parentId);
}
/**
* 根据昵称搜索消息
*/
@GetMapping("/search")
public ResponseMessage<List<Message>> searchMessagesByNickname(@RequestParam String nickname) {
logger.info("接收根据昵称搜索消息的请求: {}", nickname);
return messageService.searchMessagesByNickname(nickname);
}
/**
* 创建新消息
*/
@PostMapping
// 允许匿名用户发表评论
public ResponseMessage<Message> createMessage(@RequestBody MessageDto message) {
logger.info("接收创建消息的请求: {}", message != null ? message.getNickname() : "null");
return messageService.saveMessage(message);
}
// 点赞数增加
@PostMapping("/{id}/like")
public ResponseMessage<Message> likeMessage(@PathVariable Integer id) {
logger.info("接收点赞消息的请求: {}", id);
return messageService.likeMessage(id);
}
/**
* 根据ID删除消息
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public ResponseMessage<Message> deleteMessage(@PathVariable Integer id) {
logger.info("接收删除消息的请求: {}", id);
return messageService.deleteMessage(id);
}
//删除所有评论 - 仅管理员可操作
@DeleteMapping("/all")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Void> deleteAllMessages() {
logger.info("接收删除所有消息的请求");
return messageService.deleteAllMessages();
}
}

View File

@@ -0,0 +1,116 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Nonsense;
import com.qf.myafterprojecy.pojo.dto.NonsenseDto;
import com.qf.myafterprojecy.service.INonsenseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import java.util.List;
@RestController
@RequestMapping("/api/nonsense")
@Validated
public class NonsenseController {
private static final Logger logger = LoggerFactory.getLogger(NonsenseController.class);
@Autowired
private INonsenseService nonsenseService;
/**
* 获取所有疯言疯语内容
* @return 疯言疯语内容列表
*/
@GetMapping
public ResponseMessage<List<Nonsense>> getAllNonsense() {
logger.info("请求获取所有疯言疯语内容");
return nonsenseService.getAllNonsense();
}
/**
* 根据状态获取疯言疯语内容
* @param status 状态0未发表 1已发表 2已删除
* @return 疯言疯语内容列表
*/
@GetMapping("/status/{status}")
public ResponseMessage<List<Nonsense>> getNonsenseByStatus(
@PathVariable("status") Integer status) {
logger.info("请求获取状态为{}的疯言疯语内容", status);
return nonsenseService.getNonsenseByStatus(status);
}
/**
* 根据ID获取疯言疯语内容
* @param id 疯言疯语内容ID
* @return 疯言疯语内容
*/
@GetMapping("/{id}")
public ResponseMessage<Nonsense> getNonsenseById(@PathVariable("id") Integer id) {
logger.info("请求获取ID为{}的疯言疯语内容", id);
return nonsenseService.getNonsenseById(id);
}
/**
* 创建疯言疯语内容
* 需要管理员权限
* @param nonsenseDto 疯言疯语内容数据
* @return 创建结果
*/
@PostMapping
public ResponseMessage<Nonsense> saveNonsense(@Valid @RequestBody NonsenseDto nonsenseDto) {
logger.info("请求保存疯言疯语内容");
return nonsenseService.saveNonsense(nonsenseDto);
}
/**
* 更新疯言疯语内容
* 需要管理员权限
* @param id 疯言疯语内容ID
* @param nonsenseDto 疯言疯语内容数据
* @return 更新结果
*/
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Nonsense> updateNonsense(@PathVariable("id") Integer id, @Valid @RequestBody NonsenseDto nonsenseDto) {
logger.info("请求更新ID为{}的疯言疯语内容", id);
return nonsenseService.updateNonsense(id, nonsenseDto);
}
/**
* 删除疯言疯语内容
* 需要管理员权限
* @param id 疯言疯语内容ID
* @return 删除结果
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Boolean> deleteNonsense(@PathVariable("id") Integer id) {
logger.info("请求删除ID为{}的疯言疯语内容", id);
return nonsenseService.deleteNonsense(id);
}
/**
* 更新疯言疯语内容状态
* 需要管理员权限
* @param id 疯言疯语内容ID
* @param status 新状态0未发表 1已发表 2已删除
* @return 更新结果
*/
@PutMapping("/{id}/status/{status}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Nonsense> updateNonsenseStatus(
@PathVariable("id") Integer id,
@PathVariable("status") @Min(0) @Max(2) Integer status) {
logger.info("请求更新ID为{}的疯言疯语内容状态为{}", id, status);
return nonsenseService.updateNonsenseStatus(id, status);
}
}

View File

@@ -0,0 +1,141 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Users;
import com.qf.myafterprojecy.pojo.dto.UserDto;
import com.qf.myafterprojecy.service.IUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@Autowired
private IUserService userService;
/**
* 根据ID获取用户信息
* @param id 用户ID
* @return 用户信息
*/
@GetMapping("/{id}")
public ResponseMessage<Users> getUserById(@PathVariable Long id) {
logger.info("获取用户信息用户ID: {}", id);
return userService.getUserById(id);
}
/**
* 获取所有用户列表
* @return 用户列表
*/
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<List<Users>> getAllUsers() {
logger.info("获取所有用户列表");
return userService.getAllUsers();
}
/**
* 根据用户名获取用户信息
* @return 用户信息
*/
@GetMapping("/username/{username}")
@PreAuthorize("hasRole('ADMIN') or #username == authentication.name")
public ResponseMessage<Users> getUserByUsername(@PathVariable String username) {
logger.info("根据用户名获取用户信息,用户名: {}", username);
return userService.getUserByUsername(username);
}
/**
* 创建新用户
* @param userDto 用户数据
* @return 创建结果
*/
@PostMapping
public ResponseMessage<Users> saveUser(@Valid @RequestBody UserDto userDto) {
logger.info("创建新用户,用户名: {}", userDto.getUsername());
return userService.saveUser(userDto);
}
/**
* 更新用户信息
* @param id 用户ID
* @param userDto 用户数据
* @return 更新结果
*/
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public ResponseMessage<Users> updateUser(@PathVariable Long id, @Valid @RequestBody UserDto userDto) {
logger.info("更新用户信息用户ID: {}", id);
return userService.updateUser(id, userDto);
}
/**
* 删除用户
* @param id 用户ID
* @return 删除结果
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Boolean> deleteUser(@PathVariable Long id) {
logger.info("删除用户用户ID: {}", id);
return userService.deleteUser(id);
}
/**
* 根据角色查询用户列表
* @param role 角色
* @return 用户列表
*/
@GetMapping("/role/{role}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<List<Users>> getUsersByRole(@PathVariable int role) {
logger.info("根据角色查询用户列表,角色: {}", role);
return userService.getUsersByRole(role);
}
/**
* 检查用户名是否存在
* @param username 用户名
* @return 是否存在
*/
@GetMapping("/check/username/{username}")
public ResponseMessage<Boolean> existsByUsername(@PathVariable String username) {
logger.info("检查用户名是否存在,用户名: {}", username);
return userService.existsByUsername(username);
}
/**
* 检查邮箱是否存在
* @param email 邮箱
* @return 是否存在
*/
@GetMapping("/check/email/{email}")
public ResponseMessage<Boolean> existsByEmail(@PathVariable String email) {
logger.info("检查邮箱是否存在,邮箱: {}", email);
return userService.existsByEmail(email);
}
/**
* 检查手机号是否存在
* @param phone 手机号
* @return 是否存在
*/
@GetMapping("/check/phone/{phone}")
public ResponseMessage<Boolean> existsByPhone(@PathVariable String phone) {
logger.info("检查手机号是否存在,手机号: {}", phone);
return userService.existsByPhone(phone);
}
}

View File

@@ -0,0 +1,18 @@
package com.qf.myafterprojecy.exceptopn;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(value=Exception.class)
public ResponseMessage<String> handleException(Exception e, HttpServletRequest request) {
logger.error("请求路径:{},异常消息:{}",request.getRequestURI(),e.getMessage());
return new ResponseMessage<>(500,"服务器异常",e.getMessage());
}
}

View File

@@ -0,0 +1,257 @@
package com.qf.myafterprojecy.exceptopn;
import lombok.Data;
import org.springframework.http.HttpStatus;
/**
* 通用响应消息类,用于封装接口返回的数据结构
* 遵循RESTful API设计规范提供统一的响应格式
* @param <T> 数据类型可以是任意Java对象
*/
@Data
public class ResponseMessage<T> {
// 状态码,通常用于表示请求的处理结果
private Integer code;
// 响应消息,用于描述请求的处理结果信息
private String message;
// 请求是否成功的标志,根据状态码自动设置
private boolean success;
// 响应数据,泛型类型,支持不同类型的数据
private T data;
/**
* 构造方法,用于创建响应消息对象
* 自动根据状态码设置success字段
* @param code 状态码
* @param message 响应消息
* @param data 响应数据
*/
public ResponseMessage(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
// 自动根据状态码判断是否成功
this.success = code >= 200 && code < 300;
}
/**
* 完整参数的构造方法
* @param code 状态码
* @param message 响应消息
* @param data 响应数据
* @param success 是否成功
*/
public ResponseMessage(Integer code, String message, T data, boolean success) {
this.code = code;
this.message = message;
this.data = data;
this.success = success;
}
// ----------------------------------- 成功响应方法 -----------------------------------
/**
* 创建成功响应,默认消息为"操作成功"
* @param data 响应数据
* @param <T> 数据类型
* @return 成功响应对象
*/
public static <T> ResponseMessage<T> success(T data) {
return new ResponseMessage<>(HttpStatus.OK.value(), "操作成功", data, true);
}
/**
* 创建成功响应,自定义消息
* @param data 响应数据
* @param message 响应消息
* @param <T> 数据类型
* @return 成功响应对象
*/
public static <T> ResponseMessage<T> success(T data, String message) {
return new ResponseMessage<>(HttpStatus.OK.value(), message, data, true);
}
/**
* 创建成功响应,自定义状态码
* @param code 状态码
* @param message 响应消息
* @param data 响应数据
* @param <T> 数据类型
* @return 成功响应对象
*/
public static <T> ResponseMessage<T> successWithCode(Integer code, String message, T data) {
return new ResponseMessage<>(code, message, data, true);
}
/**
* 创建空数据的成功响应
* @param message 响应消息
* @param <T> 数据类型
* @return 成功响应对象
*/
public static <T> ResponseMessage<T> successEmpty(String message) {
return new ResponseMessage<>(HttpStatus.OK.value(), message, null, true);
}
// ----------------------------------- 错误响应方法 -----------------------------------
/**
* 创建错误响应默认状态码500
* @param message 错误消息
* @param <T> 数据类型
* @return 错误响应对象
*/
public static <T> ResponseMessage<T> error(String message) {
return new ResponseMessage<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), message, null, false);
}
/**
* 创建错误响应,自定义状态码
* @param code 状态码
* @param message 错误消息
* @param <T> 数据类型
* @return 错误响应对象
*/
public static <T> ResponseMessage<T> error(Integer code, String message) {
return new ResponseMessage<>(code, message, null, false);
}
/**
* 创建错误响应,包含错误数据
* @param code 状态码
* @param message 错误消息
* @param data 错误数据
* @param <T> 数据类型
* @return 错误响应对象
*/
public static <T> ResponseMessage<T> errorWithData(Integer code, String message, T data) {
return new ResponseMessage<>(code, message, data, false);
}
/**
* 创建参数错误响应状态码400
* @param message 错误消息
* @param <T> 数据类型
* @return 错误响应对象
*/
public static <T> ResponseMessage<T> badRequest(String message) {
return new ResponseMessage<>(HttpStatus.BAD_REQUEST.value(), message, null, false);
}
/**
* 创建未找到资源响应状态码404
* @param message 错误消息
* @param <T> 数据类型
* @return 错误响应对象
*/
public static <T> ResponseMessage<T> notFound(String message) {
return new ResponseMessage<>(HttpStatus.NOT_FOUND.value(), message, null, false);
}
/**
* 创建权限错误响应状态码403
* @param message 错误消息
* @param <T> 数据类型
* @return 错误响应对象
*/
public static <T> ResponseMessage<T> forbidden(String message) {
return new ResponseMessage<>(HttpStatus.FORBIDDEN.value(), message, null, false);
}
// ----------------------------------- 业务操作响应方法 -----------------------------------
/**
* 创建保存操作响应
* @param success 是否成功
* @param data 响应数据
* @param <T> 数据类型
* @return 操作响应对象
*/
public static <T> ResponseMessage<T> save(boolean success, T data) {
return success ?
new ResponseMessage<>(HttpStatus.OK.value(), "保存成功", data, true) :
new ResponseMessage<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), "保存失败", null, false);
}
/**
* 创建更新操作响应
* @param success 是否成功
* @param data 响应数据
* @param <T> 数据类型
* @return 操作响应对象
*/
public static <T> ResponseMessage<T> update(boolean success, T data) {
return success ?
new ResponseMessage<>(HttpStatus.OK.value(), "更新成功", data, true) :
new ResponseMessage<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), "更新失败", null, false);
}
/**
* 创建删除操作响应
* @param success 是否成功
* @param <T> 数据类型
* @return 操作响应对象
*/
public static <T> ResponseMessage<T> delete(boolean success) {
return success ?
new ResponseMessage<>(HttpStatus.OK.value(), "删除成功", null, true) :
new ResponseMessage<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), "删除失败", null, false);
}
/**
* 创建批量删除操作响应
* @param success 是否成功
* @param deletedCount 删除的数量
* @param <T> 数据类型
* @return 操作响应对象
*/
public static <T> ResponseMessage<T> batchDelete(boolean success, int deletedCount) {
return success ?
new ResponseMessage<>(HttpStatus.OK.value(), "成功删除" + deletedCount + "条数据", null, true) :
new ResponseMessage<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), "批量删除失败", null, false);
}
/**
* 创建分页查询响应
* @param data 分页数据
* @param message 响应消息
* @param <T> 数据类型
* @return 分页响应对象
*/
public static <T> ResponseMessage<T> page(T data, String message) {
return new ResponseMessage<>(HttpStatus.OK.value(), message, data, true);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}

View File

@@ -0,0 +1,65 @@
package com.qf.myafterprojecy.init;
import com.qf.myafterprojecy.pojo.Users;
import com.qf.myafterprojecy.repository.UsersRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Optional;
/**
* 用户数据初始化类
* 在应用启动时检查并创建管理员账号
*/
@Component
public class UserDataInit implements ApplicationRunner {
private static final Logger logger = LoggerFactory.getLogger(UserDataInit.class);
@Autowired
private UsersRepository usersRepository;
@Autowired
private PasswordEncoder passwordEncoder;
// 管理员账号信息
private static final String ADMIN_USERNAME = "qf1121";
private static final String ADMIN_PASSWORD = "qf1121";
private static final String ADMIN_EMAIL = "admin@qf1121.com";
private static final String ADMIN_PHONE = "13800138000";
private static final Integer ADMIN_ROLE = 1; // 1表示管理员角色
@Override
public void run(ApplicationArguments args) {
logger.info("开始检查管理员账号...");
// 检查管理员账号是否已存在
Optional<Users> adminUser = usersRepository.findByUsername(ADMIN_USERNAME);
if (adminUser.isPresent()) {
logger.info("管理员账号 {} 已存在,无需创建", ADMIN_USERNAME);
} else {
// 创建管理员账号
Users newAdmin = new Users();
newAdmin.setUsername(ADMIN_USERNAME);
// 加密密码
newAdmin.setPassword(passwordEncoder.encode(ADMIN_PASSWORD));
newAdmin.setEmail(ADMIN_EMAIL);
newAdmin.setPhone(ADMIN_PHONE);
newAdmin.setRole(ADMIN_ROLE);
newAdmin.setCreateTime(LocalDateTime.now());
try {
usersRepository.save(newAdmin);
logger.info("管理员账号 {} 创建成功", ADMIN_USERNAME);
} catch (Exception e) {
logger.error("创建管理员账号失败: {}", e.getMessage());
}
}
}
}

View File

@@ -0,0 +1,139 @@
package com.qf.myafterprojecy.pojo;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
@Entity
@Table(name = "article")
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "articleid")
private Integer articleid;
@NotBlank(message = "标题不能为空")
@Column(name = "title")
private String title;
@Column(name = "content", columnDefinition = "TEXT")
private String content;
@NotNull(message = "类别id不能为空")
@Column(name = "attribute_id")
private Integer attributeid;
@Column(name = "img")
private String img;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@Column(name = "view_count")
private Integer viewCount;
@Column(name = "likes")
private Integer likes; // 点赞数
@Column(name = "status")
private Integer status; // 0-草稿1-已发布2-已删除
@Column(name = "markdownscontent")
private String markdownscontent;
// Getters and Setters
public Integer getLikes() {
return likes;
}
public void setLikes(Integer likes) {
this.likes = likes;
}
public Integer getAttributeid() {
return attributeid;
}
public void setAttributeid(Integer attributeid) {
this.attributeid = attributeid;
}
public Integer getArticleid() {
return articleid;
}
public void setArticleid(Integer articleid) {
this.articleid = articleid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getImg() {
return img;
}
public void setImg(String img) {
this.img = img;
}
public Integer getViewCount() {
return viewCount;
}
public void setViewCount(Integer viewCount) {
this.viewCount = viewCount;
}
public String getMarkdownscontent() {
return markdownscontent;
}
public void setMarkdownscontent(String markdownscontent) {
this.markdownscontent = markdownscontent;
}
}

View File

@@ -0,0 +1,68 @@
package com.qf.myafterprojecy.pojo;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;
@Entity
@Table(name = "category")
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "categoryid")
private Integer categoryid;
@NotBlank(message = "分类名称不能为空")
@Column(name = "typename")
private String typename;
@Column(name = "description")
private String description;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// Getters and Setters
public Integer getCategoryid() {
return categoryid;
}
public void setCategoryid(Integer categoryid) {
this.categoryid = categoryid;
}
public String getTypename() {
return typename;
}
public void setTypename(String typename) {
this.typename = typename;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,42 @@
package com.qf.myafterprojecy.pojo;
import javax.persistence.*;
@Entity
@Table(name = "category_attribute")
public class Categoryattribute {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "attributeid")
private Integer attributeid;
@Column(name = "categoryid")
private Integer categoryid;
@Column(name = "attributename")
private String attributename;
public Integer getAttributeid() {
return attributeid;
}
public void setAttributeid(Integer attributeid) {
this.attributeid = attributeid;
}
public Integer getCategoryid() {
return categoryid;
}
public void setCategoryid(Integer categoryid) {
this.categoryid = categoryid;
}
public String getAttributename() {
return attributename;
}
public void setAttributename(String attributename) {
this.attributename = attributename;
}
}

View File

@@ -0,0 +1,122 @@
package com.qf.myafterprojecy.pojo;
import javax.persistence.*;
import java.util.Date;
@Entity
@Table(name = "message")
public class Message {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "messageid")
private Integer messageid;
@Column(name = "nickname")
private String nickname;
@Column(name = "email")
private String email;
@Column(name = "content", columnDefinition = "text")
private String content;
@Column(name = "messageimg")
private String messageimg;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_at")
private Date createdAt;
@Column(name = "parentid")
private Integer parentid;
@Column(name = "replyid")
private Integer replyid;
@Column(name = "articleid")
private Integer articleid;
@Column(name = "likes")
private Integer likes; // 点赞数
public Integer getLikes() {
return likes;
}
public void setLikes(Integer likes) {
this.likes = likes;
}
public Integer getReplyid() {
return replyid;
}
public void setReplyid(Integer replyid) {
this.replyid = replyid;
}
public Integer getMessageid() {
return messageid;
}
public void setMessageid(Integer messageid) {
this.messageid = messageid;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
public Integer getParentid() {
return parentid;
}
public void setParentid(Integer parentid) {
this.parentid = parentid;
}
public Integer getArticleid() {
return articleid;
}
public void setArticleid(Integer articleid) {
this.articleid = articleid;
}
public String getMessageimg() {
return messageimg;
}
public void setMessageimg(String messageimg) {
this.messageimg = messageimg;
}
}

View File

@@ -0,0 +1,46 @@
package com.qf.myafterprojecy.pojo;
import javax.persistence.*;
import java.util.Date;
@Entity
@Table(name = "nonsense")
public class Nonsense {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, unique = true)
private Integer id;
@Column(name = "content",nullable = false)
private String content;
@Column(name = "status",nullable = false)
private Integer status;//状态 0未发表 1已发表 2已删除
@Column(name = "time")
private Date time;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}

View File

@@ -0,0 +1,89 @@
package com.qf.myafterprojecy.pojo;
import java.time.LocalDateTime;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
@Entity
@Table(name = "users")
public class Users {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, unique = true)
private Long id;
@NotBlank(message = "用户名不能为空")
@Column(name = "username", nullable = false, unique = true)
private String username;
@NotBlank(message = "密码不能为空")
@Column(name = "password", nullable = false)
private String password;
@NotBlank(message = "邮箱不能为空")
@Column(name = "email", nullable = false, unique = true)
private String email;
@NotBlank(message = "手机号不能为空")
@Column(name = "phone", nullable = false, unique = true)
private String phone;
@Column(name = "role", nullable = false)
private int role;
@Column(name = "create_time", nullable = false)
private LocalDateTime createTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public int getRole() {
return role;
}
public void setRole(int role) {
this.role = role;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
}

View File

@@ -0,0 +1,55 @@
package com.qf.myafterprojecy.pojo.dto;
public class ArriclePageDto {
private Integer status;
private String title;
private Integer attributeid;
private Integer categoryid;
private Integer pagenum;
private Integer pagesize;
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getPagenum() {
return pagenum;
}
public void setPagenum(Integer pagenum) {
this.pagenum = pagenum;
}
public Integer getPagesize() {
return pagesize;
}
public void setPagesize(Integer pagesize) {
this.pagesize = pagesize;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Integer getAttributeid() {
return attributeid;
}
public void setAttributeid(Integer attributeid) {
this.attributeid = attributeid;
}
public Integer getCategoryid() {
return categoryid;
}
public void setCategoryid(Integer categoryid) {
this.categoryid = categoryid;
}
@Override
public String toString() {
return "ArPageDto{" +
"status=" + status +
", pagenum=" + pagenum +
", pagesize=" + pagesize +
'}';
}
}

View File

@@ -0,0 +1,99 @@
package com.qf.myafterprojecy.pojo.dto;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class ArticleDto {
private Integer articleid;
@NotBlank(message = "标题不能为空")
private String title;
private String content;// 如果为空说明是长篇文章 不为空则是短篇说说
@NotNull(message = "属性ID不能为空")
private Integer attributeid;
private String img;
private Integer viewCount;
private Integer likes;
private Integer status;
private String markdownscontent; // 文章内容的Markdown格式
// Getters and Setters
public Integer getArticleid() {
return articleid;
}
public void setArticleid(Integer articleid) {
this.articleid = articleid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Integer getAttributeid() {
return attributeid;
}
public void setAttributeid(Integer attributeid) {
this.attributeid = attributeid;
}
public String getImg() {
return img;
}
public void setImg(String img) {
this.img = img;
}
public Integer getViewCount() {
return viewCount;
}
public void setViewCount(Integer viewCount) {
this.viewCount = viewCount;
}
public Integer getLikes() {
return likes;
}
public void setLikes(Integer likes) {
this.likes = likes;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMarkdownscontent() {
return markdownscontent;
}
public void setMarkdownscontent(String markdownscontent) {
this.markdownscontent = markdownscontent;
}
}

View File

@@ -0,0 +1,39 @@
package com.qf.myafterprojecy.pojo.dto;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class CategoryAttributeDto {
private Integer attributeid;
@NotNull(message = "分类ID不能为空")
private Integer categoryid;
@NotBlank(message = "属性名称不能为空")
private String attributename;
// Getters and Setters
public Integer getAttributeid() {
return attributeid;
}
public void setAttributeid(Integer attributeid) {
this.attributeid = attributeid;
}
public Integer getCategoryid() {
return categoryid;
}
public void setCategoryid(Integer categoryid) {
this.categoryid = categoryid;
}
public String getAttributename() {
return attributename;
}
public void setAttributename(String attributename) {
this.attributename = attributename;
}
}

View File

@@ -0,0 +1,58 @@
package com.qf.myafterprojecy.pojo.dto;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;
public class CategoryDto {
private Integer Categoryid;
@NotBlank(message = "分类名称不能为空")
private String typename;
private String description;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// Getters and Setters
public Integer getCategoryid() {
return Categoryid;
}
public void setCategoryid(Integer Categoryid) {
this.Categoryid = Categoryid;
}
public String getTypename() {
return typename;
}
public void setTypename(String typename) {
this.typename = typename;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,105 @@
package com.qf.myafterprojecy.pojo.dto;
import java.util.Date;
public class MessageDto {
private Integer messageid;
private String nickname;
private String email;
private String content;
private Date createdAt;
private Integer parentid;
private Integer replyid;
private Integer articleid;
private Integer likes;
private String messageimg;
public Integer getReplyid() {
return replyid;
}
public void setReplyid(Integer replyid) {
this.replyid = replyid;
}
public Integer getMessageid() {
return messageid;
}
public void setMessageid(Integer messageid) {
this.messageid = messageid;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
public Integer getParentid() {
return parentid;
}
public void setParentid(Integer parentid) {
this.parentid = parentid;
}
public Integer getArticleid() {
return articleid;
}
public void setArticleid(Integer articleid) {
this.articleid = articleid;
}
public Integer getLikes() {
return likes;
}
public void setLikes(Integer likes) {
this.likes = likes;
}
public String getMessageimg() {
return messageimg;
}
public void setMessageimg(String messageimg) {
this.messageimg = messageimg;
}
}

View File

@@ -0,0 +1,34 @@
package com.qf.myafterprojecy.pojo.dto;
public class MessagePageDto {
private Integer pageNum;
private Integer pageSize;
private Integer articleid;
public Integer getPageNum() {
return pageNum;
}
public void setPageNum(Integer pageNum) {
this.pageNum = pageNum;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public Integer getArticleid() {
return articleid;
}
public void setArticleid(Integer articleid) {
this.articleid = articleid;
}
@Override
public String toString() {
return "MessagePageDto{" +
"pageNum=" + pageNum +
", pageSize=" + pageSize +
", articleid=" + articleid +
'}';
}
}

View File

@@ -0,0 +1,46 @@
package com.qf.myafterprojecy.pojo.dto;
import java.util.Date;
public class NonsenseDto {
private Integer id;
private String content;
private Integer status;//状态 0未发表 1已发表 2已删除
private Date time;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
}

View File

@@ -0,0 +1,69 @@
package com.qf.myafterprojecy.pojo.dto;
import javax.validation.constraints.NotBlank;
public class UserDto {
private Long id;
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
@NotBlank(message = "邮箱不能为空")
private String email;
@NotBlank(message = "手机号不能为空")
private String phone;
private int role;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public int getRole() {
return role;
}
public void setRole(int role) {
this.role = role;
}
}

View File

@@ -0,0 +1,135 @@
package com.qf.myafterprojecy.repository;
import com.qf.myafterprojecy.pojo.Article;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository // 表明这是一个数据访问层组件,用于持久层操作
public interface ArticleRepository extends JpaRepository<Article, Integer> {
/**
* 根据文章ID查询文章信息的方法
* 使用JPQLJava Persistence Query Language进行查询
*
* @param id 文章的唯一标识符,作为查询条件
* @return 返回一个Optional<Article>对象可能包含文章信息也可能为空如果未找到对应ID的文章
*/
@Query("SELECT a FROM Article a WHERE a.articleid = :id")
Optional<Article> findById(@Param("id") Integer id);
/**
* 根据标题查询文章列表
*
* @param title 文章标题的一部分,用于模糊查询
* @return 返回符合查询条件的文章列表
*/
@Query("SELECT a FROM Article a WHERE a.title LIKE %:title%")
List<Article> findByTitle(@Param("title") String title);
/**
* 根据文章ID查询已发布的文章
* 使用JPQL查询语句只查询状态为1已发布且指定ID的文章
*
* @return 返回符合查询条件的文章列表
*/
@Query("SELECT a FROM Article a WHERE a.status = 1")
List<Article> findPublishedByAuthor();
/**
* 根据分类ID查询已发布的文章列表
* 使用JPQL查询语句筛选状态为已发布(status=1)且指定分类(typeid)的文章
*
* @param attributeid 分类ID通过@Param注解映射到查询语句中的:attributeid参数
* @return 返回符合条件Article对象的列表
*/
@Query("SELECT a FROM Article a WHERE a.status = 1 AND a.attributeid = :attributeid")
List<Article> findPublishedByAttribute(@Param("attributeid") Integer attributeid);
/**
* 根据属性ID查询最新的文章列表
* 使用JPQL查询语句筛选状态为已发布(status=1)且指定属性(attributeid)的文章,按创建时间降序排序
*
* @param attributeid 属性ID通过@Param注解映射到查询语句中的:attributeid参数
* @return 返回符合条件Article对象的列表按创建时间降序排列
*/
@Query("SELECT a FROM Article a WHERE a.status = 1 AND a.attributeid = :attributeid ORDER BY a.createdAt DESC")
List<Article> findLatestByAttribute(@Param("attributeid") Integer attributeid);
/**
* 使用@Modifying注解标记这是一个修改操作通常用于UPDATE或DELETE语句
* 使用@Query注解定义自定义的JPQL查询语句
* 该查询用于将指定文章的浏览量(viewCount)增加1
*
* @param articleid 文章的唯一标识符,通过@Param注解将方法参数与查询参数绑定
*/
@Modifying
@Query("UPDATE Article a SET a.viewCount = COALESCE(a.viewCount, 0) + 1 WHERE a.articleid = :articleid")
void incrementViewCount(@Param("articleid") Integer articleid);
/**
* 根据浏览量降序查询状态为1的所有文章
* 该查询使用JPQL语句从Article实体中选取数据
*
* @return 返回一个Article对象的列表按浏览量(viewCount)降序排列
*/
@Query("SELECT a FROM Article a WHERE a.status = 1 ORDER BY a.viewCount DESC")
List<Article> findMostViewed();
/**
* 根据状态查询文章列表
* @param status 文章状态0-草稿1-已发布2-已删除
* @return 返回符合状态条件的文章列表
*/
@Query("SELECT a FROM Article a WHERE a.status = :status")
List<Article> findByStatus(@Param("status") Integer status);
/**
* 根据状态分页查询文章列表
* @param status 文章状态0-草稿1-已发布2-已删除
* @param pageable 分页参数,包含页码、每页大小和排序信息
* @return 返回符合状态条件的文章分页结果
*/
@Query("SELECT a FROM Article a WHERE a.status = :status ORDER BY a.createdAt DESC")
Page<Article> findByStatusWithPagination(@Param("status") Integer status, Pageable pageable);
/**
* 根据属性ID数组分页查询文章列表
* @param attributeids 文章属性ID数组
* @param status 文章状态0-草稿1-已发布2-已删除
* @param pageable 分页参数,包含页码、每页大小和排序信息
* @return 返回符合状态条件的文章分页结果
*/
@Query("SELECT a FROM Article a WHERE a.status = :status AND a.attributeid IN :attributeids ORDER BY a.createdAt DESC")
Page<Article> findByStatusWithPagination(@Param("status") Integer status, @Param("attributeids") List<Integer> attributeids, Pageable pageable);
/**
* 根据属性ID分页查询文章列表
* @param attributeid 文章属性ID
* @param status 文章状态0-草稿1-已发布2-已删除
* @param pageable 分页参数,包含页码、每页大小和排序信息
* @return 返回符合状态条件的文章分页结果
*/
@Query("SELECT a FROM Article a WHERE a.status = :status AND a.attributeid = :attributeid ORDER BY a.createdAt DESC")
Page<Article> findByStatusWithPagination(@Param("status") Integer status, @Param("attributeid") Integer attributeid, Pageable pageable);
/**
* 根据文章标题分页模糊查询文章列表
* @param title 文章标题
* @param status 文章状态0-草稿1-已发布2-已删除
* @param pageable 分页参数,包含页码、每页大小和排序信息
* @return 返回符合状态条件的文章分页结果
*/
@Query("SELECT a FROM Article a WHERE a.status = :status AND a.title LIKE %:title% ORDER BY a.createdAt DESC")
Page<Article> findByStatusWithPagination(@Param("status") Integer status, @Param("title") String title, Pageable pageable);
/**
* 统计指定状态的文章数量
* @param status 文章状态0-草稿1-已发布2-已删除
* @return 返回符合状态条件的文章数量
*/
@Query("SELECT COUNT(a) FROM Article a WHERE a.status = :status")
Integer countByStatus(@Param("status") Integer status);
}

View File

@@ -0,0 +1,46 @@
package com.qf.myafterprojecy.repository;
import com.qf.myafterprojecy.pojo.Categoryattribute;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface CategoryAttributeRepository extends JpaRepository<Categoryattribute, Integer> {
/**
* 根据分类ID查询分类属性列表
* @param categoryid 分类ID
* @return 返回该分类下的所有属性列表
*/
@Query("SELECT ca FROM Categoryattribute ca WHERE ca.categoryid = :categoryid")
List<Categoryattribute> findByCategoryId(@Param("categoryid") Integer categoryid);
/**
* 根据属性ID查询属性信息
* @param attributeid 属性ID
* @return 返回属性对象
*/
@Query("SELECT ca FROM Categoryattribute ca WHERE ca.attributeid = :attributeid")
Optional<Categoryattribute> findByAttributeId(@Param("attributeid") Integer attributeid);
/**
* 检查分类下是否存在指定名称的属性
* @param categoryid 分类ID
* @param attributename 属性名称
* @return 是否存在
*/
boolean existsByCategoryidAndAttributename(Integer categoryid, String attributename);
/**
* 根据分类ID和属性名称查询属性
* @param categoryid 分类ID
* @param attributename 属性名称
* @return 属性对象
*/
Optional<Categoryattribute> findByCategoryidAndAttributename(Integer categoryid, String attributename);
}

View File

@@ -0,0 +1,26 @@
package com.qf.myafterprojecy.repository;
import com.qf.myafterprojecy.pojo.Category;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface CategoryRepository extends JpaRepository<Category, Integer> {
/**
* 根据分类名称查询分类信息
* @param typename 分类名称
* @return 返回符合条件的分类列表
*/
List<Category> findByTypenameContaining(String typename);
/**
* 检查分类名称是否存在
* @param typename 分类名称
* @return 返回是否存在
*/
boolean existsByTypename(String typename);
}

View File

@@ -0,0 +1,96 @@
package com.qf.myafterprojecy.repository;
import com.qf.myafterprojecy.pojo.Message;
import org.springframework.boot.autoconfigure.data.web.SpringDataWebProperties.Pageable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.Modifying;
import java.util.List;
@Repository
public interface MessageRepository extends JpaRepository<Message, Integer> {
// 根据文章ID查询消息
/**
* 根据文章ID查询消息
* @param articleid 文章ID
* @return 文章下的消息列表
*/
List<Message> findByArticleid(Integer articleid);
// 查询所有父消息(回复的根消息)
/**
* 查询所有父消息(回复的根消息)
* @return 根回复消息列表
*/
List<Message> findByParentidIsNull();
// 根据父消息ID查询回复
/**
* 根据父消息ID查询回复
* @param parentid 父消息ID
* @return 回复消息列表
*/
List<Message> findByParentid(Integer parentid);
// 根据昵称模糊查询消息
/**
* 根据昵称模糊查询消息
* @param nickname 昵称关键词
* @return 包含关键词的消息列表
*/
List<Message> findByNicknameContaining(String nickname);
/**
* 查询指定文章下的所有父消息(根回复)
* @param articleId 文章ID
* @return 根回复消息列表
*/
@Query("SELECT m FROM Message m WHERE m.articleid = :articleId AND m.parentid IS NULL ORDER BY m.createdAt DESC")
List<Message> findRootMessagesByArticleId(@Param("articleId") Integer articleId);
/**
* 点赞数增加
* @param messageId 消息ID
*/
@Modifying
@Query("UPDATE Message m SET m.likes = COALESCE(m.likes, 0) + 1 WHERE m.messageid = :messageId")
void incrementLikes(@Param("messageId") Integer messageId);
// 统计指定文章的评论数量
/**
* 根据文章ID分页查询消息
* @param articleid 文章ID
* @param pageable 分页信息
* @return 分页消息列表
*/
@Query("SELECT m FROM Message m WHERE m.articleid = :articleId ORDER BY m.createdAt DESC")
Page<Message> findByArticleId(@Param("articleId") Integer articleId, PageRequest pageable);
/**
* 根据页查询消息
* @param pageable 分页信息
* @return 分页消息列表
*/
@Query("SELECT m FROM Message m WHERE m.articleid IS NULL ORDER BY m.createdAt DESC")
Page<Message> findAllMessages(PageRequest pageable);
/**
* 统计指定文章下的回复消息数量
* @param articleId 文章ID
* @return 回复消息数量
*/
@Query("SELECT COUNT(m) FROM Message m WHERE m.articleid = :articleId AND m.parentid IS NULL")
Integer countReplyByArticleId(@Param("articleId") Integer articleId);
/**
* 统计指定文章id parentid为空的回复消息数量
* @param articleId 文章ID
* @return 回复消息数量
*/
@Query("SELECT COUNT(m) FROM Message m WHERE m.articleid IS NULL AND m.parentid IS NULL")
Integer countReplyByArticleIdIsNull();
}

View File

@@ -0,0 +1,21 @@
package com.qf.myafterprojecy.repository;
import com.qf.myafterprojecy.pojo.Nonsense;
import java.util.List;
import org.springframework.data.repository.query.Param;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
@Repository
public interface NonsenseRepository extends JpaRepository<Nonsense, Integer> {
/**
* 根据状态获取文章列表
* @param status 文章状态0未发表 1已发表 2已删除
* @return 返回包含文章列表的ResponseMessage对象
*/
@Query("SELECT n FROM Nonsense n WHERE n.status = :status")
List<Nonsense> findByStatus(@Param("status") Integer status);
}

View File

@@ -0,0 +1,63 @@
package com.qf.myafterprojecy.repository;
import com.qf.myafterprojecy.pojo.Users;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
@Repository
public interface UsersRepository extends JpaRepository<Users, Long> {
/**
* 根据用户名查询用户信息
* @param username 用户名
* @return 返回符合条件的用户对象
*/
Optional<Users> findByUsername(String username);
/**
* 根据邮箱查询用户信息
* @param email 邮箱
* @return 返回符合条件的用户对象
*/
Optional<Users> findByEmail(String email);
/**
* 根据手机号查询用户信息
* @param phone 手机号
* @return 返回符合条件的用户对象
*/
Optional<Users> findByPhone(String phone);
/**
* 检查用户名是否存在
* @param username 用户名
* @return 返回是否存在
*/
boolean existsByUsername(String username);
/**
* 检查邮箱是否存在
* @param email 邮箱
* @return 返回是否存在
*/
boolean existsByEmail(String email);
/**
* 检查手机号是否存在
* @param phone 手机号
* @return 返回是否存在
*/
boolean existsByPhone(String phone);
/**
* 根据角色查询用户列表
* @param role 角色
* @return 用户列表
*/
@Query("SELECT u FROM Users u WHERE u.role = :role")
java.util.List<Users> findByRole(@Param("role") int role);
}

View File

@@ -0,0 +1,108 @@
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Article;
import com.qf.myafterprojecy.pojo.dto.ArriclePageDto;
import com.qf.myafterprojecy.pojo.dto.ArticleDto;
import org.springframework.data.domain.Page;
import java.util.List;
public interface IArticleService {
ResponseMessage<Article> getArticleById(String id);
ResponseMessage<List<Article>> getAllArticles();
/**
* 根据标题查询文章列表
*
* @param title 文章标题的一部分,用于模糊查询
* @return 返回符合查询条件的文章列表
*/
ResponseMessage<List<Article>> getArticlesByTitle(String title);
/**
* 根据状态获取文章列表
* @param status 文章状态0未发表 1已发表 2已删除
* @return 返回包含文章列表的ResponseMessage对象
*/
ResponseMessage<List<Article>> getArticlesByStatus(Integer status);
/**
* 获取文章数量
* @param status 文章状态0未发表 1已发表 2已删除
* @return 返回文章数量
*/
ResponseMessage<Integer> getArticleCount(Integer status);
/**
* 创建新文章
* 仅限AUTHOR角色用户访问
*
* @param articleDto 包含文章数据的DTO对象
* @return 返回包含新创建文章信息的ResponseMessage对象
*/
ResponseMessage<Article> saveArticle(ArticleDto articleDto);
/**
* 更新指定ID的文章
*
* @param id 文章ID
* @param articleDto 包含更新信息的ArticleDto对象
* @return 返回包含操作结果的ResponseMessage对象
*/
ResponseMessage<Article> updateArticle(Integer id, ArticleDto articleDto);
/**
* 删除指定ID的文章
*
* @param id 文章ID
* @return 返回包含操作结果的ResponseMessage对象
*/
ResponseMessage<Article> deleteArticle(Integer id);
/**
* 根据分类ID查询文章列表兼容旧接口
*
* @param typeid 分类ID
* @return 返回符合查询条件的文章列表
*/
ResponseMessage<List<Article>> getArticlesByCategory(Integer typeid);
/**
* 根据属性ID查询文章列表
*
* @param attributeid 属性ID
* @return 返回符合查询条件的文章列表
*/
ResponseMessage<List<Article>> getArticlesByAttribute(Integer attributeid);
/**
* 根据属性ID查询最新文章列表
*
* @param attributeid 属性ID
* @return 返回符合查询条件的最新文章列表
*/
ResponseMessage<List<Article>> getLatestArticlesByAttribute(Integer attributeid);
ResponseMessage<List<Article>> getMostViewedArticles();
/**
* 增加文章浏览量
*
* @param id 文章ID
* @return 返回包含更新后文章信息的ResponseMessage对象
*/
ResponseMessage<Article> incrementViewCount(Integer id);
/**
* 获取已发布的文章列表
* @return 返回包含已发布文章列表的ResponseMessage对象
*/
ResponseMessage<List<Article>> getPublishedArticles();
/**
* 根据状态分页查询文章列表
* @param status 文章状态0未发表 1已发表 2已删除
* @param page 页码从0开始
* @param size 每页大小
* @return 返回包含分页文章列表的ResponseMessage对象
*/
ResponseMessage<Page<Article>> getArticlesByStatusWithPagination(ArriclePageDto arriclePageDto);
}

View File

@@ -0,0 +1,59 @@
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Categoryattribute;
import com.qf.myafterprojecy.pojo.dto.CategoryAttributeDto;
import java.util.List;
public interface ICategoryAttributeService {
/**
* 获取全部分类属性
* @return 所有分类属性列表
*/
ResponseMessage<List<Categoryattribute>> getAllCategoryAttributes();
/**
* 根据ID获取分类属性
* @param id 属性ID
* @return 分类属性信息
*/
ResponseMessage<Categoryattribute> getCategoryAttributeById(Integer id);
/**
* 根据分类ID获取属性列表
* @param categoryId 分类ID
* @return 属性列表
*/
ResponseMessage<List<Categoryattribute>> getAttributesByCategoryId(Integer categoryId);
/**
* 创建新的分类属性
* @param dto 分类属性数据
* @return 创建结果
*/
ResponseMessage<Categoryattribute> saveCategoryAttribute(CategoryAttributeDto dto);
/**
* 更新分类属性
* @param id 属性ID
* @param dto 分类属性数据
* @return 更新结果
*/
ResponseMessage<Categoryattribute> updateCategoryAttribute(Integer id, CategoryAttributeDto dto);
/**
* 删除分类属性
* @param id 属性ID
* @return 删除结果
*/
ResponseMessage<Boolean> deleteCategoryAttribute(Integer id);
/**
* 检查分类下是否存在指定名称的属性
* @param categoryId 分类ID
* @param attributeName 属性名称
* @return 是否存在
*/
ResponseMessage<Boolean> existsByCategoryAndName(Integer categoryId, String attributeName);
}

View File

@@ -0,0 +1,51 @@
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Category;
import com.qf.myafterprojecy.pojo.dto.CategoryDto;
import java.util.List;
public interface ICategoryService {
/**
* 根据ID获取分类信息
* @param id 分类ID
* @return 返回分类信息
*/
ResponseMessage<Category> getCategoryById(Integer id);
/**
* 获取所有分类列表
* @return 返回分类列表
*/
ResponseMessage<List<Category>> getAllCategories();
/**
* 保存新分类
* @param categoryDto 分类数据传输对象
* @return 返回保存结果
*/
ResponseMessage<Category> saveCategory(CategoryDto categoryDto);
/**
* 更新分类信息
* @param id 分类ID
* @param categoryDto 分类数据传输对象
* @return 返回更新结果
*/
ResponseMessage<Category> updateCategory(Integer id, CategoryDto categoryDto);
/**
* 删除分类
* @param id 分类ID
* @return 返回删除结果
*/
ResponseMessage<Boolean> deleteCategory(Integer id);
/**
* 根据分类名称搜索分类
* @param typename 分类名称
* @return 返回符合条件的分类列表
*/
ResponseMessage<List<Category>> searchCategoriesByTypename(String typename);
}

View File

@@ -0,0 +1,99 @@
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Message;
import com.qf.myafterprojecy.pojo.dto.MessageDto;
import com.qf.myafterprojecy.pojo.dto.MessagePageDto;
import java.util.List;
public interface IMessageService {
/**
* 获取所有消息的方法
*
* @return 返回一个ResponseMessage对象其中包含一个可迭代的Message集合
* ResponseMessage是响应消息的包装类Iterable<Message>表示可迭代的消息集合
*/
ResponseMessage<Iterable<Message>> getAllMessages();
/**
* 分页
* @param id
* @return
*/
ResponseMessage<List<Message>> getMessagesByPage(MessagePageDto messagePageDto);
/**
* 获取回复消息条数 如果id为空获取文章id为空的消息条数
* @param articleId 文章id
* @return 回复消息条数
*/
ResponseMessage<Integer> getMessageCountByArticleId(Integer articleId);
/**
* 根据消息ID获取消息的方法
*
* @param id 消息的唯一标识符
* @return ResponseMessage<Message> 包含消息的响应对象其中Message为消息的具体内容
*/
ResponseMessage<Message> getMessageById(Integer id);
/**
* 保存消息的方法
*
* @param message 消息数据传输对象,包含需要保存的消息信息
* @return ResponseMessage<Message> 返回一个响应消息对象,包含操作结果和保存后的消息信息
*/
ResponseMessage<Message> saveMessage(MessageDto message);
/**
* 根据消息ID删除消息的方法
*
* @param id 要删除的消息ID
* @return ResponseMessage<Message> 包含操作结果的响应消息其中泛型Message表示被删除的消息内容
*/
ResponseMessage<Message> deleteMessage(Integer id);
/**
* 增加消息的点赞数
*
* @param id 消息ID
* @return 包含操作结果的响应消息其中泛型Message表示操作后的消息内容
*/
ResponseMessage<Message> likeMessage(Integer id);
// 新增方法
/**
* 根据文章ID查询消息
*
* @param articleId 文章ID
* @return 消息列表
*/
ResponseMessage<List<Message>> getMessagesByArticleId(Integer articleId);
/**
* 查询所有父消息(根回复)
*
* @return 父消息列表
*/
ResponseMessage<List<Message>> getRootMessages();
/**
* 根据父消息ID查询回复
*
* @param parentId 父消息ID
* @return 回复消息列表
*/
ResponseMessage<List<Message>> getRepliesByParentId(Integer parentId);
/**
* 根据昵称模糊查询消息
*
* @param nickname 昵称
* @return 匹配的消息列表
*/
ResponseMessage<List<Message>> searchMessagesByNickname(String nickname);
// 删除所有评论
ResponseMessage<Void> deleteAllMessages();
}

View File

@@ -0,0 +1,59 @@
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Nonsense;
import com.qf.myafterprojecy.pojo.dto.NonsenseDto;
import java.util.List;
public interface INonsenseService {
/**
* 获取所有疯言疯语内容
* @return 疯言疯语内容列表
*/
ResponseMessage<List<Nonsense>> getAllNonsense();
/**
* 根据ID获取疯言疯语内容
* @param id 疯言疯语内容ID
* @return 疯言疯语内容
*/
ResponseMessage<Nonsense> getNonsenseById(Integer id);
/**
* 根据状态获取疯言疯语内容
* @param status 状态0未发表 1已发表 2已删除
* @return 疯言疯语内容列表
*/
ResponseMessage<List<Nonsense>> getNonsenseByStatus(Integer status);
/**
* 更新疯言疯语内容状态
* @param id 疯言疯语内容ID
* @param status 新状态0未发表 1已发表 2已删除
* @return 更新结果
*/
ResponseMessage<Nonsense> updateNonsenseStatus(Integer id, Integer status);
/**
* 保存疯言疯语内容
* @param nonsenseDto 疯言疯语内容数据传输对象
* @return 保存结果
*/
ResponseMessage<Nonsense> saveNonsense(NonsenseDto nonsenseDto);
/**
* 更新疯言疯语内容
* @param id 疯言疯语内容ID
* @param nonsenseDto 疯言疯语内容数据传输对象
* @return 更新结果
*/
ResponseMessage<Nonsense> updateNonsense(Integer id, NonsenseDto nonsenseDto);
/**
* 删除疯言疯语内容
* @param id 疯言疯语内容ID
* @return 删除结果
*/
ResponseMessage<Boolean> deleteNonsense(Integer id);
}

View File

@@ -0,0 +1,79 @@
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Users;
import com.qf.myafterprojecy.pojo.dto.UserDto;
import java.util.List;
public interface IUserService {
/**
* 根据ID获取用户信息
* @param id 用户ID
* @return 返回用户信息
*/
ResponseMessage<Users> getUserById(Long id);
/**
* 获取所有用户列表
* @return 返回用户列表
*/
ResponseMessage<List<Users>> getAllUsers();
/**
* 根据用户名获取用户信息
* @param username 用户名
* @return 返回用户信息
*/
ResponseMessage<Users> getUserByUsername(String username);
/**
* 保存新用户
* @param userDto 用户数据传输对象
* @return 返回保存结果
*/
ResponseMessage<Users> saveUser(UserDto userDto);
/**
* 更新用户信息
* @param id 用户ID
* @param userDto 用户数据传输对象
* @return 返回更新结果
*/
ResponseMessage<Users> updateUser(Long id, UserDto userDto);
/**
* 删除用户
* @param id 用户ID
* @return 返回删除结果
*/
ResponseMessage<Boolean> deleteUser(Long id);
/**
* 根据角色查询用户列表
* @param role 角色
* @return 用户列表
*/
ResponseMessage<List<Users>> getUsersByRole(int role);
/**
* 检查用户名是否存在
* @param username 用户名
* @return 是否存在
*/
ResponseMessage<Boolean> existsByUsername(String username);
/**
* 检查邮箱是否存在
* @param email 邮箱
* @return 是否存在
*/
ResponseMessage<Boolean> existsByEmail(String email);
/**
* 检查手机号是否存在
* @param phone 手机号
* @return 是否存在
*/
ResponseMessage<Boolean> existsByPhone(String phone);
}

View File

@@ -0,0 +1,362 @@
package com.qf.myafterprojecy.service.impl;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Article;
import com.qf.myafterprojecy.pojo.Categoryattribute;
import com.qf.myafterprojecy.pojo.dto.ArriclePageDto;
import com.qf.myafterprojecy.pojo.dto.ArticleDto;
import com.qf.myafterprojecy.repository.ArticleRepository;
import com.qf.myafterprojecy.repository.CategoryAttributeRepository;
import com.qf.myafterprojecy.service.IArticleService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class ArticleService implements IArticleService {
private static final Logger log = LoggerFactory.getLogger(ArticleService.class);
@Autowired
private ArticleRepository articleRepository;
@Autowired
private CategoryAttributeRepository categoryAttributeRepository;
/**
* 根据文章ID获取文章详情
* @param id 文章ID
* @return 返回包含文章详情的ResponseMessage对象
*/
@Override
@Transactional(readOnly = true)
public ResponseMessage<Article> getArticleById(String id) {
try {
if (id == null || id.isEmpty()) {
return ResponseMessage.badRequest("文章ID不能为空");
}
Article article = articleRepository.findById(Integer.parseInt(id))
.orElseThrow(() -> new RuntimeException("文章不存在"));
// 文章浏览次数增加
articleRepository.incrementViewCount(Integer.parseInt(id));
return ResponseMessage.success(article, "获取文章成功");
} catch (NumberFormatException e) {
return ResponseMessage.badRequest("文章ID格式不正确");
} catch (RuntimeException e) {
if (e.getMessage().contains("文章不存在")) {
return ResponseMessage.notFound("文章不存在");
}
log.error("获取文章失败: {}", e.getMessage());
return ResponseMessage.error("获取文章失败");
} catch (Exception e) {
log.error("获取文章失败: {}", e.getMessage());
return ResponseMessage.error("获取文章失败");
}
}
/**
* 获取文章数量
* @param status 文章状态0未发表 1已发表 2已删除
* @return 返回文章数量
*/
@Override
@Transactional(readOnly = true)
public ResponseMessage<Integer> getArticleCount(Integer status) {
try {
if (status == null) {
return ResponseMessage.badRequest("文章状态不能为空");
}
if (status < 0 || status > 2) {
return ResponseMessage.badRequest("文章状态值必须在0到2之间");
}
Integer count = articleRepository.countByStatus(status);
return ResponseMessage.success(count, "获取文章数量成功");
} catch (Exception e) {
log.error("获取文章数量失败: {}", e.getMessage());
return ResponseMessage.error("获取文章数量失败");
}
}
/**
* 根据状态获取文章列表
* @param status 文章状态0未发表 1已发表 2已删除
* @return 返回包含文章列表的ResponseMessage对象
*/
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Article>> getArticlesByStatus(Integer status) {
try {
if (status == null) {
return ResponseMessage.badRequest("文章状态不能为空");
}
if (status < 0 || status > 2) {
return ResponseMessage.badRequest("文章状态值必须在0到2之间");
}
List<Article> articles = articleRepository.findByStatus(status);
return ResponseMessage.success(articles, "根据状态查询文章成功");
} catch (Exception e) {
log.error("根据状态查询文章列表失败: {}", e.getMessage());
return ResponseMessage.error("根据状态查询文章列表失败");
}
}
/**
* 获取已发布的文章列表
* @return 返回包含已发布文章列表的ResponseMessage对象
*/
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Article>> getPublishedArticles() {
try {
List<Article> articles = articleRepository.findByStatus(1);
return ResponseMessage.success(articles, "获取已发布文章列表成功");
} catch (Exception e) {
log.error("获取已发布文章列表失败: {}", e.getMessage());
return ResponseMessage.error("获取已发布文章列表失败");
}
}
@Override
@Transactional(readOnly = true)
public ResponseMessage<Page<Article>> getArticlesByStatusWithPagination(ArriclePageDto arriclePageDto) {
if (arriclePageDto.getPagenum() == null || arriclePageDto.getPagenum() < 0) {
arriclePageDto.setPagenum(0); // 默认第一页
}
if (arriclePageDto.getPagesize() == null || arriclePageDto.getPagesize() <= 0 || arriclePageDto.getPagesize() > 100) {
arriclePageDto.setPagesize(10); // 默认每页10条最大100条
}
try {
// 如果文章状态值是否在0到2之间则根据文章状态查询文章列表
if (arriclePageDto.getStatus() < 0 || arriclePageDto.getStatus() > 2) {
return ResponseMessage.badRequest("文章状态值必须在0到2之间");
}
PageRequest pageRequest = PageRequest.of(arriclePageDto.getPagenum(), arriclePageDto.getPagesize());
// 如果文章分类ID不为空则根据文章分类ID查询文章列表
if (arriclePageDto.getCategoryid() != null && arriclePageDto.getCategoryid() > 0) {
// 如果文章分类ID不为空则根据文章分类ID查询文章列表
List<Categoryattribute> categoryAttribute = categoryAttributeRepository.findByCategoryId(arriclePageDto.getCategoryid());
if (categoryAttribute.isEmpty()) {
return ResponseMessage.badRequest("分类下没有属性");
}
// 如果文章属性ID数组不为空则根据文章属性ID数组查询文章列表
List<Integer> attributeids = categoryAttribute.stream().map(Categoryattribute::getAttributeid).collect(Collectors.toList());
// 根据分类ID对应的属性ID数组分页查询文章列表
Page<Article> articlePage = articleRepository.findByStatusWithPagination(arriclePageDto.getStatus(), attributeids, pageRequest);
return ResponseMessage.success(articlePage, "根据分类ID分页查询文章成功");
}
// 如果文章属性ID不为空则根据文章属性ID查询文章列表
if (arriclePageDto.getAttributeid() != null && arriclePageDto.getAttributeid() > 0) {
Page<Article> articlePage = articleRepository.findByStatusWithPagination(arriclePageDto.getStatus(), arriclePageDto.getAttributeid(), pageRequest);
return ResponseMessage.success(articlePage, "根据属性ID分页查询文章成功");
}
// 如果文章标题不为空则根据文章标题查询文章列表
if (arriclePageDto.getTitle() != null && !arriclePageDto.getTitle().isEmpty()) {
Page<Article> articlePage = articleRepository.findByStatusWithPagination(arriclePageDto.getStatus(), arriclePageDto.getTitle(), pageRequest);
return ResponseMessage.success(articlePage, "根据标题分页查询文章成功");
}
Page<Article> articlePage = articleRepository.findByStatusWithPagination(arriclePageDto.getStatus(), pageRequest);
return ResponseMessage.success(articlePage, "根据状态分页查询文章成功");
} catch (Exception e) {
log.error("根据状态分页查询文章列表失败: {}", e.getMessage());
return ResponseMessage.error("根据状态分页查询文章列表失败");
}
}
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Article>> getArticlesByTitle(String title) {
try {
if (title == null || title.isEmpty()) {
return ResponseMessage.badRequest("文章标题不能为空");
}
List<Article> articles = articleRepository.findByTitle(title);
return ResponseMessage.success(articles, "根据标题查询文章成功");
} catch (Exception e) {
log.error("根据标题查询文章列表失败: {}", e.getMessage());
return ResponseMessage.error("根据标题查询文章列表失败");
}
}
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Article>> getAllArticles() {
try {
List<Article> articles = articleRepository.findAll();
return ResponseMessage.success(articles, "获取文章列表成功");
} catch (DataAccessException e) {
log.error("获取文章列表失败: {}", e.getMessage());
return ResponseMessage.error("获取文章列表失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Article> saveArticle(ArticleDto articleDto) {
try {
Article article = new Article();
BeanUtils.copyProperties(articleDto, article);
article.setCreatedAt(LocalDateTime.now());
article.setUpdatedAt(LocalDateTime.now());
article.setImg(articleDto.getImg() != null ? articleDto.getImg() : "");
article.setStatus(articleDto.getStatus() != null ? articleDto.getStatus() : 0);
Article savedArticle = articleRepository.save(article);
return ResponseMessage.save(true, savedArticle);
} catch (DataAccessException e) {
log.error("保存文章失败: {}", e.getMessage());
return ResponseMessage.error("保存文章失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Article> updateArticle(Integer id, ArticleDto articleDto) {
try {
Article article = articleRepository.findById(id)
.orElseThrow(() -> new RuntimeException("文章不存在"));
BeanUtils.copyProperties(articleDto, article);
article.setUpdatedAt(LocalDateTime.now());
Article updatedArticle = articleRepository.save(article);
return ResponseMessage.update(true, updatedArticle);
} catch (RuntimeException e) {
if (e.getMessage().contains("文章不存在")) {
return ResponseMessage.notFound("文章不存在");
}
log.error("更新文章失败: {}", e.getMessage());
return ResponseMessage.error("更新文章失败");
} catch (Exception e) {
log.error("更新文章失败: {}", e.getMessage());
return ResponseMessage.error("更新文章失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Article> deleteArticle(Integer id) {
try {
Article article = articleRepository.findById(id)
.orElseThrow(() -> new RuntimeException("文章不存在"));
article.setStatus(2); // 标记为已删除
article.setUpdatedAt(LocalDateTime.now());
articleRepository.save(article);
return ResponseMessage.delete(true);
} catch (RuntimeException e) {
if (e.getMessage().contains("文章不存在")) {
return ResponseMessage.notFound("文章不存在");
}
log.error("删除文章失败: {}", e.getMessage());
return ResponseMessage.error("删除文章失败");
} catch (Exception e) {
log.error("删除文章失败: {}", e.getMessage());
return ResponseMessage.error("删除文章失败");
}
}
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Article>> getArticlesByCategory(Integer categoryId) {
try {
// 可以考虑查询该分类下的所有属性,然后获取相关文章
log.warn("使用了旧接口getArticlesByCategory请考虑迁移到getArticlesByAttribute");
return ResponseMessage.success(articleRepository.findPublishedByAttribute(categoryId), "获取分类文章成功");
} catch (DataAccessException e) {
log.error("获取分类文章失败: {}", e.getMessage());
return ResponseMessage.error("获取分类文章失败");
}
}
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Article>> getArticlesByAttribute(Integer attributeid) {
try {
if (attributeid == null || attributeid <= 0) {
return ResponseMessage.badRequest("属性ID无效");
}
// 验证属性是否存在
if (!categoryAttributeRepository.existsById(attributeid)) {
return ResponseMessage.notFound("属性不存在");
}
List<Article> articles = articleRepository.findPublishedByAttribute(attributeid);
return ResponseMessage.success(articles, "获取属性文章成功");
} catch (DataAccessException e) {
log.error("获取属性文章失败: {}", e.getMessage());
return ResponseMessage.error("获取属性文章失败");
}
}
/**
* 增加文章浏览量
*
* @param id 文章ID
* @return 返回包含更新后文章信息的ResponseMessage对象
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Article> incrementViewCount(Integer id) {
try {
Article article = articleRepository.findById(id)
.orElseThrow(() -> new RuntimeException("文章不存在"));
articleRepository.incrementViewCount(id);
return ResponseMessage.success(article, "增加文章浏览量成功");
} catch (RuntimeException e) {
if (e.getMessage().contains("文章不存在")) {
return ResponseMessage.notFound("文章不存在");
}
log.error("增加文章浏览量失败: {}", e.getMessage());
return ResponseMessage.error("增加文章浏览量失败");
} catch (Exception e) {
log.error("增加文章浏览量失败: {}", e.getMessage());
return ResponseMessage.error("增加文章浏览量失败");
}
}
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Article>> getLatestArticlesByAttribute(Integer attributeid) {
try {
if (attributeid == null || attributeid <= 0) {
return ResponseMessage.badRequest("属性ID无效");
}
// 验证属性是否存在
if (!categoryAttributeRepository.existsById(attributeid)) {
return ResponseMessage.notFound("属性不存在");
}
List<Article> articles = articleRepository.findLatestByAttribute(attributeid);
return ResponseMessage.success(articles, "获取最新属性文章成功");
} catch (DataAccessException e) {
log.error("获取最新属性文章失败: {}", e.getMessage());
return ResponseMessage.error("获取最新属性文章失败");
}
}
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Article>> getMostViewedArticles() {
try {
List<Article> articles = articleRepository.findMostViewed();
return ResponseMessage.success(articles, "获取热门文章成功");
} catch (DataAccessException e) {
log.error("获取热门文章失败: {}", e.getMessage());
return ResponseMessage.error("获取热门文章失败");
}
}
}

View File

@@ -0,0 +1,215 @@
package com.qf.myafterprojecy.service.impl;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Categoryattribute;
import com.qf.myafterprojecy.pojo.dto.CategoryAttributeDto;
import com.qf.myafterprojecy.repository.CategoryAttributeRepository;
import com.qf.myafterprojecy.service.ICategoryAttributeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class CategoryAttributeService implements ICategoryAttributeService {
private static final Logger log = LoggerFactory.getLogger(CategoryAttributeService.class);
@Autowired
private CategoryAttributeRepository categoryAttributeRepository;
/**
* 获取全部分类属性
* @return 所有分类属性列表
*/
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Categoryattribute>> getAllCategoryAttributes() {
try {
List<Categoryattribute> attributes = categoryAttributeRepository.findAll();
return ResponseMessage.success(attributes, "获取所有分类属性成功");
} catch (DataAccessException e) {
log.error("获取所有分类属性失败: {}", e.getMessage());
return ResponseMessage.error("获取所有分类属性失败");
}
}
/**
* 根据ID获取分类属性
* @param id 属性ID
* @return 分类属性信息
*/
@Override
@Transactional(readOnly = true)
public ResponseMessage<Categoryattribute> getCategoryAttributeById(Integer id) {
try {
if (id == null || id <= 0) {
return ResponseMessage.badRequest("属性ID无效");
}
Categoryattribute attribute = categoryAttributeRepository.findById(id)
.orElseThrow(() -> new RuntimeException("分类属性不存在"));
return ResponseMessage.success(attribute, "获取分类属性成功");
} catch (RuntimeException e) {
if (e.getMessage().contains("分类属性不存在")) {
return ResponseMessage.notFound("分类属性不存在");
}
log.error("获取分类属性失败: {}", e.getMessage());
return ResponseMessage.error("获取分类属性失败");
} catch (Exception e) {
log.error("获取分类属性失败: {}", e.getMessage());
return ResponseMessage.error("获取分类属性失败");
}
}
/**
* 根据分类ID获取属性列表
* @param categoryId 分类ID
* @return 属性列表
*/
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Categoryattribute>> getAttributesByCategoryId(Integer categoryId) {
try {
if (categoryId == null || categoryId <= 0) {
return ResponseMessage.badRequest("分类ID无效");
}
List<Categoryattribute> attributes = categoryAttributeRepository.findByCategoryId(categoryId);
return ResponseMessage.success(attributes, "获取分类属性列表成功");
} catch (DataAccessException e) {
log.error("获取分类属性列表失败: {}", e.getMessage());
return ResponseMessage.error("获取分类属性列表失败");
}
}
/**
* 保存分类属性
* @param dto 分类属性DTO
* @return 保存结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Categoryattribute> saveCategoryAttribute(CategoryAttributeDto dto) {
try {
// 检查属性名称是否已存在于该分类下
if (categoryAttributeRepository.existsByCategoryidAndAttributename(
dto.getCategoryid(), dto.getAttributename())) {
return ResponseMessage.badRequest("该分类下已存在同名属性");
}
Categoryattribute attribute = new Categoryattribute();
BeanUtils.copyProperties(dto, attribute);
Categoryattribute savedAttribute = categoryAttributeRepository.save(attribute);
log.info("成功创建分类属性: {}, 分类ID: {}",
savedAttribute.getAttributename(), savedAttribute.getCategoryid());
return ResponseMessage.save(true, savedAttribute);
} catch (DataAccessException e) {
log.error("保存分类属性失败: {}", e.getMessage());
return ResponseMessage.error("保存分类属性失败");
}
}
/**
* 更新分类属性
* @param id 属性ID
* @param dto 分类属性DTO
* @return 更新结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Categoryattribute> updateCategoryAttribute(Integer id, CategoryAttributeDto dto) {
try {
if (id == null || id <= 0) {
return ResponseMessage.badRequest("属性ID无效");
}
Categoryattribute attribute = categoryAttributeRepository.findById(id)
.orElseThrow(() -> new RuntimeException("分类属性不存在"));
// 如果修改了属性名称,检查新名称是否已存在
if (!attribute.getAttributename().equals(dto.getAttributename()) &&
categoryAttributeRepository.existsByCategoryidAndAttributename(
dto.getCategoryid(), dto.getAttributename())) {
return ResponseMessage.badRequest("该分类下已存在同名属性");
}
BeanUtils.copyProperties(dto, attribute);
attribute.setAttributeid(id); // 确保ID不变
Categoryattribute updatedAttribute = categoryAttributeRepository.save(attribute);
log.info("成功更新分类属性: ID={}, 名称={}",
updatedAttribute.getAttributeid(), updatedAttribute.getAttributename());
return ResponseMessage.update(true, updatedAttribute);
} catch (RuntimeException e) {
if (e.getMessage().contains("分类属性不存在")) {
return ResponseMessage.notFound("分类属性不存在");
}
log.error("更新分类属性失败: {}", e.getMessage());
return ResponseMessage.error("更新分类属性失败");
} catch (Exception e) {
log.error("更新分类属性失败: {}", e.getMessage());
return ResponseMessage.error("更新分类属性失败");
}
}
/**
* 删除分类属性
* @param id 属性ID
* @return 删除结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Boolean> deleteCategoryAttribute(Integer id) {
try {
if (id == null || id <= 0) {
return ResponseMessage.badRequest("属性ID无效");
}
if (!categoryAttributeRepository.existsById(id)) {
return ResponseMessage.notFound("分类属性不存在");
}
categoryAttributeRepository.deleteById(id);
log.info("成功删除分类属性: ID={}", id);
return ResponseMessage.delete(true);
} catch (Exception e) {
log.error("删除分类属性失败: {}", e.getMessage());
return ResponseMessage.error("删除分类属性失败");
}
}
/**
* 检查分类属性是否存在
* @param categoryId 分类ID
* @param attributeName 属性名称
* @return 是否存在
*/
@Override
@Transactional(readOnly = true)
public ResponseMessage<Boolean> existsByCategoryAndName(Integer categoryId, String attributeName) {
try {
if (categoryId == null || categoryId <= 0 || attributeName == null || attributeName.isEmpty()) {
return ResponseMessage.badRequest("参数无效");
}
boolean exists = categoryAttributeRepository.existsByCategoryidAndAttributename(
categoryId, attributeName);
return ResponseMessage.success(exists, "检查分类属性成功");
} catch (DataAccessException e) {
log.error("检查分类属性是否存在失败: {}", e.getMessage());
return ResponseMessage.error("检查分类属性是否存在失败");
}
}
}

View File

@@ -0,0 +1,155 @@
package com.qf.myafterprojecy.service.impl;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Category;
import com.qf.myafterprojecy.pojo.dto.CategoryDto;
import com.qf.myafterprojecy.repository.CategoryRepository;
import com.qf.myafterprojecy.service.ICategoryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class CategoryService implements ICategoryService {
private static final Logger log = LoggerFactory.getLogger(CategoryService.class);
@Autowired
private CategoryRepository categoryRepository;
@Override
@Transactional(readOnly = true)
public ResponseMessage<Category> getCategoryById(Integer id) {
try {
if (id == null || id <= 0) {
return ResponseMessage.badRequest("分类ID无效");
}
Category category = categoryRepository.findById(id)
.orElseThrow(() -> new RuntimeException("分类不存在"));
return ResponseMessage.success(category, "获取分类成功");
} catch (RuntimeException e) {
if (e.getMessage().contains("分类不存在")) {
return ResponseMessage.notFound("分类不存在");
}
log.error("获取分类失败: {}", e.getMessage());
return ResponseMessage.error("获取分类失败");
} catch (Exception e) {
log.error("获取分类失败: {}", e.getMessage());
return ResponseMessage.error("获取分类失败");
}
}
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Category>> getAllCategories() {
try {
List<Category> categories = categoryRepository.findAll();
return ResponseMessage.success(categories, "获取分类列表成功");
} catch (DataAccessException e) {
log.error("获取分类列表失败: {}", e.getMessage());
return ResponseMessage.error("获取分类列表失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Category> saveCategory(CategoryDto categoryDto) {
try {
// 检查分类名称是否已存在
if (categoryRepository.existsByTypename(categoryDto.getTypename())) {
return ResponseMessage.badRequest( "分类名称已存在");
}
Category category = new Category();
BeanUtils.copyProperties(categoryDto, category);
category.setCreatedAt(LocalDateTime.now());
category.setUpdatedAt(LocalDateTime.now());
Category savedCategory = categoryRepository.save(category);
return ResponseMessage.save(true, savedCategory);
} catch (DataAccessException e) {
log.error("保存分类失败: {}", e.getMessage());
return ResponseMessage.error("保存分类失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Category> updateCategory(Integer id, CategoryDto categoryDto) {
try {
if (id == null || id <= 0) {
return ResponseMessage.badRequest("分类ID无效");
}
Category category = categoryRepository.findById(id)
.orElseThrow(() -> new RuntimeException("分类不存在"));
// 如果修改了分类名称,检查新名称是否已存在
if (!category.getTypename().equals(categoryDto.getTypename()) &&
categoryRepository.existsByTypename(categoryDto.getTypename())) {
return ResponseMessage.badRequest("分类名称已存在");
}
BeanUtils.copyProperties(categoryDto, category);
category.setUpdatedAt(LocalDateTime.now());
Category updatedCategory = categoryRepository.save(category);
return ResponseMessage.update(true, updatedCategory);
} catch (RuntimeException e) {
if (e.getMessage().contains("分类不存在")) {
return ResponseMessage.notFound("分类不存在");
}
log.error("更新分类失败: {}", e.getMessage());
return ResponseMessage.error("更新分类失败");
} catch (Exception e) {
log.error("更新分类失败: {}", e.getMessage());
return ResponseMessage.error("更新分类失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Boolean> deleteCategory(Integer id) {
try {
if (id == null || id <= 0) {
return ResponseMessage.badRequest("分类ID无效");
}
if (!categoryRepository.existsById(id)) {
return ResponseMessage.notFound("分类不存在");
}
// 注意:实际项目中可能需要先检查是否有文章引用该分类
// 如果有,可能需要先处理文章或者禁止删除
categoryRepository.deleteById(id);
return ResponseMessage.delete(true);
} catch (Exception e) {
log.error("删除分类失败: {}", e.getMessage());
return ResponseMessage.error("删除分类失败");
}
}
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Category>> searchCategoriesByTypename(String typename) {
try {
if (typename == null || typename.trim().isEmpty()) {
return ResponseMessage.badRequest("分类名称不能为空");
}
List<Category> categories = categoryRepository.findByTypenameContaining(typename);
return ResponseMessage.success(categories, "搜索分类成功");
} catch (DataAccessException e) {
log.error("搜索分类失败: {}", e.getMessage());
return ResponseMessage.error("搜索分类失败");
}
}
}

View File

@@ -0,0 +1,286 @@
package com.qf.myafterprojecy.service.impl;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Message;
import com.qf.myafterprojecy.pojo.dto.MessageDto;
import com.qf.myafterprojecy.pojo.dto.MessagePageDto;
import com.qf.myafterprojecy.repository.MessageRepository;
import com.qf.myafterprojecy.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.web.SpringDataWebProperties.Pageable;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MessageService implements IMessageService {
private static final Logger logger = LoggerFactory.getLogger(MessageService.class);
@Autowired
private MessageRepository messageRepository;
@Override
public ResponseMessage<Iterable<Message>> getAllMessages() {
try {
logger.info("查询所有消息");
Iterable<Message> messages = messageRepository.findAll();
return ResponseMessage.success(messages, "查询成功");
} catch (DataAccessException e) {
logger.error("查询所有消息失败", e);
return ResponseMessage.error("查询消息失败:" + e.getMessage());
}
}
@Override
public ResponseMessage<Message> getMessageById(Integer id) {
if (id == null || id <= 0) {
logger.warn("获取消息时ID无效: {}", id);
return ResponseMessage.badRequest("消息ID无效");
}
try {
logger.info("根据ID查询消息: {}", id);
Optional<Message> messageOptional = messageRepository.findById(id);
if (messageOptional.isPresent()) {
return ResponseMessage.success(messageOptional.get(), "查询成功");
} else {
logger.warn("未找到ID为{}的消息", id);
return ResponseMessage.notFound("未找到指定消息");
}
} catch (DataAccessException e) {
logger.error("查询消息失败: {}", id, e);
return ResponseMessage.error("查询消息失败:" + e.getMessage());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Message> saveMessage(MessageDto messageDto) {
// 参数校验
if (messageDto == null) {
logger.warn("保存消息时参数为空");
throw new IllegalArgumentException("MessageDto cannot be null");
}
// 业务逻辑校验
if (StringUtils.isEmpty(messageDto.getContent())) {
logger.warn("保存消息时内容为空");
throw new IllegalArgumentException("Message content cannot be empty");
}
if (StringUtils.isEmpty(messageDto.getNickname())) {
logger.warn("保存消息时昵称为空");
throw new IllegalArgumentException("Message nickname cannot be empty");
}
// 调用Repository保存数据
try {
logger.info("保存消息: {}", messageDto.getNickname());
Message message = new Message();
BeanUtils.copyProperties(messageDto, message);
message.setCreatedAt(new Date()); // 设置创建时间
message.setLikes(0); // 设置点赞数初始值为0
// 如果是回复确保parentid有效
if (messageDto.getParentid() != null && messageDto.getParentid() > 0) {
if (!messageRepository.existsById(messageDto.getParentid())) {
logger.warn("回复的父消息不存在: {}", messageDto.getParentid());
return ResponseMessage.notFound("回复的父消息不存在");
}
}
Message savedMessage = messageRepository.save(message);
logger.info("消息保存成功: {}", savedMessage.getMessageid());
return ResponseMessage.save(true, savedMessage);
} catch (DataAccessException e) {
logger.error("保存消息失败", e);
return ResponseMessage.error("保存消息失败:" + e.getMessage());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Message> deleteMessage(Integer id) {
if (id == null || id <= 0) {
logger.warn("删除消息时ID无效: {}", id);
return ResponseMessage.badRequest("消息ID无效");
}
try {
logger.info("删除消息: {}", id);
if (messageRepository.existsById(id)) {
messageRepository.deleteById(id);
// 同时删除该消息的所有回复
List<Message> replies = messageRepository.findByParentid(id);
if (replies != null && !replies.isEmpty()) {
messageRepository.deleteAll(replies);
logger.info("同时删除了{}条回复消息", replies.size());
}
logger.info("消息删除成功: {}", id);
return ResponseMessage.delete(true);
} else {
logger.warn("未找到要删除的消息: {}", id);
return ResponseMessage.notFound("未找到要删除的消息");
}
} catch (DataAccessException e) {
logger.error("删除消息失败: {}", id, e);
return ResponseMessage.error("删除消息失败:" + e.getMessage());
}
}
@Override
public ResponseMessage<List<Message>> getMessagesByArticleId(Integer articleId) {
if (articleId == null || articleId <= 0) {
logger.warn("根据文章ID查询消息时ID无效: {}", articleId);
return ResponseMessage.badRequest("文章ID无效");
}
try {
logger.info("根据文章ID查询消息: {}", articleId);
List<Message> messages = messageRepository.findByArticleid(articleId);
return ResponseMessage.success(messages, "查询成功");
} catch (DataAccessException e) {
logger.error("根据文章ID查询消息失败: {}", articleId, e);
return ResponseMessage.error("查询消息失败:" + e.getMessage());
}
}
@Override
public ResponseMessage<List<Message>> getRootMessages() {
try {
logger.info("查询所有根消息");
List<Message> messages = messageRepository.findByParentidIsNull();
return ResponseMessage.success(messages, "查询成功");
} catch (DataAccessException e) {
logger.error("查询根消息失败", e);
return ResponseMessage.error("查询消息失败:" + e.getMessage());
}
}
@Override
public ResponseMessage<List<Message>> getRepliesByParentId(Integer parentId) {
if (parentId == null || parentId <= 0) {
logger.warn("根据父消息ID查询回复时ID无效: {}", parentId);
return ResponseMessage.badRequest("父消息ID无效");
}
try {
logger.info("根据父消息ID查询回复: {}", parentId);
List<Message> replies = messageRepository.findByParentid(parentId);
return ResponseMessage.success(replies, "查询成功");
} catch (DataAccessException e) {
logger.error("查询回复消息失败: {}", parentId, e);
return ResponseMessage.error("查询消息失败:" + e.getMessage());
}
}
// 点赞数增加
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Message> likeMessage(Integer id) {
if (id == null || id <= 0) {
logger.warn("点赞消息时ID无效: {}", id);
return ResponseMessage.badRequest("消息ID无效");
}
try {
logger.info("点赞消息: {}", id);
messageRepository.incrementLikes(id);
Message likedMessage = messageRepository.findById(id).orElse(null);
return ResponseMessage.success(likedMessage, "点赞成功");
} catch (DataAccessException e) {
logger.error("点赞消息失败: {}", id, e);
return ResponseMessage.error("点赞消息失败:" + e.getMessage());
}
}
@Override
public ResponseMessage<List<Message>> searchMessagesByNickname(String nickname) {
if (StringUtils.isEmpty(nickname)) {
logger.warn("根据昵称查询消息时昵称为空");
return ResponseMessage.badRequest("昵称不能为空");
}
try {
logger.info("根据昵称查询消息: {}", nickname);
List<Message> messages = messageRepository.findByNicknameContaining(nickname);
return ResponseMessage.success(messages, "查询成功");
} catch (DataAccessException e) {
logger.error("根据昵称查询消息失败: {}", nickname, e);
return ResponseMessage.error("查询消息失败:" + e.getMessage());
}
}
// 删除所有评论
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Void> deleteAllMessages() {
try {
logger.info("删除所有消息");
messageRepository.deleteAll();
return ResponseMessage.delete(true);
} catch (DataAccessException e) {
logger.error("删除所有消息失败", e);
return ResponseMessage.error("删除消息失败:" + e.getMessage());
}
}
@Override
public ResponseMessage<List<Message>> getMessagesByPage(MessagePageDto messagePageDto) {
if (messagePageDto == null) {
logger.warn("分页查询消息时参数为空");
return ResponseMessage.badRequest("分页参数不能为空");
}
if (messagePageDto.getPageNum() == null) {
logger.warn("分页查询消息时页码无效: {}", messagePageDto.getPageNum());
return ResponseMessage.badRequest("页码无效");
}
if (messagePageDto.getPageSize() == null || messagePageDto.getPageSize() <= 0) {
logger.warn("分页查询消息时每页数量无效: {}", messagePageDto.getPageSize());
return ResponseMessage.badRequest("每页数量无效");
}
try {
// 如何文章id为空默认根据分页基础信息查询消息
PageRequest pageable = PageRequest.of(messagePageDto.getPageNum(), messagePageDto.getPageSize());
if (messagePageDto.getArticleid() != null && messagePageDto.getArticleid() > 0) {
// 如果文章ID存在根据文章ID查询消息
Page<Message> messagePage = messageRepository.findByArticleId(messagePageDto.getArticleid(), pageable);
return ResponseMessage.success(messagePage.getContent(), "查询成功");
}
// 如果文章ID不存在根据分页基础信息查询所有消息
Page<Message> messagePage = messageRepository.findAllMessages(pageable);
return ResponseMessage.success(messagePage.getContent(), "查询成功");
} catch (DataAccessException e) {
logger.error("分页查询消息失败: {}", messagePageDto, e);
return ResponseMessage.error("查询消息失败:" + e.getMessage());
}
}
// 获取回复消息条数 如果id为空获取文章id为空的消息条数
@Override
public ResponseMessage<Integer> getMessageCountByArticleId(Integer articleId) {
try {
logger.info("获取文章回复数量: {}", articleId);
if (articleId == null || articleId <= 0) {
Integer count = messageRepository.countReplyByArticleIdIsNull();
return ResponseMessage.success(count, "查询成功");
}
Integer count = messageRepository.countReplyByArticleId(articleId);
return ResponseMessage.success(count, "查询成功");
} catch (DataAccessException e) {
logger.error("获取文章回复数量失败: {}", articleId, e);
return ResponseMessage.error("查询回复数量失败:" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,202 @@
package com.qf.myafterprojecy.service.impl;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Nonsense;
import com.qf.myafterprojecy.pojo.dto.NonsenseDto;
import com.qf.myafterprojecy.repository.NonsenseRepository;
import com.qf.myafterprojecy.service.INonsenseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
import java.util.Optional;
@Service
public class NonsenseService implements INonsenseService {
private static final Logger logger = LoggerFactory.getLogger(NonsenseService.class);
@Autowired
private NonsenseRepository nonsenseRepository;
@Override
public ResponseMessage<List<Nonsense>> getAllNonsense() {
try {
// 获取所有疯言疯语内容但在API层面只返回已发表(1)的内容
List<Nonsense> allNonsense = nonsenseRepository.findAll();
return new ResponseMessage<>(200, "获取成功", allNonsense, true);
} catch (DataAccessException e) {
logger.error("获取所有疯言疯语内容失败", e);
return new ResponseMessage<>(500, "数据库查询异常", null, false);
} catch (Exception e) {
logger.error("获取所有疯言疯语内容失败", e);
return new ResponseMessage<>(500, "服务器内部错误", null, false);
}
}
@Override
public ResponseMessage<Nonsense> getNonsenseById(Integer id) {
try {
Optional<Nonsense> nonsenseOptional = nonsenseRepository.findById(id);
if (nonsenseOptional.isPresent()) {
Nonsense nonsense = nonsenseOptional.get();
logger.info("获取ID为{}的疯言疯语内容成功,状态: {}", id, nonsense.getStatus());
return new ResponseMessage<>(200, "获取成功", nonsense, true);
} else {
logger.warn("未找到ID为{}的疯言疯语内容", id);
return new ResponseMessage<>(404, "未找到指定疯言疯语内容", null, false);
}
} catch (DataAccessException e) {
logger.error("根据ID查询疯言疯语内容失败ID: {}", id, e);
return new ResponseMessage<>(500, "数据库查询异常", null, false);
} catch (Exception e) {
logger.error("根据ID查询疯言疯语内容失败ID: {}", id, e);
return new ResponseMessage<>(500, "服务器内部错误", null, false);
}
}
@Override
public ResponseMessage<List<Nonsense>> getNonsenseByStatus(Integer status) {
try {
// 验证状态值是否有效
if (status < 0 || status > 2) {
logger.warn("无效的状态值: {}", status);
return new ResponseMessage<>(400, "无效的状态值必须是0(未发表)、1(已发表)或2(已删除)", null, false);
}
List<Nonsense> nonsenseList = nonsenseRepository.findByStatus(status);
// 根据状态过滤
return new ResponseMessage<>(200, "获取成功", nonsenseList, true);
} catch (DataAccessException e) {
logger.error("根据状态获取疯言疯语内容失败,状态: {}", status, e);
return new ResponseMessage<>(500, "数据库查询异常", null, false);
} catch (Exception e) {
logger.error("根据状态获取疯言疯语内容失败,状态: {}", status, e);
return new ResponseMessage<>(500, "服务器内部错误", null, false);
}
}
@Override
@Transactional
public ResponseMessage<Nonsense> updateNonsenseStatus(Integer id, Integer status) {
try {
// 验证状态值是否有效
if (status < 0 || status > 2) {
logger.warn("无效的状态值: {} 用于ID为{}的疯言疯语内容", status, id);
return new ResponseMessage<>(400, "无效的状态值必须是0(未发表)、1(已发表)或2(已删除)", null, false);
}
Optional<Nonsense> nonsenseOptional = nonsenseRepository.findById(id);
if (nonsenseOptional.isPresent()) {
Nonsense nonsense = nonsenseOptional.get();
Integer oldStatus = nonsense.getStatus();
nonsense.setStatus(status);
Nonsense updatedNonsense = nonsenseRepository.save(nonsense);
logger.info("更新疯言疯语内容状态成功ID: {}, 旧状态: {}, 新状态: {}", id, oldStatus, status);
return new ResponseMessage<>(200, "状态更新成功", updatedNonsense, true);
} else {
logger.warn("更新状态失败未找到ID为{}的疯言疯语内容", id);
return new ResponseMessage<>(404, "未找到指定疯言疯语内容", null, false);
}
} catch (DataAccessException e) {
logger.error("更新疯言疯语内容状态失败ID: {}, 状态: {}", id, status, e);
return new ResponseMessage<>(500, "数据库操作异常", null, false);
} catch (Exception e) {
logger.error("更新疯言疯语内容状态失败ID: {}, 状态: {}", id, status, e);
return new ResponseMessage<>(500, "服务器内部错误", null, false);
}
}
@Override
@Transactional
public ResponseMessage<Nonsense> saveNonsense(NonsenseDto nonsenseDto) {
try {
Nonsense nonsense = new Nonsense();
BeanUtils.copyProperties(nonsenseDto, nonsense);
// 设置创建时间
if (nonsense.getTime() == null) {
nonsense.setTime(new Date());
}
// 设置默认状态为未发表(0)如果DTO中未提供状态值
if (nonsense.getStatus() == null) {
nonsense.setStatus(0);
}
Nonsense savedNonsense = nonsenseRepository.save(nonsense);
logger.info("保存疯言疯语内容成功ID: {}, 状态: {}", savedNonsense.getId(), savedNonsense.getStatus());
return new ResponseMessage<>(200, "保存成功", savedNonsense, true);
} catch (DataAccessException e) {
logger.error("保存疯言疯语内容失败", e);
return new ResponseMessage<>(500, "数据库操作异常", null, false);
} catch (Exception e) {
logger.error("保存疯言疯语内容失败", e);
return new ResponseMessage<>(500, "服务器内部错误", null, false);
}
}
@Override
@Transactional
public ResponseMessage<Nonsense> updateNonsense(Integer id, NonsenseDto nonsenseDto) {
try {
Optional<Nonsense> nonsenseOptional = nonsenseRepository.findById(id);
if (nonsenseOptional.isPresent()) {
Nonsense nonsense = nonsenseOptional.get();
// 只有当DTO中提供了status值时才更新
if (nonsenseDto.getStatus() != null) {
logger.info("更新疯言疯语内容状态ID: {}, 新状态: {}", id, nonsenseDto.getStatus());
}
BeanUtils.copyProperties(nonsenseDto, nonsense, "id");
Nonsense updatedNonsense = nonsenseRepository.save(nonsense);
logger.info("更新疯言疯语内容成功ID: {}, 当前状态: {}", id, updatedNonsense.getStatus());
return new ResponseMessage<>(200, "更新成功", updatedNonsense, true);
} else {
logger.warn("更新失败未找到ID为{}的疯言疯语内容", id);
return new ResponseMessage<>(404, "未找到指定疯言疯语内容", null, false);
}
} catch (DataAccessException e) {
logger.error("更新疯言疯语内容失败ID: {}", id, e);
return new ResponseMessage<>(500, "数据库操作异常", null, false);
} catch (Exception e) {
logger.error("更新疯言疯语内容失败ID: {}", id, e);
return new ResponseMessage<>(500, "服务器内部错误", null, false);
}
}
@Override
@Transactional
public ResponseMessage<Boolean> deleteNonsense(Integer id) {
try {
if (nonsenseRepository.existsById(id)) {
// 先将状态设置为已删除(2)
Nonsense nonsense = nonsenseRepository.findById(id).get();
nonsense.setStatus(2);
nonsenseRepository.save(nonsense);
// 物理删除
// nonsenseRepository.deleteById(id);
logger.info("删除疯言疯语内容成功ID: {}", id);
return new ResponseMessage<>(200, "删除成功", true, true);
} else {
logger.warn("删除失败未找到ID为{}的疯言疯语内容", id);
return new ResponseMessage<>(404, "未找到指定疯言疯语内容", false, false);
}
} catch (DataAccessException e) {
logger.error("删除疯言疯语内容失败ID: {}", id, e);
return new ResponseMessage<>(500, "数据库操作异常", false, false);
} catch (Exception e) {
logger.error("删除疯言疯语内容失败ID: {}", id, e);
return new ResponseMessage<>(500, "服务器内部错误", false, false);
}
}
}

View File

@@ -0,0 +1,239 @@
package com.qf.myafterprojecy.service.impl;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Users;
import com.qf.myafterprojecy.pojo.dto.UserDto;
import com.qf.myafterprojecy.repository.UsersRepository;
import com.qf.myafterprojecy.service.IUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@Service
public class UserService implements IUserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
@Autowired
private UsersRepository usersRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public ResponseMessage<Users> getUserById(Long id) {
try {
Optional<Users> userOptional = usersRepository.findById(id);
if (userOptional.isPresent()) {
return ResponseMessage.success(userOptional.get(), "获取用户成功");
} else {
return ResponseMessage.notFound("用户不存在");
}
} catch (DataAccessException e) {
logger.error("获取用户异常: {}", e.getMessage());
return ResponseMessage.error("数据库访问异常");
} catch (Exception e) {
logger.error("获取用户未知异常: {}", e.getMessage());
return ResponseMessage.error("服务器异常");
}
}
@Override
public ResponseMessage<List<Users>> getAllUsers() {
try {
List<Users> users = usersRepository.findAll();
return ResponseMessage.success(users, "获取所有用户成功");
} catch (DataAccessException e) {
logger.error("获取所有用户异常: {}", e.getMessage());
return ResponseMessage.error("数据库访问异常");
} catch (Exception e) {
logger.error("获取所有用户未知异常: {}", e.getMessage());
return ResponseMessage.error("服务器异常");
}
}
@Override
public ResponseMessage<Users> getUserByUsername(String username) {
try {
Optional<Users> userOptional = usersRepository.findByUsername(username);
if (userOptional.isPresent()) {
return ResponseMessage.success(userOptional.get(), "获取用户成功");
} else {
return ResponseMessage.notFound("用户不存在");
}
} catch (DataAccessException e) {
logger.error("根据用户名获取用户异常: {}", e.getMessage());
return ResponseMessage.error("数据库访问异常");
} catch (Exception e) {
logger.error("根据用户名获取用户未知异常: {}", e.getMessage());
return ResponseMessage.error("服务器异常");
}
}
@Transactional
@Override
public ResponseMessage<Users> saveUser(UserDto userDto) {
try {
// 检查用户名是否已存在
if (usersRepository.existsByUsername(userDto.getUsername())) {
return ResponseMessage.badRequest("用户名已存在");
}
// 检查邮箱是否已存在
if (usersRepository.existsByEmail(userDto.getEmail())) {
return ResponseMessage.badRequest("邮箱已存在");
}
// 检查手机号是否已存在
if (usersRepository.existsByPhone(userDto.getPhone())) {
return ResponseMessage.badRequest("手机号已存在");
}
// 创建新用户
Users user = new Users();
BeanUtils.copyProperties(userDto, user);
// 使用密码编码器加密密码
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setCreateTime(LocalDateTime.now());
// 保存用户
Users savedUser = usersRepository.save(user);
return ResponseMessage.save(true, savedUser);
} catch (DataAccessException e) {
logger.error("保存用户异常: {}", e.getMessage());
return ResponseMessage.error("数据库访问异常");
} catch (Exception e) {
logger.error("保存用户未知异常: {}", e.getMessage());
return ResponseMessage.error("服务器异常");
}
}
@Transactional
@Override
public ResponseMessage<Users> updateUser(Long id, UserDto userDto) {
try {
Optional<Users> userOptional = usersRepository.findById(id);
if (!userOptional.isPresent()) {
return ResponseMessage.notFound("用户不存在");
}
Users user = userOptional.get();
// 检查用户名是否被其他用户使用
if (!user.getUsername().equals(userDto.getUsername()) && usersRepository.existsByUsername(userDto.getUsername())) {
return ResponseMessage.badRequest("用户名已存在");
}
// 检查邮箱是否被其他用户使用
if (!user.getEmail().equals(userDto.getEmail()) && usersRepository.existsByEmail(userDto.getEmail())) {
return ResponseMessage.badRequest("邮箱已存在");
}
// 检查手机号是否被其他用户使用
if (!user.getPhone().equals(userDto.getPhone()) && usersRepository.existsByPhone(userDto.getPhone())) {
return ResponseMessage.badRequest("手机号已存在");
}
// 更新用户信息
BeanUtils.copyProperties(userDto, user);
// 如果提供了新密码,则进行加密
if (userDto.getPassword() != null && !userDto.getPassword().isEmpty()) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
}
Users updatedUser = usersRepository.save(user);
return ResponseMessage.update(true, updatedUser);
} catch (DataAccessException e) {
logger.error("更新用户异常: {}", e.getMessage());
return ResponseMessage.error("数据库访问异常");
} catch (Exception e) {
logger.error("更新用户未知异常: {}", e.getMessage());
return ResponseMessage.error("服务器异常");
}
}
@Transactional
@Override
public ResponseMessage<Boolean> deleteUser(Long id) {
try {
if (!usersRepository.existsById(id)) {
return ResponseMessage.notFound("用户不存在");
}
usersRepository.deleteById(id);
return ResponseMessage.delete(true);
} catch (DataAccessException e) {
logger.error("删除用户异常: {}", e.getMessage());
return ResponseMessage.error("数据库访问异常");
} catch (Exception e) {
logger.error("删除用户未知异常: {}", e.getMessage());
return ResponseMessage.error("服务器异常");
}
}
@Override
public ResponseMessage<List<Users>> getUsersByRole(int role) {
try {
List<Users> users = usersRepository.findByRole(role);
return ResponseMessage.success(users, "根据角色获取用户成功");
} catch (DataAccessException e) {
logger.error("根据角色获取用户异常: {}", e.getMessage());
return ResponseMessage.error("数据库访问异常");
} catch (Exception e) {
logger.error("根据角色获取用户未知异常: {}", e.getMessage());
return ResponseMessage.error("服务器异常");
}
}
@Override
public ResponseMessage<Boolean> existsByUsername(String username) {
try {
boolean exists = usersRepository.existsByUsername(username);
return ResponseMessage.success(exists, "查询成功");
} catch (DataAccessException e) {
logger.error("检查用户名存在性异常: {}", e.getMessage());
return ResponseMessage.error("数据库访问异常");
} catch (Exception e) {
logger.error("检查用户名存在性未知异常: {}", e.getMessage());
return ResponseMessage.error("服务器异常");
}
}
@Override
public ResponseMessage<Boolean> existsByEmail(String email) {
try {
boolean exists = usersRepository.existsByEmail(email);
return ResponseMessage.success(exists, "查询成功");
} catch (DataAccessException e) {
logger.error("检查邮箱存在性异常: {}", e.getMessage());
return ResponseMessage.error("数据库访问异常");
} catch (Exception e) {
logger.error("检查邮箱存在性未知异常: {}", e.getMessage());
return ResponseMessage.error("服务器异常");
}
}
@Override
public ResponseMessage<Boolean> existsByPhone(String phone) {
try {
boolean exists = usersRepository.existsByPhone(phone);
return ResponseMessage.success(exists, "查询成功");
} catch (DataAccessException e) {
logger.error("检查手机号存在性异常: {}", e.getMessage());
return ResponseMessage.error("数据库访问异常");
} catch (Exception e) {
logger.error("检查手机号存在性未知异常: {}", e.getMessage());
return ResponseMessage.error("服务器异常");
}
}
}

View File

@@ -0,0 +1,103 @@
package com.qf.myafterprojecy.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* JWT工具类用于生成和验证token
*/
@Component
public class JwtUtils {
@Value("${jwt.secret:default_secret_key_for_development}")
private String secret;
@Value("${jwt.expiration:86400000}")
private long expiration;
@Value("${jwt.token-prefix:Bearer}")
private String tokenPrefix;
/**
* 从token中获取用户名
*/
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
/**
* 从token中获取过期时间
*/
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
/**
* 从token中获取指定的claim
*/
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
/**
* 获取token中的所有claims
*/
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
/**
* 检查token是否过期
*/
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
/**
* 生成token
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("authorities", userDetails.getAuthorities());
return doGenerateToken(claims, userDetails.getUsername());
}
/**
* 生成token的核心方法
*/
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 验证token
*/
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
/**
* 获取token前缀
*/
public String getTokenPrefix() {
return tokenPrefix;
}
}

View File

@@ -0,0 +1,5 @@
{"properties": [{
"name": "Content-Type,",
"type": "java.lang.String",
"description": "A description for 'Content-Type,'"
}]}

View File

@@ -0,0 +1,68 @@
# ====================================================================
# 开发环境配置文件
# 说明:此配置用于本地开发和调试,只包含开发环境特定配置
# 通用配置请参考主配置文件 application.properties
# ====================================================================
# 应用基本配置 - 开发特定
server.port=7071
# ====================================================================
# 数据库与JPA配置 - 开发用
# ====================================================================
spring.datasource.url=jdbc:mysql://localhost:3306/webproject?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&createDatabaseIfNotExist=true&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库连接池配置(开发环境简化版)
spring.datasource.hikari.maximum-pool-size=5
spring.datasource.hikari.minimum-idle=2
# JPA配置开发环境使用update以便自动创建/更新表结构)
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=false
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
# ====================================================================
# JWT 配置 - 开发用(方便调试,密钥较短,过期时间短)
# ====================================================================
jwt.secret=devSecretKey2024ForLocalDevelopment
jwt.expiration=3600000
# 1小时过期方便调试
jwt.header=Authorization
jwt.token-prefix=Bearer
# ====================================================================
# 安全与CORS配置 - 开发用
# ====================================================================
# CORS配置开发环境允许所有本地前端访问
cors.allowed-origins=http://localhost:3000,http://localhost:8080,http://localhost:5173
cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS
cors.allowed-headers=*
cors.allow-credentials=true
# ====================================================================
# 日志配置 - 开发用(详细日志便于调试)
# ====================================================================
logging.level.root=INFO
logging.level.com.qf.myafterprojecy=DEBUG
logging.level.org.springframework=INFO
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
# ====================================================================
# 开发环境特有配置
# ====================================================================
# 热部署配置
spring.devtools.restart.enabled=true
spring.devtools.restart.additional-paths=src/main/java
# 解决DevTools重启时丢失profile配置的问题
spring.devtools.restart.poll-interval=2s
spring.devtools.restart.quiet-period=1s
spring.devtools.restart.exclude=static/**,public/**
# Actuator配置开发环境开放更多端点便于调试
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always

View File

@@ -0,0 +1,70 @@
# ====================================================================
# 生产环境配置文件
# 说明:此配置用于生产环境部署,敏感信息从环境变量读取
# ====================================================================
# 应用基本配置 - 生产特定
server.port=7070
# ====================================================================
# 数据库与JPA配置 - 生产用
# ====================================================================
spring.datasource.url=${DB_URL:jdbc:mysql://mysql:3306/webproject?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai}
spring.datasource.username=${DB_USERNAME:root}
spring.datasource.password=${DB_PASSWORD:root}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库连接池配置(生产环境优化版)
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.connection-timeout=20000
spring.datasource.hikari.max-lifetime=1200000
spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.hikari.pool-name=WebProjectHikariCP
# JPA配置生产环境禁用自动DDL避免意外修改表结构
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=false
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.open-in-view=false
# JPA性能优化配置
spring.jpa.properties.hibernate.jdbc.batch_size=30
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
# ====================================================================
# JWT 配置 - 生产用(敏感信息从环境变量读取)
# ====================================================================
jwt.secret=${JWT_SECRET:}
jwt.expiration=${JWT_EXPIRATION:86400000}
jwt.header=Authorization
jwt.token-prefix=Bearer
# ====================================================================
# 安全与CORS配置 - 生产用
# ====================================================================
# CORS配置生产环境限制为具体域名
cors.allowed-origins=http://qf1121.top,https://qf1121.top,http://www.qf1121.top,https://www.qf1121.top
cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS
cors.allowed-headers=*
cors.allow-credentials=true
cors.max-age=3600
# ====================================================================
# 日志配置 - 生产用(精简日志,提高性能)
# ====================================================================
logging.level.root=WARN
logging.level.com.qf.myafterprojecy=INFO
logging.level.org.springframework.security=WARN
logging.level.org.hibernate.SQL=ERROR
logging.file.name=logs/web_project.log
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
# ====================================================================
# API与应用配置 - 生产用
# ====================================================================
# Actuator配置生产环境限制访问
management.endpoints.web.exposure.include=health,info
management.endpoint.health.show-details=when_authorized

View File

@@ -0,0 +1,43 @@
# ====================================================================
# 应用基本配置 - 通用配置
# ====================================================================
# 环境激活配置
# 说明:默认激活开发环境,生产环境部署时应通过命令行参数或环境变量覆盖
spring.profiles.active=dev
server.port=7070
# 应用名称(通用配置)
spring.application.name=web_project
# ====================================================================
# 会话与编码配置 - 通用配置
# ====================================================================
# 会话配置
server.servlet.session.timeout=30m
server.session.tracking-modes=cookie
# 国际化配置
spring.web.locale=zh_CN
spring.messages.encoding=UTF-8
# 响应编码配置(通用,所有环境保持一致)
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.force=true
server.servlet.encoding.force-request=true
server.servlet.encoding.force-response=true
server.servlet.encoding.enabled=true
# ====================================================================
# API与应用配置 - 通用配置
# ====================================================================
# API 文档配置(通用)
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
# 应用性能优化配置(通用)
spring.main.allow-bean-definition-overriding=true
spring.main.lazy-initialization=false
# ====================================================================
# 说明环境特定的配置如数据库、JWT、CORS等已移至对应的环境配置文件中
# - 开发环境application-dev.properties
# - 生产环境application-prod.properties
# ====================================================================

View File

@@ -0,0 +1,6 @@
<html>
<body>
<h1>hello word!!!</h1>
<p>this is a html page</p>
</body>
</html>

View File

@@ -0,0 +1,13 @@
package com.qf.myafterprojecy;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class MyAfterProjecyApplicationTests {
@Test
void contextLoads() {
}
}