快学快用系列:一文学会java后端WebApi开发

作者:百锦再@新空间日期:10/2/2025

在这里插入图片描述

文章目录

    • 第一部分:Web API开发基础概念
      • 1.1 什么是Web API
        • 1.2 RESTful API设计原则
    • 第二部分:开发环境搭建
      • 2.1 环境要求
        • 2.2 创建Spring Boot项目
        • 2.3 配置文件
    • 第三部分:项目架构设计
      • 3.1 分层架构
        • 3.2 包结构设计
    • 第四部分:数据模型设计
      • 4.1 实体类设计
        • 4.2 DTO设计
    • 第五部分:数据访问层实现
      • 5.1 Repository接口
        • 5.2 自定义Repository实现
    • 第六部分:业务逻辑层实现
      • 6.1 Service接口设计
        • 6.2 Service实现类
    • 第七部分:控制器层实现
      • 7.1 基础控制器
        • 7.2 全局异常处理
    • 第八部分:安全配置
      • 8.1 Spring Security配置
        • 8.2 JWT认证配置
    • 第九部分:高级特性实现
      • 9.1 缓存配置
        • 9.2 异步处理
    • 第十部分:测试
      • 10.1 单元测试
        • 10.2 集成测试
    • 第十一部分:部署与监控
      • 11.1 Docker配置
        • 11.2 健康检查与监控
    • 第十二部分:最佳实践与总结
      • 12.1 API设计最佳实践
        • 12.2 性能优化建议
        • 12.3 安全考虑
        • 12.4 总结

在这里插入图片描述

第一部分:Web API开发基础概念

1.1 什么是Web API

Web API(Application Programming Interface)是一种允许不同软件系统之间进行通信的接口。在Web开发中,API通常基于HTTP协议,使用RESTful架构风格,通过URL端点提供数据和服务。

Web API的核心特点:

  • 基于HTTP/HTTPS协议
  • 返回结构化数据(JSON/XML)
  • 无状态通信
  • 跨平台兼容

1.2 RESTful API设计原则

REST(Representational State Transfer)是一种软件架构风格,包含以下核心原则:

  1. 统一接口:使用标准的HTTP方法和状态码
  2. 无状态:每个请求包含所有必要信息
  3. 可缓存:响应应标记为可缓存或不可缓存
  4. 分层系统:客户端不需要知道是否连接到最终服务器
  5. 按需代码:服务器可以临时扩展功能

第二部分:开发环境搭建

2.1 环境要求

必需工具:

  • JDK 8或以上版本
  • IDE(IntelliJ IDEA/Eclipse)
  • Maven 3.6+ 或 Gradle
  • MySQL/PostgreSQL数据库

2.2 创建Spring Boot项目

使用Spring Initializr创建项目:

1<!-- pom.xml -->
2<?xml version="1.0" encoding="UTF-8"?>
3<project xmlns="http://maven.apache.org/POM/4.0.0">
4    <modelVersion>4.0.0</modelVersion>
5    
6    <parent>
7        <groupId>org.springframework.boot</groupId>
8        <artifactId>spring-boot-starter-parent</artifactId>
9        <version>2.7.0</version>
10        <relativePath/>
11    </parent>
12    
13    <groupId>com.example</groupId>
14    <artifactId>webapi-demo</artifactId>
15    <version>1.0.0</version>
16    
17    <properties>
18        <java.version>11</java.version>
19    </properties>
20    
21    <dependencies>
22        <dependency>
23            <groupId>org.springframework.boot</groupId>
24            <artifactId>spring-boot-starter-web</artifactId>
25        </dependency>
26        <dependency>
27            <groupId>org.springframework.boot</groupId>
28            <artifactId>spring-boot-starter-data-jpa</artifactId>
29        </dependency>
30        <dependency>
31            <groupId>org.springframework.boot</groupId>
32            <artifactId>spring-boot-starter-validation</artifactId>
33        </dependency>
34        <dependency>
35            <groupId>mysql</groupId>
36            <artifactId>mysql-connector-java</artifactId>
37        </dependency>
38        <dependency>
39            <groupId>org.springframework.boot</groupId>
40            <artifactId>spring-boot-starter-test</artifactId>
41            <scope>test</scope>
42        </dependency>
43    </dependencies>
44</project>
45

2.3 配置文件

1# application.yml
2server:
3  port: 8080
4  servlet:
5    context-path: /api
6
7spring:
8  datasource:
9    url: jdbc:mysql://localhost:3306/webapi_db
10    username: root
11    password: password
12    driver-class-name: com.mysql.cj.jdbc.Driver
13  
14  jpa:
15    hibernate:
16      ddl-auto: update
17    show-sql: true
18    properties:
19      hibernate:
20        dialect: org.hibernate.dialect.MySQL8Dialect
21        format_sql: true
22
23logging:
24  level:
25    com.example: DEBUG
26    org.hibernate.SQL: DEBUG
27

第三部分:项目架构设计

3.1 分层架构

典型的Java Web API采用分层架构:

Controller层 (API接口)
    ↓
Service层 (业务逻辑)
    ↓
Repository层 (数据访问)
    ↓
Model层 (数据模型)

3.2 包结构设计

src/main/java/com/example/webapi/
├── config/          # 配置类
├── controller/      # 控制器
├── service/         # 业务逻辑
├── repository/      # 数据访问
├── model/           # 数据模型
│   ├── entity/      # 实体类
│   ├── dto/         # 数据传输对象
│   └── vo/          # 视图对象
├── exception/       # 异常处理
└── util/            # 工具类

第四部分:数据模型设计

4.1 实体类设计

