开放平台设计

1,094 阅读7分钟

应具备哪些能力

  • 应用授权
  • 签名验证
  • 密钥管理
  • 接口权限分配
  • 动态路由
  • 接口限流
  • ip黑名单/白名单
  • 监控日志
  • 文档整合
  • 基础sdk(java、php等)
  • 用户token 认证

架构图

开放平台技术架构.jpg

表设计

1、应用信息表

CREATE TABLE `app_info` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `app_key` varchar(100) NOT NULL COMMENT 'appKey',
  `status` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '1启用,2禁用',
  `sign_type` tinyint NOT NULL DEFAULT '1' COMMENT '1:MD5,2:RSA2',
  `remark` varchar(128) DEFAULT NULL COMMENT '备注',
  `ip_list` varchar(128) DEFAULT NULL COMMENT 'ip 白名单, 空-不校验',
  `third_id` int DEFAULT '0' COMMENT 'ice_third_info.id',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_app_key` (`app_key`),
  KEY `idx_userid` (`user_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='应用信息表';

2、 应用密钥

CREATE TABLE `app_keys` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `app_key` varchar(128) NOT NULL DEFAULT '',
  `sign_type` tinyint NOT NULL DEFAULT '1' COMMENT '1:MD5,2:RSA2',
  `secret` varchar(200) NOT NULL DEFAULT '' COMMENT 'sign_type=1时使用',
  `key_format` tinyint NOT NULL DEFAULT '1' COMMENT '秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用)',
  `public_key_isv` text NOT NULL COMMENT '开发者生成的公钥',
  `private_key_isv` text NOT NULL COMMENT '开发者生成的私钥(交给开发者)',
  `public_key_platform` text NOT NULL COMMENT '平台生成的公钥(交给开发者)',
  `private_key_platform` text NOT NULL COMMENT '平台生成的私钥',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_appkey` (`app_key`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='应用秘钥';

3、路由配置

CREATE TABLE `route` (
  `id` varchar(128) NOT NULL DEFAULT '' COMMENT '路由id',
  `service_id` varchar(128) NOT NULL DEFAULT '服务id',
  `name` varchar(128) NOT NULL DEFAULT '' COMMENT '接口名',
  `version` varchar(64) NOT NULL DEFAULT '' COMMENT '版本号',
  `predicates` varchar(256) DEFAULT NULL COMMENT '路由断言(SpringCloudGateway专用)',
  `filters` varchar(256) DEFAULT NULL COMMENT '路由过滤器(SpringCloudGateway专用)',
  `uri` varchar(128) NOT NULL DEFAULT '' COMMENT '路由规则转发的目标uri',
  `path` varchar(128) NOT NULL DEFAULT '' COMMENT 'uri后面跟的path',
  `order_index` int NOT NULL DEFAULT '0' COMMENT '路由执行的顺序',
  `ignore_validate` tinyint NOT NULL DEFAULT '0' COMMENT '是否忽略验证,业务参数验证除外',
  `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态,0:待审核,1:启用,2:禁用',
  `merge_result` tinyint NOT NULL DEFAULT '0' COMMENT '是否合并结果',
  `permission` tinyint NOT NULL DEFAULT '0' COMMENT '是否需要授权才能访问',
  `need_token` tinyint NOT NULL DEFAULT '0' COMMENT '是否需要token',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_serviceid` (`service_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='路由配置';

4、应用权限

CREATE TABLE `app_permission` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `app_key` varchar(128) NOT NULL COMMENT '应用 appKey',
  `route_id` varchar(64) NOT NULL COMMENT 'api_id',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_app_perm` (`app_key`,`route_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='应用url 权限';

5、限流配置

