Compare commits

...

11 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
58 changed files with 5369 additions and 1171 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

53
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

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

View File

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

View File

@@ -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,38 +13,67 @@ 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.addAllowedMethod("*");
// 允许的请求头
config.addAllowedHeader("*");
// 暴露的响应头这些头信息可以被前端JavaScript访问
config.addExposedHeader("*");
// 设置预检请求的有效期(秒)
config.setMaxAge(3600L);
config.setAllowCredentials(allowCredentials);
// 允许的HTTP方法从配置文件读取并分割
String[] methodsArray = allowedMethods.split(",");
for (String method : methodsArray) {
config.addAllowedMethod(method.trim());
}
// 允许的请求头,从配置文件读取
config.addAllowedHeader(allowedHeaders);
// 明确暴露的响应头对于JWT认证很重要
config.addExposedHeader("Authorization");
config.addExposedHeader("Content-Type");
config.addExposedHeader("X-Requested-With");
config.addExposedHeader("Accept");
config.addExposedHeader("Access-Control-Allow-Origin");
config.addExposedHeader("Access-Control-Allow-Credentials");
// 设置预检请求的有效期(秒),从配置文件读取
config.setMaxAge(maxAge);
// 创建基于URL的CORS配置源
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 为所有路径应用CORS配置
source.registerCorsConfiguration("/**", config);
// 返回配置好的CORS过滤器
return new CorsFilter(source);
}

View File

@@ -0,0 +1,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

@@ -20,6 +20,7 @@ public class PasswordEncoderConfig {
@Bean
public PasswordEncoder passwordEncoder() {
// 强度设置为10这是一个平衡安全性和性能的值
// 数值越高,计算成本越大,安全性越好
return new BCryptPasswordEncoder(10);
}
}

View File

