Spring Boot 接口开发设计原则与最佳实践指南

【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道! 10w+人浏览 1.6k人参与

作为一名 Java 后端开发程序员,学习并遵循良好的 Spring Boot 接口设计原则,是构建高可用、易维护、可扩展、安全的微服务系统的关键。下面是一份详细的《Spring Boot 接口开发设计原则与最佳实践指南》,包含设计原则、处理规范、代码示例及详尽中文注释。


📜 Spring Boot 接口开发设计原则与最佳实践指南(含完整示例)

适用对象:Java 后端开发工程师
目标:构建符合企业级标准的 RESTful API
核心理念清晰、安全、可维护、可测试、可监控


✅ 一、设计原则(Design Principles)

原则说明
1. RESTful 风格使用标准 HTTP 方法(GET/POST/PUT/DELETE)和资源路径,避免动词路径(如 /getUserById
2. 统一响应格式所有接口返回统一结构,便于前端/客户端解析
3. 状态码规范使用标准 HTTP 状态码(200、400、401、404、500 等),避免自定义数字码
4. 参数校验使用 @Valid + javax.validation 校验请求参数,拒绝非法数据
5. 异常统一处理使用 @RestControllerAdvice 全局捕获异常,返回友好错误信息
6. 日志记录接口请求/响应记录关键日志(如请求ID、耗时、用户ID),便于排查
7. 接口幂等性对于写操作(如支付、下单),确保重复请求不会产生副作用
8. 分页与过滤查询接口必须支持分页(Pageable)和字段过滤
9. 接口版本控制使用 URL 路径(如 /v1/users)或 Header 控制版本,避免破坏性变更
10. 安全性使用 Spring Security 做认证授权,敏感字段脱敏,防止 SQL 注入/XSS
11. 性能优化避免 N+1 查询,使用缓存(Redis),异步处理耗时操作
12. 文档化使用 Swagger/OpenAPI 自动生成接口文档

✅ 二、推荐处理规范(Best Practices)

1. 统一响应结构(Response Format)

所有接口返回统一结构,前端可通用处理,提升开发效率。

package com.example.demo.response;

import lombok.Data;

import java.time.LocalDateTime;

/**
 * 统一响应封装类
 * 所有接口返回都使用这个类包装
 * 优点:前端无需判断不同结构,统一处理 success / code / message / data
 */
@Data
public class ApiResponse<T> {

    /**
     * 响应码:200 表示成功,其他为错误码(建议使用业务码,如 4001 用户未登录)
     * 200: 成功
     * 400: 请求参数错误
     * 401: 未授权
     * 404: 资源不存在
     * 500: 服务器内部错误
     * 自定义业务码:如 1001 - 用户已存在
     */
    private Integer code;

    /**
     * 响应信息:对用户友好的提示语
     */
    private String message;

    /**
     * 响应数据:泛型,可为任意类型(List、Object、null)
     */
    private T data;

    /**
     * 请求时间戳,便于链路追踪和日志分析
     */
    private LocalDateTime timestamp;

    /**
     * 构造成功响应(带数据)
     */
    public static <T> ApiResponse<T> success(T data) {
        ApiResponse<T> response = new ApiResponse<>();
        response.code = 200;
        response.message = "操作成功";
        response.data = data;
        response.timestamp = LocalDateTime.now();
        return response;
    }

    /**
     * 构造成功响应(无数据)
     */
    public static <T> ApiResponse<T> success() {
        return success(null);
    }

    /**
     * 构造错误响应(带错误码和信息)
     */
    public static <T> ApiResponse<T> error(Integer code, String message) {
        ApiResponse<T> response = new ApiResponse<>();
        response.code = code;
        response.message = message;
        response.timestamp = LocalDateTime.now();
        return response;
    }

    /**
     * 构造系统异常响应(500)
     */
    public static <T> ApiResponse<T> systemError(String message) {
        return error(500, "系统繁忙,请稍后再试:" + message);
    }
}

2. 请求参数校验(Validation)

使用 JSR-303 注解 + @Valid 校验前端传参,避免脏数据进入业务层。

package com.example.demo.dto;

import lombok.Data;

import javax.validation.constraints.*;
import java.time.LocalDate;

/**
 * 用户创建请求 DTO(Data Transfer Object)
 * 专门用于接收前端参数,避免直接使用 Entity
 * 优点:解耦、可复用、可校验
 */
@Data
public class UserCreateRequest {

    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 20, message = "用户名长度必须在2~20个字符之间")
    private String username;

    @Email(message = "邮箱格式不正确")
    @NotBlank(message = "邮箱不能为空")
    private String email;

    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phone;

    @Min(value = 18, message = "年龄必须大于等于18岁")
    @Max(value = 120, message = "年龄不能超过120岁")
    private Integer age;

    @NotNull(message = "出生日期不能为空")
    private LocalDate birthDate;

    // 注意:密码不应在 DTO 中明文传输,生产环境应使用加密传输(如 HTTPS + 密码哈希)
    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 20, message = "密码长度必须在6~20个字符之间")
    private String password;
}

