# api-backend **Repository Path**: nice_z/api-backend ## Basic Information - **Project Name**: api-backend - **Description**: QhhAc-API开放平台后端 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2023-12-19 - **Last Updated**: 2023-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 开发者数据平台 ## 项目简介 在线访问地址:[https://api.qhhac.cn/](https://api.qhhac.cn/) 前端项目地址:[https://gitee.com/qhh_ac/api-frontend](https://gitee.com/qhh_ac/api-frontend) ### 项目介绍 提供API接口供开发者调用的平台,基于Spring Boot后端+Vue3前端的全栈微服务项目。 管理员可以接入丙发布接口,统计分析接口调用清空;用户可以注册登录并开通接口调用权限、浏览接口、在线调试、还能使用**客户端SDK**轻松在代码中调用接口。 项目架构图: ![图片](https://gitee.com/qhh_ac/api-backend/raw/master/assets/images/api.jpg) store(浏览接口): ![图片](https://gitee.com/qhh_ac/api-backend/raw/master/assets/images/api_store.png) 接口管理: ![图片](https://gitee.com/qhh_ac/api-backend/raw/master/assets/images/menge_interface.png) 接口热度统计: ![热度统计](https://gitee.com/qhh_ac/api-backend/raw/master/assets/images/api_top.png) 在线调试: ![图片](https://gitee.com/qhh_ac/api-backend/raw/master/assets/images/interface_invoke.png) 我的接口: ![我的接口](https://gitee.com/qhh_ac/api-backend/raw/master/assets/images/ap_interface_list.png) 用户中心: ![用户中心](https://gitee.com/qhh_ac/api-backend/raw/master/assets/images/api_user_center.png) 项目结构: ![项目结构](https://gitee.com/qhh_ac/api-backend/raw/master/assets/images/api_struct.png) 使用之际开发的客户端SDK,一行代码调用接口: ![一行代码调用接口](https://gitee.com/qhh_ac/api-backend/raw/master/assets/images/api_invoke.png) ## 技术选型 前端 - Vue3 - VueRouter - Vuex - elment Plus - Apache Echarts 后端 - java - Spring boot - Spring Boot Starter (SDK 开发) - ???(网关、限流、日志实现) 项目分期 ### 初始化和展示(第一部分) - 项目介绍、设计、技术选型 - 基础项目的搭建 - 接口管理 - 用户查看接口 ### 接口调用(第二部分) - 开发模拟API接口 - 开发调用这个接口的代码 - 保证调用的安全性(API签名认证) - 客户端 SDK 的开发 - 管理员接口 发布 与调用 - 接口文档展示、接口在线调用 ### 接口计费与保护(第三部分) - 统计用户调用次数 - 限流 - 计费 - 日志 - 开通 ### 管理、统计分析(第四部分) - 提供可视化平台,用图表的方式展示所有的接口的调用情况,便于调整业务。 ## 项目模块设计分析(数据库) - 用户表: ```sql DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id', `userName` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户昵称', `userAccount` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '账号', `userAvatar` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户头像', `gender` tinyint NULL DEFAULT NULL COMMENT '性别', `userRole` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'user' COMMENT '用户角色:user / admin', `userPassword` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码', `accessKey` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'accessKey', `secretKey` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'secretKey', `createTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `isDelete` tinyint NOT NULL DEFAULT 0 COMMENT '是否删除', `email` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `uni_userAccount`(`userAccount` ASC) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户' ROW_FORMAT = Dynamic; ``` - 接口表: ```sql DROP TABLE IF EXISTS `interface_info`; CREATE TABLE `interface_info` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '名称', `description` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', `url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '接口地址', `host` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '主机名', `requestParams` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '请求参数', `requestParamsRemark` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '请求参数说明', `responseParamsRemark` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '响应参数说明', `requestHeader` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '请求头', `responseHeader` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '响应头', `status` int NOT NULL DEFAULT 0 COMMENT '接口状态(0-关闭,1-开启)', `method` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '请求类型', `userId` bigint NOT NULL COMMENT '创建人', `createTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `isDelete` tinyint NOT NULL DEFAULT 0 COMMENT '是否删除(0-未删, 1-已删)', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '接口信息' ROW_FORMAT = DYNAMIC; ``` - 接口用户信息表: ```sql DROP TABLE IF EXISTS `user_interface_info`; CREATE TABLE `user_interface_info` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `userId` bigint NOT NULL COMMENT '调用用户 id', `interfaceInfoId` bigint NOT NULL COMMENT '接口 id', `totalNum` int NOT NULL DEFAULT 0 COMMENT '总调用次数', `leftNum` int NOT NULL DEFAULT 0 COMMENT '剩余调用次数', `status` int NOT NULL DEFAULT 0 COMMENT '0-正常,1-禁用', `createTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `isDelete` tinyint NOT NULL DEFAULT 0 COMMENT '是否删除(0-未删, 1-已删)', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 34 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户调用接口关系' ROW_FORMAT = Dynamic; ``` ## API签名认证 - 本质: - 签发签名 - 使用签名(检验签名) - 为什么需要? - 保证安全性。 - 怎么实现? - 参数一: - accessKey : 调用的标识 userA,userB - 参数二: - secretKey:密钥 (复杂、无序、无规律)该参数不妨到请求头中 (类似用户名和密码,区别:ak、sk是无状态的) - 密钥不可直接在服务器之间传递,有可能会别拦截 - 参数三:sign - 加密方式:对称加密、非对称加密、md5签名(不可解密) 签名认证算法与 用户参数 + 密钥 =》 签名算法 =》 不可解密的值 - 服务器用一模一样的参数和算法去生成签名,只要和用户传的一致,就表示一致。 - 注意问题: - 请求重放问题 - 参数四: - 加入nonce随机数,只能用一次 服务端要保存用过的随机数 - 参数五: - 加 timestamp时间戳,校验时间戳是否过期。 - 参数六: - 用户自己的请求参数 - 具体实现: 参数封装: ```java private Map getHeaderMap(String body, String method) { Map hashMap = new HashMap<>(); hashMap.put(APIHeaderConstant.ACCESSKEY, accessKey); // 一定不能直接发送 // hashMap.put("secretKey", secretKey); // 只能一次请求只能调用一次接口,用后请求作废 hashMap.put(APIHeaderConstant.NONCE, RandomUtil.randomNumbers(4)); // 请求一定时间内有效 hashMap.put(APIHeaderConstant.TIMESTAMP, String.valueOf(System.currentTimeMillis() / 1000)); // 签名 hashMap.put(APIHeaderConstant.SIGN, genSign(body, secretKey)); System.out.println("secretKey:" + secretKey); System.out.println("genSign(body, secretKey):" + genSign(body, secretKey)); // 处理参数中文问题 body = URLUtil.encode(body, CharsetUtil.CHARSET_UTF_8); hashMap.put(APIHeaderConstant.BODY, body); // 下面是寻找调用接口的关键 hashMap.put(APIHeaderConstant.METHOD, method); return hashMap; } ``` - 参数校验: ```java // 3. 用户鉴权 (判断 accessKey 和 secretKey 是否合法) HttpHeaders headers = request.getHeaders(); String accessKey = headers.getFirst(APIHeaderConstant.ACCESSKEY); String timestamp = headers.getFirst(APIHeaderConstant.TIMESTAMP); String nonce = headers.getFirst(APIHeaderConstant.NONCE); String sign = headers.getFirst(APIHeaderConstant.SIGN); String body = URLUtil.decode(headers.getFirst(APIHeaderConstant.BODY), CharsetUtil.CHARSET_UTF_8); String method = headers.getFirst(APIHeaderConstant.METHOD); if (StringUtils.isEmpty(nonce) || StringUtils.isEmpty(sign) || StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(method)){ throw new BusinessException(ErrorCode.FORBIDDEN_ERROR, "请求头参数不完整!"); } // 3、通过 accessKey 查询是否存在该用户 User invokeUser = null; try { invokeUser = innerUserService.getInvokeUser(accessKey); } catch (Exception e) { log.error("getInvokeUser error", e); } if (invokeUser == null) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "accessKey不合法"); } if (Long.parseLong(nonce) > 10000L) { return handleNoAuth(response); } // 4、判断随机数是否存在,防止重放攻击 String existNonce = (String) redisTemplate.opsForValue().get(nonce); if (StringUtil.isNotBlank(existNonce)) { throw new BusinessException(ErrorCode.FORBIDDEN_ERROR, "请求重复!"); } // 5、时间和当前时间不能超过 5 分钟 Long currentTime = System.currentTimeMillis() / 1000; final Long FIVE_MINUTES = 60 * 5L; if ((currentTime - Long.parseLong(timestamp)) >= FIVE_MINUTES) { return handleNoAuth(response); } // 5、从数据库中查出 secretKey String secretKey = invokeUser.getSecretKey(); String serverSign = SignUtils.genSign(body, secretKey); if (sign == null || !sign.equals(serverSign)) { return handleNoAuth(response); } ``` ## 创建starter步骤 1.新建一个 spring boot 初始化项目 2.添加依赖,Lombok, Spring Configuration Processor Spring Configuration Processor 的作用是自动生成代码提示 3.修改 pom 文件的版本号,并删除 build 4.删除原本自动创建的主类,新建一个类, 并添加需要用到的依赖 5.在 resources 目录下新建 META-INF 目录,并创建 spring.factories 文件,并指定配置类的目录 6.install 打包构建在本地的仓库 7.复制 groupId, artifactId, version,可以在其他项目中添加为依赖