1// User.java
2package com.example.webapi.model.entity;
3
4import javax.persistence.*;
5import javax.validation.constraints.*;
6import java.time.LocalDateTime;
7import java.util.List;
8
9@Entity
10@Table(name = "users")
11public class User {
12    @Id
13    @GeneratedValue(strategy = GenerationType.IDENTITY)
14    private Long id;
15    
16    @NotBlank(message = "用户名不能为空")
17    @Size(min = 3, max = 50, message = "用户名长度必须在3-50字符之间")
18    @Column(unique = true, nullable = false)
19    private String username;
20    
21    @Email(message = "邮箱格式不正确")
22    @Column(unique = true, nullable = false)
23    private String email;
24    
25    @NotBlank(message = "密码不能为空")
26    @Size(min = 6, message = "密码长度至少6位")
27    private String password;
28    
29    private String phone;
30    
31    @Enumerated(EnumType.STRING)
32    private UserStatus status = UserStatus.ACTIVE;
33    
34    @Column(name = "created_at")
35    private LocalDateTime createdAt;
36    
37    @Column(name = "updated_at")
38    private LocalDateTime updatedAt;
39    
40    // 构造方法
41    public User() {
42        this.createdAt = LocalDateTime.now();
43        this.updatedAt = LocalDateTime.now();
44    }
45    
46    // Getter和Setter方法
47    // ... 省略具体实现
48}
49
50enum UserStatus {
51    ACTIVE, INACTIVE, DELETED
52}
53

4.2 DTO设计

1// UserDTO.java
2package com.example.webapi.model.dto;
3
4import javax.validation.constraints.*;
5import java.time.LocalDateTime;
6
7public class UserDTO {
8    private Long id;
9    
10    @NotBlank(message = "用户名不能为空")
11    private String username;
12    
13    @Email(message = "邮箱格式不正确")
14    private String email;
15    
16    private String phone;
17    
18    private LocalDateTime createdAt;
19    
20    // 构造方法
21    public UserDTO() {}
22    
23    // Getter和Setter
24    // ... 省略具体实现
25}
26
27// CreateUserRequest.java
28package com.example.webapi.model.dto;
29
30import javax.validation.constraints.*;
31
32public class CreateUserRequest {
33    @NotBlank(message = "用户名不能为空")
34    @Size(min = 3, max = 50)
35    private String username;
36    
37    @Email
38    @NotBlank
39    private String email;
40    
41    @NotBlank
42    @Size(min = 6)
43    private String password;
44    
45    private String phone;
46    
47    // Getter和Setter
48    // ... 省略具体实现
49}
50

第五部分:数据访问层实现

5.1 Repository接口

1// UserRepository.java
2package com.example.webapi.repository;
3
4import com.example.webapi.model.entity.User;
5import com.example.webapi.model.entity.UserStatus;
6import org.springframework.data.jpa.repository.JpaRepository;
7import org.springframework.data.jpa.repository.Query;
8import org.springframework.data.repository.query.Param;
9import org.springframework.stereotype.Repository;
10
11import java.util.List;
12import java.util.Optional;
13
14@Repository
15public interface UserRepository extends JpaRepository<User, Long> {
16    
17    Optional<User> findByUsername(String username);
18    
19    Optional<User> findByEmail(String email);
20    
21    List<User> findByStatus(UserStatus status);
22    
23    boolean existsByUsername(String username);
24    
25    boolean existsByEmail(String email);
26    
27    @Query("SELECT u FROM User u WHERE u.email LIKE %:email%")
28    List<User> findByEmailContaining(@Param("email") String email);
29    
30    @Query("SELECT u FROM User u WHERE u.createdAt >= :startDate AND u.createdAt < :endDate")
31    List<User> findUsersByCreateTimeRange(@Param("startDate") LocalDateTime startDate, 
32                                         @Param("endDate") LocalDateTime endDate);
33}
34

5.2 自定义Repository实现

1// UserRepositoryCustom.java
2package com.example.webapi.repository;
3
4import com.example.webapi.model.entity.User;
5import org.springframework.data.domain.Page;
6import org.springframework.data.domain.Pageable;
7
8import java.util.List;
9
10public interface UserRepositoryCustom {
11    Page<User> findUsersWithPagination(String keyword, Pageable pageable);
12    
13    List<User> findActiveUsersWithRecentActivity();
14}
15
16// UserRepositoryCustomImpl.java
17package com.example.webapi.repository;
18
19import com.example.webapi.model.entity.User;
20import com.example.webapi.model.entity.UserStatus;
21import org.springframework.data.domain.Page;
22import org.springframework.data.domain.PageImpl;
23import org.springframework.data.domain.Pageable;
24import org.springframework.stereotype.Repository;
25
26import javax.persistence.EntityManager;
27import javax.persistence.PersistenceContext;
28import javax.persistence.TypedQuery;
29import java.time.LocalDateTime;
30import java.util.List;
31
32@Repository
33public class UserRepositoryCustomImpl implements UserRepositoryCustom {
34    
35    @PersistenceContext
36    private EntityManager entityManager;
37    
38    @Override
39    public Page<User> findUsersWithPagination(String keyword, Pageable pageable) {
40        String countQueryStr = "SELECT COUNT(u) FROM User u WHERE " +
41                "(u.username LIKE :keyword OR u.email LIKE :keyword) AND u.status = 'ACTIVE'";
42        
43        TypedQuery<Long> countQuery = entityManager.createQuery(countQueryStr, Long.class);
44        countQuery.setParameter("keyword", "%" + keyword + "%");
45        
46        Long total = countQuery.getSingleResult();
47        
48        String queryStr = "SELECT u FROM User u WHERE " +
49                "(u.username LIKE :keyword OR u.email LIKE :keyword) AND u.status = 'ACTIVE' " +
50                "ORDER BY u.createdAt DESC";
51        
52        TypedQuery<User> query = entityManager.createQuery(queryStr, User.class);
53        query.setParameter("keyword", "%" + keyword + "%");
54        query.setFirstResult((int) pageable.getOffset());
55        query.setMaxResults(pageable.getPageSize());
56        
57        List<User> users = query.getResultList();
58        
59        return new PageImpl<>(users, pageable, total);
60    }
61    
62    @Override
63    public List<User> findActiveUsersWithRecentActivity() {
64        String queryStr = "SELECT u FROM User u WHERE u.status = 'ACTIVE' " +
65                "AND u.updatedAt >= :recentTime";
66        
67        return entityManager.createQuery(queryStr, User.class)
68                .setParameter("recentTime", LocalDateTime.now().minusDays(7))
69                .getResultList();
70    }
71}
72