3. 全局异常处理(Global Exception Handler)

使用 @ControllerAdvice 统一捕获所有异常,避免返回堆栈信息给前端。

package com.example.demo.exception;

import com.example.demo.response.ApiResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
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 java.util.HashMap;
import java.util.Map;

/**
 * 全局异常处理器
 * 捕获所有 Controller 层抛出的异常,并返回统一格式的错误响应
 * 避免将堆栈信息暴露给前端,提升安全性
 */
@RestControllerAdvice // 等价于 @ControllerAdvice + @ResponseBody
public class GlobalExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 处理参数校验失败异常(@Valid 校验不通过)
     * 例如:前端传了空用户名,会触发此方法
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiResponse<Object>> handleValidationException(MethodArgumentNotValidException e) {
        // 提取所有校验错误信息
        Map<String, String> errors = new HashMap<>();
        for (FieldError error : e.getBindingResult().getFieldErrors()) {
            errors.put(error.getField(), error.getDefaultMessage());
        }

        // 记录日志(便于运维排查)
        log.warn("参数校验失败: {}", errors);

        // 返回 400 Bad Request + 错误详情
        return ResponseEntity.badRequest()
                .body(ApiResponse.error(400, "参数校验失败") + " - " + errors.toString());
    }

    /**
     * 处理 BindException(如 @ModelAttribute 绑定失败)
     */
    @ExceptionHandler(BindException.class)
    public ResponseEntity<ApiResponse<Object>> handleBindException(BindException e) {
        Map<String, String> errors = new HashMap<>();
        for (FieldError error : e.getBindingResult().getFieldErrors()) {
            errors.put(error.getField(), error.getDefaultMessage());
        }
        log.warn("参数绑定失败: {}", errors);
        return ResponseEntity.badRequest().body(ApiResponse.error(400, "参数绑定失败:" + errors));
    }

    /**
     * 处理自定义业务异常(推荐:所有业务异常继承此基类)
     */
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ApiResponse<Object>> handleBusinessException(BusinessException e) {
        log.warn("业务异常: {}", e.getMessage());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(ApiResponse.error(e.getCode(), e.getMessage()));
    }

    /**
     * 处理所有未捕获的运行时异常(如空指针、数据库异常等)
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResponse<Object>> handleGeneralException(Exception e) {
        // 生产环境不要暴露堆栈,只返回通用错误
        log.error("系统内部错误", e);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ApiResponse.systemError("未知错误,请联系管理员"));
    }
}

自定义业务异常类(推荐)

package com.example.demo.exception;

/**
 * 业务异常基类
 * 所有业务逻辑抛出的异常都继承此类
 * 便于统一处理和区分系统异常 vs 业务异常
 */
public class BusinessException extends RuntimeException {

    private final Integer code; // 业务错误码,如 1001=用户已存在

    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }
}

4. 控制器层(Controller)示例(RESTful 风格)

package com.example.demo.controller;

import com.example.demo.dto.UserCreateRequest;
import com.example.demo.entity.User;
import com.example.demo.response.ApiResponse;
import com.example.demo.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

