You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 01:57:08