第六部分:业务逻辑层实现

6.1 Service接口设计

1// UserService.java
2package com.example.webapi.service;
3
4import com.example.webapi.model.dto.CreateUserRequest;
5import com.example.webapi.model.dto.UpdateUserRequest;
6import com.example.webapi.model.dto.UserDTO;
7import org.springframework.data.domain.Page;
8import org.springframework.data.domain.Pageable;
9
10import java.util.List;
11
12public interface UserService {
13    
14    UserDTO createUser(CreateUserRequest request);
15    
16    UserDTO getUserById(Long id);
17    
18    UserDTO getUserByUsername(String username);
19    
20    Page<UserDTO> getAllUsers(Pageable pageable);
21    
22    List<UserDTO> searchUsers(String keyword);
23    
24    UserDTO updateUser(Long id, UpdateUserRequest request);
25    
26    void deleteUser(Long id);
27    
28    boolean existsByUsername(String username);
29    
30    boolean existsByEmail(String email);
31}
32

6.2 Service实现类

1// UserServiceImpl.java
2package com.example.webapi.service.impl;
3
4import com.example.webapi.model.dto.CreateUserRequest;
5import com.example.webapi.model.dto.UpdateUserRequest;
6import com.example.webapi.model.dto.UserDTO;
7import com.example.webapi.model.entity.User;
8import com.example.webapi.model.entity.UserStatus;
9import com.example.webapi.repository.UserRepository;
10import com.example.webapi.service.UserService;
11import org.springframework.beans.factory.annotation.Autowired;
12import org.springframework.data.domain.Page;
13import org.springframework.data.domain.Pageable;
14import org.springframework.security.crypto.password.PasswordEncoder;
15import org.springframework.stereotype.Service;
16import org.springframework.transaction.annotation.Transactional;
17
18import java.util.List;
19import java.util.stream.Collectors;
20
21@Service
22@Transactional
23public class UserServiceImpl implements UserService {
24    
25    @Autowired
26    private UserRepository userRepository;
27    
28    @Autowired
29    private PasswordEncoder passwordEncoder;
30    
31    @Override
32    public UserDTO createUser(CreateUserRequest request) {
33        // 检查用户名和邮箱是否已存在
34        if (userRepository.existsByUsername(request.getUsername())) {
35            throw new RuntimeException("用户名已存在");
36        }
37        
38        if (userRepository.existsByEmail(request.getEmail())) {
39            throw new RuntimeException("邮箱已存在");
40        }
41        
42        // 创建用户实体
43        User user = new User();
44        user.setUsername(request.getUsername());
45        user.setEmail(request.getEmail());
46        user.setPassword(passwordEncoder.encode(request.getPassword()));
47        user.setPhone(request.getPhone());
48        user.setStatus(UserStatus.ACTIVE);
49        
50        User savedUser = userRepository.save(user);
51        
52        return convertToDTO(savedUser);
53    }
54    
55    @Override
56    @Transactional(readOnly = true)
57    public UserDTO getUserById(Long id) {
58        User user = userRepository.findById(id)
59                .orElseThrow(() -> new RuntimeException("用户不存在"));
60        
61        return convertToDTO(user);
62    }
63    
64    @Override
65    @Transactional(readOnly = true)
66    public UserDTO getUserByUsername(String username) {
67        User user = userRepository.findByUsername(username)
68                .orElseThrow(() -> new RuntimeException("用户不存在"));
69        
70        return convertToDTO(user);
71    }
72    
73    @Override
74    @Transactional(readOnly = true)
75    public Page<UserDTO> getAllUsers(Pageable pageable) {
76        return userRepository.findAll(pageable)
77                .map(this::convertToDTO);
78    }
79    
80    @Override
81    @Transactional(readOnly = true)
82    public List<UserDTO> searchUsers(String keyword) {
83        return userRepository.findByEmailContaining(keyword).stream()
84                .map(this::convertToDTO)
85                .collect(Collectors.toList());
86    }
87    
88    @Override
89    public UserDTO updateUser(Long id, UpdateUserRequest request) {
90        User user = userRepository.findById(id)
91                .orElseThrow(() -> new RuntimeException("用户不存在"));
92        
93        // 更新用户信息
94        if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) {
95            if (userRepository.existsByEmail(request.getEmail())) {
96                throw new RuntimeException("邮箱已存在");
97            }
98            user.setEmail(request.getEmail());
99        }
100        
101        if (request.getPhone() != null) {
102            user.setPhone(request.getPhone());
103        }
104        
105        User updatedUser = userRepository.save(user);
106        
107        return convertToDTO(updatedUser);
108    }
109    
110    @Override
111    public void deleteUser(Long id) {
112        User user = userRepository.findById(id)
113                .orElseThrow(() -> new RuntimeException("用户不存在"));
114        
115        user.setStatus(UserStatus.DELETED);
116        userRepository.save(user);
117    }
118    
119    @Override
120    @Transactional(readOnly = true)
121    public boolean existsByUsername(String username) {
122        return userRepository.existsByUsername(username);
123    }
124    
125    @Override
126    @Transactional(readOnly = true)
127    public boolean existsByEmail(String email) {
128        return userRepository.existsByEmail(email);
129    }
130    
131    // 转换实体为DTO
132    private UserDTO convertToDTO(User user) {
133        UserDTO dto = new UserDTO();
134        dto.setId(user.getId());
135        dto.setUsername(user.getUsername());
136        dto.setEmail(user.getEmail());
137        dto.setPhone(user.getPhone());
138        dto.setCreatedAt(user.getCreatedAt());
139        return dto;
140    }
141}
142

