支持预发布、灰度发布

1.x
tanghc 5 years ago
parent 202267b686
commit 2e9e3c7ca7
  1. 4
      changelog.md
  2. 1
      sop-1.14.0.sql
  3. 2
      sop-admin/sop-admin-server/pom.xml
  4. 4
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/SopAdminServerApplication.java
  5. 16
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ServiceApi.java
  6. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/UserKeyDefinition.java
  7. 26
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/StatusEnum.java
  8. 3
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/ConfigGrayUserkey.java
  9. 4
      sop-admin/sop-admin-server/src/main/resources/application-dev.properties
  10. 8
      sop-admin/sop-admin-vue/src/views/service/serviceList.vue
  11. 17
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java
  12. 13
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SpringContext.java
  13. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/UserKeyDefinition.java
  14. 14
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/ValidateFilter.java
  15. 35
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java
  16. 51
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultUserKeyManager.java
  17. 24
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ManagerContext.java
  18. 15
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/UserKeyManager.java
  19. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ParamNames.java
  20. 35
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java
  21. 14
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/ZuulContext.java
  22. 9
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java
  23. 10
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreLimitFilter.java
  24. 7
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreRoutePermissionFilter.java
  25. 15
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreValidateFilter.java
  26. 23
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/EnvHttpRequestInterceptor.java
  27. 18
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/HystrixRequestVariableContext.java
  28. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/ServiceGrayConfig.java
  29. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/ZuulZookeeperRouteManager.java
  30. 2
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ParamNames.java
  31. 1
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingRequestCondition.java
  32. 7
      sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/AlipayController.java
  33. 4
      sop-gateway/src/main/java/com/gitee/sop/gateway/config/GatewayConfig.java
  34. 5
      sop-gateway/src/main/java/com/gitee/sop/gateway/config/ZuulConfig.java
  35. 3
      sop-gateway/src/main/java/com/gitee/sop/gateway/entity/ConfigGrayUserkey.java
  36. 13
      sop-gateway/src/main/java/com/gitee/sop/gateway/loadbalancer/EnvironmentServerChooser.java
  37. 2
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbIPBlacklistManager.java
  38. 4
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbIsvManager.java
  39. 2
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbIsvRoutePermissionManager.java
  40. 2
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbLimitConfigManager.java
  41. 2
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbRouteConfigManager.java
  42. 62
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbUserKeyManager.java
  43. 1
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/ManagerInitializer.java
  44. 31
      sop-test/src/test/java/com/gitee/sop/test/AlipayClientPostTest.java
  45. 1
      sop.sql

@ -1,5 +1,9 @@
# changelog # changelog
## 1.14.0
- 支持预发布、灰度发布环境
## 1.13.4 ## 1.13.4
- 修复admin服务列表最后更新时间不显示问题 - 修复admin服务列表最后更新时间不显示问题

@ -5,6 +5,7 @@ CREATE TABLE `config_gray_userkey` (
`instance_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'serviceId', `instance_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'serviceId',
`user_key_content` text COMMENT '用户key,多个用引文逗号隔开', `user_key_content` text COMMENT '用户key,多个用引文逗号隔开',
`name_version_content` text COMMENT '需要灰度的接口,goods.get=1.2,order.list=1.2', `name_version_content` text COMMENT '需要灰度的接口,goods.get=1.2,order.list=1.2',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '0:禁用,1:启用',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),

@ -25,7 +25,7 @@
<curator-recipes.version>4.0.1</curator-recipes.version> <curator-recipes.version>4.0.1</curator-recipes.version>
<okhttp.version>3.11.0</okhttp.version> <okhttp.version>3.11.0</okhttp.version>
<easyopen.version>1.16.2</easyopen.version> <easyopen.version>1.16.2</easyopen.version>
<fastmybatis.version>1.8.0</fastmybatis.version> <fastmybatis.version>1.8.1</fastmybatis.version>
</properties> </properties>
<dependencies> <dependencies>

@ -1,16 +1,12 @@
package com.gitee.sop.adminserver; package com.gitee.sop.adminserver;
import com.gitee.fastmybatis.core.FastmybatisConfig;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.Arrays;
@SpringBootApplication @SpringBootApplication
public class SopAdminServerApplication { public class SopAdminServerApplication {
public static void main(String[] args) { public static void main(String[] args) {
FastmybatisConfig.defaultIgnoreUpdateColumns = Arrays.asList("gmt_create", "gmt_modified");
SpringApplication.run(SopAdminServerApplication.class, args); SpringApplication.run(SopAdminServerApplication.class, args);
} }
} }