/**
 * 用户控制器(UserController)
 * RESTful 风格设计:
 * GET    /api/v1/users           -> 查询用户列表(分页)
 * GET    /api/v1/users/{id}      -> 根据ID查询用户
 * POST   /api/v1/users           -> 创建用户
 * PUT    /api/v1/users/{id}      -> 更新用户
 * DELETE /api/v1/users/{id}      -> 删除用户
 *
 * 注意:使用 /api/v1/ 做版本控制,便于未来升级
 */
@RestController
@RequestMapping("/api/v1/users") // 版本控制:v1
@Validated // 启用方法级校验(可选,配合 @Valid 在方法参数上使用)
@Api(tags = "用户管理接口")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 查询用户列表(分页 + 排序)
     * 请求示例:GET /api/v1/users?page=0&size=10&sort=name,asc
     */
    @GetMapping
    @ApiOperation("分页查询用户列表")
    public ResponseEntity<ApiResponse<Page<User>>> listUsers(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(defaultValue = "id,asc") String sort) {

        // Spring Data JPA 自动解析分页参数
        Pageable pageable = PageRequest.of(page, size);
        Page<User> users = userService.findAll(pageable);

        return ResponseEntity.ok(ApiResponse.success(users));
    }

    /**
     * 根据 ID 查询单个用户
     * 注意:如果用户不存在,UserService 会抛出 BusinessException
     */
    @GetMapping("/{id}")
    @ApiOperation("根据ID查询用户")
    public ResponseEntity<ApiResponse<User>> getUserById(@PathVariable Long id) {
        User user = userService.findById(id); // 可能抛出 BusinessException
        return ResponseEntity.ok(ApiResponse.success(user));
    }

    /**
     * 创建用户(POST)
     * 使用 @Valid 校验请求体
     * 如果校验失败,会被 GlobalExceptionHandler 捕获并返回 400
     */
    @PostMapping
    @ApiOperation("创建用户")
    public ResponseEntity<ApiResponse<User>> createUser(@Valid @RequestBody UserCreateRequest request) {
        // 将 DTO 转换为 Entity(推荐使用 MapStruct 或手动转换)
        User user = new User();
        user.setUsername(request.getUsername());
        user.setEmail(request.getEmail());
        user.setPhone(request.getPhone());
        user.setAge(request.getAge());
        user.setBirthDate(request.getBirthDate());
        // 密码应加密存储(生产环境使用 BCryptPasswordEncoder)
        user.setPassword(request.getPassword()); // 实际项目中应加密!

        User savedUser = userService.save(user);
        return ResponseEntity.ok(ApiResponse.success(savedUser));
    }

    /**
     * 更新用户(PUT)
     * 注意:幂等性保证:多次更新同一个用户,结果一致
     */
    @PutMapping("/{id}")
    @ApiOperation("更新用户")
    public ResponseEntity<ApiResponse<User>> updateUser(
            @PathVariable Long id,
            @Valid @RequestBody UserCreateRequest request) {

        User updatedUser = userService.update(id, request);
        return ResponseEntity.ok(ApiResponse.success(updatedUser));
    }

    /**
     * 删除用户(DELETE)
     * 注意:软删除(推荐) vs 真删除
     * 生产环境建议软删除:设置 deleted=true,避免数据丢失
     */
    @DeleteMapping("/{id}")
    @ApiOperation("删除用户(软删除)")
    public ResponseEntity<ApiResponse<Void>> deleteUser(@PathVariable Long id) {
        userService.softDelete(id);
        return ResponseEntity.ok(ApiResponse.success());
    }
}

5. 服务层(Service)最佳实践

package com.example.demo.service;

import com.example.demo.dto.UserCreateRequest;
import com.example.demo.entity.User;
import com.example.demo.exception.BusinessException;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

/**
 * 用户服务层
 * 职责:业务逻辑处理、事务管理、调用 Repository
 * 注意:不要在 Service 中处理 HTTP 相关逻辑(如状态码、响应格式)
 */
@Service
@Transactional(readOnly = true) // 默认只读,写操作显式标注 @Transactional
public class UserService {

    @Autowired
    private UserRepository userRepository;

    /**
     * 查询所有用户(分页)
     */
    public Page<User> findAll(Pageable pageable) {
        return userRepository.findAll(pageable);
    }

