背景
在使用和风天气 API 开发天气查询网站时,API 请求需要传递 apikey
作为参数。当前项目中,apikey
是直接写在代码里的,而项目仓库是开源的,这带来了 apikey
泄露的风险。
解决方案
为了解决 apikey
直接暴露的问题,需要将其从项目代码中剥离,存放到更安全的位置。针对这一需求,考虑了以下两种方案:
- 搭建独立后端服务进行请求中转
- 将接口托管到云函数平台(如 LAF、Netlify)
方案分析
-
方案一
搭建独立的后端服务,通过中转方式管理apikey
和接口。这种传统的做法虽略显繁琐,但能够完全掌控接口能力,如灵活配置鉴权机制、缓存优化和压缩策略等,同时也降低了对第三方平台的依赖。 -
方案二
在云函数平台上,将apikey
配置为环境变量,通过函数的形式供前端调用。这种方案部署简单且无需维护服务器,是一个相对省心的选择。然而,其局限性在于平台的稳定性和灵活性(例如自定义鉴权、缓存策略和请求压缩等)。
最终选择
综合考虑后,我选择了方案一,即通过搭建独立后端服务来管理 apikey
和请求接口。这不仅提升了项目的安全性,还为未来扩展功能预留了足够的空间。
技术选择
这个项目只是中转接口调用,并且接口数量不多,不涉及数据库操作,所以偏向选择简单易用,易扩展的框架,最终选择了 koa 作为后端的框架。
实现过程
刚开始时,不会 koa 这个框架,直接看了官网,不太看得懂,转到了隔壁的阮一峰写的 koa 入门教程。
从监听开始
app.listen(3000, () => {
console.log("start");
});
启动了监听服务器之后,感觉这件事情完了一半,再把接口挨着挨着写出来好像就结束了
const main = async (ctx) => {
if (ctx.request.path === "/getWeather") {
const res = axios.get('https://xx.xx')
...
ctx.response.body = {
code: 200,
...
}
}
}
app.use(main)
这样根据 path 判断请求的路径,然后调用真实的请求,拿到返回值,返回给前端,开发完成啦!! 但是这事情真完了吗,看着这种代码不经泛起了恶心,经过一阵眩晕,不觉间想起了 mvc,什么 vc,mvc!
核心问题也没有得到解决,apikey 的管理。我们一个一个来。
对于 apikey 的处理
要存起来最快能想到的是通过文件存储,创建一个文件,名字叫.env,将 apikey 和其他需要的变量写进去。
API_KEY = "xxxx";
PORT = 3000;
之后在使用 dotenv 这个库将这些值处理为环境变量
import { fileURLToPath } from "url";
import dotenv from "dotenv";
import { dirname, resolve } from "path";
// Convert the module URL to a file path
const __filename = fileURLToPath(import.meta.url);
// Get the directory name
const __dirname = dirname(__filename);
dotenv.config({ path: resolve(__dirname, "../../.env") });
export const apiKey = process.env.API_KEY;
export const port = process.env.PORT || 3000;
这样在调用请求时可以直接引入 apiKey 进行调用
主要原因是 apikey
本身需要显式声明,但其管理方式需要更加安全。为此,可以将 apikey
存放在 .env
文件中,并将该文件添加到 .gitignore
,以防止其被提交到代码仓库。
使用 dotenv
的目的是将 apikey
作为环境变量获取,避免直接在代码中硬编码。在服务器部署时,只需添加对应的 .env
文件即可完成配置管理,甚至可以通过创建不同的环境文件(如 .env.production
和 .env.development
)来区分生产环境和开发环境的配置。这种方式不仅便于管理,还能根据环境需求动态调整配置。
需要注意的是,这是一种常见的管理方式。例如,在许多 AI 项目中,通常会预留 apikey
字段,让开发者自己填写。
路由结合 mvc
当接口多了之后 if else 的判断可读性和可维护性都比较差,其实后端也需要很好的管理每个接口,让接口排排坐,就像前端管理一个个组件,这个时候需要引入路由,根据请求路径,执行对应的 代码,最后发起真实请求,这里就有了 mvc 的味儿了。
附上伪码
// src/routes/apiRoutes.js
const Router = require("koa-router");
const apiController = require("../controllers/apiController");
const router = new Router({
prefix: "/api",
});
router.get("/data", apiController.getData);
router.get("/info", apiController.getInfo);
module.exports = router;
// src/controllers/apiController.js
const apiService = require("../services/apiService");
exports.getData = async (ctx) => {
try {
const data = await apiService.fetchData();
ctx.body = data;
} catch (error) {
ctx.status = 500;
ctx.body = { message: "Internal Server Error" };
}
};
// src/services/apiService.js
const axios = require("axios");
const config = require("../config");
exports.fetchData = async () => {
const response = await axios.get("https://third-party-api.com/data", {
headers: {
Authorization: `Bearer ${config.apiKey}`,
},
});
return response.data;
};
app.use(apiRoutes.routes()).use(apiRoutes.allowedMethods());