CREATE TABLE `route_limit` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `route_id` varchar(128) DEFAULT NULL COMMENT '路由id',
  `app_key` varchar(128) DEFAULT NULL COMMENT '应用 appKey',
  `limit_ip` varchar(300) DEFAULT NULL COMMENT '限流ip,多个用英文逗号隔开',
  `service_id` varchar(64) NOT NULL DEFAULT '' COMMENT '服务id',
  `limit_type` tinyint NOT NULL DEFAULT '1' COMMENT '限流策略,1:窗口策略,2:令牌桶策略',
  `exec_count_per_second` int DEFAULT NULL COMMENT '每秒可处理请求数',
  `duration_seconds` int NOT NULL DEFAULT '1' COMMENT '限流持续时间,默认1秒,即每durationSeconds秒允许多少请求(当limit_type=1时有效)',
  `limit_code` varchar(64) DEFAULT NULL COMMENT '返回的错误码',
  `limit_msg` varchar(100) DEFAULT NULL COMMENT '返回的错误信息',
  `token_bucket_count` int DEFAULT NULL COMMENT '令牌桶容量',
  `limit_status` tinyint NOT NULL DEFAULT '0' COMMENT '限流开启状态,1:开启,0关闭',
  `order_index` int NOT NULL DEFAULT '0' COMMENT '顺序,值小的优先执行',
  `remark` varchar(128) DEFAULT NULL COMMENT '备注',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='限流配置'

6、IP 黑名单

