GithubHelp home page GithubHelp logo

flutter_oss_aliyun's Introduction

Language: 中文简体 | English

flutter_oss_aliyun

一个访问阿里云oss并且支持STS临时访问凭证访问OSS的flutter库,基本上涵盖阿里云oss sdk的所有功能。⭐

flutter pub: https://pub.dev/packages/flutter_oss_aliyun

oss sts document: https://help.aliyun.com/document_detail/100624.html

🐱  初始化Client

添加依赖

dependencies:
  flutter_oss_aliyun: ^6.4.2

初始化oss client, 这里我们提供两种方式

1. 提供sts server地址,需要后端添加这个api

Client.init(
    stsUrl: "server url get sts token",
    ossEndpoint: "oss-cn-beijing.aliyuncs.com",
    bucketName: "bucket name",
);

后端api至少需要返回以下数据:

{
  "AccessKeyId": "AccessKeyId",
  "AccessKeySecret": "AccessKeySecret",
  "SecurityToken": "SecurityToken",
  "Expiration": "2022-03-22T11:33:06Z"
}

2. 自定义authGetter得到Auth

Client.init(
    ossEndpoint: "oss-cn-beijing.aliyuncs.com",
    bucketName: "bucketName",
    authGetter: _authGetter
);

Auth _authGetter() {
  return Auth(
      accessKey: "accessKey",
      accessSecret: 'accessSecret',
      expire: '2023-02-23T14:02:46Z',
      secureToken: 'token',
  );
}

Integrate with get_it

injectable: https://pub.dev/packages/injectable

@module
abstract class OssProvider {
  @singleton
  Client client() {
    return Client.init(
      stsUrl: Env.stsUrl,
      ossEndpoint: Env.endpointUrl,
      bucketName: Env.bucketName,
    );
  }
}

你可以传入自定义的Dio

在init函数中,你可以传入dio,做到dio的定制化。比如日志或者其他的interceptors.

Client.init(
    stsUrl: "server url get sts token",
    ossEndpoint: "oss-cn-beijing.aliyuncs.com",
    bucketName: "bucket name",
    dio: Dio(BaseOptions(connectTimeout: 9000)),
);

🎨 使用

文件上传

关于callback的使用: https://help.aliyun.com/document_detail/31989.htm?spm=a2c4g.11186623.0.0.73a830ffn45LMY#reference-zkm-311-hgb

final bytes = "file bytes".codeUnits;

await Client().putObject(
  bytes,
  "test.txt",
  option: PutRequestOption(
    onSendProgress: (count, total) {
      print("send: count = $count, and total = $total");
    },
    onReceiveProgress: (count, total) {
      print("receive: count = $count, and total = $total");
    },
    override: false,
    aclModel: AclMode.publicRead,
    storageType: StorageType.ia,
    headers: {"cache-control": "no-cache"},
    callback: Callback(
      callbackUrl: "callback url",
      callbackBody: "{\"mimeType\":\${mimeType}, \"filepath\":\${object},\"size\":\${size},\"bucket\":\${bucket},\"phone\":\${x:phone}}",
      callbackVar: {"x:phone": "android"},
      calbackBodyType: CalbackBodyType.json,
    ),       
  ),
);

PutRequestOption 字段说明,字段皆为非必需

Filed Default value Description
override true true: 允许覆盖同名Object
false: 禁止覆盖同名Object
aclModel inherited 1. publicWrite: 任何人(包括匿名访问者)都可以对该Object进行读写操作
2. publicRead: 只有该Object的拥有者可以对该Object进行写操作,任何人(包括匿名访问者)都可以对该Object进行读操作
3. private: 只有Object的拥有者可以对该Object进行读写操作,其他人无法访问该Object
4. inherited: 该Object遵循Bucket的读写权限,即Bucket是什么权限,Object就是什么权限
参考文档: https://help.aliyun.com/document_detail/100676.htm?spm=a2c4g.11186623.0.0.56637952SnxOWV#concept-blw-yqm-2gb
storageType Standard 参考文档: https://help.aliyun.com/document_detail/51374.htm?spm=a2c4g.11186623.0.0.56632b55htpEQX#concept-fcn-3xt-tdb

追加文件上传

final Response<dynamic> resp = await Client().appendObject(
  Uint8List.fromList(utf8.encode("Hello World")),
  "test_append.txt",
);

final Response<dynamic> resp2 = await Client().appendObject(
  position: int.parse(resp.headers["x-oss-next-append-position"]?[0]),
  Uint8List.fromList(utf8.encode(", Fluter.")),
  "test_append.txt",
);

跨bucket复制文件

final Response<dynamic> resp = await Client().copyObject(
  const CopyRequestOption(
    sourceFileKey: 'test.csv',
    targetFileKey: "test_copy.csv",
    targetBucketName: "bucket_2"
  ),
);

