Compare commits

...

21 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
63 changed files with 8698 additions and 6367 deletions

View File

@@ -2,16 +2,16 @@
## 项目概述
MyAfterProject是一个基于Spring Boot的后端博客系统提供文章管理、留言板等功能的API接口。本文档旨在帮助前端开发者理解和使用这些API接口
MyAfterProject是一个基于Spring Boot的后端博客系统提供文章管理、留言板、用户管理等功能的RESTful API接口。系统集成了Spring Security提供安全的HTTP访问机制。本文档旨在帮助前端开发者理解和使用这些API接口包括接口规范、请求/响应格式、数据模型及最佳实践
## 技术栈
- **后端框架**: Spring Boot 2.x
- **ORM框架**: Spring Data JPA
- **数据库**: MySQL
- **缓存**: Redis
- **认证**: Spring Security + JWT
- **API风格**: RESTful
- **安全框架**: Spring Security
- **API风格**: RESTful API
- **错误处理**: 全局异常处理机制
## 项目结构
@@ -19,29 +19,52 @@ MyAfterProject是一个基于Spring Boot的后端博客系统提供文章管
src/main/java/com/qf/myafterprojecy/
├── controller/ # 控制器层处理HTTP请求
│ ├── ArticleController.java # 文章相关API
── MessageController.java # 留言相关API
── MessageController.java # 留言相关API
│ ├── UserController.java # 用户相关API
│ ├── CategoryController.java # 分类相关API
│ ├── CategoryAttributeController.java # 分类属性相关API
│ ├── MarkdownController.java # Markdown相关API
│ └── HelpController.java # 帮助文档相关API
├── pojo/ # 实体类和数据传输对象
│ ├── Article.java # 文章实体
│ ├── Message.java # 留言实体
│ ├── ResponseMessage.java # 统一响应消息格式
│ ├── Users.java # 用户实体
│ ├── Category.java # 分类实体
│ ├── Category_attribute.java # 分类属性实体
│ ├── Markdown.java # Markdown实体
│ └── dto/ # 数据传输对象
│ ├── ArticleDto.java # 文章DTO
── MessageDto.java # 留言DTO
── MessageDto.java # 留言DTO
│ ├── UserDto.java # 用户DTO
│ ├── CategoryDto.java # 分类DTO
│ ├── CategoryAttributeDto.java # 分类属性DTO
│ └── MarkdownDto.java # Markdown DTO
├── repository/ # 数据访问层
│ ├── ArticleRepository.java # 文章数据访问
── MessageRepository.java # 留言数据访问
── MessageRepository.java # 留言数据访问
│ ├── UserRepository.java # 用户数据访问
│ ├── CategoryRepository.java # 分类数据访问
│ ├── CategoryAttributeRepository.java # 分类属性数据访问
│ └── MarkdownRepository.java # Markdown数据访问
├── service/ # 业务逻辑层
│ ├── ArticleService.java # 文章服务实现
│ ├── IArticleService.java # 文章服务接口
│ ├── MessageService.java # 留言服务实现
── IMessageService.java # 留言服务接口
── IMessageService.java # 留言服务接口
│ ├── UserService.java # 用户服务实现
│ └── IUserService.java # 用户服务接口
├── config/ # 配置类
│ ├── SecurityConfig.java # Spring Security配置
│ └── ResponseMessage.java # 统一响应消息格式
├── GlobalExceptionHandler.java # 全局异常处理器
└── MyAfterProjecyApplication.java # 应用入口
```
## 配置信息
后端服务默认运行在 `http://localhost:8080` 端口前端项目需要将API请求指向该地址。
- **服务地址**: `http://localhost:8080`
- **API基础路径**: `/api`
- **安全配置**: 当前配置允许所有请求通过,不需要认证
## API接口详细说明
@@ -81,12 +104,14 @@ GET /api/articles
"articleid": 1,
"title": "文章标题",
"content": "文章内容...",
"typeid": 1,
"attributeid": 1,
"img": "图片URL",
"createdAt": "2023-01-01T10:00:00",
"updatedAt": "2023-01-01T10:00:00",
"viewCount": 100,
"status": 1
"likes": 0,
"status": 1,
"markdownid": 0
},
// 更多文章...
]
@@ -114,27 +139,28 @@ GET /api/articles/{id}
"articleid": 1,
"title": "文章标题",
"content": "文章内容...",
"typeid": 1,
"attributeid": 1,
"img": "图片URL",
"createdAt": "2023-01-01T10:00:00",
"updatedAt": "2023-01-01T10:00:00",
"viewCount": 101,
"status": 1
"likes": 0,
"status": 1,
"markdownid": 0
}
}
```
#### 1.3 根据分类获取文章
#### 1.3 根据属性获取文章
```
GET /api/articles/category/{categoryId}
GET /api/articles/attribute/{attributeId}
```
**功能**: 获取指定分类下的所有文章
**功能**: 获取指定属性下的所有文章
**请求参数**:
- `categoryId`: 分类ID路径参数
- `attributeId`: 属性ID路径参数
**返回数据**:
```json
@@ -148,7 +174,30 @@ GET /api/articles/category/{categoryId}
}
```
#### 1.4 获取热门文章
#### 1.4 增加文章浏览量
```
POST /api/articles/view/{id}
```
**功能**: 增加指定文章的浏览量
**请求参数**:
- `id`: 文章ID路径参数
**返回数据**:
```json
{
"code": 200,
"message": "更新成功",
"success": true,
"data": {
// 更新后的文章信息
}
}
```
#### 1.5 获取热门文章
```
GET /api/articles/popular
@@ -170,7 +219,7 @@ GET /api/articles/popular
}
```
#### 1.5 创建文章(需要认证)
#### 1.6 创建文章
```
POST /api/articles
@@ -178,13 +227,12 @@ POST /api/articles
**功能**: 创建新文章
**权限**: 需要AUTHOR角色
**请求体**:
```json
{
"title": "新文章标题",
"content": "新文章内容",
"attributeid": 1,
"img": "图片URL",
"status": 1 // 0-草稿1-已发布
}
@@ -205,7 +253,7 @@ POST /api/articles
}
```
#### 1.6 更新文章(需要认证)
#### 1.7 更新文章
```
PUT /api/articles/{id}
@@ -213,8 +261,6 @@ PUT /api/articles/{id}
**功能**: 更新现有文章
**权限**: 需要AUTHOR或ADMIN角色
**请求参数**:
- `id`: 文章ID路径参数
@@ -223,6 +269,7 @@ PUT /api/articles/{id}
{
"title": "更新后的标题",
"content": "更新后的内容",
"attributeid": 1,
"img": "更新后的图片URL",
"status": 1
}
@@ -240,15 +287,13 @@ PUT /api/articles/{id}
}
```
#### 1.7 删除文章(需要认证)
#### 1.8 删除文章
```
DELETE /api/articles/{id}
```
**功能**: 删除指定文章
**权限**: 需要AUTHOR或ADMIN角色
**功能**: 删除指定文章实际为软删除将状态标记为2
**请求参数**:
- `id`: 文章ID路径参数
@@ -259,9 +304,7 @@ DELETE /api/articles/{id}
"code": 200,
"message": "删除成功",
"success": true,
"data": {
// 被删除的文章信息
}
"data": null
}
```
@@ -291,7 +334,9 @@ GET /api/messages
"content": "留言内容",
"createdAt": "2023-01-01T10:00:00",
"parentid": null, // 父留言IDnull表示主留言
"articleid": null // 关联的文章IDnull表示无关联
"replyid": null, // 回复留言IDnull表示无回复
"articleid": null, // 关联的文章IDnull表示无关联
"likes": 0 // 点赞数
},
// 更多留言...
]
@@ -321,7 +366,119 @@ GET /api/messages/{id}
}
```
#### 2.3 创建留言
#### 2.3 根据文章ID获取留言
```
GET /api/messages/article/{articleId}
```
**功能**: 获取指定文章下的所有留言
**请求参数**:
- `articleId`: 文章ID路径参数
**返回数据**:
```json
{
"code": 200,
"message": "查询成功",
"success": true,
"data": [
// 留言列表
]
}
```
#### 2.4 获取根留言
```
GET /api/messages/root
```
**功能**: 获取所有根留言(非回复的留言)
**请求参数**: 无
**返回数据**:
```json
{
"code": 200,
"message": "查询成功",
"success": true,
"data": [
// 根留言列表
]
}
```
#### 2.5 获取回复列表
```
GET /api/messages/{parentId}/replies
```
**功能**: 根据父留言ID获取回复列表
**请求参数**:
- `parentId`: 父留言ID路径参数
**返回数据**:
```json
{
"code": 200,
"message": "查询成功",
"success": true,
"data": [
// 回复列表
]
}
```
#### 2.6 搜索留言
```
GET /api/messages/search?nickname={nickname}
```
**功能**: 根据昵称搜索留言
**请求参数**:
- `nickname`: 昵称(查询参数)
**返回数据**:
```json
{
"code": 200,
"message": "查询成功",
"success": true,
"data": [
// 匹配的留言列表
]
}
```
#### 2.7 获取文章评论数量
```
GET /api/messages/count/article/{articleId}
```
**功能**: 获取指定文章的评论数量
**请求参数**:
- `articleId`: 文章ID路径参数
**返回数据**:
```json
{
"code": 200,
"message": "查询成功",
"success": true,
"data": 10 // 评论数量
}
```
#### 2.8 创建留言
```
POST /api/messages
@@ -352,15 +509,36 @@ POST /api/messages
}
```
#### 2.4 删除留言(需要认证)
#### 2.9 点赞留言
```
POST /api/messages/{id}/like
```
**功能**: 增加留言的点赞数
**请求参数**:
- `id`: 留言ID路径参数
**返回数据**:
```json
{
"code": 200,
"message": "操作成功",
"success": true,
"data": {
// 更新后的留言信息
}
}
```
#### 2.10 删除留言
```
DELETE /api/messages/{id}
```
**功能**: 删除指定留言
**权限**: 需要ADMIN角色
**功能**: 删除指定留言及其所有回复
**请求参数**:
- `id`: 留言ID路径参数
@@ -371,9 +549,344 @@ DELETE /api/messages/{id}
"code": 200,
"message": "删除成功",
"success": true,
"data": {
// 被删除的留言信息
"data": null
}
```
#### 2.11 删除所有留言
```
DELETE /api/messages/all
```
**功能**: 删除所有留言
**请求参数**: 无
**返回数据**:
```json
{
"code": 200,
"message": "删除成功",
"success": true,
"data": null
}
```
### 3. 用户管理API
#### 3.1 获取所有用户
```
GET /api/users
```
**功能**: 获取所有用户列表
**请求参数**: 无
**返回数据**:
```json
{
"code": 200,
"message": "查询成功",
"success": true,
"data": [
// 用户列表
]
}
```
#### 3.2 根据ID获取用户
```
GET /api/users/{id}
```
**功能**: 根据ID获取用户信息
**请求参数**:
- `id`: 用户ID路径参数
**返回数据**:
```json
{
"code": 200,
"message": "查询成功",
"success": true,
"data": {
// 用户信息
}
}
```
#### 3.3 根据用户名获取用户
```
GET /api/users/username/{username}
```
**功能**: 根据用户名获取用户信息
**请求参数**:
- `username`: 用户名(路径参数)
**返回数据**:
```json
{
"code": 200,
"message": "查询成功",
"success": true,
"data": {
// 用户信息
}
}
```
#### 3.4 根据角色获取用户
```
GET /api/users/role/{role}
```
**功能**: 根据角色获取用户列表
**请求参数**:
- `role`: 角色ID路径参数
**返回数据**:
```json
{
"code": 200,
"message": "查询成功",
"success": true,
"data": [
// 用户列表
]
}
```
#### 3.5 创建用户
```
POST /api/users
```
**功能**: 创建新用户
**请求体**:
```json
{
"username": "新用户名",
"password": "密码",
"email": "email@example.com",
"phone": "13800138000",
"role": 1
}
```
**返回数据**:
```json
{
"code": 200,
"message": "保存成功",
"success": true,
"data": {
// 创建的用户信息
}
}
```
#### 3.6 更新用户
```
PUT /api/users/{id}
```
**功能**: 更新用户信息
**请求参数**:
- `id`: 用户ID路径参数
**请求体**:
```json
{
"username": "更新后的用户名",
"password": "新密码",
"email": "newemail@example.com",
"phone": "13900139000",
"role": 2
}
```
**返回数据**:
```json
{
"code": 200,
"message": "更新成功",
"success": true,
"data": {
// 更新后的用户信息
}
}
```
#### 3.7 删除用户
```
DELETE /api/users/{id}
```
**功能**: 删除指定用户
**请求参数**:
- `id`: 用户ID路径参数
**返回数据**:
```json
{
"code": 200,
"message": "删除成功",
"success": true,
"data": true
}
```
#### 3.8 检查用户名是否存在
```
GET /api/users/check/username/{username}
```
**功能**: 检查指定用户名是否已存在
**请求参数**:
- `username`: 用户名(路径参数)
**返回数据**:
```json
{
"code": 200,
"message": "查询成功",
"success": true,
"data": true // true表示存在false表示不存在
}
```
#### 3.9 检查邮箱是否存在
```
GET /api/users/check/email/{email}
```
**功能**: 检查指定邮箱是否已存在
**请求参数**:
- `email`: 邮箱(路径参数)
**返回数据**:
```json
{
"code": 200,
"message": "查询成功",
"success": true,
"data": true // true表示存在false表示不存在
}
```
### 4. 分类管理API
#### 4.1 获取所有分类
```
GET /api/categories
```
**功能**: 获取所有分类列表
**请求参数**: 无
**返回数据**:
```json
{
"code": 200,
"message": "查询成功",
"success": true,
"data": [
// 分类列表
]
}
```
### 5. 分类属性管理API
#### 5.1 根据ID获取分类属性
```
GET /api/category-attributes/{id}
```
**功能**: 根据ID获取分类属性详情
**请求参数**:
- `id`: 属性ID路径参数
**返回数据**:
```json
{
"code": 200,
"message": "查询成功",
"success": true,
"data": {
// 分类属性信息
}
}
```
### 6. Markdown管理API
#### 6.1 根据ID获取Markdown内容
```
GET /api/markdowns/{id}
```
**功能**: 根据ID获取Markdown内容
**请求参数**:
- `id`: Markdown ID路径参数
**返回数据**:
```json
{
"code": 200,
"message": "查询成功",
"success": true,
"data": {
// Markdown内容
}
}
```
### 7. 帮助文档API
#### 7.1 获取API文档
```
GET /api/help
```
**功能**: 获取README_API.md文档内容
**请求参数**: 无
**返回数据**:
```json
{
"code": 200,
"message": "获取API文档成功",
"success": true,
"data": "# MyAfterProject 开发文档(前端使用)\n..."
}
```
@@ -386,12 +899,14 @@ DELETE /api/messages/{id}
| articleid | Integer | 文章ID主键 | 否 |
| title | String | 文章标题 | 否 |
| content | String | 文章内容 | 否 |
| typeid | Integer | 分类ID | 否 |
| attributeid | Integer | 属性ID | 否 |
| img | String | 文章图片URL | 是 |
| createdAt | LocalDateTime | 创建时间 | 是 |
| updatedAt | LocalDateTime | 更新时间 | 是 |
| viewCount | Integer | 浏览次数 | 是 |
| likes | Integer | 点赞数 | 是 |
| status | Integer | 状态0-草稿1-已发布2-已删除) | 是 |
| markdownid | Integer | markdown文档ID0表示普通文章 | 是 |
### 2. 留言模型 (Message)
@@ -403,14 +918,20 @@ DELETE /api/messages/{id}
| content | String | 留言内容 | 是 |
| createdAt | Date | 创建时间 | 是 |
| parentid | Integer | 父留言ID用于回复功能 | 是 |
| replyid | Integer | 回复留言ID | 是 |
| articleid | Integer | 关联的文章ID | 是 |
| likes | Integer | 点赞数 | 是 |
### 3. 数据传输对象 (DTO)
### 3. 用户模型 (Users)
DTO类用于前后端数据传输是对实体类的简化只包含需要传输的字段。
- **ArticleDto**: 包含文章的基本信息,用于创建和更新文章
- **MessageDto**: 包含留言的基本信息,用于创建和更新留言
| 字段名 | 类型 | 描述 | 是否可为空 |
|-------|------|-----|-----------|
| id | Long | 用户ID主键 | 否 |
| username | String | 用户名 | 否 |
| password | String | 密码 | 否 |
| email | String | 邮箱 | 否 |
| phone | String | 手机号 | 否 |
| role | Integer | 角色ID | 否 |
## 前端调用示例
@@ -438,20 +959,6 @@ const api = axios.create({
}
});
// 请求拦截器 - 添加认证token
api.interceptors.request.use(
config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => {
return Promise.reject(error);
}
);
// 响应拦截器 - 统一处理响应
api.interceptors.response.use(
response => {
@@ -477,8 +984,10 @@ export const articleAPI = {
getAllArticles: () => api.get('/articles'),
// 获取单篇文章
getArticleById: (id) => api.get(`/articles/${id}`),
// 根据分类获取文章
getArticlesByCategory: (categoryId) => api.get(`/articles/category/${categoryId}`),
// 根据属性获取文章
getArticlesByAttribute: (attributeId) => api.get(`/articles/attribute/${attributeId}`),
// 增加文章浏览量
incrementViewCount: (id) => api.post(`/articles/view/${id}`),
// 获取热门文章
getPopularArticles: () => api.get('/articles/popular'),
// 创建文章
@@ -495,10 +1004,70 @@ export const messageAPI = {
getAllMessages: () => api.get('/messages'),
// 获取单条留言
getMessageById: (id) => api.get(`/messages/${id}`),
// 根据文章ID获取留言
getMessagesByArticleId: (articleId) => api.get(`/messages/article/${articleId}`),
// 获取根留言
getRootMessages: () => api.get('/messages/root'),
// 获取回复列表
getRepliesByParentId: (parentId) => api.get(`/messages/${parentId}/replies`),
// 搜索留言
searchMessagesByNickname: (nickname) => api.get(`/messages/search?nickname=${nickname}`),
// 获取文章评论数量
getMessageCountByArticleId: (articleId) => api.get(`/messages/count/article/${articleId}`),
// 创建留言
saveMessage: (messageData) => api.post('/messages', messageData),
// 点赞留言
likeMessage: (id) => api.post(`/messages/${id}/like`),
// 删除留言
deleteMessage: (id) => api.delete(`/messages/${id}`)
deleteMessage: (id) => api.delete(`/messages/${id}`),
// 删除所有留言
deleteAllMessages: () => api.delete('/messages/all')
};
// 用户相关API
export const userAPI = {
// 获取所有用户
getAllUsers: () => api.get('/users'),
// 根据ID获取用户
getUserById: (id) => api.get(`/users/${id}`),
// 根据用户名获取用户
getUserByUsername: (username) => api.get(`/users/username/${username}`),
// 根据角色获取用户
getUsersByRole: (role) => api.get(`/users/role/${role}`),
// 创建用户
createUser: (userData) => api.post('/users', userData),
// 更新用户
updateUser: (id, userData) => api.put(`/users/${id}`, userData),
// 删除用户
deleteUser: (id) => api.delete(`/users/${id}`),
// 检查用户名是否存在
checkUsernameExists: (username) => api.get(`/users/check/username/${username}`),
// 检查邮箱是否存在
checkEmailExists: (email) => api.get(`/users/check/email/${email}`)
};
// 分类相关API
export const categoryAPI = {
// 获取所有分类
getAllCategories: () => api.get('/categories')
};
// 分类属性相关API
export const categoryAttributeAPI = {
// 根据ID获取分类属性
getAttributeById: (id) => api.get(`/category-attributes/${id}`)
};
// Markdown相关API
export const markdownAPI = {
// 根据ID获取Markdown内容
getMarkdownById: (id) => api.get(`/markdowns/${id}`)
};
// 帮助文档API
export const helpAPI = {
// 获取API文档
getApiDocumentation: () => api.get('/help')
};
export default api;
@@ -507,7 +1076,7 @@ export default api;
### 3. 在组件中使用API
```javascript
import { articleAPI, messageAPI } from './api';
import { articleAPI, messageAPI, userAPI } from './api';
// 获取文章列表
async function fetchArticles() {
@@ -531,6 +1100,18 @@ async function postMessage(messageData) {
throw error;
}
}
// 创建用户
async function registerUser(userData) {
try {
const response = await userAPI.createUser(userData);
console.log('用户创建成功:', response.data);
return response.data;
} catch (error) {
console.error('用户创建失败:', error);
throw error;
}
}
```
## 错误处理指南
@@ -540,8 +1121,6 @@ async function postMessage(messageData) {
| HTTP状态码 | 错误描述 | 可能原因 | 处理方式 |
|-----------|---------|---------|---------|
| 400 | Bad Request | 请求参数错误或缺失 | 检查请求参数是否正确 |
| 401 | Unauthorized | 未授权访问 | 需要登录获取token |
| 403 | Forbidden | 权限不足 | 确认用户是否有足够权限 |
| 404 | Not Found | 请求资源不存在 | 检查请求URL或资源ID是否正确 |
| 500 | Internal Server Error | 服务器内部错误 | 查看服务器日志,联系后端开发人员 |
@@ -549,18 +1128,13 @@ async function postMessage(messageData) {
1. 在前端实现全局响应拦截器统一处理API返回的错误
2. 为不同类型的错误显示相应的提示信息
3. 对于需要认证的API在401错误时引导用户登录
4. 添加请求超时处理和重试机制
3. 添加请求超时处理和重试机制
## 开发环境配置
### 跨域配置
后端已配置CORS允许`http://localhost:3000` 访问(前端开发服务器默认端口)。如果前端开发服务器使用其他端口,需要修改后端的 `application.properties` 中的 `cors.allowed-origins` 配置
### 认证配置
对于需要认证的API前端需要在请求头中添加JWT token。获取token的方式可以通过登录接口本项目暂未实现完整的用户认证功能
后端已配置CORS支持,允许前端应用访问API接口
## 性能优化建议
@@ -579,18 +1153,39 @@ async function postMessage(messageData) {
| articleid |<--+ | messageid |
| title | | | nickname |
| content | | | email |
| typeid | | | content |
| attributeid | | | content |
| img | | | createdAt |
| createdAt | | | parentid |-->|
| updatedAt | | | articleid |-->+
| viewCount | | +-------------+
| viewCount | | | likes |
| likes | | +-------------+
| status | |
| markdownid | |
+-------------+ |
|
+-------------+ |
| Users | |
+-------------+ |
| id | |
| username | |
| password | |
| email | |
| phone | |
| role | |
+-------------+ |
|
+---------------+ |
| CategoryAttribute|<-+
+---------------+ |
| id | |
| attribute_name| |
| category_id | |
+---------------+ |
|
+-------------+ |
| Category |---+
+-------------+
| Reply |
+-------------+
| (通过Message.parentid实现) |
| id |
| name |
+-------------+
```

File diff suppressed because it is too large Load Diff

Binary file not shown.

51
pom.xml
View File

@@ -18,11 +18,7 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!-- Spring Security 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -81,29 +77,13 @@
<version>0.18.2</version>
</dependency>
<!-- JCache API -->
<!-- JWT依赖 -->
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- EHCache 3作为JCache实现 -->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0.1</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
<dependencyManagement>
@@ -138,6 +118,7 @@
<configuration>
<mainClass>com.qf.myafterprojecy.MyAfterProjecyApplication</mainClass>
<skip>false</skip>
<jvmArguments>-Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8</jvmArguments>
</configuration>
<executions>
<execution>
@@ -148,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

@@ -1,14 +1,42 @@
package com.qf.myafterprojecy;
import org.mybatis.spring.annotation.MapperScan;
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

@@ -1,5 +1,6 @@
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;
@@ -12,31 +13,60 @@ import org.springframework.web.filter.CorsFilter;
@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();
// 允许的来源,这里允许所有来源,实际生产环境应该限制特定域名
config.addAllowedOriginPattern("*");
// 允许的来源,从配置文件读取并分割
String[] originsArray = allowedOrigins.split(",");
for (String origin : originsArray) {
config.addAllowedOrigin(origin.trim());
}
// 允许携带凭证如Cookie
config.setAllowCredentials(true);
config.setAllowCredentials(allowCredentials);
// 允许的请求方法
config.addAllowedMethod("*");
// 允许的HTTP方法从配置文件读取并分割
String[] methodsArray = allowedMethods.split(",");
for (String method : methodsArray) {
config.addAllowedMethod(method.trim());
}
// 允许的请求头
config.addAllowedHeader("*");
// 允许的请求头,从配置文件读取
config.addAllowedHeader(allowedHeaders);
// 暴露的响应头,这些头信息可以被前端JavaScript访问
config.addExposedHeader("*");
// 明确暴露的响应头,对于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(3600L);
// 设置预检请求的有效期(秒),从配置文件读取
config.setMaxAge(maxAge);
// 创建基于URL的CORS配置源
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

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

@@ -1,21 +1,52 @@
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 配置过程中可能出现的异常
@@ -23,18 +54,81 @@ public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 启用CORS支持确保与CorsConfig中配置的过滤器配合工作
.cors().and()
// 禁用CSRF保护对于API服务通常不需要
.csrf().disable()
// 允许所有请求通过,不需要认证
// 配置URL访问权限
.authorizeRequests()
.anyRequest().permitAll()
// 允许公开访问的路径
// 登录和认证相关端点应该全部公开
.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()
// 禁用表单登录
.formLogin().disable()
// 禁用HTTP基本认证
.httpBasic().disable()
// 禁用会话管理对于无状态API服务
.sessionManagement().disable();
// 配置会话管理,使用无状态会话策略
// 这意味着每个请求都需要包含认证信息如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

@@ -1,9 +1,13 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Article;
import com.qf.myafterprojecy.pojo.ResponseMessage;
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;
@@ -18,6 +22,7 @@ import java.util.List;
*/
@RestController
@RequestMapping("/api/articles")
@Validated
public class ArticleController {
@@ -33,6 +38,37 @@ public class ArticleController {
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);
}
/**
* 获取所有文章列表
@@ -42,6 +78,44 @@ public class ArticleController {
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);
}
/**
* 创建新文章
@@ -50,20 +124,28 @@ public class ArticleController {
* @return 返回包含新创建文章信息的ResponseMessage对象
*/
@PostMapping
@PreAuthorize("hasRole('AUTHOR')")
@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或ADMIN角色用户访问
* 仅限AUTHOR角色用户访问
* @param id 要更新的文章ID
* @param articleDto 包含更新后文章数据的DTO对象
* @return 返回包含更新后文章信息的ResponseMessage对象
*/
@PutMapping("/{id}")
@PreAuthorize("hasRole('AUTHOR') or hasRole('ADMIN')")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Article> updateArticle(
@PathVariable Integer id,
@Valid @RequestBody ArticleDto articleDto) {
@@ -77,28 +159,9 @@ public class ArticleController {
* @return 返回包含被删除文章信息的ResponseMessage对象
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('AUTHOR') or hasRole('ADMIN')")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Article> deleteArticle(@PathVariable Integer id) {
return articleService.deleteArticle(id);
}
/**
* 根据分类ID获取该分类下的所有文章
* @param typeid 分类ID
* @return 返回包含文章列表的ResponseMessage对象
*/
@GetMapping("/category/{categoryId}")
public ResponseMessage<List<Article>> getArticlesByCategory(@PathVariable Integer typeid) {
return articleService.getArticlesByCategory(typeid);
}
/**
* 获取浏览量最高的文章列表
* @return 返回包含热门文章列表的ResponseMessage对象
*/
@GetMapping("/popular")
public ResponseMessage<List<Article>> getMostViewedArticles() {
return articleService.getMostViewedArticles();
}
}

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

@@ -1,6 +1,5 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.pojo.ResponseMessage;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
@@ -10,6 +9,8 @@ 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;

View File

@@ -1,12 +1,15 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Message;
import com.qf.myafterprojecy.pojo.ResponseMessage;
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;
@@ -28,7 +31,25 @@ public class MessageController {
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获取消息
*/
@@ -38,26 +59,6 @@ public class MessageController {
return messageService.getMessageById(id);
}
/**
* 创建新消息
*/
@PostMapping
public ResponseMessage<Message> createMessage(@RequestBody MessageDto message) {
logger.info("接收创建消息的请求: {}", message != null ? message.getNickname() : "null");
return messageService.saveMessage(message);
}
/**
* 根据ID删除消息
*/
@DeleteMapping("/{id}")
public ResponseMessage<Message> deleteMessage(@PathVariable Integer id) {
logger.info("接收删除消息的请求: {}", id);
return messageService.deleteMessage(id);
}
// 新增API端点
/**
* 根据文章ID获取消息列表
*/
@@ -95,11 +96,34 @@ public class MessageController {
}
/**
* 获取指定文章的评论数量
* 创建新消息
*/
@GetMapping("/count/article/{articleId}")
public ResponseMessage<Long> getMessageCountByArticleId(@PathVariable Integer articleId) {
logger.info("接收获取文章评论数量的请求: {}", articleId);
return messageService.getMessageCountByArticleId(articleId);
@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

@@ -1,7 +1,4 @@
package com.qf.myafterprojecy;
import com.qf.myafterprojecy.pojo.ResponseMessage;
package com.qf.myafterprojecy.exceptopn;
import javax.servlet.http.HttpServletRequest;

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

@@ -1,96 +0,0 @@
package com.qf.myafterprojecy.init;
import com.qf.myafterprojecy.pojo.Message;
import com.qf.myafterprojecy.repository.MessageRepository;
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.stereotype.Component;
import java.util.Date;
/**
* 消息数据初始化类用于在应用启动时为Message表添加默认的测试数据
*/
@Component
public class MessageDataInit implements ApplicationRunner {
private static final Logger logger = LoggerFactory.getLogger(MessageDataInit.class);
@Autowired
private MessageRepository messageRepository;
@Override
public void run(ApplicationArguments args) throws Exception {
logger.info("===== 消息数据初始化开始 =====");
// 检查数据库中是否已有消息数据
long count = messageRepository.count();
logger.info("当前数据库中消息数量: {}", count);
// 如果没有消息数据,添加一些测试数据
if (count == 0) {
logger.info("数据库中没有消息数据,开始添加初始化数据...");
addInitialMessages();
} else {
logger.info("数据库中已存在消息数据,无需初始化");
}
logger.info("===== 消息数据初始化结束 =====");
}
private void addInitialMessages() {
// 添加第一篇文章的评论
Message message1 = new Message();
message1.setNickname("系统用户");
message1.setEmail("system@example.com");
message1.setContent("这是系统自动添加的第一条评论,欢迎使用本系统!");
message1.setCreatedAt(new Date());
message1.setArticleid(1);
message1.setParentid(null); // 根评论
messageRepository.save(message1);
// 添加回复
Message reply1 = new Message();
reply1.setNickname("管理员");
reply1.setEmail("admin@example.com");
reply1.setContent("感谢您的支持,如有任何问题请随时联系我们!");
reply1.setCreatedAt(new Date());
reply1.setArticleid(1);
reply1.setParentid(message1.getMessageid()); // 回复第一条评论
messageRepository.save(reply1);
// 添加第二篇文章的评论
Message message2 = new Message();
message2.setNickname("访客");
message2.setEmail("visitor@example.com");
message2.setContent("这篇文章写得非常好,学到了很多知识。");
message2.setCreatedAt(new Date());
message2.setArticleid(2);
message2.setParentid(null);
messageRepository.save(message2);
// 再添加一些测试数据
Message message3 = new Message();
message3.setNickname("测试用户1");
message3.setEmail("test1@example.com");
message3.setContent("这是测试内容1");
message3.setCreatedAt(new Date());
message3.setArticleid(1);
message3.setParentid(null);
messageRepository.save(message3);
Message reply2 = new Message();
reply2.setNickname("测试用户2");
reply2.setEmail("test2@example.com");
reply2.setContent("回复测试内容1");
reply2.setCreatedAt(new Date());
reply2.setArticleid(1);
reply2.setParentid(message3.getMessageid());
messageRepository.save(reply2);
logger.info("成功添加了{}条初始化消息数据", messageRepository.count());
}
}

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

@@ -17,13 +17,12 @@ public class Article {
@Column(name = "title")
private String title;
@NotBlank(message = "内容不能为空")
@Column(name = "content", columnDefinition = "TEXT")
private String content;
@NotNull(message = "类别id不能为空")
@Column(name = "typeid")
private Integer typeid;
@Column(name = "attribute_id")
private Integer attributeid;
@Column(name = "img")
private String img;
@@ -33,14 +32,38 @@ public class Article {
@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;
@@ -66,14 +89,6 @@ public class Article {
this.content = content;
}
public Integer getTypeid() {
return typeid;
}
public void setTypeid(Integer typeid) {
this.typeid = typeid;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
@@ -113,4 +128,12 @@ public class Article {
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

@@ -8,22 +8,53 @@ import java.util.Date;
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(columnDefinition = "text")
@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;
@@ -80,4 +111,12 @@ public class Message {
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

@@ -1,128 +0,0 @@
package com.qf.myafterprojecy.pojo;
import lombok.Data;
import org.springframework.http.HttpStatus;
/**
* 通用响应消息类,用于封装接口返回的数据结构
* 使用泛型T来支持不同类型的数据返回
* @param <T> 数据类型可以是任意Java对象
*/
@Data
public class ResponseMessage<T> {
// 状态码,通常用于表示请求的处理结果
private Integer code;
// 响应消息,用于描述请求的处理结果信息
private String message;
// 请求是否成功的标志
private boolean success;
// 响应数据,泛型类型,支持不同类型的数据
private T data;
/**
* 构造方法,用于创建响应消息对象
* @param code 状态码
* @param message 响应消息
* @param data 响应数据
*/
public ResponseMessage(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
// 获取成功状态的getter方法
public boolean isSuccess() {
return success;
}
// 设置成功状态的setter方法
public void setSuccess(boolean success) {
this.success = success;
}
// 获取状态码的getter方法
public Integer getCode() {
return code;
}
// 设置状态码的setter方法
public void setCode(Integer code) {
this.code = code;
}
// 获取响应消息的getter方法
public String getMessage() {
return message;
}
// 设置响应消息的setter方法
public void setMessage(String message) {
this.message = message;
}
// 获取响应数据的getter方法
public T getData() {
return data;
}
// 设置响应数据的setter方法
public void setData(T data) {
this.data = data;
}
/**
* 完整参数的构造方法
* @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;
}
// 接口请求成功
public static <T> ResponseMessage<T> success(T data ,String message ,boolean success) {
return new ResponseMessage(HttpStatus.OK.value(), message, data ,success);
}
/**
* 创建一个表示操作失败的响应消息
* @param message 失败原因的描述信息
* @return 返回一个包含错误状态码和错误信息的ResponseMessage对象
*/
public static <T> ResponseMessage<T> failure(String message) {
return new ResponseMessage<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), message, null, false);
}
public static <T> ResponseMessage<T> success(T data) {
return new ResponseMessage<>(HttpStatus.OK.value(), "操作成功", data, true);
}
public static <T> ResponseMessage<T> success(T data, String message) {
return new ResponseMessage<>(HttpStatus.OK.value(), message, data, true);
}
public static <T> ResponseMessage<T> error(String message) {
return new ResponseMessage<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), message, null, false);
}
public static <T> ResponseMessage<T> error(Integer code, String message) {
return new ResponseMessage<>(code, message, null, false);
}
public static <T> ResponseMessage<T> Save(boolean success) {
return success ?
new ResponseMessage<>(HttpStatus.OK.value(), "保存成功", null, true) :
new ResponseMessage<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), "保存失败", null, false);
}
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);
}
}

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

@@ -1,32 +1,36 @@
package com.qf.myafterprojecy.pojo.dto;
import lombok.Getter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Getter
public class ArticleDto {
private Integer id;
private Integer articleid;
@NotBlank(message = "标题不能为空")
private String title;
@NotBlank(message = "内容不能为空")
private String content;
private String content;// 如果为空说明是长篇文章 不为空则是短篇说说
@NotNull(message = "属性ID不能为空")
private Integer attributeid;
private String img;
private Integer viewCount;
private Integer likes;
private Integer status;
// Getters and Setters
private String markdownscontent; // 文章内容的Markdown格式
public Integer getId() {
return id;
// Getters and Setters
public Integer getArticleid() {
return articleid;
}
public void setId(Integer id) {
this.id = id;
public void setArticleid(Integer articleid) {
this.articleid = articleid;
}
public String getTitle() {
@@ -45,12 +49,12 @@ public class ArticleDto {
this.content = content;
}
public Integer getStatus() {
return status;
public Integer getAttributeid() {
return attributeid;
}
public void setStatus(Integer status) {
this.status = status;
public void setAttributeid(Integer attributeid) {
this.attributeid = attributeid;
}
public String getImg() {
@@ -60,4 +64,36 @@ public class ArticleDto {
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

@@ -1,6 +1,5 @@
package com.qf.myafterprojecy.pojo.dto;
import javax.persistence.*;
import java.util.Date;
public class MessageDto {
@@ -16,8 +15,21 @@ public class MessageDto {
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;
@@ -74,4 +86,20 @@ public class MessageDto {
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

@@ -1,6 +1,8 @@
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;
@@ -21,6 +23,16 @@ public interface ArticleRepository extends JpaRepository<Article, Integer> {
*/
@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的文章
@@ -34,11 +46,21 @@ public interface ArticleRepository extends JpaRepository<Article, Integer> {
* 根据分类ID查询已发布的文章列表
* 使用JPQL查询语句筛选状态为已发布(status=1)且指定分类(typeid)的文章
*
* @param typeid 分类ID通过@Param注解映射到查询语句中的:typeid参数
* @param attributeid 分类ID通过@Param注解映射到查询语句中的:attributeid参数
* @return 返回符合条件Article对象的列表
*/
@Query("SELECT a FROM Article a WHERE a.status = 1 AND a.typeid = :typeid")
List<Article> findPublishedByCategory(@Param("typeid") Integer typeid);
@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语句
@@ -48,7 +70,7 @@ public interface ArticleRepository extends JpaRepository<Article, Integer> {
* @param articleid 文章的唯一标识符,通过@Param注解将方法参数与查询参数绑定
*/
@Modifying
@Query("UPDATE Article a SET a.viewCount = a.viewCount + 1 WHERE a.articleid = :articleid")
@Query("UPDATE Article a SET a.viewCount = COALESCE(a.viewCount, 0) + 1 WHERE a.articleid = :articleid")
void incrementViewCount(@Param("articleid") Integer articleid);
/**
@@ -59,4 +81,55 @@ public interface ArticleRepository extends JpaRepository<Article, Integer> {
*/
@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

@@ -1,9 +1,15 @@
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;
@@ -11,22 +17,80 @@ import java.util.List;
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);
// 查询指定文章下的所有父消息(根回复)
@Query("SELECT m FROM Message m WHERE m.articleid = ?1 AND m.parentid IS NULL ORDER BY m.createdAt DESC")
List<Message> findRootMessagesByArticleId(Integer articleId);
/**
* 查询指定文章下的所有父消息(根回复)
* @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);
// 统计指定文章的评论数量
@Query("SELECT COUNT(m) FROM Message m WHERE m.articleid = ?1")
Long countByArticleId(Integer articleId);
/**
* 根据文章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

@@ -1,165 +0,0 @@
package com.qf.myafterprojecy.runner;
import com.qf.myafterprojecy.pojo.Message;
import com.qf.myafterprojecy.pojo.ResponseMessage;
import com.qf.myafterprojecy.pojo.dto.MessageDto;
import com.qf.myafterprojecy.repository.MessageRepository;
import com.qf.myafterprojecy.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Optional;
/**
* 消息数据检查器,用于验证消息相关的业务代码是否正常工作
*/
@Component
public class MessageDataChecker implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(MessageDataChecker.class);
@Autowired
private MessageRepository messageRepository;
@Autowired
private IMessageService messageService;
@Override
public void run(String... args) throws Exception {
logger.info("===== 消息数据检查器开始运行 =====");
// 检查数据库中是否已有消息数据
long count = messageRepository.count();
logger.info("当前数据库中消息数量: {}", count);
// 如果没有消息数据,添加一些测试数据
if (count == 0) {
logger.info("数据库中没有消息数据,开始添加测试数据...");
addTestMessages();
}
// 测试查询方法
testQueryMethods();
// 测试服务层方法
testServiceMethods();
logger.info("===== 消息数据检查器运行结束 =====");
}
private void addTestMessages() {
// 添加第一篇文章的评论
Message message1 = new Message();
message1.setNickname("张三");
message1.setEmail("zhangsan@example.com");
message1.setContent("这是一篇很棒的文章!");
message1.setCreatedAt(new Date());
message1.setArticleid(1);
message1.setParentid(null); // 根评论
messageRepository.save(message1);
// 添加回复
Message reply1 = new Message();
reply1.setNickname("李四");
reply1.setEmail("lisi@example.com");
reply1.setContent("同意你的观点!");
reply1.setCreatedAt(new Date());
reply1.setArticleid(1);
reply1.setParentid(message1.getMessageid()); // 回复第一篇评论
messageRepository.save(reply1);
// 添加第二篇文章的评论
Message message2 = new Message();
message2.setNickname("王五");
message2.setEmail("wangwu@example.com");
message2.setContent("学到了很多东西,谢谢分享!");
message2.setCreatedAt(new Date());
message2.setArticleid(2);
message2.setParentid(null);
messageRepository.save(message2);
logger.info("成功添加了{}条测试消息数据", messageRepository.count());
}
private void testQueryMethods() {
logger.info("===== 测试Repository查询方法 =====");
// 测试根据文章ID查询
List<Message> article1Messages = messageRepository.findByArticleid(1);
logger.info("文章ID为1的消息数量: {}", article1Messages.size());
// 测试查询所有根消息
List<Message> rootMessages = messageRepository.findByParentidIsNull();
logger.info("根消息数量: {}", rootMessages.size());
// 测试根据昵称模糊查询
List<Message> zhangMessages = messageRepository.findByNicknameContaining("");
logger.info("昵称包含'张'的消息数量: {}", zhangMessages.size());
// 测试统计文章评论数量
Long article1Count = messageRepository.countByArticleId(1);
logger.info("文章ID为1的评论数量: {}", article1Count);
// 如果有根消息,测试查询回复
if (!rootMessages.isEmpty()) {
Integer firstRootId = rootMessages.get(0).getMessageid();
List<Message> replies = messageRepository.findByParentid(firstRootId);
logger.info("消息ID为{}的回复数量: {}", firstRootId, replies.size());
}
}
private void testServiceMethods() {
logger.info("===== 测试Service层方法 =====");
// 测试获取所有消息
ResponseMessage<Iterable<Message>> allMessagesResponse = messageService.getAllMessages();
logger.info("获取所有消息: 成功={}, 消息数量={}", allMessagesResponse.isSuccess(),
((List<Message>)allMessagesResponse.getData()).size());
// 测试根据ID获取消息
if (messageRepository.count() > 0) {
Message firstMessage = messageRepository.findAll().iterator().next();
Integer messageId = firstMessage.getMessageid();
ResponseMessage<Message> messageResponse = messageService.getMessageById(messageId);
logger.info("根据ID{}获取消息: 成功={}, 昵称={}", messageId,
messageResponse.isSuccess(),
messageResponse.getData() != null ? messageResponse.getData().getNickname() : "null");
// 测试获取指定文章的评论数量
ResponseMessage<Long> countResponse = messageService.getMessageCountByArticleId(1);
logger.info("获取文章ID为1的评论数量: 成功={}, 数量={}",
countResponse.isSuccess(), countResponse.getData());
// 测试获取根消息
ResponseMessage<List<Message>> rootResponse = messageService.getRootMessages();
logger.info("获取根消息: 成功={}, 数量={}",
rootResponse.isSuccess(),
rootResponse.getData() != null ? rootResponse.getData().size() : 0);
}
// 测试保存新消息
MessageDto newMessage = new MessageDto();
newMessage.setNickname("测试用户");
newMessage.setEmail("test@example.com");
newMessage.setContent("这是一条测试消息");
newMessage.setArticleid(1);
newMessage.setParentid(null);
ResponseMessage<Message> saveResponse = messageService.saveMessage(newMessage);
logger.info("保存新消息: 成功={}, 消息ID={}",
saveResponse.isSuccess(),
saveResponse.getData() != null ? saveResponse.getData().getMessageid() : "null");
// 如果保存成功,测试删除
if (saveResponse.isSuccess() && saveResponse.getData() != null) {
Integer savedId = saveResponse.getData().getMessageid();
ResponseMessage<Message> deleteResponse = messageService.deleteMessage(savedId);
logger.info("删除消息ID{}: 成功={}", savedId, deleteResponse.isSuccess());
}
}
}

View File

@@ -1,136 +0,0 @@
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.pojo.Article;
import com.qf.myafterprojecy.pojo.ResponseMessage;
import com.qf.myafterprojecy.pojo.dto.ArticleDto;
import com.qf.myafterprojecy.repository.ArticleRepository;
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 ArticleService implements IArticleService {
private static final Logger log = LoggerFactory.getLogger(ArticleService.class);
@Autowired
private ArticleRepository articleRepository;
@Override
@Transactional(readOnly = true)
public ResponseMessage<Article> getArticleById(String id) {
try {
if (id == null || id.isEmpty()) {
return ResponseMessage.failure("文章ID不能为空");
}
Article article = articleRepository.findById(Integer.parseInt(id))
.orElseThrow(() -> new RuntimeException("文章不存在"));
// 暂时不增加浏览次数,以避免事务问题
// articleRepository.incrementViewCount(id);
return ResponseMessage.success(article);
} catch (Exception e) {
log.error("获取文章失败: {}", e.getMessage());
return ResponseMessage.failure("获取文章失败");
}
}
@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.failure("获取文章列表失败");
}
}
@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.success(savedArticle);
} catch (DataAccessException e) {
log.error("保存文章失败: {}", e.getMessage());
return ResponseMessage.failure("保存文章失败");
}
}
@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.success(updatedArticle);
} catch (Exception e) {
log.error("更新文章失败: {}", e.getMessage());
return ResponseMessage.failure("更新文章失败");
}
}
@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.success(null);
} catch (Exception e) {
log.error("删除文章失败: {}", e.getMessage());
return ResponseMessage.failure("删除文章失败");
}
}
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Article>> getArticlesByCategory(Integer categoryId) {
try {
List<Article> articles = articleRepository.findPublishedByCategory(categoryId);
return ResponseMessage.success(articles);
} catch (DataAccessException e) {
log.error("获取分类文章失败: {}", e.getMessage());
return ResponseMessage.failure("获取分类文章失败");
}
}
@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.failure("获取热门文章失败");
}
}
}

View File

@@ -1,17 +1,108 @@
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Article;
import com.qf.myafterprojecy.pojo.ResponseMessage;
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

@@ -1,40 +1,70 @@
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Message;
import com.qf.myafterprojecy.pojo.ResponseMessage;
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 消息列表
*/
@@ -42,12 +72,14 @@ public interface IMessageService {
/**
* 查询所有父消息(根回复)
*
* @return 父消息列表
*/
ResponseMessage<List<Message>> getRootMessages();
/**
* 根据父消息ID查询回复
*
* @param parentId 父消息ID
* @return 回复消息列表
*/
@@ -55,15 +87,13 @@ public interface IMessageService {
/**
* 根据昵称模糊查询消息
*
* @param nickname 昵称
* @return 匹配的消息列表
*/
ResponseMessage<List<Message>> searchMessagesByNickname(String nickname);
/**
* 获取指定文章的评论数量
* @param articleId 文章ID
* @return 评论数量
*/
ResponseMessage<Long> getMessageCountByArticleId(Integer articleId);
// 删除所有评论
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

@@ -1,14 +1,20 @@
package com.qf.myafterprojecy.service;
package com.qf.myafterprojecy.service.impl;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Message;
import com.qf.myafterprojecy.pojo.ResponseMessage;
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;
@@ -16,6 +22,8 @@ import java.util.Date;
import java.util.List;
import java.util.Optional;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MessageService implements IMessageService {
@@ -29,10 +37,10 @@ public class MessageService implements IMessageService {
try {
logger.info("查询所有消息");
Iterable<Message> messages = messageRepository.findAll();
return ResponseMessage.success(messages, "查询成功", true);
return ResponseMessage.success(messages, "查询成功");
} catch (DataAccessException e) {
logger.error("查询所有消息失败", e);
return ResponseMessage.failure("查询消息失败:" + e.getMessage());
return ResponseMessage.error("查询消息失败:" + e.getMessage());
}
}
@@ -40,25 +48,26 @@ public class MessageService implements IMessageService {
public ResponseMessage<Message> getMessageById(Integer id) {
if (id == null || id <= 0) {
logger.warn("获取消息时ID无效: {}", id);
return ResponseMessage.failure("消息ID无效");
return ResponseMessage.badRequest("消息ID无效");
}
try {
logger.info("根据ID查询消息: {}", id);
Optional<Message> messageOptional = messageRepository.findById(id);
if (messageOptional.isPresent()) {
return ResponseMessage.success(messageOptional.get(), "查询成功", true);
return ResponseMessage.success(messageOptional.get(), "查询成功");
} else {
logger.warn("未找到ID为{}的消息", id);
return ResponseMessage.failure("未找到指定消息");
return ResponseMessage.notFound("未找到指定消息");
}
} catch (DataAccessException e) {
logger.error("查询消息失败: {}", id, e);
return ResponseMessage.failure("查询消息失败:" + e.getMessage());
return ResponseMessage.error("查询消息失败:" + e.getMessage());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Message> saveMessage(MessageDto messageDto) {
// 参数校验
if (messageDto == null) {
@@ -83,29 +92,30 @@ public class MessageService implements IMessageService {
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.failure("回复的父消息不存在");
return ResponseMessage.notFound("回复的父消息不存在");
}
}
Message savedMessage = messageRepository.save(message);
logger.info("消息保存成功: {}", savedMessage.getMessageid());
return ResponseMessage.success(savedMessage, "保存成功", true);
return ResponseMessage.save(true, savedMessage);
} catch (DataAccessException e) {
logger.error("保存消息失败", e);
return ResponseMessage.failure("保存消息失败:" + e.getMessage());
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.failure("消息ID无效");
return ResponseMessage.badRequest("消息ID无效");
}
try {
@@ -119,14 +129,14 @@ public class MessageService implements IMessageService {
logger.info("同时删除了{}条回复消息", replies.size());
}
logger.info("消息删除成功: {}", id);
return ResponseMessage.success(null, "删除成功", true);
return ResponseMessage.delete(true);
} else {
logger.warn("未找到要删除的消息: {}", id);
return ResponseMessage.failure("未找到要删除的消息");
return ResponseMessage.notFound("未找到要删除的消息");
}
} catch (DataAccessException e) {
logger.error("删除消息失败: {}", id, e);
return ResponseMessage.failure("删除消息失败:" + e.getMessage());
return ResponseMessage.error("删除消息失败:" + e.getMessage());
}
}
@@ -134,16 +144,16 @@ public class MessageService implements IMessageService {
public ResponseMessage<List<Message>> getMessagesByArticleId(Integer articleId) {
if (articleId == null || articleId <= 0) {
logger.warn("根据文章ID查询消息时ID无效: {}", articleId);
return ResponseMessage.failure("文章ID无效");
return ResponseMessage.badRequest("文章ID无效");
}
try {
logger.info("根据文章ID查询消息: {}", articleId);
List<Message> messages = messageRepository.findByArticleid(articleId);
return ResponseMessage.success(messages, "查询成功", true);
return ResponseMessage.success(messages, "查询成功");
} catch (DataAccessException e) {
logger.error("根据文章ID查询消息失败: {}", articleId, e);
return ResponseMessage.failure("查询消息失败:" + e.getMessage());
return ResponseMessage.error("查询消息失败:" + e.getMessage());
}
}
@@ -152,10 +162,10 @@ public class MessageService implements IMessageService {
try {
logger.info("查询所有根消息");
List<Message> messages = messageRepository.findByParentidIsNull();
return ResponseMessage.success(messages, "查询成功", true);
return ResponseMessage.success(messages, "查询成功");
} catch (DataAccessException e) {
logger.error("查询根消息失败", e);
return ResponseMessage.failure("查询消息失败:" + e.getMessage());
return ResponseMessage.error("查询消息失败:" + e.getMessage());
}
}
@@ -163,16 +173,34 @@ public class MessageService implements IMessageService {
public ResponseMessage<List<Message>> getRepliesByParentId(Integer parentId) {
if (parentId == null || parentId <= 0) {
logger.warn("根据父消息ID查询回复时ID无效: {}", parentId);
return ResponseMessage.failure("父消息ID无效");
return ResponseMessage.badRequest("父消息ID无效");
}
try {
logger.info("根据父消息ID查询回复: {}", parentId);
List<Message> replies = messageRepository.findByParentid(parentId);
return ResponseMessage.success(replies, "查询成功", true);
return ResponseMessage.success(replies, "查询成功");
} catch (DataAccessException e) {
logger.error("查询回复消息失败: {}", parentId, e);
return ResponseMessage.failure("查询消息失败:" + e.getMessage());
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());
}
}
@@ -180,33 +208,79 @@ public class MessageService implements IMessageService {
public ResponseMessage<List<Message>> searchMessagesByNickname(String nickname) {
if (StringUtils.isEmpty(nickname)) {
logger.warn("根据昵称查询消息时昵称为空");
return ResponseMessage.failure("昵称不能为空");
return ResponseMessage.badRequest("昵称不能为空");
}
try {
logger.info("根据昵称查询消息: {}", nickname);
List<Message> messages = messageRepository.findByNicknameContaining(nickname);
return ResponseMessage.success(messages, "查询成功", true);
return ResponseMessage.success(messages, "查询成功");
} catch (DataAccessException e) {
logger.error("根据昵称查询消息失败: {}", nickname, e);
return ResponseMessage.failure("查询消息失败:" + e.getMessage());
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<Long> getMessageCountByArticleId(Integer articleId) {
if (articleId == null || articleId <= 0) {
logger.warn("获取文章评论数量时ID无效: {}", articleId);
return ResponseMessage.failure("文章ID无效");
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);
Long count = messageRepository.countByArticleId(articleId);
return ResponseMessage.success(count, "查询成功", true);
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.failure("查询评论数量失败:" + e.getMessage());
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

@@ -1,110 +1,43 @@
# 应用服务 WEB 访问端口
server.port=8080
# ====================================================================
# 应用基本配置 - 通用配置
# ====================================================================
# 环境激活配置
# 说明:默认激活开发环境,生产环境部署时应通过命令行参数或环境变量覆盖
spring.profiles.active=dev
server.port=7070
# 应用名称(通用配置)
spring.application.name=web_project
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/webproject?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&createDatabaseIfNotExist=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=10
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-auto改为none
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true
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
# 暂时禁用Hibernate二级缓存和查询缓存
# spring.jpa.properties.hibernate.cache.use_second_level_cache=true
# spring.jpa.properties.hibernate.cache.use_query_cache=true
# 缓存配置
# spring.cache.type=redis
# spring.cache.redis.time-to-live=1800000
# spring.cache.redis.key-prefix=CACHE_
# spring.cache.redis.use-key-prefix=true
# spring.cache.redis.cache-null-values=false
# Redis配置
# spring.redis.host=localhost
# spring.redis.port=6379
# spring.redis.password=123456
# spring.redis.database=0
# spring.redis.timeout=10000ms
# Redis连接池优化配置
#spring.redis.lettuce.pool.max-active=8
#spring.redis.lettuce.pool.max-wait=10000ms
#spring.redis.lettuce.pool.max-idle=8
#spring.redis.lettuce.pool.min-idle=2
#spring.redis.lettuce.shutdown-timeout=100ms
# 日志配置
logging.level.root=INFO
logging.level.com.qf.myafterprojecy=DEBUG
logging.level.org.springframework.security=INFO
logging.level.org.hibernate.SQL=WARN
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
# 日志文件配置
logging.file.name=logs/web_project.log
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
# Actuator配置 - 生产环境建议限制暴露的端点
management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.endpoint.health.show-details=when_authorized
management.metrics.export.prometheus.enabled=true
# JWT配置 - 生产环境应使用更安全的密钥和环境变量
jwt.secret=mySecretKey
jwt.expiration=86400000
jwt.header=Authorization
jwt.token-prefix=Bearer
# CORS配置 - 生产环境应限制允许的源
cors.allowed-origins=http://localhost:3000
cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS
cors.allowed-headers=*,
Content-Type,
X-Requested-With,
accept,
Origin,
Access-Control-Request-Method,
Access-Control-Request-Headers,
Authorization
cors.allow-credentials=true
cors.max-age=3600
# 安全配置增强
security.basic.enabled=false
security.ignored=/css/**,/js/**,/images/**,/favicon.ico
# 生产环境建议配置
# server.ssl.key-store=classpath:keystore.p12
# server.ssl.key-store-password=password
# server.ssl.key-store-type=PKCS12
# server.ssl.key-alias=tomcat
# ====================================================================
# 会话与编码配置 - 通用配置
# ====================================================================
# 会话配置
server.servlet.session.timeout=30m
server.session.tracking-modes=cookie
# 国际化配置
#spring.mvc.locale-resolver=fixed
#spring.mvc.locale=zh_CN
#
## 响应编码配置
#spring.http.encoding.charset=UTF-8
#spring.http.encoding.enabled=true
#spring.http.encoding.force=true
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
# ====================================================================