CREATE TABLE `ip_black` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `ip` varchar(64) NOT NULL DEFAULT '' COMMENT 'ip',
  `remark` varchar(128) DEFAULT NULL COMMENT '备注',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_ip` (`ip`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='IP黑名单';

7、接口监控信息

CREATE TABLE `monitor_info` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `route_id` varchar(128) NOT NULL DEFAULT '' COMMENT '路由id',
  `name` varchar(128) NOT NULL DEFAULT '' COMMENT '接口名',
  `version` varchar(64) NOT NULL DEFAULT '' COMMENT '版本号',
  `service_id` varchar(64) NOT NULL DEFAULT '',
  `instance_id` varchar(128) NOT NULL DEFAULT '',
  `max_time` int NOT NULL DEFAULT '0' COMMENT '请求耗时最长时间',
  `min_time` int NOT NULL DEFAULT '0' COMMENT '请求耗时最小时间',
  `total_time` bigint NOT NULL DEFAULT '0' COMMENT '总时长,毫秒',
  `total_request_count` bigint NOT NULL DEFAULT '0' COMMENT '总调用次数',
  `success_count` bigint NOT NULL DEFAULT '0' COMMENT '成功次数',
  `error_count` bigint NOT NULL DEFAULT '0' COMMENT '失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败)',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_routeid` (`route_id`,`instance_id`) USING BTREE,
  KEY `idex_name` (`name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='接口监控信息';

8、监控错误日志

CREATE TABLE `monitor_info_error` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `error_id` varchar(64) NOT NULL DEFAULT '' COMMENT '错误id,md5Hex(instanceId + routeId + errorMsg)',
  `instance_id` varchar(128) NOT NULL DEFAULT '' COMMENT '实例id',
  `route_id` varchar(128) NOT NULL DEFAULT '',
  `error_msg` text NOT NULL,
  `error_status` int NOT NULL DEFAULT '0' COMMENT 'http status,非200错误',
  `count` int NOT NULL DEFAULT '0' COMMENT '错误次数',
  `is_deleted` tinyint NOT NULL DEFAULT '0',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_errorid` (`error_id`) USING BTREE,
  KEY `idx_routeid` (`route_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

9、资源表

CREATE TABLE `resource` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(64) NOT NULL DEFAULT '' COMMENT '资源名称',
  `content` varchar(128) NOT NULL DEFAULT '' COMMENT '资源内容(URL)',
  `ext_content` text,
  `version` varchar(32) NOT NULL DEFAULT '' COMMENT '版本',
  `type` tinyint NOT NULL DEFAULT '0' COMMENT '资源类型:0:SDK链接',
  `is_deleted` tinyint NOT NULL DEFAULT '0',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='资源表';

方法名

1、 增加 @interface Open.class

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Open {

    /**
     * 接口名,如:fee.query
     */
    String value();

    /**
     * 版本号,默认版本号是""
     */
    String version() default "";

    /**
     * 忽略验证,业务参数除外
     */
    boolean ignoreValidate() default false;

    /**
     * 指定接口是否需要授权才能访问,可在admin中进行修改
     */
    boolean permission() default false;

    /**
     * 是否需要appAuthToken,设置为true,网关端会校验token是否存在
     */
    boolean needToken() default false;

    /**
     * 定义业务错误码,用于文档显示
     */
    BizCode[] bizCode() default {};
}

2、 增加@interface BizCode

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BizCode {

    /**
     * 错误码
     *
     * @return
     */
    String code();

    /**
     * 错误描述
     * @return
     */
    String msg();

    /**
     * 解决方案
     * @return
     */
    String solution() default "";
}

  1. 实现方式, 在restfull 接口上增加 @Open("") 注解
@Open("fee.query")
@RequestMapping("/fee/query") 
public ObjectResponse<QueryFeeResponse> request(ApiBaseRequest apiBaseRequest) {
   return null;
}
  1. serviceName 名称规范

接口命名没有做强制要求,但我们还是推荐按照下面的方式进行命名:

接口名的命名规则为:服务模块.业务模块.功能模块.行为,如:

  • fee.user.monthcard.query 计费服务.用户模块.月卡.查寻

如果觉得命名规则有点长可以精简为:服务模块.功能模块.行为,如fee.monthcard.query,前提是确保前缀要有所区分,不和其它服务冲突。

路由自动加载

  1. 应用服务监听 ApplicationListener 的ContextRefreshedEvent 事件
  2. 获取所有RequestMappingHandlerMapping 接口映射
  3. 组装ice_route 路由对象
  4. 将组装的路由对象列表 异步发送 给 base 项目
  5. base 项目把 路由信息存储到db,并使用消息总线 刷新gateway route信息

伪代码 :

@Slf4j
public class RequestMappingScan implements ApplicationListener<ContextRefreshedEvent> {
    private RabbitSender rabbitSender;
    private static final AntPathMatcher pathMatch = new AntPathMatcher();

    private CenterScanProperties scanProperties;
    
    public RequestMappingScan(RabbitSender rabbitSender, CenterScanProperties centerScanProperties) {
        this.rabbitSender = rabbitSender;
        this.scanProperties = centerScanProperties;
    }

    /**
     * 初始化方法
     *
     * @param event
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        if (rabbitSender == null || scanProperties == null || !scanProperties.isRegisterRequestMapping()) {
            return;
        }
        Environment env = applicationContext.getEnvironment();
        // 服务名称
        String serviceId = env.getProperty("spring.application.name", "application");
        // 所有接口映射
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        // 获取url与类和方法的对应信息
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
        List<RequestMatcher> permitAll = Lists.newArrayList();
       
        List<Map<String, String>> list = new ArrayList<Map<String, String>>();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> m : map.entrySet()) {
            RequestMappingInfo info = m.getKey();
            HandlerMethod method = m.getValue();
       
           if (method.getMethodAnnotation(Open.class) == null) {
                // 忽略非open 接口
                continue;
            }
            Set<MediaType> mediaTypeSet = info.getProducesCondition().getProducibleMediaTypes();
            for (MethodParameter params : method.getMethodParameters()) {
                if (params.hasParameterAnnotation(RequestBody.class)) {
                    mediaTypeSet.add(MediaType.APPLICATION_JSON_UTF8);
                    break;
                }
            }
            list.add(convertApi(serviceId, mediaTypeSet, info, method, permitAll));
        }
        JSONObject resource = new JSONObject();
        resource.put("application", serviceId);
        resource.put("mapping", list);
        log.info("ApplicationReadyEvent:[{}]", serviceId);
        rabbitSender.sendMessage("", QueueConstants.QUEUE_SCAN_API_RESOURCE, resource);
    }
}

gateway 处理

1、获取请求参数,验签

2、修改请求参数,路由转发。参考自:blog.csdn.net/fuck487/art…

3、接口监控处理

4、gateway 监听消息总线事件 自动刷新路由或者配置信息(路由信息、应用权限、ip黑名单)

门户网站

1、 申请应用信息

2、上传 应用公钥或者服务密钥

3、开发文档

  • 请求地址
  • 公共参数(pid、serviceName、 sign、 timestamp、signType )
  • 请求业务参数说明
  • 返回参数说明
  • 请求示例
  • 响应示例
  • 异常示例
  • 返回错误码说明
  • API 调试工具

image-20211115172616296.png

4、门户服务监听nacos 事件。业务启动时,门户服务获取 api-doc 文档,执行 http://{ip}:{port}/v2/api-docs ,讲swagge json 放入本地内存中。 当业务服务关闭时,删除内存中的 swagge json。

5、h5 重写swagge ui