第七部分:控制器层实现

7.1 基础控制器

1// UserController.java
2package com.example.webapi.controller;
3
4import com.example.webapi.model.dto.CreateUserRequest;
5import com.example.webapi.model.dto.UpdateUserRequest;
6import com.example.webapi.model.dto.UserDTO;
7import com.example.webapi.service.UserService;
8import org.springframework.beans.factory.annotation.Autowired;
9import org.springframework.data.domain.Page;
10import org.springframework.data.domain.PageRequest;
11import org.springframework.data.domain.Pageable;
12import org.springframework.data.domain.Sort;
13import org.springframework.http.HttpStatus;
14import org.springframework.http.ResponseEntity;
15import org.springframework.validation.annotation.Validated;
16import org.springframework.web.bind.annotation.*;
17
18import javax.validation.Valid;
19import java.util.HashMap;
20import java.util.List;
21import java.util.Map;
22
23@RestController
24@RequestMapping("/users")
25@Validated
26public class UserController {
27    
28    @Autowired
29    private UserService userService;
30    
31    @PostMapping
32    public ResponseEntity<?> createUser(@Valid @RequestBody CreateUserRequest request) {
33        try {
34            UserDTO user = userService.createUser(request);
35            return ResponseEntity.status(HttpStatus.CREATED).body(
36                    createSuccessResponse("用户创建成功", user)
37            );
38        } catch (RuntimeException e) {
39            return ResponseEntity.badRequest().body(
40                    createErrorResponse(e.getMessage())
41            );
42        }
43    }
44    
45    @GetMapping("/{id}")
46    public ResponseEntity<?> getUserById(@PathVariable Long id) {
47        try {
48            UserDTO user = userService.getUserById(id);
49            return ResponseEntity.ok(createSuccessResponse("获取用户成功", user));
50        } catch (RuntimeException e) {
51            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(
52                    createErrorResponse(e.getMessage())
53            );
54        }
55    }
56    
57    @GetMapping
58    public ResponseEntity<?> getAllUsers(
59            @RequestParam(defaultValue = "0") int page,
60            @RequestParam(defaultValue = "10") int size,
61            @RequestParam(defaultValue = "createdAt") String sort) {
62        
63        Pageable pageable = PageRequest.of(page, size, Sort.by(sort).descending());
64        Page<UserDTO> users = userService.getAllUsers(pageable);
65        
66        Map<String, Object> response = new HashMap<>();
67        response.put("success", true);
68        response.put("message", "获取用户列表成功");
69        response.put("data", users.getContent());
70        response.put("currentPage", users.getNumber());
71        response.put("totalItems", users.getTotalElements());
72        response.put("totalPages", users.getTotalPages());
73        
74        return ResponseEntity.ok(response);
75    }
76    
77    @GetMapping("/search")
78    public ResponseEntity<?> searchUsers(@RequestParam String keyword) {
79        List<UserDTO> users = userService.searchUsers(keyword);
80        
81        return ResponseEntity.ok(createSuccessResponse("搜索用户成功", users));
82    }
83    
84    @PutMapping("/{id}")
85    public ResponseEntity<?> updateUser(
86            @PathVariable Long id, 
87            @Valid @RequestBody UpdateUserRequest request) {
88        
89        try {
90            UserDTO user = userService.updateUser(id, request);
91            return ResponseEntity.ok(createSuccessResponse("用户更新成功", user));
92        } catch (RuntimeException e) {
93            return ResponseEntity.badRequest().body(
94                    createErrorResponse(e.getMessage())
95            );
96        }
97    }
98    
99    @DeleteMapping("/{id}")
100    public ResponseEntity<?> deleteUser(@PathVariable Long id) {
101        try {
102            userService.deleteUser(id);
103            return ResponseEntity.ok(createSuccessResponse("用户删除成功", null));
104        } catch (RuntimeException e) {
105            return ResponseEntity.badRequest().body(
106                    createErrorResponse(e.getMessage())
107            );
108        }
109    }
110    
111    // 工具方法:创建成功响应
112    private Map<String, Object> createSuccessResponse(String message, Object data) {
113        Map<String, Object> response = new HashMap<>();
114        response.put("success", true);
115        response.put("message", message);
116        response.put("data", data);
117        response.put("timestamp", System.currentTimeMillis());
118        return response;
119    }
120    
121    // 工具方法:创建错误响应
122    private Map<String, Object> createErrorResponse(String message) {
123        Map<String, Object> response = new HashMap<>();
124        response.put("success", false);
125        response.put("message", message);
126        response.put("timestamp", System.currentTimeMillis());
127        return response;
128    }
129}
130

7.2 全局异常处理

