Java Web API 开发完整指南
文章目录
- 技术栈选择
- 1. 项目搭建和基础配置
- 1.1 创建 Spring Boot 项目
- 1.2 应用配置文件
- 2. 核心架构和原理
- 2.1 分层架构
- 2.2 请求流程
- 3. 完整实现代码
- 3.1 实体类 (Entity)
- 3.2 数据访问层 (Repository)
- 3.3 JWT 工具类
- 3.4 Spring Security 配置
- 3.5 DTO 和数据验证
- 3.6 控制器 (Controller)
- 3.7 业务服务层 (Service)
- 3.8 数据初始化
- 3.9 定时任务
- 3.10 全局异常处理
- 3.11 应用主类
- 4. API 测试和使用
- 4.1 启动应用
- 4.2 API 文档访问
- 4.3 测试 API
- 5. 核心原理总结
- 5.1 安全流程
- 5.2 数据流程
- 5.3 定时任务原理
技术栈选择
- 框架: Spring Boot 3.x
- 安全: Spring Security + JWT
- 数据: Spring Data JPA + H2/MySQL
- 文档: OpenAPI 3 (Swagger)
- 任务调度: Spring Scheduler
- 构建工具: Maven
1. 项目搭建和基础配置
1.1 创建 Spring Boot 项目
使用 Spring Initializr 或以下 Maven 配置:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.0</version><relativePath/></parent><groupId>com.example</groupId><artifactId>java-webapi</artifactId><version>1.0.0</version><name>Java Web API</name><properties><java.version>17</java.version></properties><dependencies><!-- Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Security --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- JPA --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- Validation --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!-- H2 Database --><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency><!-- JWT --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><!-- OpenAPI --><dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>2.3.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
1.2 应用配置文件
application.yml
server:port: 8080servlet:context-path: /apispring:datasource:url: jdbc:h2:mem:testdbdriver-class-name: org.h2.Driverusername: sapassword: jpa:database-platform: org.hibernate.dialect.H2Dialecthibernate:ddl-auto: create-dropshow-sql: trueproperties:hibernate:format_sql: trueh2:console:enabled: truepath: /h2-consoleapp:jwt:secret: "mySecretKeymySecretKeymySecretKeymySecretKeymySecretKeymySecretKey"expiration: 86400000 # 24 hours
2. 核心架构和原理
2.1 分层架构
Controller层 (REST端点)↓
Service层 (业务逻辑)↓
Repository层 (数据访问)↓
Database
2.2 请求流程
HTTP请求 → 安全过滤器 → 控制器 → 服务层 → 数据层 → 数据库↓
HTTP响应 ← 控制器 ← 服务层 ← 数据层
3. 完整实现代码
3.1 实体类 (Entity)
User.java
package com.example.javawebapi.entity;import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;@Entity
@Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "username"),@UniqueConstraint(columnNames = "email")
})
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@NotBlank@Size(max = 50)private String username;@NotBlank@Size(max = 120)private String password;@NotBlank@Size(max = 50)@Emailprivate String email;@ManyToMany(fetch = FetchType.LAZY)@JoinTable(name = "user_roles",joinColumns = @JoinColumn(name = "user_id"),inverseJoinColumns = @JoinColumn(name = "role_id"))private Set<Role> roles = new HashSet<>();private boolean enabled = true;private LocalDateTime createdAt;private LocalDateTime updatedAt;@PrePersistprotected void onCreate() {createdAt = LocalDateTime.now();updatedAt = LocalDateTime.now();}@PreUpdateprotected void onUpdate() {updatedAt = LocalDateTime.now();}// Constructorspublic User() {}public User(String username, String email, String password) {this.username = username;this.email = email;this.password = password;}// Getters and Setterspublic Long getId() { return id; }public void setId(Long id) { this.id = id; }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 String getEmail() { return email; }public void setEmail(String email) { this.email = email; }public Set<Role> getRoles() { return roles; }public void setRoles(Set<Role> roles) { this.roles = roles; }public boolean isEnabled() { return enabled; }public void setEnabled(boolean enabled) { this.enabled = enabled; }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; }
}
Role.java
package com.example.javawebapi.entity;import jakarta.persistence.*;@Entity
@Table(name = "roles")
public class Role {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer id;@Enumerated(EnumType.STRING)@Column(length = 20)private ERole name;public Role() {}public Role(ERole name) {this.name = name;}// Getters and Setterspublic Integer getId() { return id; }public void setId(Integer id) { this.id = id; }public ERole getName() { return name; }public void setName(ERole name) { this.name = name; }
}enum ERole {ROLE_USER,ROLE_ADMIN
}
3.2 数据访问层 (Repository)
UserRepository.java
package com.example.javawebapi.repository;import com.example.javawebapi.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;import java.util.Optional;@Repository
public interface UserRepository extends JpaRepository<User, Long> {Optional<User> findByUsername(String username);Optional<User> findByEmail(String email);Boolean existsByUsername(String username);Boolean existsByEmail(String email);
}
RoleRepository.java
package com.example.javawebapi.repository;import com.example.javawebapi.entity.ERole;
import com.example.javawebapi.entity.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;import java.util.Optional;@Repository
public interface RoleRepository extends JpaRepository<Role, Integer> {Optional<Role> findByName(ERole name);
}
3.3 JWT 工具类
JwtUtils.java
package com.example.javawebapi.security.jwt;import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;import java.security.Key;
import java.util.Date;@Component
public class JwtUtils {private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);@Value("${app.jwt.secret}")private String jwtSecret;@Value("${app.jwt.expiration}")private int jwtExpirationMs;public String generateJwtToken(Authentication authentication) {UserDetails userPrincipal = (UserDetails) authentication.getPrincipal();return Jwts.builder().setSubject((userPrincipal.getUsername())).setIssuedAt(new Date()).setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)).signWith(key(), SignatureAlgorithm.HS256).compact();}private Key key() {return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret));}public String getUserNameFromJwtToken(String token) {return Jwts.parserBuilder().setSigningKey(key()).build().parseClaimsJws(token).getBody().getSubject();}public boolean validateJwtToken(String authToken) {try {Jwts.parserBuilder().setSigningKey(key()).build().parseClaimsJws(authToken);return true;} catch (MalformedJwtException e) {logger.error("Invalid JWT token: {}", e.getMessage());} catch (ExpiredJwtException e) {logger.error("JWT token is expired: {}", e.getMessage());} catch (UnsupportedJwtException e) {logger.error("JWT token is unsupported: {}", e.getMessage());} catch (IllegalArgumentException e) {logger.error("JWT claims string is empty: {}", e.getMessage());}return false;}
}
3.4 Spring Security 配置
WebSecurityConfig.java
package com.example.javawebapi.security;import com.example.javawebapi.security.jwt.AuthTokenFilter;
import com.example.javawebapi.security.services.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {@AutowiredUserDetailsServiceImpl userDetailsService;@Beanpublic AuthTokenFilter authenticationJwtTokenFilter() {return new AuthTokenFilter();}@Beanpublic DaoAuthenticationProvider authenticationProvider() {DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();authProvider.setUserDetailsService(userDetailsService);authProvider.setPasswordEncoder(passwordEncoder());return authProvider;}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {return authConfig.getAuthenticationManager();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf(csrf -> csrf.disable()).sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeHttpRequests(auth -> auth.requestMatchers("/auth/**").permitAll().requestMatchers("/h2-console/**").permitAll().requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll().anyRequest().authenticated());http.authenticationProvider(authenticationProvider());http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);// For H2 consolehttp.headers(headers -> headers.frameOptions(frame -> frame.sameOrigin()));return http.build();}
}
AuthTokenFilter.java
package com.example.javawebapi.security.jwt;import com.example.javawebapi.security.services.UserDetailsServiceImpl;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import java.io.IOException;public class AuthTokenFilter extends OncePerRequestFilter {@Autowiredprivate JwtUtils jwtUtils;@Autowiredprivate UserDetailsServiceImpl userDetailsService;private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class);@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {try {String jwt = parseJwt(request);if (jwt != null && jwtUtils.validateJwtToken(jwt)) {String username = jwtUtils.getUserNameFromJwtToken(jwt);UserDetails userDetails = userDetailsService.loadUserByUsername(username);UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authentication);}} catch (Exception e) {logger.error("Cannot set user authentication: {}", e);}filterChain.doFilter(request, response);}private String parseJwt(HttpServletRequest request) {String headerAuth = request.getHeader("Authorization");if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {return headerAuth.substring(7);}return null;}
}
UserDetailsServiceImpl.java
package com.example.javawebapi.security.services;import com.example.javawebapi.entity.User;
import com.example.javawebapi.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class UserDetailsServiceImpl implements UserDetailsService {@AutowiredUserRepository userRepository;@Override@Transactionalpublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));return UserDetailsImpl.build(user);}
}
UserDetailsImpl.java
package com.example.javawebapi.security.services;import com.example.javawebapi.entity.User;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;public class UserDetailsImpl implements UserDetails {private static final long serialVersionUID = 1L;private Long id;private String username;private String email;@JsonIgnoreprivate String password;private Collection<? extends GrantedAuthority> authorities;public UserDetailsImpl(Long id, String username, String email, String password,Collection<? extends GrantedAuthority> authorities) {this.id = id;this.username = username;this.email = email;this.password = password;this.authorities = authorities;}public static UserDetailsImpl build(User user) {List<GrantedAuthority> authorities = user.getRoles().stream().map(role -> new SimpleGrantedAuthority(role.getName().name())).collect(Collectors.toList());return new UserDetailsImpl(user.getId(),user.getUsername(),user.getEmail(),user.getPassword(),authorities);}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return authorities;}public Long getId() { return id; }public String getEmail() { return email; }@Overridepublic String getPassword() { return password; }@Overridepublic String getUsername() { return username; }@Overridepublic boolean isAccountNonExpired() { return true; }@Overridepublic boolean isAccountNonLocked() { return true; }@Overridepublic boolean isCredentialsNonExpired() { return true; }@Overridepublic boolean isEnabled() { return true; }@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;UserDetailsImpl user = (UserDetailsImpl) o;return Objects.equals(id, user.id);}
}
3.5 DTO 和数据验证
LoginRequest.java
package com.example.javawebapi.payload.request;import jakarta.validation.constraints.NotBlank;public class LoginRequest {@NotBlankprivate String username;@NotBlankprivate String 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; }
}
SignupRequest.java
package com.example.javawebapi.payload.request;import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.util.Set;public class SignupRequest {@NotBlank@Size(min = 3, max = 20)private String username;@NotBlank@Size(max = 50)@Emailprivate String email;private Set<String> role;@NotBlank@Size(min = 6, max = 40)private String password;public String getUsername() { return username; }public void setUsername(String username) { this.username = username; }public String getEmail() { return email; }public void setEmail(String email) { this.email = email; }public String getPassword() { return password; }public void setPassword(String password) { this.password = password; }public Set<String> getRole() { return this.role; }public void setRole(Set<String> role) { this.role = role; }
}
JwtResponse.java
package com.example.javawebapi.payload.response;import java.util.List;public class JwtResponse {private String token;private String type = "Bearer";private Long id;private String username;private String email;private List<String> roles;public JwtResponse(String accessToken, Long id, String username, String email, List<String> roles) {this.token = accessToken;this.id = id;this.username = username;this.email = email;this.roles = roles;}// Getters and Setterspublic String getAccessToken() { return token; }public void setAccessToken(String accessToken) { this.token = accessToken; }public String getTokenType() { return type; }public void setTokenType(String tokenType) { this.type = tokenType; }public Long getId() { return id; }public void setId(Long id) { this.id = id; }public String getEmail() { return email; }public void setEmail(String email) { this.email = email; }public String getUsername() { return username; }public void setUsername(String username) { this.username = username; }public List<String> getRoles() { return roles; }
}
MessageResponse.java
package com.example.javawebapi.payload.response;public class MessageResponse {private String message;public MessageResponse(String message) {this.message = message;}public String getMessage() { return message; }public void setMessage(String message) { this.message = message; }
}
3.6 控制器 (Controller)
AuthController.java
package com.example.javawebapi.controller;import com.example.javawebapi.entity.ERole;
import com.example.javawebapi.entity.Role;
import com.example.javawebapi.entity.User;
import com.example.javawebapi.payload.request.LoginRequest;
import com.example.javawebapi.payload.request.SignupRequest;
import com.example.javawebapi.payload.response.JwtResponse;
import com.example.javawebapi.payload.response.MessageResponse;
import com.example.javawebapi.repository.RoleRepository;
import com.example.javawebapi.repository.UserRepository;
import com.example.javawebapi.security.jwt.JwtUtils;
import com.example.javawebapi.security.services.UserDetailsImpl;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/auth")
public class AuthController {@AutowiredAuthenticationManager authenticationManager;@AutowiredUserRepository userRepository;@AutowiredRoleRepository roleRepository;@AutowiredPasswordEncoder encoder;@AutowiredJwtUtils jwtUtils;@PostMapping("/signin")public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));SecurityContextHolder.getContext().setAuthentication(authentication);String jwt = jwtUtils.generateJwtToken(authentication);UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();List<String> roles = userDetails.getAuthorities().stream().map(item -> item.getAuthority()).collect(Collectors.toList());return ResponseEntity.ok(new JwtResponse(jwt,userDetails.getId(),userDetails.getUsername(),userDetails.getEmail(),roles));}@PostMapping("/signup")public ResponseEntity<?> registerUser(@Valid @RequestBody SignupRequest signUpRequest) {if (userRepository.existsByUsername(signUpRequest.getUsername())) {return ResponseEntity.badRequest().body(new MessageResponse("Error: Username is already taken!"));}if (userRepository.existsByEmail(signUpRequest.getEmail())) {return ResponseEntity.badRequest().body(new MessageResponse("Error: Email is already in use!"));}// Create new user's accountUser user = new User(signUpRequest.getUsername(),signUpRequest.getEmail(),encoder.encode(signUpRequest.getPassword()));Set<String> strRoles = signUpRequest.getRole();Set<Role> roles = new HashSet<>();if (strRoles == null) {Role userRole = roleRepository.findByName(ERole.ROLE_USER).orElseThrow(() -> new RuntimeException("Error: Role is not found."));roles.add(userRole);} else {strRoles.forEach(role -> {switch (role) {case "admin":Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN).orElseThrow(() -> new RuntimeException("Error: Role is not found."));roles.add(adminRole);break;default:Role userRole = roleRepository.findByName(ERole.ROLE_USER).orElseThrow(() -> new RuntimeException("Error: Role is not found."));roles.add(userRole);}});}user.setRoles(roles);userRepository.save(user);return ResponseEntity.ok(new MessageResponse("User registered successfully!"));}
}
UserController.java
package com.example.javawebapi.controller;import com.example.javawebapi.entity.User;
import com.example.javawebapi.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;import java.util.List;@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/users")
public class UserController {@AutowiredUserRepository userRepository;@GetMapping@PreAuthorize("hasRole('ADMIN')")public List<User> getAllUsers() {return userRepository.findAll();}@GetMapping("/{id}")@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")public User getUserById(@PathVariable Long id) {return userRepository.findById(id).orElseThrow(() -> new RuntimeException("Error: User not found."));}@DeleteMapping("/{id}")@PreAuthorize("hasRole('ADMIN')")public String deleteUser(@PathVariable Long id) {userRepository.deleteById(id);return "User deleted successfully!";}
}
3.7 业务服务层 (Service)
UserService.java
package com.example.javawebapi.service;import com.example.javawebapi.entity.User;
import com.example.javawebapi.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.Optional;@Service
public class UserService {@Autowiredprivate UserRepository userRepository;public List<User> findAll() {return userRepository.findAll();}public Optional<User> findById(Long id) {return userRepository.findById(id);}public User save(User user) {return userRepository.save(user);}public void deleteById(Long id) {userRepository.deleteById(id);}public boolean existsByUsername(String username) {return userRepository.existsByUsername(username);}public boolean existsByEmail(String email) {return userRepository.existsByEmail(email);}
}
3.8 数据初始化
DataLoader.java
package com.example.javawebapi.config;import com.example.javawebapi.entity.ERole;
import com.example.javawebapi.entity.Role;
import com.example.javawebapi.repository.RoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;@Component
public class DataLoader implements CommandLineRunner {@Autowiredprivate RoleRepository roleRepository;@Overridepublic void run(String... args) throws Exception {// Create roles if they don't existif (roleRepository.findByName(ERole.ROLE_USER).isEmpty()) {roleRepository.save(new Role(ERole.ROLE_USER));}if (roleRepository.findByName(ERole.ROLE_ADMIN).isEmpty()) {roleRepository.save(new Role(ERole.ROLE_ADMIN));}}
}
3.9 定时任务
ScheduledTasks.java
package com.example.javawebapi.task;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;
import java.util.Date;@Component
public class ScheduledTasks {private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");// 每5分钟执行一次@Scheduled(fixedRate = 300000)public void reportCurrentTime() {log.info("定时任务执行 - 当前时间: {}", dateFormat.format(new Date()));}// 每天凌晨1点执行@Scheduled(cron = "0 0 1 * * ?")public void performDailyCleanup() {log.info("执行每日清理任务 - 当前时间: {}", dateFormat.format(new Date()));// 这里可以添加数据清理、统计等逻辑}// 每小时执行一次@Scheduled(cron = "0 0 * * * ?")public void performHourlyTask() {log.info("执行每小时任务 - 当前时间: {}", dateFormat.format(new Date()));// 这里可以添加缓存刷新、数据同步等逻辑}
}
3.10 全局异常处理
GlobalExceptionHandler.java
package com.example.javawebapi.exception;import com.example.javawebapi.payload.response.MessageResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;import java.util.HashMap;
import java.util.Map;@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<?> handleValidationExceptions(MethodArgumentNotValidException ex) {Map<String, String> errors = new HashMap<>();ex.getBindingResult().getAllErrors().forEach((error) -> {String fieldName = ((FieldError) error).getField();String errorMessage = error.getDefaultMessage();errors.put(fieldName, errorMessage);});return ResponseEntity.badRequest().body(errors);}@ExceptionHandler(RuntimeException.class)public ResponseEntity<?> handleRuntimeException(RuntimeException ex, WebRequest request) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new MessageResponse("Error: " + ex.getMessage()));}@ExceptionHandler(Exception.class)public ResponseEntity<?> globalExceptionHandler(Exception ex, WebRequest request) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new MessageResponse("An unexpected error occurred"));}
}
3.11 应用主类
JavaWebApiApplication.java
package com.example.javawebapi;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;@SpringBootApplication
@EnableScheduling
public class JavaWebApiApplication {public static void main(String[] args) {SpringApplication.run(JavaWebApiApplication.class, args);}
}
4. API 测试和使用
4.1 启动应用
mvn spring-boot:run
4.2 API 文档访问
- Swagger UI: http://localhost:8080/api/swagger-ui.html
- H2 数据库控制台: http://localhost:8080/api/h2-console
4.3 测试 API
注册用户:
curl -X POST http://localhost:8080/api/auth/signup \-H "Content-Type: application/json" \-d '{"username": "testuser","email": "test@example.com","password": "password123","role": ["user"]}'
登录获取token:
curl -X POST http://localhost:8080/api/auth/signin \-H "Content-Type: application/json" \-d '{"username": "testuser","password": "password123"}'
访问受保护接口:
curl -X GET http://localhost:8080/api/users \-H "Authorization: Bearer YOUR_JWT_TOKEN"
5. 核心原理总结
5.1 安全流程
- 用户登录 → 验证凭证 → 生成JWT
- 后续请求携带JWT → 过滤器验证 → 设置安全上下文
- 方法级权限控制 → @PreAuthorize注解
5.2 数据流程
- 控制器接收请求 → 数据验证
- 服务层处理业务逻辑
- 数据层访问数据库
- 返回响应DTO
5.3 定时任务原理
- @Scheduled注解标记定时方法
- Spring调度器基于线程池执行
- 支持fixedRate、cron表达式等