1 JSON基础
对象、数组、值



JSON 解析实例

1. 什么是这里的 "JSON 数据"?
从服务器收到的字符串格式数据:
bash
{"name":"runoob", "alexa":"10000", "site":"www.runoob.com"}
它是键值对结构的文本,是前后端传递数据的常用格式。
2. 为什么要用JSON.parse()?
JavaScript 不能直接用这个字符串里的内容(比如拿name的值),所以需要用 JSON.parse()把它转成 JavaScript 对象:
javascript
var obj = JSON.parse('{"name":"runoob", "alexa":"10000", "site":"www.runoob.com"}');
执行后,obj就变成了一个 JS 对象,结构是:
javascript
{
name: "runoob",
alexa: "10000",
site: "www.runoob.com"
}
3. 注意点:JSON 格式必须 "标准"
如果 JSON 字符串写错(比如键没加双引号、逗号漏了),JSON.parse()会报错。比如下面的写法是错的(name 没加双引号):
bash
{name:"runoob"} // 错误,JSON的键必须用双引号
4. 解析后怎么用?
转成 JS 对象后,就可以通过对象。属性名获取对应的值,比如在网页中显示:
html
<!-- 页面上的容器 -->
<p id="demo"></p>
<script>
var obj = JSON.parse('{"name":"runoob", "alexa":"10000", "site":"www.runoob.com"}');
// 把name和site的值放到p标签里
document.getElementById("demo").innerHTML = obj.name + " " + obj.site;
</script>
最终页面会显示:runoob www.runoob.com
2 API 调用 400 错误修复
问题深度剖析
- 错误本质:第三方 API(和风天气)的接口契约明确要求查询参数为「城市 ID」,而非中文城市名。中文城市名存在两大核心问题:
- ① 多义性(如 "西安" 可能指西安市或下属的西安区,不同行政区域对应不同天气数据);
- ② 编码差异(中文参数在 URL 传输时需经过 UTF-8 编码,若前端编码不规范或后端解码逻辑不一致,会导致服务器无法识别参数),最终触发 HTTP 400 Bad Request(请求参数格式非法)。
- 错误重现 :当直接调用
https://devapi.qweather.com/v7/weather/now?location=北京&key=你的API密钥时,服务器因无法解析location=北京这个非标准参数,直接返回 400 错误,响应体通常包含 "参数错误:location 格式非法" 的提示。
解决方案的详细实现与逻辑
(1)创建城市 ID 映射表(constants.ts)
- 设计思路:城市 ID 是和风天气 API 定义的唯一标识(由数字组成,无歧义),从官方城市列表中获取准确 ID 后,通过映射表将用户易懂的城市名与机器可识别的城市 ID 绑定,既保证参数规范性,又不影响用户操作体验。
- 完整代码示例:
TypeScript
// constants.ts
/**
* 城市ID映射表(数据来源:和风天气官方城市列表API)
* 文档参考:https://dev.qweather.com/docs/api/geo/city-lookup/
* as const 关键字:将对象变为只读常量,TypeScript会精准推导每个键值对的类型(而非笼统的string)
*/
export const CITY_ID_MAP = {
北京: '101010100', // 北京市(直辖市,一级行政单位)
上海: '101020100', // 上海市(直辖市)
广州: '101280101', // 广州市(广东省省会)
深圳: '101280601', // 深圳市(广东省副省级市)
杭州: '101210101', // 杭州市(浙江省省会)
南京: '101190101', // 南京市(江苏省省会)
成都: '101270101', // 成都市(四川省省会)
重庆: '101040100' // 重庆市(直辖市)
} as const;
/**
* 支持的城市列表(从映射表中推导,避免手动重复维护)
* 类型:("北京" | "上海" | ... | "重庆")[],具备严格的类型约束
*/
export const SUPPORTED_CITIES = Object.keys(CITY_ID_MAP) as Array<keyof typeof CITY_ID_MAP>;
(2)更新 API 调用逻辑(App.tsx)
- 核心修改:将原来直接传递城市名的逻辑,改为先通过映射表获取城市 ID,再传递 ID 参数。
- 完整代码示例:
TypeScript
// App.tsx
import { useState } from 'react';
import { getCurrentWeather } from './api/weather';
import { CITY_ID_MAP, SUPPORTED_CITIES } from './constants';
const App = () => {
const [selectedCity, setSelectedCity] = useState<keyof typeof CITY_ID_MAP>('北京');
const [weatherData, setWeatherData] = useState(null);
const [error, setError] = useState('');
// 查询天气的核心函数
const fetchWeather = async () => {
try {
setError('');
// 1. 通过城市名获取对应的城市ID(从映射表中读取,确保参数合法)
const cityId = CITY_ID_MAP[selectedCity];
// 2. 调用API时,将location参数设为城市ID(符合和风天气API规范)
const data = await getCurrentWeather(cityId);
setWeatherData(data);
} catch (err: any) {
setError(err.message || '查询天气失败,请稍后重试');
}
};
return (
<div className="app">
<h1>天气查询</h1>
{/* 城市选择器:仅展示支持的城市,避免用户选择无效城市 */}
<select
value={selectedCity}
onChange={(e) => setSelectedCity(e.target.value as keyof typeof CITY_ID_MAP)}
>
{SUPPORTED_CITIES.map((city) => (
<option key={city} value={city}>
{city}
</option>
))}
</select>
<button onClick={fetchWeather}>查询天气</button>
{/* 错误提示与天气展示 */}
{error && <div className="error">{error}</div>}
{weatherData && (
<div className="weather-card">
<h3>{selectedCity} 实时天气</h3>
<p>温度:{weatherData.now.temp}℃</p>
<p>天气状况:{weatherData.now.text}</p>
<p>更新时间:{weatherData.now.obsTime}</p>
</div>
)}
</div>
);
};
export default App;
(3)添加城市有效性验证(防御性编程)
- 设计思路:即使前端 UI 仅提供支持的城市选择,仍需防止通过手动修改 DOM、接口直接调用等方式传入无效城市名,因此在工具函数中添加验证逻辑。
- 工具函数示例:
TypeScript
// utils/cityUtils.ts
import { CITY_ID_MAP, SUPPORTED_CITIES } from '../constants';
/**
* 验证城市是否支持,并返回对应的城市ID
* @param cityName - 用户输入的城市名
* @returns 合法的城市ID
* @throws 不支持的城市会抛出错误
*/
export const validateAndGetCityId = (cityName: string) => {
// 严格校验:城市名必须在支持的列表中
if (!SUPPORTED_CITIES.includes(cityName as keyof typeof CITY_ID_MAP)) {
throw new Error(`暂不支持查询"${cityName}"的天气,请选择以下支持的城市:${SUPPORTED_CITIES.join('、')}`);
}
// 类型断言:确保返回的城市ID是映射表中定义的准确值
return CITY_ID_MAP[cityName as keyof typeof CITY_ID_MAP];
};
// 在API调用中使用
import { validateAndGetCityId } from '../utils/cityUtils';
export const getCurrentWeather = async (cityName: string) => {
const cityId = validateAndGetCityId(cityName);
const response = await axios.get('/v7/weather/now', {
params: { location: cityId, key: import.meta.env.VITE_QWEATHER_KEY }
});
return response.data;
};
相关官方文档
- 和风天气 API「实时天气接口」参数说明:https://dev.qweather.com/docs/api/weather/now/
- 和风天气 API「城市列表接口」(获取城市 ID):https://dev.qweather.com/docs/api/geo/city-lookup/
- TypeScript
as const关键字用法:https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions
3 JWT 认证问题(前端身份认证核心)
问题深度剖析
- 错误本质 :JWT(JSON Web Token)生成时必须使用有效的「私钥(Secret/Key)」进行签名,若私钥配置缺失、为空或格式错误,会导致
jsonwebtoken库抛出 "invalid signature"(无效签名)错误,无法生成合法令牌;进而导致 API 请求的Authorization头无效,服务器返回 401 Unauthorized(未授权)。 - 常见场景 :开发环境中可能因
.env文件配置遗漏(如VITE_JWT_SECRET未定义),或私钥字符串包含特殊字符未转义,导致认证失败。
解决方案的详细实现与逻辑
(1)添加备用私钥与环境变量兼容(jwtUtils.ts)
- 设计思路:优先从环境变量读取私钥(符合生产环境配置规范),若环境变量未配置,则使用备用私钥(保证开发环境功能可用),同时设置令牌过期时间(避免永久有效导致安全风险)。
- 完整代码示例:
TypeScript
// utils/jwtUtils.ts
import jwt from 'jsonwebtoken';
/**
* JWT配置:优先读取环境变量, fallback 到备用私钥
* 生产环境必须在.env文件中配置 VITE_JWT_SECRET(长度建议≥16位,包含大小写、数字、特殊字符)
*/
const JWT_SECRET = import.meta.env.VITE_JWT_SECRET || 'weather-app-backup-secret-2024_@#$';
const JWT_EXPIRES_IN = '1h'; // 令牌有效期1小时(生产环境可根据需求调整)
/**
* 生成JWT令牌
* @param payload - 存储在令牌中的非敏感数据(如用户ID、角色)
* @returns 签名后的JWT令牌字符串
*/
export const generateJwtToken = (payload: object) => {
try {
// 签名并设置过期时间:jwt.sign(数据, 私钥, 配置选项)
return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
} catch (error: any) {
console.error('JWT令牌生成失败:', error.message);
throw new Error('认证令牌生成失败,请检查配置');
}
};
/**
* 验证JWT令牌有效性(前端可选,主要用于后端验证;前端可用于解析令牌数据)
* @param token - 待验证的JWT令牌
* @returns 解析后的 payload 数据(若令牌有效)
*/
export const verifyJwtToken = (token: string) => {
try {
return jwt.verify(token, JWT_SECRET);
} catch (error: any) {
if (error.name === 'TokenExpiredError') {
throw new Error('认证令牌已过期,请重新获取');
}
throw new Error('无效的认证令牌');
}
};
(2)在 Axios 请求拦截器中添加认证头
- 核心逻辑 :每次 API 请求前,自动生成 JWT 令牌并添加到
Authorization头,符合 Bearer Token 认证规范。 - 代码示例:
TypeScript
// api/request.ts(Axios实例配置)
import axios from 'axios';
import { generateJwtToken } from '../utils/jwtUtils';
// 创建Axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 5000
});
// 请求拦截器:添加JWT认证头
service.interceptors.request.use(
(config) => {
// 生成令牌(实际项目中可缓存令牌,避免每次请求都重新生成)
const token = generateJwtToken({ app: 'weather-app', timestamp: Date.now() });
// 设置Bearer Token:格式必须为 "Bearer {token}"(空格不可省略)
config.headers.Authorization = `Bearer ${token}`;
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器:统一处理认证错误
service.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response?.status === 401) {
// 401错误:令牌无效/过期,可触发重新登录逻辑(此处简化处理)
alert('认证失败,请刷新页面重试');
}
return Promise.reject(error);
}
);
export default service;
(3)移除调试日志(代码规范优化)
- 优化原因 :开发阶段的
console.log(token)、console.log(secret)等日志,可能在生产环境泄露敏感信息(如私钥、令牌),且增加代码冗余;通过eslint配置可强制禁止生产环境输出调试日志。 - 辅助配置(.eslintrc.js):
TypeScript
module.exports = {
rules: {
// 生产环境禁止console.log(开发环境可放行)
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn'
}
};
相关官方文档
- JWT 官方文档(核心原理与结构):https://jwt.io/introduction/
jsonwebtoken库官方文档(Node.js/JWT 实现):https://github.com/auth0/node-jsonwebtoken- Axios 拦截器官方文档:https://axios-http.com/docs/interceptors
- Vite 环境变量配置:https://vitejs.dev/guide/env-and-mode.html
