本文记录了如何实现用获取户信息,用户信息更新,用户头像上传三大基础功能
先上接口实现截图:
一、项目结构概览
先介绍一下
个人博客系统采用了标准的 Spring Boot 项目结构,用户功能相关的文件主要分布在以下几个目录:
WeblogSystem/
├── src/main/java/com/zxy/weblogsystem/
│ ├── controller/ # 控制器层,处理HTTP请求
│ ├── service/ # 服务层,实现业务逻辑
│ │ └── impl/ # 服务实现类
│ ├── repository/ # 数据访问层,与数据库交互
│ ├── entity/ # 实体类,映射数据库表
│ ├── dto/ # 数据传输对象,用于API交互
│ ├── exception/ # 自定义异常类
│ └── config/ # 配置类
├── src/main/resources/
│ ├── static/ # 静态资源
│ ├── templates/ # 模板文件
│ ├── application.properties # 应用配置
│ ├── schema.sql # 数据库表结构
│ └── data.sql # 初始数据
└── docs/ # 项目文档
二、用户信息功能实现
- 实体类定义
文件位置:src/main/java/com/zxy/weblogsystem/entity/User.java
功能说明:定义用户实体类,映射数据库中的 users 表。
主要内容:
@Data // Lombok注解,自动生成getter/setter等方法
@NoArgsConstructor // 无参构造函数
@AllArgsConstructor // 全参构造函数
@Entity // JPA实体类注解
@Table(name = "users") // 指定表名
public class User {
@Id // 主键
@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增策略
private Long id;
@Column(nullable = false, unique = true, length = 50)
private String username;
@Column(nullable = false, length = 100)
private String password;
@Column(nullable = false, unique = true, length = 100)
private String email;
@Column(length = 50)
private String nickname;
@Column(length = 255)
private String avatarUrl; // 头像URL
@Column(nullable = false, length = 20)
private String role = "USER";
@Column(nullable = false)
private Integer status = 1;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}
- 数据传输对象(DTO)
2.1 用户信息DTO
文件位置:src/main/java/com/zxy/weblogsystem/dto/UserInfoDto.java
功能说明:用于返回用户信息的数据传输对象。
主要内容:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserInfoDto {
private Long id;
private String username;
private String nickname;
private String email;
private String avatarUrl;
private String role;
private Integer status;
private Integer followersCount; // 粉丝数
private Integer followingCount; // 关注数
private Integer articleCount; // 文章数
private LocalDateTime createdAt;
}
2.2 用户更新DTO
文件位置:src/main/java/com/zxy/weblogsystem/dto/UserUpdateDto.java
功能说明:用于接收用户信息更新请求的数据传输对象。
主要内容:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserUpdateDto {
@Size(max = 50, message = "昵称长度不能超过50个字符")
private String nickname;
@URL(message = "头像URL格式不正确")
private String avatarUrl;
}
2.3 API响应DTO
文件位置:src/main/java/com/zxy/weblogsystem/dto/ApiResponse.java
功能说明:统一的API响应格式。
主要内容:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
private Integer code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "OK", data);
}
public static <T> ApiResponse<T> success(String message, T data) {
return new ApiResponse<>(200, message, data);
}
public static <T> ApiResponse<T> error(Integer code, String message) {
return new ApiResponse<>(code, message, null);
}
}
- 数据访问层
文件位置:src/main/java/com/zxy/weblogsystem/repository/UserRepository.java
功能说明:用户数据访问接口,提供数据库操作方法。
主要内容:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
}
- 服务层
4.1 用户服务接口
文件位置:src/main/java/com/zxy/weblogsystem/service/UserService.java
功能说明:定义用户相关的业务逻辑接口。
主要内容:
public interface UserService {
/**
* 根据用户ID获取用户信息
* @param userId 用户ID
* @return 用户详细信息DTO
*/
UserInfoDto getUserInfo(Long userId);
/**
* 根据用户ID更新用户信息
* @param userId 用户ID
* @param userUpdateDto 用户更新信息DTO
* @return 更新后的用户信息DTO
*/
UserInfoDto updateUserInfo(Long userId, UserUpdateDto userUpdateDto);
/**
* 更新用户头像
* @param userId 用户ID
* @param avatarUrl 头像URL
* @return 更新后的用户信息DTO
*/
UserInfoDto updateUserAvatar(Long userId, String avatarUrl);
}
4.2 用户服务实现类
文件位置:src/main/java/com/zxy/weblogsystem/service/impl/UserServiceImpl.java
功能说明:实现用户服务接口中定义的业务逻辑。
主要内容:
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final UserFollowRepository userFollowRepository;
private final ArticleRepository articleRepository;
@Override
@Transactional(readOnly = true)
public UserInfoDto getUserInfo(Long userId) {
// 1. 查询用户基本信息
User user = userRepository.findById(userId)
.orElseThrow(() -> new ResourceNotFoundException("用户不存在,ID: " + userId));
// 2. 查询统计数据
Integer followersCount = userFollowRepository.countByFollowedId(userId);
Integer followingCount = userFollowRepository.countByFollowerId(userId);
Integer articleCount = articleRepository.countByAuthorId(userId);
// 3. 构建并返回DTO
return UserInfoDto.builder()
.id(user.getId())
.username(user.getUsername())
.nickname(user.getNickname())
.email(user.getEmail())
.avatarUrl(user.getAvatarUrl())
.role(user.getRole())
.status(user.getStatus())
.followersCount(followersCount)
.followingCount(followingCount)
.articleCount(articleCount)
.createdAt(user.getCreatedAt())
.build();
}
@Override
@Transactional
public UserInfoDto updateUserInfo(Long userId, UserUpdateDto userUpdateDto) {
// 1. 查询用户是否存在
User user = userRepository.findById(userId)
.orElseThrow(() -> new ResourceNotFoundException("用户不存在,ID: " + userId));
// 2. 更新用户信息
boolean isUpdated = false;
// 更新昵称
if (userUpdateDto.getNickname() != null && !userUpdateDto.getNickname().isEmpty()) {
user.setNickname(userUpdateDto.getNickname());
isUpdated = true;
}
// 3. 保存更新后的用户信息
if (isUpdated) {
user = userRepository.save(user);
}
// 4. 查询统计数据并构建返回DTO
return getUserInfo(userId);
}
@Override
@Transactional
public UserInfoDto updateUserAvatar(Long userId, String avatarUrl) {
// 1. 查询用户是否存在
User user = userRepository.findById(userId)
.orElseThrow(() -> new ResourceNotFoundException("用户不存在,ID: " + userId));
// 2. 更新用户头像URL
user.setAvatarUrl(avatarUrl);
// 3. 保存更新后的用户信息
userRepository.save(user);
// 4. 查询统计数据并构建返回DTO
return getUserInfo(userId);
}
}
- 控制器层
文件位置:src/main/java/com/zxy/weblogsystem/controller/UserController.java
功能说明:处理用户相关的HTTP请求。
主要内容:
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
private final FileService fileService;
/**
* 获取用户信息
*/
@GetMapping("/{id}")
public ApiResponse<UserInfoDto> getUserInfo(@PathVariable Long id) {
UserInfoDto userInfo = userService.getUserInfo(id);
return ApiResponse.success(userInfo);
}
/**
* 更新用户信息
*/
@PutMapping("/{id}")
public ApiResponse<UserInfoDto> updateUserInfo(@PathVariable Long id, @Valid @RequestBody UserUpdateDto userUpdateDto) {
UserInfoDto updatedUserInfo = userService.updateUserInfo(id, userUpdateDto);
return ApiResponse.success("更新成功", updatedUserInfo);
}
/**
* 上传用户头像
*/
@PostMapping("/{id}/avatar")
public ApiResponse<Map<String, String>> uploadAvatar(@PathVariable Long id, @RequestParam("file") MultipartFile file) {
// 1. 调用文件服务上传头像
String avatarUrl = fileService.uploadAvatar(id, file);
// 2. 更新用户头像信息
userService.updateUserAvatar(id, avatarUrl);
// 3. 返回头像URL
Map<String, String> result = new HashMap<>();
result.put("avatarUrl", avatarUrl);
return ApiResponse.success("上传成功", result);
}
}
三、文件上传功能实现
- 文件上传配置
文件位置:src/main/java/com/zxy/weblogsystem/config/FileUploadConfig.java
功能说明:配置文件上传相关参数和静态资源访问。
主要内容:
@Configuration
public class FileUploadConfig implements WebMvcConfigurer {
@Value("${file.upload.path:uploads}")
private String uploadPath;
@Value("${file.access.path:/uploads/}")
private String accessPath;
@Bean
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
@Override
public void addResourceHandlers(@NonNull ResourceHandlerRegistry registry) {
// 确保上传目录存在
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 获取上传目录的绝对路径
String absolutePath = uploadDir.getAbsolutePath();
// 添加资源处理器,将上传路径映射到访问路径
registry.addResourceHandler(accessPath + "**")
.addResourceLocations("file:" + absolutePath + "/");
}
}
- 文件上传异常
文件位置:src/main/java/com/zxy/weblogsystem/exception/FileUploadException.java
功能说明:自定义文件上传异常类。
主要内容:
public class FileUploadException extends RuntimeException {
public FileUploadException(String message) {
super(message);
}
public FileUploadException(String message, Throwable cause) {
super(message, cause);
}
}
- 文件服务接口
文件位置:src/main/java/com/zxy/weblogsystem/service/FileService.java
功能说明:定义文件上传相关的业务逻辑接口。
主要内容:
public interface FileService {
/**
* 上传头像文件
*
* @param userId 用户ID
* @param file 头像文件
* @return 头像访问URL
*/
String uploadAvatar(Long userId, MultipartFile file);
}
- 文件服务实现类
文件位置:src/main/java/com/zxy/weblogsystem/service/impl/FileServiceImpl.java
功能说明:实现文件上传相关的业务逻辑。
主要内容:
@Service
@RequiredArgsConstructor
public class FileServiceImpl implements FileService {
@Value("${file.upload.path:uploads}")
private String uploadPath;
@Value("${file.access.path:/uploads/}")
private String accessPath;
private static final List<String> ALLOWED_IMAGE_TYPES = Arrays.asList("image/jpeg", "image/png");
@Override
public String uploadAvatar(Long userId, MultipartFile file) {
// 1. 校验文件是否为空
if (file == null || file.isEmpty()) {
throw new FileUploadException("上传文件不能为空");
}
// 2. 校验文件类型
String contentType = file.getContentType();
if (contentType == null || !ALLOWED_IMAGE_TYPES.contains(contentType)) {
throw new FileUploadException("只支持JPG和PNG格式的图片");
}
// 3. 校验文件大小
if (file.getSize() > 2 * 1024 * 1024) { // 2MB
throw new FileUploadException("文件大小不能超过2MB");
}
try {
// 4. 确保上传目录存在
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 5. 创建用户头像目录
String userAvatarDir = uploadPath + "/avatars/" + userId;
File userDir = new File(userAvatarDir);
if (!userDir.exists()) {
userDir.mkdirs();
}
// 6. 生成唯一文件名
String originalFilename = file.getOriginalFilename();
String fileExtension = originalFilename != null ?
originalFilename.substring(originalFilename.lastIndexOf(".")) : ".jpg";
String newFilename = UUID.randomUUID().toString() + fileExtension;
// 7. 保存文件
Path targetPath = Paths.get(userAvatarDir, newFilename);
Files.copy(file.getInputStream(), targetPath);
// 8. 返回文件访问URL
return accessPath + "avatars/" + userId + "/" + newFilename;
} catch (IOException e) {
throw new FileUploadException("文件上传失败: " + e.getMessage());
}
}
}
四、配置文件
- 应用配置
文件位置:src/main/resources/application.properties
功能说明:配置应用参数,包括数据库连接、文件上传等。
主要内容:
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/weblog?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA配置
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
# 文件上传配置
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=2MB
spring.servlet.multipart.max-request-size=10MB
spring.servlet.multipart.file-size-threshold=0
# 文件存储路径配置
file.upload.path=e:/个人博客系统/WeblogSystem/uploads
file.access.path=/uploads/
- 数据库表结构
文件位置:src/main/resources/schema.sql
功能说明:定义数据库表结构。
主要内容:
-- 用户表
CREATE TABLE IF NOT EXISTS users (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
password VARCHAR(100) NOT NULL COMMENT '密码(加密)',
email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱',
nickname VARCHAR(50) COMMENT '昵称',
avatar_url VARCHAR(255) COMMENT '头像URL',
role VARCHAR(20) NOT NULL DEFAULT 'USER' COMMENT '角色',
status INT NOT NULL DEFAULT 1 COMMENT '状态(0=禁用,1=启用)',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_email(email),
INDEX idx_username(username)
) COMMENT '用户表';
五、功能测试
- 获取用户信息
请求方法:GET
URL:/users/{id}
示例:GET http://localhost:8080/users/1
响应示例:
{
"code": 200,
"message": "OK",
"data": {
"id": 1,
"username": "admin",
"nickname": "管理员",
"email": "admin@example.com",
"avatarUrl": "/uploads/avatars/1/63675700-ad83-4e05-a64f-2e59f8a16eeb.jpg",
"role": "ADMIN",
"status": 1,
"followersCount": 0,
"followingCount": 0,
"articleCount": 0,
"createdAt": "2025-04-13T12:00:00"
}
}
- 更新用户信息
请求方法:PUT
URL:/users/{id}
请求体:
{
"nickname": "新昵称"
}
示例:PUT http://localhost:8080/users/1
响应示例:
{
"code": 200,
"message": "更新成功",
"data": {
"id": 1,
"username": "admin",
"nickname": "新昵称",
"email": "admin@example.com",
"avatarUrl": "/uploads/avatars/1/63675700-ad83-4e05-a64f-2e59f8a16eeb.jpg",
"role": "ADMIN",
"status": 1,
"followersCount": 0,
"followingCount": 0,
"articleCount": 0,
"createdAt": "2025-04-13T12:00:00"
}
}
- 上传用户头像
请求方法:POST
URL:/users/{id}/avatar
Content-Type:multipart/form-data
请求参数:
file: 图片文件(支持jpg、png,最大2MB)
示例:POST http://localhost:8080/users/1/avatar
响应示例:
{
"code": 200,
"message": "上传成功",
"data": {
"avatarUrl": "/uploads/avatars/1/63675700-ad83-4e05-a64f-2e59f8a16eeb.jpg"
}
}
六、总结
通过以上实现,完成了用户信息管理的三个主要功能:
获取用户信息:通过用户ID获取用户详细信息,包括基本资料和统计数据
更新用户信息:支持更新用户昵称等基本信息
上传用户头像:支持上传JPG、PNG格式的头像图片,并自动更新用户头像URL