Spring Boot 系列:RestFul API 接口实现统一格式返回

RestFul API 接口实现统一格式返回

一、背景

在分布式、微服务盛行的今天,绝大部分项目都采用的微服务框架,前后端分离方式。前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,后端服务器接收请求,进行业务处理,返回数据给前端。

所以统一接口的返回值,保证接口返回值的幂等性很重要,本文主要介绍博主当前使用的结果集。

二、统一格式设计

2.1 统一结果的一般形式

  • 示例:
{
	# 是否响应成功
	success: true,
	# 响应状态码
	code: 200,		
	# 响应数据
	data: Object
	# 返回错误信息
	message: "",
}

2.2 结果类枚举

public enum ResultCodeEnum {
    /*** 通用部分 100 - 599***/
    // 成功请求
    SUCCESS(200, "successful"),
    // 重定向
    REDIRECT(301, "redirect"),
    // 资源未找到
    NOT_FOUND(404, "not found"),
    // 服务器错误
    SERVER_ERROR(500,"server error"),

    /*** 这里可以根据不同模块用不同的区级分开错误码,例如:  ***/

    // 1000~1999 区间表示用户模块错误
    // 2000~2999 区间表示订单模块错误
    // 3000~3999 区间表示商品模块错误
    // 。。。

    ;
    /**
     * 响应状态码
     */
    private Integer code;
    /**
     * 响应信息
     */
    private String message;

    ResultCodeEnum(Integer code, String msg) {
        this.code = code;
        this.message = msg;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}
  • code:响应状态码

一般小伙伴们是在开发的时候需要什么,就添加什么。但是,为了规范,我们应当参考HTTP请求返回的状态码。

code区间类型含义
1**100-199信息服务器接收到请求,需要请求者继续执行操作
2**200-299成功请求被成功接收并处理
3**300-399重定向需要进一步的操作以完成请求
4**400-499客户端错误请求包含语法错误或无法完成请求
5**500-599服务器错误服务器在处理的时候发生错误

常见的HTTP状态码:

  1. 200 - 请求成功;
  2. 301 - 资源(网页等)被永久转移到其它URL
  3. 404 - 请求的资源(网页等)不存在;
  4. 500 - 内部服务器错误。
  • message:错误信息

在发生错误时,如何友好的进行提示?

  1. 根据code 给予对应的错误码定位;
  2. 把错误描述记录到message中,便于接口调用者更详细的了解错误。

2.3 统一结果类

public class HttpResult <T> implements Serializable {

    /**
     * 是否响应成功
     */
    private Boolean success;
    /**
     * 响应状态码
     */
    private Integer code;
    /**
     * 响应数据
     */
    private T data;
    /**
     * 错误信息
     */
    private String message;

    // 构造器开始
    /**
     * 无参构造器(构造器私有,外部不可以直接创建)
     */
    private HttpResult() {
        this.code = 200;
        this.success = true;
    }
    /**
     * 有参构造器
     * @param obj
     */
    private HttpResult(T obj) {
        this.code = 200;
        this.data = obj;
        this.success = true;
    }

    /**
     * 有参构造器
     * @param resultCode
     */
    private HttpResult(ResultCodeEnum resultCode) {
        this.success = false;
        this.code = resultCode.getCode();
        this.message = resultCode.getMessage();
    }
    // 构造器结束

    /**
     * 通用返回成功(没有返回结果)
     * @param <T>
     * @return
     */
    public static<T> HttpResult<T> success(){
        return new HttpResult();
    }

    /**
     * 返回成功(有返回结果)
     * @param data
     * @param <T>
     * @return
     */
    public static<T> HttpResult<T> success(T data){
        return new HttpResult<T>(data);
    }

    /**
     * 通用返回失败
     * @param resultCode
     * @param <T>
     * @return
     */
    public static<T> HttpResult<T> failure(ResultCodeEnum resultCode){
        return  new HttpResult<T>(resultCode);
    }

    public Boolean getSuccess() {
        return success;
    }

    public void setSuccess(Boolean success) {
        this.success = success;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "HttpResult{" +
                "success=" + success +
                ", code=" + code +
                ", data=" + data +
                ", message='" + message + '\'' +
                '}';
    }
}

说明:

  1. 构造器私有,外部不可以直接创建;
  2. 只可以调用统一返回类的静态方法返回对象;
  3. success 是一个Boolean 值,通过这个值,可以直接观察到该次请求是否成功;
  4. data 表示响应数据,用于请求成功后,返回客户端需要的数据。

三、测试及总结

3.1 简单的接口测试

@RestController
@RequestMapping("/httpRest")
@Api(tags = "统一结果测试")
public class HttpRestController {

    @ApiOperation(value = "通用返回成功(没有返回结果)", httpMethod = "GET")
    @GetMapping("/success")
    public HttpResult success(){
        return HttpResult.success();
    }

    @ApiOperation(value = "返回成功(有返回结果)", httpMethod = "GET")
    @GetMapping("/successWithData")
    public HttpResult successWithData(){
        return HttpResult.success("风尘博客");
    }

