Commit 795acc7e5cb0789ab5aa00acd80f91c742bf7c44

Authored by Key
1 parent 37b78a15

feat: aliyun oss & avatar upload

lib/common/request/apis.dart
@@ -70,4 +70,7 @@ class Apis { @@ -70,4 +70,7 @@ class Apis {
70 /// 首页弹窗 70 /// 首页弹窗
71 /// get 71 /// get
72 static const String homePopup = 'home/popup'; 72 static const String homePopup = 'home/popup';
  73 +
  74 + /// 获取阿里云oss鉴权信息
  75 + static const String aliyunOssSts = 'oss/sts/upload';
73 } 76 }
lib/common/request/basic_config.dart
1 class BasicConfig { 1 class BasicConfig {
2 bool isTestDev = true; 2 bool isTestDev = true;
3 -}  
4 \ No newline at end of file 3 \ No newline at end of file
  4 +}
lib/generated/json/aliyun_oss_upload_sts_entity.g.dart 0 → 100644
  1 +import 'package:wow_english/generated/json/base/json_convert_content.dart';
  2 +import 'package:wow_english/models/aliyun_oss_upload_sts_entity.dart';
  3 +
  4 +AliyunOssUploadStsEntity $AliyunOssUploadStsEntityFromJson(Map<String, dynamic> json) {
  5 + final AliyunOssUploadStsEntity aliyunOssUploadStsEntity = AliyunOssUploadStsEntity();
  6 + final String? securityToken = jsonConvert.convert<String>(json['securityToken']);
  7 + if (securityToken != null) {
  8 + aliyunOssUploadStsEntity.securityToken = securityToken;
  9 + }
  10 + final String? expiration = jsonConvert.convert<String>(json['expiration']);
  11 + if (expiration != null) {
  12 + aliyunOssUploadStsEntity.expiration = expiration;
  13 + }
  14 + final String? endpoint = jsonConvert.convert<String>(json['endpoint']);
  15 + if (endpoint != null) {
  16 + aliyunOssUploadStsEntity.endpoint = endpoint;
  17 + }
  18 + final String? fileKey = jsonConvert.convert<String>(json['fileKey']);
  19 + if (fileKey != null) {
  20 + aliyunOssUploadStsEntity.fileKey = fileKey;
  21 + }
  22 + final String? accessKeyId = jsonConvert.convert<String>(json['accessKeyId']);
  23 + if (accessKeyId != null) {
  24 + aliyunOssUploadStsEntity.accessKeyId = accessKeyId;
  25 + }
  26 + final String? accessKeySecret = jsonConvert.convert<String>(json['accessKeySecret']);
  27 + if (accessKeySecret != null) {
  28 + aliyunOssUploadStsEntity.accessKeySecret = accessKeySecret;
  29 + }
  30 + final String? bucket = jsonConvert.convert<String>(json['bucket']);
  31 + if (bucket != null) {
  32 + aliyunOssUploadStsEntity.bucket = bucket;
  33 + }
  34 + final String? ossDomain = jsonConvert.convert<String>(json['ossDomain']);
  35 + if (ossDomain != null) {
  36 + aliyunOssUploadStsEntity.ossDomain = ossDomain;
  37 + }
  38 + final String? host = jsonConvert.convert<String>(json['host']);
  39 + if (host != null) {
  40 + aliyunOssUploadStsEntity.host = host;
  41 + }
  42 + final AliyunOssUploadStsCallbackParam? callbackParam = jsonConvert.convert<AliyunOssUploadStsCallbackParam>(json['callbackParam']);
  43 + if (callbackParam != null) {
  44 + aliyunOssUploadStsEntity.callbackParam = callbackParam;
  45 + }
  46 + return aliyunOssUploadStsEntity;
  47 +}
  48 +
  49 +Map<String, dynamic> $AliyunOssUploadStsEntityToJson(AliyunOssUploadStsEntity entity) {
  50 + final Map<String, dynamic> data = <String, dynamic>{};
  51 + data['securityToken'] = entity.securityToken;
  52 + data['expiration'] = entity.expiration;
  53 + data['endpoint'] = entity.endpoint;
  54 + data['fileKey'] = entity.fileKey;
  55 + data['accessKeyId'] = entity.accessKeyId;
  56 + data['accessKeySecret'] = entity.accessKeySecret;
  57 + data['bucket'] = entity.bucket;
  58 + data['ossDomain'] = entity.ossDomain;
  59 + data['host'] = entity.host;
  60 + data['callbackParam'] = entity.callbackParam.toJson();
  61 + return data;
  62 +}
  63 +
  64 +AliyunOssUploadStsCallbackParam $AliyunOssUploadStsCallbackParamFromJson(Map<String, dynamic> json) {
  65 + final AliyunOssUploadStsCallbackParam aliyunOssUploadStsCallbackParam = AliyunOssUploadStsCallbackParam();
  66 + final String? callbackBody = jsonConvert.convert<String>(json['callbackBody']);
  67 + if (callbackBody != null) {
  68 + aliyunOssUploadStsCallbackParam.callbackBody = callbackBody;
  69 + }
  70 + final String? callbackBodyType = jsonConvert.convert<String>(json['callbackBodyType']);
  71 + if (callbackBodyType != null) {
  72 + aliyunOssUploadStsCallbackParam.callbackBodyType = callbackBodyType;
  73 + }
  74 + final String? callbackUrl = jsonConvert.convert<String>(json['callbackUrl']);
  75 + if (callbackUrl != null) {
  76 + aliyunOssUploadStsCallbackParam.callbackUrl = callbackUrl;
  77 + }
  78 + return aliyunOssUploadStsCallbackParam;
  79 +}
  80 +
  81 +Map<String, dynamic> $AliyunOssUploadStsCallbackParamToJson(AliyunOssUploadStsCallbackParam entity) {
  82 + final Map<String, dynamic> data = <String, dynamic>{};
  83 + data['callbackBody'] = entity.callbackBody;
  84 + data['callbackBodyType'] = entity.callbackBodyType;
  85 + data['callbackUrl'] = entity.callbackUrl;
  86 + return data;
  87 +}
