有些 API 请求需要用到密钥,直接将密钥暴露在前端存在一定的风险,那么可以尝试把该部分程序放在服务器端执行,直接把结果返回给前端。
听起来很有道理,可惜我不会。不会就去现学,在 ChatGPT 这个不靠谱的老师教导下,摸索半天终于搞出了点效果。
这里整理记录一下,也不知道对不对。

概述

这里以和风天气为例,做个获取当前城市天气信息的 API。和风天气有现成的插件,但是样式是固定的,只能添加一个插件,而且密钥是暴露在前端的。所以我打算用和风天气 API 获取天气数据。和风天气 API 请求 URL 结构如下:

1
2
3
https://devapi.qweather.com/v7/weather/now?location=xxx&key=xxx
\___/ \_________________/\______________/\__________________/
scheme host (port) path query parameters

其中重要参数:

  • location: 城市代码;
  • key: 和风 KEY。

参考文档:

和风 KEY 在控制台可以找到,城市代码则需要由和风天气 GeoAPI 城市搜索功能获取。GeoAPI 城市搜索请求 URL 结构如下:

1
2
3
https://geoapi.qweather.com/v2/city/lookup?location=xxx&adm=xxx&key=xxx
\___/ \_________________/\______________/\__________________________/
scheme host (port) path query parameters

其中重要参数:

  • location: 必选,城市名称;
  • adm: 可选,所属上一级行政区域;
  • key: 必选,和风 KEY;
  • 另外还有 range、number、lang 等可选参数,这里暂不需要。

参考文档:

这里城市名称需要用到 腾讯位置服务 的 IP 定位功能。

1
2
3
https://apis.map.qq.com/ws/location/v1/ip?ip=xxx&key=xxx
\___/ \_____________/\_________________/\____________/
scheme host (port) path query parameters

其中重要参数:

  • ip: 客户端 IP 地址;
  • key: 腾讯 KEY。

参考文档:

客户端 IP 地址由前端获取并传入,这里采用的由 Paul Seelman 提供的 IP 定位服务,无需参数,自动获取当前网络 IP。

1
2
3
4
5
6
7
8
9
fetch('https://api.ipify.org?format=json')
.then(response => response.json())
.then(data => {
var ipAddress = data.ip;
console.log("IP 地址:" + ipAddress);
})
.catch(error => {
console.error('获取 IP 地址失败:', error);
});

整体流程如下:

流程图

新建 github 仓库

首先,我们在 github 新建一个 私有的 仓库,并上传几个文件。

index.js

这里包含了服务器端处理的程序,注意文件名一定要是 index ,包括后面的文件里也要保持一致。另外为解决跨域问题,需要引入 cors 模块,将你的域名填入白名单。

这里按理说使用通配符“*”就可以,但是不知道为什么没有效果,就老老实实写全域名了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 导入所需包和模块
const fetch = require('node-fetch');
const express = require('express');
const cors = require('cors');
require('dotenv').config(); // 导入 dotenv 并加载 .env 文件
const app = express();
app.use(cors({
origin: ['https://cansin.top', 'https://blog.cansin.top', 'https://gavin-chen.top', 'https://*.gavin-chen.top'] // 指定允许跨域访问的域名
}));
// 创建 Express 应用程序
app.use(express.json());
// 路由和处理程序
app.get('/weather', async (req, res) => {
try {
const ip = req.query.ip; // 请求后面带ip的值,格式:domain/weather?ip=xxx
const weatherKey = process.env.WEATHER_API_KEY; // 从环境变量中获取 API 密钥
const locationKey = process.env.LOCATION_API_KEY; // 从环境变量中获取 API 密钥
var weatherNowJson = {};
var weatherJson = {};
var locationJson = {};
const response1 = await fetch(`https://apis.map.qq.com/ws/location/v1/ip?ip=${ip}&key=${locationKey}`);
const data1 = await response1.json();
locationJson = data1;
try {
var province = data1.result.ad_info.province;
var city = data1.result.ad_info.city;
var district = data1.result.ad_info.district;
var place = district == '' ? city : district;
var adm = district == '' ? province : city;
const response2 = await fetch(`https://geoapi.qweather.com/v2/city/lookup?location=${place}&adm=${adm}&key=${weatherKey}`);
const data2 = await response2.json();
try {
var cityId = data2.location[0].id;
const response3 = await fetch(`https://devapi.qweather.com/v7/weather/now?location=${cityId}&key=${weatherKey}`);
const data3 = await response3.json();
const response4 = await fetch(`https://devapi.qweather.com/v7/weather/24h?location=${cityId}&key=${weatherKey}`);
const data4 = await response4.json();
weatherNowJson = data3;
weatherJson = data4;
res.json({ weatherNowJson, weatherJson, locationJson }); // 将 JSON 数据返回给前端
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error3' });
}
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error2' });
}
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error1' });
}
});

// 启动服务器
const server = app.listen(process.env.PORT || 3000, () => {
const port = server.address().port;
console.log(`Server is running on port ${port}`);
});

package.json

这里注意文件名 index.js ,还有 dependencies 中列出所有需要的包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"name": "projectname",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"author": "Gavin",
"license": "MIT",
"dependencies": {
"axios": "^0.27.2",
"express": "^4.18.1",
"dotenv": "^8.2.0",
"node-fetch": "^2.6.1",
"cors": "^2.8.5"
}
}

vercel.json

这里在环境变量里填入密钥。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"version": 2,
"builds": [
{
"src": "./index.js",
"use": "@vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "/"
}
],
"env": {
"WEATHER_API_KEY": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"LOCATION_API_KEY": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}

导入 vercel

接下来就是在 vercel 导入该仓库,并绑定自己的域名,比如 https://api.domain.com ,此时在浏览器输入 https://api.domain.com/weather?ip=xxx.xxx.xxx.xxx ,即可返回一串包含了天气信息的 json 数据。

前端处理

注意 url 后加上 '&output=jsonp' ,防止跨域问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fetch('https://api.ipify.org?format=json')
.then(response => response.json())
.then(data => {
var ipAddress = data.ip;
console.log("IP 地址:" + ipAddress);
fetch('https://apis.cansin.top/weather?ip='+ipAddress+'&output=jsonp')
.then(response => response.json())
.then(data => {
console.log(data);
// 此处填写json数据读取与处理程序
})
.catch(error => {
console.error('获取天气信息失败:', error);
})
})
.catch(error => {
console.error('获取 IP 地址失败:', error);
});

最后

目前来看,利用ChatGPT学习还是不太方便的,首先是对提问的句式有很高的要求,其次给出的答案经常不是最优解,甚至常常给出错误答案(比如代码方面),我需要不停的发现问题,纠正它的错误——这其实还不如一篇CSDN的经验贴。因此,作为初学者,还是老老实实看一些系统性的课程或者专业的书籍,而ChatGPT可以作为一本辞典来使用。
上面的代码很多地方还没琢磨明白,有空还是得翻翻文档仔细研究研究。
最后要感谢Wei Wei视频的讲解,照着现成的项目摸索简直是事半功倍,希望这种小白类的视频可以多一点。