HTTP状态码可以指明请求是否成功,还可以揭示请求失败的确切原因,是前后端进行“交流”的重要语言。在实际开发过程中,存在着对不同状态码应用场景不清楚,如何正确抛出合适的状态码的问题,使得前端开发人员不能很好的获取发起请求后后台的反应。本文旨在通过实际应用对现业务涉及的状态码进行探究记录,为以后开发中遇到类似问题作个参考。

1. 查询一个资源

例如:GET:/v1/characters/{id}

1.1 接口定义

1
2
3
4
5
6
7
8
9
public interface CharacterService {
@GET
@Path("{id}")
@ApiOperation(value = "根据人物id查询人物详情", notes = "根据人物id查询人物详情", response = CharacterDTO.class, tags = {"CharacterService",})
@ApiResponses(value = {
@ApiResponse(code = 204, message = "没有数据"),
@ApiResponse(code = 404, message = "@06404:请求参数错误"),
@ApiResponse(code = 500, message = "@06500:服务器异常")})
CharacterDTO getCharacterById(@ApiParam(value = "人物ID", required = true) @NotNull(message = "CharacterID must be not null") @PathParam("id") Long id);

1.2 接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public CharacterDTO getCharacterById(Long characterId) {
CharacterDTO characterDTO = null;
try{
Character character = characterRepository.findByIdAndStatus(characterId, Status.VALID);
if (null == character){
return null;
//此处return null表示查询请求正确,但是没有资源,不抛异常,返回码204,表示没有数据。
//LOGGER.error("can not query character from db and character is: " + characterId);
// throw new WebApplicationException(ScriptsTranslationReturnCode.NOT_FOUND_MODEL, "can not find character");
}
return beanMapper.map(character,CharacterDTO.class);
} catch (WebApplicationException we){
LOGGER.error(we.getMessage(),we);
throw we;
} catch (Exception e){
LOGGER.error("query character information by id error",e);
throw new WebApplicationException(ScriptsTranslationReturnCode.SERVICE_EXCEPTION, "service exception");
}
}
  • 参数正确请求得到一个资源,返回200;
  • 参数正确资源不存在,返回204,或者404;
  • 参数类型错误(其他应该返回400),返回404,路径斜杠后的参数错误不会返回400.

2. 查询一个资源,该资源可能关联其他子资源。

例如:GET: {id}/lines-info

2.1 接口定义

1
2
3
4
5
6
7
8
9
@GET
@Path("{id : \\d+}/device-models")
@ApiOperation(value = "根据镜像id得到终端型号列表", notes = "根据镜像id得到终端型号列表", response = SimpleImageContentDTO.class, responseContainer = "List", tags={ "image content"})
@ApiResponses(value = {
@ApiResponse(code = 200, message = "OK", response = DeviceModelDTO.class, responseContainer = "List"),
@ApiResponse(code = 204, message = "没有数据"),
@ApiResponse(code = 422, message = "06450:无法处理的镜像资源", responseContainer = "Object"),
@ApiResponse(code = 500, message = "06500:服务异常", responseContainer = "Object") })
List<DeviceModelDTO> queryDeviceModelsById(@ApiParam(value = "镜像id",required = true) @NotNull(message = "The image content id must not be null") @PathParam("id") Long id);

2.2 接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public List<DeviceModelDTO> queryDeviceModelsById(Long id) {
try{
ImageContent imageContent = imageContentRepository.findByIdAndStatus(id, Status.VALID);
if (imageContent==null){
throw new UnprocessableEntityException(TerminalDeviceReturnCode.UNPROCESSABLE_IMAGE_CONTENT,"image content not found");
}
List<DeviceModel> deviceModelList = deviceModelRepository.queryDeviceModelsByImageContentId(id);
if (CollectionUtils.isEmpty(deviceModelList)){
return null;
}
return beanMapper.mapAsList(deviceModelList,DeviceModelDTO.class);
} catch (WebApplicationException we) {
LOGGER.error("query device models by Id error!" ,we);
throw we;
} catch (Exception e) {
LOGGER.error("query device models by Id error!" ,e);
throw new WebApplicationException(TerminalDeviceReturnCode.SERVICE_EXCEPTION, "service exception!");
}
}

根据A资源查询B资源

