Commit 977fa279 by lijinqi

1.api调用前置校验完成

2.接口返回成功后扣除次数
parent 4cabaec9
package com.luhu.computility.framework.signature.core.aop; package com.luhu.computility.framework.signature.core.aop;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.crypto.digest.DigestUtil;
import com.luhu.computility.framework.common.exception.ServiceException; import com.luhu.computility.framework.common.exception.ServiceException;
import com.luhu.computility.framework.common.exception.enums.GlobalErrorCodeConstants; import com.luhu.computility.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.luhu.computility.framework.common.pojo.CommonResult;
import com.luhu.computility.framework.common.util.servlet.ServletUtils; import com.luhu.computility.framework.common.util.servlet.ServletUtils;
import com.luhu.computility.framework.signature.core.annotation.ApiSignature; import com.luhu.computility.framework.signature.core.annotation.ApiSignature;
import com.luhu.computility.framework.signature.core.redis.ApiSignatureRedisDAO; import com.luhu.computility.framework.signature.core.redis.ApiSignatureRedisDAO;
import com.luhu.computility.module.apihub.controller.admin.userapiusage.vo.JoinUserApiUsageResult;
import com.luhu.computility.module.apihub.dal.dataobject.apiendpoint.ApiEndpointDO;
import com.luhu.computility.module.apihub.dal.dataobject.appcredential.AppCredentialDO;
import com.luhu.computility.module.apihub.service.apiendpoint.ApiEndpointService;
import com.luhu.computility.module.apihub.service.appcredential.AppCredentialService;
import com.luhu.computility.module.apihub.service.userapiusage.UserApiUsageService;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Before;
import org.checkerframework.checker.units.qual.A;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.TreeMap; import java.util.TreeMap;
import static com.luhu.computility.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; import static com.luhu.computility.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
import static com.luhu.computility.framework.signature.core.redis.ApiSignatureRedisDAO.USAGE_KEY;
import static com.luhu.computility.module.apihub.enums.ErrorCodeConstants.API_ENDPOINT_EXPIRED;
import static com.luhu.computility.module.apihub.enums.ErrorCodeConstants.API_ENDPOINT_NOT_AVAILABLE;
import static com.luhu.computility.module.apihub.enums.ErrorCodeConstants.API_ENDPOINT_NOT_EXISTS;
import static com.luhu.computility.module.apihub.enums.ErrorCodeConstants.HEAD_EXCEPTION;
import static com.luhu.computility.module.apihub.enums.ErrorCodeConstants.INVALID_APPID;
import static com.luhu.computility.module.apihub.enums.ErrorCodeConstants.TIMESTAMP_EXCEPTION;
/** /**
* 拦截声明了 {@link ApiSignature} 注解的方法,实现签名 * 拦截声明了 {@link ApiSignature} 注解的方法,实现签名
...@@ -37,51 +60,144 @@ public class ApiSignatureAspect { ...@@ -37,51 +60,144 @@ public class ApiSignatureAspect {
private final ApiSignatureRedisDAO signatureRedisDAO; private final ApiSignatureRedisDAO signatureRedisDAO;
@Before("@annotation(signature)") @Resource
public void beforePointCut(JoinPoint joinPoint, ApiSignature signature) { private AppCredentialService appCredentialService;
// 1. 验证通过,直接结束
if (verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) { @Resource
return; private ApiEndpointService appEndpointService;
@Resource
private UserApiUsageService userApiUsageService;
public ApiSignatureAspect(ApiSignatureRedisDAO signatureRedisDAO) {
this.signatureRedisDAO = signatureRedisDAO;
}
@Around("@annotation(signature)")
public Object aroundPointCut(ProceedingJoinPoint joinPoint, ApiSignature signature) throws Throwable {
HttpServletRequest request = Objects.requireNonNull(ServletUtils.getRequest());
// 1.校验 Header
/* if (!verifyHeaders(signature, request)) {
throw new ServiceException(HEAD_EXCEPTION);
}*/
//查询appId对应的Secret
String appId = request.getHeader(signature.appId());
AppCredentialDO appCredentialDO = appCredentialService.getAppSecretByAppid(appId);
if (!ObjectUtil.isEmpty(appCredentialDO)) {
Assert.notNull(appCredentialDO.getAppId(), "[appId({})] 找不到对应的 appSecret", appId);
} else {
throw new ServiceException(INVALID_APPID);
}
// 2. 签名校验
/*if (!verifySignature(signature, request, appCredentialDO)) {
log.error("[aroundPointCut][方法 {} 参数({}) 签名失败]", joinPoint.getSignature(), joinPoint.getArgs());
throw new ServiceException(BAD_REQUEST.getCode(),
StrUtil.blankToDefault(signature.message(), BAD_REQUEST.getMsg()));
}*/
// 2. 应用-接口绑定校验
String path = request.getRequestURI();
String method = request.getMethod();
//2.1是否有该接口
ApiEndpointDO apiEndpoint = appEndpointService.getApiEndpointByPathAndMethod(path, method);
if (Objects.isNull(apiEndpoint)) {
throw new ServiceException(API_ENDPOINT_NOT_EXISTS);
}
//2.2该接口是否订阅过
List<JoinUserApiUsageResult> joinUserApiUsageResults = userApiUsageService.selectJoinUserApiUsageResult(appCredentialDO.getUserId(), apiEndpoint.getId());
if (CollectionUtil.isEmpty(joinUserApiUsageResults)){
throw new ServiceException(API_ENDPOINT_NOT_AVAILABLE);
} }
//3. 用户套餐校验(只校验,不扣减)如果有一个套餐符合条件,则继续往下走
JoinUserApiUsageResult joinUserApiUsageResult = null;
LocalDateTime now = LocalDateTime.now();
for (JoinUserApiUsageResult j : joinUserApiUsageResults) {
// 未过期且有剩余额度
if (j.getExpireTime().isAfter(now) && j.getUsedTimes() < j.getPackageTimes()) {
if (joinUserApiUsageResult == null || j.getExpireTime().isBefore(joinUserApiUsageResult.getExpireTime())) {
joinUserApiUsageResult = j;
}
}
}
if (Objects.isNull(joinUserApiUsageResult)) {
throw new ServiceException(API_ENDPOINT_EXPIRED);
}
// 4. 执行目标方法
Object result = joinPoint.proceed();
// 2. 验证不通过,抛出异常 // 5. 只有当接口执行成功才扣减额度
log.error("[beforePointCut][方法{} 参数({}) 签名失败]", joinPoint.getSignature().toString(), if (isSuccessResult(result)) {
joinPoint.getArgs()); consumeUsage(joinUserApiUsageResults);
throw new ServiceException(BAD_REQUEST.getCode(), }
StrUtil.blankToDefault(signature.message(), BAD_REQUEST.getMsg()));
return result;
} }
public boolean verifySignature(ApiSignature signature, HttpServletRequest request) {
// 1.1 校验 Header
if (!verifyHeaders(signature, request)) { /** 扣减一次额度(优先扣最近过期的套餐,用 Redis 保证并发安全) */
return false; private void consumeUsage(List<JoinUserApiUsageResult> joinUserApiUsageResults) {
JoinUserApiUsageResult target = null;
LocalDateTime now = LocalDateTime.now();
// 找到过期时间最早且可用的套餐
for (JoinUserApiUsageResult j : joinUserApiUsageResults) {
if (!ObjectUtil.isEmpty(j.getExpireTime()) && j.getExpireTime().isAfter(now)
&& j.getUsedTimes() < j.getPackageTimes()) {
if (ObjectUtil.isEmpty(target) || j.getExpireTime().isBefore(target.getExpireTime())) {
target = j;
}
}
} }
// Redis key:USAGE_KEY + apiEndpointId + userId
String key = ApiSignatureRedisDAO.USAGE_KEY + target.getApiEndPointId() + target.getUserId();
// 自增使用次数,如果是第一次使用则设置过期时间
Integer used = signatureRedisDAO.incrementUsageWithExpire(key);
if (!ObjectUtil.isEmpty(used) && used <= target.getPackageTimes()) {
// 异步回写 DB
userApiUsageService.asyncUpdateUsage(target.getId(), used);
}
}
/** 判断返回结果是否为成功(可按你项目的 CommonResult 判断) */
private boolean isSuccessResult(Object result) {
if (result instanceof CommonResult) {
return ((CommonResult<?>) result).isSuccess();
}
return true; // 默认认为执行成功
}
public boolean verifySignature(ApiSignature signature, HttpServletRequest request, AppCredentialDO appCredentialDO) {
// 1.2 校验 appId 是否能获取到对应的 appSecret // 1.2 校验 appId 是否能获取到对应的 appSecret
String appId = request.getHeader(signature.appId()); String appId = request.getHeader(signature.appId());
String appSecret = signatureRedisDAO.getAppSecret(appId);
Assert.notNull(appSecret, "[appId({})] 找不到对应的 appSecret", appId);
// 2. 校验签名【重要!】 // 2. 校验签名【重要!】
String clientSignature = request.getHeader(signature.sign()); // 客户端签名 String clientSignature = request.getHeader(signature.sign()); // 客户端签名
String serverSignatureString = buildSignatureString(signature, request, appSecret); // 服务端签名字符串 String serverSignatureString = buildSignatureString(signature, request, appCredentialDO.getAppSecret()); // 服务端签名字符串
String serverSignature = DigestUtil.sha256Hex(serverSignatureString); // 服务端签名 String serverSignature = DigestUtil.sha256Hex(serverSignatureString); // 服务端签名
log.error(DigestUtil.sha256Hex(serverSignatureString));
if (ObjUtil.notEqual(clientSignature, serverSignature)) { if (ObjUtil.notEqual(clientSignature, serverSignature)) {
return false; return false;
} }
// 3. 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 ) // 3. 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 )
String nonce = request.getHeader(signature.nonce()); String nonce = request.getHeader(signature.nonce());
if (BooleanUtil.isFalse(signatureRedisDAO.setNonce(appId, nonce, signature.timeout() * 2, signature.timeUnit()))) { signatureRedisDAO.setNonce(appId, nonce, signature.timeout() * 2, signature.timeUnit());
String timestamp = request.getHeader(signature.timestamp());
log.info("[verifySignature][appId({}) timestamp({}) nonce({}) sign({}) 存在重复请求]", appId, timestamp, nonce, clientSignature);
throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), "存在重复请求");
}
return true; return true;
} }
/** /**
* 校验请求头加签参数 * 校验请求头加签参数
* <p> *
* 1. appId 是否为空 * 1. appId 是否为空
* 2. timestamp 是否为空,请求是否已经超时,默认 10 分钟 * 2. timestamp 是否为空,请求是否已经超时,默认 10 分钟
* 3. nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了 * 3. nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了
...@@ -113,18 +229,21 @@ public class ApiSignatureAspect { ...@@ -113,18 +229,21 @@ public class ApiSignatureAspect {
// 2. 检查 timestamp 是否超出允许的范围 (重点一:此处需要取绝对值) // 2. 检查 timestamp 是否超出允许的范围 (重点一:此处需要取绝对值)
long expireTime = signature.timeUnit().toMillis(signature.timeout()); long expireTime = signature.timeUnit().toMillis(signature.timeout());
long requestTimestamp = Long.parseLong(timestamp); long requestTimestamp = Long.parseLong(timestamp);
log.error("当前时间:"+System.currentTimeMillis()+"");;
long timestampDisparity = Math.abs(System.currentTimeMillis() - requestTimestamp); long timestampDisparity = Math.abs(System.currentTimeMillis() - requestTimestamp);
if (timestampDisparity > expireTime) { if (timestampDisparity > expireTime) {
return false; throw new ServiceException(TIMESTAMP_EXCEPTION);
} }
// 3. 检查 nonce 是否存在,有且仅能使用一次 // 3. 检查 nonce 是否存在,有且仅能使用一次
return signatureRedisDAO.getNonce(appId, nonce) == null; return signatureRedisDAO.getNonce(appId, nonce) == null;
} }
/** /**
* 构建签名字符串 * 构建签名字符串
* <p> *
* 格式为 = 请求参数 + 请求体 + 请求头 + 密钥 * 格式为 = 请求参数 + 请求体 + 请求头 + 密钥
* *
* @param signature signature * @param signature signature
...@@ -133,19 +252,17 @@ public class ApiSignatureAspect { ...@@ -133,19 +252,17 @@ public class ApiSignatureAspect {
* @return 签名字符串 * @return 签名字符串
*/ */
private String buildSignatureString(ApiSignature signature, HttpServletRequest request, String appSecret) { private String buildSignatureString(ApiSignature signature, HttpServletRequest request, String appSecret) {
SortedMap<String, String> parameterMap = getRequestParameterMap(request); // 请求头
SortedMap<String, String> headerMap = getRequestHeaderMap(signature, request); // 请求参数 SortedMap<String, String> headerMap = getRequestHeaderMap(signature, request); // 请求参数
String requestBody = StrUtil.nullToDefault(ServletUtils.getBody(request), ""); // 请求体 log.error(MapUtil.join(headerMap, "&", "=")
return MapUtil.join(parameterMap, "&", "=") + "&appSecret=" + appSecret);
+ requestBody return MapUtil.join(headerMap, "&", "=")
+ MapUtil.join(headerMap, "&", "=") + "&appSecret=" + appSecret;
+ appSecret;
} }
/** /**
* 获取请求头加签参数 Map * 获取请求头加签参数 Map
* *
* @param request 请求 * @param request 请求
* @param signature 签名注解 * @param signature 签名注解
* @return signature params * @return signature params
*/ */
......
package com.luhu.computility.framework.signature.core.redis; package com.luhu.computility.framework.signature.core.redis;
import cn.hutool.core.util.BooleanUtil;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
...@@ -25,6 +26,11 @@ public class ApiSignatureRedisDAO { ...@@ -25,6 +26,11 @@ public class ApiSignatureRedisDAO {
private static final String SIGNATURE_NONCE = "api_signature_nonce:%s:%s"; private static final String SIGNATURE_NONCE = "api_signature_nonce:%s:%s";
/** /**
* 用户套餐使用次数 Key 前缀
*/
public static final String USAGE_KEY = "api_usage:";
/**
* 签名密钥 * 签名密钥
* <p> * <p>
* HASH 结构 * HASH 结构
...@@ -54,4 +60,54 @@ public class ApiSignatureRedisDAO { ...@@ -54,4 +60,54 @@ public class ApiSignatureRedisDAO {
return (String) stringRedisTemplate.opsForHash().get(SIGNATURE_APPID, appId); return (String) stringRedisTemplate.opsForHash().get(SIGNATURE_APPID, appId);
} }
// ========== 用户套餐使用次数 ==========
/**
* 原子自增用户套餐使用次数
*
* @param key Redis key(USAGE_KEY + apiEndpointId + userId)
* @return 当前使用次数
*/
public Long incrementUsage(String key) {
return stringRedisTemplate.opsForValue().increment(key, 1);
}
/**
* 获取用户套餐已使用次数
*
* @param key Redis key
* @return 使用次数
*/
public Long getUsage(String key) {
String value = stringRedisTemplate.opsForValue().get(key);
if (value == null) {
return 0L;
}
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
return 0L;
}
}
/**
* 重置用户套餐使用次数(可选,用于测试或套餐刷新)
*/
public void resetUsage(String key) {
stringRedisTemplate.opsForValue().set(key, "0");
}
/**
* 原子自增用户套餐使用次数,并设置过期时间
*
* @param key Redis key
* @return 当前使用次数
*/
public Integer incrementUsageWithExpire(String key) {
Integer used = stringRedisTemplate.opsForValue().increment(key, 1).intValue();
return used;
}
} }
...@@ -66,9 +66,9 @@ public class ApiSignatureTest { ...@@ -66,9 +66,9 @@ public class ApiSignatureTest {
when(signatureRedisDAO.setNonce(eq(appId), eq(nonce), eq(120), eq(TimeUnit.SECONDS))).thenReturn(true); when(signatureRedisDAO.setNonce(eq(appId), eq(nonce), eq(120), eq(TimeUnit.SECONDS))).thenReturn(true);
// 调用 // 调用
boolean result = apiSignatureAspect.verifySignature(apiSignature, request); //boolean result = apiSignatureAspect.verifySignature(apiSignature, request);
// 断言结果 // 断言结果
assertTrue(result); // assertTrue(result);
} }
} }
\ No newline at end of file
package com.luhu.computility.module.apihub.controller.admin.userapiusage.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "用户对应接口 ")
@Data
@ExcelIgnoreUnannotated
public class JoinUserApiUsageResult {
@Schema(description = "记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2826")
@ExcelProperty("记录ID")
private Long id;
@Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2826")
@ExcelProperty("用户ID")
private Long userId;
@Schema(description = "接口ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2826")
@ExcelProperty("接口ID")
private Long apiEndPointId;
@Schema(description = "接口名", requiredMode = Schema.RequiredMode.REQUIRED, example = "26592")
@ExcelProperty("接口名")
private String apiEndPintName;
@Schema(description = "购买的API应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "10347")
private String apiName;
@Schema(description = "购买的套餐名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "8957")
private String packageName;
@Schema(description = "套餐最大可用次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "8957")
private Integer packageTimes;
@Schema(description = "套餐价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "8957")
private Integer packagePrice;
@Schema(description = "套餐有效期", requiredMode = Schema.RequiredMode.REQUIRED, example = "8957")
private Integer packageValidDays;
@Schema(description = "已使用次数", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("已使用次数")
private Integer usedTimes;
@Schema(description = "过期时间")
@ExcelProperty("过期时间")
private LocalDateTime expireTime;
@Schema(description = "备注", example = "随便")
@ExcelProperty("备注")
private String remark;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}
\ No newline at end of file
...@@ -37,6 +37,14 @@ public interface ApiEndpointMapper extends BaseMapperX<ApiEndpointDO> { ...@@ -37,6 +37,14 @@ public interface ApiEndpointMapper extends BaseMapperX<ApiEndpointDO> {
return selectList(); return selectList();
} }
default ApiEndpointDO getApiEndpointByPathAndMethod(String path, String method) {
return selectOne(new LambdaQueryWrapperX<ApiEndpointDO>()
.eqIfPresent(ApiEndpointDO::getMethod, method)
.eqIfPresent(ApiEndpointDO::getPath, path));
}
} }
\ No newline at end of file
...@@ -26,4 +26,9 @@ public interface AppCredentialMapper extends BaseMapperX<AppCredentialDO> { ...@@ -26,4 +26,9 @@ public interface AppCredentialMapper extends BaseMapperX<AppCredentialDO> {
.orderByDesc(AppCredentialDO::getId)); .orderByDesc(AppCredentialDO::getId));
} }
default AppCredentialDO selectOneByAppId(String appId) {
return selectOne(new LambdaQueryWrapperX<AppCredentialDO>()
.eqIfPresent(AppCredentialDO::getAppId, appId));
}
} }
\ No newline at end of file
...@@ -3,6 +3,7 @@ package com.luhu.computility.module.apihub.dal.mysql.userapiusage; ...@@ -3,6 +3,7 @@ package com.luhu.computility.module.apihub.dal.mysql.userapiusage;
import java.util.*; import java.util.*;
import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.luhu.computility.framework.common.pojo.PageResult; import com.luhu.computility.framework.common.pojo.PageResult;
import com.luhu.computility.framework.mybatis.core.query.LambdaQueryWrapperX; import com.luhu.computility.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.luhu.computility.framework.mybatis.core.mapper.BaseMapperX; import com.luhu.computility.framework.mybatis.core.mapper.BaseMapperX;
...@@ -10,7 +11,10 @@ import com.luhu.computility.framework.mybatis.core.query.MPJLambdaWrapperX; ...@@ -10,7 +11,10 @@ import com.luhu.computility.framework.mybatis.core.query.MPJLambdaWrapperX;
import com.luhu.computility.module.apihub.controller.app.userapiusage.vo.AppUserApiUsageRespVO; import com.luhu.computility.module.apihub.controller.app.userapiusage.vo.AppUserApiUsageRespVO;
import com.luhu.computility.module.apihub.dal.dataobject.api.ApiDO; import com.luhu.computility.module.apihub.dal.dataobject.api.ApiDO;
import com.luhu.computility.module.apihub.dal.dataobject.apicategory.ApiCategoryDO; import com.luhu.computility.module.apihub.dal.dataobject.apicategory.ApiCategoryDO;
import com.luhu.computility.module.apihub.dal.dataobject.apiendpoint.ApiEndpointDO;
import com.luhu.computility.module.apihub.dal.dataobject.apiendpointrel.ApiEndpointRelDO;
import com.luhu.computility.module.apihub.dal.dataobject.userapiusage.UserApiUsageDO; import com.luhu.computility.module.apihub.dal.dataobject.userapiusage.UserApiUsageDO;
import com.luhu.computility.module.apihub.service.apiendpointrel.ApiEndpointRelService;
import com.luhu.computility.module.member.dal.dataobject.user.MemberUserDO; import com.luhu.computility.module.member.dal.dataobject.user.MemberUserDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import com.luhu.computility.module.apihub.controller.admin.userapiusage.vo.*; import com.luhu.computility.module.apihub.controller.admin.userapiusage.vo.*;
...@@ -46,4 +50,25 @@ public interface UserApiUsageMapper extends BaseMapperX<UserApiUsageDO> { ...@@ -46,4 +50,25 @@ public interface UserApiUsageMapper extends BaseMapperX<UserApiUsageDO> {
.orderByDesc(UserApiUsageDO::getCreateTime)); .orderByDesc(UserApiUsageDO::getCreateTime));
} }
default List<JoinUserApiUsageResult> selectJoinUserApiUsageResult(Long userId, Long apiIdEndpoint) {
return selectJoinList(JoinUserApiUsageResult.class, new MPJLambdaWrapper<UserApiUsageDO>()
.select(UserApiUsageDO::getApiName)
.selectAs(UserApiUsageDO::getId, JoinUserApiUsageResult::getId)
.selectAs(ApiEndpointDO::getId, JoinUserApiUsageResult::getApiEndPointId)
.selectAs(UserApiUsageDO::getUserId, JoinUserApiUsageResult::getUserId)
.selectAs(ApiEndpointDO::getId, JoinUserApiUsageResult::getApiEndPointId)
.selectAs(UserApiUsageDO::getExpireTime, JoinUserApiUsageResult::getExpireTime)
.selectAs(UserApiUsageDO::getUsedTimes, JoinUserApiUsageResult::getUsedTimes)
.selectAs(UserApiUsageDO::getPackageTimes, JoinUserApiUsageResult::getPackageTimes)
.select(ApiEndpointDO::getName, ApiEndpointDO::getPath, ApiEndpointDO::getMethod) // 额外查接口信息
.innerJoin(ApiEndpointRelDO.class, ApiEndpointRelDO::getApiId, UserApiUsageDO::getApiId)
.innerJoin(ApiEndpointDO.class, ApiEndpointDO::getId, ApiEndpointRelDO::getApiEndpointId)
.eq(UserApiUsageDO::getUserId, userId)
.eqIfExists(ApiEndpointDO::getId, apiIdEndpoint)
/*.gt(UserApiUsageDO::getExpireTime, new Date()) // 没过期
.apply("(package_times - used_times) > 0") */ // 还有剩余额度
);
}
} }
\ No newline at end of file
...@@ -19,8 +19,15 @@ public interface ErrorCodeConstants { ...@@ -19,8 +19,15 @@ public interface ErrorCodeConstants {
ErrorCode API_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR = new ErrorCode(1_010_001_014, "API订单更新支付状态失败,支付单编号不匹配"); ErrorCode API_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR = new ErrorCode(1_010_001_014, "API订单更新支付状态失败,支付单编号不匹配");
ErrorCode API_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1_010_001_015, "API订单更新支付状态失败,支付单状态不是【支付成功】状态"); ErrorCode API_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1_010_001_015, "API订单更新支付状态失败,支付单状态不是【支付成功】状态");
ErrorCode API_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1_010_001_016, "API订单更新支付状态失败,支付单金额不匹配"); ErrorCode API_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1_010_001_016, "API订单更新支付状态失败,支付单金额不匹配");
ErrorCode API_ENDPOINT_NOT_EXISTS = new ErrorCode(1_010_001_017, "API 接口不存在"); ErrorCode API_ENDPOINT_NOT_EXISTS = new ErrorCode(1_010_001_017, "接口不存在");
ErrorCode API_ENDPOINT_REL_NOT_EXISTS = new ErrorCode(1_010_001_018, "API 应用与接口关系不存在"); ErrorCode API_ENDPOINT_REL_NOT_EXISTS = new ErrorCode(1_010_001_018, "API 应用与接口关系不存在");
ErrorCode APP_CREDENTIAL_NOT_EXISTS = new ErrorCode(1_010_001_019, "用户密钥信息不存在"); ErrorCode APP_CREDENTIAL_NOT_EXISTS = new ErrorCode(1_010_001_019, "用户密钥信息不存在");
ErrorCode API_ENDPOINT_APPLICATION_REL_NOT_EXISTS = new ErrorCode(1_010_001_020, "行业应用与接口关联不存在"); ErrorCode API_ENDPOINT_APPLICATION_REL_NOT_EXISTS = new ErrorCode(1_010_001_020, "行业应用与接口关联不存在");
ErrorCode HEAD_EXCEPTION = new ErrorCode(1_010_001_021, "Header异常,缺少必须的参数");
ErrorCode API_ENDPOINT_NOT_AVAILABLE = new ErrorCode(1_010_001_022, "未订阅该接口");
ErrorCode API_ENDPOINT_EXPIRED = new ErrorCode(1_010_001_023, "接口调用额度不足或已过期");
ErrorCode TIMESTAMP_EXCEPTION = new ErrorCode(1_010_001_024, "参数timestamp异常");
ErrorCode INVALID_APPID = new ErrorCode(1_010_001_025, "无效的Appid");
} }
...@@ -68,6 +68,14 @@ public interface ApiEndpointService { ...@@ -68,6 +68,14 @@ public interface ApiEndpointService {
List<ApiEndpointDO> getApiEndpointList(); List<ApiEndpointDO> getApiEndpointList();
/**
* 根据path和方法 获得API 接口
*
* @param path、method 编号
* @return API 接口
*/
ApiEndpointDO getApiEndpointByPathAndMethod(String path, String method);
} }
\ No newline at end of file
...@@ -88,6 +88,11 @@ public class ApiEndpointServiceImpl implements ApiEndpointService { ...@@ -88,6 +88,11 @@ public class ApiEndpointServiceImpl implements ApiEndpointService {
} }
@Override
public ApiEndpointDO getApiEndpointByPathAndMethod(String path, String method) {
return apiEndpointMapper.getApiEndpointByPathAndMethod(path, method);
}
} }
\ No newline at end of file
...@@ -59,4 +59,13 @@ public interface AppCredentialService { ...@@ -59,4 +59,13 @@ public interface AppCredentialService {
*/ */
PageResult<AppCredentialDO> getAppCredentialPage(AppCredentialPageReqVO pageReqVO); PageResult<AppCredentialDO> getAppCredentialPage(AppCredentialPageReqVO pageReqVO);
/**
* 获得用户密钥信息
*
* @param appId 编号
* @return 用户密钥信息
*/
AppCredentialDO getAppSecretByAppid(String appId);
} }
\ No newline at end of file
...@@ -82,4 +82,12 @@ public class AppCredentialServiceImpl implements AppCredentialService { ...@@ -82,4 +82,12 @@ public class AppCredentialServiceImpl implements AppCredentialService {
return appCredentialMapper.selectPage(pageReqVO); return appCredentialMapper.selectPage(pageReqVO);
} }
@Override
public AppCredentialDO getAppSecretByAppid(String appId) {
return appCredentialMapper.selectOneByAppId(appId);
}
} }
\ No newline at end of file
...@@ -69,4 +69,8 @@ public interface UserApiUsageService { ...@@ -69,4 +69,8 @@ public interface UserApiUsageService {
*/ */
PageResult<AppUserApiUsageRespVO> getAppUserApiUsagePage(UserApiUsagePageReqVO pageReqVO); PageResult<AppUserApiUsageRespVO> getAppUserApiUsagePage(UserApiUsagePageReqVO pageReqVO);
List<JoinUserApiUsageResult> selectJoinUserApiUsageResult(Long userId, Long apiIdEndpoint);
void asyncUpdateUsage(Long id, Integer used);
} }
\ No newline at end of file
...@@ -2,6 +2,7 @@ package com.luhu.computility.module.apihub.service.userapiusage; ...@@ -2,6 +2,7 @@ package com.luhu.computility.module.apihub.service.userapiusage;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import com.luhu.computility.module.apihub.controller.app.userapiusage.vo.AppUserApiUsageRespVO; import com.luhu.computility.module.apihub.controller.app.userapiusage.vo.AppUserApiUsageRespVO;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
...@@ -88,4 +89,21 @@ public class UserApiUsageServiceImpl implements UserApiUsageService { ...@@ -88,4 +89,21 @@ public class UserApiUsageServiceImpl implements UserApiUsageService {
return userApiUsageMapper.selectAppPage(pageReqVO); return userApiUsageMapper.selectAppPage(pageReqVO);
} }
@Override
public List<JoinUserApiUsageResult> selectJoinUserApiUsageResult(Long userId, Long apiIdEndpoint) {
return userApiUsageMapper.selectJoinUserApiUsageResult(userId, apiIdEndpoint);
}
@Override
@Async
public void asyncUpdateUsage(Long id, Integer used) {
// 校验存在
validateUserApiUsageExists(id);
UserApiUsageDO userApiUsage = getUserApiUsage(id);
userApiUsage.setUsedTimes(userApiUsage.getUsedTimes() - used);
userApiUsageMapper.updateById(userApiUsage);
}
} }
\ No newline at end of file
package com.luhu.computility.module.external.utils; package com.luhu.computility.module.apihub.utils;
import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.crypto.digest.DigestUtil;
......
package com.luhu.computility.module.external.utils; package com.luhu.computility.module.apihub.utils;
import cn.hutool.crypto.Mode; import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding; import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES; import cn.hutool.crypto.symmetric.AES;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
/** /**
* @version 1.0 * @version 1.0
......
...@@ -7,6 +7,7 @@ import com.luhu.computility.framework.common.exception.ServiceException; ...@@ -7,6 +7,7 @@ import com.luhu.computility.framework.common.exception.ServiceException;
import com.luhu.computility.framework.common.exception.enums.GlobalResponseCodeConstants; import com.luhu.computility.framework.common.exception.enums.GlobalResponseCodeConstants;
import com.luhu.computility.framework.common.pojo.CommonResult; import com.luhu.computility.framework.common.pojo.CommonResult;
import com.luhu.computility.framework.common.util.http.HttpUtils; import com.luhu.computility.framework.common.util.http.HttpUtils;
import com.luhu.computility.framework.signature.core.annotation.ApiSignature;
import com.luhu.computility.module.external.controller.openapi.dto.ImageRespDTO; import com.luhu.computility.module.external.controller.openapi.dto.ImageRespDTO;
import com.luhu.computility.module.external.controller.openapi.dto.PoetryImageReqDTO; import com.luhu.computility.module.external.controller.openapi.dto.PoetryImageReqDTO;
import com.luhu.computility.module.external.controller.openapi.dto.TextToImageReqDTO; import com.luhu.computility.module.external.controller.openapi.dto.TextToImageReqDTO;
...@@ -47,7 +48,7 @@ public class AigcNewApiController { ...@@ -47,7 +48,7 @@ public class AigcNewApiController {
@ApiAccessLog @ApiAccessLog
@PostMapping(value = "/text-to-image/season") @PostMapping(value = "/text-to-image/season")
@Operation(summary = "四季和景点id生成图", description = "接收简单生图参数,将生成图片保存在本地服务器,并返回生成图片的url") @Operation(summary = "四季和景点id生成图", description = "接收简单生图参数,将生成图片保存在本地服务器,并返回生成图片的url")
//@ApiSignature @ApiSignature
public CommonResult<ImageRespDTO> textToImageV2(@RequestBody TextToImageReqDTO textToImageReqDTO){ public CommonResult<ImageRespDTO> textToImageV2(@RequestBody TextToImageReqDTO textToImageReqDTO){
try { try {
String requestBody = JSONUtil.toJsonStr(textToImageReqDTO); String requestBody = JSONUtil.toJsonStr(textToImageReqDTO);
...@@ -71,7 +72,7 @@ public class AigcNewApiController { ...@@ -71,7 +72,7 @@ public class AigcNewApiController {
@ApiAccessLog @ApiAccessLog
@PostMapping("/text-to-image/poetry") @PostMapping("/text-to-image/poetry")
@Operation(summary = "获取藏头诗图片", description = "接收关键词、景点id、省份id这些参数,返回藏头诗图片url") @Operation(summary = "获取藏头诗图片", description = "接收关键词、景点id、省份id这些参数,返回藏头诗图片url")
//@ApiSignature @ApiSignature
public CommonResult<ImageRespDTO> textToImageByPoetry(@RequestBody PoetryImageReqDTO poetryImageReqDTO){ public CommonResult<ImageRespDTO> textToImageByPoetry(@RequestBody PoetryImageReqDTO poetryImageReqDTO){
try { try {
String requestBody = JSONUtil.toJsonStr(poetryImageReqDTO); String requestBody = JSONUtil.toJsonStr(poetryImageReqDTO);
......
...@@ -16,19 +16,6 @@ import com.luhu.computility.module.external.controller.openapi.dto.ViewImageReqD ...@@ -16,19 +16,6 @@ import com.luhu.computility.module.external.controller.openapi.dto.ViewImageReqD
import com.luhu.computility.module.external.controller.openapi.dto.ViewSourceRespDTO; import com.luhu.computility.module.external.controller.openapi.dto.ViewSourceRespDTO;
import com.luhu.computility.module.external.controller.openapi.dto.ViewVideoReqDTO; import com.luhu.computility.module.external.controller.openapi.dto.ViewVideoReqDTO;
import com.luhu.computility.module.external.controller.openapi.service.OpenApiService; import com.luhu.computility.module.external.controller.openapi.service.OpenApiService;
import com.luhu.computility.module.external.controller.openapi.dto.AIQAReqDTO;
import com.luhu.computility.module.external.controller.openapi.dto.AIQARespDTO;
import com.luhu.computility.module.external.controller.openapi.dto.CeateVideoStreamReqDTO;
import com.luhu.computility.module.external.controller.openapi.dto.CeateVideoStreamRespDTO;
import com.luhu.computility.module.external.controller.openapi.dto.ConversationReqDTO;
import com.luhu.computility.module.external.controller.openapi.dto.ConversationRespDTO;
import com.luhu.computility.module.external.controller.openapi.dto.GenerateFaceSwapRespDTO;
import com.luhu.computility.module.external.controller.openapi.dto.MatchImageRespDTO;
import com.luhu.computility.module.external.controller.openapi.dto.UploadImageRespDTO;
import com.luhu.computility.module.external.controller.openapi.dto.ViewImageReqDTO;
import com.luhu.computility.module.external.controller.openapi.dto.ViewSourceRespDTO;
import com.luhu.computility.module.external.controller.openapi.dto.ViewVideoReqDTO;
import com.luhu.computility.module.external.controller.openapi.service.OpenApiService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.Parameters;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment