Flutter项目中Last.fm API认证与调用异常排查求助
解决Last.fm API调用与认证问题的关键修复点
1. 修复语法错误
代码中secret变量的字符串未闭合,会直接导致编译失败:
// 错误写法 final String secret = 'API_SECRET // 正确写法 final String secret = 'API_SECRET';
2. 修正签名生成顺序
Last.fm签名规则要求先拼接排序后的参数键值对,再追加Shared Secret,原代码顺序颠倒会导致签名无效:
// 错误写法 final signature = utf8.encode(secret + paramStrings); // 正确写法 final signature = utf8.encode(paramStrings + secret);
3. 解决明文HTTP被拦截问题
默认情况下Android/iOS会阻止明文HTTP请求,改用HTTPS地址:
final String baseUrl = 'https://ws.audioscrobbler.com/2.0/';
若必须使用HTTP,需在AndroidManifest.xml中添加android:usesCleartextTraffic="true",iOS则在Info.plist中配置ATS例外。
4. 完善认证请求格式
auth.getMobileSession请求需指定format: 'json',否则默认返回XML会导致jsonDecode报错;同时增加错误状态判断:
Future<void> authenticate(String username, String password) async { final timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).round(); final params = { 'api_key': apiKey, 'method': 'auth.getMobileSession', 'password': password, 'username': username, 'timestamp': timestamp.toString(), 'format': 'json', // 强制返回JSON格式 }; params['api_sig'] = _generateSignature(params); final response = await http.post(Uri.parse(baseUrl), body: params); if (response.statusCode == 200) { final jsonBody = jsonDecode(response.body); if (jsonBody['status'] == 'failed') { throw Exception('${jsonBody['error']}: ${jsonBody['message']}'); } final session = jsonBody['session']; sessionKey = session?['key']; if (sessionKey == null) throw Exception('Failed to get session key'); } else { throw Exception('Authentication failed: HTTP ${response.statusCode}'); } }
5. 修复track.getsimilar参数缺失问题
该接口必须传入有效artist参数,空字符串会触发错误码6。同时调整返回类型以支持页面展示完整曲目信息:
// 修改返回类型为完整曲目对象列表 Future<List<Map<String, dynamic>>> getSimilarTracks(String track, String artist) async { if (sessionKey == null) throw Exception('Not authenticated'); final timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).round(); final params = { 'api_key': apiKey, 'format': 'json', 'limit': '10', 'sk': sessionKey!, 'method': 'track.getsimilar', 'timestamp': timestamp.toString(), 'track': track, 'artist': artist, 'autocorrect': '1', }; params['api_sig'] = _generateSignature(params); // 使用Uri内置方法拼接参数,避免手动拼接错误 final uri = Uri.parse(baseUrl).replace(queryParameters: params); final response = await http.get(uri); if (response.statusCode == 200) { final jsonBody = jsonDecode(response.body); if (jsonBody.containsKey('error')) { throw Exception('${jsonBody['error']}: ${jsonBody['message']}'); } final tracks = jsonBody['similartracks']['track'] as List<dynamic>; return tracks.cast<Map<String, dynamic>>(); } else { throw Exception('Failed to get tracks: HTTP ${response.statusCode}'); } }
6. 修正页面控制器管理
HomePage使用StatelessWidget管理控制器存在内存泄漏风险,改为StatefulWidget并添加艺术家输入框:
class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { final TextEditingController _trackController = TextEditingController(); final TextEditingController _artistController = TextEditingController(); final TextEditingController _usernameController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); final LastfmApi _lastfmApi = LastfmApi(); @override void dispose() { // 销毁控制器避免内存泄漏 _trackController.dispose(); _artistController.dispose(); _usernameController.dispose(); _passwordController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Last.fm Music Recommendations')), body: Padding( padding: EdgeInsets.all(16), child: Column( children: [ TextField(controller: _trackController, decoration: InputDecoration(labelText: 'Track Name')), TextField(controller: _artistController, decoration: InputDecoration(labelText: 'Artist Name')), TextField(controller: _usernameController, decoration: InputDecoration(labelText: 'Last.fm Username')), TextField(controller: _passwordController, obscureText: true, decoration: InputDecoration(labelText: 'Last.fm Password')), SizedBox(height: 16), ElevatedButton( onPressed: () async { try { await _lastfmApi.authenticate(_usernameController.text, _passwordController.text); final recommendations = await _lastfmApi.getSimilarTracks(_trackController.text, _artistController.text); Navigator.push(context, MaterialPageRoute(builder: (context) => RecommendationPage(track: _trackController.text, recommendations: recommendations))); } catch (e) { print('Error: $e'); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error: $e'))); } }, child: Text('Get Recommendations'), ), ], ), ), ); } }
7. 修正推荐页面类型匹配
调整RecommendationPage参数类型,处理图片为空的边界情况:
class RecommendationPage extends StatelessWidget { final String track; final List<Map<String, dynamic>> recommendations; const RecommendationPage({super.key, required this.track, required this.recommendations}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Recommendations for $track')), body: ListView.builder( itemCount: recommendations.length, itemBuilder: (context, index) { final rec = recommendations[index]; final imageUrl = rec['image'].isNotEmpty ? rec['image'][2]['#text'] : 'https://via.placeholder.com/64'; return ListTile( leading: Image.network(imageUrl), title: Text(rec['name']), subtitle: Text(rec['artist']['name']), ); }, ), ); } }
内容的提问来源于stack exchange,提问作者jfortini81
相关产品推荐
相关产品推荐