取消文件上传

final CancelToken cancelToken = CancelToken();
final bytes = ("long long bytes" * 1000).codeUnits;

Client().putObject(
  Uint8List.fromList(utf8.encode(string)),
  "cancel_token_test.txt",
  cancelToken: cancelToken,
  option: PutRequestOption(
    onSendProgress: (count, total) {
      if (kDebugMode) {
        print("send: count = $count, and total = $total");
      }
      if (count > 56) {
        cancelToken.cancel("cancel the uploading.");
      }
    },
  ),
).then((response) {
  // success
  print("upload success = ${response.statusCode}");
}).catchError((err) {
  if (CancelToken.isCancel(err)) {
    print("error message = ${err.message}");
  } else {
    // handle other errors
  }
});

批量文件上传

await Client().putObjects([
  AssetEntity(
    filename: "filename1.txt",
    bytes: "files1".codeUnits,
    option: PutRequestOption(
      onSendProgress: (count, total) {
        print("send: count = $count, and total = $total");
      },
      onReceiveProgress: (count, total) {
        print("receive: count = $count, and total = $total");
      },
      aclModel: AclMode.private,
    ),
  ),
  AssetEntity(filename: "filename2.txt", bytes: "files2".codeUnits),
]);

本地文件上传

final Response<dynamic> resp = await Client().putObjectFile(
  "/Users/aaa.pdf",
  fileKey: "aaa.png",
  option: PutRequestOption(
    onSendProgress: (count, total) {
      print("send: count = $count, and total = $total");
    },
    onReceiveProgress: (count, total) {
      print("receive: count = $count, and total = $total");
    },
    aclModel: AclMode.private,
    callback: Callback(
      callbackUrl: callbackUrl,
      callbackBody:
          "{\"mimeType\":\${mimeType}, \"filepath\":\${object},\"size\":\${size},\"bucket\":\${bucket},\"phone\":\${x:phone}}",
      callbackVar: {"x:phone": "android"},
      calbackBodyType: CalbackBodyType.json,
    ),    
  ),
);

批量本地文件上传

final List<Response<dynamic>> resp = await Client().putObjectFiles(
  [
    AssetFileEntity(
      filepath: "//Users/private.txt",
      option: PutRequestOption(
        onSendProgress: (count, total) {
          print("send: count = $count, and total = $total");
        },
        onReceiveProgress: (count, total) {
          print("receive: count = $count, and total = $total");
        },
        override: false,
        aclModel: AclMode.private,
      ),
    ),
    AssetFileEntity(
      filepath: "//Users/splash.png",
      filename: "aaa.png",
      option: PutRequestOption(
        onSendProgress: (count, total) {
          print("send: count = $count, and total = $total");
        },
        onReceiveProgress: (count, total) {
          print("receive: count = $count, and total = $total");
        },
        override: true,
      ),
    ),
  ],
);

文件下载

await Client().getObject(
  "test.txt",
  onReceiveProgress: (count, total) {
    debugPrint("received = $count, total = $total");
  },
);

查询文件是否存在

final bool isExisted = await Client().doesObjectExist(
    "aaa.jpg",
);

文件下载并保存

await Client().downloadObject(
  "test.txt",
  "./example/test.txt",
  onReceiveProgress: (count, total) {
    debugPrint("received = $count, total = $total");
  },
);

文件删除

await Client().deleteObject("test.txt");

批量文件删除

await Client().deleteObjects(["filename1.txt", "filename2.txt"]);

获取已签名的文件url

需要注意的是: 这个操作并不安全,因为url包含security-token信息,即使过期时间比较短. 这个url可以直接在浏览器访问

final String url = await Client().getSignedUrl(
  "test.jpg",
  params: {"x-oss-process": "image/resize,w_10/quality,q_90"},
);

获取多个已签名的文件url

需要注意的是: 这个操作并不安全,因为url包含security-token信息,即使过期时间比较短

final Map<String, String> result = await Client().getSignedUrls(["test.txt", "filename1.txt"]);

列举所有的存储空间

列举请求者拥有的所有存储空间(Bucket)。您还可以通过设置prefix、marker或者max-keys参数列举满足指定条件的存储空间。参考: https://help.aliyun.com/document_detail/31957.html

final Response<dynamic> resp = await Client().listBuckets({"max-keys": 2});

列举存储空间中所有文件

接口用于列举存储空间(Bucket)中所有文件(Object)的信息。请求参数和返回结果,请参考: https://help.aliyun.com/document_detail/187544.html

final Response<dynamic> resp = await Client().listFiles({});

获取bucket信息

查看存储空间(Bucket)的相关信息。返回结果请参考: https://help.aliyun.com/document_detail/31968.html

final Response<dynamic> resp = await Client().getBucketInfo();

获取bucket的储容量以及文件数量