lib/generated/json/base/json_convert_content.dart
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 4
5 // This file is automatically generated. DO NOT EDIT, all your changes would be lost. 5 // This file is automatically generated. DO NOT EDIT, all your changes would be lost.
6 import 'package:flutter/material.dart' show debugPrint; 6 import 'package:flutter/material.dart' show debugPrint;
  7 +import 'package:wow_english/models/aliyun_oss_upload_sts_entity.dart';
7 import 'package:wow_english/models/course_entity.dart'; 8 import 'package:wow_english/models/course_entity.dart';
8 import 'package:wow_english/models/course_module_entity.dart'; 9 import 'package:wow_english/models/course_module_entity.dart';
9 import 'package:wow_english/models/course_process_entity.dart'; 10 import 'package:wow_english/models/course_process_entity.dart';
@@ -18,6 +19,8 @@ typedef EnumConvertFunction&lt;T&gt; = T Function(String value); @@ -18,6 +19,8 @@ typedef EnumConvertFunction&lt;T&gt; = T Function(String value);
18 19
19 class JsonConvert { 20 class JsonConvert {
20 static final Map<String, JsonConvertFunction> convertFuncMap = { 21 static final Map<String, JsonConvertFunction> convertFuncMap = {
  22 + (AliyunOssUploadStsEntity).toString(): AliyunOssUploadStsEntity.fromJson,
  23 + (AliyunOssUploadStsCallbackParam).toString(): AliyunOssUploadStsCallbackParam.fromJson,
21 (CourseEntity).toString(): CourseEntity.fromJson, 24 (CourseEntity).toString(): CourseEntity.fromJson,
22 (CourseCourseLessons).toString(): CourseCourseLessons.fromJson, 25 (CourseCourseLessons).toString(): CourseCourseLessons.fromJson,
23 (CourseModuleEntity).toString(): CourseModuleEntity.fromJson, 26 (CourseModuleEntity).toString(): CourseModuleEntity.fromJson,
@@ -108,6 +111,12 @@ List&lt;T&gt;? convertListNotNull&lt;T&gt;(dynamic value, {EnumConvertFunction? enumConvert} @@ -108,6 +111,12 @@ List&lt;T&gt;? convertListNotNull&lt;T&gt;(dynamic value, {EnumConvertFunction? enumConvert}
108 111
109 //list is returned by type 112 //list is returned by type
110 static M? _getListChildType<M>(List<Map<String, dynamic>> data) { 113 static M? _getListChildType<M>(List<Map<String, dynamic>> data) {
  114 + if(<AliyunOssUploadStsEntity>[] is M){
  115 + return data.map<AliyunOssUploadStsEntity>((Map<String, dynamic> e) => AliyunOssUploadStsEntity.fromJson(e)).toList() as M;
  116 + }
  117 + if(<AliyunOssUploadStsCallbackParam>[] is M){
  118 + return data.map<AliyunOssUploadStsCallbackParam>((Map<String, dynamic> e) => AliyunOssUploadStsCallbackParam.fromJson(e)).toList() as M;
  119 + }
111 if(<CourseEntity>[] is M){ 120 if(<CourseEntity>[] is M){
112 return data.map<CourseEntity>((Map<String, dynamic> e) => CourseEntity.fromJson(e)).toList() as M; 121 return data.map<CourseEntity>((Map<String, dynamic> e) => CourseEntity.fromJson(e)).toList() as M;
113 } 122 }
@@ -146,7 +155,7 @@ List&lt;T&gt;? convertListNotNull&lt;T&gt;(dynamic value, {EnumConvertFunction? enumConvert} @@ -146,7 +155,7 @@ List&lt;T&gt;? convertListNotNull&lt;T&gt;(dynamic value, {EnumConvertFunction? enumConvert}
146 } 155 }
147 156
148 debugPrint("${M.toString()} not found"); 157 debugPrint("${M.toString()} not found");
149 - 158 +
150 return null; 159 return null;
151 } 160 }
152 161
@@ -157,4 +166,4 @@ List&lt;T&gt;? convertListNotNull&lt;T&gt;(dynamic value, {EnumConvertFunction? enumConvert} @@ -157,4 +166,4 @@ List&lt;T&gt;? convertListNotNull&lt;T&gt;(dynamic value, {EnumConvertFunction? enumConvert}
157 return jsonConvert.convert<M>(json); 166 return jsonConvert.convert<M>(json);
158 } 167 }
159 } 168 }
160 -}  
161 \ No newline at end of file 169 \ No newline at end of file
  170 +}
lib/generated/json/course_module_entity.g.dart
@@ -59,7 +59,6 @@ CourseModuleEntity $CourseModuleEntityFromJson(Map&lt;String, dynamic&gt; json) { @@ -59,7 +59,6 @@ CourseModuleEntity $CourseModuleEntityFromJson(Map&lt;String, dynamic&gt; json) {
59 if (status != null) { 59 if (status != null) {
60 courseModuleEntity.status = status; 60 courseModuleEntity.status = status;
61 } 61 }
62 -  
63 final String? courseModuleThemeColor = jsonConvert.convert<String>(json['courseModuleThemeColor']); 62 final String? courseModuleThemeColor = jsonConvert.convert<String>(json['courseModuleThemeColor']);
64 if (courseModuleThemeColor != null) { 63 if (courseModuleThemeColor != null) {
65 courseModuleEntity.courseModuleThemeColor = courseModuleThemeColor; 64 courseModuleEntity.courseModuleThemeColor = courseModuleThemeColor;
@@ -85,4 +84,4 @@ Map&lt;String, dynamic&gt; $CourseModuleEntityToJson(CourseModuleEntity entity) { @@ -85,4 +84,4 @@ Map&lt;String, dynamic&gt; $CourseModuleEntityToJson(CourseModuleEntity entity) {
85 data['status'] = entity.status; 84 data['status'] = entity.status;
86 data['courseModuleThemeColor'] = entity.courseModuleThemeColor; 85 data['courseModuleThemeColor'] = entity.courseModuleThemeColor;
87 return data; 86 return data;
88 -}  
89 \ No newline at end of file 87 \ No newline at end of file
  88 +}
lib/models/aliyun_oss_upload_sts_entity.dart 0 → 100644
  1 +import 'dart:convert';
  2 +
  3 +import 'package:wow_english/generated/json/aliyun_oss_upload_sts_entity.g.dart';
  4 +import 'package:wow_english/generated/json/base/json_field.dart';
  5 +
  6 +@JsonSerializable()
  7 +class AliyunOssUploadStsEntity {
  8 + late String securityToken;
  9 + late String expiration;
  10 + late String endpoint;
  11 + late String fileKey;
  12 + late String accessKeyId;
  13 + late String accessKeySecret;
  14 + late String bucket;
  15 + late String ossDomain;
  16 + late String host;
  17 + late AliyunOssUploadStsCallbackParam callbackParam;
  18 +
  19 + AliyunOssUploadStsEntity();
  20 +
  21 + factory AliyunOssUploadStsEntity.fromJson(Map<String, dynamic> json) => $AliyunOssUploadStsEntityFromJson(json);
  22 +
  23 + Map<String, dynamic> toJson() => $AliyunOssUploadStsEntityToJson(this);
  24 +
  25 + @override
  26 + String toString() {
  27 + return jsonEncode(this);
  28 + }
  29 +}
  30 +
  31 +@JsonSerializable()
  32 +class AliyunOssUploadStsCallbackParam {
  33 + late String callbackBody;
  34 + late String callbackBodyType;
  35 + late String callbackUrl;
  36 +
  37 + AliyunOssUploadStsCallbackParam();
  38 +
  39 + factory AliyunOssUploadStsCallbackParam.fromJson(Map<String, dynamic> json) => $AliyunOssUploadStsCallbackParamFromJson(json);
  40 +
  41 + Map<String, dynamic> toJson() => $AliyunOssUploadStsCallbackParamToJson(this);
  42 +
  43 + @override
  44 + String toString() {
  45 + return jsonEncode(this);
  46 + }
  47 +}
lib/pages/user/modify/user_avatar_bloc/user_avatar_bloc.dart
@@ -4,13 +4,15 @@ import &#39;package:image_picker/image_picker.dart&#39;; @@ -4,13 +4,15 @@ import &#39;package:image_picker/image_picker.dart&#39;;
4 import 'package:permission_handler/permission_handler.dart'; 4 import 'package:permission_handler/permission_handler.dart';
5 import 'package:wow_english/common/core/assets_const.dart'; 5 import 'package:wow_english/common/core/assets_const.dart';
6 import 'package:wow_english/common/core/user_util.dart'; 6 import 'package:wow_english/common/core/user_util.dart';
  7 +import 'package:wow_english/common/request/dao/user_dao.dart';
  8 +import 'package:wow_english/utils/aliyun_oss_util.dart';
  9 +import 'package:wow_english/utils/log_util.dart';
7 10
8 part 'user_avatar_event.dart'; 11 part 'user_avatar_event.dart';
9 part 'user_avatar_state.dart'; 12 part 'user_avatar_state.dart';
10 13
11 class UserAvatarBloc extends Bloc<UserAvatarEvent, UserAvatarState> { 14 class UserAvatarBloc extends Bloc<UserAvatarEvent, UserAvatarState> {
12 -  
13 - String _imageUrl = UserUtil.getUser()!.avatarUrl??AssetsConst.wowLogo; 15 + String _imageUrl = UserUtil.getUser()!.avatarUrl ?? AssetsConst.wowLogo;
14 16
15 String get imageUrl => _imageUrl; 17 String get imageUrl => _imageUrl;
16 18
@@ -18,27 +20,34 @@ class UserAvatarBloc extends Bloc&lt;UserAvatarEvent, UserAvatarState&gt; { @@ -18,27 +20,34 @@ class UserAvatarBloc extends Bloc&lt;UserAvatarEvent, UserAvatarState&gt; {
18 20
19 XFile? get file => _file; 21 XFile? get file => _file;
20 22
21 - bool _canInsertApp =false; 23 + bool _canInsertApp = false;
22 24
23 bool get canInsertApp => _canInsertApp; 25 bool get canInsertApp => _canInsertApp;
24 26
25 final ImagePicker picker = ImagePicker(); 27 final ImagePicker picker = ImagePicker();
26 28
27 -  
28 UserAvatarBloc() : super(UserAvatarInitial()) { 29 UserAvatarBloc() : super(UserAvatarInitial()) {
29 on<ChangeImageEvent>(_changeImage); 30 on<ChangeImageEvent>(_changeImage);
30 on<GetImageFromPhotoEvent>(_getImageFromPhoto); 31 on<GetImageFromPhotoEvent>(_getImageFromPhoto);
31 on<GetImageFromCameraEvent>(_getImageFromCamera); 32 on<GetImageFromCameraEvent>(_getImageFromCamera);
32 } 33 }
33 34
34 - void _changeImage(ChangeImageEvent event,Emitter<UserAvatarState> emitter) async { 35 + void _changeImage(ChangeImageEvent event, Emitter<UserAvatarState> emitter) async {
35 _imageUrl = event.imagePath; 36 _imageUrl = event.imagePath;
36 - emitter(ChangeImageState()); 37 + try {
  38 + // todo 加个loading UI
  39 + String avatarUrl = await AliyunOssUtil.uploadFile(event.imagePath);
  40 + // 上传服务器
  41 + await UserDao.updateUserInfoField(avatarUrl: avatarUrl);
  42 + emitter(ChangeImageState());
  43 + } catch (e) {
  44 + Log.e('上传头像失败:$e');
  45 + }
37 } 46 }
38 47
39 - void _getImageFromPhoto(GetImageFromPhotoEvent event,Emitter<UserAvatarState> emitter) async { 48 + void _getImageFromPhoto(GetImageFromPhotoEvent event, Emitter<UserAvatarState> emitter) async {
40 await getPhotoPermissionStatus().then((value) async { 49 await getPhotoPermissionStatus().then((value) async {
41 - if (!value){ 50 + if (!value) {
42 debugPrint('失败$value'); 51 debugPrint('失败$value');
43 return; 52 return;
44 } 53 }
@@ -47,9 +56,9 @@ class UserAvatarBloc extends Bloc&lt;UserAvatarEvent, UserAvatarState&gt; { @@ -47,9 +56,9 @@ class UserAvatarBloc extends Bloc&lt;UserAvatarEvent, UserAvatarState&gt; {
47 }); 56 });
48 } 57 }
49 58
50 - void _getImageFromCamera(GetImageFromCameraEvent event,Emitter<UserAvatarState> emitter) async { 59 + void _getImageFromCamera(GetImageFromCameraEvent event, Emitter<UserAvatarState> emitter) async {
51 await getCameraPermissionStatus().then((value) async { 60 await getCameraPermissionStatus().then((value) async {
52 - if (!value){ 61 + if (!value) {
53 debugPrint('失败$value'); 62 debugPrint('失败$value');
54 return; 63 return;
55 } 64 }
@@ -71,9 +80,7 @@ class UserAvatarBloc extends Bloc&lt;UserAvatarEvent, UserAvatarState&gt; { @@ -71,9 +80,7 @@ class UserAvatarBloc extends Bloc&lt;UserAvatarEvent, UserAvatarState&gt; {
71 openAppSettings(); 80 openAppSettings();
72 } else if (status.isRestricted) { 81 } else if (status.isRestricted) {
73 _requestPermission(permission); 82 _requestPermission(permission);
74 - } else {  
75 -  
76 - } 83 + } else {}
77 return false; 84 return false;
78 } 85 }
79 86
@@ -89,9 +96,7 @@ class UserAvatarBloc extends Bloc&lt;UserAvatarEvent, UserAvatarState&gt; { @@ -89,9 +96,7 @@ class UserAvatarBloc extends Bloc&lt;UserAvatarEvent, UserAvatarState&gt; {
89 openAppSettings(); 96 openAppSettings();
90 } else if (status.isRestricted) { 97 } else if (status.isRestricted) {
91 _requestPermission(permission); 98 _requestPermission(permission);
92 - } else {  
93 -  
94 - } 99 + } else {}
95 return false; 100 return false;
96 } 101 }
97 102
lib/utils/aliyun_oss_util.dart 0 → 100644
  1 +import 'package:dio/dio.dart';
  2 +import 'package:flutter_oss_aliyun/flutter_oss_aliyun.dart';
  3 +import 'package:wow_english/common/request/request_client.dart';
  4 +import 'package:wow_english/models/aliyun_oss_upload_sts_entity.dart';
  5 +import 'package:wow_english/utils/log_util.dart';
  6 +
  7 +/// 阿里云 oss 工具类,服务端给的鉴权一次有效
  8 +/// 这个库的Client是个单例,如果并发使用请注意所调用的Client归属,Client.init会生成一个新的_instance
  9 +class AliyunOssUtil {
  10 + static Future<String> uploadFile(String filePath) async {
  11 + // 取出文件名
  12 + String fileName = filePath.substring(filePath.lastIndexOf("/") + 1, filePath.length);
  13 + Log.d("待上传文件fileName: $fileName");
  14 + // 获取鉴权信息
  15 + AliyunOssUploadStsEntity stsEntity = await _getSts(fileName);
  16 + // 鉴权
  17 + Client.init(
  18 + ossEndpoint: stsEntity.endpoint,
  19 + bucketName: stsEntity.bucket,
  20 + authGetter: stsEntity.authGetter,
  21 + );
  22 +
  23 + // 上传文件
  24 + final Response<dynamic> resp = await Client().putObjectFile(
  25 + filePath,
  26 + fileKey: stsEntity.fileKey,
  27 + option: PutRequestOption(
  28 + onSendProgress: (count, total) {
  29 + Log.d("send: count = $count, and total = $total");
  30 + },
  31 + onReceiveProgress: (count, total) {
  32 + Log.d("receive: count = $count, and total = $total");
  33 + },
  34 + aclModel: AclMode.private,
  35 + callback: Callback(
  36 + callbackUrl: stsEntity.callbackParam.callbackUrl,
  37 + callbackBody: stsEntity.callbackParam.callbackBody,
  38 + calbackBodyType: CalbackBodyType.json,
  39 + ),
  40 + ),
  41 + );
  42 + return '${stsEntity.host}/${stsEntity.fileKey}';
  43 + }
  44 +
  45 + /// 获取鉴权信息
  46 + static Future<AliyunOssUploadStsEntity> _getSts(String fileName) async {
  47 + var result = await requestClient.get(Apis.aliyunOssSts, queryParameters: {'fileName': fileName});
  48 + return result;
  49 + }
  50 +}
  51 +
  52 +extension StsExtension on AliyunOssUploadStsEntity {
  53 + Auth authGetter() {
  54 + return Auth(
  55 + accessKey: accessKeyId,
  56 + accessSecret: accessKeySecret,
  57 + expire: expiration,
  58 + secureToken: securityToken,
  59 + );
  60 + }
  61 +}
lib/utils/log_util.dart
  1 +import 'package:wow_english/common/request/basic_config.dart';
  2 +
1 enum LogLevel { debug, info, warning, error } 3 enum LogLevel { debug, info, warning, error }
2 4
3 class Log { 5 class Log {
4 - static LogLevel level = LogLevel.debug; 6 + static LogLevel level = BasicConfig().isTestDev ? LogLevel.debug : LogLevel.error;
5 7
6 /// debug 8 /// debug
7 static void d(Object? object) { 9 static void d(Object? object) {
pubspec.yaml
@@ -97,6 +97,8 @@ dependencies: @@ -97,6 +97,8 @@ dependencies:
97 flutter_sound: ^9.2.13 97 flutter_sound: ^9.2.13
98 # 文件管理 https://pub.dev/packages/path_provider 98 # 文件管理 https://pub.dev/packages/path_provider
99 path_provider: ^2.0.15 99 path_provider: ^2.0.15
  100 + # 阿里云oss https://pub.dev/packages/flutter_oss_aliyun
  101 + flutter_oss_aliyun: ^6.2.7
100 102
101 dev_dependencies: 103 dev_dependencies:
102 build_runner: ^2.4.4 104 build_runner: ^2.4.4