服务端接口设计模式(二)
服务端接口设计模式(一)
服务端接口设计模式(二)
服务端接口设计模式(三)
服务端接口设计模式(四)
上一篇中讲解了服务的入口实现,本版将讲解本设计中一个最重要的类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还支持正则表达式,非业务上的校验都可以用注解实现,业务代码中再也不用写这些基本的代码了。
作者原创,转载请注明出处。