  • 正确查询,返回200;
  • 根据查不到A资源信息,返回422,抛异常UnprocessableException。
  • 可以查到A资源,但是根据A资源查不到B资源,返回204或者404;

3. 修改一个资源

例如:PUT:/translation-lines-info/{id}

3.1接口定义

1
2
3
4
5
6
7
8
9
10
@PUT
@Path("{id}")
@ApiOperation(value = "修改人物信息", notes = "修改人物信息", response = CharacterDTO.class, tags = {"CharacterService",})
@ApiResponses(value = {
@ApiResponse(code = 201, message = "修改成功"),
@ApiResponse(code = 404, message = "@06404:人物信息不存在"),
@ApiResponse(code = 400, message = "@06400: 请求参数错误"),
@ApiResponse(code = 409, message = "@06411:人物名称冲突"),
@ApiResponse(code = 500, message = "@06500:服务异常")})
CharacterDTO modifyCharacter(@ApiParam(value = "人物id", required = true) @NotNull(message = "parameter must not be null") @PathParam("id") Long id, @NotNull(message = "parameter must be not null") @ApiParam(value = "人物信息", required = true) CharacterDTO characterDTO);

3.2 接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public CharacterDTO modifyCharacter(Long id, CharacterDTO characterDTO) {
try {
Character character = characterRepository.findByIdAndStatus(id,Status.VALID);
if(character == null){
throw new WebApplicationException(ScriptsTranslationReturnCode.NOT_FOUND_MODEL, "modify character error! can not found character info exception!");
}
String chName = characterDTO.getName();
String enName = characterDTO.getEnglishName();
Long dramaId = characterDTO.getDramaId();
if(org.springframework.util.StringUtils.isEmpty(chName) || org.springframework.util.StringUtils.isEmpty(enName) || dramaId == null){
throw new ClientErrorException(HttpStatus.BAD_REQUEST, ScriptsTranslationReturnCode.PARAM_ERROR, " chname or enname or dramaid is empty!");
}
List<Character> chNamesOrEnNames = characterRepository.findOtherIdExistNames(chName,enName,id,dramaId);
if(chNamesOrEnNames.size() > 0){
throw new ClientErrorException(HttpStatus.CONFLICT, ScriptsTranslationReturnCode.DRAMA_ADD_OR_UPDATE_CHARACTER_NAME_CONFICT, " chname or enname conflict");
}
Character modifyCharacher = fillingModifyParams(characterDTO, character);
Character modifyResult = characterRepository.saveAndFlush(modifyCharacher);
return beanMapper.map(modifyResult, CharacterDTO.class);
}catch (ClientErrorException we){
LOGGER.error(we.getMessage(),we);
throw we;
} catch (Exception e){
LOGGER.error("modify character error",e);
throw new WebApplicationException(ScriptsTranslationReturnCode.SERVICE_EXCEPTION, "service exception");
}
}
  • 修改成功返回201;
  • 根据路径上的id查询人物信息没查到,返回404;
  • 如果路径上的id为非数字类型,返回404;
  • 如果dto里的类型错误,返回400(body和?后的参数类型错误,返回400),抛出BadRequestException;
  • 修改的数据与原数据产生了冲突(根据业务定义),返回409,抛出ClienErrorException。

4. 总结

现在涉及到的状态码有以下几个:

  • 200:用于成功查询到信息。
  • 201:用于修改或添加成功。
  • 204:用于删除成功,也可用于找不到资源。
  • 400:body和“?”后的参数类型错误,抛出BadRequestException。
  • 404:“/”后的参数错误,或者找不到资源。
  • 409:修改的数据与原数据产生了冲突(根据业务定义),返回409,抛出ClienErrorException。
  • 422:当用户针对不存在的资源进行操作时,则返回404,当用户传入无效字段,则返回422,抛出异常UnprocessableException。
    例如:
    修改A资源,但A资源不存在,则返回404;
    根据A资源,添加A与B资源的关系,此时操作主体是A与B资源的关系表,当A和B都不存在时,则是属于创建某个资源,发生了一个验证错误,此时应该返回422。

附1. 常用http状态码

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

  • 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)(在编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。)

  • 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。

  • 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)

  • 204 NO CONTENT - [DELETE]:用户删除数据成功。

  • 400 INVALID REQUEST - [POST/PUT/PATCH]:用户请求错误,参数格式不符合调用API规定、资源不存在、语义参数服务端无法处理等

  • 401 Unauthorized - [*] :表示用户没有权限(令牌、用户名、密码错误)。

  • 403 Forbidden - [*]:表示用户得到授权(与401错误相对),但是访问是被禁止的。

  • 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。

  • 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。

  • 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。

  • 422 Unprocesable entity - [POST/PUT/PATCH]:当创建一个对象时,发生一个验证错误。

  • 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

附2. 服务接口方法名规

  • add开头方法代表添加数据:add

  • modify开头方法代表修改业务数据:modify

  • query开头方法代表查询多条数据:queryByCondition,queryByIDs

  • get开头方法代表返回一条数据:getByID

  • delete开头方法代表删除数据:deleteByID

  • 提供服务的方法不要有重载

  • 方法参数不要超过5个,多于5个请使用包装类(DTO)

  • 方法参数类型不能使用int/char/double/long/boolean/short/float,必须换成对应Integer/Character/Double/Long/Boolean/Short/Float类型