feat: 添加UTF-8编码支持并优化DTO验证

refactor: 重构用户服务密码更新逻辑
fix: 删除不再使用的MarkdownDto类
style: 清理日志文件并优化日志配置
build: 更新pom.xml配置以支持UTF-8编码
docs: 更新application.properties配置文档
This commit is contained in:
qingfeng1121
2025-10-30 19:00:47 +08:00
parent 5803080352
commit f6d1d719a9
18 changed files with 2778 additions and 729 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

12
pom.xml
View File

@@ -138,6 +138,7 @@
<configuration> <configuration>
<mainClass>com.qf.myafterprojecy.MyAfterProjecyApplication</mainClass> <mainClass>com.qf.myafterprojecy.MyAfterProjecyApplication</mainClass>
<skip>false</skip> <skip>false</skip>
<jvmArguments>-Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8</jvmArguments>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>
@@ -149,6 +150,17 @@
</executions> </executions>
</plugin> </plugin>
</plugins> </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> </build>
</project> </project>

View File

@@ -3,11 +3,40 @@ package com.qf.myafterprojecy;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.nio.charset.StandardCharsets;
/**
* 应用主类
* 设置系统编码并启动Spring Boot应用
*/
@SpringBootApplication @SpringBootApplication
public class MyAfterProjecyApplication { public class MyAfterProjecyApplication {
public static void main(String[] args) { public static void main(String[] args) {
// 在应用启动前设置系统编码确保所有输出都使用UTF-8
setSystemEncoding();
// 启动Spring Boot应用
SpringApplication.run(MyAfterProjecyApplication.class, args); 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

@@ -14,6 +14,7 @@ import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/** /**
* Spring Security配置类 * Spring Security配置类
@@ -61,8 +62,10 @@ public class SecurityConfig {
.antMatchers(HttpMethod.GET,"/api/markdowns/**").permitAll() .antMatchers(HttpMethod.GET,"/api/markdowns/**").permitAll()
.antMatchers(HttpMethod.GET,"/api/articles/**").permitAll() .antMatchers(HttpMethod.GET,"/api/articles/**").permitAll()
.antMatchers(HttpMethod.GET,"/api/messages/**").permitAll() .antMatchers(HttpMethod.GET,"/api/messages/**").permitAll()
.antMatchers(HttpMethod.GET,"/api/categories/**").permitAll()
// 公开post请求 // 公开post请求
.antMatchers(HttpMethod.POST,"/api/messages/**").permitAll() .antMatchers(HttpMethod.POST,"/api/messages/**").permitAll()
.antMatchers(HttpMethod.POST,"/api/users/**").permitAll()
// 管理员才能访问的路径 // 管理员才能访问的路径
.antMatchers("/api/admin/**").hasRole("ADMIN") .antMatchers("/api/admin/**").hasRole("ADMIN")
// 其他所有请求都需要认证 // 其他所有请求都需要认证
@@ -72,6 +75,15 @@ public class SecurityConfig {
.sessionManagement() .sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS); .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 确保Spring Security不会添加额外的CharacterEncodingFilter
// 因为我们在CharacterEncodingConfig中已经配置了自定义的过滤器
http.addFilterBefore((request, response, chain) -> {
// 确保响应使用UTF-8编码
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
chain.doFilter(request, response);
}, UsernamePasswordAuthenticationFilter.class);
return http.build(); return http.build();
} }
} }

View File

@@ -67,8 +67,7 @@ public class AuthController {
try { try {
// 创建认证令牌 // 创建认证令牌
UsernamePasswordAuthenticationToken authenticationToken = UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
// 执行认证 // 执行认证
Authentication authentication = authenticationManager.authenticate(authenticationToken); Authentication authentication = authenticationManager.authenticate(authenticationToken);
@@ -78,12 +77,11 @@ public class AuthController {
// 获取认证后的用户信息 // 获取认证后的用户信息
UserDetails userDetails = (UserDetails) authentication.getPrincipal(); UserDetails userDetails = (UserDetails) authentication.getPrincipal();
// 构建返回数据 // 构建返回数据
Map<String, Object> data = new HashMap<>(); Map<String, Object> data = new HashMap<>();
data.put("username", userDetails.getUsername()); data.put("username", userDetails.getUsername());
data.put("authorities", userDetails.getAuthorities()); data.put("authorities", userDetails.getAuthorities());
data.put("message", "登录成功"); // data.put("message", "登录成功");
return ResponseMessage.success(data, "登录成功"); return ResponseMessage.success(data, "登录成功");

View File

@@ -1,13 +1,10 @@
package com.qf.myafterprojecy.pojo.dto; package com.qf.myafterprojecy.pojo.dto;
import lombok.Getter;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@Getter
public class ArticleDto { public class ArticleDto {
private Integer id; private Integer articleid;
@NotBlank(message = "标题不能为空") @NotBlank(message = "标题不能为空")
private String title; private String title;
@@ -19,17 +16,23 @@ public class ArticleDto {
private Integer attributeid; private Integer attributeid;
private String img; private String img;
private Integer viewCount;
private Integer likes;
private Integer status; private Integer status;
// Getters and Setters @NotBlank(message = "Markdown内容不能为空")
private String markdownscontent;
public Integer getId() { // Getters and Setters
return id; public Integer getArticleid() {
return articleid;
} }
public void setId(Integer id) { public void setArticleid(Integer articleid) {
this.id = id; this.articleid = articleid;
} }
public String getTitle() { public String getTitle() {
@@ -48,12 +51,12 @@ public class ArticleDto {
this.content = content; this.content = content;
} }
public Integer getStatus() { public Integer getAttributeid() {
return status; return attributeid;
} }
public void setStatus(Integer status) { public void setAttributeid(Integer attributeid) {
this.status = status; this.attributeid = attributeid;
} }
public String getImg() { public String getImg() {
@@ -63,4 +66,36 @@ public class ArticleDto {
public void setImg(String img) { public void setImg(String img) {
this.img = 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; import javax.validation.constraints.NotNull;
public class CategoryAttributeDto { public class CategoryAttributeDto {
private Integer attributeid;
@NotNull(message = "分类ID不能为空") @NotNull(message = "分类ID不能为空")
private Integer categoryid; private Integer categoryid;
@@ -12,6 +13,14 @@ public class CategoryAttributeDto {
private String attributename; private String attributename;
// Getters and Setters // Getters and Setters
public Integer getAttributeid() {
return attributeid;
}
public void setAttributeid(Integer attributeid) {
this.attributeid = attributeid;
}
public Integer getCategoryid() { public Integer getCategoryid() {
return categoryid; return categoryid;
} }

View File

@@ -1,12 +1,18 @@
package com.qf.myafterprojecy.pojo.dto; package com.qf.myafterprojecy.pojo.dto;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime; import java.time.LocalDateTime;
public class CategoryDto { public class CategoryDto {
private Integer typeid; private Integer typeid;
@NotBlank(message = "分类名称不能为空")
private String typename; private String typename;
private String description; private String description;
private LocalDateTime createdAt; private LocalDateTime createdAt;
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
// Getters and Setters // Getters and Setters

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,8 @@ public class MessageDto {
private Integer replyid; private Integer replyid;
private Integer articleid; private Integer articleid;
private Integer likes;
public Integer getReplyid() { public Integer getReplyid() {
return replyid; return replyid;
@@ -26,6 +28,7 @@ public class MessageDto {
public void setReplyid(Integer replyid) { public void setReplyid(Integer replyid) {
this.replyid = replyid; this.replyid = replyid;
} }
public Integer getMessageid() { public Integer getMessageid() {
return messageid; return messageid;
} }
@@ -81,4 +84,12 @@ public class MessageDto {
public void setArticleid(Integer articleid) { public void setArticleid(Integer articleid) {
this.articleid = articleid; this.articleid = articleid;
} }
public Integer getLikes() {
return likes;
}
public void setLikes(Integer likes) {
this.likes = likes;
}
} }

View File

@@ -3,6 +3,8 @@ package com.qf.myafterprojecy.pojo.dto;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
public class UserDto { public class UserDto {
private Long id;
@NotBlank(message = "用户名不能为空") @NotBlank(message = "用户名不能为空")
private String username; private String username;
@@ -15,9 +17,16 @@ public class UserDto {
@NotBlank(message = "手机号不能为空") @NotBlank(message = "手机号不能为空")
private String phone; private String phone;
@NotBlank(message = "角色不能为空")
private int role; private int role;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() { public String getUsername() {
return username; return username;
} }
@@ -57,5 +66,4 @@ public class UserDto {
public void setRole(int role) { public void setRole(int role) {
this.role = role; this.role = role;
} }
} }

View File

@@ -147,7 +147,10 @@ public class UserService implements IUserService {
// 更新用户信息 // 更新用户信息
BeanUtils.copyProperties(userDto, user); BeanUtils.copyProperties(userDto, user);
// 保存更新后的用户 // 如果提供了新密码,则进行加密
if (userDto.getPassword() != null && !userDto.getPassword().isEmpty()) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
}
Users updatedUser = usersRepository.save(user); Users updatedUser = usersRepository.save(user);
return ResponseMessage.update(true, updatedUser); return ResponseMessage.update(true, updatedUser);
} catch (DataAccessException e) { } catch (DataAccessException e) {

View File

@@ -0,0 +1,38 @@
// package com.qf.myafterprojecy.util;
// import org.slf4j.Logger;
// import org.slf4j.LoggerFactory;
// import org.springframework.stereotype.Component;
// import javax.annotation.PostConstruct;
// /**
// * 编码测试工具类
// * 用于验证系统编码配置是否正确,解决中文乱码问题
// */
// @Component
// public class EncodingTestUtil {
// private static final Logger logger = LoggerFactory.getLogger(EncodingTestUtil.class);
// /**
// * 在Bean初始化时执行编码测试
// * 验证日志系统是否能正确输出中文
// */
// @PostConstruct
// public void testEncoding() {
// // 输出系统编码信息
// logger.info("===== 系统编码测试开始 =====");
// logger.info("默认字符编码: {}", java.nio.charset.Charset.defaultCharset());
// logger.info("file.encoding: {}", System.getProperty("file.encoding"));
// logger.info("sun.stdout.encoding: {}", System.getProperty("sun.stdout.encoding"));
// logger.info("sun.stderr.encoding: {}", System.getProperty("sun.stderr.encoding"));
// // 测试中文字符输出
// logger.info("中文测试 - 这是一条测试日志,用于验证中文是否正常显示");
// logger.warn("中文警告测试 - 这是一条警告日志,用于验证中文是否正常显示");
// logger.error("中文错误测试 - 这是一条错误日志,用于验证中文是否正常显示");
// logger.info("===== 系统编码测试结束 =====");
// }
// }

View File

@@ -61,7 +61,11 @@ logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
# 日志文件配置 # 日志文件配置
logging.file.name=logs/web_project.log 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.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
# 确保控制台输出使用UTF-8编码
logging.pattern.console=%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
# 日志编码配置 - 强制使用UTF-8
logging.charset.file=UTF-8
logging.charset.console=UTF-8
# Actuator配置 - 生产环境建议限制暴露的端点 # Actuator配置 - 生产环境建议限制暴露的端点
management.endpoints.web.exposure.include=health,info,metrics,prometheus management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.endpoint.health.show-details=when_authorized management.endpoint.health.show-details=when_authorized
@@ -85,20 +89,41 @@ security.basic.enabled=false
security.ignored=/css/**,/js/**,/images/**,/favicon.ico 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.servlet.session.timeout=30m
server.session.tracking-modes=cookie server.session.tracking-modes=cookie
# 国际化配置 # 国际化配置
spring.mvc.locale-resolver=fixed
spring.web.locale=zh_CN spring.web.locale=zh_CN
# spring.messages.encoding=UTF-8
## 响应编码配置 # 响应编码配置 - 确保所有响应使用UTF-8编码
spring.http.encoding.charset=UTF-8 server.servlet.encoding.charset=UTF-8
spring.http.encoding.enabled=true server.servlet.encoding.force=true
spring.http.encoding.force=true server.servlet.encoding.force-request=true
server.servlet.encoding.force-response=true
server.servlet.encoding.enabled=true
# 配置控制台输出编码 - 通过日志系统配置确保中文显示正常
# logging.pattern.console=%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40logger{39}}{cyan} %clr{:}{faint} %m%n%wEx
# 配置Maven启动JVM参数需在启动时通过命令行指定或在pom.xml中配置
# 实际使用时请在启动命令中添加:-Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8
# 更详细的日志配置 - 确保所有日志输出正确编码
# logging.level.root=INFO
# logging.level.org.springframework.web=DEBUG
# logging.level.org.springframework.security=INFO
# logging.level.com.qf.myafterprojecy=DEBUG
# 确保数据库连接编码正确
spring.datasource.hikari.data-source-properties.useUnicode=true
spring.datasource.hikari.data-source-properties.serverTimezone=Asia/Shanghai
spring.datasource.hikari.data-source-properties.characterEncoding=utf-8
# 应用性能优化配置
spring.main.allow-bean-definition-overriding=true
spring.main.lazy-initialization=false
# API 文档配置
spring.mvc.pathmatch.matching-strategy=ant_path_matcher