1// GlobalExceptionHandler.java
2package com.example.webapi.exception;
3
4import org.springframework.http.HttpStatus;
5import org.springframework.http.ResponseEntity;
6import org.springframework.validation.FieldError;
7import org.springframework.web.bind.MethodArgumentNotValidException;
8import org.springframework.web.bind.annotation.ExceptionHandler;
9import org.springframework.web.bind.annotation.RestControllerAdvice;
10
11import javax.servlet.http.HttpServletRequest;
12import java.util.HashMap;
13import java.util.Map;
14
15@RestControllerAdvice
16public class GlobalExceptionHandler {
17    
18    @ExceptionHandler(MethodArgumentNotValidException.class)
19    public ResponseEntity<?> handleValidationExceptions(
20            MethodArgumentNotValidException ex, HttpServletRequest request) {
21        
22        Map<String, String> errors = new HashMap<>();
23        ex.getBindingResult().getAllErrors().forEach((error) -> {
24            String fieldName = ((FieldError) error).getField();
25            String errorMessage = error.getDefaultMessage();
26            errors.put(fieldName, errorMessage);
27        });
28        
29        Map<String, Object> response = new HashMap<>();
30        response.put("success", false);
31        response.put("message", "参数验证失败");
32        response.put("errors", errors);
33        response.put("path", request.getRequestURI());
34        response.put("timestamp", System.currentTimeMillis());
35        
36        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
37    }
38    
39    @ExceptionHandler(RuntimeException.class)
40    public ResponseEntity<?> handleRuntimeException(
41            RuntimeException ex, HttpServletRequest request) {
42        
43        Map<String, Object> response = new HashMap<>();
44        response.put("success", false);
45        response.put("message", ex.getMessage());
46        response.put("path", request.getRequestURI());
47        response.put("timestamp", System.currentTimeMillis());
48        
49        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
50    }
51    
52    @ExceptionHandler(Exception.class)
53    public ResponseEntity<?> handleGlobalException(
54            Exception ex, HttpServletRequest request) {
55        
56        Map<String, Object> response = new HashMap<>();
57        response.put("success", false);
58        response.put("message", "服务器内部错误");
59        response.put("path", request.getRequestURI());
60        response.put("timestamp", System.currentTimeMillis());
61        
62        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
63    }
64}
65

第八部分:安全配置

8.1 Spring Security配置

1// SecurityConfig.java
2package com.example.webapi.config;
3
4import org.springframework.beans.factory.annotation.Autowired;
5import org.springframework.context.annotation.Bean;
6import org.springframework.context.annotation.Configuration;
7import org.springframework.security.config.annotation.web.builders.HttpSecurity;
8import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
9import org.springframework.security.config.http.SessionCreationPolicy;
10import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
11import org.springframework.security.crypto.password.PasswordEncoder;
12import org.springframework.security.web.SecurityFilterChain;
13
14@Configuration
15@EnableWebSecurity
16public class SecurityConfig {
17    
18    @Bean
19    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
20        http
21            .cors().and()
22            .csrf().disable()
23            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
24            .and()
25            .authorizeRequests()
26            .antMatchers("/api/auth/**").permitAll()
27            .antMatchers("/api/users/create").permitAll()
28            .antMatchers("/api/public/**").permitAll()
29            .anyRequest().authenticated();
30        
31        return http.build();
32    }
33    
34    @Bean
35    public PasswordEncoder passwordEncoder() {
36        return new BCryptPasswordEncoder();
37    }
38}
39

8.2 JWT认证配置

1// JwtUtils.java
2package com.example.webapi.util;
3
4import io.jsonwebtoken.*;
5import org.springframework.beans.factory.annotation.Value;
6import org.springframework.stereotype.Component;
7
8import java.util.Date;
9
10@Component
11public class JwtUtils {
12    
13    @Value("${app.jwt.secret}")
14    private String jwtSecret;
15    
16    @Value("${app.jwt.expiration}")
17    private int jwtExpirationMs;
18    
19    public String generateJwtToken(String username) {
20        return Jwts.builder()
21                .setSubject(username)
22                .setIssuedAt(new Date())
23                .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
24                .signWith(SignatureAlgorithm.HS512, jwtSecret)
25                .compact();
26    }
27    
28    public String getUserNameFromJwtToken(String token) {
29        return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
30    }
31    
32    public boolean validateJwtToken(String authToken) {
33        try {
34            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
35            return true;
36        } catch (SignatureException e) {
37            // 日志记录
38        } catch (MalformedJwtException e) {
39            // 日志记录
40        } catch (ExpiredJwtException e) {
41            // 日志记录
42        } catch (UnsupportedJwtException e) {
43            // 日志记录
44        } catch (IllegalArgumentException e) {
45            // 日志记录
46        }
47        return false;
48    }
49}
50

第九部分:高级特性实现

9.1 缓存配置

1// CacheConfig.java
2package com.example.webapi.config;
3
4import org.springframework.cache.CacheManager;
5import org.springframework.cache.annotation.EnableCaching;
6import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
7import org.springframework.context.annotation.Bean;
8import org.springframework.context.annotation.Configuration;
9
10import java.util.Arrays;
11
12@Configuration
13@EnableCaching
14public class CacheConfig {
15    
16    @Bean
17    public CacheManager cacheManager() {
18        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
19        cacheManager.setCacheNames(Arrays.asList("users", "products"));
20        return cacheManager;
21    }
22}
23
24// 在Service中使用缓存
25@Service
26public class UserServiceImpl implements UserService {
27    
28    @Cacheable(value = "users", key = "#id")
29    @Override
30    public UserDTO getUserById(Long id) {
31        // 从数据库获取用户
32    }
33    
34    @CacheEvict(value = "users", key = "#id")
35    @Override
36    public UserDTO updateUser(Long id, UpdateUserRequest request) {
37        // 更新用户
38    }
39}
40

9.2 异步处理

1// AsyncConfig.java
2package com.example.webapi.config;
3
4import org.springframework.context.annotation.Configuration;
5import org.springframework.scheduling.annotation.EnableAsync;
6import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
7
8import java.util.concurrent.Executor;
9
10@Configuration
11@EnableAsync
12public class AsyncConfig {
13    
14    @Bean(name = "taskExecutor")
15    public Executor taskExecutor() {
16        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
17        executor.setCorePoolSize(5);
18        executor.setMaxPoolSize(10);
19        executor.setQueueCapacity(100);
20        executor.setThreadNamePrefix("AsyncThread-");
21        executor.initialize();
22        return executor;
23    }
24}
25
26// 异步服务
27@Service
28public class EmailService {
29    
30    @Async("taskExecutor")
31    public void sendWelcomeEmail(String email, String username) {
32        // 发送邮件的逻辑
33        try {
34            Thread.sleep(5000); // 模拟耗时操作
35            System.out.println("欢迎邮件已发送至: " + email);
36        } catch (InterruptedException e) {
37            Thread.currentThread().interrupt();
38        }
39    }
40}
41

第十部分:测试

10.1 单元测试

1// UserServiceTest.java
2package com.example.webapi.service;
3
4import com.example.webapi.model.dto.CreateUserRequest;
5import com.example.webapi.model.dto.UserDTO;
6import com.example.webapi.model.entity.User;
7import com.example.webapi.repository.UserRepository;
8import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import org.junit.jupiter.api.extension.ExtendWith;
11import org.mockito.InjectMocks;
12import org.mockito.Mock;
13import org.mockito.junit.jupiter.MockitoExtension;
14import org.springframework.security.crypto.password.PasswordEncoder;
15
16import java.util.Optional;
17
18import static org.junit.jupiter.api.Assertions.*;
19import static org.mockito.ArgumentMatchers.any;
20import static org.mockito.Mockito.*;
21
22@ExtendWith(MockitoExtension.class)
23class UserServiceTest {
24    
25    @Mock
26    private UserRepository userRepository;
27    
28    @Mock
29    private PasswordEncoder passwordEncoder;
30    
31    @InjectMocks
32    private UserServiceImpl userService;
33    
34    private CreateUserRequest createUserRequest;
35    
36    @BeforeEach
37    void setUp() {
38        createUserRequest = new CreateUserRequest();
39        createUserRequest.setUsername("testuser");
40        createUserRequest.setEmail("[email protected]");
41        createUserRequest.setPassword("password123");
42        createUserRequest.setPhone("13800138000");
43    }
44    
45    @Test
46    void createUser_Success() {
47        // 准备
48        when(userRepository.existsByUsername("testuser")).thenReturn(false);
49        when(userRepository.existsByEmail("[email protected]")).thenReturn(false);
50        when(passwordEncoder.encode("password123")).thenReturn("encodedPassword");
51        
52        User savedUser = new User();
53        savedUser.setId(1L);
54        savedUser.setUsername("testuser");
55        savedUser.setEmail("[email protected]");
56        when(userRepository.save(any(User.class))).thenReturn(savedUser);
57        
58        // 执行
59        UserDTO result = userService.createUser(createUserRequest);
60        
61        // 验证
62        assertNotNull(result);
63        assertEquals(1L, result.getId());
64        assertEquals("testuser", result.getUsername());
65        assertEquals("[email protected]", result.getEmail());
66        
67        verify(userRepository, times(1)).save(any(User.class));
68    }
69    
70    @Test
71    void getUserById_UserExists() {
72        // 准备
73        User user = new User();
74        user.setId(1L);
75        user.setUsername("testuser");
76        user.setEmail("[email protected]");
77        
78        when(userRepository.findById(1L)).thenReturn(Optional.of(user));
79        
80        // 执行
81        UserDTO result = userService.getUserById(1L);
82        
83        // 验证
84        assertNotNull(result);
85        assertEquals(1L, result.getId());
86        assertEquals("testuser", result.getUsername());
87    }
88}
89

10.2 集成测试

1// UserControllerIntegrationTest.java
2package com.example.webapi.controller;
3
4import com.example.webapi.model.dto.CreateUserRequest;
5import com.example.webapi.repository.UserRepository;
6import com.fasterxml.jackson.databind.ObjectMapper;
7import org.junit.jupiter.api.Test;
8import org.springframework.beans.factory.annotation.Autowired;
9import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
10import org.springframework.boot.test.context.SpringBootTest;
11import org.springframework.http.MediaType;
12import org.springframework.test.web.servlet.MockMvc;
13import org.springframework.transaction.annotation.Transactional;
14
15import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
16import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
17
18@SpringBootTest
19@AutoConfigureMockMvc
20@Transactional
21class UserControllerIntegrationTest {
22    
23    @Autowired
24    private MockMvc mockMvc;
25    
26    @Autowired
27    private ObjectMapper objectMapper;
28    
29    @Autowired
30    private UserRepository userRepository;
31    
32    @Test
33    void createUser_ValidRequest_ReturnsCreated() throws Exception {
34        CreateUserRequest request = new CreateUserRequest();
35        request.setUsername("integrationtest");
36        request.setEmail("[email protected]");
37        request.setPassword("password123");
38        
39        mockMvc.perform(post("/api/users")
40                .contentType(MediaType.APPLICATION_JSON)
41                .content(objectMapper.writeValueAsString(request)))
42                .andExpect(status().isCreated())
43                .andExpect(jsonPath("$.success").value(true))
44                .andExpect(jsonPath("$.data.username").value("integrationtest"));
45    }
46    
47    @Test
48    void getUserById_UserExists_ReturnsUser() throws Exception {
49        // 先创建用户
50        CreateUserRequest request = new CreateUserRequest();
51        request.setUsername("testuser");
52        request.setEmail("[email protected]");
53        request.setPassword("password123");
54        
55        String response = mockMvc.perform(post("/api/users")
56                .contentType(MediaType.APPLICATION_JSON)
57                .content(objectMapper.writeValueAsString(request)))
58                .andReturn().getResponse().getContentAsString();
59        
60        // 提取用户ID并查询
61        // 这里简化处理,实际应该解析响应获取ID
62        mockMvc.perform(get("/api/users/1"))
63                .andExpect(status().isOk())
64                .andExpect(jsonPath("$.success").value(true));
65    }
66}
67

