一. 接口上线/下线功能
接口的上线和下线是由管理员负责进行的。
上线接口,即发布接口。首先需要判断接口是否存在,然后判断接口是否可调用。如果可以调用,就修改数据库中该接口的状态字段为 1,代表发布,默认状态为 0,代表下线。
下线接口,与发布类似。先判断接口是否存在,如果存在,就修改数据库中该接口的状态字段为 0,代表下线。
1. 接收的参数
这两个功能接收的参数只需要 id 即可。因为 id 为接口的主键,唯一且不重复。
2. 管理员权限
这两个功能仅能管理员使用,所以给接口打上注解 @AuthCheck(mustRole = "admin")。
该注解是自定义实现的权限校验切面注解。首先获取到登录用户的信息,然后判断用户是否具有管理员权限,如果没有就抛异常,有权限才继续执行后续操作。
3. 上线接口
/**
* 发布
*
* @param idRequest
* @param request
* @return
*/
@PostMapping("/online")
@AuthCheck(mustRole = "admin")
public BaseResponse<Boolean> onlineInterfaceInfo(@RequestBody IdRequest idRequest,
HttpServletRequest request) {
if (idRequest == null || idRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
long id = idRequest.getId();
// 判断是否存在
InterfaceInfo oldInterfaceInfo = interfaceInfoService.getById(id);
if (oldInterfaceInfo == null) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
}
// 判断该接口是否可以调用
com.khr.kapiclientsdk.model.User user = new com.khr.kapiclientsdk.model.User();
user.setUsername("test");
String username = kApiClient.getUsernameByPost(user);
if (StringUtils.isBlank(username)) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "接口验证失败");
}
// 仅本人或管理员可修改
InterfaceInfo interfaceInfo = new InterfaceInfo();
interfaceInfo.setId(id);
interfaceInfo.setStatus(InterfaceInfoStatusEnum.ONLINE.getValue());
boolean result = interfaceInfoService.updateById(interfaceInfo);
return ResultUtils.success(result);
}
首先判断接口是否存在,不存在直接抛异常。然后判断接口是否可以调用,直接使用先前开发的 SDK 即可。最后修改数据库中的状态字段为上线,此处使用了枚举值来表示,
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* 接口信息状态枚举
*/
public enum InterfaceInfoStatusEnum {
OFFLINE("关闭", 0),
ONLINE("上线", 1);
private final String text;
private final int value;
InterfaceInfoStatusEnum(String text, int value) {
this.text = text;
this.value = value;
}
/**
* 获取值列表
*
* @return
*/
public static List<Integer> getValues() {
return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList());
}
public int getValue() {
return value;
}
public String getText() {
return text;
}
}
4. 下线接口
下线接口与上线几乎一致,只需要删除判断接口是否可调用逻辑,并将上线状态修改为下线状态即可。
/**
* 下线
*
* @param idRequest
* @param request
* @return
*/
@PostMapping("/offline")
@AuthCheck(mustRole = "admin")
public BaseResponse<Boolean> offlineInterfaceInfo(@RequestBody IdRequest idRequest,
HttpServletRequest request) {
if (idRequest == null || idRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
long id = idRequest.getId();
// 判断是否存在
InterfaceInfo oldInterfaceInfo = interfaceInfoService.getById(id);
if (oldInterfaceInfo == null) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
}
// 仅本人或管理员可修改
InterfaceInfo interfaceInfo = new InterfaceInfo();
interfaceInfo.setId(id);
interfaceInfo.setStatus(InterfaceInfoStatusEnum.OFFLINE.getValue());
boolean result = interfaceInfoService.updateById(interfaceInfo);
return ResultUtils.success(result);
}
二. 申请签名
需要为每个新注册的用户自动分配一对 accessKey 和 secretKey,所以需要修改注册流程。
/**
* 盐值,混淆密码
*/
private static final String SALT = "khr";
@Override
public long userRegister(String userAccount, String userPassword, String checkPassword) {
// 1. 校验
if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
}
if (userAccount.length() < 4) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短");
}
if (userPassword.length() < 8 || checkPassword.length() < 8) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");
}
// 密码和校验密码相同
if (!userPassword.equals(checkPassword)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致");
}
synchronized (userAccount.intern()) {
// 账户不能重复
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userAccount", userAccount);
long count = userMapper.selectCount(queryWrapper);
if (count > 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
}
// 2. 加密
String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
// 3. 分配 accessKey, secretKey
String accessKey = DigestUtil.md5Hex(SALT + userAccount + RandomUtil.randomNumbers(5));
String secretKey = DigestUtil.md5Hex(SALT + userAccount + RandomUtil.randomNumbers(8));
// 4. 插入数据
User user = new User();
user.setUserAccount(userAccount);
user.setUserPassword(encryptPassword);
user.setAccessKey(accessKey);
user.setSecretKey(secretKey);
boolean saveResult = this.save(user);
if (!saveResult) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败,数据库错误");
}
return user.getId();
}
}
aK 和 sK 的生成是将盐值、用户账户和 5/8 位随机数进行拼接,并采用 MD5 加密生成,保证其随机性。
三. 接口测试调用
首先给这个接口封装一个参数,创建一个名为 InterfaceInfoInvokeRequest 的类。
import lombok.Data;
import java.io.Serializable;
/**
* 接口调用请求
*
*/
@Data
public class InterfaceInfoInvokeRequest implements Serializable {
/**
* 主键
*/
private Long id;
/**
* 用户请求参数
*/
private String userRequestParams;
private static final long serialVersionUID = 1L;
}
测试调用的完整代码为:
/**
* 测试调用
*
* @param interfaceInfoInvokeRequest
* @param request
* @return
*/
@PostMapping("/invoke")
public BaseResponse<Object> invokeInterfaceInfo(@RequestBody InterfaceInfoInvokeRequest interfaceInfoInvokeRequest,
HttpServletRequest request) {
if (interfaceInfoInvokeRequest == null || interfaceInfoInvokeRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
long id = interfaceInfoInvokeRequest.getId();
String userRequestParams = interfaceInfoInvokeRequest.getUserRequestParams();
// 判断是否存在
InterfaceInfo oldInterfaceInfo = interfaceInfoService.getById(id);
if (oldInterfaceInfo == null) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
}
if (oldInterfaceInfo.getStatus() == InterfaceInfoStatusEnum.OFFLINE.getValue()) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "接口已关闭");
}
// 获取当前登录用户的 aK 和 sK,这样相当于用户以自己的身份去调用
// 即使用户恶意刷量,数据库中也能知道是谁刷的
User loginUser = userService.getLoginUser(request);
String accessKey = loginUser.getAccessKey();
String secretKey = loginUser.getSecretKey();
// 创建一个临时的 KApiClient 对象,并传入 aK 和 sK
KApiClient tempClient = new KApiClient(accessKey, secretKey);
Gson gson = new Gson();
com.yupi.yuapiclientsdk.model.User user = gson.fromJson(userRequestParams, com.yupi.yuapiclientsdk.model.User.class);
String usernameByPost = tempClient.getUsernameByPost(user);
return ResultUtils.success(usernameByPost);
}
后续会持续更新整理。