获取指定存储空间(Bucket)的存储容量以及文件(Object)数量。返回结果请参考: https://help.aliyun.com/document_detail/426056.html

final Response<dynamic> resp = await Client().getBucketStat();

获取文件元信息

final Response<dynamic> resp = await Client().getObjectMeta("huhx.csv");

regions的查询

  • 查询所有
final Response<dynamic> resp = await Client().getAllRegions();
  • 查询特定
final Response<dynamic> resp = await Client().getRegion("oss-ap-northeast-1");

bucket acl的操作

  • 查询
final Response<dynamic> resp = await Client().getBucketAcl(
  bucketName: "bucket-name",
);
  • 更新
final Response<dynamic> resp = await Client().putBucketAcl(
  AciMode.publicRead, 
  bucketName: "bucket-name",
);

bucket policy的操作

  • 查询
final Response<dynamic> resp = await Client().getBucketPolicy(
  bucketName: "bucket-name",
);
  • 更新
final Response<dynamic> resp = await Client().putBucketPolicy(
  {}, 
  bucketName: "bucket-name",
);
  • 删除
final Response<dynamic> resp = await Client().deleteBucketPolicy(
  bucketName: "bucket-name",
);

Drop a ⭐ if it is help to you

flutter_oss_aliyun's People

Contributors

gohuhx avatar huhx avatar luo3house avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

flutter_oss_aliyun's Issues

可以拿到返回结果吗

我想要拿到上传完毕的返回结果 发现未null

难后在 putObjectFile 里面使用 callback 发现好像并没有生效 都没有跑进去

putObjectFile(
file.path,
fileKey: fileName,
option: PutRequestOption(
onSendProgress: (count, total) {
print("send: count = $count, and total = $total");
},
onReceiveProgress: (count, total) {
print("receive: count = $count, and total = $total");
},
aclModel: AclMode.inherited,
callback: Callback(
callbackUrl: "url",
callbackBody: "{"mimeType":${mimeType}, "filepath":${object},"size":${size},"bucket":${bucket},"phone":${x:phone}}",
callbackVar: {"x:phone": "android"},
calbackBodyType: CalbackBodyType.json,
),
),
)

Client.init()方法定义修改建议

Client.ini()方法
String Function()? tokenGetter
这个入参应该定义成返回Future吧?
Future<String> Function()? tokenGetter
要不然当tokenGetter声明成异步方法,加上async之后,
要把返回值定义成Future就不符合入参要求了,
然后自定义的tokenGetter内部就被限制使用await了

Improve code quality

  • Better request and response result
  • Better global configuration
  • Better error handling
  • Better package structure

Ossutil 可以上传, 使用这个就报错啊

DioException [connection error]: The connection errored: Failed host lookup: 'kxx-test.http' This indicates an error which most likely cannot be solved by the library.
I/flutter ( 2514): Error: SocketException: Failed host lookup: 'kxx-test.http' (OS Error: No address associated with hostname, errno = 7)
I/flutter ( 2514):
E/flutter ( 2514): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: DioException [connection error]: The connection errored: Failed host lookup: 'kxx-test.http' This indicates an error which most likely cannot be solved by the library.
E/flutter ( 2514): Error: SocketException: Failed host lookup: 'kxx-test.http' (OS Error: No address associated with hostname, errno = 7)

上传进度回调,只回调了一次。

1111
我使用 await Client().putObject上传图片时,希望拿到图片上传进度的回调。
但是onSendProgress回调实际上只回调了一次,
这样的话,我没法在页面上展示上传进度了,类似于从1%-100%。
希望大佬可以修复该问题。

Flutter pub中文链接问题

当遇到以下情况时,flutter pub网站不能work:

