feat: upgrade user management demo
This commit is contained in:
@@ -2,6 +2,7 @@ package com.example.demo.controller;
|
||||
|
||||
import com.example.demo.common.ApiResponse;
|
||||
import com.example.demo.dto.UserRequest;
|
||||
import com.example.demo.dto.UserStatsResponse;
|
||||
import com.example.demo.model.User;
|
||||
import com.example.demo.service.UserService;
|
||||
import jakarta.validation.Valid;
|
||||
@@ -24,31 +25,38 @@ public class UserController {
|
||||
return ApiResponse.ok(userService.findAll());
|
||||
}
|
||||
|
||||
@GetMapping("/stats")
|
||||
public ApiResponse<UserStatsResponse> getUserStats() {
|
||||
return ApiResponse.ok(userService.getStats());
|
||||
}
|
||||
|
||||
@GetMapping("/search")
|
||||
public ApiResponse<List<User>> searchUsers(@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String name) {
|
||||
String term = keyword != null && !keyword.isBlank() ? keyword : name;
|
||||
return ApiResponse.ok(userService.search(term));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<User> getUserById(@PathVariable Long id) {
|
||||
return ApiResponse.ok(userService.findById(id));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<User> createUser(@Valid @RequestBody UserRequest req) {
|
||||
User user = new User(null, req.name(), req.email(), req.age());
|
||||
return ApiResponse.ok("创建成功", userService.create(user));
|
||||
public ApiResponse<User> createUser(@Valid @RequestBody UserRequest request) {
|
||||
User user = new User(null, request.name(), request.email(), request.age());
|
||||
return ApiResponse.ok("User created successfully", userService.create(user));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<User> updateUser(@PathVariable Long id, @Valid @RequestBody UserRequest req) {
|
||||
User user = new User(id, req.name(), req.email(), req.age());
|
||||
return ApiResponse.ok("更新成功", userService.update(id, user));
|
||||
public ApiResponse<User> updateUser(@PathVariable Long id, @Valid @RequestBody UserRequest request) {
|
||||
User user = new User(id, request.name(), request.email(), request.age());
|
||||
return ApiResponse.ok("User updated successfully", userService.update(id, user));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResponse<Void> deleteUser(@PathVariable Long id) {
|
||||
userService.delete(id);
|
||||
return ApiResponse.ok("删除成功", null);
|
||||
}
|
||||
|
||||
@GetMapping("/search")
|
||||
public ApiResponse<List<User>> searchUsers(@RequestParam String name) {
|
||||
return ApiResponse.ok(userService.findByName(name));
|
||||
return ApiResponse.ok("User deleted successfully", null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,21 @@ import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public record UserRequest(
|
||||
@NotBlank(message = "姓名不能为空")
|
||||
@NotBlank(message = "Name is required")
|
||||
@Size(max = 40, message = "Name must be at most 40 characters")
|
||||
String name,
|
||||
|
||||
@NotBlank(message = "邮箱不能为空")
|
||||
@Email(message = "邮箱格式不正确")
|
||||
@NotBlank(message = "Email is required")
|
||||
@Email(message = "Email format is invalid")
|
||||
String email,
|
||||
|
||||
@Min(value = 1, message = "年龄最小为 1")
|
||||
@Max(value = 120, message = "年龄最大为 120")
|
||||
@NotNull(message = "Age is required")
|
||||
@Min(value = 1, message = "Age must be at least 1")
|
||||
@Max(value = 120, message = "Age must be at most 120")
|
||||
Integer age
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.example.demo.dto;
|
||||
|
||||
public record UserStatsResponse(
|
||||
long totalUsers,
|
||||
long adults,
|
||||
long underThirty,
|
||||
double averageAge
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.example.demo.exception;
|
||||
|
||||
public class DuplicateEmailException extends RuntimeException {
|
||||
public DuplicateEmailException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -15,24 +16,31 @@ import java.util.Map;
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(ResourceNotFoundException.class)
|
||||
public ResponseEntity<ApiResponse<Void>> handleNotFound(ResourceNotFoundException e) {
|
||||
public ResponseEntity<ApiResponse<Void>> handleNotFound(ResourceNotFoundException exception) {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||
.body(ApiResponse.fail(404, e.getMessage()));
|
||||
.body(ApiResponse.fail(404, exception.getMessage()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(DuplicateEmailException.class)
|
||||
public ResponseEntity<ApiResponse<Void>> handleDuplicateEmail(DuplicateEmailException exception) {
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT)
|
||||
.body(ApiResponse.fail(409, exception.getMessage()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ApiResponse<Map<String, String>>> handleValidation(MethodArgumentNotValidException e) {
|
||||
public ResponseEntity<ApiResponse<Map<String, String>>> handleValidation(MethodArgumentNotValidException exception) {
|
||||
Map<String, String> errors = new HashMap<>();
|
||||
for (FieldError error : e.getBindingResult().getFieldErrors()) {
|
||||
for (FieldError error : exception.getBindingResult().getFieldErrors()) {
|
||||
errors.put(error.getField(), error.getDefaultMessage());
|
||||
}
|
||||
|
||||
return ResponseEntity.badRequest()
|
||||
.body(new ApiResponse<>(400, "参数校验失败", errors, java.time.Instant.now()));
|
||||
.body(new ApiResponse<>(400, "Validation failed", errors, Instant.now()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ApiResponse<Void>> handleAny(Exception e) {
|
||||
public ResponseEntity<ApiResponse<Void>> handleAny(Exception exception) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(ApiResponse.fail(500, "服务器内部错误: " + e.getMessage()));
|
||||
.body(ApiResponse.fail(500, "Unexpected server error"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +1,112 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.example.demo.dto.UserStatsResponse;
|
||||
import com.example.demo.exception.DuplicateEmailException;
|
||||
import com.example.demo.exception.ResourceNotFoundException;
|
||||
import com.example.demo.model.User;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class UserService {
|
||||
|
||||
private final List<User> users = new ArrayList<>();
|
||||
private final List<User> users = new CopyOnWriteArrayList<>();
|
||||
private final AtomicLong idGenerator = new AtomicLong(1);
|
||||
|
||||
public UserService() {
|
||||
users.add(new User(idGenerator.getAndIncrement(), "张三", "zhangsan@example.com", 25));
|
||||
users.add(new User(idGenerator.getAndIncrement(), "李四", "lisi@example.com", 30));
|
||||
users.add(new User(idGenerator.getAndIncrement(), "王五", "wangwu@example.com", 28));
|
||||
users.add(new User(idGenerator.getAndIncrement(), "Alice Chen", "alice@example.com", 25));
|
||||
users.add(new User(idGenerator.getAndIncrement(), "Brandon Li", "brandon@example.com", 30));
|
||||
users.add(new User(idGenerator.getAndIncrement(), "Carol Wang", "carol@example.com", 28));
|
||||
}
|
||||
|
||||
public List<User> findAll() {
|
||||
return new ArrayList<>(users);
|
||||
return users.stream()
|
||||
.sorted((left, right) -> Long.compare(left.getId(), right.getId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public User findById(Long id) {
|
||||
return users.stream()
|
||||
.filter(u -> u.getId().equals(id))
|
||||
.filter(user -> user.getId().equals(id))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new ResourceNotFoundException("用户不存在: id=" + id));
|
||||
.orElseThrow(() -> new ResourceNotFoundException("User not found: id=" + id));
|
||||
}
|
||||
|
||||
public List<User> findByName(String name) {
|
||||
public List<User> search(String keyword) {
|
||||
if (keyword == null || keyword.isBlank()) {
|
||||
return findAll();
|
||||
}
|
||||
|
||||
String normalizedKeyword = keyword.trim().toLowerCase();
|
||||
return users.stream()
|
||||
.filter(u -> u.getName().contains(name))
|
||||
.filter(user -> user.getName().toLowerCase().contains(normalizedKeyword)
|
||||
|| user.getEmail().toLowerCase().contains(normalizedKeyword))
|
||||
.sorted((left, right) -> Long.compare(left.getId(), right.getId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public User create(User user) {
|
||||
user.setId(idGenerator.getAndIncrement());
|
||||
users.add(user);
|
||||
return user;
|
||||
ensureEmailAvailable(user.getEmail(), null);
|
||||
User normalized = normalize(user, idGenerator.getAndIncrement());
|
||||
users.add(normalized);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
public User update(Long id, User user) {
|
||||
findById(id);
|
||||
user.setId(id);
|
||||
for (int i = 0; i < users.size(); i++) {
|
||||
if (users.get(i).getId().equals(id)) {
|
||||
users.set(i, user);
|
||||
return user;
|
||||
ensureEmailAvailable(user.getEmail(), id);
|
||||
|
||||
User normalized = normalize(user, id);
|
||||
for (int index = 0; index < users.size(); index++) {
|
||||
if (users.get(index).getId().equals(id)) {
|
||||
users.set(index, normalized);
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
throw new ResourceNotFoundException("用户不存在: id=" + id);
|
||||
|
||||
throw new ResourceNotFoundException("User not found: id=" + id);
|
||||
}
|
||||
|
||||
public void delete(Long id) {
|
||||
boolean removed = users.removeIf(u -> u.getId().equals(id));
|
||||
boolean removed = users.removeIf(user -> user.getId().equals(id));
|
||||
if (!removed) {
|
||||
throw new ResourceNotFoundException("用户不存在: id=" + id);
|
||||
throw new ResourceNotFoundException("User not found: id=" + id);
|
||||
}
|
||||
}
|
||||
|
||||
public UserStatsResponse getStats() {
|
||||
long totalUsers = users.size();
|
||||
long adults = users.stream().filter(user -> user.getAge() >= 18).count();
|
||||
long underThirty = users.stream().filter(user -> user.getAge() < 30).count();
|
||||
double averageAge = users.stream()
|
||||
.mapToInt(User::getAge)
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
|
||||
return new UserStatsResponse(totalUsers, adults, underThirty, averageAge);
|
||||
}
|
||||
|
||||
private void ensureEmailAvailable(String email, Long currentUserId) {
|
||||
String normalizedEmail = email == null ? "" : email.trim().toLowerCase();
|
||||
boolean exists = users.stream()
|
||||
.anyMatch(user -> user.getEmail().equalsIgnoreCase(normalizedEmail)
|
||||
&& (currentUserId == null || !user.getId().equals(currentUserId)));
|
||||
|
||||
if (exists) {
|
||||
throw new DuplicateEmailException("A user with email " + normalizedEmail + " already exists.");
|
||||
}
|
||||
}
|
||||
|
||||
private User normalize(User user, Long id) {
|
||||
return new User(
|
||||
id,
|
||||
user.getName().trim(),
|
||||
user.getEmail().trim().toLowerCase(),
|
||||
user.getAge()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user