@@ -1,6 +1,6 @@
package com.qf.myafterprojecy.config;
import javax.ws.rs.HttpMethod;
import org.springframework.http.HttpMethod;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@@ -14,6 +14,8 @@ 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配置类
@@ -31,6 +33,9 @@ public class SecurityConfig {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
/**
* 配置AuthenticationManager Bean
* 使用AuthenticationConfiguration来获取认证管理器这是更现代的方式
@@ -49,28 +54,81 @@ public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 启用CORS支持确保与CorsConfig中配置的过滤器配合工作
.cors().and()
// 禁用CSRF保护对于API服务通常不需要
.csrf().disable()
// 配置URL访问权限
.authorizeRequests()
// 允许公开访问的路径
// 公开get请求
.antMatchers(HttpMethod.GET,"/api/auth/**").permitAll()
.antMatchers(HttpMethod.GET,"/api/help/**").permitAll()
.antMatchers(HttpMethod.GET,"/api/category-attributes/**").permitAll()
.antMatchers(HttpMethod.GET,"/api/markdowns/**").permitAll()
.antMatchers(HttpMethod.GET,"/api/articles/**").permitAll()
.antMatchers(HttpMethod.GET,"/api/messages/**").permitAll()
// 公开post请求
.antMatchers(HttpMethod.POST,"/api/messages/**").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()
// 配置会话管理,使用无状态会话
// 配置会话管理,使用无状态会话策略
// 这意味着每个请求都需要包含认证信息如JWT
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
.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,12 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.config.ResponseMessage;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Article;
import com.qf.myafterprojecy.pojo.dto.ArticleDto;
import com.qf.myafterprojecy.service.imp.IArticleService;
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;
@@ -19,6 +22,7 @@ import java.util.List;
*/
@RestController
@RequestMapping("/api/articles")
@Validated
public class ArticleController {
@@ -35,13 +39,37 @@ public class ArticleController {
return articleService.getArticleById(id);
}
/**
* 获取已发布的文章列表
* 获取已发布或未发布的文章列表
* @return 返回包含已发布文章列表的ResponseMessage对象
*/
@GetMapping("/published")
public ResponseMessage<List<Article>> getPublishedArticles() {
return articleService.getPublishedArticles();
}
/**
* 根据状态获取文章列表
* @param status 文章状态0未发表 1已发表 2已删除
* @return 返回包含文章列表的ResponseMessage对象
*/
@GetMapping("/status/{status}")
public ResponseMessage<List<Article>> getArticlesByStatus(@PathVariable Integer status) {
return articleService.getArticlesByStatus(status);
}
/**
* 根据状态分页获取文章列表
* @param status 文章状态0未发表 1已发表 2已删除
* @param page 页码从0开始可选默认为0
* @param size 每页大小可选默认为10最大为100
* @return 返回包含分页文章列表的ResponseMessage对象
*/
// api/articles/status/page?status=1&page=1&size=2
// get 只能这样不能传递json
@GetMapping("/status/page")
public ResponseMessage<Page<Article>> getArticlesByStatusWithPagination(ArriclePageDto pageDto) {
return articleService.getArticlesByStatusWithPagination(pageDto);
}
/**
* 获取所有文章列表
* @return 返回包含文章列表的ResponseMessage对象
@@ -50,6 +78,15 @@ 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 文章标题
@@ -70,7 +107,6 @@ public class ArticleController {
return articleService.getArticlesByAttribute(attributeId);
}
/**
/**
* 根据属性ID获取最新文章按创建时间降序
* @param attributeId 属性ID
@@ -88,7 +124,7 @@ public class ArticleController {
* @return 返回包含新创建文章信息的ResponseMessage对象
*/
@PostMapping
@PreAuthorize("hasRole('AUTHOR')")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Article> createArticle(@Valid @RequestBody ArticleDto articleDto) {
return articleService.saveArticle(articleDto);
}
@@ -109,7 +145,7 @@ public class ArticleController {
* @return 返回包含更新后文章信息的ResponseMessage对象
*/
@PutMapping("/{id}")
@PreAuthorize("hasRole('AUTHOR')")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Article> updateArticle(
@PathVariable Integer id,
@Valid @RequestBody ArticleDto articleDto) {
@@ -123,7 +159,7 @@ 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);
}

View File

@@ -1,6 +1,7 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.config.ResponseMessage;
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;
@@ -31,6 +32,9 @@ public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtils jwtUtils;
/**
* 用户登录请求体
*/
@@ -67,8 +71,7 @@ public class AuthController {
try {
// 创建认证令牌
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
// 执行认证
Authentication authentication = authenticationManager.authenticate(authenticationToken);
@@ -78,12 +81,14 @@ public class AuthController {
// 获取认证后的用户信息
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("message", "登录成功");
data.put("token", token);
data.put("tokenPrefix", jwtUtils.getTokenPrefix());
return ResponseMessage.success(data, "登录成功");

View File

@@ -1,13 +1,14 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.config.ResponseMessage;
import com.qf.myafterprojecy.pojo.Category_attribute;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Categoryattribute;
import com.qf.myafterprojecy.pojo.dto.CategoryAttributeDto;
import com.qf.myafterprojecy.service.imp.ICategoryAttributeService;
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.*;
@@ -30,11 +31,15 @@ public class CategoryAttributeController {
* @return 属性信息
*/
@GetMapping("/{id}")
public ResponseMessage<Category_attribute> getAttributeById(@PathVariable Integer 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获取属性列表
@@ -42,7 +47,7 @@ public class CategoryAttributeController {
* @return 属性列表
*/
@GetMapping("/category/{categoryId}")
public ResponseMessage<List<Category_attribute>> getAttributesByCategory(@PathVariable Integer categoryId) {
public ResponseMessage<List<Categoryattribute>> getAttributesByCategory(@PathVariable Integer categoryId) {
log.info("接收根据分类ID获取属性列表的请求: 分类ID={}", categoryId);
return categoryAttributeService.getAttributesByCategoryId(categoryId);
}
@@ -53,7 +58,8 @@ public class CategoryAttributeController {
* @return 创建结果
*/
@PostMapping
public ResponseMessage<Category_attribute> createAttribute(@Valid @RequestBody CategoryAttributeDto dto) {
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Categoryattribute> createAttribute(@Valid @RequestBody CategoryAttributeDto dto) {
log.info("接收创建分类属性的请求: 分类ID={}, 属性名称={}",
dto.getCategoryid(), dto.getAttributename());
return categoryAttributeService.saveCategoryAttribute(dto);
@@ -66,7 +72,8 @@ public class CategoryAttributeController {
* @return 更新结果
*/
@PutMapping("/{id}")
public ResponseMessage<Category_attribute> updateAttribute(
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Categoryattribute> updateAttribute(
@PathVariable Integer id,
@Valid @RequestBody CategoryAttributeDto dto) {
log.info("接收更新分类属性的请求: ID={}, 分类ID={}, 属性名称={}",
@@ -80,6 +87,7 @@ public class CategoryAttributeController {
* @return 删除结果
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Boolean> deleteAttribute(@PathVariable Integer id) {
log.info("接收删除分类属性的请求: ID={}", id);
return categoryAttributeService.deleteCategoryAttribute(id);

View File

@@ -1,13 +1,14 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.config.ResponseMessage;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Category;
import com.qf.myafterprojecy.pojo.dto.CategoryDto;
import com.qf.myafterprojecy.service.imp.ICategoryService;
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.*;
@@ -55,6 +56,7 @@ public class CategoryController {
* @return 返回创建结果
*/
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Category> createCategory(@Valid @RequestBody CategoryDto categoryDto) {
log.info("接收创建分类的请求: {}", categoryDto.getTypename());
return categoryService.saveCategory(categoryDto);
@@ -67,6 +69,7 @@ public class CategoryController {
* @return 返回更新结果
*/
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Category> updateCategory(
@PathVariable Integer id,
@Valid @RequestBody CategoryDto categoryDto) {
@@ -80,6 +83,7 @@ public class CategoryController {
* @return 返回删除结果
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Boolean> deleteCategory(@PathVariable Integer id) {
log.info("接收删除分类的请求: {}", id);
return categoryService.deleteCategory(id);

View File

@@ -9,7 +9,7 @@ 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.config.ResponseMessage;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import java.io.File;
import java.io.FileInputStream;
@@ -53,8 +53,8 @@ public class HelpController {
// 读取文件内容
String markdownContent = new String(FileCopyUtils.copyToByteArray(new FileInputStream(readmeFile)), StandardCharsets.UTF_8);
// 将Markdown转换为HTML
// String htmlContent = convertMarkdownToHtml(markdownContent);
return ResponseMessage.success(markdownContent, "获取API文档成功");
String htmlContent = convertMarkdownToHtml(markdownContent);
return ResponseMessage.success(htmlContent, "获取API文档成功");
} catch (IOException e) {
return ResponseMessage.error("读取README_API.md文件失败: " + e.getMessage());
}

View File

@@ -1,13 +1,15 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.config.ResponseMessage;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Message;
import com.qf.myafterprojecy.pojo.dto.MessageDto;
import com.qf.myafterprojecy.service.imp.IMessageService;
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;
@@ -29,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获取消息
*/
@@ -75,18 +95,11 @@ public class MessageController {
return messageService.searchMessagesByNickname(nickname);
}
/**
* 获取指定文章的评论数量
*/
@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);
@@ -101,16 +114,16 @@ public class MessageController {
* 根据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();
}
// 新增API端点
}

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

@@ -1,13 +1,15 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.config.ResponseMessage;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Users;
import com.qf.myafterprojecy.pojo.dto.UserDto;
import com.qf.myafterprojecy.service.imp.IUserService;
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;
@@ -15,6 +17,7 @@ import java.util.List;
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@@ -38,6 +41,7 @@ public class UserController {
* @return 用户列表
*/
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<List<Users>> getAllUsers() {
logger.info("获取所有用户列表");
return userService.getAllUsers();
@@ -45,10 +49,10 @@ public class UserController {
/**
* 根据用户名获取用户信息
* @param username 用户名
* @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);
@@ -72,6 +76,7 @@ public class UserController {
* @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);
@@ -83,6 +88,7 @@ public class UserController {
* @return 删除结果
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Boolean> deleteUser(@PathVariable Long id) {
logger.info("删除用户用户ID: {}", id);
return userService.deleteUser(id);
@@ -94,6 +100,7 @@ public class UserController {
* @return 用户列表
*/
@GetMapping("/role/{role}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<List<Users>> getUsersByRole(@PathVariable int role) {
logger.info("根据角色查询用户列表,角色: {}", role);
return userService.getUsersByRole(role);

View File

@@ -1,4 +1,4 @@
package com.qf.myafterprojecy;
package com.qf.myafterprojecy.exceptopn;
import javax.servlet.http.HttpServletRequest;
@@ -7,8 +7,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.qf.myafterprojecy.config.ResponseMessage;
@RestControllerAdvice
public class GlobalExceptionHandler {
Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

View File

@@ -1,4 +1,4 @@
package com.qf.myafterprojecy.config;
package com.qf.myafterprojecy.exceptopn;
import lombok.Data;
import org.springframework.http.HttpStatus;

View File

@@ -1,85 +0,0 @@
// package com.qf.myafterprojecy.init;
// import com.qf.myafterprojecy.pojo.Category;
// import com.qf.myafterprojecy.repository.CategoryRepository;
// 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.time.LocalDateTime;
// import java.util.ArrayList;
// import java.util.List;
// /**
// * 分类数据初始化类,用于在应用启动时初始化分类数据
// */
// @Component
// public class CategoryDataInit implements ApplicationRunner {
// private static final Logger logger = LoggerFactory.getLogger(CategoryDataInit.class);
// @Autowired
// private CategoryRepository categoryRepository;
// @Override
// public void run(ApplicationArguments args) throws Exception {
// logger.info("===== 分类数据初始化开始 =====");
// // 检查数据库中是否已有分类数据
// long count = categoryRepository.count();
// logger.info("当前数据库中分类数量: {}", count);
// // 如果没有分类数据,添加一些测试数据
// if (count == 0) {
// logger.info("数据库中没有分类数据,开始添加初始化数据...");
// addInitialCategories();
// } else {
// logger.info("数据库中已存在分类数据,无需初始化");
// }
// logger.info("===== 分类数据初始化结束 =====");
// }
// /**
// * 添加初始分类数据
// */
// private void addInitialCategories() {
// List<Category> categories = new ArrayList<>();
// // 创建几个常见的文章分类
// Category category1 = new Category();
// category1.setTypename("技术分享");
// category1.setDescription("技术文章、教程、经验分享等");
// category1.setCreatedAt(LocalDateTime.now());
// category1.setUpdatedAt(LocalDateTime.now());
// categories.add(category1);
// Category category2 = new Category();
// category2.setTypename("生活随笔");
// category2.setDescription("日常生活、心情记录、随笔等");
// category2.setCreatedAt(LocalDateTime.now());
// category2.setUpdatedAt(LocalDateTime.now());
// categories.add(category2);
// Category category3 = new Category();
// category3.setTypename("学习笔记");
// category3.setDescription("学习过程中的笔记、总结等");
// category3.setCreatedAt(LocalDateTime.now());
// category3.setUpdatedAt(LocalDateTime.now());
// categories.add(category3);
// Category category4 = new Category();
// category4.setTypename("行业动态");
// category4.setDescription("行业新闻、趋势分析等");
// category4.setCreatedAt(LocalDateTime.now());
// category4.setUpdatedAt(LocalDateTime.now());
// categories.add(category4);
// // 保存分类数据到数据库
// categoryRepository.saveAll(categories);
// logger.info("成功添加 {} 条分类数据", categories.size());
// }
// }

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,7 +17,6 @@ public class Article {
@Column(name = "title")
private String title;
@NotBlank(message = "内容不能为空")
@Column(name = "content", columnDefinition = "TEXT")
private String content;
@@ -44,7 +43,6 @@ public class Article {
private Integer status; // 0-草稿1-已发布2-已删除
@Column(name = "markdownscontent")
@NotBlank(message = "Markdown内容不能为空")
private String markdownscontent;
// Getters and Setters

View File

@@ -9,8 +9,8 @@ import java.time.LocalDateTime;
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "typeid")
private Integer typeid;
@Column(name = "categoryid")
private Integer categoryid;
@NotBlank(message = "分类名称不能为空")
@Column(name = "typename")
@@ -26,12 +26,12 @@ public class Category {
private LocalDateTime updatedAt;
// Getters and Setters
public Integer getTypeid() {
return typeid;
public Integer getCategoryid() {
return categoryid;
}
public void setTypeid(Integer typeid) {
this.typeid = typeid;
public void setCategoryid(Integer categoryid) {
this.categoryid = categoryid;
}
public String getTypename() {

View File

@@ -4,7 +4,7 @@ import javax.persistence.*;
@Entity
@Table(name = "category_attribute")
public class Category_attribute {
public class Categoryattribute {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "attributeid")

View File

@@ -8,7 +8,6 @@ import java.util.Date;
public class Message {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "messageid")
private Integer messageid;
@@ -21,6 +20,9 @@ public class Message {
@Column(name = "content", columnDefinition = "text")
private String content;
@Column(name = "messageimg")
private String messageimg;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_at")
private Date createdAt;
@@ -37,6 +39,7 @@ public class Message {
@Column(name = "likes")
private Integer likes; // 点赞数
public Integer getLikes() {
return likes;
}
@@ -108,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

@@ -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,35 +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() {
@@ -48,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() {
@@ -63,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

@@ -4,6 +4,7 @@ import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class CategoryAttributeDto {
private Integer attributeid;
@NotNull(message = "分类ID不能为空")
private Integer categoryid;
@@ -12,6 +13,14 @@ public class CategoryAttributeDto {
private String attributename;
// Getters and Setters
public Integer getAttributeid() {
return attributeid;
}
public void setAttributeid(Integer attributeid) {
this.attributeid = attributeid;
}
public Integer getCategoryid() {
return categoryid;
}

View File

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

View File

@@ -1,20 +0,0 @@
package com.qf.myafterprojecy.pojo.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class MarkdownDto {
@NotBlank(message = "Markdown内容不能为空")
private String markdownscontent;
public String getMarkdownscontent() {
return markdownscontent;
}
public void setMarkdownscontent(String markdownscontent) {
this.markdownscontent = markdownscontent;
}
}

View File

@@ -18,6 +18,10 @@ public class MessageDto {
private Integer replyid;
private Integer articleid;
private Integer likes;
private String messageimg;
public Integer getReplyid() {
return replyid;
@@ -26,6 +30,7 @@ public class MessageDto {
public void setReplyid(Integer replyid) {
this.replyid = replyid;
}
public Integer getMessageid() {
return messageid;
}
@@ -81,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

@@ -3,6 +3,8 @@ package com.qf.myafterprojecy.pojo.dto;
import javax.validation.constraints.NotBlank;
public class UserDto {
private Long id;
@NotBlank(message = "用户名不能为空")
private String username;
@@ -15,9 +17,16 @@ public class UserDto {
@NotBlank(message = "手机号不能为空")
private String phone;
@NotBlank(message = "角色不能为空")
private int role;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
@@ -57,5 +66,4 @@ public class UserDto {
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;
@@ -79,6 +81,7 @@ 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-已删除
@@ -86,4 +89,47 @@ public interface ArticleRepository extends JpaRepository<Article, Integer> {
*/
@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

@@ -1,6 +1,6 @@
package com.qf.myafterprojecy.repository;
import com.qf.myafterprojecy.pojo.Category_attribute;
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;
@@ -10,23 +10,23 @@ import java.util.List;
import java.util.Optional;
@Repository
public interface CategoryAttributeRepository extends JpaRepository<Category_attribute, Integer> {
public interface CategoryAttributeRepository extends JpaRepository<Categoryattribute, Integer> {
/**
* 根据分类ID查询分类属性列表
* @param categoryid 分类ID
* @return 返回该分类下的所有属性列表
*/
@Query("SELECT ca FROM Category_attribute ca WHERE ca.categoryid = :categoryid")
List<Category_attribute> findByCategoryId(@Param("categoryid") Integer categoryid);
@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 Category_attribute ca WHERE ca.attributeid = :attributeid")
Optional<Category_attribute> findByAttributeId(@Param("attributeid") Integer attributeid);
@Query("SELECT ca FROM Categoryattribute ca WHERE ca.attributeid = :attributeid")
Optional<Categoryattribute> findByAttributeId(@Param("attributeid") Integer attributeid);
/**
* 检查分类下是否存在指定名称的属性
@@ -42,5 +42,5 @@ public interface CategoryAttributeRepository extends JpaRepository<Category_attr
* @param attributename 属性名称
* @return 属性对象
*/
Optional<Category_attribute> findByCategoryidAndAttributename(Integer categoryid, String attributename);
Optional<Categoryattribute> findByCategoryidAndAttributename(Integer categoryid, String attributename);
}

View File

@@ -1,6 +1,10 @@
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;
@@ -60,10 +64,33 @@ public interface MessageRepository extends JpaRepository<Message, Integer> {
void incrementLikes(@Param("messageId") Integer messageId);
// 统计指定文章的评论数量
/**
* 统计指定文章的评论数量
* @param articleId 文章ID
* @return 评论数量
* 根据文章ID分页查询消息
* @param articleid 文章ID
* @param pageable 分页信息
* @return 分页消息列表
*/
@Query("SELECT COUNT(m) FROM Message m WHERE m.articleid = :articleId")
Long countByArticleId(@Param("articleId") Integer articleId);
@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

@@ -1,8 +1,10 @@
package com.qf.myafterprojecy.service.imp;
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.config.ResponseMessage;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Article;
import com.qf.myafterprojecy.pojo.dto.ArriclePageDto;
import com.qf.myafterprojecy.pojo.dto.ArticleDto;
import org.springframework.data.domain.Page;
import java.util.List;
@@ -18,7 +20,18 @@ public interface IArticleService {
* @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角色用户访问
@@ -83,4 +96,13 @@ public interface IArticleService {
* @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

@@ -1,33 +1,38 @@
package com.qf.myafterprojecy.service.imp;
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.config.ResponseMessage;
import com.qf.myafterprojecy.pojo.Category_attribute;
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<Category_attribute> getCategoryAttributeById(Integer id);
ResponseMessage<Categoryattribute> getCategoryAttributeById(Integer id);
/**
* 根据分类ID获取属性列表
* @param categoryId 分类ID
* @return 属性列表
*/
ResponseMessage<List<Category_attribute>> getAttributesByCategoryId(Integer categoryId);
ResponseMessage<List<Categoryattribute>> getAttributesByCategoryId(Integer categoryId);
/**
* 创建新的分类属性
* @param dto 分类属性数据
* @return 创建结果
*/
ResponseMessage<Category_attribute> saveCategoryAttribute(CategoryAttributeDto dto);
ResponseMessage<Categoryattribute> saveCategoryAttribute(CategoryAttributeDto dto);
/**
* 更新分类属性
@@ -35,7 +40,7 @@ public interface ICategoryAttributeService {
* @param dto 分类属性数据
* @return 更新结果
*/
ResponseMessage<Category_attribute> updateCategoryAttribute(Integer id, CategoryAttributeDto dto);
ResponseMessage<Categoryattribute> updateCategoryAttribute(Integer id, CategoryAttributeDto dto);
/**
* 删除分类属性

View File

@@ -1,6 +1,6 @@
package com.qf.myafterprojecy.service.imp;
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.config.ResponseMessage;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Category;
import com.qf.myafterprojecy.pojo.dto.CategoryDto;

View File

@@ -1,8 +1,11 @@
package com.qf.myafterprojecy.service.imp;
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.config.ResponseMessage;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Message;
import com.qf.myafterprojecy.pojo.dto.MessageDto;
import com.qf.myafterprojecy.pojo.dto.MessagePageDto;
import java.util.List;
@@ -14,7 +17,18 @@ public interface IMessageService {
* 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获取消息的方法
*
@@ -82,11 +96,4 @@ public interface IMessageService {
// 删除所有评论
ResponseMessage<Void> deleteAllMessages();
/**
* 获取指定文章的评论数量
*
* @param articleId 文章ID
* @return 评论数量
*/
ResponseMessage<Long> getMessageCountByArticleId(Integer articleId);
}

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

@@ -1,6 +1,6 @@
package com.qf.myafterprojecy.service.imp;
package com.qf.myafterprojecy.service;
import com.qf.myafterprojecy.config.ResponseMessage;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Users;
import com.qf.myafterprojecy.pojo.dto.UserDto;

View File

@@ -1,22 +1,27 @@
package com.qf.myafterprojecy.service;
package com.qf.myafterprojecy.service.impl;
import com.qf.myafterprojecy.config.ResponseMessage;
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.imp.IArticleService;
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 {
@@ -29,6 +34,11 @@ public class ArticleService implements IArticleService {
@Autowired
private CategoryAttributeRepository categoryAttributeRepository;
/**
* 根据文章ID获取文章详情
* @param id 文章ID
* @return 返回包含文章详情的ResponseMessage对象
*/
@Override
@Transactional(readOnly = true)
public ResponseMessage<Article> getArticleById(String id) {
@@ -38,9 +48,10 @@ public class ArticleService implements IArticleService {
}
Article article = articleRepository.findById(Integer.parseInt(id))
.orElseThrow(() -> new RuntimeException("文章不存在"));
// 文章浏览次数增加
articleRepository.incrementViewCount(Integer.parseInt(id));
// 暂时不增加浏览次数以避免事务问题
// articleRepository.incrementViewCount(Integer.parseInt(id));
return ResponseMessage.success(article, "获取文章成功");
} catch (NumberFormatException e) {
return ResponseMessage.badRequest("文章ID格式不正确");
@@ -55,6 +66,50 @@ public class ArticleService implements IArticleService {
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对象
@@ -70,6 +125,52 @@ public class ArticleService implements IArticleService {
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) {
@@ -168,8 +269,6 @@ public class ArticleService implements IArticleService {
@Transactional(readOnly = true)
public ResponseMessage<List<Article>> getArticlesByCategory(Integer categoryId) {
try {
// 为了兼容旧接口这里需要调整逻辑
// 旧接口使用typeid但我们现在用attributeid
// 可以考虑查询该分类下的所有属性然后获取相关文章
log.warn("使用了旧接口getArticlesByCategory请考虑迁移到getArticlesByAttribute");
return ResponseMessage.success(articleRepository.findPublishedByAttribute(categoryId), "获取分类文章成功");

View File

@@ -1,10 +1,10 @@
package com.qf.myafterprojecy.service;
package com.qf.myafterprojecy.service.impl;
import com.qf.myafterprojecy.config.ResponseMessage;
import com.qf.myafterprojecy.pojo.Category_attribute;
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.imp.ICategoryAttributeService;
import com.qf.myafterprojecy.service.ICategoryAttributeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -24,15 +24,36 @@ public class CategoryAttributeService implements ICategoryAttributeService {
@Autowired
private CategoryAttributeRepository categoryAttributeRepository;
/**
* 获取全部分类属性
* @return 所有分类属性列表
*/
@Override
@Transactional(readOnly = true)
public ResponseMessage<Category_attribute> getCategoryAttributeById(Integer id) {
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无效");
}
Category_attribute attribute = categoryAttributeRepository.findById(id)
Categoryattribute attribute = categoryAttributeRepository.findById(id)
.orElseThrow(() -> new RuntimeException("分类属性不存在"));
return ResponseMessage.success(attribute, "获取分类属性成功");
@@ -48,15 +69,20 @@ public class CategoryAttributeService implements ICategoryAttributeService {
}
}
/**
* 根据分类ID获取属性列表
* @param categoryId 分类ID
* @return 属性列表
*/
@Override
@Transactional(readOnly = true)
public ResponseMessage<List<Category_attribute>> getAttributesByCategoryId(Integer categoryId) {
public ResponseMessage<List<Categoryattribute>> getAttributesByCategoryId(Integer categoryId) {
try {
if (categoryId == null || categoryId <= 0) {
return ResponseMessage.badRequest("分类ID无效");
}
List<Category_attribute> attributes = categoryAttributeRepository.findByCategoryId(categoryId);
List<Categoryattribute> attributes = categoryAttributeRepository.findByCategoryId(categoryId);
return ResponseMessage.success(attributes, "获取分类属性列表成功");
} catch (DataAccessException e) {
log.error("获取分类属性列表失败: {}", e.getMessage());
@@ -64,9 +90,14 @@ public class CategoryAttributeService implements ICategoryAttributeService {
}
}
/**
* 保存分类属性
* @param dto 分类属性DTO
* @return 保存结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Category_attribute> saveCategoryAttribute(CategoryAttributeDto dto) {
public ResponseMessage<Categoryattribute> saveCategoryAttribute(CategoryAttributeDto dto) {
try {
// 检查属性名称是否已存在于该分类下
if (categoryAttributeRepository.existsByCategoryidAndAttributename(
@@ -74,10 +105,10 @@ public class CategoryAttributeService implements ICategoryAttributeService {
return ResponseMessage.badRequest("该分类下已存在同名属性");
}
Category_attribute attribute = new Category_attribute();
Categoryattribute attribute = new Categoryattribute();
BeanUtils.copyProperties(dto, attribute);
Category_attribute savedAttribute = categoryAttributeRepository.save(attribute);
Categoryattribute savedAttribute = categoryAttributeRepository.save(attribute);
log.info("成功创建分类属性: {}, 分类ID: {}",
savedAttribute.getAttributename(), savedAttribute.getCategoryid());
@@ -88,15 +119,21 @@ public class CategoryAttributeService implements ICategoryAttributeService {
}
}
/**
* 更新分类属性
* @param id 属性ID
* @param dto 分类属性DTO
* @return 更新结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Category_attribute> updateCategoryAttribute(Integer id, CategoryAttributeDto dto) {
public ResponseMessage<Categoryattribute> updateCategoryAttribute(Integer id, CategoryAttributeDto dto) {
try {
if (id == null || id <= 0) {
return ResponseMessage.badRequest("属性ID无效");
}
Category_attribute attribute = categoryAttributeRepository.findById(id)
Categoryattribute attribute = categoryAttributeRepository.findById(id)
.orElseThrow(() -> new RuntimeException("分类属性不存在"));
// 如果修改了属性名称检查新名称是否已存在
@@ -109,7 +146,7 @@ public class CategoryAttributeService implements ICategoryAttributeService {
BeanUtils.copyProperties(dto, attribute);
attribute.setAttributeid(id); // 确保ID不变
Category_attribute updatedAttribute = categoryAttributeRepository.save(attribute);
Categoryattribute updatedAttribute = categoryAttributeRepository.save(attribute);
log.info("成功更新分类属性: ID={}, 名称={}",
updatedAttribute.getAttributeid(), updatedAttribute.getAttributename());
@@ -126,6 +163,11 @@ public class CategoryAttributeService implements ICategoryAttributeService {
}
}
/**
* 删除分类属性
* @param id 属性ID
* @return 删除结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseMessage<Boolean> deleteCategoryAttribute(Integer id) {
@@ -147,7 +189,12 @@ public class CategoryAttributeService implements ICategoryAttributeService {
return ResponseMessage.error("删除分类属性失败");
}
}
/**
* 检查分类属性是否存在
* @param categoryId 分类ID
* @param attributeName 属性名称
* @return 是否存在
*/
@Override
@Transactional(readOnly = true)
public ResponseMessage<Boolean> existsByCategoryAndName(Integer categoryId, String attributeName) {

View File

@@ -1,10 +1,10 @@
package com.qf.myafterprojecy.service;
package com.qf.myafterprojecy.service.impl;
import com.qf.myafterprojecy.config.ResponseMessage;
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.imp.ICategoryService;
import com.qf.myafterprojecy.service.ICategoryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -1,16 +1,20 @@
package com.qf.myafterprojecy.service;
package com.qf.myafterprojecy.service.impl;
import com.qf.myafterprojecy.config.ResponseMessage;
import com.qf.myafterprojecy.exceptopn.ResponseMessage;
import com.qf.myafterprojecy.pojo.Message;
import com.qf.myafterprojecy.pojo.dto.MessageDto;
import com.qf.myafterprojecy.pojo.dto.MessagePageDto;
import com.qf.myafterprojecy.repository.MessageRepository;
import com.qf.myafterprojecy.service.imp.IMessageService;
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;
@@ -231,20 +235,52 @@ public class MessageService implements IMessageService {
}
}
@Override
public ResponseMessage<Long> getMessageCountByArticleId(Integer articleId) {
if (articleId == null || articleId <= 0) {
logger.warn("获取文章评论数量时ID无效: {}", articleId);
return ResponseMessage.badRequest("文章ID无效");
}
@Override
public ResponseMessage<List<Message>> getMessagesByPage(MessagePageDto messagePageDto) {
if (messagePageDto == null) {
logger.warn("分页查询消息时参数为空");
return ResponseMessage.badRequest("分页参数不能为空");
}
if (messagePageDto.getPageNum() == null) {
logger.warn("分页查询消息时页码无效: {}", messagePageDto.getPageNum());
return ResponseMessage.badRequest("页码无效");
}
if (messagePageDto.getPageSize() == null || messagePageDto.getPageSize() <= 0) {
logger.warn("分页查询消息时每页数量无效: {}", messagePageDto.getPageSize());
return ResponseMessage.badRequest("每页数量无效");
}
try {
logger.info("获取文章评论数量: {}", articleId);
Long count = messageRepository.countByArticleId(articleId);
// 如何文章id为空默认根据分页基础信息查询消息
PageRequest pageable = PageRequest.of(messagePageDto.getPageNum(), messagePageDto.getPageSize());
if (messagePageDto.getArticleid() != null && messagePageDto.getArticleid() > 0) {
// 如果文章ID存在根据文章ID查询消息
Page<Message> messagePage = messageRepository.findByArticleId(messagePageDto.getArticleid(), pageable);
return ResponseMessage.success(messagePage.getContent(), "查询成功");
}
// 如果文章ID不存在根据分页基础信息查询所有消息
Page<Message> messagePage = messageRepository.findAllMessages(pageable);
return ResponseMessage.success(messagePage.getContent(), "查询成功");
} catch (DataAccessException e) {
logger.error("分页查询消息失败: {}", messagePageDto, e);
return ResponseMessage.error("查询消息失败:" + e.getMessage());
}
}
// 获取回复消息条数 如果id为空获取文章id为空的消息条数
@Override
public ResponseMessage<Integer> getMessageCountByArticleId(Integer articleId) {
try {
logger.info("获取文章回复数量: {}", articleId);
if (articleId == null || articleId <= 0) {
Integer count = messageRepository.countReplyByArticleIdIsNull();
return ResponseMessage.success(count, "查询成功");
}
Integer count = messageRepository.countReplyByArticleId(articleId);
return ResponseMessage.success(count, "查询成功");
} catch (DataAccessException e) {
logger.error("获取文章评论数量失败: {}", articleId, e);
return ResponseMessage.error("查询评论数量失败:" + e.getMessage());
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

@@ -1,10 +1,10 @@
package com.qf.myafterprojecy.service;
package com.qf.myafterprojecy.service.impl;
import com.qf.myafterprojecy.config.ResponseMessage;
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.imp.IUserService;
import com.qf.myafterprojecy.service.IUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -147,7 +147,10 @@ public class UserService implements IUserService {
// 更新用户信息
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) {

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,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,104 +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&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=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=*,
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.web.locale=zh_CN
#
## 响应编码配置
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
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
# ====================================================================