This commit is contained in:
六如
2024-10-25 20:28:42 +08:00
parent 8b34c0a80d
commit dabf8032ca
87 changed files with 1031 additions and 307 deletions

View File

@@ -38,7 +38,7 @@
<commons-logging.version>1.2</commons-logging.version>
<validation-api.version>2.0.1.Final</validation-api.version>
<hibernate-validator.version>6.0.13.Final</hibernate-validator.version>
<fastmybatis.version>3.0.11</fastmybatis.version>
<fastmybatis.version>3.0.12</fastmybatis.version>
</properties>
<dependencies>
@@ -229,7 +229,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<version>1.18.34</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>

View File

@@ -11,6 +11,8 @@ import java.io.Serializable;
public class ApiInfoDTO implements Serializable {
private static final long serialVersionUID = 2183251167679411550L;
private Long id;
/**
* 所属应用
*/

View File

@@ -0,0 +1,11 @@
package com.gitee.sop.gateway.common;
/**
* @author 六如
*/
public class CacheKey {
public static final String KEY_API = "sop:api";
public static final String KEY_ISV = "sop:isv";
public static final String KEY_SEC = "sop:sec";
public static final String KEY_ISV_PERM = "sop:isv-perm";
}

View File

@@ -12,5 +12,6 @@ public class SopConstants {
public static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8;
public static final String UTF8 = "UTF-8";
public static final String NULL = "null";
}

View File

@@ -1,4 +1,4 @@
package com.gitee.sop.gateway.common;
package com.gitee.sop.gateway.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;

View File

@@ -0,0 +1,26 @@
package com.gitee.sop.gateway.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Objects;
/**
* @author 六如
*/
@AllArgsConstructor
@Getter
public enum YesOrNoEnum {
YES(1),
NO(0);
private final int value;
public static YesOrNoEnum of(Integer value) {
return Objects.equals(value, YES.value) ? YES : NO;
}
public static YesOrNoEnum of(Boolean value) {
return Objects.equals(value, true) ? YES : NO;
}
}

View File

@@ -7,12 +7,15 @@ import com.gitee.sop.gateway.service.RouteService;
import com.gitee.sop.gateway.service.RouteServiceImpl;
import com.gitee.sop.gateway.service.interceptor.internal.ResultRouteInterceptor;
import com.gitee.sop.gateway.service.manager.ApiManager;
import com.gitee.sop.gateway.service.manager.IsvApiPermissionManager;
import com.gitee.sop.gateway.service.manager.IsvManager;
import com.gitee.sop.gateway.service.manager.SecretManager;
import com.gitee.sop.gateway.service.manager.impl.LocalApiManagerImpl;
import com.gitee.sop.gateway.service.manager.impl.LocalIsvApiPermissionManagerImpl;
import com.gitee.sop.gateway.service.manager.impl.LocalIsvManagerImpl;
import com.gitee.sop.gateway.service.manager.impl.LocalSecretManagerImpl;
import com.gitee.sop.gateway.service.manager.impl.RedisApiManagerImpl;
import com.gitee.sop.gateway.service.manager.impl.RedisIsvApiPermissionManagerImpl;
import com.gitee.sop.gateway.service.manager.impl.RedisIsvManagerImpl;
import com.gitee.sop.gateway.service.manager.impl.RedisSecretManager;
import lombok.extern.slf4j.Slf4j;
@@ -66,6 +69,18 @@ public class IndexConfig {
return new RedisSecretManager();
}
@Bean
@ConditionalOnProperty(value = "gateway.manager.isv-api-perm", havingValue = "local", matchIfMissing = true)
public IsvApiPermissionManager localIsvApiPermissionManager() {
return new LocalIsvApiPermissionManagerImpl();
}
@Bean
@ConditionalOnProperty(value = "gateway.manager.isv-api-perm", havingValue = "redis")
public IsvApiPermissionManager redisIsvApiPermissionManager() {
return new RedisIsvApiPermissionManagerImpl();
}
// DEFAULT ROUTE INTERCEPTOR
@Bean

View File

@@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@@ -22,10 +22,10 @@ import java.io.IOException;
@Controller
public class IndexController {
@Resource
@Autowired
private RouteService routeService;
@Resource
@Autowired
private ParamExecutor<HttpServletRequest, HttpServletResponse> paramExecutor;
@GetMapping("/")

View File

@@ -1,7 +1,6 @@
package com.gitee.sop.gateway.dao.entity;
import java.time.LocalDateTime;
import java.util.Date;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.PkStrategy;
@@ -20,9 +19,15 @@ import lombok.Data;
@Data
public class IsvKeys {
/**
* id
*/
private Long id;
private String appId;
/**
* isv_info.id
*/
private Long isvId;
/**
* 秘钥格式1PKCS8(JAVA适用)2PKCS1(非JAVA适用)
@@ -49,8 +54,14 @@ public class IsvKeys {
*/
private String privateKeyPlatform;
/**
* 添加时间
*/
private LocalDateTime addTime;
/**
* 修改时间
*/
private LocalDateTime updateTime;

View File

@@ -0,0 +1,48 @@
package com.gitee.sop.gateway.dao.entity;
import java.time.LocalDateTime;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.PkStrategy;
import com.gitee.fastmybatis.annotation.Table;
import lombok.Data;
/**
* 表名perm_group_permission
* 备注:组权限表
*
* @author 六如
*/
@Table(name = "perm_group_permission", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT))
@Data
public class PermGroupPermission {
/**
* id
*/
private Long id;
/**
* 组id
*/
private Long groupId;
/**
* api_info.id
*/
private Long apiId;
/**
* 添加时间
*/
private LocalDateTime addTime;
/**
* 修改时间
*/
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,39 @@
package com.gitee.sop.gateway.dao.entity;
import java.time.LocalDateTime;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.PkStrategy;
import com.gitee.fastmybatis.annotation.Table;
import lombok.Data;
/**
* 表名perm_isv_group
* 备注isv分组
*
* @author 六如
*/
@Table(name = "perm_isv_group", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT))
@Data
public class PermIsvGroup {
private Long id;
/**
* isv_info表id
*/
private Long isvId;
/**
* 组id
*/
private Long groupId;
private LocalDateTime addTime;
private LocalDateTime updateTime;
}

View File

@@ -3,10 +3,12 @@ package com.gitee.sop.gateway.dao.mapper;
import com.gitee.fastmybatis.core.mapper.BaseMapper;
import com.gitee.sop.gateway.dao.entity.ApiInfo;
import org.apache.ibatis.annotations.Mapper;
/**
* @author 六如
*/
@Mapper
public interface ApiInfoMapper extends BaseMapper<ApiInfo> {
default ApiInfo getByNameVersion(String apiName, String apiVersion) {

View File

@@ -2,10 +2,12 @@ package com.gitee.sop.gateway.dao.mapper;
import com.gitee.fastmybatis.core.mapper.BaseMapper;
import com.gitee.sop.gateway.dao.entity.IsvInfo;
import org.apache.ibatis.annotations.Mapper;
/**
* @author 六如
*/
@Mapper
public interface IsvInfoMapper extends BaseMapper<IsvInfo> {
default IsvInfo getByAppId(String appId) {

View File

@@ -2,14 +2,16 @@ package com.gitee.sop.gateway.dao.mapper;
import com.gitee.fastmybatis.core.mapper.BaseMapper;
import com.gitee.sop.gateway.dao.entity.IsvKeys;
import org.apache.ibatis.annotations.Mapper;
/**
* @author 六如
*/
@Mapper
public interface IsvKeysMapper extends BaseMapper<IsvKeys> {
default IsvKeys getByAppId(String appId) {
return this.get(IsvKeys::getAppId, appId);
default IsvKeys getByIsvId(Long isvId) {
return this.get(IsvKeys::getIsvId, isvId);
}
}

View File

@@ -0,0 +1,13 @@
package com.gitee.sop.gateway.dao.mapper;
import com.gitee.fastmybatis.core.mapper.BaseMapper;
import com.gitee.sop.gateway.dao.entity.PermGroupPermission;
import org.apache.ibatis.annotations.Mapper;
/**
* @author 六如
*/
@Mapper
public interface PermGroupPermissionMapper extends BaseMapper<PermGroupPermission> {
}

View File

@@ -0,0 +1,13 @@
package com.gitee.sop.gateway.dao.mapper;
import com.gitee.fastmybatis.core.mapper.BaseMapper;
import com.gitee.sop.gateway.dao.entity.PermIsvGroup;
import org.apache.ibatis.annotations.Mapper;
/**
* @author 六如
*/
@Mapper
public interface PermIsvGroupMapper extends BaseMapper<PermIsvGroup> {
}

View File

@@ -15,7 +15,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@@ -36,7 +36,7 @@ public class ParamExecutorImpl implements ParamExecutor<HttpServletRequest, Http
private static final String MULTIPART = "multipart";
private static final String FORM = "form";
@Resource
@Autowired
private ApiConfig apiConfig;
@Override

View File

@@ -26,7 +26,7 @@ import org.springframework.util.ObjectUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -44,13 +44,13 @@ import java.util.Optional;
@Slf4j
public class RouteServiceImpl implements RouteService {
@Resource
@Autowired
protected Validator validator;
@Resource
@Autowired
protected GenericServiceInvoker genericServiceInvoker;
@Resource
@Autowired
protected ExceptionExecutor exceptionExecutor;
@Autowired(required = false)

View File

@@ -1,7 +1,7 @@
package com.gitee.sop.gateway.service.dubbo;
import com.gitee.sop.gateway.common.ApiInfoDTO;
import com.gitee.sop.gateway.common.StatusEnum;
import com.gitee.sop.gateway.common.enums.StatusEnum;
import com.gitee.sop.gateway.dao.entity.ApiInfo;
import com.gitee.sop.gateway.dao.mapper.ApiInfoMapper;
import com.gitee.sop.gateway.service.manager.ApiManager;
@@ -11,7 +11,7 @@ import com.gitee.sop.support.service.dto.RegisterDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author 六如
@@ -20,10 +20,12 @@ import javax.annotation.Resource;
@DubboService
public class ApiRegisterServiceImpl implements ApiRegisterService {
@Resource
private static final int REG_SOURCE_SYS = 1;
@Autowired
private ApiManager apiCacheManager;
@Resource
@Autowired
private ApiInfoMapper apiInfoMapper;
@Override
@@ -37,7 +39,7 @@ public class ApiRegisterServiceImpl implements ApiRegisterService {
apiInfo = new ApiInfo();
}
CopyUtil.copyPropertiesIgnoreNull(apiInfoDTO, apiInfo);
apiInfo.setRegSource(1);
apiInfo.setRegSource(REG_SOURCE_SYS);
// 保存到数据库
apiInfoMapper.saveOrUpdate(apiInfo);
// 保存到缓存

View File

@@ -1,26 +0,0 @@
package com.gitee.sop.gateway.service.dubbo;
import com.gitee.sop.gateway.service.manager.IsvManager;
import com.gitee.sop.support.service.IsvService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import javax.annotation.Resource;
/**
* @author 六如
*/
@DubboService
@Slf4j
public class IsvServiceImpl implements IsvService {
@Resource
private IsvManager isvManager;
@Override
public void refresh(String appId) {
log.info("refresh Isv, appId={}", appId);
// 刷新isv信息
isvManager.reload(appId);
}
}

View File

@@ -0,0 +1,50 @@
package com.gitee.sop.gateway.service.dubbo;
import com.gitee.sop.gateway.service.manager.ApiManager;
import com.gitee.sop.gateway.service.manager.IsvApiPermissionManager;
import com.gitee.sop.gateway.service.manager.IsvManager;
import com.gitee.sop.gateway.service.manager.SecretManager;
import com.gitee.sop.support.service.RefreshService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
/**
* @author 六如
*/
@DubboService
@Slf4j
public class RefreshServiceImpl implements RefreshService {
@Autowired
private IsvManager isvManager;
@Autowired
private SecretManager secretManager;
@Autowired
private IsvApiPermissionManager isvApiPermissionManager;
@Autowired
private ApiManager apiManager;
@Override
public void refreshApi(Collection<Long> apiIds) {
apiManager.refresh(apiIds);
}
@Override
public void refreshIsv(Collection<String> appIds) {
isvManager.refresh(appIds);
}
@Override
public void refreshIsvPerm(Collection<Long> isvIds) {
isvApiPermissionManager.refresh(isvIds);
}
@Override
public void refreshSecret(Collection<Long> isvIds) {
secretManager.refresh(isvIds);
}
}

View File

@@ -2,12 +2,14 @@ package com.gitee.sop.gateway.service.manager;
import com.gitee.sop.gateway.common.ApiInfoDTO;
import java.util.Collection;
import java.util.Map;
import java.util.function.Supplier;
/**
* @author 六如
*/
public interface ApiManager {
public interface ApiManager extends Manager<Collection<Long>, Map<Long, ApiInfoDTO>> {
void save(ApiInfoDTO apiInfoDTO);

View File

@@ -0,0 +1,9 @@
package com.gitee.sop.gateway.service.manager;
/**
* @author 六如
*/
public interface CacheManager {
}

View File

@@ -1,12 +1,24 @@
package com.gitee.sop.gateway.service.manager;
import com.gitee.sop.gateway.common.ApiInfoDTO;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* isv接口授权管理
*
* @author 六如
*/
public interface IsvApiPermissionManager {
boolean hasPermission(String appId, String apiNameVersion);
public interface IsvApiPermissionManager extends Manager<Collection<Long>, Map<Long, List<Long>>> {
/**
* isv是否可以访问接口
* @param isvId isvId
* @param apiInfoDTO apiInfoDTO
* @return true:能访问
*/
boolean hasPermission(Long isvId, ApiInfoDTO apiInfoDTO);
}

View File

@@ -2,10 +2,13 @@ package com.gitee.sop.gateway.service.manager;
import com.gitee.sop.gateway.service.manager.dto.IsvDTO;
import java.util.Collection;
import java.util.Map;
/**
* @author 六如
*/
public interface IsvManager {
public interface IsvManager extends Manager<Collection<String>, Map<String, IsvDTO>> {
/**
* 获取isv信息
@@ -14,11 +17,4 @@ public interface IsvManager {
* @return 返回isv信息, 没有返回null
*/
IsvDTO getIsv(String appId);
/**
* 重新加载isv信息到内存中
*
* @param appId appId
*/
void reload(String appId);
}

View File

@@ -0,0 +1,16 @@
package com.gitee.sop.gateway.service.manager;
/**
* @param <T> 入参
* @param <R> 出参
* @author 六如
*/
public interface Manager<T, R> {
R refresh(T id);
default void init() {
}
}

View File

@@ -1,19 +1,20 @@
package com.gitee.sop.gateway.service.manager;
import java.util.Collection;
import java.util.Map;
/**
* 秘钥管理
*
* @author 六如
*/
public interface SecretManager {
public interface SecretManager extends Manager<Collection<Long>, Map<Long, String>> {
/**
* 获取用户上传的公钥
*
* @param appId appId
* @param isvId isvId
* @return 返回公钥内容
*/
String getIsvPublicKey(String appId);
String reload(String appId);
String getIsvPublicKey(Long isvId);
}

View File

@@ -8,6 +8,8 @@ import lombok.Data;
@Data
public class IsvDTO {
private Long id;
private String appId;
private Integer status;

View File

@@ -1,16 +0,0 @@
package com.gitee.sop.gateway.service.manager.impl;
import com.gitee.sop.gateway.service.manager.IsvApiPermissionManager;
import org.springframework.stereotype.Service;
/**
* @author 六如
*/
@Service
public class IsvApiPermissionManagerImpl implements IsvApiPermissionManager {
@Override
public boolean hasPermission(String appId, String apiNameVersion) {
return false;
}
}

View File

@@ -6,20 +6,26 @@ import com.gitee.sop.gateway.dao.mapper.ApiInfoMapper;
import com.gitee.sop.gateway.service.manager.ApiManager;
import com.gitee.sop.gateway.util.CopyUtil;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
/**
* 本地存储接口信息.
* @author 六如
*/
@Slf4j
public class LocalApiManagerImpl implements ApiManager {
private static final Map<String, Optional<ApiInfoDTO>> CACHE = new ConcurrentHashMap<>();
@Resource
@Autowired
protected ApiInfoMapper apiInfoMapper;
@Override
@@ -28,6 +34,7 @@ public class LocalApiManagerImpl implements ApiManager {
CACHE.put(key, Optional.of(apiInfoDTO));
}
@Override
public ApiInfoDTO get(String apiName, String apiVersion) {
String key = apiName + apiVersion;
@@ -36,4 +43,24 @@ public class LocalApiManagerImpl implements ApiManager {
return Optional.ofNullable(CopyUtil.copyBean(apiInfo, ApiInfoDTO::new));
}).orElse(null);
}
@Override
public Map<Long, ApiInfoDTO> refresh(Collection<Long> id) {
log.info("刷新api信息, id={}", id);
Map<Long, ApiInfo> apiIdMap = apiInfoMapper.query()
.in(ApiInfo::getId, id)
.map(ApiInfo::getId, Function.identity());
apiIdMap.values().forEach(this::cache);
return Collections.emptyMap();
}
protected ApiInfoDTO cache(ApiInfo apiInfo) {
ApiInfoDTO apiInfoDTO = CopyUtil.copyBean(apiInfo, ApiInfoDTO::new);
String key = apiInfoDTO.buildApiNameVersion();
CACHE.put(key, Optional.of(apiInfoDTO));
log.info("更新接口本地缓存, apiInfoDTO={}", apiInfoDTO);
return apiInfoDTO;
}
}

View File

@@ -0,0 +1,99 @@
package com.gitee.sop.gateway.service.manager.impl;
import com.gitee.sop.gateway.common.ApiInfoDTO;
import com.gitee.sop.gateway.common.enums.YesOrNoEnum;
import com.gitee.sop.gateway.dao.entity.ApiInfo;
import com.gitee.sop.gateway.dao.entity.PermGroupPermission;
import com.gitee.sop.gateway.dao.entity.PermIsvGroup;
import com.gitee.sop.gateway.dao.mapper.ApiInfoMapper;
import com.gitee.sop.gateway.dao.mapper.PermGroupPermissionMapper;
import com.gitee.sop.gateway.dao.mapper.PermIsvGroupMapper;
import com.gitee.sop.gateway.service.manager.IsvApiPermissionManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 缓存ISV接口权限
*
* @author 六如
*/
@Slf4j
public class LocalIsvApiPermissionManagerImpl implements IsvApiPermissionManager {
// key:isvId, value: List<apiName+apiVersion>
private static final Map<Long, List<Long>> CACHE = new HashMap<>();
@Autowired
private PermGroupPermissionMapper permGroupPermissionMapper;
@Autowired
private PermIsvGroupMapper permIsvGroupMapper;
@Autowired
private ApiInfoMapper apiInfoMapper;
@Override
public boolean hasPermission(Long isvId, ApiInfoDTO apiInfoDTO) {
// 通用接口都可以访问
if (Objects.equals(apiInfoDTO.getIsPermission(), YesOrNoEnum.NO.getValue())) {
return true;
}
return doCheck(isvId, apiInfoDTO);
}
public boolean doCheck(Long isvId, ApiInfoDTO apiInfoDTO) {
List<Long> apiNameVerionList = CACHE.computeIfAbsent(isvId, k -> this.listApiId(isvId));
if (CollectionUtils.isEmpty(apiNameVerionList)) {
return false;
}
return apiNameVerionList.contains(apiInfoDTO.getId());
}
@Override
public Map<Long, List<Long>> refresh(Collection<Long> isvIds) {
log.info("刷新isv接口权限, isvIds={}", isvIds);
if (CollectionUtils.isEmpty(isvIds)) {
return Collections.emptyMap();
}
Map<Long, List<Long>> map = new HashMap<>(isvIds.size() * 2);
for (Long isvId : isvIds) {
List<Long> apiIdList = this.listApiId(isvId);
map.put(isvId, apiIdList);
// 缓存
cache(isvId, apiIdList);
}
return map;
}
protected void cache(Long isvId, List<Long> apiIdList) {
CACHE.put(isvId, apiIdList);
log.info("更新isv接口id本地缓存, isvId={}, apiIdList={}", isvId, apiIdList);
}
protected List<Long> listApiId(Long isvId) {
List<Long> groupIds = permIsvGroupMapper.query()
.eq(PermIsvGroup::getIsvId, isvId)
.listUniqueValue(PermIsvGroup::getGroupId);
if (groupIds.isEmpty()) {
return Collections.emptyList();
}
List<Long> apiIdList = permGroupPermissionMapper.query()
.in(PermGroupPermission::getGroupId, groupIds)
.listUniqueValue(PermGroupPermission::getApiId);
if (apiIdList.isEmpty()) {
return Collections.emptyList();
}
return apiInfoMapper.query()
.select(ApiInfo::getApiName, ApiInfo::getApiVersion)
.in(ApiInfo::getId, apiIdList)
.listUniqueValue(ApiInfo::getId);
}
}

View File

@@ -5,8 +5,13 @@ import com.gitee.sop.gateway.dao.mapper.IsvInfoMapper;
import com.gitee.sop.gateway.service.manager.IsvManager;
import com.gitee.sop.gateway.service.manager.dto.IsvDTO;
import com.gitee.sop.gateway.util.CopyUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
@@ -14,12 +19,12 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* @author 六如
*/
@Slf4j
public class LocalIsvManagerImpl implements IsvManager {
private static final Map<String, Optional<IsvDTO>> CACHE = new ConcurrentHashMap<>();
@Resource
@Autowired
protected IsvInfoMapper isvInfoMapper;
@Override
@@ -31,10 +36,25 @@ public class LocalIsvManagerImpl implements IsvManager {
}
@Override
public void reload(String appId) {
IsvInfo isvInfo = isvInfoMapper.getByAppId(appId);
IsvDTO isvDTO = CopyUtil.copyBean(isvInfo, IsvDTO::new);
public Map<String, IsvDTO> refresh(Collection<String> appIds) {
log.info("刷新isv, appId={}", appIds);
if (CollectionUtils.isEmpty(appIds)) {
return Collections.emptyMap();
}
Map<String, IsvDTO> map = new HashMap<>(appIds.size() * 2);
for (String appId : appIds) {
IsvInfo isvInfo = isvInfoMapper.getByAppId(appId);
IsvDTO isvDTO = CopyUtil.copyBean(isvInfo, IsvDTO::new);
map.put(appId, isvDTO);
cache(appId, isvDTO);
}
return map;
}
protected void cache(String appId, IsvDTO isvDTO) {
CACHE.put(appId, Optional.ofNullable(isvDTO));
log.info("更新isv本地缓存, isvDTO={}", isvDTO);
}
}

View File

@@ -3,8 +3,13 @@ package com.gitee.sop.gateway.service.manager.impl;
import com.gitee.sop.gateway.dao.entity.IsvKeys;
import com.gitee.sop.gateway.dao.mapper.IsvKeysMapper;
import com.gitee.sop.gateway.service.manager.SecretManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
@@ -12,30 +17,48 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* @author 六如
*/
@Slf4j
public class LocalSecretManagerImpl implements SecretManager {
private static final Map<String, Optional<String>> CACHE = new ConcurrentHashMap<>();
private static final Map<Long, Optional<String>> CACHE = new ConcurrentHashMap<>();
@Resource
@Autowired
protected IsvKeysMapper isvKeysMapper;
@Override
public String getIsvPublicKey(String appId) {
return CACHE.computeIfAbsent(appId, k -> {
String publicKey = isvKeysMapper.query()
.eq(IsvKeys::getAppId, appId)
.getValue(IsvKeys::getPublicKeyIsv);
public String getIsvPublicKey(Long isvId) {
return CACHE.computeIfAbsent(isvId, k -> {
String publicKey = doGetPublicKey(isvId);
return Optional.ofNullable(publicKey);
})
.orElse(null);
}
@Override
public String reload(String appId) {
String publicKey = isvKeysMapper.query()
.eq(IsvKeys::getAppId, appId)
.getValue(IsvKeys::getPublicKeyIsv);
CACHE.put(appId, Optional.ofNullable(publicKey));
return publicKey;
public Map<Long, String> refresh(Collection<Long> isvIds) {
log.info("刷新isv秘钥, isvId={}", isvIds);
if (CollectionUtils.isEmpty(isvIds)) {
return Collections.emptyMap();
}
Map<Long, String> map = new HashMap<>(isvIds.size() * 2);
for (Long isvId : isvIds) {
String publicKey = doGetPublicKey(isvId);
map.put(isvId, publicKey);
this.cache(isvId, publicKey);
}
return map;
}
protected void cache(Long isvId, String publicKey) {
CACHE.put(isvId, Optional.ofNullable(publicKey));
log.info("更新isv秘钥本地缓存, isvId={}", isvId);
}
protected String doGetPublicKey(Long isvId) {
return isvKeysMapper.query()
.eq(IsvKeys::getIsvId, isvId)
.getValue(IsvKeys::getPublicKeyIsv);
}
}

View File

@@ -1,14 +1,17 @@
package com.gitee.sop.gateway.service.manager.impl;
import com.gitee.sop.gateway.common.ApiInfoDTO;
import com.gitee.sop.gateway.common.CacheKey;
import com.gitee.sop.gateway.dao.entity.ApiInfo;
import com.gitee.sop.gateway.util.CopyUtil;
import com.gitee.sop.gateway.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.ObjectUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
/**
@@ -19,10 +22,10 @@ import java.util.List;
@Slf4j
public class RedisApiManagerImpl extends LocalApiManagerImpl {
private static final String KEY_API = "sop:api";
private static final String KEY_API = CacheKey.KEY_API;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Autowired
StringRedisTemplate stringRedisTemplate;
@Override
public void save(ApiInfoDTO apiInfoDTO) {
@@ -30,20 +33,33 @@ public class RedisApiManagerImpl extends LocalApiManagerImpl {
stringRedisTemplate.opsForHash().put(KEY_API, key, JsonUtil.toJSONString(apiInfoDTO));
}
protected void cache(String key, ApiInfo apiInfo) {
protected ApiInfoDTO cache(ApiInfo apiInfo) {
String key = apiInfo.getApiName() + apiInfo.getApiVersion();
ApiInfoDTO apiInfoDTO = CopyUtil.copyBean(apiInfo, ApiInfoDTO::new);
stringRedisTemplate.opsForHash().put(KEY_API, key, JsonUtil.toJSONString(apiInfoDTO));
log.info("更新接口redis缓存, apiInfoDTO={}", apiInfoDTO);
return apiInfoDTO;
}
@Override
public ApiInfoDTO get(String apiName, String apiVersion) {
String key = apiName + apiVersion;
try {
Object value = stringRedisTemplate.opsForHash().get(KEY_API, key);
BoundHashOperations<String, String, String> operations = stringRedisTemplate.boundHashOps(KEY_API);
String value = operations.get(key);
if (value == null) {
// 从数据库中读取
ApiInfo apiInfo = apiInfoMapper.getByNameVersion(apiName, apiVersion);
if (apiInfo == null) {
operations.put(key, "");
return null;
}
return this.cache(apiInfo);
}
if (ObjectUtils.isEmpty(value)) {
return null;
}
return JsonUtil.parseObject(String.valueOf(value), ApiInfoDTO.class);
return JsonUtil.parseObject(value, ApiInfoDTO.class);
} catch (Exception e) {
log.error("redis访问失败", e);
return super.get(apiName, apiVersion);
@@ -55,8 +71,7 @@ public class RedisApiManagerImpl extends LocalApiManagerImpl {
log.info("load apiInfo to redis");
List<ApiInfo> apiInfos = this.apiInfoMapper.listAll();
for (ApiInfo apiInfo : apiInfos) {
String key = apiInfo.getApiName() + apiInfo.getApiVersion();
this.cache(key, apiInfo);
this.cache(apiInfo);
}
}
}

View File

@@ -0,0 +1,71 @@
package com.gitee.sop.gateway.service.manager.impl;
import com.gitee.sop.gateway.common.ApiInfoDTO;
import com.gitee.sop.gateway.common.CacheKey;
import com.gitee.sop.gateway.common.SopConstants;
import com.gitee.sop.gateway.dao.entity.IsvInfo;
import com.gitee.sop.gateway.dao.mapper.IsvInfoMapper;
import com.gitee.sop.gateway.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author 六如
*/
@Slf4j
public class RedisIsvApiPermissionManagerImpl extends LocalIsvApiPermissionManagerImpl {
private static final String CACHE_KEY = CacheKey.KEY_ISV_PERM;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private IsvInfoMapper isvInfoMapper;
@Override
public boolean doCheck(Long isvId, ApiInfoDTO apiInfoDTO) {
BoundHashOperations<String, Object, String> operations = stringRedisTemplate.boundHashOps(CACHE_KEY);
String value = operations.get(isvId);
if (Objects.equals(value, SopConstants.NULL)) {
return false;
}
List<Long> apiIdList;
if (value == null) {
Map<Long, List<Long>> cache = this.refresh(Collections.singletonList(isvId));
apiIdList = cache.get(isvId);
} else {
apiIdList = JsonUtil.parseArray(value, Long.class);
}
return apiIdList != null && apiIdList.contains(apiInfoDTO.getId());
}
@Override
protected void cache(Long isvId, List<Long> apiIdList) {
stringRedisTemplate.opsForHash().put(CACHE_KEY, isvId, JsonUtil.toJSONString(apiIdList));
log.info("更新isv接口id redis缓存, isvId={}, apiIdList={}", isvId, apiIdList);
}
@PostConstruct
@Override
public void init() {
Set<Long> isvIds = isvInfoMapper.listAll()
.stream()
.map(IsvInfo::getId)
.collect(Collectors.toSet());
this.refresh(isvIds);
}
}

View File

@@ -1,5 +1,7 @@
package com.gitee.sop.gateway.service.manager.impl;
import com.gitee.sop.gateway.common.CacheKey;
import com.gitee.sop.gateway.common.SopConstants;
import com.gitee.sop.gateway.dao.entity.IsvInfo;
import com.gitee.sop.gateway.service.manager.dto.IsvDTO;
import com.gitee.sop.gateway.util.CopyUtil;
@@ -8,8 +10,11 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* @author 六如
@@ -17,19 +22,21 @@ import java.util.List;
@Slf4j
public class RedisIsvManagerImpl extends LocalIsvManagerImpl {
private static final String KEY_ISV = "sop:isv";
private static final String KEY_ISV = CacheKey.KEY_ISV;
@Resource
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public IsvDTO getIsv(String appId) {
try {
Object value = stringRedisTemplate.opsForHash().get(KEY_ISV, appId);
if (Objects.equals(value, SopConstants.NULL)) {
return null;
}
if (value == null) {
IsvInfo isvInfo = this.isvInfoMapper.getByAppId(appId);
return this.cache(isvInfo);
Map<String, IsvDTO> cache = this.refresh(Collections.singletonList(appId));
return cache.get(appId);
}
return JsonUtil.parseObject(String.valueOf(value), IsvDTO.class);
} catch (Exception e) {
@@ -39,23 +46,19 @@ public class RedisIsvManagerImpl extends LocalIsvManagerImpl {
}
@Override
public void reload(String appId) {
IsvInfo isvInfo = isvInfoMapper.getByAppId(appId);
this.cache(isvInfo);
protected void cache(String appId, IsvDTO isvDTO) {
stringRedisTemplate.opsForHash().put(KEY_ISV, appId, JsonUtil.toJSONString(isvDTO));
log.info("更新isv redis缓存, isvDTO={}", isvDTO);
}
@PostConstruct
@Override
public void init() {
log.info("load isvInfo to redis");
List<IsvInfo> isvInfos = this.isvInfoMapper.listAll();
for (IsvInfo isvInfo : isvInfos) {
this.cache(isvInfo);
this.cache(isvInfo.getAppId(), CopyUtil.copyBean(isvInfo, IsvDTO::new));
}
}
protected IsvDTO cache(IsvInfo isvInfo) {
IsvDTO isvDTO = CopyUtil.copyBean(isvInfo, IsvDTO::new);
stringRedisTemplate.opsForHash().put(KEY_ISV, isvInfo.getAppId(), JsonUtil.toJSONString(isvDTO));
return isvDTO;
}
}

View File

@@ -1,13 +1,17 @@
package com.gitee.sop.gateway.service.manager.impl;
import com.gitee.sop.gateway.common.CacheKey;
import com.gitee.sop.gateway.common.SopConstants;
import com.gitee.sop.gateway.dao.entity.IsvKeys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Map;
import java.util.Objects;
/**
* @author 六如
@@ -15,44 +19,45 @@ import java.util.Optional;
@Slf4j
public class RedisSecretManager extends LocalSecretManagerImpl {
private static final String KEY_ISV = "sop:sec";
private static final String KEY_SEC = CacheKey.KEY_SEC;
@Resource
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public String getIsvPublicKey(String appId) {
public String getIsvPublicKey(Long isvId) {
try {
Object value = stringRedisTemplate.opsForHash().get(KEY_ISV, appId);
Object value = stringRedisTemplate.opsForHash().get(KEY_SEC, isvId);
if (Objects.equals(value, SopConstants.NULL)) {
return null;
}
if (value == null) {
return this.reload(appId);
Map<Long, String> cache = this.refresh(Collections.singletonList(isvId));
return cache.get(isvId);
}
return String.valueOf(value);
} catch (Exception e) {
log.error("操作redis失败", e);
return super.getIsvPublicKey(appId);
return super.getIsvPublicKey(isvId);
}
}
@Override
public String reload(String appId) {
IsvKeys isvKeys = this.isvKeysMapper.getByAppId(appId);
return this.cache(appId, isvKeys);
}
protected String cache(String appId, IsvKeys isvKeys) {
String publicKey = Optional.ofNullable(isvKeys).map(IsvKeys::getPublicKeyIsv).orElse("");
stringRedisTemplate.opsForHash().put(KEY_ISV, appId, publicKey);
return publicKey;
protected void cache(Long isvId, String publicKey) {
if (publicKey == null) {
publicKey = SopConstants.NULL;
}
stringRedisTemplate.opsForHash().put(KEY_SEC, isvId, publicKey);
log.info("更新isv秘钥redis缓存, isvId={}", isvId);
}
@PostConstruct
@Override
public void init() {
log.info("load isvKey to redis");
List<IsvKeys> isvKeys = this.isvKeysMapper.listAll();
for (IsvKeys isvKey : isvKeys) {
this.cache(isvKey.getAppId(), isvKey);
this.cache(isvKey.getIsvId(), isvKey.getPublicKeyIsv());
}
}

View File

@@ -1,7 +1,7 @@
package com.gitee.sop.gateway.service.validate;
import com.gitee.sop.gateway.common.ApiInfoDTO;
import com.gitee.sop.gateway.common.StatusEnum;
import com.gitee.sop.gateway.common.enums.StatusEnum;
import com.gitee.sop.gateway.config.ApiConfig;
import com.gitee.sop.gateway.exception.ApiException;
import com.gitee.sop.gateway.message.ErrorEnum;
@@ -24,7 +24,7 @@ import org.springframework.util.unit.DataSize;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -55,25 +55,25 @@ public class ApiValidator implements Validator {
@Value("${upload.total-file-max-size}")
private DataSize maxFileSize;
@Resource
@Autowired
private Signer signer;
@Resource
@Autowired
private ApiConfig apiConfig;
@Resource
@Autowired
private ApiManager apiCacheManager;
@Resource
@Autowired
private IpBlacklistManager ipBlacklistManager;
@Resource
@Autowired
private IsvApiPermissionManager isvApiPermissionManager;
@Resource
@Autowired
private IsvManager isvManager;
@Resource
@Autowired
private SecretManager secretManager;
private DateTimeFormatter dateTimeFormatter;
@@ -96,7 +96,7 @@ public class ApiValidator implements Validator {
ApiRequest apiRequest = apiRequestContext.getApiRequest();
ApiInfoDTO apiInfo = apiCacheManager.get(apiRequest.getMethod(), apiRequest.getVersion());
// 检查接口信息
checkApiInfo(apiRequestContext, apiInfo);
checkApiInfo(apiRequestContext, apiInfo, isvDTO);
// 检查上传文件
checkUploadFile(apiRequestContext);
@@ -105,7 +105,7 @@ public class ApiValidator implements Validator {
return apiInfo;
}
public void checkApiInfo(ApiRequestContext apiRequestContext, ApiInfoDTO apiInfoDTO) {
public void checkApiInfo(ApiRequestContext apiRequestContext, ApiInfoDTO apiInfoDTO, IsvDTO isvDTO) {
// 检查路由是否存在
if (apiInfoDTO == null) {
throw new ApiException(ErrorEnum.ISV_INVALID_METHOD, apiRequestContext.getLocale());
@@ -119,8 +119,7 @@ public class ApiValidator implements Validator {
if (needCheckPermission) {
ApiRequest apiRequest = apiRequestContext.getApiRequest();
String appKey = apiRequest.getAppId();
String nameVersion = apiRequest.takeNameVersion();
boolean hasPermission = isvApiPermissionManager.hasPermission(appKey, nameVersion);
boolean hasPermission = isvApiPermissionManager.hasPermission(isvDTO.getId(), apiInfoDTO);
if (!hasPermission) {
throw new ApiException(ErrorEnum.ISV_ROUTE_NO_PERMISSIONS, apiRequestContext.getLocale());
}
@@ -255,7 +254,7 @@ public class ApiValidator implements Validator {
apiRequest.takeNameVersion(), apiConfig.getSignName());
}
// ISV上传的公钥
String publicKey = secretManager.getIsvPublicKey(isv.getAppId());
String publicKey = secretManager.getIsvPublicKey(isv.getId());
if (ObjectUtils.isEmpty(publicKey)) {
throw new ApiException(ErrorEnum.ISV_MISSING_SIGNATURE_CONFIG, apiRequestContext.getLocale(),
apiRequest.takeNameVersion());

View File

@@ -12,7 +12,7 @@ import com.gitee.sop.gateway.service.validate.Signer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashMap;
import java.util.Map;
@@ -26,7 +26,7 @@ import java.util.Map;
@Component
public class AlipaySigner implements Signer {
@Resource
@Autowired
private ApiConfig apiConfig;
@Override

View File

@@ -3,6 +3,7 @@ package com.gitee.sop.gateway.util;
import com.alibaba.fastjson2.JSON;
import org.springframework.util.ObjectUtils;
import java.util.List;
import java.util.Objects;
/**
@@ -26,4 +27,11 @@ public class JsonUtil {
return JSON.parseObject(value, clazz);
}
public static <T> List<T> parseArray(String value, Class<T> clazz) {
if (Objects.equals(value, "null") || ObjectUtils.isEmpty(value)) {
return null;
}
return JSON.parseArray(value, clazz);
}
}

View File

@@ -2,12 +2,8 @@ dubbo.registry.address=nacos://localhost:8848
mybatis.print-sql=true
# api manager,local/redis
gateway.manager.api=redis
# isv manager,local/redis
gateway.manager.isv=redis
# secret manger,local/redis
gateway.manager.secret=redis
# manager cache type, local/redis
gateway.manager.cache-type=redis
# mysql config
mysql.host=127.0.0.1:3306

View File

@@ -2,28 +2,32 @@ spring.profiles.active=dev
spring.application.name=sop-index
server.port=8081
####### index config #######
####### gateway config #######
# request entry path
gateway.path=/api
# manager cache type, local/redis
gateway.manager.cache-type=local
# api manager,local/redis
gateway.manager.api=local
gateway.manager.api=${gateway.manager.cache-type}
# isv manager,local/redis
gateway.manager.isv=local
gateway.manager.isv=${gateway.manager.cache-type}
# secret manger,local/redis
gateway.manager.secret=local
gateway.manager.secret=${gateway.manager.cache-type}
# isv api permission manager,local/redis
gateway.manager.isv-api-perm=${gateway.manager.cache-type}
####### parameter name config #######
api.param.app-id-name=app_id
api.param.api-name=method
api.param.format-name=format
api.param.charset-name=charset
api.param.sign-type-name=sign_type
api.param.sign-name=sign
api.param.timestamp-name=timestamp
api.param.version-name=version
api.param.notify-url-name=notify_url
api.param.app-auth-token-name=app_auth_token
api.param.biz-content-name=biz_content
api.app-id-name=app_id
api.api-name=method
api.format-name=format
api.charset-name=charset
api.sign-type-name=sign_type
api.sign-name=sign
api.timestamp-name=timestamp
api.version-name=version
api.notify-url-name=notify_url
api.app-auth-token-name=app_auth_token
api.biz-content-name=biz_content
####### other config #######
# api request timeout
@@ -81,9 +85,10 @@ mybatis.fill.com.gitee.fastmybatis.core.support.LocalDateTimeFillInsert=add_time
mybatis.fill.com.gitee.fastmybatis.core.support.LocalDateTimeFillUpdate=update_time
# mybatis config file
mybatis.config-location=classpath:mybatis/mybatisConfig.xml
# print sql, true/false
mybatis.print-sql=false
# print SQL
logging.level.com.gitee.sop.gateway.dao=error
logging.level.com.gitee.fastmybatis=info
mybatis.print-sql=false