    /**
     * 根据 ID 查询用户
     * 如果不存在,抛出自定义业务异常
     * 优点:让 Controller 层专注于 HTTP,异常由全局处理器处理
     */
    public User findById(Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new BusinessException(1001, "用户不存在,ID:" + id));
    }

    /**
     * 创建用户(事务性操作)
     * 保证数据一致性
     */
    @Transactional
    public User save(User user) {
        // 检查用户名是否重复(业务规则)
        if (userRepository.findByUsername(user.getUsername()).isPresent()) {
            throw new BusinessException(1002, "用户名已存在:" + user.getUsername());
        }

        // 检查邮箱是否重复
        if (userRepository.findByEmail(user.getEmail()).isPresent()) {
            throw new BusinessException(1003, "邮箱已被注册:" + user.getEmail());
        }

        // 可在此处添加日志、埋点、发送邮件等
        return userRepository.save(user);
    }

    /**
     * 更新用户(幂等操作)
     */
    @Transactional
    public User update(Long id, UserCreateRequest request) {
        User existingUser = findById(id); // 不存在会抛异常

        // 检查用户名是否被他人占用
        if (!existingUser.getUsername().equals(request.getUsername())) {
            if (userRepository.findByUsername(request.getUsername()).isPresent()) {
                throw new BusinessException(1002, "用户名已存在:" + request.getUsername());
            }
        }

        // 检查邮箱是否被他人占用
        if (!existingUser.getEmail().equals(request.getEmail())) {
            if (userRepository.findByEmail(request.getEmail()).isPresent()) {
                throw new BusinessException(1003, "邮箱已被注册:" + request.getEmail());
            }
        }

        // 更新字段
        existingUser.setUsername(request.getUsername());
        existingUser.setEmail(request.getEmail());
        existingUser.setPhone(request.getPhone());
        existingUser.setAge(request.getAge());
        existingUser.setBirthDate(request.getBirthDate());
        // 密码更新需加密(此处省略加密逻辑)
        // existingUser.setPassword(passwordEncoder.encode(request.getPassword()));

        return userRepository.save(existingUser);
    }

    /**
     * 软删除(推荐)
     * 不物理删除,设置 deleted=true
     */
    @Transactional
    public void softDelete(Long id) {
        User user = findById(id);
        user.setDeleted(true); // 假设 User 实体有 deleted 字段
        userRepository.save(user);
    }
}

6. 数据访问层(Repository)与实体类(Entity)

package com.example.demo.entity;

import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.time.LocalDateTime;

/**
 * 用户实体类(对应数据库表 user)
 * 使用 Lombok 简化 getter/setter/toString
 * 注意:实体类不直接暴露给前端,应通过 DTO 转换
 */
@Entity
@Table(name = "users")
@Data
@EntityListeners(AuditingEntityListener.class) // 自动维护创建/更新时间
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true, length = 50)
    private String username;

    @Column(nullable = false, unique = true, length = 100)
    private String email;

    private String phone;

    private Integer age;

    @Column(name = "birth_date")
    private LocalDateTime birthDate;

    private String password; // 注意:生产环境必须加密存储(BCrypt)

    @Column(name = "deleted")
    private Boolean deleted = false; // 软删除字段

    @CreatedDate
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
}
package com.example.demo.repository;

import com.example.demo.entity.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.Optional;

/**
 * 用户数据访问层
 * 继承 JpaRepository,自动获得 CRUD 方法
 * 自定义查询使用 @Query
 */
@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    Optional<User> findByUsername(String username);

    Optional<User> findByEmail(String email);

    // 自定义分页查询(可选)
    @Query("SELECT u FROM User u WHERE u.deleted = false")
    Page<User> findActiveUsers(Pageable pageable);
}

7. 日志记录增强(请求日志拦截器)

记录每个请求的耗时、IP、用户ID、参数,便于监控和审计。

package com.example.demo.interceptor;

import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

/**
 * 请求日志拦截器
 * 为每个请求生成唯一 traceId,便于链路追踪
 * 记录请求方法、路径、参数、响应状态、耗时
 */
@Component
public class RequestLoggingInterceptor implements HandlerInterceptor {

    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RequestLoggingInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 生成唯一请求ID,用于链路追踪
        String traceId = UUID.randomUUID().toString().substring(0, 8);
        MDC.put("traceId", traceId);

        // 记录请求开始
        String method = request.getMethod();
        String uri = request.getRequestURI();
        String params = request.getQueryString() != null ? "?" + request.getQueryString() : "";
        log.info("【请求开始】{} {}{} | IP: {}", method, uri, params, request.getRemoteAddr());

        return true; // 继续执行
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // 记录请求结束
        String method = request.getMethod();
        String uri = request.getRequestURI();
        int status = response.getStatus();
        String traceId = MDC.get("traceId");

        log.info("【请求结束】{} {} | 状态码:{} | traceId:{}", method, uri, status, traceId);
        MDC.clear(); // 清理 MDC,避免线程池污染
    }
}

注册拦截器

package com.example.demo.config;

import com.example.demo.interceptor.RequestLoggingInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private RequestLoggingInterceptor requestLoggingInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(requestLoggingInterceptor)
                .addPathPatterns("/api/v1/**") // 只对 API 接口生效
                .excludePathPatterns("/api/v1/users/login"); // 登录接口可选排除
    }
}

✅ 三、推荐工具与技术栈

类别推荐技术
接口文档Swagger UI / SpringDoc OpenAPI
参数校验Hibernate Validator(JSR-303)
异常处理@ControllerAdvice + 自定义业务异常
日志SLF4J + Logback + MDC(traceId)
安全Spring Security + JWT
缓存Redis(缓存热点数据)
异步@Async + 线程池
序列化Jackson(默认)
工具类Lombok、MapStruct(DTO-Entity 转换)
监控Actuator + Prometheus + Grafana

✅ 四、总结:最佳实践清单(Checklist)

✅ 使用 RESTful 风格路径(资源导向)
✅ 所有接口返回 ApiResponse<T> 统一格式
✅ 使用 @Valid + @NotBlank 等注解校验参数
✅ 全局异常处理 @ControllerAdvice
✅ 业务异常继承 BusinessException
✅ 使用 DTO 隔离 Entity 与 API
✅ 接口支持分页(Pageable)
✅ 使用版本控制 /v1/
✅ 日志记录 traceId + 请求耗时
✅ 软删除代替物理删除
✅ 敏感数据脱敏(如手机号:138****1234)
✅ 使用 HTTPS + JWT 认证
✅ 接口幂等设计(如:订单创建使用唯一幂等键)
✅ 使用 Swagger 自动生成文档
✅ 单元测试覆盖核心接口(MockMvc)


✅ 五、扩展建议(进阶)

  • ✅ 使用 OpenFeign 调用下游服务
  • ✅ 使用 Redis 缓存用户信息、配置项
  • ✅ 使用 RabbitMQ/Kafka 异步处理短信、邮件
  • ✅ 使用 Spring Boot Actuator + Prometheus 监控接口 QPS、延迟
  • ✅ 使用 JaegerSkyWalking 做分布式链路追踪
  • ✅ 使用 TestContainers 做集成测试(真实数据库)
  • ✅ 使用 GitLab CI/CD 自动部署

📌 结语

好的接口不是写出来的,是设计出来的。
遵循以上原则,你的 Spring Boot 项目将具备:

  • 高可用:异常不崩溃,服务稳定
  • 易维护:结构清晰,日志齐全
  • 可扩展:模块分离,版本可控
  • 安全可靠:校验+认证+脱敏
  • 团队协作友好:文档清晰,规范统一

💡 附:项目结构建议(推荐目录)

src/main/java/com/example/demo/
├── config/                 # 配置类(Web、Security、Redis)
├── controller/             # 控制器层(REST API)
├── service/                # 服务层(业务逻辑)
├── repository/             # 数据访问层(JPA)
├── entity/                 # 实体类(Entity)
├── dto/                    # 数据传输对象(DTO)
├── response/               # 统一响应封装
├── exception/              # 自定义异常
├── interceptor/            # 拦截器(日志、权限)
├── util/                   # 工具类(加密、脱敏、时间)
├── aspect/                 # 切面(性能监控、日志)
└── DemoApplication.java    # 启动类

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值