-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1f7c3cd
commit 741f806
Showing
6 changed files
with
316 additions
and
1 deletion.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
--- | ||
title: 使用jsonp请求心知天气数据 | ||
createTime: 2024/09/26 23:53:03 | ||
permalink: /article/use-jsonp-request-xinzhi-weather/ | ||
outline: [2, 6] | ||
--- | ||
没想到一个简单的 API 请求,调通花了我一个小时,只能说官网文档描述的不够清晰,而且不同文档之间还有出入,挺让人迷糊的。 | ||
|
||
下面完整记录一下流程,其实真的很简单。 | ||
|
||
## 准备工作 | ||
### 获取公钥和私钥 | ||
使用 jsonp 请求天气数据,必须要通过公钥签名的方式进行请求,不能通过私钥请求。 | ||
|
||
当你添加了 API 产品后,即可在`控制台`->`产品管理`中点击某个产品,查看该 API 产品的密钥,这里用的就是免费版。 | ||
|
||
![](../.vuepress/public/images/0ec1ac493140fcb94fcb64e9a80a7ea4.png) | ||
|
||
### 配置域名 | ||
心知天气回对发起请求的原站做域名校验,只有绑定的域名才被准许请求,对于本地调试来说,就需要绑定一个 `127.0.0.1`,见上图。 | ||
|
||
同时本地开发环境也需要修改,vue 默认的预览地址是 localhost,也需要改成 `127.0.0.1`。我这里使用的是 vite,在 defineConfig 中添加 server 成员即可。 | ||
|
||
```javascript | ||
export default defineConfig({ | ||
// 其他配置 | ||
server: { | ||
host: '127.0.0.1', | ||
port: 8080, | ||
}, | ||
}) | ||
``` | ||
|
||
## jsonp 封装 | ||
jsonp 请求比较具有工具性质,可以单独封装一下,后面也可以重复使用,这里直接给出我写的接口,里面有详细注释,后面使用均基于此。 | ||
|
||
```javascript | ||
export function jsonpRequest(url, callbackName) { | ||
return new Promise((resolve, reject) => { | ||
// 检查 URL 是否已经包含 callback 参数 | ||
let urlAlreadyHasCallback = /[?&]callback=/.test(url); | ||
let scriptSrc = url; | ||
let actualCallbackName = callbackName; | ||
|
||
if (!urlAlreadyHasCallback) { | ||
// 使用用户指定的 callbackName 或生成一个唯一的回调函数名 | ||
actualCallbackName = `${callbackName}_${Date.now()}_${Math.random().toString(16).slice(2)}`; | ||
} | ||
|
||
// 定义全局的回调函数 | ||
window[actualCallbackName] = function (data) { | ||
// 调用对应的 resolve 函数 | ||
resolve(data); | ||
// 移除回调函数 | ||
delete window[actualCallbackName]; | ||
// 移除 script 标签 | ||
let script = document.querySelector(`script[src*="${actualCallbackName}"]`); | ||
if (script) { | ||
document.body.removeChild(script); | ||
} | ||
}; | ||
|
||
if (!urlAlreadyHasCallback) { | ||
scriptSrc = url + (url.indexOf('?') === -1 ? '?' : '&') + 'callback=' + actualCallbackName; | ||
} | ||
|
||
// 创建一个 script 标签 | ||
let script = document.createElement('script'); | ||
script.type = 'text/javascript'; | ||
script.src = scriptSrc; | ||
script.onerror = function () { | ||
reject(new Error('JSONP request failed')); | ||
// 移除回调函数 | ||
delete window[actualCallbackName]; | ||
document.body.removeChild(script); | ||
}; | ||
|
||
// 添加 script 标签到文档中 | ||
document.body.appendChild(script); | ||
}); | ||
} | ||
``` | ||
|
||
## 请求 URL 构建 | ||
这里以查询地址为例(因为需要地址 ID 去查询天气,这个是基础)。 | ||
|
||
### 确定请求 API | ||
心知天气的 API,查询地址的 API 如下: | ||
|
||
```javascript | ||
https://api.seniverse.com/v3/location/search.json | ||
``` | ||
|
||
其中请求所需参数为 `q=查询文本`。 | ||
|
||
### 确认 jsonp API 参数 | ||
通过 jsonp 请求,除了基本的 API 参数外,还需要额外的公钥、时间戳、保活时间、回调函数,其中保活时间不是必须的。 | ||
|
||
```javascript | ||
let params = { | ||
q: searchText, | ||
public_key: import.meta.env.VITE_APP_XINZHI_WEATHER_PUBLIC_KEY, | ||
ts: Date.now(), | ||
ttl: 300, | ||
callback: callbackName, | ||
}; | ||
``` | ||
|
||
### 计算签名 | ||
使用上一步的所有参数通过特定算法进行计算,具体步骤如下: | ||
|
||
1、将所有参数按照 `key=value` 的格式字典序升序进行排列,中间用`&`进行连接。对于本请求,就是下面这样: | ||
|
||
```javascript | ||
callback=callbackName&public_key=your_public_key&q=beijing&ts=1727534325845&ttl=300 | ||
``` | ||
|
||
2、使用私钥,对上一步产生的字符串进行 HMAC-SHA1 的方式做哈希运算得到二进制结果,并用 Base64 的方式编码,得到一串哈希字符串。 | ||
|
||
3、再将上一步的哈希字符串做 URLEncode 编码,即可得到最终的签名。 | ||
|
||
看起来很复杂,实际很简单,这里直接贴出计算代码,需要注意的是,首先要安装 `crypto-js` 库: | ||
|
||
```javascript | ||
npm install crypto-js | ||
``` | ||
|
||
然后就可以计算签名了: | ||
|
||
```javascript | ||
import CryptoJS from "crypto-js" | ||
|
||
function generateSig(params, privateKey) { | ||
// 先按照键的字典序升序排列所有的键值对 | ||
let entries = Object.entries(params).sort((a, b) => a[0].localeCompare(b[0])); | ||
// 将键值对转换为 "key=value" 格式 | ||
let keyValuePairs = entries.map(([key, value]) => `${key}=${value}`); | ||
// 将键值对连接成字符串 | ||
let paramsString = keyValuePairs.join('&'); | ||
// 生成签名 | ||
let sig = encodeURIComponent( | ||
CryptoJS.HmacSHA1(paramsString, privateKey) | ||
.toString(CryptoJS.enc.Base64)); | ||
|
||
return sig; | ||
} | ||
``` | ||
|
||
### 拼接最终 URL | ||
计算完签名后,需要把签名在拼接到上一步排列好的字符串后面,即在后面加上 `&sig=sigString` 。 | ||
|
||
其实可以在上一步一并完成,这里合并为一个新的接口: | ||
|
||
```javascript | ||
function (params) { | ||
// 先按照键的字典序升序排列所有的键值对 | ||
let entries = Object.entries(params).sort((a, b) => a[0].localeCompare(b[0])); | ||
// 将键值对转换为 "key=value" 格式 | ||
let keyValuePairs = entries.map(([key, value]) => `${key}=${value}`); | ||
// 将键值对连接成字符串 | ||
let paramsString = keyValuePairs.join('&'); | ||
// 生成签名 | ||
let sig = encodeURIComponent( | ||
CryptoJS.HmacSHA1(paramsString, import.meta.env.VITE_APP_XINZHI_WEATHER_PRIVATE_KEY) | ||
.toString(CryptoJS.enc.Base64)); | ||
paramsString += '&sig=' + sig; | ||
|
||
return paramsString; | ||
} | ||
``` | ||
## 测试请求 | ||
结合封装好的 jsonp,就可以进行请求测试了: | ||
```javascript | ||
function searchLocation(searchText) { | ||
return new Promise((resolve, reject) => { | ||
let searchLocationUrl = 'https://api.seniverse.com/v3/location/search.json'; | ||
let callbackName = 'jsonpCallback_searchLocation'; | ||
let params = { | ||
q: searchText, | ||
public_key: import.meta.env.VITE_APP_XINZHI_WEATHER_PUBLIC_KEY, | ||
ts: Date.now(), | ||
ttl: 300, | ||
callback: callbackName, | ||
}; | ||
let paramsString = generateRequestParamsString(params); | ||
searchLocationUrl += '?' + paramsString; | ||
jsonpRequest(searchLocationUrl, callbackName) | ||
.then(data => { | ||
if (data.results.length > 0) { | ||
resolve(data.results); | ||
} else { | ||
reject(data); | ||
} | ||
}) | ||
.catch(error => { | ||
reject(error); | ||
}); | ||
}) | ||
} | ||
``` | ||
上述代码除了公钥和私钥需要自行替换外,可以直接使用,一共三个接口 `jsonpRequest`、`generateRequestParamsString`、`searchLocation`。搜索地址的接口也可以根据实际情况修改为其他业务,比如查询当前的天气,获取未来的天气等等。 | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
--- | ||
title: 在GitHub Action中使用密钥 | ||
createTime: 2024/09/28 23:24:49 | ||
permalink: /article/use-secret-in-github-action/ | ||
outline: [2, 6] | ||
--- | ||
## 写在前面 | ||
我们在开发引用第三方 API 时,往往需要用到 API 平台要求的 KEY,这些密钥在本地可以通过文件的形式引入到代码中,然后版本维护的时候不上传到 GitHub,这样可以一定程度上避免密钥泄露的风险,当我们使用 GitHub 进行打包编译的时候,又需要用到密钥,这时候就可以用 GitHub 提供的密钥管理工具管理我们的密钥。 | ||
|
||
## 本地密钥 | ||
这里本地的密钥是通过 vite 环境变量的方式引用,即将密钥写在单独的环境变量文件 .env 中,然后在 .gitignore 中忽略 .env 文件。 | ||
|
||
```javascript | ||
VITE_MY_PRAVITE_KEY=xxxxxxxxxxxx | ||
``` | ||
|
||
代码里就可以通过如下方式直接使用: | ||
|
||
```javascript | ||
import.meta.env.VITE_MY_PRAVITE_KEY | ||
``` | ||
|
||
## GitHub 密钥 | ||
### 创建密钥 | ||
在代码仓库 `Setting`->`Actions secrets and variables`->`Actions`->`Repository secrets`中新建一个密钥。 | ||
|
||
![](../.vuepress/public/images/5412c6455a3a6a003755d37ae3864ee2.png) | ||
|
||
输入密钥名称和密钥,点击添加即可: | ||
|
||
![](../.vuepress/public/images/a70be4b8f603226eb28257a603e74693.png) | ||
|
||
### 使用密钥 | ||
然后在 GitHub Aciton 的部署文件 deploy.xml 中就可以通过如下方式进行引用: | ||
|
||
```javascript | ||
# 其他构建步骤 | ||
|
||
- name: Set environment secrets | ||
env: | ||
VITE_MY_PRAVITE_KEY: ${{ secrets.VITE_MY_PRAVITE_KEY }} | ||
run: | | ||
touch .env | ||
echo "VITE_MY_PRAVITE_KEY = VITE_MY_PRAVITE_KEY" >> .env | ||
|
||
# 其他构建步骤 | ||
``` | ||
|
||
这里就是在原本的部署流程中添加了一个创建环境变量文件的步骤,将环境变量写入 .env 文件中,然后再进行构建,这样就达到了和本地打包构建一样的效果。 | ||
|
||
在构建完成后,构建环境会直接被清除,因此上述步骤中创建的文件也并不会被保存。 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters