服务端接口设计模式(一)
服务端接口设计模式(二)
服务端接口设计模式(三)
服务端接口设计模式(四)

上一篇中讲解了服务的入口实现,本版将讲解本设计中一个最重要的类AbstractBusiService,它是一个抽象类,但扮演着重要的角色。
1、AbstractBusiService代码如下:

public abstract class AbstractBusiService<T, R> implements BusinessService<R> {

   /**
    * 接口实现
    * @param requestMessage
    * @return
    */
   @Override
   public Result<R> doHandle(RequestMessage requestMessage) {
      T t = this.parseParams(requestMessage.getData());
      Result<R> result = this.handle(t);
      return result;
   }

   /**
    * 转换参数
    * @param data
    * @return
    */
   protected abstract T parseParams(JSONObject data);

   /**
    * 详细业务
    * @param t
    * @return
    */
   protected abstract Result<R> handle(T t);
}

看上去里面什么也没有做,只是实现了BusinessService接口,然后新增了2个抽象方法,感觉有些多余,其实并不然,后面会结合实际场景说明。这里有个知识点,我在面试的时候有很多面试者不能确认抽象类是否能实现接口,可以肯定的说是可以的。下面继续完善用户登录的接口,新建UserLoginImpl 类,代码如下:

@Component(BusiServiceName.USER_LOGIN)
public class UserLoginImpl extends AbstractBusiService<LoginReq, LoginRes> {

   @Autowired
   private UserService userService;

   @Override
   protected LoginReq parseParams(JSONObject data) {
      return JSON.toJavaObject(data, LoginReq.class);
   }

   @Override
   protected Result<LoginRes> handle(LoginReq req) {
      Result<LoginRes> result = Result.errorResult();
      if(userService.existUser(req.getUsername(), req.getPassword())){
         result = Result.successResult();
         LoginRes res = new LoginRes();
         //仅作为示例,生产可以使用Jwt生成token
         res.setToken("qwertyuiop");
         result.setData(res);
      }else{
         result.setMessage("用户名或密码不正确!");
      }
      return result;
   }
}

因为BusinessService会有多个实现类,所以Spring容器中的bean用名字来区分,BusiServiceName.USER_LOGIN是我定义的一个常量,建议这么做。常量值必须与报文中定义的接口名称相同,定义如下:

/**
 * 登录
 */
public static final String USER_LOGIN = "userLogin";

parseParams方法用于解析业务报文,handle中处理具体的登录逻辑,这样一个登录接口就完成了。这里再完善一下这个接口,实际生产中会对请求参数的格式进行基本的格式校验,下面就来加上校验功能。这里我采用validation注解框架来实现统一的参数验证,在LoginReq的参数字段中加上注解规则,代码如下:

import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
@Setter
@Getter
public class LoginReq {

   @NotBlank(message = "用户名不能为空")
   @Length(min = 4, max = 20, message = "用户名长度是4~20个字符")
   private String username;

   @NotBlank(message = "密码不能为空")
   @Length(min = 6, max = 20, message = "密码长度是6~20个字符")
   private String password;
}

然后在AbstractBusiService中初始化一个Validator验证器,新增了一个passValidator的私有验证方法,在doHandle中业务方法执行前加上了验证的代码,验证通过则执行业务逻辑,如果不通过则直接返回,具体代码如下:

private Validator validator;

/**
 * 初始化校验器Validator
 */
@PostConstruct
public void initValidation(){
   validator = Validation.byProvider(HibernateValidator.class)
         .configure()
         .failFast(true)
         .buildValidatorFactory()
         .getValidator();
}

/**
 * 接口实现
 * @param requestMessage
 * @return
 */
@Override
public Result<R> doHandle(RequestMessage requestMessage) {
   T t = this.parseParams(requestMessage.getData());
   Result<R> result = this.passValidator(t);
   if(RespCode.SUCCESS.getCode().equals(result.getCode())) {
      result = this.handle(t);
   }
   return result;
}

/**
 * 参数校验
 * @param t
 * @param groups
 * @return
 */
private Result passValidator(T t, Class<?>... groups){
   Result result = Result.successResult();
   Set<ConstraintViolation<Object>> constraintViolations = validator.validate(t, groups);
   if (!constraintViolations.isEmpty()) {
      StringBuilder msg = new StringBuilder();
      for(ConstraintViolation<Object> constraint:  constraintViolations){
         msg.append(constraint.getMessage()).append(",");
      }
      result = Result.errorResult();
      result.setMessage(msg.toString());
   }
   return result;
}

跑起来测试一下,结果如下:

POST http://localhost:8080/api/service/v1

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 57
Date: Mon, 15 Mar 2021 09:12:13 GMT

{"code":"3000","message":"密码长度是6~20个字符,"}

Response code: 200; Time: 167ms; Content length: 41 bytes

非常的方便,Validator还支持正则表达式,非业务上的校验都可以用注解实现,业务代码中再也不用写这些基本的代码了。

作者原创,转载请注明出处。

发表评论