第十一部分:部署与监控

11.1 Docker配置

1# Dockerfile
2FROM openjdk:11-jre-slim
3
4WORKDIR /app
5
6COPY target/webapi-demo-1.0.0.jar app.jar
7
8RUN sh -c 'touch /app.jar'
9
10ENV JAVA_OPTS=""
11
12EXPOSE 8080
13
14ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
15
1# docker-compose.yml
2version: '3.8'
3
4services:
5  webapi:
6    build: .
7    ports:
8      - "8080:8080"
9    environment:
10      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/webapi_db
11      - SPRING_DATASOURCE_USERNAME=root
12      - SPRING_DATASOURCE_PASSWORD=password
13    depends_on:
14      - mysql
15
16  mysql:
17    image: mysql:8.0
18    environment:
19      - MYSQL_ROOT_PASSWORD=password
20      - MYSQL_DATABASE=webapi_db
21    ports:
22      - "3306:3306"
23    volumes:
24      - mysql_data:/var/lib/mysql
25
26volumes:
27  mysql_data:
28

11.2 健康检查与监控

1// HealthCheckController.java
2package com.example.webapi.controller;
3
4import org.springframework.beans.factory.annotation.Autowired;
5import org.springframework.boot.actuate.health.HealthComponent;
6import org.springframework.boot.actuate.health.HealthEndpoint;
7import org.springframework.jdbc.core.JdbcTemplate;
8import org.springframework.web.bind.annotation.GetMapping;
9import org.springframework.web.bind.annotation.RequestMapping;
10import org.springframework.web.bind.annotation.RestController;
11
12import java.util.HashMap;
13import java.util.Map;
14
15@RestController
16@RequestMapping("/health")
17public class HealthCheckController {
18    
19    @Autowired
20    private JdbcTemplate jdbcTemplate;
21    
22    @Autowired
23    private HealthEndpoint healthEndpoint;
24    
25    @GetMapping
26    public Map<String, Object> healthCheck() {
27        Map<String, Object> health = new HashMap<>();
28        
29        // 数据库健康检查
30        try {
31            jdbcTemplate.execute("SELECT 1");
32            health.put("database", "UP");
33        } catch (Exception e) {
34            health.put("database", "DOWN");
35        }
36        
37        // 系统健康检查
38        HealthComponent systemHealth = healthEndpoint.health();
39        health.put("status", systemHealth.getStatus().getCode());
40        health.put("timestamp", System.currentTimeMillis());
41        
42        return health;
43    }
44}
45

第十二部分:最佳实践与总结

12.1 API设计最佳实践

  1. 使用合适的HTTP状态码
    • 200: 成功
    • 201: 创建成功
    • 400: 客户端错误
    • 401: 未授权
    • 403: 禁止访问
    • 404: 资源不存在
    • 500: 服务器错误
  2. 统一的响应格式
1{
2  "success": true,
3  "message": "操作成功",
4  "data": {},
5  "timestamp": 1640995200000
6}
7
  1. 版本控制
    • URL路径版本: /api/v1/users
    • 请求头版本: Accept: application/vnd.example.v1+json
  2. 分页和过滤
    • GET /api/users?page=0&size=10&sort=createdAt,desc
    • GET /api/users?name=john&email=example.com

12.2 性能优化建议

  1. 数据库优化
    • 合理使用索引
    • 避免N+1查询问题
    • 使用连接查询替代多次查询
  2. 缓存策略
    • 使用Redis进行会话存储
    • 缓存热点数据
    • 设置合理的缓存过期时间
  3. 异步处理
    • 使用消息队列处理耗时操作
    • 异步发送邮件和通知
    • 后台任务处理

12.3 安全考虑

  1. 输入验证
    • 使用Bean Validation注解
    • 防范SQL注入
    • XSS防护
  2. 认证授权
    • 使用JWT进行无状态认证
    • 基于角色的访问控制
    • API密钥管理
  3. 其他安全措施
    • HTTPS强制使用
    • 定期更新依赖
    • 安全头部配置

12.4 总结

通过本文的详细讲解,您应该已经掌握了Java后端Web API开发的全流程。从环境搭建、项目架构设计,到具体的编码实现和测试部署,我们覆盖了开发一个完整Web API项目所需的所有关键知识点。

核心要点回顾:

  • 采用分层架构,保持代码清晰和可维护性
  • 使用Spring Boot快速开发,减少配置工作
  • 实现完整的CRUD操作和业务逻辑
  • 添加适当的异常处理和日志记录
  • 编写全面的测试用例
  • 考虑安全性和性能优化

在实际项目开发中,还需要根据具体需求不断调整和优化架构设计,同时关注代码质量、团队协作和持续集成等工程实践。希望本文能为您的Java Web API开发之旅提供有力的帮助!


快学快用系列:一文学会java后端WebApi开发》 是转载文章,点击查看原文


上一篇:ASCII 码表

下一篇:链表转置算法

相关推荐


基于PyTorch深度学习遥感影像地物分类与目标检测、分割及遥感影像问题深度学习优化实践技术应用
AAIshangyanxiu10/1/2025