- [上传文件附带进度回调](#上传文件附带进度回调)

### 上传文件附带进度回调

分析原因在于对于中文或者特殊符号,flutter pub会忽略.

比如: ### abc上传文件附带进度回调aa
锚点变成:abcaa

文件样式(Style)功能添加

我在client.dart=>getSignedUrl方法里面加了一个"x-oss-process": process,参数, 但是浏览器打开链接403, 发现这个要在Signature参数里面添加签名后才有效.

目前尝试了在auth.dart->getSignature=>stringToSign第二个参数以及"${_getResourceString(bucket, key)}?security-token=$secureToken&x-oss-process=$process"都没有效果.

请问能添加一下这个功能吗? 谢谢 文档地址

[optimize]根据acl决定是否需要auth

1、如果是private的,那么read, write都需要签名
2、如果是publicRead,那么read不需要签名
3、如果是public, 那么write和read都不需要签名

getObject Method print out a bunch of garbled code

1.invoking

static getDownloadUrl() async{
Response url = await Client().getObject(
"DEV/cbl/image/order/13/77/2022113177231KcscQoyUqfSR.png",
bucketName: bucket,
onReceiveProgress: (count, total) {
print("received = $count, total = $total");
},
);
print("--------getObject-------->$url");
}

2.print result

2022-11-15 11:16:55.879 16483-16803/cn.cpl I/flutter: ��I-�S�$���C�ve��fC"c7��Q�j�T&�;�!|�ep�Z��@N�\�+�-�X*����D�Qc���#��j y�*� '*���K,�k�n'?1ĀƧ�_-q��f��v����N[��� D��f�7��Y�H@�a������yN�jс&��K+:�&�J�)"Jd�!��R�(6G 2022-11-15 11:16:55.879 16483-16803/cn.c I/flutter: �%C*��Kf5x���,r��[�h�da$��+����W8��e,������3��y�Rc�UR5�!<���7������Pl �UYp��s+��/9.� 2022-11-15 11:16:55.880 16483-16803/cn.c I/flutter: p�[�o�� �\�.��<�����O�湦+���}o��������m��a��է����qw����������p���S�/�^&���+������������u�c0Y�빆/���}g�O���V��G��Z�9���
2022-11-15 11:16:55.880 16483-16803/cn. I/flutter:
2022-11-15 11:16:56.022 16483-16803/cn. I/flutter: --------getObject-------->�����@exif

403报错

image
image

上传w文件正常,下载文件报403是什么原因啊
111

这个能改改吗

[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: type 'String' is not a subtype of type 'Map<String, dynamic>'
#0 Client.init. (package:flutter_oss_aliyun/src/client.dart:46:45)

#1 AuthMixin.getAuth (package:flutter_oss_aliyun/src/auth_mixin.dart:13:14)

#2 Client.putObject (package:flutter_oss_aliyun/src/client.dart:256:23)

请求使用帮助,我无法使用ak以访问aliyun oss

很抱歉在此打扰各位开发者,但我没在网上找到相关帮助,实在抱歉

自定义authGetter得到Auth 阶段,我尝试使用ak来完成Client.init(),我现在得到了阿里云oss服务的ak,毫无疑问它是正确的,这在ossbrowser中得到了验证。

Auth( accessKey: OssConfig.accessKeyId, accessSecret: OssConfig.accessKeySecret, expire: DateTime.now().toIso8601String(), secureToken: 'token', )

这是我的Auth函数,我不明白哪里出了问题,我也无法得知如何去获取token,如果有人能帮助我,我将不胜感激。Thank you

Accept better `tokenGetter` on `init`

In my opinion, tokenGetter should have a FutureOr<Auth> Function()? type.

Here're 2 changes:

  1. Change the actual acceptable type to Auth so that we can construct it manually with Auth(...) rather than convert return value from our function into a patterned json. For example, Aliyun's TypeScript STS SDK @alicloud/sts20150401 would return a camelCase json, while the patterned json use a PascalCase json. As a result, we have to make a conversion here.
  2. Replace Future<T> with FutureOr<T>, since it would allow user to write simpler code here rather than wrap it with () async => or something alike while the token can be access synchronously.

支持Stream上传吗

我好像就看见Dio里面是有Stream的,但是它的Stream也是从二进制转换的,这样子对大文件上传下载是不是效率很低

NextContinuationToken 出现

请求的代码为如下:

Response<dynamic> res = await Client().listObjects({'prefix': "images", "continuation-token": token, 'max-keys': "5"});

返回的结果为:

<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>

The 'aliyun_sls/flutter_sdk' channel sent a message from native to Flutter on a non-platform thread. Platform channel messages must be sent on the platform thread. Failure to do so may result in data loss or crashes, and must be fixed in the plugin or application code creating that channel.

请问有人遇到这个提示吗?

版本: flutter_oss_aliyun: 6.4.1

flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.16.9, on macOS 14.2.1 23C71 darwin-arm64, locale zh-Hans-CN)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2022.3)
[✓] VS Code (version 1.88.1)
[✓] Connected device (3 available)
    ! Error: Browsing on the local area network forxxx的iPhone. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac.
      The device must be opted into Developer Mode to connect wirelessly. (code -27)
    ! Error: Browsing on the local area network for 测试机iphone15. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac.
      The device must be opted into Developer Mode to connect wirelessly. (code -27)
[✓] Network resources

• No issues found!

打开vpn时,上传图片Dio报错。

如果我不开vpn,一切正常。
如果打开vpn,调用await Client().putObject(imageData, fileKey).then((re.Response value)
报错:
image
麻烦看看这是什么原因

The getSignedUrl method has a probability of signature mismatch

String url = await Client().getSignedUrl(path,expireSeconds:10*60);
When I add pictures one by one, two or three of them will have mismatched signatures, even if I set the expiration time to ten minutes or more

so Then I try to call getSignedUrls for these pictures once, and they still exist

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.