本章目标
通过本章学习,你将掌握: - Servlet和JSP基础开发 - Spring框架核心概念和应用 - Spring Boot快速开发 - RESTful Web服务设计与实现 - Web安全和认证机制 - 前后端分离架构设计 - Web应用性能优化
1. Servlet和JSP基础
1.1 Servlet基础
// 基础Servlet演示
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.*;
import java.util.*;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置响应内容类型
response.setContentType("text/html;charset=UTF-8");
// 获取请求参数
String name = request.getParameter("name");
if (name == null || name.trim().isEmpty()) {
name = "World";
}
// 生成响应
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head><title>Hello Servlet</title></head>");
out.println("<body>");
out.println("<h1>Hello, " + escapeHtml(name) + "!</h1>");
out.println("<p>当前时间: " + new Date() + "</p>");
out.println("</body>");
out.println("</html>");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
// HTML转义,防止XSS攻击
private String escapeHtml(String input) {
if (input == null) return "";
return input.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}
}
2.2 Spring MVC
// 用户控制器
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "*")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.findAll();
return ResponseEntity.ok(users);
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
try {
User user = userService.findById(id);
return ResponseEntity.ok(user);
} catch (UserNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
@PostMapping
public ResponseEntity<?> createUser(@Valid @RequestBody CreateUserRequest request) {
try {
User user = new User(null, request.getName(), request.getEmail());
User savedUser = userService.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
} catch (DuplicateEmailException e) {
return ResponseEntity.badRequest()
.body(new ErrorResponse("EMAIL_EXISTS", e.getMessage()));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest()
.body(new ErrorResponse("INVALID_INPUT", e.getMessage()));
}
}
@PutMapping("/{id}")
public ResponseEntity<?> updateUser(@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request) {
try {
User existingUser = userService.findById(id);
existingUser.setName(request.getName());
existingUser.setEmail(request.getEmail());
User updatedUser = userService.save(existingUser);
return ResponseEntity.ok(updatedUser);
} catch (UserNotFoundException e) {
return ResponseEntity.notFound().build();
} catch (DuplicateEmailException e) {
return ResponseEntity.badRequest()
.body(new ErrorResponse("EMAIL_EXISTS", e.getMessage()));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest()
.body(new ErrorResponse("INVALID_INPUT", e.getMessage()));
}
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
try {
userService.deleteById(id);
return ResponseEntity.noContent().build();
} catch (UserNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
@GetMapping("/search")
public ResponseEntity<User> getUserByEmail(@RequestParam String email) {
try {
User user = userService.findByEmail(email);
return ResponseEntity.ok(user);
} catch (UserNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
}
// 请求DTO类
public class CreateUserRequest {
@NotBlank(message = "姓名不能为空")
@Size(min = 2, max = 50, message = "姓名长度必须在2-50个字符之间")
private String name;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式无效")
private String email;
// 构造函数
public CreateUserRequest() {}
public CreateUserRequest(String name, String email) {
this.name = name;
this.email = email;
}
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
public class UpdateUserRequest {
@NotBlank(message = "姓名不能为空")
@Size(min = 2, max = 50, message = "姓名长度必须在2-50个字符之间")
private String name;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式无效")
private String email;
// 构造函数
public UpdateUserRequest() {}
public UpdateUserRequest(String name, String email) {
this.name = name;
this.email = email;
}
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
// 错误响应类
public class ErrorResponse {
private String code;
private String message;
private long timestamp;
public ErrorResponse(String code, String message) {
this.code = code;
this.message = message;
this.timestamp = System.currentTimeMillis();
}
// Getters and Setters
public String getCode() { return code; }
public void setCode(String code) { this.code = code; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public long getTimestamp() { return timestamp; }
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
}
// 全局异常处理器
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
MethodArgumentNotValidException ex) {
StringBuilder message = new StringBuilder("验证失败: ");
ex.getBindingResult().getFieldErrors().forEach(error -> {
message.append(error.getField()).append(" ").append(error.getDefaultMessage()).append("; ");
});
ErrorResponse error = new ErrorResponse("VALIDATION_ERROR", message.toString());
return ResponseEntity.badRequest().body(error);
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFoundException(UserNotFoundException ex) {
ErrorResponse error = new ErrorResponse("USER_NOT_FOUND", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(DuplicateEmailException.class)
public ResponseEntity<ErrorResponse> handleDuplicateEmailException(DuplicateEmailException ex) {
ErrorResponse error = new ErrorResponse("EMAIL_EXISTS", ex.getMessage());
return ResponseEntity.badRequest().body(error);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException ex) {
ErrorResponse error = new ErrorResponse("INVALID_INPUT", ex.getMessage());
return ResponseEntity.badRequest().body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
logger.error("未处理的异常", ex);
ErrorResponse error = new ErrorResponse("INTERNAL_ERROR", "服务器内部错误");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
// Web配置
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.maxAge(3600);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggingInterceptor())
.addPathPatterns("/api/**");
registry.addInterceptor(new AuthenticationInterceptor())
.addPathPatterns("/api/admin/**")
.excludePathPatterns("/api/auth/**");
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 配置JSON消息转换器
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
jsonConverter.setObjectMapper(objectMapper);
converters.add(jsonConverter);
}
}
// 日志拦截器
public class LoggingInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
String method = request.getMethod();
String uri = request.getRequestURI();
String queryString = request.getQueryString();
logger.info("请求开始: {} {}{}", method, uri,
queryString != null ? "?" + queryString : "");
request.setAttribute("startTime", System.currentTimeMillis());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
Long startTime = (Long) request.getAttribute("startTime");
long duration = System.currentTimeMillis() - startTime;
String method = request.getMethod();
String uri = request.getRequestURI();
int status = response.getStatus();
logger.info("请求完成: {} {} -> {} ({} ms)", method, uri, status, duration);
if (ex != null) {
logger.error("请求异常: " + ex.getMessage(), ex);
}
}
}
// 认证拦截器
public class AuthenticationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"error\": \"未授权访问\"}");
return false;
}
String token = authHeader.substring(7);
// 简单的token验证(实际项目中应使用JWT等)
if (!isValidToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"error\": \"无效的访问令牌\"}");
return false;
}
return true;
}
private boolean isValidToken(String token) {
// 简单的token验证逻辑
return "valid-token-123".equals(token);
}
}
3. Spring Boot快速开发
3.1 Spring Boot应用
// Spring Boot主应用类
@SpringBootApplication
@EnableJpaRepositories
@EnableScheduling
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
@Bean
public CommandLineRunner initData(UserRepository userRepository) {
return args -> {
if (userRepository.count() == 0) {
userRepository.save(new User(null, "管理员", "admin@example.com", "ADMIN"));
userRepository.save(new User(null, "普通用户", "user@example.com", "USER"));
System.out.println("初始数据已创建");
}
};
}
}
// 用户实体(JPA版本)
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(nullable = false, unique = true, length = 150)
private String email;
@Column(length = 20)
@Enumerated(EnumType.STRING)
private UserRole role = UserRole.USER;
@Column(name = "created_at")
@CreationTimestamp
private LocalDateTime createdAt;
@Column(name = "updated_at")
@UpdateTimestamp
private LocalDateTime updatedAt;
// 构造函数
public User() {}
public User(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public User(Long id, String name, String email, String role) {
this.id = id;
this.name = name;
this.email = email;
this.role = UserRole.valueOf(role);
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public UserRole getRole() { return role; }
public void setRole(UserRole role) { this.role = role; }
public String getRoleString() { return role != null ? role.name() : null; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
@Override
public String toString() {
return "User{id=" + id + ", name='" + name + "', email='" + email +
"', role=" + role + ", createdAt=" + createdAt + "}";
}
}
// 用户角色枚举
public enum UserRole {
USER("普通用户"),
ADMIN("管理员"),
MODERATOR("版主");
private final String displayName;
UserRole(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}
// JPA用户仓库
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
List<User> findByRole(UserRole role);
@Query("SELECT u FROM User u WHERE u.name LIKE %:name%")
List<User> findByNameContaining(@Param("name") String name);
@Query("SELECT u FROM User u WHERE u.createdAt >= :startDate")
List<User> findUsersCreatedAfter(@Param("startDate") LocalDateTime startDate);
@Modifying
@Query("UPDATE User u SET u.role = :role WHERE u.id = :id")
int updateUserRole(@Param("id") Long id, @Param("role") UserRole role);
}
// 增强的用户服务
@Service
@Transactional
public class EnhancedUserService {
private final UserRepository userRepository;
private final EmailService emailService;
private final ApplicationEventPublisher eventPublisher;
public EnhancedUserService(UserRepository userRepository,
EmailService emailService,
ApplicationEventPublisher eventPublisher) {
this.userRepository = userRepository;
this.emailService = emailService;
this.eventPublisher = eventPublisher;
}
@Transactional(readOnly = true)
public Page<User> findUsers(Pageable pageable) {
return userRepository.findAll(pageable);
}
@Transactional(readOnly = true)
public List<User> searchUsers(String name) {
return userRepository.findByNameContaining(name);
}
@Transactional(readOnly = true)
public List<User> findUsersByRole(UserRole role) {
return userRepository.findByRole(role);
}
@Transactional(readOnly = true)
public List<User> findRecentUsers(int days) {
LocalDateTime startDate = LocalDateTime.now().minusDays(days);
return userRepository.findUsersCreatedAfter(startDate);
}
public User createUser(CreateUserRequest request) {
// 检查邮箱是否已存在
if (userRepository.existsByEmail(request.getEmail())) {
throw new DuplicateEmailException("邮箱已存在: " + request.getEmail());
}
User user = new User(null, request.getName(), request.getEmail());
User savedUser = userRepository.save(user);
// 发布用户创建事件
eventPublisher.publishEvent(new UserCreatedEvent(savedUser));
return savedUser;
}
public User updateUserRole(Long userId, UserRole newRole) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("用户不存在: " + userId));
UserRole oldRole = user.getRole();
user.setRole(newRole);
User updatedUser = userRepository.save(user);
// 发布角色变更事件
eventPublisher.publishEvent(new UserRoleChangedEvent(updatedUser, oldRole, newRole));
return updatedUser;
}
@Cacheable(value = "users", key = "#id")
@Transactional(readOnly = true)
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("用户不存在: " + id));
}
@CacheEvict(value = "users", key = "#user.id")
public User save(User user) {
return userRepository.save(user);
}
@CacheEvict(value = "users", key = "#id")
public void deleteById(Long id) {
if (!userRepository.existsById(id)) {
throw new UserNotFoundException("用户不存在: " + id);
}
userRepository.deleteById(id);
// 发布用户删除事件
eventPublisher.publishEvent(new UserDeletedEvent(id));
}
}
// 用户事件类
public class UserCreatedEvent {
private final User user;
private final LocalDateTime timestamp;
public UserCreatedEvent(User user) {
this.user = user;
this.timestamp = LocalDateTime.now();
}
public User getUser() { return user; }
public LocalDateTime getTimestamp() { return timestamp; }
}
public class UserRoleChangedEvent {
private final User user;
private final UserRole oldRole;
private final UserRole newRole;
private final LocalDateTime timestamp;
public UserRoleChangedEvent(User user, UserRole oldRole, UserRole newRole) {
this.user = user;
this.oldRole = oldRole;
this.newRole = newRole;
this.timestamp = LocalDateTime.now();
}
public User getUser() { return user; }
public UserRole getOldRole() { return oldRole; }
public UserRole getNewRole() { return newRole; }
public LocalDateTime getTimestamp() { return timestamp; }
}
public class UserDeletedEvent {
private final Long userId;
private final LocalDateTime timestamp;
public UserDeletedEvent(Long userId) {
this.userId = userId;
this.timestamp = LocalDateTime.now();
}
public Long getUserId() { return userId; }
public LocalDateTime getTimestamp() { return timestamp; }
}
// 事件监听器
@Component
public class UserEventListener {
private static final Logger logger = LoggerFactory.getLogger(UserEventListener.class);
private final EmailService emailService;
public UserEventListener(EmailService emailService) {
this.emailService = emailService;
}
@EventListener
@Async
public void handleUserCreated(UserCreatedEvent event) {
logger.info("处理用户创建事件: {}", event.getUser().getEmail());
try {
emailService.sendWelcomeEmail(event.getUser());
} catch (Exception e) {
logger.error("发送欢迎邮件失败: " + e.getMessage(), e);
}
}
@EventListener
public void handleUserRoleChanged(UserRoleChangedEvent event) {
logger.info("用户角色变更: {} 从 {} 变更为 {}",
event.getUser().getEmail(),
event.getOldRole(),
event.getNewRole());
// 可以在这里添加角色变更的业务逻辑
// 例如:更新权限缓存、发送通知等
}
@EventListener
public void handleUserDeleted(UserDeletedEvent event) {
logger.info("用户删除事件: 用户ID {}", event.getUserId());
// 可以在这里添加用户删除后的清理逻辑
// 例如:清理相关数据、发送通知等
}
}
// 缓存配置
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
cacheManager.setCacheNames(Arrays.asList("users", "userStats"));
return cacheManager;
}
}
// 异步配置
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}
}
3.2 配置文件
# application.properties
# 服务器配置
server.port=8080
server.servlet.context-path=/api
# 数据库配置
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# JPA配置
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# H2控制台
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# 日志配置
logging.level.com.example=DEBUG
logging.level.org.springframework.web=DEBUG
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
# 应用配置
app.email.enabled=false
app.email.from=noreply@example.com
# 缓存配置
spring.cache.type=simple
# JSON配置
spring.jackson.property-naming-strategy=SNAKE_CASE
spring.jackson.default-property-inclusion=NON_NULL
# 分页配置
spring.data.web.pageable.default-page-size=20
spring.data.web.pageable.max-page-size=100
# application.yml (YAML格式)
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
properties:
hibernate:
format_sql: true
database-platform: org.hibernate.dialect.H2Dialect
h2:
console:
enabled: true
path: /h2-console
jackson:
property-naming-strategy: SNAKE_CASE
default-property-inclusion: NON_NULL
cache:
type: simple
data:
web:
pageable:
default-page-size: 20
max-page-size: 100
logging:
level:
com.example: DEBUG
org.springframework.web: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
app:
email:
enabled: false
from: noreply@example.com
---
# 开发环境配置
spring:
config:
activate:
on-profile: dev
datasource:
url: jdbc:h2:file:./data/devdb
jpa:
hibernate:
ddl-auto: update
logging:
level:
root: INFO
com.example: DEBUG
---
# 生产环境配置
spring:
config:
activate:
on-profile: prod
datasource:
url: jdbc:mysql://localhost:3306/webapp
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: validate
show-sql: false
logging:
level:
root: WARN
com.example: INFO
file:
name: logs/webapp.log
app:
email:
enabled: true
4. RESTful API设计
4.1 RESTful API最佳实践
// API版本控制
@RestController
@RequestMapping("/api/v1/users")
public class UserApiV1Controller {
private final EnhancedUserService userService;
public UserApiV1Controller(EnhancedUserService userService) {
this.userService = userService;
}
@GetMapping
public ResponseEntity<ApiResponse<Page<UserDto>>> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "id") String sort,
@RequestParam(defaultValue = "asc") String direction,
@RequestParam(required = false) String search,
@RequestParam(required = false) UserRole role) {
Sort.Direction sortDirection = "desc".equalsIgnoreCase(direction) ?
Sort.Direction.DESC : Sort.Direction.ASC;
Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sort));
Page<User> users;
if (search != null && !search.trim().isEmpty()) {
users = userService.searchUsers(search).stream()
.collect(Collectors.collectingAndThen(
Collectors.toList(),
list -> new PageImpl<>(list, pageable, list.size())));
} else if (role != null) {
users = userService.findUsersByRole(role).stream()
.collect(Collectors.collectingAndThen(
Collectors.toList(),
list -> new PageImpl<>(list, pageable, list.size())));
} else {
users = userService.findUsers(pageable);
}
Page<UserDto> userDtos = users.map(this::convertToDto);
ApiResponse<Page<UserDto>> response = ApiResponse.success(userDtos);
return ResponseEntity.ok(response);
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<UserDto>> getUserById(@PathVariable Long id) {
try {
User user = userService.findById(id);
UserDto userDto = convertToDto(user);
return ResponseEntity.ok(ApiResponse.success(userDto));
} catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("USER_NOT_FOUND", e.getMessage()));
}
}
@PostMapping
public ResponseEntity<ApiResponse<UserDto>> createUser(
@Valid @RequestBody CreateUserRequest request,
HttpServletRequest httpRequest) {
try {
User user = userService.createUser(request);
UserDto userDto = convertToDto(user);
// 构建资源URI
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(user.getId())
.toUri();
return ResponseEntity.created(location)
.body(ApiResponse.success(userDto, "用户创建成功"));
} catch (DuplicateEmailException e) {
return ResponseEntity.status(HttpStatus.CONFLICT)
.body(ApiResponse.error("EMAIL_EXISTS", e.getMessage()));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("INVALID_INPUT", e.getMessage()));
}
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<UserDto>> updateUser(
@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request) {
try {
User existingUser = userService.findById(id);
existingUser.setName(request.getName());
existingUser.setEmail(request.getEmail());
User updatedUser = userService.save(existingUser);
UserDto userDto = convertToDto(updatedUser);
return ResponseEntity.ok(ApiResponse.success(userDto, "用户更新成功"));
} catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("USER_NOT_FOUND", e.getMessage()));
} catch (DuplicateEmailException e) {
return ResponseEntity.status(HttpStatus.CONFLICT)
.body(ApiResponse.error("EMAIL_EXISTS", e.getMessage()));
}
}
@PatchMapping("/{id}/role")
public ResponseEntity<ApiResponse<UserDto>> updateUserRole(
@PathVariable Long id,
@RequestBody Map<String, String> request) {
try {
String roleStr = request.get("role");
if (roleStr == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("MISSING_ROLE", "角色参数不能为空"));
}
UserRole newRole = UserRole.valueOf(roleStr.toUpperCase());
User updatedUser = userService.updateUserRole(id, newRole);
UserDto userDto = convertToDto(updatedUser);
return ResponseEntity.ok(ApiResponse.success(userDto, "用户角色更新成功"));
} catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("USER_NOT_FOUND", e.getMessage()));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("INVALID_ROLE", "无效的角色: " + request.get("role")));
}
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteUser(@PathVariable Long id) {
try {
userService.deleteById(id);
return ResponseEntity.ok(ApiResponse.success(null, "用户删除成功"));
} catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("USER_NOT_FOUND", e.getMessage()));
}
}
@GetMapping("/stats")
public ResponseEntity<ApiResponse<UserStatsDto>> getUserStats() {
UserStatsDto stats = new UserStatsDto();
stats.setTotalUsers(userService.findUsers(Pageable.unpaged()).getTotalElements());
stats.setAdminUsers(userService.findUsersByRole(UserRole.ADMIN).size());
stats.setRegularUsers(userService.findUsersByRole(UserRole.USER).size());
stats.setRecentUsers(userService.findRecentUsers(7).size());
return ResponseEntity.ok(ApiResponse.success(stats));
}
private UserDto convertToDto(User user) {
UserDto dto = new UserDto();
dto.setId(user.getId());
dto.setName(user.getName());
dto.setEmail(user.getEmail());
dto.setRole(user.getRole());
dto.setRoleDisplayName(user.getRole().getDisplayName());
dto.setCreatedAt(user.getCreatedAt());
dto.setUpdatedAt(user.getUpdatedAt());
return dto;
}
}
// 统一API响应格式
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
private String errorCode;
private long timestamp;
public ApiResponse() {
this.timestamp = System.currentTimeMillis();
}
public static <T> ApiResponse<T> success(T data) {
return success(data, "操作成功");
}
public static <T> ApiResponse<T> success(T data, String message) {
ApiResponse<T> response = new ApiResponse<>();
response.success = true;
response.message = message;
response.data = data;
return response;
}
public static <T> ApiResponse<T> error(String errorCode, String message) {
ApiResponse<T> response = new ApiResponse<>();
response.success = false;
response.errorCode = errorCode;
response.message = message;
return response;
}
// Getters and Setters
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public T getData() { return data; }
public void setData(T data) { this.data = data; }
public String getErrorCode() { return errorCode; }
public void setErrorCode(String errorCode) { this.errorCode = errorCode; }
public long getTimestamp() { return timestamp; }
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
}
// 用户DTO
public class UserDto {
private Long id;
private String name;
private String email;
private UserRole role;
private String roleDisplayName;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public UserRole getRole() { return role; }
public void setRole(UserRole role) { this.role = role; }
public String getRoleDisplayName() { return roleDisplayName; }
public void setRoleDisplayName(String roleDisplayName) { this.roleDisplayName = roleDisplayName; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
}
// 用户统计DTO
public class UserStatsDto {
private long totalUsers;
private long adminUsers;
private long regularUsers;
private long recentUsers;
// Getters and Setters
public long getTotalUsers() { return totalUsers; }
public void setTotalUsers(long totalUsers) { this.totalUsers = totalUsers; }
public long getAdminUsers() { return adminUsers; }
public void setAdminUsers(long adminUsers) { this.adminUsers = adminUsers; }
public long getRegularUsers() { return regularUsers; }
public void setRegularUsers(long regularUsers) { this.regularUsers = regularUsers; }
public long getRecentUsers() { return recentUsers; }
public void setRecentUsers(long recentUsers) { this.recentUsers = recentUsers; }
}
4.2 API文档和测试
// Swagger配置
@Configuration
@EnableOpenApi
public class SwaggerConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("用户管理API")
.version("1.0")
.description("用户管理系统的RESTful API文档")
.contact(new Contact()
.name("开发团队")
.email("dev@example.com"))
.license(new License()
.name("MIT License")
.url("https://opensource.org/licenses/MIT")))
.servers(Arrays.asList(
new Server().url("http://localhost:8080").description("开发环境"),
new Server().url("https://api.example.com").description("生产环境")
))
.components(new Components()
.addSecuritySchemes("bearerAuth",
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
}
// API测试控制器
@RestController
@RequestMapping("/api/test")
public class ApiTestController {
@GetMapping("/health")
public ResponseEntity<Map<String, Object>> healthCheck() {
Map<String, Object> health = new HashMap<>();
health.put("status", "UP");
health.put("timestamp", LocalDateTime.now());
health.put("version", "1.0.0");
health.put("environment", System.getProperty("spring.profiles.active", "default"));
return ResponseEntity.ok(health);
}
@GetMapping("/info")
public ResponseEntity<Map<String, Object>> appInfo() {
Map<String, Object> info = new HashMap<>();
info.put("name", "用户管理系统");
info.put("description", "基于Spring Boot的用户管理RESTful API");
info.put("version", "1.0.0");
info.put("java.version", System.getProperty("java.version"));
info.put("spring.version", SpringVersion.getVersion());
return ResponseEntity.ok(info);
}
@PostMapping("/echo")
public ResponseEntity<Map<String, Object>> echo(@RequestBody Map<String, Object> request) {
Map<String, Object> response = new HashMap<>();
response.put("received", request);
response.put("timestamp", LocalDateTime.now());
response.put("method", "POST");
return ResponseEntity.ok(response);
}
}
5. 前端集成
5.1 JavaScript客户端
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户管理系统</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header {
text-align: center;
margin-bottom: 30px;
color: #333;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
input, select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
button {
background-color: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
font-size: 14px;
}
button:hover {
background-color: #0056b3;
}
.btn-danger {
background-color: #dc3545;
}
.btn-danger:hover {
background-color: #c82333;
}
.user-list {
margin-top: 30px;
}
.user-item {
background: #f8f9fa;
padding: 15px;
margin-bottom: 10px;
border-radius: 4px;
border-left: 4px solid #007bff;
}
.user-actions {
margin-top: 10px;
}
.message {
padding: 10px;
margin-bottom: 15px;
border-radius: 4px;
}
.message.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.message.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 8px;
text-align: center;
}
.stat-number {
font-size: 2em;
font-weight: bold;
margin-bottom: 5px;
}
.stat-label {
font-size: 0.9em;
opacity: 0.9;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>用户管理系统</h1>
<p>基于Spring Boot的RESTful API演示</p>
</div>
<div id="message"></div>
<div class="stats" id="stats"></div>
<form id="userForm">
<div class="form-group">
<label for="name">姓名:</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="role">角色:</label>
<select id="role" name="role">
<option value="USER">普通用户</option>
<option value="ADMIN">管理员</option>
<option value="MODERATOR">版主</option>
</select>
</div>
<button type="submit">创建用户</button>
<button type="button" onclick="clearForm()">清空表单</button>
<button type="button" onclick="loadUsers()">刷新列表</button>
</form>
<div class="user-list">
<h3>用户列表</h3>
<div id="userList"></div>
</div>
</div>
<script>
const API_BASE = '/api/v1';
let editingUserId = null;
// 页面加载时初始化
document.addEventListener('DOMContentLoaded', function() {
loadStats();
loadUsers();
document.getElementById('userForm').addEventListener('submit', handleSubmit);
});
// 加载统计信息
async function loadStats() {
try {
const response = await fetch(`${API_BASE}/users/stats`);
const result = await response.json();
if (result.success) {
displayStats(result.data);
}
} catch (error) {
console.error('加载统计信息失败:', error);
}
}
// 显示统计信息
function displayStats(stats) {
const statsContainer = document.getElementById('stats');
statsContainer.innerHTML = `
<div class="stat-card">
<div class="stat-number">${stats.totalUsers}</div>
<div class="stat-label">总用户数</div>
</div>
<div class="stat-card">
<div class="stat-number">${stats.adminUsers}</div>
<div class="stat-label">管理员</div>
</div>
<div class="stat-card">
<div class="stat-number">${stats.regularUsers}</div>
<div class="stat-label">普通用户</div>
</div>
<div class="stat-card">
<div class="stat-number">${stats.recentUsers}</div>
<div class="stat-label">近7天新增</div>
</div>
`;
}
// 处理表单提交
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
const userData = {
name: formData.get('name'),
email: formData.get('email')
};
try {
let response;
if (editingUserId) {
// 更新用户
response = await fetch(`${API_BASE}/users/${editingUserId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});
} else {
// 创建用户
response = await fetch(`${API_BASE}/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});
}
const result = await response.json();
if (result.success) {
showMessage(result.message, 'success');
clearForm();
loadUsers();
loadStats();
} else {
showMessage(result.message, 'error');
}
} catch (error) {
showMessage('操作失败: ' + error.message, 'error');
}
}
// 加载用户列表
async function loadUsers() {
try {
const response = await fetch(`${API_BASE}/users?size=50`);
const result = await response.json();
if (result.success) {
displayUsers(result.data.content);
} else {
showMessage('加载用户列表失败', 'error');
}
} catch (error) {
showMessage('加载用户列表失败: ' + error.message, 'error');
}
}
// 显示用户列表
function displayUsers(users) {
const userList = document.getElementById('userList');
if (users.length === 0) {
userList.innerHTML = '<p>暂无用户数据</p>';
return;
}
userList.innerHTML = users.map(user => `
<div class="user-item">
<strong>${user.name}</strong> (${user.email})
<br>
<small>角色: ${user.roleDisplayName} | 创建时间: ${formatDate(user.createdAt)}</small>
<div class="user-actions">
<button onclick="editUser(${user.id})">编辑</button>
<button onclick="updateUserRole(${user.id})" class="btn-warning">更改角色</button>
<button onclick="deleteUser(${user.id})" class="btn-danger">删除</button>
</div>
</div>
`).join('');
}
// 编辑用户
async function editUser(userId) {
try {
const response = await fetch(`${API_BASE}/users/${userId}`);
const result = await response.json();
if (result.success) {
const user = result.data;
document.getElementById('name').value = user.name;
document.getElementById('email').value = user.email;
document.getElementById('role').value = user.role;
editingUserId = userId;
document.querySelector('button[type="submit"]').textContent = '更新用户';
showMessage('正在编辑用户: ' + user.name, 'success');
}
} catch (error) {
showMessage('加载用户信息失败: ' + error.message, 'error');
}
}
// 更新用户角色
async function updateUserRole(userId) {
const newRole = prompt('请输入新角色 (USER/ADMIN/MODERATOR):');
if (!newRole) return;
try {
const response = await fetch(`${API_BASE}/users/${userId}/role`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ role: newRole.toUpperCase() })
});
const result = await response.json();
if (result.success) {
showMessage(result.message, 'success');
loadUsers();
loadStats();
} else {
showMessage(result.message, 'error');
}
} catch (error) {
showMessage('更新角色失败: ' + error.message, 'error');
}
}
// 删除用户
async function deleteUser(userId) {
if (!confirm('确定要删除这个用户吗?')) return;
try {
const response = await fetch(`${API_BASE}/users/${userId}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
showMessage(result.message, 'success');
loadUsers();
loadStats();
} else {
showMessage(result.message, 'error');
}
} catch (error) {
showMessage('删除用户失败: ' + error.message, 'error');
}
}
// 清空表单
function clearForm() {
document.getElementById('userForm').reset();
editingUserId = null;
document.querySelector('button[type="submit"]').textContent = '创建用户';
}
// 显示消息
function showMessage(message, type) {
const messageDiv = document.getElementById('message');
messageDiv.innerHTML = `<div class="message ${type}">${message}</div>`;
setTimeout(() => {
messageDiv.innerHTML = '';
}, 5000);
}
// 格式化日期
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleString('zh-CN');
}
</script>
</body>
</html>
6. Web安全
6.1 Spring Security集成
// Security配置
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
private final UserDetailsService userDetailsService;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtRequestFilter jwtRequestFilter;
public SecurityConfig(UserDetailsService userDetailsService,
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
JwtRequestFilter jwtRequestFilter) {
this.userDetailsService = userDetailsService;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtRequestFilter = jwtRequestFilter;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/test/**").permitAll()
.requestMatchers("/h2-console/**").permitAll()
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/v1/users").hasAnyRole("USER", "ADMIN")
.requestMatchers(HttpMethod.POST, "/api/v1/users").hasRole("ADMIN")
.requestMatchers(HttpMethod.PUT, "/api/v1/users/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.DELETE, "/api/v1/users/**").hasRole("ADMIN")
.requestMatchers("/api/v1/users/stats").hasRole("ADMIN")
.anyRequest().authenticated()
)
.exceptionHandling(ex -> ex.authenticationEntryPoint(jwtAuthenticationEntryPoint))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.headers(headers -> headers.frameOptions().disable()); // 为H2控制台
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
// JWT工具类
@Component
public class JwtTokenUtil {
private static final String SECRET = "mySecretKey";
private static final int JWT_TOKEN_VALIDITY = 5 * 60 * 60; // 5小时
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
// JWT认证入口点
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
String jsonResponse = "{\"error\": \"未授权访问\", \"message\": \"" +
authException.getMessage() + "\"}";
response.getWriter().write(jsonResponse);
}
}
// JWT请求过滤器
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final JwtTokenUtil jwtTokenUtil;
public JwtRequestFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil) {
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
logger.error("无法获取JWT Token", e);
} catch (ExpiredJwtException e) {
logger.error("JWT Token已过期", e);
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
chain.doFilter(request, response);
}
}
// 认证控制器
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthenticationManager authenticationManager;
private final JwtTokenUtil jwtTokenUtil;
private final UserDetailsService userDetailsService;
public AuthController(AuthenticationManager authenticationManager,
JwtTokenUtil jwtTokenUtil,
UserDetailsService userDetailsService) {
this.authenticationManager = authenticationManager;
this.jwtTokenUtil = jwtTokenUtil;
this.userDetailsService = userDetailsService;
}
@PostMapping("/login")
public ResponseEntity<ApiResponse<JwtResponse>> login(@RequestBody JwtRequest authRequest) {
try {
authenticate(authRequest.getUsername(), authRequest.getPassword());
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
JwtResponse response = new JwtResponse(token, userDetails.getUsername(),
userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()));
return ResponseEntity.ok(ApiResponse.success(response, "登录成功"));
} catch (BadCredentialsException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponse.error("INVALID_CREDENTIALS", "用户名或密码错误"));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("LOGIN_ERROR", "登录失败: " + e.getMessage()));
}
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("用户已被禁用", e);
} catch (BadCredentialsException e) {
throw new Exception("用户名或密码错误", e);
}
}
}
// JWT请求和响应类
public class JwtRequest {
private String username;
private String password;
public JwtRequest() {}
public JwtRequest(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
public class JwtResponse {
private String token;
private String username;
private List<String> roles;
public JwtResponse(String token, String username, List<String> roles) {
this.token = token;
this.username = username;
this.roles = roles;
}
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public List<String> getRoles() { return roles; }
public void setRoles(List<String> roles) { this.roles = roles; }
}
6.2 安全最佳实践
// 输入验证和清理
@Component
public class InputValidator {
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9+_.-]+@([A-Za-z0-9.-]+\\.[A-Za-z]{2,})$");
private static final Pattern NAME_PATTERN =
Pattern.compile("^[\\p{L}\\p{M}\\s'-]{2,50}$");
public boolean isValidEmail(String email) {
return email != null && EMAIL_PATTERN.matcher(email).matches();
}
public boolean isValidName(String name) {
return name != null && NAME_PATTERN.matcher(name).matches();
}
public String sanitizeInput(String input) {
if (input == null) return null;
// 移除潜在的恶意字符
return input.replaceAll("[<>\"'&]", "")
.trim();
}
public String escapeHtml(String input) {
if (input == null) return null;
return input.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}
}
// CSRF保护
@Configuration
public class CsrfConfig {
@Bean
public CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
// 速率限制
@Component
public class RateLimitingFilter implements Filter {
private final Map<String, List<Long>> requestCounts = new ConcurrentHashMap<>();
private final int maxRequests = 100; // 每分钟最大请求数
private final long timeWindow = 60000; // 1分钟
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String clientIp = getClientIp(httpRequest);
if (isRateLimited(clientIp)) {
httpResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
httpResponse.setContentType("application/json");
httpResponse.getWriter().write(
"{\"error\": \"请求过于频繁\", \"message\": \"请稍后再试\"}");
return;
}
chain.doFilter(request, response);
}
private boolean isRateLimited(String clientIp) {
long currentTime = System.currentTimeMillis();
requestCounts.compute(clientIp, (key, timestamps) -> {
if (timestamps == null) {
timestamps = new ArrayList<>();
}
// 移除过期的时间戳
timestamps.removeIf(timestamp -> currentTime - timestamp > timeWindow);
// 添加当前时间戳
timestamps.add(currentTime);
return timestamps;
});
return requestCounts.get(clientIp).size() > maxRequests;
}
private String getClientIp(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
String xRealIp = request.getHeader("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty()) {
return xRealIp;
}
return request.getRemoteAddr();
}
}
// 安全头配置
@Configuration
public class SecurityHeadersConfig {
@Bean
public FilterRegistrationBean<SecurityHeadersFilter> securityHeadersFilter() {
FilterRegistrationBean<SecurityHeadersFilter> registrationBean =
new FilterRegistrationBean<>();
registrationBean.setFilter(new SecurityHeadersFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(1);
return registrationBean;
}
public static class SecurityHeadersFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 防止点击劫持
httpResponse.setHeader("X-Frame-Options", "DENY");
// 防止MIME类型嗅探
httpResponse.setHeader("X-Content-Type-Options", "nosniff");
// XSS保护
httpResponse.setHeader("X-XSS-Protection", "1; mode=block");
// 强制HTTPS
httpResponse.setHeader("Strict-Transport-Security",
"max-age=31536000; includeSubDomains");
// 内容安全策略
httpResponse.setHeader("Content-Security-Policy",
"default-src 'self'; script-src 'self' 'unsafe-inline'; " +
"style-src 'self' 'unsafe-inline'; img-src 'self' data:");
// 引用者策略
httpResponse.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
chain.doFilter(request, response);
}
}
}
7. Web开发最佳实践
7.1 性能优化
// 缓存配置
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofMinutes(10))
.recordStats());
return cacheManager;
}
@Bean
public CacheMetricsRegistrar cacheMetricsRegistrar(MeterRegistry meterRegistry) {
return new CacheMetricsRegistrar(meterRegistry);
}
}
// 异步处理
@Service
public class AsyncUserService {
private final UserRepository userRepository;
private final EmailService emailService;
public AsyncUserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
@Async
public CompletableFuture<Void> sendWelcomeEmailAsync(User user) {
try {
emailService.sendWelcomeEmail(user.getEmail(), user.getName());
return CompletableFuture.completedFuture(null);
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
}
@Async
public CompletableFuture<List<User>> findUsersAsync(Pageable pageable) {
return CompletableFuture.supplyAsync(() ->
userRepository.findAll(pageable).getContent());
}
@Async
public CompletableFuture<Void> updateUserStatisticsAsync() {
return CompletableFuture.runAsync(() -> {
// 更新用户统计信息
long totalUsers = userRepository.count();
// 缓存统计信息
// ...
});
}
}
// 数据库优化
@Repository
public interface OptimizedUserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.email = :email")
@QueryHints(@QueryHint(name = "org.hibernate.cacheable", value = "true"))
Optional<User> findByEmailCached(@Param("email") String email);
@Query("SELECT u FROM User u WHERE u.role = :role")
@QueryHints(@QueryHint(name = "org.hibernate.fetchSize", value = "50"))
List<User> findByRoleOptimized(@Param("role") UserRole role);
@Modifying
@Query("UPDATE User u SET u.lastLoginAt = :loginTime WHERE u.id = :userId")
void updateLastLoginTime(@Param("userId") Long userId,
@Param("loginTime") LocalDateTime loginTime);
@Query(value = "SELECT * FROM users WHERE created_at >= :since ORDER BY created_at DESC",
nativeQuery = true)
List<User> findRecentUsersNative(@Param("since") LocalDateTime since);
}
// 响应压缩
@Configuration
public class CompressionConfig {
@Bean
public FilterRegistrationBean<CompressionFilter> compressionFilter() {
FilterRegistrationBean<CompressionFilter> registrationBean =
new FilterRegistrationBean<>();
registrationBean.setFilter(new CompressionFilter());
registrationBean.addUrlPatterns("/api/*");
return registrationBean;
}
public static class CompressionFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String acceptEncoding = httpRequest.getHeader("Accept-Encoding");
if (acceptEncoding != null && acceptEncoding.contains("gzip")) {
httpResponse.setHeader("Content-Encoding", "gzip");
// 实现GZIP压缩逻辑
}
chain.doFilter(request, response);
}
}
}
7.2 监控和日志
// 应用监控
@Component
public class ApplicationMetrics {
private final MeterRegistry meterRegistry;
private final Counter userCreationCounter;
private final Timer userQueryTimer;
private final Gauge activeUsersGauge;
public ApplicationMetrics(MeterRegistry meterRegistry, UserService userService) {
this.meterRegistry = meterRegistry;
this.userCreationCounter = Counter.builder("users.created")
.description("用户创建计数")
.register(meterRegistry);
this.userQueryTimer = Timer.builder("users.query.time")
.description("用户查询耗时")
.register(meterRegistry);
this.activeUsersGauge = Gauge.builder("users.active")
.description("活跃用户数")
.register(meterRegistry, userService, UserService::getActiveUserCount);
}
public void incrementUserCreation() {
userCreationCounter.increment();
}
public Timer.Sample startUserQueryTimer() {
return Timer.start(meterRegistry);
}
public void recordUserQueryTime(Timer.Sample sample) {
sample.stop(userQueryTimer);
}
}
// 自定义日志
@Component
public class AuditLogger {
private static final Logger auditLog = LoggerFactory.getLogger("AUDIT");
public void logUserCreation(String username, String createdBy) {
auditLog.info("用户创建 - 用户名: {}, 创建者: {}, 时间: {}",
username, createdBy, LocalDateTime.now());
}
public void logUserDeletion(String username, String deletedBy) {
auditLog.warn("用户删除 - 用户名: {}, 删除者: {}, 时间: {}",
username, deletedBy, LocalDateTime.now());
}
public void logLoginAttempt(String username, String ip, boolean success) {
if (success) {
auditLog.info("登录成功 - 用户名: {}, IP: {}, 时间: {}",
username, ip, LocalDateTime.now());
} else {
auditLog.warn("登录失败 - 用户名: {}, IP: {}, 时间: {}",
username, ip, LocalDateTime.now());
}
}
public void logSecurityEvent(String event, String details) {
auditLog.error("安全事件 - 事件: {}, 详情: {}, 时间: {}",
event, details, LocalDateTime.now());
}
}
// 健康检查
@Component
public class CustomHealthIndicator implements HealthIndicator {
private final UserRepository userRepository;
private final EmailService emailService;
public CustomHealthIndicator(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
@Override
public Health health() {
try {
// 检查数据库连接
long userCount = userRepository.count();
// 检查邮件服务
boolean emailServiceUp = emailService.isServiceAvailable();
if (emailServiceUp) {
return Health.up()
.withDetail("database", "可用")
.withDetail("userCount", userCount)
.withDetail("emailService", "可用")
.build();
} else {
return Health.down()
.withDetail("database", "可用")
.withDetail("userCount", userCount)
.withDetail("emailService", "不可用")
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.build();
}
}
}
7.3 错误处理和异常管理
// 全局异常处理增强
@ControllerAdvice
public class EnhancedGlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(EnhancedGlobalExceptionHandler.class);
private final AuditLogger auditLogger;
public EnhancedGlobalExceptionHandler(AuditLogger auditLogger) {
this.auditLogger = auditLogger;
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleUserNotFound(UserNotFoundException e) {
logger.warn("用户未找到: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("USER_NOT_FOUND", e.getMessage()));
}
@ExceptionHandler(DuplicateEmailException.class)
public ResponseEntity<ApiResponse<Void>> handleDuplicateEmail(DuplicateEmailException e) {
logger.warn("邮箱重复: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.CONFLICT)
.body(ApiResponse.error("EMAIL_EXISTS", e.getMessage()));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Map<String, String>>> handleValidationErrors(
MethodArgumentNotValidException e) {
Map<String, String> errors = new HashMap<>();
e.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage()));
logger.warn("输入验证失败: {}", errors);
return ResponseEntity.badRequest()
.body(ApiResponse.error("VALIDATION_ERROR", "输入验证失败"));
}
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ApiResponse<Void>> handleAccessDenied(
AccessDeniedException e, HttpServletRequest request) {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
auditLogger.logSecurityEvent("访问被拒绝",
String.format("用户: %s, 路径: %s", username, request.getRequestURI()));
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error("ACCESS_DENIED", "访问被拒绝"));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleGenericException(
Exception e, HttpServletRequest request) {
String errorId = UUID.randomUUID().toString();
logger.error("未处理的异常 [{}]: {}", errorId, e.getMessage(), e);
// 在生产环境中不暴露详细错误信息
String message = "服务器内部错误,错误ID: " + errorId;
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("INTERNAL_ERROR", message));
}
}
// 重试机制
@Service
public class RetryableUserService {
private final UserRepository userRepository;
private final EmailService emailService;
public RetryableUserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
@Retryable(value = {DataAccessException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
public User saveUserWithRetry(User user) {
return userRepository.save(user);
}
@Retryable(value = {EmailServiceException.class}, maxAttempts = 3, backoff = @Backoff(delay = 2000))
public void sendEmailWithRetry(String to, String subject, String content) {
emailService.sendEmail(to, subject, content);
}
@Recover
public User recoverSaveUser(DataAccessException e, User user) {
// 保存失败后的恢复逻辑
throw new ServiceUnavailableException("用户保存服务暂时不可用");
}
@Recover
public void recoverSendEmail(EmailServiceException e, String to, String subject, String content) {
// 邮件发送失败后的恢复逻辑
// 可以将邮件放入队列稍后重试
}
}
8. 本章小结
8.1 Web开发技术对比
技术 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Servlet | 性能高、控制精细 | 开发复杂、代码冗余 | 底层控制、性能要求高 |
JSP | 易于上手、模板简单 | 维护困难、性能一般 | 简单页面、快速原型 |
Spring MVC | 功能完整、生态丰富 | 配置复杂、学习成本高 | 企业级应用、复杂业务 |
Spring Boot | 开箱即用、约定优于配置 | 黑盒化、定制困难 | 快速开发、微服务 |
8.2 性能优化要点
数据库优化
- 使用连接池
- 查询优化和索引
- 缓存策略
- 分页查询
应用层优化
- 异步处理
- 缓存机制
- 响应压缩
- 静态资源优化
网络优化
- CDN使用
- HTTP/2支持
- 资源合并
- 懒加载
8.3 安全考虑
认证和授权
- JWT令牌
- 角色权限控制
- 会话管理
输入验证
- 参数校验
- SQL注入防护
- XSS防护
传输安全
- HTTPS加密
- 安全头设置
- CSRF保护
8.4 开发建议
架构设计
- 分层架构
- 依赖注入
- 接口抽象
- 配置外部化
代码质量
- 单元测试
- 代码审查
- 文档完善
- 异常处理
运维监控
- 日志记录
- 性能监控
- 健康检查
- 错误追踪
下一章预告
下一章我们将学习微服务架构,包括: - Spring Cloud基础 - 服务注册与发现 - 配置中心 - 服务网关 - 分布式追踪 - 容器化部署
练习题
基础练习
- 创建一个简单的博客系统,包含文章管理功能
- 实现用户注册、登录和权限控制
- 添加文章的增删改查功能
进阶练习
- 为博客系统添加RESTful API
- 实现JWT认证和授权
- 添加文章搜索和分页功能
- 集成Swagger API文档
高级练习
- 实现文章的缓存机制
- 添加异步邮件通知功能
- 实现文件上传和图片处理
- 添加应用监控和日志记录
项目练习
- 开发一个完整的电商系统后端
- 包含用户管理、商品管理、订单管理
- 实现购物车、支付集成、库存管理
- 添加完整的安全机制和性能优化
// 用户管理Servlet @WebServlet(“/users/*”) public class UserServlet extends HttpServlet {
private List<User> users = new ArrayList<>();
@Override
public void init() throws ServletException {
// 初始化一些测试数据
users.add(new User(1, "张三", "zhangsan@example.com"));
users.add(new User(2, "李四", "lisi@example.com"));
users.add(new User(3, "王五", "wangwu@example.com"));
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String pathInfo = request.getPathInfo();
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
if (pathInfo == null || pathInfo.equals("/")) {
// 获取所有用户
out.println(usersToJson(users));
} else {
// 获取特定用户
String[] parts = pathInfo.split("/");
if (parts.length >= 2) {
int userId = Integer.parseInt(parts[1]);
User user = findUserById(userId);
if (user != null) {
out.println(userToJson(user));
} else {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
out.println("{\"error\": \"用户不存在\"}");
}
}
}
} catch (NumberFormatException e) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
out.println("{\"error\": \"无效的用户ID\"}");
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 创建新用户
String name = request.getParameter("name");
String email = request.getParameter("email");
if (name == null || email == null || name.trim().isEmpty() || email.trim().isEmpty()) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().println("{\"error\": \"姓名和邮箱不能为空\"}");
return;
}
int newId = users.size() + 1;
User newUser = new User(newId, name.trim(), email.trim());
users.add(newUser);
response.setStatus(HttpServletResponse.SC_CREATED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(userToJson(newUser));
}
@Override
protected void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String pathInfo = request.getPathInfo();
if (pathInfo == null || pathInfo.equals("/")) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().println("{\"error\": \"缺少用户ID\"}");
return;
}
try {
String[] parts = pathInfo.split("/");
int userId = Integer.parseInt(parts[1]);
User user = findUserById(userId);
if (user == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().println("{\"error\": \"用户不存在\"}");
return;
}
// 读取请求体
StringBuilder sb = new StringBuilder();
BufferedReader reader = request.getReader();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
// 简单的JSON解析(实际项目中应使用JSON库)
String requestBody = sb.toString();
if (requestBody.contains("name")) {
String name = extractJsonValue(requestBody, "name");
if (name != null && !name.trim().isEmpty()) {
user.setName(name.trim());
}
}
if (requestBody.contains("email")) {
String email = extractJsonValue(requestBody, "email");
if (email != null && !email.trim().isEmpty()) {
user.setEmail(email.trim());
}
}
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(userToJson(user));
} catch (NumberFormatException e) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().println("{\"error\": \"无效的用户ID\"}");
}
}
@Override
protected void doDelete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String pathInfo = request.getPathInfo();
if (pathInfo == null || pathInfo.equals("/")) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().println("{\"error\": \"缺少用户ID\"}");
return;
}
try {
String[] parts = pathInfo.split("/");
int userId = Integer.parseInt(parts[1]);
boolean removed = users.removeIf(user -> user.getId() == userId);
if (removed) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
} else {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().println("{\"error\": \"用户不存在\"}");
}
} catch (NumberFormatException e) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().println("{\"error\": \"无效的用户ID\"}");
}
}
private User findUserById(int id) {
return users.stream()
.filter(user -> user.getId() == id)
.findFirst()
.orElse(null);
}
private String userToJson(User user) {
return String.format(
"{\"id\": %d, \"name\": \"%s\", \"email\": \"%s\"}",
user.getId(), user.getName(), user.getEmail()
);
}
private String usersToJson(List<User> users) {
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < users.size(); i++) {
if (i > 0) sb.append(", ");
sb.append(userToJson(users.get(i)));
}
sb.append("]");
return sb.toString();
}
private String extractJsonValue(String json, String key) {
String pattern = "\"" + key + "\":\"";
int start = json.indexOf(pattern);
if (start == -1) return null;
start += pattern.length();
int end = json.indexOf("\"", start);
if (end == -1) return null;
return json.substring(start, end);
}
}
// 用户实体类 class User { private int id; private String name; private String email;
public User(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// Getters and Setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
@Override
public String toString() {
return "User{id=" + id + ", name='" + name + "', email='" + email + "'}";
}
}
### 1.2 JSP基础
```jsp
<%-- userList.jsp - 用户列表页面 --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.*, java.text.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用户管理系统</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header {
text-align: center;
color: #333;
margin-bottom: 30px;
}
.user-form {
background-color: #f9f9f9;
padding: 20px;
border-radius: 5px;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.btn {
background-color: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn:hover {
background-color: #0056b3;
}
.btn-danger {
background-color: #dc3545;
}
.btn-danger:hover {
background-color: #c82333;
}
.user-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.user-table th, .user-table td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}
.user-table th {
background-color: #f8f9fa;
font-weight: bold;
}
.user-table tr:nth-child(even) {
background-color: #f9f9f9;
}
.message {
padding: 10px;
margin-bottom: 20px;
border-radius: 4px;
}
.message.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.message.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
</style>
</head>
<body>
<div class="container">
<h1 class="header">用户管理系统</h1>
<%-- 显示消息 --%>
<c:if test="${not empty message}">
<div class="message ${messageType}">
${message}
</div>
</c:if>
<%-- 添加用户表单 --%>
<div class="user-form">
<h2>添加新用户</h2>
<form action="${pageContext.request.contextPath}/users" method="post">
<div class="form-group">
<label for="name">姓名:</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
</div>
<button type="submit" class="btn">添加用户</button>
</form>
</div>
<%-- 用户列表 --%>
<h2>用户列表</h2>
<c:choose>
<c:when test="${empty users}">
<p>暂无用户数据</p>
</c:when>
<c:otherwise>
<table class="user-table">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<c:forEach var="user" items="${users}">
<tr>
<td>${user.id}</td>
<td><c:out value="${user.name}" /></td>
<td><c:out value="${user.email}" /></td>
<td>
<form action="${pageContext.request.contextPath}/users/${user.id}"
method="post" style="display: inline;">
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="btn btn-danger"
onclick="return confirm('确定要删除用户 ${user.name} 吗?')">
删除
</button>
</form>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</c:otherwise>
</c:choose>
<%-- 页面信息 --%>
<div style="margin-top: 30px; text-align: center; color: #666;">
<p>当前时间: <fmt:formatDate value="${now}" pattern="yyyy-MM-dd HH:mm:ss" /></p>
<p>总用户数: ${users.size()}</p>
</div>
</div>
<script>
// 简单的客户端验证
document.querySelector('form').addEventListener('submit', function(e) {
const name = document.getElementById('name').value.trim();
const email = document.getElementById('email').value.trim();
if (!name) {
alert('请输入姓名');
e.preventDefault();
return;
}
if (!email) {
alert('请输入邮箱');
e.preventDefault();
return;
}
// 简单的邮箱格式验证
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
alert('请输入有效的邮箱地址');
e.preventDefault();
return;
}
});
</script>
</body>
</html>
1.3 Filter和Listener
// 字符编码过滤器
@WebFilter("/*")
public class CharacterEncodingFilter implements Filter {
private String encoding = "UTF-8";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String encodingParam = filterConfig.getInitParameter("encoding");
if (encodingParam != null && !encodingParam.trim().isEmpty()) {
this.encoding = encodingParam;
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 设置请求编码
request.setCharacterEncoding(encoding);
// 设置响应编码
response.setCharacterEncoding(encoding);
// 继续过滤链
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 清理资源
}
}
// 日志记录过滤器
@WebFilter("/*")
public class LoggingFilter implements Filter {
private static final Logger logger = Logger.getLogger(LoggingFilter.class.getName());
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 记录请求开始时间
long startTime = System.currentTimeMillis();
// 记录请求信息
String method = httpRequest.getMethod();
String uri = httpRequest.getRequestURI();
String queryString = httpRequest.getQueryString();
String remoteAddr = httpRequest.getRemoteAddr();
String userAgent = httpRequest.getHeader("User-Agent");
logger.info(String.format("请求开始 - %s %s%s from %s [%s]",
method, uri,
queryString != null ? "?" + queryString : "",
remoteAddr, userAgent));
try {
// 继续过滤链
chain.doFilter(request, response);
} finally {
// 记录响应信息
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
int status = httpResponse.getStatus();
logger.info(String.format("请求完成 - %s %s -> %d (%d ms)",
method, uri, status, duration));
}
}
}
// 安全过滤器
@WebFilter("/admin/*")
public class SecurityFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession(false);
// 检查用户是否已登录
boolean isLoggedIn = (session != null && session.getAttribute("user") != null);
if (!isLoggedIn) {
// 未登录,重定向到登录页面
String loginUrl = httpRequest.getContextPath() + "/login";
httpResponse.sendRedirect(loginUrl);
return;
}
// 检查用户权限
User user = (User) session.getAttribute("user");
if (!hasAdminRole(user)) {
httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpResponse.getWriter().println("访问被拒绝:需要管理员权限");
return;
}
// 继续过滤链
chain.doFilter(request, response);
}
private boolean hasAdminRole(User user) {
// 简单的权限检查逻辑
return user != null && "admin".equals(user.getRole());
}
}
// 应用程序生命周期监听器
@WebListener
public class AppContextListener implements ServletContextListener {
private static final Logger logger = Logger.getLogger(AppContextListener.class.getName());
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
logger.info("Web应用程序启动中...");
// 初始化应用程序配置
initializeConfiguration(context);
// 初始化数据库连接池
initializeDatabase(context);
// 初始化缓存
initializeCache(context);
// 设置应用程序启动时间
context.setAttribute("startupTime", new Date());
logger.info("Web应用程序启动完成");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
logger.info("Web应用程序关闭中...");
// 清理缓存
cleanupCache(context);
// 关闭数据库连接池
cleanupDatabase(context);
logger.info("Web应用程序关闭完成");
}
private void initializeConfiguration(ServletContext context) {
// 加载配置文件
Properties config = new Properties();
try (InputStream is = context.getResourceAsStream("/WEB-INF/app.properties")) {
if (is != null) {
config.load(is);
context.setAttribute("appConfig", config);
logger.info("配置文件加载成功");
}
} catch (IOException e) {
logger.warning("配置文件加载失败: " + e.getMessage());
}
}
private void initializeDatabase(ServletContext context) {
// 初始化数据库连接池(示例)
try {
// 这里应该初始化真实的数据库连接池
context.setAttribute("dbInitialized", true);
logger.info("数据库连接池初始化成功");
} catch (Exception e) {
logger.severe("数据库连接池初始化失败: " + e.getMessage());
}
}
private void initializeCache(ServletContext context) {
// 初始化缓存(示例)
Map<String, Object> cache = new ConcurrentHashMap<>();
context.setAttribute("appCache", cache);
logger.info("缓存初始化成功");
}
private void cleanupCache(ServletContext context) {
Map<String, Object> cache = (Map<String, Object>) context.getAttribute("appCache");
if (cache != null) {
cache.clear();
logger.info("缓存清理完成");
}
}
private void cleanupDatabase(ServletContext context) {
// 清理数据库连接池
Boolean dbInitialized = (Boolean) context.getAttribute("dbInitialized");
if (Boolean.TRUE.equals(dbInitialized)) {
// 这里应该关闭真实的数据库连接池
logger.info("数据库连接池清理完成");
}
}
}
// 会话监听器
@WebListener
public class SessionListener implements HttpSessionListener, HttpSessionAttributeListener {
private static final Logger logger = Logger.getLogger(SessionListener.class.getName());
private static final AtomicInteger activeSessions = new AtomicInteger(0);
@Override
public void sessionCreated(HttpSessionEvent se) {
int count = activeSessions.incrementAndGet();
logger.info("新会话创建,会话ID: " + se.getSession().getId() +
",当前活跃会话数: " + count);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
int count = activeSessions.decrementAndGet();
logger.info("会话销毁,会话ID: " + se.getSession().getId() +
",当前活跃会话数: " + count);
}
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
logger.fine("会话属性添加: " + se.getName() + " = " + se.getValue());
}
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
logger.fine("会话属性移除: " + se.getName());
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
logger.fine("会话属性替换: " + se.getName() + " = " + se.getValue());
}
public static int getActiveSessionCount() {
return activeSessions.get();
}
}
2. Spring框架基础
2.1 Spring IoC容器
// 用户服务接口
public interface UserService {
User findById(Long id);
User save(User user);
List<User> findAll();
void deleteById(Long id);
User findByEmail(String email);
}
// 用户服务实现
@Service
@Transactional
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final EmailService emailService;
// 构造函数注入
public UserServiceImpl(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
@Override
@Transactional(readOnly = true)
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("用户不存在: " + id));
}
@Override
public User save(User user) {
// 验证用户数据
validateUser(user);
// 检查邮箱是否已存在
if (user.getId() == null && userRepository.existsByEmail(user.getEmail())) {
throw new DuplicateEmailException("邮箱已存在: " + user.getEmail());
}
// 保存用户
User savedUser = userRepository.save(user);
// 发送欢迎邮件(新用户)
if (user.getId() == null) {
emailService.sendWelcomeEmail(savedUser);
}
return savedUser;
}
@Override
@Transactional(readOnly = true)
public List<User> findAll() {
return userRepository.findAll();
}
@Override
public void deleteById(Long id) {
if (!userRepository.existsById(id)) {
throw new UserNotFoundException("用户不存在: " + id);
}
userRepository.deleteById(id);
}
@Override
@Transactional(readOnly = true)
public User findByEmail(String email) {
return userRepository.findByEmail(email)
.orElseThrow(() -> new UserNotFoundException("用户不存在: " + email));
}
private void validateUser(User user) {
if (user == null) {
throw new IllegalArgumentException("用户不能为空");
}
if (user.getName() == null || user.getName().trim().isEmpty()) {
throw new IllegalArgumentException("用户名不能为空");
}
if (user.getEmail() == null || user.getEmail().trim().isEmpty()) {
throw new IllegalArgumentException("邮箱不能为空");
}
if (!isValidEmail(user.getEmail())) {
throw new IllegalArgumentException("邮箱格式无效");
}
}
private boolean isValidEmail(String email) {
return email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");
}
}
// 用户仓库接口
public interface UserRepository {
Optional<User> findById(Long id);
User save(User user);
List<User> findAll();
void deleteById(Long id);
boolean existsById(Long id);
Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
}
// 用户仓库实现(内存版本)
@Repository
public class InMemoryUserRepository implements UserRepository {
private final Map<Long, User> users = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(1);
@PostConstruct
public void init() {
// 初始化一些测试数据
save(new User(null, "张三", "zhangsan@example.com"));
save(new User(null, "李四", "lisi@example.com"));
save(new User(null, "王五", "wangwu@example.com"));
}
@Override
public Optional<User> findById(Long id) {
return Optional.ofNullable(users.get(id));
}
@Override
public User save(User user) {
if (user.getId() == null) {
user.setId(idGenerator.getAndIncrement());
}
users.put(user.getId(), user);
return user;
}
@Override
public List<User> findAll() {
return new ArrayList<>(users.values());
}
@Override
public void deleteById(Long id) {
users.remove(id);
}
@Override
public boolean existsById(Long id) {
return users.containsKey(id);
}
@Override
public Optional<User> findByEmail(String email) {
return users.values().stream()
.filter(user -> email.equals(user.getEmail()))
.findFirst();
}
@Override
public boolean existsByEmail(String email) {
return users.values().stream()
.anyMatch(user -> email.equals(user.getEmail()));
}
}
// 邮件服务接口
public interface EmailService {
void sendWelcomeEmail(User user);
void sendPasswordResetEmail(User user, String resetToken);
void sendNotificationEmail(User user, String subject, String content);
}
// 邮件服务实现
@Service
public class EmailServiceImpl implements EmailService {
private static final Logger logger = LoggerFactory.getLogger(EmailServiceImpl.class);
@Value("${app.email.from:noreply@example.com}")
private String fromEmail;
@Value("${app.email.enabled:false}")
private boolean emailEnabled;
@Override
public void sendWelcomeEmail(User user) {
String subject = "欢迎加入我们的平台";
String content = String.format(
"亲爱的 %s,\n\n" +
"欢迎加入我们的平台!您的账户已成功创建。\n\n" +
"如有任何问题,请随时联系我们。\n\n" +
"祝好!\n" +
"平台团队",
user.getName()
);
sendEmail(user.getEmail(), subject, content);
}
@Override
public void sendPasswordResetEmail(User user, String resetToken) {
String subject = "密码重置请求";
String content = String.format(
"亲爱的 %s,\n\n" +
"我们收到了您的密码重置请求。\n\n" +
"重置令牌: %s\n\n" +
"如果这不是您的操作,请忽略此邮件。\n\n" +
"祝好!\n" +
"平台团队",
user.getName(), resetToken
);
sendEmail(user.getEmail(), subject, content);
}
@Override
public void sendNotificationEmail(User user, String subject, String content) {
sendEmail(user.getEmail(), subject, content);
}
private void sendEmail(String to, String subject, String content) {
if (!emailEnabled) {
logger.info("邮件发送已禁用,模拟发送邮件:");
logger.info("收件人: {}", to);
logger.info("主题: {}", subject);
logger.info("内容: {}", content);
return;
}
try {
// 这里应该实现真实的邮件发送逻辑
// 例如使用 JavaMail API 或 Spring Mail
logger.info("发送邮件到 {} - 主题: {}", to, subject);
// 模拟邮件发送延迟
Thread.sleep(100);
} catch (Exception e) {
logger.error("邮件发送失败: " + e.getMessage(), e);
throw new EmailSendException("邮件发送失败", e);
}
}
}
// 自定义异常类
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
public class DuplicateEmailException extends RuntimeException {
public DuplicateEmailException(String message) {
super(message);
}
}
public class EmailSendException extends RuntimeException {
public EmailSendException(String message, Throwable cause) {
super(message, cause);
}
}
// Spring配置类
@Configuration
@ComponentScan(basePackages = "com.example")
@PropertySource("classpath:application.properties")
@EnableTransactionManagement
public class AppConfig {
@Bean
public PropertySourcesPlaceholderConfigurer propertyConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
public PlatformTransactionManager transactionManager() {
// 简单的事务管理器(实际项目中应使用数据库事务管理器)
return new ResourcelessTransactionManager();
}
@Bean
@Profile("dev")
public UserRepository devUserRepository() {
return new InMemoryUserRepository();
}
@Bean
@Profile("prod")
public UserRepository prodUserRepository() {
// 生产环境应该返回数据库实现
return new InMemoryUserRepository();
}
}