@ -19,6 +19,7 @@ import com.gitee.sop.adminserver.bean.UserKeyDefinition;
import com.gitee.sop.adminserver.bean.ZookeeperContext; import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.common.BizException; import com.gitee.sop.adminserver.common.BizException;
import com.gitee.sop.adminserver.common.ChannelOperation; import com.gitee.sop.adminserver.common.ChannelOperation;
import com.gitee.sop.adminserver.common.StatusEnum;
import com.gitee.sop.adminserver.common.ZookeeperPathExistException; import com.gitee.sop.adminserver.common.ZookeeperPathExistException;
import com.gitee.sop.adminserver.common.ZookeeperPathNotExistException; import com.gitee.sop.adminserver.common.ZookeeperPathNotExistException;
import com.gitee.sop.adminserver.entity.ConfigGrayUserkey; import com.gitee.sop.adminserver.entity.ConfigGrayUserkey;
@ -49,6 +50,7 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
public class ServiceApi { public class ServiceApi {
@Autowired @Autowired
private RegistryService registryService; private RegistryService registryService;
@ -219,10 +221,12 @@ public class ServiceApi {
configGrayUserkey.setInstanceId(instanceId); configGrayUserkey.setInstanceId(instanceId);
configGrayUserkey.setUserKeyContent(userKeyContent); configGrayUserkey.setUserKeyContent(userKeyContent);
configGrayUserkey.setNameVersionContent(nameVersionContent); configGrayUserkey.setNameVersionContent(nameVersionContent);
configGrayUserkey.setStatus(StatusEnum.STATUS_ENABLE.getStatus());
configGrayUserkeyMapper.save(configGrayUserkey); configGrayUserkeyMapper.save(configGrayUserkey);
} else { } else {
configGrayUserkey.setUserKeyContent(userKeyContent); configGrayUserkey.setUserKeyContent(userKeyContent);
configGrayUserkey.setNameVersionContent(nameVersionContent); configGrayUserkey.setNameVersionContent(nameVersionContent);
configGrayUserkey.setStatus(StatusEnum.STATUS_ENABLE.getStatus());
configGrayUserkeyMapper.update(configGrayUserkey); configGrayUserkeyMapper.update(configGrayUserkey);
} }
this.sendUserKeyMsg(instanceId, ChannelOperation.GRAY_USER_KEY_SET); this.sendUserKeyMsg(instanceId, ChannelOperation.GRAY_USER_KEY_SET);
@ -238,7 +242,13 @@ public class ServiceApi {
try { try {
MetadataEnum envPre = MetadataEnum.ENV_ONLINE; MetadataEnum envPre = MetadataEnum.ENV_ONLINE;
registryService.setMetadata(param, envPre.getKey(), envPre.getValue()); registryService.setMetadata(param, envPre.getKey(), envPre.getValue());
this.sendUserKeyMsg(param.getServiceId(), ChannelOperation.GRAY_USER_KEY_CLEAR);
ConfigGrayUserkey configGrayUserkey = configGrayUserkeyMapper.getByColumn("instance_id", param.getInstanceId());
if (configGrayUserkey != null && configGrayUserkey.getStatus() == StatusEnum.STATUS_ENABLE.getStatus()) {
configGrayUserkey.setStatus(StatusEnum.STATUS_DISABLE.getStatus());
configGrayUserkeyMapper.update(configGrayUserkey);
}
this.sendUserKeyMsg(param.getInstanceId(), ChannelOperation.GRAY_USER_KEY_CLEAR);
} catch (Exception e) { } catch (Exception e) {
log.error("上线失败,param:{}", param, e); log.error("上线失败,param:{}", param, e);
throw new BizException("上线失败,请查看日志"); throw new BizException("上线失败,请查看日志");
@ -250,9 +260,9 @@ public class ServiceApi {
return configGrayUserkeyMapper.getByColumn("instance_id", param.getInstanceId()); return configGrayUserkeyMapper.getByColumn("instance_id", param.getInstanceId());
} }
private void sendUserKeyMsg(String serviceId, ChannelOperation channelOperation) { private void sendUserKeyMsg(String instanceId, ChannelOperation channelOperation) {
UserKeyDefinition userKeyDefinition = new UserKeyDefinition(); UserKeyDefinition userKeyDefinition = new UserKeyDefinition();
userKeyDefinition.setServiceId(serviceId); userKeyDefinition.setInstanceId(instanceId);
ChannelMsg channelMsg = new ChannelMsg(channelOperation, userKeyDefinition); ChannelMsg channelMsg = new ChannelMsg(channelOperation, userKeyDefinition);
String jsonData = JSON.toJSONString(channelMsg); String jsonData = JSON.toJSONString(channelMsg);
String path = ZookeeperContext.getUserKeyChannelPath(); String path = ZookeeperContext.getUserKeyChannelPath();

@ -7,6 +7,6 @@ import lombok.Data;
*/ */
@Data @Data
public class UserKeyDefinition { public class UserKeyDefinition {
private String serviceId; private String instanceId;
private String data; private String data;
} }

@ -0,0 +1,26 @@
package com.gitee.sop.adminserver.common;
import lombok.Getter;
/**
* 通用状态枚举
*
* @author tanghc
*/
@Getter
public enum StatusEnum {
/**
* 启用
*/
STATUS_ENABLE((byte)1),
/**
* 禁用
*/
STATUS_DISABLE((byte)0),
;
private byte status;
StatusEnum(byte status) {
this.status = status;
}
}

@ -34,6 +34,9 @@ public class ConfigGrayUserkey {
/** 需要灰度的接口,goods.get=1.2,order.list=1.2, 数据库字段:name_version_content */ /** 需要灰度的接口,goods.get=1.2,order.list=1.2, 数据库字段:name_version_content */
private String nameVersionContent; private String nameVersionContent;
/** 0:禁用,1:启用, 数据库字段:status */
private Byte status;
/** 数据库字段:gmt_create */ /** 数据库字段:gmt_create */
private Date gmtCreate; private Date gmtCreate;

@ -44,5 +44,9 @@ registry.name=eureka
logging.level.com.gitee=debug logging.level.com.gitee=debug
# 不用改
mybatis.ignore-update-columns[0]=gmt_create
mybatis.ignore-update-columns[1]=gmt_modified
# 不用改,如果要改,请全局替换修改 # 不用改,如果要改,请全局替换修改
zuul.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC zuul.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC

@ -163,7 +163,7 @@
</el-form-item> </el-form-item>
</td> </td>
<td style="vertical-align: baseline;"> <td style="vertical-align: baseline;">
<el-button v-if="index > 0" type="text" @click.prevent="removeNameVersion(grayRouteConfig)">删除</el-button> <el-button v-show="grayForm.grayRouteConfigList.length > 1" type="text" @click.prevent="removeNameVersion(grayRouteConfig)">删除</el-button>
</td> </td>
</tr> </tr>
</table> </table>
@ -203,11 +203,7 @@ export default {
ipPort: '', ipPort: '',
userKeyContent: '', userKeyContent: '',
onlyUpdateGrayUserkey: false, onlyUpdateGrayUserkey: false,
grayRouteConfigList: [{ grayRouteConfigList: []
oldRouteId: '',
newVersion: '',
key: Date.now()
}]
}, },
tabsActiveName: 'first', tabsActiveName: 'first',
routeList: [], routeList: [],

@ -10,11 +10,13 @@ import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager;
import com.gitee.sop.gatewaycommon.manager.DefaultLimitConfigManager; import com.gitee.sop.gatewaycommon.manager.DefaultLimitConfigManager;
import com.gitee.sop.gatewaycommon.manager.DefaultRouteConfigManager; import com.gitee.sop.gatewaycommon.manager.DefaultRouteConfigManager;
import com.gitee.sop.gatewaycommon.manager.DefaultServiceErrorManager; import com.gitee.sop.gatewaycommon.manager.DefaultServiceErrorManager;
import com.gitee.sop.gatewaycommon.manager.DefaultUserKeyManager;
import com.gitee.sop.gatewaycommon.manager.IPBlacklistManager; import com.gitee.sop.gatewaycommon.manager.IPBlacklistManager;
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager; import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
import com.gitee.sop.gatewaycommon.manager.LimitConfigManager; import com.gitee.sop.gatewaycommon.manager.LimitConfigManager;
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager; import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager; import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager;
import com.gitee.sop.gatewaycommon.manager.UserKeyManager;
import com.gitee.sop.gatewaycommon.param.ParamBuilder; import com.gitee.sop.gatewaycommon.param.ParamBuilder;
import com.gitee.sop.gatewaycommon.result.DataNameBuilder; import com.gitee.sop.gatewaycommon.result.DataNameBuilder;
import com.gitee.sop.gatewaycommon.result.DefaultDataNameBuilder; import com.gitee.sop.gatewaycommon.result.DefaultDataNameBuilder;
@ -97,11 +99,6 @@ public class ApiConfig {
*/ */
private SessionManager sessionManager = new ApiSessionManager(); private SessionManager sessionManager = new ApiSessionManager();
/**
* zuul网关全局异常处理
*/
private ZuulErrorController zuulErrorController = new ZuulErrorController();
/** /**
* isv路由权限 * isv路由权限
*/ */
@ -127,6 +124,11 @@ public class ApiConfig {
*/ */
private LimitManager limitManager = new DefaultLimitManager(); private LimitManager limitManager = new DefaultLimitManager();
/**
* 用户key管理
*/
private UserKeyManager userKeyManager = new DefaultUserKeyManager();
/** /**
* 构建数据节点名称 * 构建数据节点名称
*/ */
@ -142,6 +144,11 @@ public class ApiConfig {
*/ */
private ServiceErrorManager serviceErrorManager = new DefaultServiceErrorManager(); private ServiceErrorManager serviceErrorManager = new DefaultServiceErrorManager();
/**
* zuul网关全局异常处理
*/
private ZuulErrorController zuulErrorController = new ZuulErrorController();
// -------- fields --------- // -------- fields ---------
/** /**

@ -1,21 +1,14 @@
package com.gitee.sop.gatewaycommon.bean; package com.gitee.sop.gatewaycommon.bean;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/** /**
* @author tanghc * @author tanghc
*/ */
public class SpringContext implements ApplicationContextAware { public class SpringContext {
private static ApplicationContext ctx; private static ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ctx = applicationContext;
}
public static <T> T getBean(Class<T> clazz) { public static <T> T getBean(Class<T> clazz) {
return ctx.getBean(clazz); return ctx.getBean(clazz);
} }
@ -24,6 +17,10 @@ public class SpringContext implements ApplicationContextAware {
return ctx.getBean(beanName); return ctx.getBean(beanName);
} }
public static void setApplicationContext(ApplicationContext ctx) {
SpringContext.ctx = ctx;
}
public static ApplicationContext getApplicationContext() { public static ApplicationContext getApplicationContext() {
return ctx; return ctx;
} }

@ -7,6 +7,6 @@ import lombok.Data;
*/ */
@Data @Data
public class UserKeyDefinition { public class UserKeyDefinition {
private String serviceId; private String instanceId;
private String data; private String data;
} }

@ -1,12 +1,12 @@
package com.gitee.sop.gatewaycommon.gateway.filter; package com.gitee.sop.gatewaycommon.gateway.filter;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.exception.ApiException; import com.gitee.sop.gatewaycommon.exception.ApiException;
import com.gitee.sop.gatewaycommon.gateway.GatewayContext; import com.gitee.sop.gatewaycommon.gateway.GatewayContext;
import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
import com.gitee.sop.gatewaycommon.validate.Validator; import com.gitee.sop.gatewaycommon.validate.Validator;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
@ -19,14 +19,18 @@ import reactor.core.publisher.Mono;
@Slf4j @Slf4j
public class ValidateFilter implements GlobalFilter, Ordered { public class ValidateFilter implements GlobalFilter, Ordered {
@Autowired
private ParamBuilder<ServerWebExchange> paramBuilder;
@Autowired
private Validator validator;
@Override @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ApiConfig apiConfig = ApiContext.getApiConfig();
// 解析参数 // 解析参数
ApiParam param = apiConfig.getGatewayParamBuilder().build(exchange); ApiParam param = paramBuilder.build(exchange);
GatewayContext.setApiParam(exchange, param); GatewayContext.setApiParam(exchange, param);
// 验证操作,这里有负责验证签名参数 // 验证操作,这里有负责验证签名参数
Validator validator = apiConfig.getValidator();
try { try {
validator.validate(param); validator.validate(param);
} catch (ApiException e) { } catch (ApiException e) {

@ -3,10 +3,15 @@ package com.gitee.sop.gatewaycommon.manager;
import com.gitee.sop.gatewaycommon.bean.ApiConfig; import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.ApiContext; import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.bean.BeanInitializer; import com.gitee.sop.gatewaycommon.bean.BeanInitializer;
import com.gitee.sop.gatewaycommon.bean.SpringContext;
import com.gitee.sop.gatewaycommon.limit.LimitManager;
import com.gitee.sop.gatewaycommon.message.ErrorFactory; import com.gitee.sop.gatewaycommon.message.ErrorFactory;
import com.gitee.sop.gatewaycommon.secret.IsvManager; import com.gitee.sop.gatewaycommon.secret.IsvManager;
import com.gitee.sop.gatewaycommon.session.SessionManager;
import com.gitee.sop.gatewaycommon.validate.Validator;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -35,30 +40,59 @@ public class AbstractConfiguration implements ApplicationContextAware {
} }
@Bean @Bean
@ConditionalOnMissingBean
Validator validator() {
return ApiConfig.getInstance().getValidator();
}
@Bean
@ConditionalOnMissingBean
IsvManager isvManager() { IsvManager isvManager() {
return ApiConfig.getInstance().getIsvManager(); return ApiConfig.getInstance().getIsvManager();
} }
@Bean @Bean
@ConditionalOnMissingBean
IsvRoutePermissionManager isvRoutePermissionManager() { IsvRoutePermissionManager isvRoutePermissionManager() {
return ApiConfig.getInstance().getIsvRoutePermissionManager(); return ApiConfig.getInstance().getIsvRoutePermissionManager();
} }
@Bean @Bean
@ConditionalOnMissingBean
RouteConfigManager routeConfigManager() { RouteConfigManager routeConfigManager() {
return ApiConfig.getInstance().getRouteConfigManager(); return ApiConfig.getInstance().getRouteConfigManager();
} }
@Bean @Bean
@ConditionalOnMissingBean
LimitConfigManager limitConfigManager() { LimitConfigManager limitConfigManager() {
return ApiConfig.getInstance().getLimitConfigManager(); return ApiConfig.getInstance().getLimitConfigManager();
} }
@Bean @Bean
@ConditionalOnMissingBean
LimitManager limitManager() {
return ApiConfig.getInstance().getLimitManager();
}
@Bean
@ConditionalOnMissingBean
IPBlacklistManager ipBlacklistManager() { IPBlacklistManager ipBlacklistManager() {
return ApiConfig.getInstance().getIpBlacklistManager(); return ApiConfig.getInstance().getIpBlacklistManager();
} }
@Bean
@ConditionalOnMissingBean
UserKeyManager userKeyManager() {
return ApiConfig.getInstance().getUserKeyManager();
}
@Bean
@ConditionalOnMissingBean
SessionManager sessionManager() {
return ApiConfig.getInstance().getSessionManager();
}
/** /**
* 跨域过滤器 * 跨域过滤器
* *
@ -89,6 +123,7 @@ public class AbstractConfiguration implements ApplicationContextAware {
@PostConstruct @PostConstruct
public final void after() { public final void after() {
SpringContext.setApplicationContext(applicationContext);
if (RouteRepositoryContext.getRouteRepository() == null) { if (RouteRepositoryContext.getRouteRepository() == null) {
throw new IllegalArgumentException("RouteRepositoryContext.setRouteRepository()方法未使用"); throw new IllegalArgumentException("RouteRepositoryContext.setRouteRepository()方法未使用");
} }

@ -0,0 +1,51 @@
package com.gitee.sop.gatewaycommon.manager;
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.ServiceGrayConfig;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.Map;
/**
* @author tanghc
*/
public class DefaultUserKeyManager implements UserKeyManager {
/**
* KEY:instanceId
*/
private Map<String, ServiceGrayConfig> serviceUserKeyMap = Maps.newConcurrentMap();
@Override
public boolean containsKey(String instanceId, Object userKey) {
if (instanceId == null || userKey == null) {
return false;
}
return this.getServiceGrayConfig(instanceId).containsKey(userKey);
}
@Override
public String getVersion(String instanceId, String nameVersion) {
if (instanceId == null || nameVersion == null) {
return null;
}
return this.getServiceGrayConfig(instanceId).getVersion(nameVersion);
}
@Override
public ServiceGrayConfig getServiceGrayConfig(String instanceId) {
ServiceGrayConfig serviceGrayConfig = serviceUserKeyMap.get(instanceId);
if (serviceGrayConfig == null) {
serviceGrayConfig = new ServiceGrayConfig();
serviceGrayConfig.setUserKeys(Sets.newConcurrentHashSet());
serviceGrayConfig.setGrayNameVersion(Maps.newConcurrentMap());
serviceUserKeyMap.put(instanceId, serviceGrayConfig);
}
return serviceGrayConfig;
}
@Override
public void load() {
}
}

@ -0,0 +1,24 @@
package com.gitee.sop.gatewaycommon.manager;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
/**
* @author tanghc
*/
public class ManagerContext {
private static ApplicationContext ctx;
public static void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ctx = applicationContext;
}
public static <T> T getManager(Class<T> clazz) {
return ctx.getBean(clazz);
}
public static ApplicationContext getApplicationContext() {
return ctx;
}
}

@ -0,0 +1,15 @@
package com.gitee.sop.gatewaycommon.manager;
import com.gitee.sop.gatewaycommon.bean.BeanInitializer;
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.ServiceGrayConfig;
/**
* @author tanghc
*/
public interface UserKeyManager extends BeanInitializer {
boolean containsKey(String instanceId, Object userKey);
String getVersion(String instanceId, String nameVersion);
ServiceGrayConfig getServiceGrayConfig(String instanceId);
}

@ -45,7 +45,7 @@ public class ParamNames {
/** 返回sign名称 */ /** 返回sign名称 */
public static String RESPONSE_SIGN_NAME = "sign"; public static String RESPONSE_SIGN_NAME = "sign";
public static String HEADER_VERSION_NAME = "x-sop-version"; public static String GRAY_HEADER_VERSION_NAME = "sop_gray_version";
} }

@ -15,11 +15,11 @@ import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.param.ParamNames; import com.gitee.sop.gatewaycommon.param.ParamNames;
import com.gitee.sop.gatewaycommon.param.UploadContext; import com.gitee.sop.gatewaycommon.param.UploadContext;
import com.gitee.sop.gatewaycommon.secret.IsvManager; import com.gitee.sop.gatewaycommon.secret.IsvManager;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -35,15 +35,26 @@ import java.util.List;
* *
* @author tanghc * @author tanghc
*/ */
@Slf4j
@Getter
public class ApiValidator implements Validator { public class ApiValidator implements Validator {
private static final Logger logger = LoggerFactory.getLogger(ApiValidator.class);
private static final int MILLISECOND_OF_ONE_SECOND = 1000; private static final int MILLISECOND_OF_ONE_SECOND = 1000;
public static final int STATUS_FORBIDDEN = 2; private static final int STATUS_FORBIDDEN = 2;
private static List<String> FORMAT_LIST = Arrays.asList("json", "xml"); private static List<String> FORMAT_LIST = Arrays.asList("json", "xml");
@Autowired
private IsvManager isvManager;
@Autowired
private IsvRoutePermissionManager isvRoutePermissionManager;
@Autowired
private IPBlacklistManager ipBlacklistManager;
@Autowired
private RouteConfigManager routeConfigManager;
@Override @Override
public void validate(ApiParam param) { public void validate(ApiParam param) {
@ -52,8 +63,8 @@ public class ApiValidator implements Validator {
ApiConfig apiConfig = ApiContext.getApiConfig(); ApiConfig apiConfig = ApiContext.getApiConfig();
if (apiConfig.isIgnoreValidate() || param.fetchIgnoreValidate()) { if (apiConfig.isIgnoreValidate() || param.fetchIgnoreValidate()) {
if (logger.isDebugEnabled()) { if (log.isDebugEnabled()) {
logger.debug("忽略所有验证(ignoreValidate=true), name:{}, version:{}", param.fetchName(), param.fetchVersion()); log.debug("忽略所有验证(ignoreValidate=true), name:{}, version:{}", param.fetchName(), param.fetchVersion());
} }
return; return;
} }
@ -71,7 +82,6 @@ public class ApiValidator implements Validator {
* @param param 接口参数 * @param param 接口参数
*/ */
protected void checkIP(ApiParam param) { protected void checkIP(ApiParam param) {
IPBlacklistManager ipBlacklistManager = ApiConfig.getInstance().getIpBlacklistManager();
String ip = param.fetchIp(); String ip = param.fetchIp();
if (ipBlacklistManager.contains(ip)) { if (ipBlacklistManager.contains(ip)) {
throw ErrorEnum.ISV_IP_FORBIDDEN.getErrorMeta().getException(); throw ErrorEnum.ISV_IP_FORBIDDEN.getErrorMeta().getException();
@ -87,7 +97,6 @@ public class ApiValidator implements Validator {
// 检查路由是否存在 // 检查路由是否存在
RouteRepositoryContext.checkExist(routeId, ErrorEnum.ISV_INVALID_METHOD); RouteRepositoryContext.checkExist(routeId, ErrorEnum.ISV_INVALID_METHOD);
// 检查路由是否启用 // 检查路由是否启用
RouteConfigManager routeConfigManager = ApiConfig.getInstance().getRouteConfigManager();
RouteConfig routeConfig = routeConfigManager.get(routeId); RouteConfig routeConfig = routeConfigManager.get(routeId);
if (!routeConfig.enable()) { if (!routeConfig.enable()) {
throw ErrorEnum.ISP_API_DISABLED.getErrorMeta().getException(); throw ErrorEnum.ISP_API_DISABLED.getErrorMeta().getException();
@ -115,7 +124,7 @@ public class ApiValidator implements Validator {
} }
} }
} catch (IOException e) { } catch (IOException e) {
logger.error("验证上传文件MD5错误", e); log.error("验证上传文件MD5错误", e);
throw ErrorEnum.ISV_UPLOAD_FAIL.getErrorMeta().getException(); throw ErrorEnum.ISV_UPLOAD_FAIL.getErrorMeta().getException();
} }
} }
@ -147,8 +156,6 @@ public class ApiValidator implements Validator {
if (StringUtils.isEmpty(param.fetchAppKey())) { if (StringUtils.isEmpty(param.fetchAppKey())) {
throw ErrorEnum.ISV_MISSING_APP_ID.getErrorMeta().getException(); throw ErrorEnum.ISV_MISSING_APP_ID.getErrorMeta().getException();
} }
IsvManager isvManager = ApiContext.getApiConfig().getIsvManager();
Assert.notNull(isvManager, "isvManager未初始化");
Isv isv = isvManager.getIsv(param.fetchAppKey()); Isv isv = isvManager.getIsv(param.fetchAppKey());
// 没有用户 // 没有用户
if (isv == null) { if (isv == null) {
@ -167,7 +174,6 @@ public class ApiValidator implements Validator {
throw ErrorEnum.ISV_MISSING_SIGNATURE.getErrorMeta().getException(param.fetchNameVersion(), ParamNames.SIGN_NAME); throw ErrorEnum.ISV_MISSING_SIGNATURE.getErrorMeta().getException(param.fetchNameVersion(), ParamNames.SIGN_NAME);
} }
ApiConfig apiConfig = ApiContext.getApiConfig(); ApiConfig apiConfig = ApiContext.getApiConfig();
IsvManager isvManager = apiConfig.getIsvManager();
// 根据appId获取秘钥 // 根据appId获取秘钥
Isv isvInfo = isvManager.getIsv(param.fetchAppKey()); Isv isvInfo = isvManager.getIsv(param.fetchAppKey());
String secret = isvInfo.getSecretInfo(); String secret = isvInfo.getSecretInfo();
@ -205,7 +211,6 @@ public class ApiValidator implements Validator {
BaseRouteDefinition routeDefinition = targetRoute.getRouteDefinition(); BaseRouteDefinition routeDefinition = targetRoute.getRouteDefinition();
boolean needCheckPermission = BooleanUtils.toBoolean(routeDefinition.getPermission()); boolean needCheckPermission = BooleanUtils.toBoolean(routeDefinition.getPermission());
if (needCheckPermission) { if (needCheckPermission) {
IsvRoutePermissionManager isvRoutePermissionManager = ApiConfig.getInstance().getIsvRoutePermissionManager();
String appKey = apiParam.fetchAppKey(); String appKey = apiParam.fetchAppKey();
boolean hasPermission = isvRoutePermissionManager.hasPermission(appKey, routeId); boolean hasPermission = isvRoutePermissionManager.hasPermission(appKey, routeId);
if (!hasPermission) { if (!hasPermission) {

@ -69,20 +69,6 @@ public class ZuulContext extends ApiContext {
} }
/**
* 返回自定义的session,被SessionManager管理
*
* @return 如果sessionId为null则返回null
*/
public static HttpSession getManagedSession() {
String sessionId = getSessionId();
if (sessionId != null) {
return getSessionManager().getSession(sessionId);
} else {
return null;
}
}
/** /**
* 获取登陆的token * 获取登陆的token
* *

@ -1,9 +1,10 @@
package com.gitee.sop.gatewaycommon.zuul.configuration; package com.gitee.sop.gatewaycommon.zuul.configuration;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.ApiContext; import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.bean.SpringContext;
import com.gitee.sop.gatewaycommon.manager.AbstractConfiguration; import com.gitee.sop.gatewaycommon.manager.AbstractConfiguration;
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext; import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
import com.gitee.sop.gatewaycommon.zuul.filter.ErrorFilter; import com.gitee.sop.gatewaycommon.zuul.filter.ErrorFilter;
import com.gitee.sop.gatewaycommon.zuul.filter.FormBodyWrapperFilterExt; import com.gitee.sop.gatewaycommon.zuul.filter.FormBodyWrapperFilterExt;
import com.gitee.sop.gatewaycommon.zuul.filter.PostResultFilter; import com.gitee.sop.gatewaycommon.zuul.filter.PostResultFilter;
@ -14,6 +15,7 @@ import com.gitee.sop.gatewaycommon.zuul.filter.Servlet30WrapperFilterExt;
import com.gitee.sop.gatewaycommon.zuul.route.SopRouteLocator; import com.gitee.sop.gatewaycommon.zuul.route.SopRouteLocator;
import com.gitee.sop.gatewaycommon.zuul.route.ZuulRouteRepository; import com.gitee.sop.gatewaycommon.zuul.route.ZuulRouteRepository;
import com.gitee.sop.gatewaycommon.zuul.route.ZuulZookeeperRouteManager; import com.gitee.sop.gatewaycommon.zuul.route.ZuulZookeeperRouteManager;
import com.netflix.zuul.context.RequestContext;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties;
@ -37,8 +39,8 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
SpringContext springContext() { ParamBuilder<RequestContext> paramBuilder() {
return new SpringContext(); return ApiConfig.getInstance().getZuulParamBuilder();
} }
/** /**
@ -129,6 +131,7 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
* 统一错误处理 * 统一错误处理
*/ */
@Bean @Bean
@ConditionalOnMissingBean
ZuulErrorController baseZuulController() { ZuulErrorController baseZuulController() {
return ApiContext.getApiConfig().getZuulErrorController(); return ApiContext.getApiConfig().getZuulErrorController();
} }

@ -12,6 +12,7 @@ import com.gitee.sop.gatewaycommon.util.RequestUtil;
import com.gitee.sop.gatewaycommon.zuul.ZuulContext; import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException; import com.netflix.zuul.exception.ZuulException;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList; import java.util.ArrayList;
@ -26,6 +27,12 @@ import java.util.List;
*/ */
public class PreLimitFilter extends BaseZuulFilter { public class PreLimitFilter extends BaseZuulFilter {
@Autowired
private LimitManager limitManager;
@Autowired
private LimitConfigManager limitConfigManager;
@Override @Override
protected FilterType getFilterType() { protected FilterType getFilterType() {
return FilterType.PRE; return FilterType.PRE;
@ -53,7 +60,6 @@ public class PreLimitFilter extends BaseZuulFilter {
return null; return null;
} }
byte limitType = configLimitDto.getLimitType().byteValue(); byte limitType = configLimitDto.getLimitType().byteValue();
LimitManager limitManager = ApiConfig.getInstance().getLimitManager();
// 如果是漏桶策略 // 如果是漏桶策略
if (limitType == LimitType.LEAKY_BUCKET.getType()) { if (limitType == LimitType.LEAKY_BUCKET.getType()) {
boolean acquire = limitManager.acquire(configLimitDto); boolean acquire = limitManager.acquire(configLimitDto);
@ -67,8 +73,6 @@ public class PreLimitFilter extends BaseZuulFilter {
} }
protected ConfigLimitDto findConfigLimitDto(ApiConfig apiConfig, ApiParam apiParam, HttpServletRequest request) { protected ConfigLimitDto findConfigLimitDto(ApiConfig apiConfig, ApiParam apiParam, HttpServletRequest request) {
LimitConfigManager limitConfigManager = apiConfig.getLimitConfigManager();
String routeId = apiParam.fetchNameVersion(); String routeId = apiParam.fetchNameVersion();
String appKey = apiParam.fetchAppKey(); String appKey = apiParam.fetchAppKey();
String ip = RequestUtil.getIP(request); String ip = RequestUtil.getIP(request);

@ -1,6 +1,5 @@
package com.gitee.sop.gatewaycommon.zuul.filter; package com.gitee.sop.gatewaycommon.zuul.filter;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition; import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.TargetRoute; import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager; import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
@ -11,6 +10,7 @@ import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException; import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
/** /**
* 路由权限校验有些接口需要配置权限才能访问 * 路由权限校验有些接口需要配置权限才能访问
@ -19,6 +19,10 @@ import org.apache.commons.lang3.BooleanUtils;
*/ */
@Deprecated @Deprecated
public class PreRoutePermissionFilter extends BaseZuulFilter { public class PreRoutePermissionFilter extends BaseZuulFilter {
@Autowired
private IsvRoutePermissionManager isvRoutePermissionManager;
@Override @Override
protected FilterType getFilterType() { protected FilterType getFilterType() {
return FilterType.PRE; return FilterType.PRE;
@ -38,7 +42,6 @@ public class PreRoutePermissionFilter extends BaseZuulFilter {
BaseRouteDefinition routeDefinition = targetRoute.getRouteDefinition(); BaseRouteDefinition routeDefinition = targetRoute.getRouteDefinition();
boolean needCheckPermission = BooleanUtils.toBoolean(routeDefinition.getPermission()); boolean needCheckPermission = BooleanUtils.toBoolean(routeDefinition.getPermission());
if (needCheckPermission) { if (needCheckPermission) {
IsvRoutePermissionManager isvRoutePermissionManager = ApiConfig.getInstance().getIsvRoutePermissionManager();
String appKey = apiParam.fetchAppKey(); String appKey = apiParam.fetchAppKey();
boolean hasPermission = isvRoutePermissionManager.hasPermission(appKey, routeId); boolean hasPermission = isvRoutePermissionManager.hasPermission(appKey, routeId);
if (!hasPermission) { if (!hasPermission) {

@ -1,13 +1,13 @@
package com.gitee.sop.gatewaycommon.zuul.filter; package com.gitee.sop.gatewaycommon.zuul.filter;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.exception.ApiException; import com.gitee.sop.gatewaycommon.exception.ApiException;
import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
import com.gitee.sop.gatewaycommon.validate.Validator; import com.gitee.sop.gatewaycommon.validate.Validator;
import com.gitee.sop.gatewaycommon.zuul.ZuulContext; import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException; import com.netflix.zuul.exception.ZuulException;
import org.springframework.beans.factory.annotation.Autowired;
/** /**
* 前置校验 * 前置校验
@ -15,6 +15,13 @@ import com.netflix.zuul.exception.ZuulException;
* @author tanghc * @author tanghc
*/ */
public class PreValidateFilter extends BaseZuulFilter { public class PreValidateFilter extends BaseZuulFilter {
@Autowired
private ParamBuilder<RequestContext> paramBuilder;
@Autowired
private Validator validator;
@Override @Override
protected FilterType getFilterType() { protected FilterType getFilterType() {
return FilterType.PRE; return FilterType.PRE;
@ -27,12 +34,10 @@ public class PreValidateFilter extends BaseZuulFilter {
@Override @Override
protected Object doRun(RequestContext requestContext) throws ZuulException { protected Object doRun(RequestContext requestContext) throws ZuulException {
ApiConfig apiConfig = ApiContext.getApiConfig();
// 解析参数 // 解析参数
ApiParam param = apiConfig.getZuulParamBuilder().build(requestContext); ApiParam param = paramBuilder.build(requestContext);
ZuulContext.setApiParam(param); ZuulContext.setApiParam(param);
// 验证操作,这里有负责验证签名参数 // 验证操作,这里有负责验证签名参数
Validator validator = apiConfig.getValidator();
try { try {
validator.validate(param); validator.validate(param);
} catch (ApiException e) { } catch (ApiException e) {

@ -0,0 +1,23 @@
package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
import com.gitee.sop.gatewaycommon.param.ParamNames;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.support.HttpRequestWrapper;
import java.io.IOException;
/**
* @author tanghc
*/
public class EnvHttpRequestInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpRequestWrapper requestWrapper = new HttpRequestWrapper(request);
String newVersion = HystrixRequestVariableContext.getVersion();
requestWrapper.getHeaders().add(ParamNames.GRAY_HEADER_VERSION_NAME, newVersion);
return execution.execute(requestWrapper, body);
}
}

@ -0,0 +1,18 @@
package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
/**
* @author tanghc
*/
public class HystrixRequestVariableContext {
// private static final HystrixRequestVariableDefault<String> VERSION_HOLDER = new HystrixRequestVariableDefault<>();
private static final ThreadLocal<String> VERSION_HOLDER = new ThreadLocal<>();
public static void setVersion(String version) {
VERSION_HOLDER.set(version);
}
public static String getVersion() {
return VERSION_HOLDER.get();
}
}

@ -1,4 +1,4 @@
package com.gitee.sop.gateway.loadbalancer; package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
import lombok.Data; import lombok.Data;

@ -34,7 +34,7 @@ public class ZuulZookeeperRouteManager extends BaseRouteManager<ZuulServiceRoute
@Override @Override
protected ZuulTargetRoute buildRouteDefinition(ZuulServiceRouteInfo serviceRouteInfo, ZuulRouteDefinition routeDefinition) { protected ZuulTargetRoute buildRouteDefinition(ZuulServiceRouteInfo serviceRouteInfo, ZuulRouteDefinition routeDefinition) {
Route route = new Route(routeDefinition.getId(), routeDefinition.getPath(), RouteUtil.getZuulLocation(routeDefinition.getUri()), null, RETRYABLE, null); Route route = new Route(routeDefinition.getId(), routeDefinition.getPath(), RouteUtil.getZuulLocation(routeDefinition.getUri()), "", RETRYABLE, null);
return new ZuulTargetRoute(serviceRouteInfo, routeDefinition, route); return new ZuulTargetRoute(serviceRouteInfo, routeDefinition, route);
} }
} }

@ -42,7 +42,7 @@ public class ParamNames {
/** 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档 */ /** 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档 */
public static String BIZ_CONTENT_NAME = "biz_content"; public static String BIZ_CONTENT_NAME = "biz_content";
public static String HEADER_VERSION_NAME = "x-sop-version"; public static String HEADER_VERSION_NAME = "sop_gray_version";
/** 时间戳格式 */ /** 时间戳格式 */
public static String TIMESTAMP_PATTERN = "yyyy-MM-dd HH:mm:ss"; public static String TIMESTAMP_PATTERN = "yyyy-MM-dd HH:mm:ss";

@ -15,7 +15,6 @@ import javax.servlet.http.HttpServletRequest;
@Slf4j @Slf4j
public class ApiMappingRequestCondition implements RequestCondition<ApiMappingRequestCondition> { public class ApiMappingRequestCondition implements RequestCondition<ApiMappingRequestCondition> {
public static final String UTF_8 = "UTF-8";
private String defaultVersion = ServiceConfig.getInstance().getDefaultVersion(); private String defaultVersion = ServiceConfig.getInstance().getDefaultVersion();
private ApiMappingInfo apiMappingInfo; private ApiMappingInfo apiMappingInfo;

@ -130,7 +130,7 @@ public class AlipayController {
public Story getStory() { public Story getStory() {
Story story = new Story(); Story story = new Story();
story.setId(1); story.setId(1);
story.setName("海底小纵队(alipay.story.get),2222"); story.setName("海底小纵队(alipay.story.get1.0)");
return story; return story;
} }
@ -160,7 +160,10 @@ public class AlipayController {
*/ */
@ApiMapping(value = "alipay.story.get", version = "1.2") @ApiMapping(value = "alipay.story.get", version = "1.2")
public Story getStory11(Story story) { public Story getStory11(Story story) {
return story; Story story2 = new Story();
story2.setId(1);
story2.setName("海底小纵队(alipay.story.get1.2)");
return story2;
} }
/** /**

@ -1,6 +1,5 @@
package com.gitee.sop.gateway.config; package com.gitee.sop.gateway.config;
import com.gitee.sop.gateway.manager.ManagerInitializer;
import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration; import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration;
@ -20,9 +19,6 @@ import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfigurat
//@Configuration //@Configuration
public class GatewayConfig extends AlipayGatewayConfiguration { public class GatewayConfig extends AlipayGatewayConfiguration {
static {
new ManagerInitializer();
}
} }
/** /**

@ -7,7 +7,6 @@ package com.gitee.sop.gateway.config;
*/ */
import com.gitee.sop.gateway.loadbalancer.SopPropertiesFactory; import com.gitee.sop.gateway.loadbalancer.SopPropertiesFactory;
import com.gitee.sop.gateway.manager.ManagerInitializer;
import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration; import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration;
import org.springframework.cloud.netflix.ribbon.PropertiesFactory; import org.springframework.cloud.netflix.ribbon.PropertiesFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -20,10 +19,6 @@ import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
public class ZuulConfig extends AlipayZuulConfiguration { public class ZuulConfig extends AlipayZuulConfiguration {
static {
new ManagerInitializer();
}
@Bean @Bean
PropertiesFactory propertiesFactory() { PropertiesFactory propertiesFactory() {
return new SopPropertiesFactory(); return new SopPropertiesFactory();

@ -34,6 +34,9 @@ public class ConfigGrayUserkey {
/** 需要灰度的接口,goods.get=1.2,order.list=1.2, 数据库字段:name_version_content */ /** 需要灰度的接口,goods.get=1.2,order.list=1.2, 数据库字段:name_version_content */
private String nameVersionContent; private String nameVersionContent;
/** 0:禁用,1:启用, 数据库字段:status */
private Byte status;
/** 数据库字段:gmt_create */ /** 数据库字段:gmt_create */
private Date gmtCreate; private Date gmtCreate;

@ -1,6 +1,6 @@
package com.gitee.sop.gateway.loadbalancer; package com.gitee.sop.gateway.loadbalancer;
import com.gitee.sop.gateway.manager.UserKeyManager; import com.gitee.sop.gateway.manager.DbUserKeyManager;
import com.gitee.sop.gatewaycommon.bean.SpringContext; import com.gitee.sop.gatewaycommon.bean.SpringContext;
import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.param.Param; import com.gitee.sop.gatewaycommon.param.Param;
@ -83,14 +83,15 @@ public class EnvironmentServerChooser extends BaseServerChooser {
*/ */
protected boolean canVisitGray(Server server, HttpServletRequest request) { protected boolean canVisitGray(Server server, HttpServletRequest request) {
ApiParam apiParam = ZuulContext.getApiParam(); ApiParam apiParam = ZuulContext.getApiParam();
UserKeyManager userKeyManager = SpringContext.getBean(UserKeyManager.class); DbUserKeyManager userKeyManager = SpringContext.getBean(DbUserKeyManager.class);
boolean canVisit = false; boolean canVisit = false;
if (this.isGrayUser(apiParam, userKeyManager, server, request)) { if (this.isGrayUser(apiParam, userKeyManager, server, request)) {
// 指定灰度版本号 // 指定灰度版本号
String instanceId = server.getId(); String instanceId = server.getMetaInfo().getInstanceId();
String newVersion = userKeyManager.getVersion(instanceId, apiParam.fetchNameVersion()); String newVersion = userKeyManager.getVersion(instanceId, apiParam.fetchNameVersion());
if (newVersion != null) { if (newVersion != null) {
RequestContext.getCurrentContext().getZuulRequestHeaders().put(ParamNames.HEADER_VERSION_NAME, newVersion); // 在header中设置新的版本号,然后微服务端先获取这个新版本号
RequestContext.getCurrentContext().addZuulRequestHeader(ParamNames.GRAY_HEADER_VERSION_NAME, newVersion);
canVisit = true; canVisit = true;
} }
} }
@ -107,8 +108,8 @@ public class EnvironmentServerChooser extends BaseServerChooser {
* @param request request * @param request request
* @return true * @return true
*/ */
protected boolean isGrayUser(Param param, UserKeyManager userKeyManager, Server server, HttpServletRequest request) { protected boolean isGrayUser(Param param, DbUserKeyManager userKeyManager, Server server, HttpServletRequest request) {
String instanceId = server.getId(); String instanceId = server.getMetaInfo().getInstanceId();
// 这里的灰度用户为appKey,包含此appKey则为灰度用户,允许访问 // 这里的灰度用户为appKey,包含此appKey则为灰度用户,允许访问
String appKey = param.fetchAppKey(); String appKey = param.fetchAppKey();
return userKeyManager.containsKey(instanceId, appKey); return userKeyManager.containsKey(instanceId, appKey);

@ -9,6 +9,7 @@ import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.List; import java.util.List;
@ -19,6 +20,7 @@ import java.util.List;
* @author tanghc * @author tanghc
*/ */
@Slf4j @Slf4j
@Service
public class DbIPBlacklistManager extends DefaultIPBlacklistManager { public class DbIPBlacklistManager extends DefaultIPBlacklistManager {
@Autowired @Autowired

@ -3,6 +3,7 @@ package com.gitee.sop.gateway.manager;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.gitee.sop.gateway.entity.IsvDetailDTO; import com.gitee.sop.gateway.entity.IsvDetailDTO;
import com.gitee.sop.gateway.mapper.IsvInfoMapper; import com.gitee.sop.gateway.mapper.IsvInfoMapper;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.ChannelMsg; import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
import com.gitee.sop.gatewaycommon.bean.IsvDefinition; import com.gitee.sop.gatewaycommon.bean.IsvDefinition;
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext; import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
@ -11,6 +12,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.List; import java.util.List;
@ -19,6 +21,7 @@ import java.util.List;
* @author tanghc * @author tanghc
*/ */
@Slf4j @Slf4j
@Service
public class DbIsvManager extends CacheIsvManager { public class DbIsvManager extends CacheIsvManager {
@Autowired @Autowired
@ -41,6 +44,7 @@ public class DbIsvManager extends CacheIsvManager {
@PostConstruct @PostConstruct
protected void after() throws Exception { protected void after() throws Exception {
ApiConfig.getInstance().setIsvManager(this);
ZookeeperContext.setEnvironment(environment); ZookeeperContext.setEnvironment(environment);
String isvChannelPath = ZookeeperContext.getIsvInfoChannelPath(); String isvChannelPath = ZookeeperContext.getIsvInfoChannelPath();
ZookeeperContext.listenPath(isvChannelPath, nodeCache -> { ZookeeperContext.listenPath(isvChannelPath, nodeCache -> {

@ -17,6 +17,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -35,6 +36,7 @@ import static java.util.stream.Collectors.toList;
* @author tanghc * @author tanghc
*/ */
@Slf4j @Slf4j
@Service
public class DbIsvRoutePermissionManager extends DefaultIsvRoutePermissionManager { public class DbIsvRoutePermissionManager extends DefaultIsvRoutePermissionManager {
@Autowired @Autowired

@ -11,6 +11,7 @@ import com.gitee.sop.gatewaycommon.util.MyBeanUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -19,6 +20,7 @@ import javax.annotation.PostConstruct;
* @author tanghc * @author tanghc
*/ */
@Slf4j @Slf4j
@Service
public class DbLimitConfigManager extends DefaultLimitConfigManager { public class DbLimitConfigManager extends DefaultLimitConfigManager {
@Autowired @Autowired

@ -14,6 +14,7 @@ import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.Collection; import java.util.Collection;
@ -22,6 +23,7 @@ import java.util.Collection;
* @author tanghc * @author tanghc
*/ */
@Slf4j @Slf4j
@Service
public class DbRouteConfigManager extends DefaultRouteConfigManager { public class DbRouteConfigManager extends DefaultRouteConfigManager {
@Autowired @Autowired

@ -1,14 +1,14 @@
package com.gitee.sop.gateway.manager; package com.gitee.sop.gateway.manager;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.gateway.entity.ConfigGrayUserkey; import com.gitee.sop.gateway.entity.ConfigGrayUserkey;
import com.gitee.sop.gateway.loadbalancer.ServiceGrayConfig;
import com.gitee.sop.gateway.mapper.ConfigGrayUserkeyMapper; import com.gitee.sop.gateway.mapper.ConfigGrayUserkeyMapper;
import com.gitee.sop.gatewaycommon.bean.ChannelMsg; import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
import com.gitee.sop.gatewaycommon.bean.UserKeyDefinition; import com.gitee.sop.gatewaycommon.bean.UserKeyDefinition;
import com.gitee.sop.gatewaycommon.manager.DefaultUserKeyManager;
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext; import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
import com.google.common.collect.Maps; import com.gitee.sop.gatewaycommon.zuul.loadbalancer.ServiceGrayConfig;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -27,14 +27,11 @@ import java.util.stream.Stream;
* *
* @author tanghc * @author tanghc
*/ */
@Service
@Slf4j @Slf4j
public class UserKeyManager { @Service
public class DbUserKeyManager extends DefaultUserKeyManager {
/** private static final int STATUS_ENABLE = 1;
* KEY:instanceId
*/
private Map<String, ServiceGrayConfig> serviceUserKeyMap = Maps.newConcurrentMap();
@Autowired @Autowired
private Environment environment; private Environment environment;
@ -42,18 +39,14 @@ public class UserKeyManager {
@Autowired @Autowired
private ConfigGrayUserkeyMapper configGrayUserkeyMapper; private ConfigGrayUserkeyMapper configGrayUserkeyMapper;
public boolean containsKey(String serviceId, Object userKey) { @Override
if (serviceId == null || userKey == null) { public void load() {
return false; Query query = new Query();
query.eq("status", STATUS_ENABLE);
List<ConfigGrayUserkey> list = configGrayUserkeyMapper.list(query);
for (ConfigGrayUserkey configGrayUserkey : list) {
this.setServiceGrayConfig(configGrayUserkey);
} }
return this.getServiceGrayConfig(serviceId).containsKey(userKey);
}
public String getVersion(String serviceId, String nameVersion) {
if (serviceId == null || nameVersion == null) {
return null;
}
return this.getServiceGrayConfig(serviceId).getVersion(nameVersion);
} }
/** /**
@ -61,11 +54,12 @@ public class UserKeyManager {
* *
* @param configGrayUserkey 灰度配置 * @param configGrayUserkey 灰度配置
*/ */
public void setServiceGrayConfig(String serviceId, ConfigGrayUserkey configGrayUserkey) { public void setServiceGrayConfig(ConfigGrayUserkey configGrayUserkey) {
if (configGrayUserkey == null) { if (configGrayUserkey == null) {
return; return;
} }
this.clear(serviceId); String instanceId = configGrayUserkey.getInstanceId();
this.clear(instanceId);
String userKeyData = configGrayUserkey.getUserKeyContent(); String userKeyData = configGrayUserkey.getUserKeyContent();
String nameVersionContent = configGrayUserkey.getNameVersionContent(); String nameVersionContent = configGrayUserkey.getNameVersionContent();
String[] userKeys = StringUtils.split(userKeyData, ','); String[] userKeys = StringUtils.split(userKeyData, ',');
@ -73,7 +67,7 @@ public class UserKeyManager {
log.info("添加userKey,userKeys.length:{}, nameVersionList:{}", userKeys.length, Arrays.toString(nameVersionList)); log.info("添加userKey,userKeys.length:{}, nameVersionList:{}", userKeys.length, Arrays.toString(nameVersionList));
List<String> list = Stream.of(userKeys).collect(Collectors.toList()); List<String> list = Stream.of(userKeys).collect(Collectors.toList());
ServiceGrayConfig serviceGrayConfig = getServiceGrayConfig(serviceId); ServiceGrayConfig serviceGrayConfig = getServiceGrayConfig(instanceId);
serviceGrayConfig.getUserKeys().addAll(list); serviceGrayConfig.getUserKeys().addAll(list);
Map<String, String> grayNameVersion = serviceGrayConfig.getGrayNameVersion(); Map<String, String> grayNameVersion = serviceGrayConfig.getGrayNameVersion();
@ -89,20 +83,10 @@ public class UserKeyManager {
/** /**
* 清空用户key * 清空用户key
*/ */
public void clear(String serviceId) { public void clear(String instanceId) {
getServiceGrayConfig(serviceId).clear(); getServiceGrayConfig(instanceId).clear();
} }
public ServiceGrayConfig getServiceGrayConfig(String serviceId) {
ServiceGrayConfig serviceGrayConfig = serviceUserKeyMap.get(serviceId);
if (serviceGrayConfig == null) {
serviceGrayConfig = new ServiceGrayConfig();
serviceGrayConfig.setUserKeys(Sets.newConcurrentHashSet());
serviceGrayConfig.setGrayNameVersion(Maps.newConcurrentMap());
serviceUserKeyMap.put(serviceId, serviceGrayConfig);
}
return serviceGrayConfig;
}
@PostConstruct @PostConstruct
protected void after() throws Exception { protected void after() throws Exception {
@ -113,14 +97,14 @@ public class UserKeyManager {
ChannelMsg channelMsg = JSON.parseObject(nodeData, ChannelMsg.class); ChannelMsg channelMsg = JSON.parseObject(nodeData, ChannelMsg.class);
String data = channelMsg.getData(); String data = channelMsg.getData();
UserKeyDefinition userKeyDefinition = JSON.parseObject(data, UserKeyDefinition.class); UserKeyDefinition userKeyDefinition = JSON.parseObject(data, UserKeyDefinition.class);
String serviceId = userKeyDefinition.getServiceId(); String instanceId = userKeyDefinition.getInstanceId();
switch (channelMsg.getOperation()) { switch (channelMsg.getOperation()) {
case "set": case "set":
ConfigGrayUserkey configGrayUserkey = configGrayUserkeyMapper.getByColumn("service_id", serviceId); ConfigGrayUserkey configGrayUserkey = configGrayUserkeyMapper.getByColumn("instance_id", instanceId);
this.setServiceGrayConfig(serviceId, configGrayUserkey); this.setServiceGrayConfig(configGrayUserkey);
break; break;
case "clear": case "clear":
clear(serviceId); clear(instanceId);
break; break;
default: default:
log.error("userKey消息,错误的消息指令,nodeData:{}", nodeData); log.error("userKey消息,错误的消息指令,nodeData:{}", nodeData);

@ -14,5 +14,6 @@ public class ManagerInitializer {
apiConfig.setRouteConfigManager(new DbRouteConfigManager()); apiConfig.setRouteConfigManager(new DbRouteConfigManager());
apiConfig.setLimitConfigManager(new DbLimitConfigManager()); apiConfig.setLimitConfigManager(new DbLimitConfigManager());
apiConfig.setIpBlacklistManager(new DbIPBlacklistManager()); apiConfig.setIpBlacklistManager(new DbIPBlacklistManager());
apiConfig.setUserKeyManager(new DbUserKeyManager());
} }
} }

@ -231,4 +231,35 @@ public class AlipayClientPostTest extends TestBase {
System.out.println(responseData); System.out.println(responseData);
} }
@Test
public void testApiAbility() throws AlipayApiException {
// 公共请求参数
Map<String, String> params = new HashMap<String, String>();
params.put("app_id", appId);
params.put("method", "getStory2");
params.put("version", "1.0");
params.put("format", "json");
params.put("charset", "utf-8");
params.put("sign_type", "RSA2");
params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
// 业务参数
Map<String, String> bizContent = new HashMap<>();
params.put("biz_content", JSON.toJSONString(bizContent));
System.out.println("----------- 请求信息 -----------");
System.out.println("请求参数:" + buildParamQuery(params));
System.out.println("商户秘钥:" + privateKey);
String content = AlipaySignature.getSignContent(params);
System.out.println("待签名内容:" + content);
String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8");
System.out.println("签名(sign):" + sign);
params.put("sign", sign);
System.out.println("----------- 返回结果 -----------");
String responseData = get(url, params);// 发送请求
System.out.println(responseData);
}
} }

@ -181,6 +181,7 @@ CREATE TABLE `config_gray_userkey` (
`instance_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'serviceId', `instance_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'serviceId',
`user_key_content` text COMMENT '用户key,多个用引文逗号隔开', `user_key_content` text COMMENT '用户key,多个用引文逗号隔开',
`name_version_content` text COMMENT '需要灰度的接口,goods.get=1.2,order.list=1.2', `name_version_content` text COMMENT '需要灰度的接口,goods.get=1.2,order.list=1.2',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '0:禁用,1:启用',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),

Loading…
Cancel
Save