    @ApiOperation(value = "通用返回失败", httpMethod = "GET")
    @GetMapping("/failure")
    public HttpResult failure(){
        return HttpResult.failure(ResultCodeEnum.NOT_FOUND);
    }

}

这里 Swagger以及SpringMVC的配置就没贴出来了,详见Github 示例代码。

3.2 返回结果

http://localhost:8080/swagger-ui.html#/

{
  "code": 200,
  "success": true
}
{
  "code": 200,
  "data": "风尘博客",
  "success": true
}
{
  "code": 404,
  "message": "not found",
  "success": false
}

四、总结

没有哪一种方案是适用于各种情况的,如:分页情况,还可以增加返回分页结果的静态方案,具体实现,这里就不展示了。所以,适合自己的,具有一定可读性都是很好的,欢迎持不同意见的大佬给出意见建议。

Github 示例代码

4.1 日常求赞

博主祖传秘籍 Spring Boot 葵花宝典 开源中,欢迎前来吐槽,提供线索,告诉博主接下来更新哪方面文章,共同进步!

4.2 文化交流

  1. 风尘博客
  2. 风尘博客-掘金
  3. 风尘博客-博客园
  4. 风尘博客-CSDN
  5. Github

最新文章,欢迎关注:公众号-风尘博客;交流观点,欢迎添加:个人微信

风尘博客个人微信号

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的示例: 首先,创建一个 Spring Boot 项目,并添加以下依赖: ```xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.197</version> <scope>runtime</scope> </dependency> </dependencies> ``` 其中,`spring-boot-starter-web` 用于创建 web 项目,`spring-boot-starter-data-jpa` 用于访问数据库,`spring-boot-starter-security` 用于实现安全认证,`jjwt` 用于生成和验证 JWT Token,`h2` 用于创建内存数据库。 接下来,创建一个 User 实体类: ```java @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true) private String username; @Column(nullable = false) private String password; @Transient private String confirmPassword; // getter 和 setter 省略 } ``` 该实体类包含 id、username 和 password 三个字段,其中 username 是唯一的,password 会在存储时进行加密处理,confirmPassword 字段在注册时用于确认密码,不会被存储。 接着,创建 UserRepository 接口: ```java @Repository public interface UserRepository extends JpaRepository<User, Long> { User findByUsername(String username); } ``` 该接口继承了 JpaRepository 接口,用于访问 User 实体类对应的表。其中,`findByUsername` 方法用于通过 username 查找 User 对象。 然后,创建一个 JwtUtil 工具类: ```java @Component public class JwtUtil { private String SECRET_KEY = "secret"; public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return doGenerateToken(claims, userDetails.getUsername()); } private String doGenerateToken(Map<String, Object> claims, String subject) { Date now = new Date(); Date expirationDate = new Date(now.getTime() + 3600000); return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(now) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } public boolean validateToken(String token, UserDetails userDetails) { final String username = extractUsername(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } private boolean isTokenExpired(String token) { final Date expiration = extractExpiration(token); return expiration.before(new Date()); } private Date extractExpiration(String token) { return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getExpiration(); } private String extractUsername(String token) { return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getSubject(); } } ``` 该工具类用于生成和验证 JWT Token。其中,`generateToken` 方法用于生成 Token,`validateToken` 方法用于验证 Token 是否有效。 接下来,创建一个 AuthService 接口: ```java public interface AuthService { User register(User user); String login(String username, String password); } ``` 该接口定义了注册和登录两个方法。 然后,创建一个 AuthServiceImpl 实现类: ```java @Service public class AuthServiceImpl implements AuthService { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserRepository userRepository; @Autowired private JwtUtil jwtUtil; @Override public User register(User user) { String encodedPassword = new BCryptPasswordEncoder().encode(user.getPassword()); user.setPassword(encodedPassword); return userRepository.save(user); } @Override public String login(String username, String password) { try { authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); } catch (Exception e) { throw new RuntimeException("Invalid username or password"); } final UserDetails userDetails = new User(userRepository.findByUsername(username).getUsername(), userRepository.findByUsername(username).getPassword(), new ArrayList<>()); return jwtUtil.generateToken(userDetails); } } ``` 该实现类中,`register` 方法用于注册用户,将密码进行加密处理后保存到数据库中;`login` 方法用于登录,通过验证用户名和密码后生成 Token 并返回。 最后,创建一个 UserController 类: ```java @RestController @RequestMapping("/api") public class UserController { @Autowired private AuthService authService; @PostMapping("/register") public ResponseEntity<?> register(@RequestBody User user) { authService.register(user); return ResponseEntity.ok("User registered successfully"); } @PostMapping("/login") public ResponseEntity<?> login(@RequestBody Map<String, String> loginData) { String username = loginData.get("username"); String password = loginData.get("password"); String token = authService.login(username, password); return ResponseEntity.ok(token); } } ``` 该类中,`register` 方法用于注册用户,接受一个 User 对象作为参数;`login` 方法用于登录,接受一个包含用户名和密码的 Map 对象作为参数。 现在,启动应用程序,并使用 Postman 或其他工具测试接口。可以使用 `/api/register` 接口注册用户,使用 `/api/login` 接口登录,并将返回的 Token 作为请求头中的 Authorization 参数访问其他受保护的接口

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值