我国高分辨率对地观测系统重大专项已全面启动,高空间、高光谱、高时间分辨率和宽地面覆盖于一体的全球天空地一体化立体对地观测网逐步形成,将成为保障国家安全的基础性和战略性资源。未来10年全球每天获取的观测数据将超过10PB,遥感大数据时代已然来临。随着小卫星星座的普及,对地观测已具备3次以上的全球覆盖能力,


【Vue实现跳转页面】功能 - 总结
这是个栗子9/30/2025

在 Vue.js 中实现跳转到登录页是常见的需求,通常用于用户未登录或token 失效等场景。


阿里开源 Java 诊断神器Arthas
讓丄帝愛伱2025/10/2

支持 JDK6+,零侵入,通过 Attach 机制连接 JVM,无需重启服务即可定位问题。 CLI 支持 Tab 自动补全,并提供 Web Console。 Github | 官网文档 一、核心价值 线上问题快速定位:CPU 飙升、内存泄漏、线程阻塞动态反编译验证代码:jad 命令方法级性能分析:耗时、调用频次、异常统计热更新/日志修改:无需重启即可修改代码或日志格式 优势: 零侵入全功能 CLI多环境支持(Linux/Mac/Windows + JDK6+) 二、安装与


数模之路获奖总结——数据分析交流(R语言)
统计学小王子2025/10/2

目录 0、引言1、主要参赛类型2、涉及领域汇总2.1、 数据科学与人工智能前沿应用2.2、 社会经济与公共政策研究2.3、 医疗卫生与生物制药2.4、 能源环境与可持续发展2.5、工程技术与运筹优化2.6、 计算与通信基础设施2.7、 其他特色领域2.8、总结 3、主要比赛获奖总结4、写在最后的话 0、引言 自2018年1月起,开始跟着学校由徐老师负责的培训老师团队了解、入门和学习数学建模并通过选拔拿到第一张国赛入场券。时至今日(2025年9月27),已经关注和参加了大大小小的建模


VUE3+element plus 实现表格行合并
rggrgerj2025/10/3

基础实现方法 通过给el-table传入span-method方法可以实现合并行或列,该方法的参数包含当前行row、当前列column、当前行号rowIndex和当前列号columnIndex四个属性15。该方法可以返回包含rowspan和colspan的数组或对象,例如: javascriptCopy Code const spanMethod = ({ row, column, rowIndex, columnIndex }) => { if (columnIndex === 0


为什么 Vue 组件中的 data 必须是一个函数?(含 Vue2/3 对比)
excel2025/10/5

在 Vue 面试或日常开发中,经常会被问到这样一个问题:为什么组件中的 data 必须是一个函数,而根实例的 data 可以是对象或函数? 本文将从 实例与组件的区别、数据污染问题、源码实现原理,以及 Vue2/3 的差异 四个角度进行深入分析。 一、实例与组件定义 data 的区别 在 Vue 根实例 中,data 属性既可以是对象,也可以是函数: // 对象格式 const app = new Vue({ el: "#app", data: { foo: "foo" }


【微服务】SpringBoot + Docker 实现微服务容器多节点负载均衡详解
小码农叔叔2025/10/6

目录 一、前言 二、前置准备 2.1 基本环境 2.2 准备一个springboot工程 2.2.1 准备几个测试接口 2.3 准备Dockerfile文件 2.4 打包上传到服务器 三、制作微服务镜像与运行服务镜像 3.1 拷贝Dockerfile文件到服务器 3.2 制作服务镜像 3.3 启动镜像服务 3.4 访问一下服务接口 四、配置负载均衡 4.1 源码包方式安装nginx 4.1.1 下载nginx安装包 4.1.2 解压安装包 4.1.3 进入解


Less resolver error:‘~antd/es/style/themes/index.less‘ wasn‘t found.
北阳AI知行录2025/10/7

记录一次使用Ant Design Pro框架时出现的bug 这是我最开始的package.json版本,然后执行npm run build(max build) 打包时会报上面的错误 { "name": "ant-design-pro", "version": "6.0.0", "private": true, "description": "An out-of-box UI solution for enterprise applications", "repo


sensitive-word:一个简单易用的敏感词过滤框架
勇哥Java实战2025/10/9

这篇文章,分享一个开源项目:sensitive-word 。 Github 地址:github.com/houbb/sensi… sensitive-word 是一个功能强大的 Java 敏感词过滤框架,它不仅提供了基础的敏感词检测功能,还支持单词标签分类分级、繁简体互换、全角半角互换、汉字转拼音、模糊搜索等高级特性。 它的核心特性如下: 🚀 高性能: 基于 DFA 算法,匹配效率极高 🏷️ 标签分类: 支持敏感词分类分级管理 🔄 字符处理: 支持繁简体、全角半角互换 🎯 模糊搜


【腾讯拥抱开源】Youtu-Embedding:基于CoDiEmb的一个协作而独特的框架,用于信息检索与语义文本相似性中的统一表征学习
吴脑的键客2025/10/10

🎯 简介 Youtu-Embedding 是由腾讯优图实验室开发的尖端通用文本嵌入模型。该模型在信息检索(IR)、语义文本相似度(STS)、聚类、重排序和分类等各类自然语言处理任务中均展现出卓越性能。 顶尖性能表现:截至2025年9月,在权威的CMTEB(中文大规模文本嵌入基准)评测中以77.46分位列榜首,彰显其强大稳健的文本表征能力。 创新训练框架:采用协同判别式微调框架,通过统一数据格式、任务差异化损失函数及动态单任务采样机制,有效解决多任务学习中的"负迁移"问题。 注:您可

上一篇:ASCII 码表

下一篇:链表转置算法

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0