├── .github └── workflows │ ├── ci-test.yml │ ├── codeql-analysis.yml │ └── version-check.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── OWNERS ├── README.md ├── audit ├── apis │ ├── api_query_log.go │ ├── apis.go │ └── query_log │ │ └── api.go └── doc.go ├── auth ├── context.go ├── credentials.go ├── credentials_test.go ├── qbox │ ├── doc.go │ ├── qbox_auth.go │ └── qbox_test.go └── signer.go ├── cdn ├── anti_leech.go ├── anti_leech_test.go ├── api.go ├── api_test.go ├── data_type.go ├── datatype_string.go ├── doc.go └── option.go ├── client ├── client.go ├── client_1.12.go ├── client_1.13.go ├── client_prod_env.go ├── client_test_env.go ├── dialer.go ├── dialer_test.go ├── header.go ├── header_test.go ├── json_decode.go └── json_decode_test.go ├── codecov.yml ├── conf ├── conf.go └── doc.go ├── doc.go ├── err.go ├── examples ├── bucket_image_unimage.go ├── cdn_create_timestamp_antileech_url.go ├── cdn_get_bandwidth_data.go ├── cdn_get_flux_data.go ├── cdn_get_log_list.go ├── cdn_prefetch_urls.go ├── cdn_refresh_urls_and_dirs.go ├── create_uptoken.go ├── form_upload_simple.go ├── prefop.go ├── resume_upload_advanced.go ├── resume_upload_simple.go ├── rs_async_fetch.go ├── rs_batch_change_mime.go ├── rs_batch_change_type.go ├── rs_batch_copy.go ├── rs_batch_delete.go ├── rs_batch_deleteAfterDays.go ├── rs_batch_move.go ├── rs_batch_stat.go ├── rs_change_mime.go ├── rs_change_type.go ├── rs_copy.go ├── rs_delete.go ├── rs_deleteAfterDays.go ├── rs_download.go ├── rs_fetch.go ├── rs_list_bucket.go ├── rs_list_bucket_context.go ├── rs_list_files.go ├── rs_move.go ├── rs_prefetch.go ├── rs_stat.go ├── rtc.go └── video_pfop.go ├── go.mod ├── go.sum ├── iam ├── apis │ ├── api_create_group.go │ ├── api_create_policy.go │ ├── api_create_user.go │ ├── api_create_user_keypairs.go │ ├── api_delete_group.go │ ├── api_delete_group_policies.go │ ├── api_delete_group_users.go │ ├── api_delete_policy.go │ ├── api_delete_user.go │ ├── api_delete_user_keypair.go │ ├── api_delete_user_policy.go │ ├── api_disable_user_keypair.go │ ├── api_enable_user_keypair.go │ ├── api_get_actions.go │ ├── api_get_audits.go │ ├── api_get_group.go │ ├── api_get_group_policies.go │ ├── api_get_group_service_action_resources.go │ ├── api_get_group_users.go │ ├── api_get_groups.go │ ├── api_get_policies.go │ ├── api_get_policy.go │ ├── api_get_policy_groups.go │ ├── api_get_policy_users.go │ ├── api_get_services.go │ ├── api_get_user.go │ ├── api_get_user_available_services.go │ ├── api_get_user_groups.go │ ├── api_get_user_keypairs.go │ ├── api_get_user_policies.go │ ├── api_get_user_service_action_resources.go │ ├── api_get_users.go │ ├── api_modify_group.go │ ├── api_modify_group_policies.go │ ├── api_modify_group_users.go │ ├── api_modify_policy.go │ ├── api_modify_user.go │ ├── api_modify_user_policies.go │ ├── api_update_group_policies.go │ ├── api_update_group_users.go │ ├── api_update_policy_groups.go │ ├── api_update_policy_users.go │ ├── api_update_user_groups.go │ ├── api_update_user_policies.go │ ├── apis.go │ ├── create_group │ │ └── api.go │ ├── create_policy │ │ └── api.go │ ├── create_user │ │ └── api.go │ ├── create_user_keypairs │ │ └── api.go │ ├── delete_group │ │ └── api.go │ ├── delete_group_policies │ │ └── api.go │ ├── delete_group_users │ │ └── api.go │ ├── delete_policy │ │ └── api.go │ ├── delete_user │ │ └── api.go │ ├── delete_user_keypair │ │ └── api.go │ ├── delete_user_policy │ │ └── api.go │ ├── disable_user_keypair │ │ └── api.go │ ├── enable_user_keypair │ │ └── api.go │ ├── get_actions │ │ └── api.go │ ├── get_audits │ │ └── api.go │ ├── get_group │ │ └── api.go │ ├── get_group_policies │ │ └── api.go │ ├── get_group_service_action_resources │ │ └── api.go │ ├── get_group_users │ │ └── api.go │ ├── get_groups │ │ └── api.go │ ├── get_policies │ │ └── api.go │ ├── get_policy │ │ └── api.go │ ├── get_policy_groups │ │ └── api.go │ ├── get_policy_users │ │ └── api.go │ ├── get_services │ │ └── api.go │ ├── get_user │ │ └── api.go │ ├── get_user_available_services │ │ └── api.go │ ├── get_user_groups │ │ └── api.go │ ├── get_user_keypairs │ │ └── api.go │ ├── get_user_policies │ │ └── api.go │ ├── get_user_service_action_resources │ │ └── api.go │ ├── get_users │ │ └── api.go │ ├── modify_group │ │ └── api.go │ ├── modify_group_policies │ │ └── api.go │ ├── modify_group_users │ │ └── api.go │ ├── modify_policy │ │ └── api.go │ ├── modify_user │ │ └── api.go │ ├── modify_user_policies │ │ └── api.go │ ├── update_group_policies │ │ └── api.go │ ├── update_group_users │ │ └── api.go │ ├── update_policy_groups │ │ └── api.go │ ├── update_policy_users │ │ └── api.go │ ├── update_user_groups │ │ └── api.go │ └── update_user_policies │ │ └── api.go └── doc.go ├── internal ├── api-generator │ ├── client.go │ ├── form.go │ ├── headers.go │ ├── json.go │ ├── main.go │ ├── multipart.go │ ├── path.go │ ├── query.go │ ├── request.go │ ├── response.go │ ├── types.go │ └── utils.go ├── cache │ ├── cache.go │ └── cache_test.go ├── clientv2 │ ├── client.go │ ├── client_test.go │ ├── context.go │ ├── interceptor.go │ ├── interceptor_anti_hijacking.go │ ├── interceptor_auth.go │ ├── interceptor_auth_test.go │ ├── interceptor_buffer_response.go │ ├── interceptor_debug.go │ ├── interceptor_default_header.go │ ├── interceptor_retry_hosts.go │ ├── interceptor_retry_hosts_test.go │ ├── interceptor_retry_simple.go │ ├── interceptor_retry_simple_test.go │ ├── interceptor_uptoken.go │ ├── multipart.go │ ├── multipart_test.go │ ├── request.go │ ├── request_compatible.go │ ├── request_test.go │ └── retry_config.go ├── configfile │ ├── config_file.go │ ├── config_file_test.go │ ├── go1.11.go │ └── go1.12.go ├── context │ ├── context_test.go │ ├── go1.19.go │ └── go1.20.go ├── dialer │ ├── dialer.go │ └── dialer_test.go ├── env │ └── env.go ├── freezer │ └── freezer.go ├── hostprovider │ └── host_provider.go ├── io │ ├── compatible.go │ ├── compatible_test.go │ ├── go1.15.go │ ├── go1.16.go │ ├── go1.19.go │ ├── go1.20.go │ ├── io.go │ ├── io_test.go │ └── nopcloser.go ├── log │ ├── logger.go │ └── logger_test.go └── uplog │ ├── block_uplog.go │ ├── dns1.12.go │ ├── dns1.13.go │ ├── quality_uplog.go │ ├── request_uplog.go │ ├── uplog.go │ ├── uplog_buffer.go │ ├── uplog_buffer_test.go │ ├── uplog_prod_env.go │ ├── uplog_test_env.go │ ├── uplog_upload.go │ └── utils.go ├── linking ├── README.md ├── conf.go ├── device.go ├── deviceHistoryActivity.go ├── deviceHistoryActivity_test.go ├── deviceKey.go ├── deviceKey_test.go ├── device_test.go ├── dtoken.go ├── dtoken_test.go ├── manager.go ├── manager_test.go ├── util.go ├── vod.go └── vod_test.go ├── media ├── apis │ ├── api_pfop.go │ ├── api_prefop.go │ ├── apis.go │ ├── pfop │ │ └── api.go │ └── prefop │ │ └── api.go └── doc.go ├── pili ├── README.md ├── auth.go ├── conf.go ├── domain.go ├── domain_model.go ├── domain_test.go ├── error.go ├── hub.go ├── hub_model.go ├── hub_test.go ├── manager.go ├── manager_test.go ├── stat.go ├── stat_model.go ├── stat_test.go ├── stream.go ├── stream_model.go ├── stream_test.go ├── url.go ├── url_model.go ├── url_test.go ├── validate.go └── validate_test.go ├── qvs ├── README.md ├── conf.go ├── device.go ├── device_test.go ├── manager.go ├── manager_test.go ├── namespace.go ├── namespace_test.go ├── record.go ├── record_test.go ├── stats.go ├── stats_test.go ├── stream.go ├── stream_test.go ├── template.go ├── template_test.go └── util.go ├── reqid └── reqid.go ├── rtc ├── api.go ├── api_test.go ├── doc.go └── util.go ├── sms ├── bytes │ ├── bytes.go │ ├── bytes_test.go │ └── seekable │ │ └── seekable.go ├── client │ ├── qiniumac.go │ └── transport.go ├── doc.go ├── enums.go ├── main_test.go ├── manager.go ├── message.go ├── rpc │ ├── client.go │ └── transport.go ├── signature.go ├── signature_test.go ├── template.go └── template_test.go ├── storage ├── backward_compatible.go ├── backward_compatible_test.go ├── base64_upload.go ├── base64_upload_test.go ├── bucket.go ├── bucket_get.go ├── bucket_get_test.go ├── bucket_list.go ├── bucket_list_test.go ├── bucket_test.go ├── buckets_v4.go ├── config.go ├── config_test.go ├── doc.go ├── endpoint_test.go ├── errs.go ├── form_upload.go ├── form_upload_test.go ├── pfop.go ├── pfop_test.go ├── recorder.go ├── region.go ├── region_group.go ├── region_test.go ├── region_uc_test.go ├── region_uc_v2.go ├── region_uc_v2_test.go ├── region_uc_v4.go ├── resume_uploader.go ├── resume_uploader_apis.go ├── resume_uploader_base.go ├── resume_uploader_deprecated.go ├── resume_uploader_test.go ├── resume_uploader_v2.go ├── resume_uploader_v2_test.go ├── token.go ├── token_test.go ├── uc.go ├── uc_test.go ├── upload_manager.go ├── upload_manager_test.go ├── upload_manager_uplog_test.go ├── upload_source.go ├── uploader_base.go ├── util.go ├── worker_test.go └── zone.go ├── storagev2 ├── apis │ ├── add_bucket_event_rule │ │ └── api.go │ ├── add_bucket_rules │ │ └── api.go │ ├── api_add_bucket_event_rule.go │ ├── api_add_bucket_rules.go │ ├── api_async_fetch_object.go │ ├── api_batch_ops.go │ ├── api_check_share.go │ ├── api_copy_object.go │ ├── api_create_bucket.go │ ├── api_create_share.go │ ├── api_delete_bucket.go │ ├── api_delete_bucket_event_rule.go │ ├── api_delete_bucket_rules.go │ ├── api_delete_bucket_taggings.go │ ├── api_delete_object.go │ ├── api_delete_object_after_days.go │ ├── api_disable_bucket_index_page.go │ ├── api_fetch_object.go │ ├── api_get_async_fetch_task.go │ ├── api_get_bucket_cors_rules.go │ ├── api_get_bucket_domains.go │ ├── api_get_bucket_domains_v3.go │ ├── api_get_bucket_event_rules.go │ ├── api_get_bucket_info.go │ ├── api_get_bucket_infos.go │ ├── api_get_bucket_quota.go │ ├── api_get_bucket_rules.go │ ├── api_get_bucket_taggings.go │ ├── api_get_buckets.go │ ├── api_get_buckets_v4.go │ ├── api_get_objects.go │ ├── api_get_objects_v2.go │ ├── api_get_regions.go │ ├── api_modify_object_life_cycle.go │ ├── api_modify_object_metadata.go │ ├── api_modify_object_status.go │ ├── api_move_object.go │ ├── api_post_object.go │ ├── api_prefetch_object.go │ ├── api_query_bucket_v2.go │ ├── api_query_bucket_v4.go │ ├── api_restore_archived_object.go │ ├── api_resumable_upload_v1_bput.go │ ├── api_resumable_upload_v1_make_block.go │ ├── api_resumable_upload_v1_make_file.go │ ├── api_resumable_upload_v2_abort_multipart_upload.go │ ├── api_resumable_upload_v2_complete_multipart_upload.go │ ├── api_resumable_upload_v2_initiate_multipart_upload.go │ ├── api_resumable_upload_v2_list_parts.go │ ├── api_resumable_upload_v2_upload_part.go │ ├── api_set_bucket_access_mode.go │ ├── api_set_bucket_cors_rules.go │ ├── api_set_bucket_image.go │ ├── api_set_bucket_max_age.go │ ├── api_set_bucket_private.go │ ├── api_set_bucket_quota.go │ ├── api_set_bucket_refer_anti_leech.go │ ├── api_set_bucket_remark.go │ ├── api_set_bucket_taggings.go │ ├── api_set_buckets_mirror.go │ ├── api_set_object_file_type.go │ ├── api_stat_object.go │ ├── api_unset_bucket_image.go │ ├── api_update_bucket_event_rule.go │ ├── api_update_bucket_rules.go │ ├── api_verify_share.go │ ├── apis.go │ ├── async_fetch_object │ │ └── api.go │ ├── batch_ops │ │ └── api.go │ ├── check_share │ │ └── api.go │ ├── copy_object │ │ └── api.go │ ├── create_bucket │ │ └── api.go │ ├── create_share │ │ └── api.go │ ├── delete_bucket │ │ └── api.go │ ├── delete_bucket_event_rule │ │ └── api.go │ ├── delete_bucket_rules │ │ └── api.go │ ├── delete_bucket_taggings │ │ └── api.go │ ├── delete_object │ │ └── api.go │ ├── delete_object_after_days │ │ └── api.go │ ├── disable_bucket_index_page │ │ └── api.go │ ├── fetch_object │ │ └── api.go │ ├── get_async_fetch_task │ │ └── api.go │ ├── get_bucket_cors_rules │ │ └── api.go │ ├── get_bucket_domains │ │ └── api.go │ ├── get_bucket_domains_v3 │ │ └── api.go │ ├── get_bucket_event_rules │ │ └── api.go │ ├── get_bucket_info │ │ └── api.go │ ├── get_bucket_infos │ │ └── api.go │ ├── get_bucket_quota │ │ └── api.go │ ├── get_bucket_rules │ │ └── api.go │ ├── get_bucket_taggings │ │ └── api.go │ ├── get_buckets │ │ └── api.go │ ├── get_buckets_v4 │ │ └── api.go │ ├── get_objects │ │ └── api.go │ ├── get_objects_v2 │ │ └── api.go │ ├── get_regions │ │ └── api.go │ ├── modify_object_life_cycle │ │ └── api.go │ ├── modify_object_metadata │ │ └── api.go │ ├── modify_object_status │ │ └── api.go │ ├── move_object │ │ └── api.go │ ├── post_object │ │ └── api.go │ ├── prefetch_object │ │ └── api.go │ ├── query_bucket_v2 │ │ └── api.go │ ├── query_bucket_v4 │ │ └── api.go │ ├── restore_archived_object │ │ └── api.go │ ├── resumable_upload_v1_bput │ │ └── api.go │ ├── resumable_upload_v1_make_block │ │ └── api.go │ ├── resumable_upload_v1_make_file │ │ └── api.go │ ├── resumable_upload_v2_abort_multipart_upload │ │ └── api.go │ ├── resumable_upload_v2_complete_multipart_upload │ │ └── api.go │ ├── resumable_upload_v2_initiate_multipart_upload │ │ └── api.go │ ├── resumable_upload_v2_list_parts │ │ └── api.go │ ├── resumable_upload_v2_upload_part │ │ └── api.go │ ├── set_bucket_access_mode │ │ └── api.go │ ├── set_bucket_cors_rules │ │ └── api.go │ ├── set_bucket_image │ │ └── api.go │ ├── set_bucket_max_age │ │ └── api.go │ ├── set_bucket_private │ │ └── api.go │ ├── set_bucket_quota │ │ └── api.go │ ├── set_bucket_refer_anti_leech │ │ └── api.go │ ├── set_bucket_remark │ │ └── api.go │ ├── set_bucket_taggings │ │ └── api.go │ ├── set_buckets_mirror │ │ └── api.go │ ├── set_object_file_type │ │ └── api.go │ ├── stat_object │ │ └── api.go │ ├── unset_bucket_image │ │ └── api.go │ ├── update_bucket_event_rule │ │ └── api.go │ ├── update_bucket_rules │ │ └── api.go │ └── verify_share │ │ └── api.go ├── apistest │ └── apis_test.go ├── backoff │ ├── backoff.go │ └── backoff_test.go ├── chooser │ ├── chooser.go │ ├── chooser_test.go │ ├── ip_chooser.go │ ├── ip_chooser_test.go │ ├── shuffle_chooser.go │ ├── smart_ip_chooser.go │ ├── subnet_chooser.go │ └── subnet_chooser_test.go ├── credentials │ └── credentials.go ├── defaults │ └── defaults.go ├── doc.go ├── downloader │ ├── destination │ │ ├── destination.go │ │ └── destination_test.go │ ├── download_manager.go │ ├── download_manager_test.go │ ├── downloaders.go │ ├── downloaders_test.go │ ├── interceptor.go │ ├── interfaces.go │ ├── resumable_recorder │ │ ├── dummy.go │ │ ├── json_file_system.go │ │ ├── json_file_system_test.go │ │ └── resumable_recorder.go │ ├── signers.go │ ├── urls_provider.go │ ├── urls_provider_test.go │ └── urls_provider_utils.go ├── errors │ └── errors.go ├── http_client │ ├── context.go │ ├── http_client.go │ ├── http_client_test.go │ └── multipart.go ├── internal │ └── api-specs │ │ ├── add_bucket_event_rule.yml │ │ ├── delete_bucket_event_rule.yml │ │ ├── disable_bucket_index_page.yml │ │ ├── get_bucket_domains_v3.yml │ │ ├── get_bucket_event_rules.yml │ │ ├── get_bucket_info.yml │ │ ├── get_bucket_infos.yml │ │ ├── query_bucket_v2.yml │ │ ├── query_bucket_v4.yml │ │ ├── set_bucket_image.yml │ │ ├── set_bucket_refer_anti_leech.yml │ │ ├── unset_bucket_image.yml │ │ └── update_bucket_event_rule.yml ├── objects │ ├── batch.go │ ├── batch_test.go │ ├── bucket.go │ ├── directory.go │ ├── directory_test.go │ ├── lister.go │ ├── lister_test.go │ ├── object.go │ ├── object_details.go │ ├── object_test.go │ ├── objects_manager.go │ └── operations.go ├── region │ ├── all.go │ ├── all_test.go │ ├── query.go │ ├── query_test.go │ ├── region.go │ └── region_test.go ├── resolver │ ├── resolver.go │ └── resolver_test.go ├── retrier │ ├── dns1.12.go │ ├── dns1.13.go │ └── retrier.go ├── uploader │ ├── credentials_uptoken_signer.go │ ├── form_uploader_test.go │ ├── interfaces.go │ ├── multi_parts_uploader_test.go │ ├── multi_parts_uploader_v1.go │ ├── multi_parts_uploader_v1_test.go │ ├── multi_parts_uploader_v2.go │ ├── multi_parts_uploader_v2_test.go │ ├── params.go │ ├── resumable_recorder.go │ ├── resumable_recorder │ │ ├── dummy.go │ │ ├── json_file_system.go │ │ ├── json_file_system_test.go │ │ └── resumable_recorder.go │ ├── schedulers.go │ ├── schedulers_test.go │ ├── source │ │ ├── source.go │ │ └── source_test.go │ ├── upload_manager.go │ ├── upload_manager_test.go │ └── uploaders.go ├── uplog │ └── uplog.go └── uptoken │ ├── putpolicy.go │ ├── uploadtoken.go │ └── uploadtoken_test.go └── types.go /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | 21 | jobs: 22 | analyze: 23 | name: Analyze 24 | runs-on: ubuntu-latest 25 | permissions: 26 | actions: read 27 | contents: read 28 | security-events: write 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | language: [ 'go' ] 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@v2 37 | - name: Initialize CodeQL 38 | uses: github/codeql-action/init@v1 39 | with: 40 | languages: ${{ matrix.language }} 41 | - name: Autobuild 42 | uses: github/codeql-action/autobuild@v1 43 | - name: Perform CodeQL Analysis 44 | uses: github/codeql-action/analyze@v1 45 | -------------------------------------------------------------------------------- /.github/workflows/version-check.yml: -------------------------------------------------------------------------------- 1 | name: Golang SDK Version Check 2 | on: 3 | push: 4 | tags: 5 | - "v[0-9]+.[0-9]+.[0-9]+" 6 | jobs: 7 | linux: 8 | name: Version Check 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | - name: Set env 14 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/v}" >> $GITHUB_ENV 15 | - name: Check 16 | run: | 17 | set -e 18 | grep -qF "## ${RELEASE_VERSION}" CHANGELOG.md 19 | grep -qF "require github.com/qiniu/go-sdk/v7 v${RELEASE_VERSION}" README.md 20 | grep -qF "const Version = \"${RELEASE_VERSION}\"" conf/conf.go 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | *.dylib 6 | *.dll 7 | 8 | # Folders 9 | _obj 10 | _test 11 | .idea 12 | .vscode 13 | .DS_Store 14 | 15 | # Architecture specific extensions/prefixes 16 | *.[568vq] 17 | [568vq].out 18 | 19 | *.cgo1.go 20 | *.cgo2.c 21 | _cgo_defun.c 22 | _cgo_gotypes.go 23 | _cgo_export.* 24 | 25 | _testmain.go 26 | 27 | *.exe 28 | *.test 29 | *.prof 30 | 31 | # coverage file 32 | coverage.txt 33 | 34 | # Go workspace file 35 | go.work 36 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "api-specs"] 2 | path = api-specs 3 | url = https://github.com/qiniu/api-specs.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Qiniu, Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | go test -tags='unit integration' -failfast -count=1 -v -timeout 350m -coverprofile=coverage.txt `go list ./... | egrep -v 'examples|sms'` 3 | 4 | unittest: 5 | go test -tags=unit -failfast -count=1 -v -coverprofile=coverage.txt `go list ./... | egrep -v 'examples|sms'` 6 | 7 | integrationtest: 8 | go test -tags=integration -failfast -count=1 -parallel 1 -v -coverprofile=coverage.txt `go list ./... | egrep -v 'examples|sms'` 9 | 10 | staticcheck: 11 | staticcheck `go list ./... | egrep -v 'examples|sms'` 12 | 13 | generate: 14 | go generate ./storagev2/ 15 | go generate ./iam/ 16 | go generate ./media/ 17 | go generate ./audit/ 18 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # see the OWNERS docs at https://go.k8s.io/owners 2 | 3 | approvers: 4 | - CarlJi 5 | - bachue 6 | - YangSen-qn 7 | - Mei-Zhao 8 | - sxci 9 | reviewers: 10 | - CarlJi 11 | - bachue 12 | - YangSen-qn 13 | - Mei-Zhao 14 | - sxci 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | github.com/qiniu/go-sdk 2 | =============== 3 | 4 | [![LICENSE](https://img.shields.io/github/license/qiniu/go-sdk.svg)](https://github.com/qiniu/go-sdk/blob/master/LICENSE) 5 | [![Build Status](https://github.com/qiniu/go-sdk/workflows/Run%20Test%20Cases/badge.svg)](https://github.com/qiniu/go-sdk/actions) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/qiniu/go-sdk)](https://goreportcard.com/report/github.com/qiniu/go-sdk) 7 | [![GitHub release](https://img.shields.io/github/v/tag/qiniu/go-sdk.svg?label=release)](https://github.com/qiniu/go-sdk/releases) 8 | [![codecov](https://codecov.io/gh/qiniu/go-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/qiniu/go-sdk) 9 | [![GoDoc](https://godoc.org/github.com/qiniu/go-sdk/v7?status.svg)](https://godoc.org/github.com/qiniu/go-sdk/v7) 10 | 11 | [![Qiniu Logo](http://open.qiniudn.com/logo.png)](http://qiniu.com/) 12 | 13 | # 下载 14 | 15 | ## 使用 Go mod【推荐】 16 | 17 | 在您的项目中的 `go.mod` 文件内添加这行代码 18 | 19 | ``` 20 | require github.com/qiniu/go-sdk/v7 v7.25.1 21 | ``` 22 | 23 | 并且在项目中使用 `"github.com/qiniu/go-sdk/v7"` 引用 Qiniu Go SDK。 24 | 25 | 例如 26 | 27 | ```go 28 | import ( 29 | "github.com/qiniu/go-sdk/v7/auth" 30 | "github.com/qiniu/go-sdk/v7/storage" 31 | ) 32 | ``` 33 | 34 | # Golang 版本需求 35 | 36 | 需要 go1.10 或者 1.10 以上 37 | 38 | # 文档 39 | 40 | [七牛SDK文档站](https://developer.qiniu.com/kodo/sdk/1238/go) 或者 [项目WIKI](https://github.com/qiniu/go-sdk/wiki) 41 | 42 | # 示例 43 | 44 | [参考代码](https://github.com/qiniu/go-sdk/tree/master/examples) 45 | -------------------------------------------------------------------------------- /audit/apis/apis.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | package apis 4 | 5 | import ( 6 | httpclient "github.com/qiniu/go-sdk/v7/storagev2/http_client" 7 | region "github.com/qiniu/go-sdk/v7/storagev2/region" 8 | ) 9 | 10 | // API 客户端 11 | type Audit struct { 12 | client *httpclient.Client 13 | } 14 | 15 | // 创建 API 客户端 16 | func NewAudit(options *httpclient.Options) *Audit { 17 | return &Audit{client: httpclient.NewClient(options)} 18 | } 19 | 20 | // API 客户端选项 21 | type Options struct { 22 | OverwrittenBucketHosts region.EndpointsProvider 23 | OverwrittenBucketName string 24 | OverwrittenEndpoints region.EndpointsProvider 25 | OverwrittenRegion region.RegionsProvider 26 | OnRequestProgress func(uint64, uint64) 27 | } 28 | -------------------------------------------------------------------------------- /audit/doc.go: -------------------------------------------------------------------------------- 1 | // audit 包提供了账号审计等功能。 2 | package audit 3 | 4 | //go:generate go run ../internal/api-generator -- --api-specs=../api-specs/audit --output=apis/ --struct-name=Audit --api-package=github.com/qiniu/go-sdk/v7/audit/apis 5 | //go:generate go build ./apis/... 6 | -------------------------------------------------------------------------------- /auth/context.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // MacContextKey 是用户的密钥信息 8 | // context.Context中的键值不应该使用普通的字符串, 有可能导致命名冲突 9 | type macContextKey struct{} 10 | 11 | // tokenTypeKey 是签名算法类型key 12 | type tokenTypeKey struct{} 13 | 14 | // WithCredentials 返回一个包含密钥信息的context 15 | func WithCredentials(ctx context.Context, cred *Credentials) context.Context { 16 | if ctx == nil { 17 | ctx = context.Background() 18 | } 19 | return context.WithValue(ctx, macContextKey{}, cred) 20 | } 21 | 22 | // WithCredentialsType 返回一个context, 保存了密钥信息和token类型 23 | func WithCredentialsType(ctx context.Context, cred *Credentials, t TokenType) context.Context { 24 | ctx = WithCredentials(ctx, cred) 25 | return context.WithValue(ctx, tokenTypeKey{}, t) 26 | } 27 | 28 | // CredentialsFromContext 从context获取密钥信息 29 | func CredentialsFromContext(ctx context.Context) (cred *Credentials, t TokenType, ok bool) { 30 | cred, ok = ctx.Value(macContextKey{}).(*Credentials) 31 | t, yes := ctx.Value(tokenTypeKey{}).(TokenType) 32 | if !yes { 33 | t = TokenQBox 34 | } 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /auth/qbox/doc.go: -------------------------------------------------------------------------------- 1 | // qbox 包提供了该SDK需要的相关鉴权方法 2 | package qbox 3 | -------------------------------------------------------------------------------- /auth/qbox/qbox_auth.go: -------------------------------------------------------------------------------- 1 | package qbox 2 | 3 | import ( 4 | "github.com/qiniu/go-sdk/v7/auth" 5 | "net/http" 6 | ) 7 | 8 | type Mac = auth.Credentials 9 | 10 | // 兼容保留 11 | func NewMac(accessKey, secretKey string) *Mac { 12 | return auth.New(accessKey, secretKey) 13 | } 14 | 15 | // Sign 一般用于下载凭证的签名 16 | func Sign(mac *Mac, data []byte) string { 17 | return mac.Sign(data) 18 | } 19 | 20 | // SignWithData 一般用于上传凭证的签名 21 | func SignWithData(mac *Mac, data []byte) string { 22 | return mac.SignWithData(data) 23 | } 24 | 25 | // VerifyCallback 验证上传回调请求是否来自七牛 26 | func VerifyCallback(mac *Mac, req *http.Request) (bool, error) { 27 | return mac.VerifyCallback(req) 28 | } 29 | -------------------------------------------------------------------------------- /auth/qbox/qbox_test.go: -------------------------------------------------------------------------------- 1 | //go:build unit 2 | // +build unit 3 | 4 | package qbox 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | var mac *Mac 11 | 12 | func init() { 13 | mac = NewMac("ak", "sk") 14 | } 15 | 16 | func TestMacSign(t *testing.T) { 17 | testStrs := []struct { 18 | Data string 19 | Signed string 20 | }{ 21 | {Data: "hello", Signed: "ak:NDN8cM0rwosxhHJ6QAcI7ialr0g="}, 22 | {Data: "world", Signed: "ak:wZ-sw41ayh3PFDmQA-D3o7eBJIY="}, 23 | {Data: "-test", Signed: "ak:oJ59sZasiWSqL1o7ugZs5OInEK4="}, 24 | {Data: "ba#a-", Signed: "ak:tqHL8V2BbNI0dVDXsvueZp_2QnI="}, 25 | } 26 | for _, b := range testStrs { 27 | got := mac.Sign([]byte(b.Data)) 28 | if got != b.Signed { 29 | t.Errorf("Sign %q, want=%q, got=%q\n", b.Data, b.Signed, got) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /auth/signer.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | // 七牛签名算法的类型: 4 | // QBoxToken, QiniuToken, BearToken, QiniuMacToken 5 | type TokenType int 6 | 7 | const ( 8 | TokenQiniu TokenType = iota 9 | TokenQBox 10 | ) 11 | -------------------------------------------------------------------------------- /cdn/anti_leech.go: -------------------------------------------------------------------------------- 1 | package cdn 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "net/url" 7 | "time" 8 | ) 9 | 10 | // CreateTimestampAntileechURL 用来构建七牛CDN时间戳防盗链的访问链接 11 | func CreateTimestampAntileechURL(urlStr string, encryptKey string, 12 | durationInSeconds int64) (antileechURL string, err error) { 13 | u, err := url.Parse(urlStr) 14 | if err != nil { 15 | return 16 | } 17 | 18 | expireTime := time.Now().Add(time.Second * time.Duration(durationInSeconds)).Unix() 19 | toSignStr := fmt.Sprintf("%s%s%x", encryptKey, u.EscapedPath(), expireTime) 20 | signedStr := fmt.Sprintf("%x", md5.Sum([]byte(toSignStr))) 21 | 22 | q := url.Values{} 23 | q.Add("sign", signedStr) 24 | q.Add("t", fmt.Sprintf("%x", expireTime)) 25 | 26 | if u.RawQuery == "" { 27 | antileechURL = u.String() + "?" + q.Encode() 28 | } else { 29 | antileechURL = u.String() + "&" + q.Encode() 30 | } 31 | 32 | return 33 | } 34 | -------------------------------------------------------------------------------- /cdn/anti_leech_test.go: -------------------------------------------------------------------------------- 1 | //go:build unit 2 | // +build unit 3 | 4 | package cdn 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestCreateTimestampAntiLeech(t *testing.T) { 11 | type args struct { 12 | urlStr string 13 | encryptKey string 14 | durationInSeconds int64 15 | } 16 | tests := []struct { 17 | name string 18 | args args 19 | wantErr bool 20 | }{ 21 | { 22 | name: "antileech_1", 23 | args: args{ 24 | urlStr: "http://www.example.com/testfile.jpg", 25 | encryptKey: "abc123", 26 | durationInSeconds: 3600, 27 | }, 28 | wantErr: false, 29 | }, 30 | } 31 | for _, tt := range tests { 32 | t.Run(tt.name, func(t *testing.T) { 33 | targetUrl, err := CreateTimestampAntileechURL(tt.args.urlStr, tt.args.encryptKey, tt.args.durationInSeconds) 34 | if (err != nil) != tt.wantErr { 35 | t.Errorf("CreateTimestampAntiLeech() error = %v, wantErr %v", err, tt.wantErr) 36 | return 37 | } 38 | t.Log(targetUrl) 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cdn/data_type.go: -------------------------------------------------------------------------------- 1 | package cdn 2 | 3 | //go:generate stringer -type DataType -linecomment 4 | type DataType int 5 | 6 | const ( 7 | // 静态cdn带宽(bps) 8 | DataTypeBandwidth DataType = iota + 1 // bandwidth 9 | // 302cdn带宽(bps) 10 | DataType302Bandwidth // 302bandwidth 11 | // 302MIX带宽(bps) 12 | DataType302mBandwidth // 302mbandwidth 13 | // 静态cdn流量(bytes) 14 | DataTypeFlow // flow 15 | // 302cdn流量(bytes) 16 | DataType302Flow // 302flow 17 | // 302MIX流量(bytes) 18 | DataType302mFlow // 302mflow 19 | ) 20 | 21 | func DataTypeOf(datatype string) DataType { 22 | for d := DataTypeBandwidth; d <= DataType302mFlow; d++ { 23 | if d.String() == datatype { 24 | return d 25 | } 26 | } 27 | return -1 28 | } 29 | 30 | func (d DataType) Valid() bool { 31 | return DataTypeBandwidth <= d && d <= DataType302mFlow 32 | } 33 | -------------------------------------------------------------------------------- /cdn/datatype_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type DataType -linecomment"; DO NOT EDIT. 2 | 3 | package cdn 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[DataTypeBandwidth-1] 12 | _ = x[DataType302Bandwidth-2] 13 | _ = x[DataType302mBandwidth-3] 14 | _ = x[DataTypeFlow-4] 15 | _ = x[DataType302Flow-5] 16 | _ = x[DataType302mFlow-6] 17 | } 18 | 19 | const _DataType_name = "bandwidth302bandwidth302mbandwidthflow302flow302mflow" 20 | 21 | var _DataType_index = [...]uint8{0, 9, 21, 34, 38, 45, 53} 22 | 23 | func (i DataType) String() string { 24 | i -= 1 25 | if i < 0 || i >= DataType(len(_DataType_index)-1) { 26 | return "DataType(" + strconv.FormatInt(int64(i+1), 10) + ")" 27 | } 28 | return _DataType_name[_DataType_index[i]:_DataType_index[i+1]] 29 | } 30 | -------------------------------------------------------------------------------- /cdn/doc.go: -------------------------------------------------------------------------------- 1 | // cdn 包提供了 Fusion CDN的常见功能。相关功能的文档参考:https://developer.qiniu.com/fusion。 2 | // 目前提供了文件和目录刷新,文件预取,获取域名带宽和流量数据,获取域名日志列表等功能。 3 | package cdn 4 | -------------------------------------------------------------------------------- /cdn/option.go: -------------------------------------------------------------------------------- 1 | package cdn 2 | 3 | type Option interface { 4 | Apply(opt interface{}) 5 | } 6 | 7 | type OptionFunc func(interface{}) 8 | 9 | func (f OptionFunc) Apply(opt interface{}) { 10 | f(opt) 11 | } 12 | -------------------------------------------------------------------------------- /client/client_1.12.go: -------------------------------------------------------------------------------- 1 | //go:build !1.13 2 | // +build !1.13 3 | 4 | package client 5 | 6 | import ( 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | var DefaultTransport http.RoundTripper = &http.Transport{ 12 | Proxy: http.ProxyFromEnvironment, 13 | DialContext: defaultDialFunc, 14 | MaxIdleConns: 100, 15 | IdleConnTimeout: 90 * time.Second, 16 | TLSHandshakeTimeout: 10 * time.Second, 17 | ExpectContinueTimeout: 1 * time.Second, 18 | } 19 | -------------------------------------------------------------------------------- /client/client_1.13.go: -------------------------------------------------------------------------------- 1 | //go:build 1.13 2 | // +build 1.13 3 | 4 | package client 5 | 6 | import ( 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | var DefaultTransport http.RoundTripper = &http.Transport{ 12 | Proxy: http.ProxyFromEnvironment, 13 | DialContext: defaultDialFunc, 14 | ForceAttemptHTTP2: true, 15 | MaxIdleConns: 100, 16 | IdleConnTimeout: 90 * time.Second, 17 | TLSHandshakeTimeout: 10 * time.Second, 18 | ExpectContinueTimeout: 1 * time.Second, 19 | } 20 | -------------------------------------------------------------------------------- /client/client_prod_env.go: -------------------------------------------------------------------------------- 1 | //go:build !unit && !integration 2 | // +build !unit,!integration 3 | 4 | package client 5 | 6 | const testRuntime = false 7 | -------------------------------------------------------------------------------- /client/client_test_env.go: -------------------------------------------------------------------------------- 1 | //go:build unit || integration 2 | // +build unit integration 3 | 4 | package client 5 | 6 | const testRuntime = true 7 | -------------------------------------------------------------------------------- /client/dialer_test.go: -------------------------------------------------------------------------------- 1 | //go:build unit 2 | // +build unit 3 | 4 | package client 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "net" 10 | "net/http" 11 | "net/http/httptest" 12 | "testing" 13 | ) 14 | 15 | func TestDefaultDialer(t *testing.T) { 16 | var responseBody struct { 17 | Status string `json:"status"` 18 | } 19 | mux := http.NewServeMux() 20 | mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 21 | w.Header().Add("Content-Type", "application/json") 22 | w.Write([]byte(`{"status":"ok"}`)) 23 | })) 24 | server := httptest.NewServer(mux) 25 | defer server.Close() 26 | 27 | port := server.Listener.Addr().(*net.TCPAddr).Port 28 | 29 | ctx := WithResolvedIPs(context.Background(), "www.qiniu.com", []net.IP{net.IPv4(127, 0, 0, 1)}) 30 | err := DefaultClient.Call(ctx, &responseBody, http.MethodGet, fmt.Sprintf("http://www.qiniu.com:%d/", port), nil) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | if responseBody.Status != "ok" { 35 | t.Fatal("unexpected response") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /client/header.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/qiniu/go-sdk/v7/conf" 8 | ) 9 | 10 | const ( 11 | RequestHeaderKeyXQiniuDate = "X-Qiniu-Date" 12 | ) 13 | 14 | func addDefaultHeader(headers http.Header) error { 15 | return addHttpHeaderXQiniuDate(headers) 16 | } 17 | 18 | func addHttpHeaderXQiniuDate(headers http.Header) error { 19 | if conf.IsDisableQiniuTimestampSignature() { 20 | return nil 21 | } 22 | 23 | timeString := time.Now().UTC().Format("20060102T150405Z") 24 | headers.Set(RequestHeaderKeyXQiniuDate, timeString) 25 | return nil 26 | } 27 | 28 | func AddHttpHeaderRange(header http.Header, contentRange string) { 29 | if len(contentRange) == 0 { 30 | return 31 | } 32 | 33 | header.Set("Range", contentRange) 34 | } 35 | -------------------------------------------------------------------------------- /client/json_decode.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | type jsonDecodeError struct { 11 | original error 12 | data []byte 13 | } 14 | 15 | func (e jsonDecodeError) Error() string { return fmt.Sprintf("%s: %s", e.original.Error(), e.data) } 16 | 17 | func (e jsonDecodeError) Unwrap() error { return e.original } 18 | 19 | func decodeJsonFromData(data []byte, v interface{}) error { 20 | err := json.Unmarshal(data, v) 21 | if err != nil { 22 | return jsonDecodeError{original: err, data: data} 23 | } 24 | return nil 25 | } 26 | 27 | func DecodeJsonFromReader(reader io.Reader, v interface{}) error { 28 | buf := new(bytes.Buffer) 29 | t := io.TeeReader(reader, buf) 30 | err := json.NewDecoder(t).Decode(v) 31 | if err != nil { 32 | return jsonDecodeError{original: err, data: buf.Bytes()} 33 | } 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /client/json_decode_test.go: -------------------------------------------------------------------------------- 1 | //go:build unit 2 | // +build unit 3 | 4 | package client 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestJsonDecode(t *testing.T) { 11 | var ret struct{} 12 | body := ` 13 | 14 | NoSuchKey 15 | The resource you requested does not exist 16 | /mybucket/myfoto.jpg 17 | 4442587FB7D0A2F9 18 | ` 19 | err := decodeJsonFromData([]byte(body), &ret) 20 | if err.Error() != "invalid character '<' looking for beginning of value: "+body { 21 | t.Fatal("unexpected error message", err.Error()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | ci: 3 | - prow.qiniu.io # prow 里面运行需添加,其他 CI 不要 4 | require_ci_to_pass: no # 改为 no,否则 codecov 会等待其他 GitHub 上所有 CI 通过才会留言。 5 | 6 | github_checks: #关闭github checks 7 | annotations: false 8 | 9 | comment: 10 | layout: "reach, diff, flags, files" 11 | behavior: new # 默认是更新旧留言,改为 new,删除旧的,增加新的。 12 | require_changes: false # if true: only post the comment if coverage changes 13 | require_base: no # [yes :: must have a base report to post] 14 | require_head: yes # [yes :: must have a head report to post] 15 | branches: # branch names that can post comment 16 | - "master" 17 | 18 | coverage: 19 | status: # 评判 pr 通过的标准 20 | patch: off 21 | project: # project 统计所有代码x 22 | default: 23 | # basic 24 | target: 50% # 总体通过标准 25 | threshold: 2% # 允许单次下降的幅度 26 | base: auto 27 | if_not_found: success 28 | if_ci_failed: error -------------------------------------------------------------------------------- /conf/conf.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "github.com/qiniu/go-sdk/v7/internal/env" 5 | ) 6 | 7 | const Version = "7.25.1" 8 | 9 | const ( 10 | CONTENT_TYPE_JSON = "application/json" 11 | CONTENT_TYPE_FORM = "application/x-www-form-urlencoded" 12 | CONTENT_TYPE_OCTET = "application/octet-stream" 13 | CONTENT_TYPE_MULTIPART = "multipart/form-data" 14 | ) 15 | 16 | func IsDisableQiniuTimestampSignature() bool { 17 | isDisabled, _ := env.DisableQiniuTimestampSignatureFromEnvironment() 18 | return isDisabled 19 | } 20 | -------------------------------------------------------------------------------- /conf/doc.go: -------------------------------------------------------------------------------- 1 | // conf 包提供了设置APP名称的方法。该APP名称会被放入API请求的UserAgent中,方便后续查询日志分析问题。 2 | package conf 3 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | /* 4 | 包 github.com/qiniu/go-sdk 是七牛 Go 语言 SDK v7.x 版本。 5 | 6 | 主要提供了存储的数据上传,下载,管理以及CDN相关的功能。要求Go语言版本>=1.10.0。 7 | 8 | Go SDK 中主要包含几个包: 9 | 10 | auth 包提供鉴权相关方法,conf 包提供配置相关方法,cdn包提供CDN相关的功能,storage包提供存储相关的功能。 11 | */ 12 | -------------------------------------------------------------------------------- /err.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // QError QINIU SDK error type 4 | // 可以根据Code判断是何种类型错误 5 | type QError struct { 6 | Code string 7 | Message string 8 | } 9 | 10 | // Error 继承error接口 11 | func (e *QError) Error() string { 12 | return e.Code + ": " + e.Message 13 | } 14 | 15 | // NewError 返回QError指针 16 | func NewError(code, message string) *QError { 17 | return &QError{ 18 | Code: code, 19 | Message: message, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/bucket_image_unimage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | cfg := storage.Config{} 19 | mac := auth.New(accessKey, secretKey) 20 | bucketManger := storage.NewBucketManager(mac, &cfg) 21 | siteURL := "http://devtools.qiniu.com" 22 | 23 | // 设置镜像存储 24 | err := bucketManger.SetImage(siteURL, bucket) 25 | if err != nil { 26 | fmt.Println(err) 27 | } 28 | 29 | // 取消设置镜像存储 30 | err = bucketManger.UnsetImage(bucket) 31 | if err != nil { 32 | fmt.Println(err) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /examples/cdn_create_timestamp_antileech_url.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/qiniu/go-sdk/v7/cdn" 8 | ) 9 | 10 | func main() { 11 | urlStr := "http://image.example.com/qiniu_do_not_delete.gif" 12 | cryptKey := "your crypt key" 13 | deadline := time.Now().Add(time.Second * 3600).Unix() 14 | accessUrl, err := cdn.CreateTimestampAntileechURL(urlStr, cryptKey, deadline) 15 | if err != nil { 16 | fmt.Println(err) 17 | return 18 | } 19 | fmt.Println(accessUrl) 20 | } 21 | -------------------------------------------------------------------------------- /examples/cdn_get_bandwidth_data.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/cdn" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | domain = os.Getenv("QINIU_TEST_DOMAIN") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | cdnManager := cdn.NewCdnManager(mac) 20 | 21 | startDate := "2017-07-20" 22 | endDate := "2017-07-30" 23 | g := "day" 24 | data, err := cdnManager.GetBandwidthData(startDate, endDate, g, []string{domain}) 25 | if err != nil { 26 | fmt.Println(err) 27 | return 28 | } 29 | 30 | fmt.Printf("%v\n", data) 31 | } 32 | -------------------------------------------------------------------------------- /examples/cdn_get_flux_data.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/cdn" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | domain = os.Getenv("QINIU_TEST_DOMAIN") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | cdnManager := cdn.NewCdnManager(mac) 20 | 21 | startDate := "2017-07-20" 22 | endDate := "2017-07-30" 23 | g := "day" 24 | data, err := cdnManager.GetFluxData(startDate, endDate, g, []string{domain}) 25 | if err != nil { 26 | fmt.Println(err) 27 | return 28 | } 29 | 30 | fmt.Printf("%v\n", data) 31 | } 32 | -------------------------------------------------------------------------------- /examples/cdn_get_log_list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/cdn" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | domain = os.Getenv("QINIU_TEST_DOMAIN") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | cdnManager := cdn.NewCdnManager(mac) 20 | 21 | domains := []string{ 22 | domain, 23 | } 24 | day := "2017-07-30" 25 | ret, err := cdnManager.GetCdnLogList(day, domains) 26 | if err != nil { 27 | fmt.Println(err) 28 | return 29 | } 30 | domainLogs := ret.Data 31 | for domain, logs := range domainLogs { 32 | fmt.Println(domain) 33 | for _, item := range logs { 34 | fmt.Println(item.Name, item.URL, item.Size, item.ModifiedTime) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/cdn_prefetch_urls.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/cdn" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | domain = os.Getenv("QINIU_TEST_DOMAIN") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | cdnManager := cdn.NewCdnManager(mac) 20 | 21 | // 预取链接,单次请求链接不可以超过100个,如果超过,请分批发送请求 22 | urlsToPrefetch := []string{ 23 | "http://if-pbl.qiniudn.com/qiniu.png", 24 | "http://if-pbl.qiniudn.com/github.png", 25 | } 26 | ret, err := cdnManager.PrefetchUrls(urlsToPrefetch) 27 | if err != nil { 28 | fmt.Println(err) 29 | return 30 | } 31 | fmt.Println(ret.Code) 32 | fmt.Println(ret.RequestID) 33 | } 34 | -------------------------------------------------------------------------------- /examples/cdn_refresh_urls_and_dirs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/cdn" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | domain = os.Getenv("QINIU_TEST_DOMAIN") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | cdnManager := cdn.NewCdnManager(mac) 20 | 21 | //刷新链接,单次请求链接不可以超过100个,如果超过,请分批发送请求 22 | urlsToRefresh := []string{ 23 | "http://if-pbl.qiniudn.com/qiniu.png", 24 | "http://if-pbl.qiniudn.com/github.png", 25 | } 26 | ret, err := cdnManager.RefreshUrls(urlsToRefresh) 27 | if err != nil { 28 | fmt.Println(err) 29 | return 30 | } 31 | fmt.Println(ret.Code) 32 | fmt.Println(ret.RequestID) 33 | 34 | // 刷新目录,刷新目录需要联系七牛技术支持开通权限 35 | // 单次请求链接不可以超过10个,如果超过,请分批发送请求 36 | dirsToRefresh := []string{ 37 | "http://if-pbl.qiniudn.com/images/", 38 | "http://if-pbl.qiniudn.com/static/", 39 | } 40 | ret, err = cdnManager.RefreshDirs(dirsToRefresh) 41 | if err != nil { 42 | fmt.Println(err) 43 | return 44 | } 45 | fmt.Println(ret.Code) 46 | fmt.Println(ret.RequestID) 47 | fmt.Println(ret.Error) 48 | } 49 | -------------------------------------------------------------------------------- /examples/form_upload_simple.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/qiniu/go-sdk/v7/auth" 9 | "github.com/qiniu/go-sdk/v7/storage" 10 | ) 11 | 12 | var ( 13 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 14 | secretKey = os.Getenv("QINIU_SECRET_KEY") 15 | bucket = os.Getenv("QINIU_TEST_BUCKET") 16 | localFile = os.Getenv("LOCAL_FILE") 17 | key = os.Getenv("KEY") 18 | ) 19 | 20 | func main() { 21 | putPolicy := storage.PutPolicy{ 22 | Scope: bucket + ":" + key, 23 | } 24 | 25 | mac := auth.New(accessKey, secretKey) 26 | upToken := putPolicy.UploadToken(mac) 27 | cfg := storage.Config{} 28 | // 空间对应的机房 29 | cfg.Zone = &storage.ZoneHuadong 30 | // 是否使用https域名 31 | cfg.UseHTTPS = false 32 | // 上传是否使用CDN上传加速 33 | cfg.UseCdnDomains = false 34 | 35 | // 构建表单上传的对象 36 | formUploader := storage.NewFormUploader(&cfg) 37 | ret := storage.PutRet{} 38 | // 可选配置 39 | putExtra := storage.PutExtra{ 40 | Params: map[string]string{ 41 | "x:name": "github logo", 42 | }, 43 | } 44 | err := formUploader.PutFile(context.Background(), &ret, upToken, key, localFile, &putExtra) 45 | if err != nil { 46 | fmt.Println(err) 47 | return 48 | } 49 | fmt.Println(ret.Key, ret.Hash) 50 | } 51 | -------------------------------------------------------------------------------- /examples/prefop.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | ) 15 | 16 | func main() { 17 | mac := auth.New(accessKey, secretKey) 18 | cfg := storage.Config{ 19 | UseHTTPS: false, 20 | } 21 | // 指定空间所在的区域,如果不指定将自动探测 22 | // 如果没有特殊需求,默认不需要指定 23 | //cfg.Zone=&storage.ZoneHuabei 24 | operationManager := storage.NewOperationManager(mac, &cfg) 25 | persistentId := "z0.597f28b445a2650c994bb208" 26 | ret, err := operationManager.Prefop(persistentId) 27 | if err != nil { 28 | fmt.Println(err) 29 | return 30 | } 31 | fmt.Println(ret.String()) 32 | } 33 | -------------------------------------------------------------------------------- /examples/resume_upload_advanced.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "context" 8 | 9 | "github.com/qiniu/go-sdk/v7/auth" 10 | "github.com/qiniu/go-sdk/v7/storage" 11 | ) 12 | 13 | var ( 14 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 15 | secretKey = os.Getenv("QINIU_SECRET_KEY") 16 | bucket = os.Getenv("QINIU_TEST_BUCKET") 17 | localFile = os.Getenv("LOCAL_FILE") 18 | key = os.Getenv("KEY") 19 | 20 | // 指定的进度文件保存目录,实际情况下,请确保该目录存在,而且只用于记录进度文件 21 | recordDir = os.Getenv("RECORD_DIR") 22 | ) 23 | 24 | func main() { 25 | putPolicy := storage.PutPolicy{ 26 | Scope: bucket, 27 | } 28 | mac := auth.New(accessKey, secretKey) 29 | upToken := putPolicy.UploadToken(mac) 30 | 31 | cfg := storage.Config{} 32 | // 是否使用https域名 33 | cfg.UseHTTPS = false 34 | // 上传是否使用CDN上传加速 35 | cfg.UseCdnDomains = false 36 | 37 | mErr := os.MkdirAll(recordDir, 0755) 38 | if mErr != nil { 39 | fmt.Println("mkdir for record dir error,", mErr) 40 | return 41 | } 42 | 43 | resumeUploader := storage.NewResumeUploader(&cfg) 44 | ret := storage.PutRet{} 45 | 46 | recorder, err := storage.NewFileRecorder(recordDir) 47 | if err != nil { 48 | fmt.Println(err) 49 | return 50 | } 51 | if err = resumeUploader.PutFile(context.Background(), &ret, upToken, key, localFile, &storage.RputExtra{ 52 | Recorder: recorder, 53 | }); err != nil { 54 | fmt.Println(err) 55 | return 56 | } 57 | fmt.Println(ret.Key, ret.Hash) 58 | } 59 | -------------------------------------------------------------------------------- /examples/resume_upload_simple.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/qiniu/go-sdk/v7/auth" 9 | "github.com/qiniu/go-sdk/v7/storage" 10 | ) 11 | 12 | var ( 13 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 14 | secretKey = os.Getenv("QINIU_SECRET_KEY") 15 | bucket = os.Getenv("QINIU_TEST_BUCKET") 16 | localFile = os.Getenv("LOCAL_FILE") 17 | key = os.Getenv("KEY") 18 | ) 19 | 20 | func main() { 21 | putPolicy := storage.PutPolicy{ 22 | Scope: bucket, 23 | } 24 | mac := auth.New(accessKey, secretKey) 25 | 26 | cfg := storage.Config{} 27 | // 是否使用https域名 28 | cfg.UseHTTPS = false 29 | // 上传是否使用CDN上传加速 30 | cfg.UseCdnDomains = false 31 | 32 | resumeUploader := storage.NewResumeUploader(&cfg) 33 | upToken := putPolicy.UploadToken(mac) 34 | 35 | ret := storage.PutRet{} 36 | 37 | err := resumeUploader.PutFile(context.Background(), &ret, upToken, key, localFile, nil) 38 | if err != nil { 39 | fmt.Println(err) 40 | return 41 | } 42 | 43 | fmt.Println(ret.Key, ret.Hash) 44 | } 45 | -------------------------------------------------------------------------------- /examples/rs_async_fetch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | 29 | param := storage.AsyncFetchParam{ 30 | Url: "http://devtools.qiniu.com/qiniu.png", 31 | Bucket: bucket, 32 | Key: "qiniu.png", 33 | } 34 | 35 | // 指定保存的key 36 | ret, err := bucketManager.AsyncFetch(param) 37 | if err != nil { 38 | fmt.Println("fetch error,", err) 39 | } else { 40 | fmt.Println(ret) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /examples/rs_batch_change_mime.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | chgmKeys := map[string]string{ 29 | "github1.png": "image/x-png", 30 | "github2.png": "image/x-png", 31 | "github3.png": "image/x-png", 32 | "github4.png": "image/x-png", 33 | "github5.png": "image/x-png", 34 | } 35 | chgmOps := make([]string, 0, len(chgmKeys)) 36 | for key, newMime := range chgmKeys { 37 | chgmOps = append(chgmOps, storage.URIChangeMime(bucket, key, newMime)) 38 | } 39 | rets, err := bucketManager.Batch(chgmOps) 40 | if err != nil { 41 | // 遇到错误 42 | if _, ok := err.(*storage.ErrorInfo); ok { 43 | for _, ret := range rets { 44 | // 200 为成功 45 | fmt.Printf("%d\n", ret.Code) 46 | if ret.Code != 200 { 47 | fmt.Printf("%s\n", ret.Data.Error) 48 | } 49 | } 50 | } else { 51 | fmt.Printf("batch error, %s", err) 52 | } 53 | } else { 54 | // 完全成功 55 | for _, ret := range rets { 56 | // 200 为成功 57 | fmt.Printf("%d\n", ret.Code) 58 | if ret.Code != 200 { 59 | fmt.Printf("%s\n", ret.Data.Error) 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/rs_batch_change_type.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | 29 | //每个batch的操作数量不可以超过1000个,如果总数量超过1000,需要分批发送 30 | 31 | chtypeKeys := map[string]int{ 32 | "github1.png": 1, 33 | "github2.png": 1, 34 | "github3.png": 1, 35 | "github4.png": 1, 36 | "github5.png": 1, 37 | } 38 | chtypeOps := make([]string, 0, len(chtypeKeys)) 39 | for key, fileType := range chtypeKeys { 40 | chtypeOps = append(chtypeOps, storage.URIChangeType(bucket, key, fileType)) 41 | } 42 | rets, err := bucketManager.Batch(chtypeOps) 43 | if err != nil { 44 | // 遇到错误 45 | if _, ok := err.(*storage.ErrorInfo); ok { 46 | for _, ret := range rets { 47 | // 200 为成功 48 | fmt.Printf("%d\n", ret.Code) 49 | if ret.Code != 200 { 50 | fmt.Printf("%s\n", ret.Data.Error) 51 | } 52 | } 53 | } else { 54 | fmt.Printf("batch error, %s", err) 55 | } 56 | } else { 57 | // 完全成功 58 | for _, ret := range rets { 59 | // 200 为成功 60 | fmt.Printf("%d\n", ret.Code) 61 | if ret.Code != 200 { 62 | fmt.Printf("%s\n", ret.Data.Error) 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /examples/rs_batch_delete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | 29 | //每个batch的操作数量不可以超过1000个,如果总数量超过1000,需要分批发送 30 | keys := []string{ 31 | "github1.png", 32 | "github2.png", 33 | "github3.png", 34 | "github4.png", 35 | "github5.png", 36 | } 37 | deleteOps := make([]string, 0, len(keys)) 38 | for _, key := range keys { 39 | deleteOps = append(deleteOps, storage.URIDelete(bucket, key)) 40 | } 41 | rets, err := bucketManager.Batch(deleteOps) 42 | if err != nil { 43 | // 遇到错误 44 | if _, ok := err.(*storage.ErrorInfo); ok { 45 | for _, ret := range rets { 46 | // 200 为成功 47 | fmt.Printf("%d\n", ret.Code) 48 | if ret.Code != 200 { 49 | fmt.Printf("%s\n", ret.Data.Error) 50 | } 51 | } 52 | } else { 53 | fmt.Printf("batch error, %s", err) 54 | } 55 | } else { 56 | // 完全成功 57 | for _, ret := range rets { 58 | // 200 为成功 59 | fmt.Printf("%d\n", ret.Code) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/rs_batch_deleteAfterDays.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | 29 | //每个batch的操作数量不可以超过1000个,如果总数量超过1000,需要分批发送 30 | expireKeys := map[string]int{ 31 | "github1.png": 7, 32 | "github2.png": 8, 33 | "github3.png": 9, 34 | "github4.png": 10, 35 | "github5.png": 11, 36 | } 37 | expireOps := make([]string, 0, len(expireKeys)) 38 | for key, expire := range expireKeys { 39 | expireOps = append(expireOps, storage.URIDeleteAfterDays(bucket, key, expire)) 40 | } 41 | rets, err := bucketManager.Batch(expireOps) 42 | if err != nil { 43 | // 遇到错误 44 | if _, ok := err.(*storage.ErrorInfo); ok { 45 | for _, ret := range rets { 46 | // 200 为成功 47 | fmt.Printf("%d\n", ret.Code) 48 | if ret.Code != 200 { 49 | fmt.Printf("%s\n", ret.Data.Error) 50 | } 51 | } 52 | } else { 53 | fmt.Printf("batch error, %s", err) 54 | } 55 | } else { 56 | // 完全成功 57 | for _, ret := range rets { 58 | // 200 为成功 59 | fmt.Printf("%d\n", ret.Code) 60 | if ret.Code != 200 { 61 | fmt.Printf("%s\n", ret.Data.Error) 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/rs_batch_stat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | 29 | //每个batch的操作数量不可以超过1000个,如果总数量超过1000,需要分批发送 30 | keys := []string{ 31 | "github1.png", 32 | "github2.png", 33 | "github3.png", 34 | "github4.png", 35 | "github5.png", 36 | } 37 | statOps := make([]string, 0, len(keys)) 38 | for _, key := range keys { 39 | statOps = append(statOps, storage.URIStat(bucket, key)) 40 | } 41 | rets, err := bucketManager.Batch(statOps) 42 | if err != nil { 43 | // 遇到错误 44 | if _, ok := err.(*storage.ErrorInfo); ok { 45 | for _, ret := range rets { 46 | // 200 为成功 47 | fmt.Printf("%d\n", ret.Code) 48 | if ret.Code != 200 { 49 | fmt.Printf("%s\n", ret.Data.Error) 50 | } else { 51 | fmt.Printf("%v\n", ret.Data) 52 | } 53 | } 54 | } else { 55 | fmt.Printf("batch error, %s", err) 56 | } 57 | } else { 58 | // 完全成功 59 | for _, ret := range rets { 60 | // 200 为成功 61 | fmt.Printf("%d\n", ret.Code) 62 | fmt.Printf("%v\n", ret.Data) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/rs_change_mime.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | 29 | key := "github.png" 30 | newMime := "image/x-png" 31 | err := bucketManager.ChangeMime(bucket, key, newMime) 32 | if err != nil { 33 | fmt.Println(err) 34 | return 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/rs_change_type.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | 29 | key := "github.png" 30 | fileType := 1 // 0 表示普通存储,1表示低频存储 31 | err := bucketManager.ChangeType(bucket, key, fileType) 32 | if err != nil { 33 | fmt.Println(err) 34 | return 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/rs_copy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | 29 | srcBucket := bucket 30 | srcKey := "github.png" 31 | //目标空间可以和源空间相同,但是不能为跨机房的空间 32 | destBucket := srcBucket 33 | //目标文件名可以和源文件名相同,也可以不同 34 | destKey := "github-new.png" 35 | //如果目标文件存在,是否强制覆盖,如果不覆盖,默认返回614 file exists 36 | force := false 37 | err := bucketManager.Copy(srcBucket, srcKey, destBucket, destKey, force) 38 | if err != nil { 39 | fmt.Println(err) 40 | return 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/rs_delete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | 29 | key := "github.png" 30 | err := bucketManager.Delete(bucket, key) 31 | if err != nil { 32 | fmt.Println(err) 33 | return 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/rs_deleteAfterDays.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | 29 | key := "github.png" 30 | days := 7 31 | err := bucketManager.DeleteAfterDays(bucket, key, days) 32 | if err != nil { 33 | fmt.Println(err) 34 | return 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/rs_download.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | "github.com/qiniu/go-sdk/v7/auth" 9 | "github.com/qiniu/go-sdk/v7/storage" 10 | ) 11 | 12 | var ( 13 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 14 | secretKey = os.Getenv("QINIU_SECRET_KEY") 15 | bucket = os.Getenv("QINIU_TEST_BUCKET") 16 | ) 17 | 18 | func main() { 19 | mac := auth.New(accessKey, secretKey) 20 | 21 | // 公开空间访问 22 | domain := "https://image.example.com" 23 | key := "这是一个测试文件.jpg" 24 | publicAccessURL := storage.MakePublicURL(domain, key) 25 | fmt.Println(publicAccessURL) 26 | 27 | // 私有空间访问 28 | domain = "https://image.example.com" 29 | key = "这是一个测试文件.jpg" 30 | deadline := time.Now().Add(time.Second * 3600).Unix() //1小时有效期 31 | privateAccessURL := storage.MakePrivateURL(mac, domain, key, deadline) 32 | fmt.Println(privateAccessURL) 33 | } 34 | -------------------------------------------------------------------------------- /examples/rs_fetch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | 29 | resURL := "http://devtools.qiniu.com/qiniu.png" 30 | // 指定保存的key 31 | fetchRet, err := bucketManager.Fetch(resURL, bucket, "qiniu.png") 32 | if err != nil { 33 | fmt.Println("fetch error,", err) 34 | } else { 35 | fmt.Println(fetchRet.String()) 36 | } 37 | // 不指定保存的key,默认用文件hash作为文件名 38 | fetchRet, err = bucketManager.FetchWithoutKey(resURL, bucket) 39 | if err != nil { 40 | fmt.Println("fetch error,", err) 41 | } else { 42 | fmt.Println(fetchRet.String()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/rs_list_bucket.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | "strings" 10 | ) 11 | 12 | var ( 13 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 14 | secretKey = os.Getenv("QINIU_SECRET_KEY") 15 | bucket = os.Getenv("QINIU_TEST_BUCKET") 16 | ) 17 | 18 | func main() { 19 | mac := auth.New(accessKey, secretKey) 20 | 21 | cfg := storage.Config{ 22 | // 是否使用https域名进行资源管理 23 | UseHTTPS: false, 24 | } 25 | // 指定空间所在的区域,如果不指定将自动探测 26 | // 如果没有特殊需求,默认不需要指定 27 | //cfg.Zone=&storage.ZoneHuabei 28 | bucketManager := storage.NewBucketManager(mac, &cfg) 29 | 30 | //列举所有文件 31 | prefix, delimiter, marker := "", "", "" 32 | entries, err := bucketManager.ListBucket(bucket, prefix, delimiter, marker) 33 | if err != nil { 34 | fmt.Fprintf(os.Stderr, "ListBucket: %v\n", err) 35 | os.Exit(1) 36 | } 37 | for listItem := range entries { 38 | fmt.Println(listItem.Marker) 39 | fmt.Println(listItem.Item) 40 | fmt.Println(listItem.Dir) 41 | fmt.Println(strings.Repeat("-", 30)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/rs_list_bucket_context.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "context" 8 | "github.com/qiniu/go-sdk/v7/auth" 9 | "github.com/qiniu/go-sdk/v7/storage" 10 | "strings" 11 | ) 12 | 13 | var ( 14 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 15 | secretKey = os.Getenv("QINIU_SECRET_KEY") 16 | bucket = os.Getenv("QINIU_TEST_BUCKET") 17 | ) 18 | 19 | func main() { 20 | mac := auth.New(accessKey, secretKey) 21 | 22 | cfg := storage.Config{ 23 | // 是否使用https域名进行资源管理 24 | UseHTTPS: false, 25 | } 26 | // 指定空间所在的区域,如果不指定将自动探测 27 | // 如果没有特殊需求,默认不需要指定 28 | //cfg.Zone=&storage.ZoneHuabei 29 | bucketManager := storage.NewBucketManager(mac, &cfg) 30 | 31 | //列举所有文件 32 | prefix, delimiter, marker := "", "", "" 33 | 34 | ctx, cancelFunc := context.WithCancel(context.Background()) 35 | entries, err := bucketManager.ListBucketContext(ctx, bucket, prefix, delimiter, marker) 36 | if err != nil { 37 | fmt.Fprintf(os.Stderr, "ListBucket: %v\n", err) 38 | os.Exit(1) 39 | } 40 | 41 | ind := 0 42 | for listItem := range entries { 43 | if ind > 3 { 44 | fmt.Println("calling cancelFunc()") 45 | cancelFunc() 46 | } 47 | fmt.Println(listItem.Marker) 48 | fmt.Println(listItem.Item) 49 | fmt.Println(listItem.Dir) 50 | fmt.Println(strings.Repeat("-", 30)) 51 | ind++ 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/rs_list_files.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | 29 | limit := 1000 30 | prefix := "qiniu" 31 | delimiter := "" 32 | //初始列举marker为空 33 | marker := "" 34 | for { 35 | entries, _, nextMarker, hashNext, err := bucketManager.ListFiles(bucket, prefix, delimiter, marker, limit) 36 | if err != nil { 37 | fmt.Println("list error,", err) 38 | break 39 | } 40 | //print entries 41 | for _, entry := range entries { 42 | fmt.Println(entry.Key) 43 | } 44 | if hashNext { 45 | marker = nextMarker 46 | } else { 47 | //list end 48 | break 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/rs_move.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | 29 | srcBucket := bucket 30 | srcKey := "github.png" 31 | //目标空间可以和源空间相同,但是不能为跨机房的空间 32 | destBucket := srcBucket 33 | //目标文件名可以和源文件名相同,也可以不同 34 | destKey := "github-new.png" 35 | //如果目标文件存在,是否强制覆盖,如果不覆盖,默认返回614 file exists 36 | force := false 37 | err := bucketManager.Move(srcBucket, srcKey, destBucket, destKey, force) 38 | if err != nil { 39 | fmt.Println(err) 40 | return 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/rs_prefetch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | 29 | key := "qiniu.png" 30 | err := bucketManager.Prefetch(bucket, key) 31 | if err != nil { 32 | fmt.Println("fetch error,", err) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/rs_stat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/qiniu/go-sdk/v7/auth" 8 | "github.com/qiniu/go-sdk/v7/storage" 9 | ) 10 | 11 | var ( 12 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 13 | secretKey = os.Getenv("QINIU_SECRET_KEY") 14 | bucket = os.Getenv("QINIU_TEST_BUCKET") 15 | ) 16 | 17 | func main() { 18 | mac := auth.New(accessKey, secretKey) 19 | 20 | cfg := storage.Config{ 21 | // 是否使用https域名进行资源管理 22 | UseHTTPS: false, 23 | } 24 | // 指定空间所在的区域,如果不指定将自动探测 25 | // 如果没有特殊需求,默认不需要指定 26 | //cfg.Zone=&storage.ZoneHuabei 27 | bucketManager := storage.NewBucketManager(mac, &cfg) 28 | 29 | key := "qiniu.png" 30 | fileInfo, sErr := bucketManager.Stat(bucket, key) 31 | if sErr != nil { 32 | fmt.Println(sErr) 33 | return 34 | } 35 | fmt.Println(fileInfo.String()) 36 | //可以解析文件的PutTime 37 | fmt.Println(storage.ParsePutTime(fileInfo.PutTime)) 38 | } 39 | -------------------------------------------------------------------------------- /examples/video_pfop.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/qiniu/go-sdk/v7/auth" 10 | "github.com/qiniu/go-sdk/v7/storage" 11 | ) 12 | 13 | var ( 14 | accessKey = os.Getenv("QINIU_ACCESS_KEY") 15 | secretKey = os.Getenv("QINIU_SECRET_KEY") 16 | bucket = os.Getenv("QINIU_TEST_BUCKET") 17 | // 数据处理的私有队列,必须指定以保障处理速度 18 | pipeline = os.Getenv("QINIU_TEST_PIPELINE") 19 | ) 20 | 21 | func main() { 22 | mac := auth.New(accessKey, secretKey) 23 | cfg := storage.Config{ 24 | UseHTTPS: true, 25 | } 26 | // 指定空间所在的区域,如果不指定将自动探测 27 | // 如果没有特殊需求,默认不需要指定 28 | //cfg.Zone=&storage.ZoneHuabei 29 | operationManager := storage.NewOperationManager(mac, &cfg) 30 | key := "qiniu.mp4" 31 | saveBucket := bucket 32 | // 处理指令集合 33 | fopAvthumb := fmt.Sprintf("avthumb/mp4/s/480x320/vb/500k|saveas/%s", 34 | storage.EncodedEntry(saveBucket, "pfop_test_qiniu.mp4")) 35 | fopVframe := fmt.Sprintf("vframe/jpg/offset/10|saveas/%s", 36 | storage.EncodedEntry(saveBucket, "pfop_test_qiniu.jpg")) 37 | fopVsample := fmt.Sprintf("vsample/jpg/interval/20/pattern/%s", 38 | base64.URLEncoding.EncodeToString([]byte("pfop_test_$(count).jpg"))) 39 | fopBatch := []string{fopAvthumb, fopVframe, fopVsample} 40 | fops := strings.Join(fopBatch, ";") 41 | // 强制重新执行数据处理任务 42 | force := true 43 | // 数据处理指令全部完成之后,通知该地址 44 | notifyURL := "http://api.example.com/pfop/callback" 45 | // 数据处理的私有队列,必须指定以保障处理速度 46 | persistentId, err := operationManager.Pfop(bucket, key, fops, pipeline, notifyURL, force) 47 | if err != nil { 48 | fmt.Println(err) 49 | return 50 | } 51 | fmt.Println(persistentId) 52 | } 53 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/qiniu/go-sdk/v7 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/BurntSushi/toml v1.3.2 7 | github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 8 | github.com/dave/jennifer v1.6.1 9 | github.com/elastic/go-sysinfo v1.0.2 10 | github.com/gammazero/toposort v0.1.1 11 | github.com/go-playground/universal-translator v0.18.0 // indirect 12 | github.com/go-playground/validator/v10 v10.7.0 13 | github.com/gofrs/flock v0.8.1 14 | github.com/gorilla/mux v1.8.1 15 | github.com/iancoleman/strcase v0.3.0 16 | github.com/jessevdk/go-flags v1.4.0 17 | github.com/kr/pretty v0.3.0 // indirect 18 | github.com/leodido/go-urn v1.2.1 // indirect 19 | github.com/qiniu/dyn v1.3.0 20 | github.com/rogpeppe/go-internal v1.8.0 // indirect 21 | github.com/stretchr/testify v1.6.1 22 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 23 | golang.org/x/text v0.3.7 // indirect 24 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 25 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b 26 | modernc.org/fileutil v1.0.0 27 | ) 28 | -------------------------------------------------------------------------------- /iam/apis/apis.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | package apis 4 | 5 | import ( 6 | httpclient "github.com/qiniu/go-sdk/v7/storagev2/http_client" 7 | region "github.com/qiniu/go-sdk/v7/storagev2/region" 8 | ) 9 | 10 | // API 客户端 11 | type Iam struct { 12 | client *httpclient.Client 13 | } 14 | 15 | // 创建 API 客户端 16 | func NewIam(options *httpclient.Options) *Iam { 17 | return &Iam{client: httpclient.NewClient(options)} 18 | } 19 | 20 | // API 客户端选项 21 | type Options struct { 22 | OverwrittenBucketHosts region.EndpointsProvider 23 | OverwrittenBucketName string 24 | OverwrittenEndpoints region.EndpointsProvider 25 | OverwrittenRegion region.RegionsProvider 26 | OnRequestProgress func(uint64, uint64) 27 | } 28 | -------------------------------------------------------------------------------- /iam/apis/delete_group/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 删除用户分组 4 | package delete_group 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Alias string // 用户分组别名 11 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 12 | } 13 | 14 | // 获取 API 所用的响应 15 | type Response struct{} 16 | -------------------------------------------------------------------------------- /iam/apis/delete_group_policies/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 从用户分组中删除授权策略 4 | package delete_group_policies 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | errors "github.com/qiniu/go-sdk/v7/storagev2/errors" 10 | ) 11 | 12 | // 调用 API 所用的请求 13 | type Request struct { 14 | Alias string // 用户分组别名 15 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 16 | PolicyAliases PolicyAliases // 授权策略别名集合 17 | } 18 | 19 | // 从用户分组中删除的授权策略别名集合 20 | type PolicyAliases = []string 21 | 22 | // 从用户分组中删除授权策略参数 23 | type DeletedGroupIamPoliciesParam = Request 24 | type jsonRequest struct { 25 | PolicyAliases PolicyAliases `json:"policy_aliases"` // 授权策略别名集合 26 | } 27 | 28 | func (j *Request) MarshalJSON() ([]byte, error) { 29 | if err := j.validate(); err != nil { 30 | return nil, err 31 | } 32 | return json.Marshal(&jsonRequest{PolicyAliases: j.PolicyAliases}) 33 | } 34 | func (j *Request) UnmarshalJSON(data []byte) error { 35 | var nj jsonRequest 36 | if err := json.Unmarshal(data, &nj); err != nil { 37 | return err 38 | } 39 | j.PolicyAliases = nj.PolicyAliases 40 | return nil 41 | } 42 | func (j *Request) validate() error { 43 | if len(j.PolicyAliases) == 0 { 44 | return errors.MissingRequiredFieldError{Name: "PolicyAliases"} 45 | } 46 | return nil 47 | } 48 | 49 | // 获取 API 所用的响应 50 | type Response struct{} 51 | -------------------------------------------------------------------------------- /iam/apis/delete_group_users/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 从用户分组中删除 IAM 子账号 4 | package delete_group_users 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | errors "github.com/qiniu/go-sdk/v7/storagev2/errors" 10 | ) 11 | 12 | // 调用 API 所用的请求 13 | type Request struct { 14 | Alias string // 用户分组别名 15 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 16 | UserAliases UserAliases // IAM 子账号别名集合 17 | } 18 | 19 | // 从用户分组中删除 IAM 子账号别名集合 20 | type UserAliases = []string 21 | 22 | // 从用户分组删除 IAM 子账号参数 23 | type DeletedGroupIamUsersParam = Request 24 | type jsonRequest struct { 25 | UserAliases UserAliases `json:"user_aliases"` // IAM 子账号别名集合 26 | } 27 | 28 | func (j *Request) MarshalJSON() ([]byte, error) { 29 | if err := j.validate(); err != nil { 30 | return nil, err 31 | } 32 | return json.Marshal(&jsonRequest{UserAliases: j.UserAliases}) 33 | } 34 | func (j *Request) UnmarshalJSON(data []byte) error { 35 | var nj jsonRequest 36 | if err := json.Unmarshal(data, &nj); err != nil { 37 | return err 38 | } 39 | j.UserAliases = nj.UserAliases 40 | return nil 41 | } 42 | func (j *Request) validate() error { 43 | if len(j.UserAliases) == 0 { 44 | return errors.MissingRequiredFieldError{Name: "UserAliases"} 45 | } 46 | return nil 47 | } 48 | 49 | // 获取 API 所用的响应 50 | type Response struct{} 51 | -------------------------------------------------------------------------------- /iam/apis/delete_policy/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 删除指定的授权策略 4 | package delete_policy 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Alias string // 授权策略别名 11 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 12 | } 13 | 14 | // 获取 API 所用的响应 15 | type Response struct{} 16 | -------------------------------------------------------------------------------- /iam/apis/delete_user/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 删除 IAM 子账号 4 | package delete_user 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Alias string // 子账号别名 11 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 12 | } 13 | 14 | // 获取 API 所用的响应 15 | type Response struct{} 16 | -------------------------------------------------------------------------------- /iam/apis/delete_user_keypair/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 删除 IAM 子账号密钥 4 | package delete_user_keypair 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Alias string // 子账号别名 11 | AccessKey string // IAM 子账号 Access Key 12 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 13 | } 14 | 15 | // 获取 API 所用的响应 16 | type Response struct{} 17 | -------------------------------------------------------------------------------- /iam/apis/delete_user_policy/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 删除 IAM 子账号特定的授权策略 4 | package delete_user_policy 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | errors "github.com/qiniu/go-sdk/v7/storagev2/errors" 10 | ) 11 | 12 | // 调用 API 所用的请求 13 | type Request struct { 14 | Alias string // 子账号别名 15 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 16 | PolicyAliases PolicyAliases // 授权策略别名集合 17 | } 18 | 19 | // 删除用户的授权策略别名集合 20 | type PolicyAliases = []string 21 | 22 | // 为子账号删除授权策略参数 23 | type DeletedIamUserPoliciesParam = Request 24 | type jsonRequest struct { 25 | PolicyAliases PolicyAliases `json:"policy_aliases"` // 授权策略别名集合 26 | } 27 | 28 | func (j *Request) MarshalJSON() ([]byte, error) { 29 | if err := j.validate(); err != nil { 30 | return nil, err 31 | } 32 | return json.Marshal(&jsonRequest{PolicyAliases: j.PolicyAliases}) 33 | } 34 | func (j *Request) UnmarshalJSON(data []byte) error { 35 | var nj jsonRequest 36 | if err := json.Unmarshal(data, &nj); err != nil { 37 | return err 38 | } 39 | j.PolicyAliases = nj.PolicyAliases 40 | return nil 41 | } 42 | func (j *Request) validate() error { 43 | if len(j.PolicyAliases) == 0 { 44 | return errors.MissingRequiredFieldError{Name: "PolicyAliases"} 45 | } 46 | return nil 47 | } 48 | 49 | // 获取 API 所用的响应 50 | type Response struct{} 51 | -------------------------------------------------------------------------------- /iam/apis/disable_user_keypair/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 禁用 IAM 子账号密钥 4 | package disable_user_keypair 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Alias string // 子账号别名 11 | AccessKey string // IAM 子账号 Access Key 12 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 13 | } 14 | 15 | // 获取 API 所用的响应 16 | type Response struct{} 17 | -------------------------------------------------------------------------------- /iam/apis/enable_user_keypair/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 启用 IAM 子账号密钥 4 | package enable_user_keypair 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Alias string // 子账号别名 11 | AccessKey string // IAM 子账号 Access Key 12 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 13 | } 14 | 15 | // 获取 API 所用的响应 16 | type Response struct{} 17 | -------------------------------------------------------------------------------- /iam/apis/get_services/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 查询 IAM 的服务列表 4 | package get_services 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | errors "github.com/qiniu/go-sdk/v7/storagev2/errors" 10 | ) 11 | 12 | // 调用 API 所用的请求 13 | type Request struct { 14 | Page int64 // 分页页号,从 1 开始,默认 1 15 | PageSize int64 // 分页大小,默认 20,最大 2000 16 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 17 | } 18 | 19 | // 获取 API 所用的响应 20 | type Response struct { 21 | Data Services // 服务列表信息 22 | } 23 | 24 | // 服务列表 25 | type Services = []string 26 | 27 | // 返回的服务列表响应 28 | type GetServicesResp = Response 29 | type jsonResponse struct { 30 | Data Services `json:"data"` // 服务列表信息 31 | } 32 | 33 | func (j *Response) MarshalJSON() ([]byte, error) { 34 | if err := j.validate(); err != nil { 35 | return nil, err 36 | } 37 | return json.Marshal(&jsonResponse{Data: j.Data}) 38 | } 39 | func (j *Response) UnmarshalJSON(data []byte) error { 40 | var nj jsonResponse 41 | if err := json.Unmarshal(data, &nj); err != nil { 42 | return err 43 | } 44 | j.Data = nj.Data 45 | return nil 46 | } 47 | func (j *Response) validate() error { 48 | if len(j.Data) == 0 { 49 | return errors.MissingRequiredFieldError{Name: "Data"} 50 | } 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /iam/apis/get_user_available_services/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 列举子账号可用服务 4 | package get_user_available_services 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | errors "github.com/qiniu/go-sdk/v7/storagev2/errors" 10 | ) 11 | 12 | // 调用 API 所用的请求 13 | type Request struct { 14 | Alias string // 子账号别名 15 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 16 | } 17 | 18 | // 获取 API 所用的响应 19 | type Response struct { 20 | Data Services // IAM 子账号可用服务信息 21 | } 22 | 23 | // 可用服务列表 24 | type Services = []string 25 | 26 | // 返回的 IAM 子账号可用服务列表响应 27 | type GetIamUserAvailableServicesResp = Response 28 | type jsonResponse struct { 29 | Data Services `json:"data"` // IAM 子账号可用服务信息 30 | } 31 | 32 | func (j *Response) MarshalJSON() ([]byte, error) { 33 | if err := j.validate(); err != nil { 34 | return nil, err 35 | } 36 | return json.Marshal(&jsonResponse{Data: j.Data}) 37 | } 38 | func (j *Response) UnmarshalJSON(data []byte) error { 39 | var nj jsonResponse 40 | if err := json.Unmarshal(data, &nj); err != nil { 41 | return err 42 | } 43 | j.Data = nj.Data 44 | return nil 45 | } 46 | func (j *Response) validate() error { 47 | if len(j.Data) == 0 { 48 | return errors.MissingRequiredFieldError{Name: "Data"} 49 | } 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /iam/apis/modify_group_policies/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 添加授权策略到用户分组 4 | package modify_group_policies 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | errors "github.com/qiniu/go-sdk/v7/storagev2/errors" 10 | ) 11 | 12 | // 调用 API 所用的请求 13 | type Request struct { 14 | Alias string // 用户分组别名 15 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 16 | PolicyAliases PolicyAliases // 授权策略别名集合 17 | } 18 | 19 | // 添加给用户分组的授权策略别名集合 20 | type PolicyAliases = []string 21 | 22 | // 为用户分组修改授权策略参数 23 | type ModifiedGroupIamPoliciesParam = Request 24 | type jsonRequest struct { 25 | PolicyAliases PolicyAliases `json:"policy_aliases"` // 授权策略别名集合 26 | } 27 | 28 | func (j *Request) MarshalJSON() ([]byte, error) { 29 | if err := j.validate(); err != nil { 30 | return nil, err 31 | } 32 | return json.Marshal(&jsonRequest{PolicyAliases: j.PolicyAliases}) 33 | } 34 | func (j *Request) UnmarshalJSON(data []byte) error { 35 | var nj jsonRequest 36 | if err := json.Unmarshal(data, &nj); err != nil { 37 | return err 38 | } 39 | j.PolicyAliases = nj.PolicyAliases 40 | return nil 41 | } 42 | func (j *Request) validate() error { 43 | if len(j.PolicyAliases) == 0 { 44 | return errors.MissingRequiredFieldError{Name: "PolicyAliases"} 45 | } 46 | return nil 47 | } 48 | 49 | // 获取 API 所用的响应 50 | type Response struct{} 51 | -------------------------------------------------------------------------------- /iam/apis/modify_group_users/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 添加 IAM 子账号到用户分组 4 | package modify_group_users 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | errors "github.com/qiniu/go-sdk/v7/storagev2/errors" 10 | ) 11 | 12 | // 调用 API 所用的请求 13 | type Request struct { 14 | Alias string // 用户分组别名 15 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 16 | UserAliases UserAliases // IAM 子账号别名集合 17 | } 18 | 19 | // 添加给用户分组的 IAM 子账号别名集合 20 | type UserAliases = []string 21 | 22 | // 为用户分组修改 IAM 子账号参数 23 | type ModifiedGroupIamUsersParam = Request 24 | type jsonRequest struct { 25 | UserAliases UserAliases `json:"user_aliases"` // IAM 子账号别名集合 26 | } 27 | 28 | func (j *Request) MarshalJSON() ([]byte, error) { 29 | if err := j.validate(); err != nil { 30 | return nil, err 31 | } 32 | return json.Marshal(&jsonRequest{UserAliases: j.UserAliases}) 33 | } 34 | func (j *Request) UnmarshalJSON(data []byte) error { 35 | var nj jsonRequest 36 | if err := json.Unmarshal(data, &nj); err != nil { 37 | return err 38 | } 39 | j.UserAliases = nj.UserAliases 40 | return nil 41 | } 42 | func (j *Request) validate() error { 43 | if len(j.UserAliases) == 0 { 44 | return errors.MissingRequiredFieldError{Name: "UserAliases"} 45 | } 46 | return nil 47 | } 48 | 49 | // 获取 API 所用的响应 50 | type Response struct{} 51 | -------------------------------------------------------------------------------- /iam/apis/modify_user_policies/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 为子账号添加授权策略 4 | package modify_user_policies 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | errors "github.com/qiniu/go-sdk/v7/storagev2/errors" 10 | ) 11 | 12 | // 调用 API 所用的请求 13 | type Request struct { 14 | Alias string // 子账号别名 15 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 16 | PolicyAliases PolicyAliases // 授权策略别名集合 17 | } 18 | 19 | // 添加给用户的授权策略别名集合 20 | type PolicyAliases = []string 21 | 22 | // 为子账号修改授权策略参数 23 | type ModifiedIamUserPoliciesParam = Request 24 | type jsonRequest struct { 25 | PolicyAliases PolicyAliases `json:"policy_aliases"` // 授权策略别名集合 26 | } 27 | 28 | func (j *Request) MarshalJSON() ([]byte, error) { 29 | if err := j.validate(); err != nil { 30 | return nil, err 31 | } 32 | return json.Marshal(&jsonRequest{PolicyAliases: j.PolicyAliases}) 33 | } 34 | func (j *Request) UnmarshalJSON(data []byte) error { 35 | var nj jsonRequest 36 | if err := json.Unmarshal(data, &nj); err != nil { 37 | return err 38 | } 39 | j.PolicyAliases = nj.PolicyAliases 40 | return nil 41 | } 42 | func (j *Request) validate() error { 43 | if len(j.PolicyAliases) == 0 { 44 | return errors.MissingRequiredFieldError{Name: "PolicyAliases"} 45 | } 46 | return nil 47 | } 48 | 49 | // 获取 API 所用的响应 50 | type Response struct{} 51 | -------------------------------------------------------------------------------- /iam/apis/update_group_policies/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 为用户分组重新分配授权策略 4 | package update_group_policies 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | ) 10 | 11 | // 调用 API 所用的请求 12 | type Request struct { 13 | Alias string // 用户分组别名 14 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 15 | PolicyAliases PolicyAliases // 授权策略别名集合 16 | } 17 | 18 | // 为用户分组重新分配的授权策略别名集合 19 | type PolicyAliases = []string 20 | 21 | // 为用户分组重新分配授权策略参数 22 | type UpdatedGroupIamPoliciesParam = Request 23 | type jsonRequest struct { 24 | PolicyAliases PolicyAliases `json:"policy_aliases,omitempty"` // 授权策略别名集合 25 | } 26 | 27 | func (j *Request) MarshalJSON() ([]byte, error) { 28 | if err := j.validate(); err != nil { 29 | return nil, err 30 | } 31 | return json.Marshal(&jsonRequest{PolicyAliases: j.PolicyAliases}) 32 | } 33 | func (j *Request) UnmarshalJSON(data []byte) error { 34 | var nj jsonRequest 35 | if err := json.Unmarshal(data, &nj); err != nil { 36 | return err 37 | } 38 | j.PolicyAliases = nj.PolicyAliases 39 | return nil 40 | } 41 | func (j *Request) validate() error { 42 | return nil 43 | } 44 | 45 | // 获取 API 所用的响应 46 | type Response struct{} 47 | -------------------------------------------------------------------------------- /iam/apis/update_group_users/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 为用户分组中重新分配 IAM 子账号 4 | package update_group_users 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | ) 10 | 11 | // 调用 API 所用的请求 12 | type Request struct { 13 | Alias string // 用户分组别名 14 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 15 | UserAliases UserAliases // IAM 子账号别名集合 16 | } 17 | 18 | // 为用户分组重新分配 IAM 子账号别名集合 19 | type UserAliases = []string 20 | 21 | // 为用户分组重新分配 IAM 子账号参数 22 | type UpdatedGroupIamUsersParam = Request 23 | type jsonRequest struct { 24 | UserAliases UserAliases `json:"user_aliases,omitempty"` // IAM 子账号别名集合 25 | } 26 | 27 | func (j *Request) MarshalJSON() ([]byte, error) { 28 | if err := j.validate(); err != nil { 29 | return nil, err 30 | } 31 | return json.Marshal(&jsonRequest{UserAliases: j.UserAliases}) 32 | } 33 | func (j *Request) UnmarshalJSON(data []byte) error { 34 | var nj jsonRequest 35 | if err := json.Unmarshal(data, &nj); err != nil { 36 | return err 37 | } 38 | j.UserAliases = nj.UserAliases 39 | return nil 40 | } 41 | func (j *Request) validate() error { 42 | return nil 43 | } 44 | 45 | // 获取 API 所用的响应 46 | type Response struct{} 47 | -------------------------------------------------------------------------------- /iam/apis/update_policy_groups/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 重新分配用户分组给指定策略 4 | package update_policy_groups 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | ) 10 | 11 | // 调用 API 所用的请求 12 | type Request struct { 13 | Alias string // 子账号别名 14 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 15 | GroupAliases GroupAliases // 分组别名集合 16 | } 17 | 18 | // 重新分配给授权策略的分组别名集合 19 | type GroupAliases = []string 20 | 21 | // 为授权策略重新分配分组参数 22 | type UpdatedPolicyGroupsParam = Request 23 | type jsonRequest struct { 24 | GroupAliases GroupAliases `json:"group_aliases,omitempty"` // 分组别名集合 25 | } 26 | 27 | func (j *Request) MarshalJSON() ([]byte, error) { 28 | if err := j.validate(); err != nil { 29 | return nil, err 30 | } 31 | return json.Marshal(&jsonRequest{GroupAliases: j.GroupAliases}) 32 | } 33 | func (j *Request) UnmarshalJSON(data []byte) error { 34 | var nj jsonRequest 35 | if err := json.Unmarshal(data, &nj); err != nil { 36 | return err 37 | } 38 | j.GroupAliases = nj.GroupAliases 39 | return nil 40 | } 41 | func (j *Request) validate() error { 42 | return nil 43 | } 44 | 45 | // 获取 API 所用的响应 46 | type Response struct{} 47 | -------------------------------------------------------------------------------- /iam/apis/update_policy_users/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 重新分配用户给指定授权策略 4 | package update_policy_users 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | ) 10 | 11 | // 调用 API 所用的请求 12 | type Request struct { 13 | Alias string // 授权策略分组别名 14 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 15 | UserAliases UserAliases // IAM 子账号别名集合 16 | } 17 | 18 | // 为授权策略重新分配 IAM 子账号别名集合 19 | type UserAliases = []string 20 | 21 | // 为授权策略重新分配 IAM 子账号参数 22 | type UpdatedPolicyIamUsersParam = Request 23 | type jsonRequest struct { 24 | UserAliases UserAliases `json:"user_aliases,omitempty"` // IAM 子账号别名集合 25 | } 26 | 27 | func (j *Request) MarshalJSON() ([]byte, error) { 28 | if err := j.validate(); err != nil { 29 | return nil, err 30 | } 31 | return json.Marshal(&jsonRequest{UserAliases: j.UserAliases}) 32 | } 33 | func (j *Request) UnmarshalJSON(data []byte) error { 34 | var nj jsonRequest 35 | if err := json.Unmarshal(data, &nj); err != nil { 36 | return err 37 | } 38 | j.UserAliases = nj.UserAliases 39 | return nil 40 | } 41 | func (j *Request) validate() error { 42 | return nil 43 | } 44 | 45 | // 获取 API 所用的响应 46 | type Response struct{} 47 | -------------------------------------------------------------------------------- /iam/apis/update_user_groups/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 为用户重新分配分组 4 | package update_user_groups 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | ) 10 | 11 | // 调用 API 所用的请求 12 | type Request struct { 13 | Alias string // 子账号别名 14 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 15 | GroupAliases GroupAliases // 分组别名集合 16 | } 17 | 18 | // 重新分配给用户的分组别名集合 19 | type GroupAliases = []string 20 | 21 | // 为用户重新分配分组参数 22 | type UpdatedIamUserGroupsParam = Request 23 | type jsonRequest struct { 24 | GroupAliases GroupAliases `json:"group_aliases,omitempty"` // 分组别名集合 25 | } 26 | 27 | func (j *Request) MarshalJSON() ([]byte, error) { 28 | if err := j.validate(); err != nil { 29 | return nil, err 30 | } 31 | return json.Marshal(&jsonRequest{GroupAliases: j.GroupAliases}) 32 | } 33 | func (j *Request) UnmarshalJSON(data []byte) error { 34 | var nj jsonRequest 35 | if err := json.Unmarshal(data, &nj); err != nil { 36 | return err 37 | } 38 | j.GroupAliases = nj.GroupAliases 39 | return nil 40 | } 41 | func (j *Request) validate() error { 42 | return nil 43 | } 44 | 45 | // 获取 API 所用的响应 46 | type Response struct{} 47 | -------------------------------------------------------------------------------- /iam/apis/update_user_policies/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 为子账号重新分配授权策略 4 | package update_user_policies 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | ) 10 | 11 | // 调用 API 所用的请求 12 | type Request struct { 13 | Alias string // 子账号别名 14 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 15 | PolicyAliases PolicyAliases // 授权策略别名集合 16 | } 17 | 18 | // 重新分配给用户的授权策略别名集合 19 | type PolicyAliases = []string 20 | 21 | // 为子账号重新分配授权策略参数 22 | type UpdatedIamUserPoliciesParam = Request 23 | type jsonRequest struct { 24 | PolicyAliases PolicyAliases `json:"policy_aliases,omitempty"` // 授权策略别名集合 25 | } 26 | 27 | func (j *Request) MarshalJSON() ([]byte, error) { 28 | if err := j.validate(); err != nil { 29 | return nil, err 30 | } 31 | return json.Marshal(&jsonRequest{PolicyAliases: j.PolicyAliases}) 32 | } 33 | func (j *Request) UnmarshalJSON(data []byte) error { 34 | var nj jsonRequest 35 | if err := json.Unmarshal(data, &nj); err != nil { 36 | return err 37 | } 38 | j.PolicyAliases = nj.PolicyAliases 39 | return nil 40 | } 41 | func (j *Request) validate() error { 42 | return nil 43 | } 44 | 45 | // 获取 API 所用的响应 46 | type Response struct{} 47 | -------------------------------------------------------------------------------- /iam/doc.go: -------------------------------------------------------------------------------- 1 | // iam 包提供了 IAM 子账号管理等功能。 2 | package iam 3 | 4 | //go:generate go run ../internal/api-generator -- --api-specs=../api-specs/iam --output=apis/ --struct-name=IAM --api-package=github.com/qiniu/go-sdk/v7/iam/apis 5 | //go:generate go build ./apis/... 6 | -------------------------------------------------------------------------------- /internal/api-generator/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "path/filepath" 5 | "strings" 6 | ) 7 | 8 | func extractApiSpecName(name string) string { 9 | baseName := filepath.Base(name) 10 | if index := strings.Index(baseName, "."); index >= 0 { 11 | baseName = baseName[:index] 12 | } 13 | return baseName 14 | } 15 | -------------------------------------------------------------------------------- /internal/clientv2/context.go: -------------------------------------------------------------------------------- 1 | package clientv2 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "sort" 7 | ) 8 | 9 | type intercetorsContextKey struct{} 10 | 11 | func WithInterceptors(req *http.Request, interceptors ...Interceptor) *http.Request { 12 | newInterceptors, ok := req.Context().Value(intercetorsContextKey{}).(interceptorList) 13 | if !ok { 14 | newInterceptors = interceptorList(interceptors) 15 | } else { 16 | newInterceptors = append(newInterceptors, interceptors...) 17 | } 18 | return req.WithContext(context.WithValue(req.Context(), intercetorsContextKey{}, newInterceptors)) 19 | } 20 | 21 | func getIntercetorsFromRequest(req *http.Request) interceptorList { 22 | if req == nil { 23 | return interceptorList{} 24 | } 25 | interceptors, ok := req.Context().Value(intercetorsContextKey{}).(interceptorList) 26 | if !ok { 27 | return interceptorList{} 28 | } 29 | sort.Sort(interceptors) 30 | return interceptors 31 | } 32 | -------------------------------------------------------------------------------- /internal/clientv2/interceptor_anti_hijacking.go: -------------------------------------------------------------------------------- 1 | package clientv2 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/qiniu/go-sdk/v7/storagev2/retrier" 7 | ) 8 | 9 | type antiHijackingInterceptor struct { 10 | } 11 | 12 | func NewAntiHijackingInterceptor() Interceptor { 13 | return &antiHijackingInterceptor{} 14 | } 15 | 16 | func (interceptor *antiHijackingInterceptor) Priority() InterceptorPriority { 17 | return InterceptorPriorityAntiHijacking 18 | } 19 | 20 | func (interceptor *antiHijackingInterceptor) Intercept(req *http.Request, handler Handler) (response *http.Response, err error) { 21 | response, err = handler(req) 22 | if err != nil { 23 | return 24 | } 25 | reqId := response.Header.Get("x-reqid") 26 | log := response.Header.Get("x-log") 27 | if reqId == "" && log == "" { 28 | return nil, retrier.ErrMaliciousResponse 29 | } 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /internal/clientv2/interceptor_auth.go: -------------------------------------------------------------------------------- 1 | package clientv2 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/qiniu/go-sdk/v7/auth" 7 | "github.com/qiniu/go-sdk/v7/storagev2/credentials" 8 | ) 9 | 10 | type AuthConfig struct { 11 | // 鉴权参数 12 | Credentials credentials.CredentialsProvider 13 | // 鉴权类型,不包含上传 14 | TokenType auth.TokenType 15 | // 签名前回调函数 16 | BeforeSign func(*http.Request) 17 | // 签名后回调函数 18 | AfterSign func(*http.Request) 19 | // 签名失败回调函数 20 | SignError func(*http.Request, error) 21 | } 22 | 23 | type authInterceptor struct { 24 | config AuthConfig 25 | } 26 | 27 | func NewAuthInterceptor(config AuthConfig) Interceptor { 28 | return &authInterceptor{ 29 | config: config, 30 | } 31 | } 32 | 33 | func (interceptor *authInterceptor) Priority() InterceptorPriority { 34 | return InterceptorPriorityAuth 35 | } 36 | 37 | func (interceptor *authInterceptor) Intercept(req *http.Request, handler Handler) (*http.Response, error) { 38 | if interceptor == nil || req == nil { 39 | return handler(req) 40 | } 41 | 42 | if credentials := interceptor.config.Credentials; credentials != nil { 43 | creds, err := credentials.Get(req.Context()) 44 | if err != nil { 45 | return nil, err 46 | } 47 | if interceptor.config.BeforeSign != nil { 48 | interceptor.config.BeforeSign(req) 49 | } 50 | if err := creds.AddToken(interceptor.config.TokenType, req); err != nil { 51 | if interceptor.config.SignError != nil { 52 | interceptor.config.SignError(req, err) 53 | } 54 | return nil, err 55 | } else if interceptor.config.AfterSign != nil { 56 | interceptor.config.AfterSign(req) 57 | } 58 | } 59 | 60 | return handler(req) 61 | } 62 | -------------------------------------------------------------------------------- /internal/clientv2/interceptor_auth_test.go: -------------------------------------------------------------------------------- 1 | //go:build unit 2 | // +build unit 3 | 4 | package clientv2 5 | 6 | import ( 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/qiniu/go-sdk/v7/auth" 12 | clientV1 "github.com/qiniu/go-sdk/v7/client" 13 | ) 14 | 15 | func TestAuthInterceptor(t *testing.T) { 16 | clientV1.DebugMode = true 17 | defer func() { 18 | clientV1.DebugMode = false 19 | }() 20 | 21 | interceptor := NewAuthInterceptor(AuthConfig{ 22 | Credentials: auth.New("ak", "sk"), 23 | TokenType: auth.TokenQiniu, 24 | BeforeSign: func(req *http.Request) { 25 | if authorization := req.Header.Get("Authorization"); authorization != "" { 26 | t.Fatal("Authorization header should be empty") 27 | } 28 | }, 29 | AfterSign: func(req *http.Request) { 30 | if authorization := req.Header.Get("Authorization"); authorization == "" { 31 | t.Fatal("Authorization header should not be empty") 32 | } else if !strings.HasPrefix(authorization, "Qiniu ak:") { 33 | t.Fatal("Unexpected Authorization header") 34 | } 35 | }, 36 | }) 37 | c := NewClient(&testClient{statusCode: http.StatusOK}, interceptor) 38 | resp, err := Do(c, RequestParams{ 39 | Context: nil, 40 | Method: RequestMethodGet, 41 | Url: "https://test.qiniu.com/path/123", 42 | Header: nil, 43 | GetBody: nil, 44 | }) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | if resp.StatusCode != http.StatusOK { 49 | t.Fatal("status code not 200") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /internal/clientv2/interceptor_buffer_response.go: -------------------------------------------------------------------------------- 1 | package clientv2 2 | 3 | import "net/http" 4 | 5 | type bufferResponseInterceptor struct { 6 | } 7 | 8 | func NewBufferResponseInterceptor() Interceptor { 9 | return bufferResponseInterceptor{} 10 | } 11 | 12 | func (interceptor bufferResponseInterceptor) Priority() InterceptorPriority { 13 | return InterceptorPriorityBufferResponse 14 | } 15 | 16 | func (interceptor bufferResponseInterceptor) Intercept(req *http.Request, handler Handler) (resp *http.Response, err error) { 17 | toBufferResponse := req.Context().Value(bufferResponseContextKey{}) != nil 18 | resp, err = handler(req) 19 | if err == nil && toBufferResponse { 20 | err = bufferResponse(resp) 21 | } 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /internal/clientv2/interceptor_default_header.go: -------------------------------------------------------------------------------- 1 | package clientv2 2 | 3 | import ( 4 | clientV1 "github.com/qiniu/go-sdk/v7/client" 5 | "github.com/qiniu/go-sdk/v7/conf" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | type defaultHeaderInterceptor struct { 11 | } 12 | 13 | func newDefaultHeaderInterceptor() Interceptor { 14 | return &defaultHeaderInterceptor{} 15 | } 16 | 17 | func (interceptor *defaultHeaderInterceptor) Priority() InterceptorPriority { 18 | return InterceptorPrioritySetHeader 19 | } 20 | 21 | func (interceptor *defaultHeaderInterceptor) Intercept(req *http.Request, handler Handler) (resp *http.Response, err error) { 22 | if interceptor == nil || req == nil { 23 | return handler(req) 24 | } 25 | 26 | if req.Header == nil { 27 | req.Header = http.Header{} 28 | } 29 | 30 | if e := addUseragent(req.Header); e != nil { 31 | return nil, e 32 | } 33 | 34 | if e := addXQiniuDate(req.Header); e != nil { 35 | return nil, e 36 | } 37 | 38 | return handler(req) 39 | } 40 | 41 | func addUseragent(headers http.Header) error { 42 | headers.Set("User-Agent", clientV1.UserAgent) 43 | return nil 44 | } 45 | 46 | func addXQiniuDate(headers http.Header) error { 47 | if conf.IsDisableQiniuTimestampSignature() { 48 | return nil 49 | } 50 | 51 | timeString := time.Now().UTC().Format("20060102T150405Z") 52 | headers.Set("X-Qiniu-Date", timeString) 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /internal/clientv2/interceptor_uptoken.go: -------------------------------------------------------------------------------- 1 | package clientv2 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/qiniu/go-sdk/v7/storagev2/uptoken" 7 | ) 8 | 9 | type UpTokenConfig struct { 10 | // 上传凭证 11 | UpToken uptoken.UpTokenProvider 12 | } 13 | 14 | type uptokenInterceptor struct { 15 | config UpTokenConfig 16 | } 17 | 18 | func NewUpTokenInterceptor(config UpTokenConfig) Interceptor { 19 | return &uptokenInterceptor{ 20 | config: config, 21 | } 22 | } 23 | 24 | func (interceptor *uptokenInterceptor) Priority() InterceptorPriority { 25 | return InterceptorPriorityAuth 26 | } 27 | 28 | func (interceptor *uptokenInterceptor) Intercept(req *http.Request, handler Handler) (*http.Response, error) { 29 | if interceptor == nil || req == nil { 30 | return handler(req) 31 | } 32 | 33 | if upToken := interceptor.config.UpToken; upToken != nil { 34 | if upToken, err := upToken.GetUpToken(req.Context()); err != nil { 35 | return nil, err 36 | } else { 37 | req.Header.Set("Authorization", "UpToken "+upToken) 38 | } 39 | } 40 | 41 | return handler(req) 42 | } 43 | -------------------------------------------------------------------------------- /internal/clientv2/retry_config.go: -------------------------------------------------------------------------------- 1 | package clientv2 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/qiniu/go-sdk/v7/storagev2/backoff" 8 | "github.com/qiniu/go-sdk/v7/storagev2/retrier" 9 | ) 10 | 11 | type RetryConfig struct { 12 | RetryMax int // 最大重试次数 13 | RetryInterval func() time.Duration // 重试时间间隔 v1 14 | Backoff backoff.Backoff // 重试时间间隔 v2,优先级高于 RetryInterval 15 | ShouldRetry func(req *http.Request, resp *http.Response, err error) bool 16 | Retrier retrier.Retrier // 重试器 17 | } 18 | 19 | func (c *RetryConfig) init() { 20 | if c == nil { 21 | return 22 | } 23 | 24 | if c.RetryMax < 0 { 25 | c.RetryMax = 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/configfile/go1.11.go: -------------------------------------------------------------------------------- 1 | //go:build !1.12 2 | // +build !1.12 3 | 4 | package configfile 5 | 6 | import ( 7 | "errors" 8 | "os" 9 | "runtime" 10 | ) 11 | 12 | func userHomeDir() (string, error) { 13 | env, enverr := "HOME", "$HOME" 14 | switch runtime.GOOS { 15 | case "windows": 16 | env, enverr = "USERPROFILE", "%userprofile%" 17 | case "plan9": 18 | env, enverr = "home", "$home" 19 | } 20 | if v := os.Getenv(env); v != "" { 21 | return v, nil 22 | } 23 | // On some geese the home directory is not always defined. 24 | switch runtime.GOOS { 25 | case "android": 26 | return "/sdcard", nil 27 | case "ios": 28 | return "/", nil 29 | } 30 | return "", errors.New(enverr + " is not defined") 31 | } 32 | -------------------------------------------------------------------------------- /internal/configfile/go1.12.go: -------------------------------------------------------------------------------- 1 | //go:build 1.12 2 | // +build 1.12 3 | 4 | package configfile 5 | 6 | import "os" 7 | 8 | func userHomeDir() (string, error) { 9 | return os.UserHomeDir() 10 | } 11 | -------------------------------------------------------------------------------- /internal/context/context_test.go: -------------------------------------------------------------------------------- 1 | //go:build unit 2 | // +build unit 3 | 4 | package context_test 5 | 6 | import ( 7 | offical_context "context" 8 | "io" 9 | "testing" 10 | 11 | "github.com/qiniu/go-sdk/v7/internal/context" 12 | ) 13 | 14 | func TestCause(t *testing.T) { 15 | ctx, cancel := context.WithCancelCause(context.Background()) 16 | 17 | select { 18 | case <-ctx.Done(): 19 | t.Fatalf("Expect ctx.Done() is not done") 20 | default: 21 | } 22 | 23 | if err := ctx.Err(); err != nil { 24 | t.Fatalf("Expect ctx.Err() to return nil, but %s", err) 25 | } 26 | 27 | cancel(io.EOF) 28 | 29 | select { 30 | case <-ctx.Done(): 31 | default: 32 | t.Fatalf("Expect ctx.Done() is done") 33 | } 34 | 35 | if err := ctx.Err(); err != offical_context.Canceled { 36 | t.Fatalf("Expect ctx.Err() to return Canceled, but %s", err) 37 | } 38 | 39 | if c := context.Cause(ctx); c != io.EOF { 40 | t.Fatalf("Expect context.Cause(ctx) to return io.EOF, but %T", c) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /internal/context/go1.19.go: -------------------------------------------------------------------------------- 1 | //go:build !1.20 2 | // +build !1.20 3 | 4 | package context 5 | 6 | import ( 7 | "context" 8 | "sync" 9 | ) 10 | 11 | type ( 12 | Context = context.Context 13 | CancelCauseFunc func(cause error) 14 | cancelCauseErrorKey struct{} 15 | cancelCauseErrorValue struct { 16 | mutex sync.Mutex 17 | err error 18 | } 19 | ) 20 | 21 | func Cause(c Context) error { 22 | if v := c.Value(cancelCauseErrorKey{}); v != nil { 23 | if val, ok := v.(*cancelCauseErrorValue); ok { 24 | val.mutex.Lock() 25 | defer val.mutex.Unlock() 26 | return val.err 27 | } 28 | } 29 | return c.Err() 30 | } 31 | 32 | func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) { 33 | errWrapper := new(cancelCauseErrorValue) 34 | newCtx, cancelFunc := context.WithCancel(context.WithValue(parent, cancelCauseErrorKey{}, errWrapper)) 35 | return newCtx, func(cause error) { 36 | errWrapper.mutex.Lock() 37 | defer errWrapper.mutex.Unlock() 38 | if errWrapper.err == nil { 39 | errWrapper.err = cause 40 | } 41 | cancelFunc() 42 | } 43 | } 44 | 45 | func Background() Context { 46 | return context.Background() 47 | } 48 | -------------------------------------------------------------------------------- /internal/context/go1.20.go: -------------------------------------------------------------------------------- 1 | //go:build 1.20 2 | // +build 1.20 3 | 4 | package context 5 | 6 | import ( 7 | "context" 8 | ) 9 | 10 | type ( 11 | Context = context.Context 12 | CancelCauseFunc = context.CancelCauseFunc 13 | ) 14 | 15 | func Cause(c Context) error { 16 | return Cause(c) 17 | } 18 | 19 | func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) { 20 | return context.WithCancelCause(parent) 21 | } 22 | 23 | func Background() Context { 24 | return context.Background() 25 | } 26 | -------------------------------------------------------------------------------- /internal/dialer/dialer_test.go: -------------------------------------------------------------------------------- 1 | //go:build unit 2 | // +build unit 3 | 4 | package dialer_test 5 | 6 | import ( 7 | "context" 8 | "net" 9 | "os" 10 | "testing" 11 | "time" 12 | 13 | "github.com/qiniu/go-sdk/v7/internal/dialer" 14 | ) 15 | 16 | func TestDialContext(t *testing.T) { 17 | listener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 9901}) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | defer listener.Close() 22 | 23 | now := time.Now() 24 | conn, err := dialer.DialContext(context.Background(), "tcp", []net.IP{net.IPv4(8, 8, 8, 8), net.IPv4(8, 8, 4, 4)}, "9901", dialer.DialOptions{Timeout: 3 * time.Second, KeepAlive: time.Second}) 25 | if err != nil { 26 | if !os.IsTimeout(err) { 27 | t.Fatal("Unexpected error", err) 28 | } 29 | } else { 30 | conn.Close() 31 | t.Fatalf("Returning connection is unexpected") 32 | } 33 | 34 | if time.Since(now) < 3*time.Second || time.Since(now) > 4*time.Second { 35 | t.Fatalf("Unexpected time elapsed") 36 | } 37 | 38 | now = time.Now() 39 | conn, err = dialer.DialContext(context.Background(), "tcp", []net.IP{net.IPv4(8, 8, 8, 8), net.IPv4(8, 8, 4, 4), net.IPv4(127, 0, 0, 1)}, "9901", dialer.DialOptions{Timeout: 3 * time.Second, KeepAlive: time.Second}) 40 | if err != nil { 41 | t.Fatal("Unexpected error", err) 42 | } 43 | conn.Close() 44 | 45 | if time.Since(now) < 2*time.Second || time.Since(now) > 3*time.Second { 46 | t.Fatalf("Unexpected time elapsed") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /internal/freezer/freezer.go: -------------------------------------------------------------------------------- 1 | package freezer 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type Freezer interface { 9 | Available(itemId string) bool 10 | Freeze(itemId string, duration time.Duration) error 11 | Unfreeze(itemId string) error 12 | } 13 | 14 | func New() Freezer { 15 | return &freezer{ 16 | freezerItems: &sync.Map{}, 17 | } 18 | } 19 | 20 | type freezer struct { 21 | freezerItems *sync.Map 22 | } 23 | 24 | func (i *freezer) Available(itemId string) bool { 25 | unfreezeTime, ok := i.freezerItems.Load(itemId) 26 | if !ok { 27 | return true 28 | } 29 | 30 | unfreezeTimeInt64, ok := unfreezeTime.(int64) 31 | if !ok { 32 | return false 33 | } 34 | 35 | timestamp := time.Now().Unix() 36 | return timestamp > unfreezeTimeInt64 37 | } 38 | 39 | func (i *freezer) Freeze(itemId string, duration time.Duration) error { 40 | timestamp := time.Now().Unix() 41 | i.freezerItems.Store(itemId, timestamp+int64(duration/time.Second)) 42 | return nil 43 | } 44 | 45 | func (i *freezer) Unfreeze(itemId string) error { 46 | i.freezerItems.Delete(itemId) 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /internal/hostprovider/host_provider.go: -------------------------------------------------------------------------------- 1 | package hostprovider 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/qiniu/go-sdk/v7/internal/freezer" 8 | ) 9 | 10 | var ( 11 | ErrNoHostFound = errors.New("no host found") 12 | ErrAllHostsFrozen = errors.New("all hosts are frozen") 13 | ) 14 | 15 | type ( 16 | HostProvider interface { 17 | Provider() (string, error) 18 | Freeze(host string, cause error, duration time.Duration) error 19 | } 20 | 21 | arrayHostProvider struct { 22 | hosts []string 23 | freezer freezer.Freezer 24 | lastFreezeErr error 25 | } 26 | ) 27 | 28 | func NewWithHosts(hosts []string) HostProvider { 29 | return &arrayHostProvider{ 30 | hosts: hosts, 31 | freezer: freezer.New(), 32 | } 33 | } 34 | 35 | func (a *arrayHostProvider) Provider() (string, error) { 36 | if len(a.hosts) == 0 { 37 | return "", ErrNoHostFound 38 | } 39 | 40 | for _, host := range a.hosts { 41 | if a.freezer.Available(host) { 42 | return host, nil 43 | } 44 | } 45 | 46 | if a.lastFreezeErr != nil { 47 | return "", a.lastFreezeErr 48 | } else { 49 | return "", ErrAllHostsFrozen 50 | } 51 | } 52 | 53 | func (a *arrayHostProvider) Freeze(host string, cause error, duration time.Duration) error { 54 | if duration <= 0 { 55 | return nil 56 | } 57 | 58 | a.lastFreezeErr = cause 59 | return a.freezer.Freeze(host, duration) 60 | } 61 | -------------------------------------------------------------------------------- /internal/io/compatible.go: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "syscall" 7 | ) 8 | 9 | func MakeReadSeekCloserFromReader(r io.Reader) ReadSeekCloser { 10 | return &readSeekCloserFromReader{r: r} 11 | } 12 | 13 | type readSeekCloserFromReader struct { 14 | r io.Reader 15 | } 16 | 17 | func (r *readSeekCloserFromReader) Read(p []byte) (int, error) { 18 | return r.r.Read(p) 19 | } 20 | 21 | func (r *readSeekCloserFromReader) Seek(offset int64, whence int) (int64, error) { 22 | if seeker, ok := r.r.(io.Seeker); ok { 23 | return seeker.Seek(offset, whence) 24 | } 25 | return 0, syscall.ESPIPE 26 | } 27 | 28 | func (r *readSeekCloserFromReader) Close() error { 29 | return nil 30 | } 31 | 32 | func MakeReadSeekCloserFromLimitedReader(r io.Reader, size int64) ReadSeekCloser { 33 | return &sizedReadSeekCloserFromReader{r: io.LimitedReader{R: r, N: size}, size: size} 34 | } 35 | 36 | type sizedReadSeekCloserFromReader struct { 37 | r io.LimitedReader 38 | size int64 39 | } 40 | 41 | func (r *sizedReadSeekCloserFromReader) Read(p []byte) (int, error) { 42 | return r.r.Read(p) 43 | } 44 | 45 | func (r *sizedReadSeekCloserFromReader) Seek(offset int64, whence int) (int64, error) { 46 | if seeker, ok := r.r.R.(io.ReadSeeker); ok { 47 | newPos, err := seeker.Seek(offset, whence) 48 | if err != nil { 49 | return newPos, err 50 | } 51 | r.r.N = r.size - newPos 52 | return newPos, nil 53 | } 54 | return 0, errors.New("not support seek") 55 | } 56 | 57 | func (r *sizedReadSeekCloserFromReader) Close() error { 58 | return nil 59 | } 60 | 61 | func (r *sizedReadSeekCloserFromReader) DetectLength() (int64, error) { 62 | return r.size, nil 63 | } 64 | -------------------------------------------------------------------------------- /internal/io/go1.15.go: -------------------------------------------------------------------------------- 1 | //go:build !1.16 2 | // +build !1.16 3 | 4 | package io 5 | 6 | import ( 7 | "io" 8 | ) 9 | 10 | type ReadSeekCloser interface { 11 | io.Reader 12 | io.Seeker 13 | io.Closer 14 | } 15 | -------------------------------------------------------------------------------- /internal/io/go1.16.go: -------------------------------------------------------------------------------- 1 | //go:build 1.16 2 | // +build 1.16 3 | 4 | package io 5 | 6 | import ( 7 | "io" 8 | ) 9 | 10 | type ReadSeekCloser = io.ReadSeekCloser 11 | -------------------------------------------------------------------------------- /internal/io/go1.19.go: -------------------------------------------------------------------------------- 1 | //go:build !1.20 2 | // +build !1.20 3 | 4 | package io 5 | 6 | import ( 7 | "errors" 8 | "io" 9 | ) 10 | 11 | var ( 12 | errWhence = errors.New("Seek: invalid whence") 13 | errOffset = errors.New("Seek: invalid offset") 14 | ) 15 | 16 | // An OffsetWriter maps writes at offset base to offset base+off in the underlying writer. 17 | type OffsetWriter struct { 18 | w io.WriterAt 19 | base int64 // the original offset 20 | off int64 // the current offset 21 | } 22 | 23 | // NewOffsetWriter returns an [OffsetWriter] that writes to w 24 | // starting at offset off. 25 | func NewOffsetWriter(w io.WriterAt, off int64) *OffsetWriter { 26 | return &OffsetWriter{w, off, off} 27 | } 28 | 29 | func (o *OffsetWriter) Write(p []byte) (n int, err error) { 30 | n, err = o.w.WriteAt(p, o.off) 31 | o.off += int64(n) 32 | return 33 | } 34 | 35 | func (o *OffsetWriter) WriteAt(p []byte, off int64) (n int, err error) { 36 | if off < 0 { 37 | return 0, errOffset 38 | } 39 | 40 | off += o.base 41 | return o.w.WriteAt(p, off) 42 | } 43 | 44 | func (o *OffsetWriter) Seek(offset int64, whence int) (int64, error) { 45 | switch whence { 46 | default: 47 | return 0, errWhence 48 | case io.SeekStart: 49 | offset += o.base 50 | case io.SeekCurrent: 51 | offset += o.off 52 | } 53 | if offset < o.base { 54 | return 0, errOffset 55 | } 56 | o.off = offset 57 | return offset - o.base, nil 58 | } 59 | -------------------------------------------------------------------------------- /internal/io/go1.20.go: -------------------------------------------------------------------------------- 1 | //go:build 1.20 2 | // +build 1.20 3 | 4 | package io 5 | 6 | import "io" 7 | 8 | type OffsetWriter = io.OffsetWriter 9 | 10 | var NewOffsetWriter = io.NewOffsetWriter 11 | -------------------------------------------------------------------------------- /internal/io/io.go: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "io/ioutil" 7 | "strings" 8 | ) 9 | 10 | func ReadAll(r io.Reader) ([]byte, error) { 11 | switch b := r.(type) { 12 | case *BytesNopCloser: 13 | _, err := b.Seek(0, io.SeekEnd) 14 | return b.Bytes(), err 15 | default: 16 | return ioutil.ReadAll(r) 17 | } 18 | } 19 | 20 | func SinkAll(r io.Reader) (err error) { 21 | switch b := r.(type) { 22 | case *BytesNopCloser: 23 | _, err = b.Seek(0, io.SeekEnd) 24 | case *bytes.Buffer: 25 | b.Truncate(0) 26 | case *bytes.Reader: 27 | _, err = b.Seek(0, io.SeekEnd) 28 | case *strings.Reader: 29 | _, err = b.Seek(0, io.SeekEnd) 30 | default: 31 | _, err = io.Copy(ioutil.Discard, r) 32 | } 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /internal/log/logger.go: -------------------------------------------------------------------------------- 1 | // Package log只是SDK本身自己使用,用来调试代码使用,比如输出HTTP请求和响应信息 2 | package log 3 | 4 | import ( 5 | "io" 6 | "log" 7 | "os" 8 | ) 9 | 10 | type Logger struct { 11 | *log.Logger 12 | level LogLevel 13 | } 14 | 15 | // New 返回一个Logger 指针 16 | func New(out io.Writer, prefix string, flag int, level LogLevel) *Logger { 17 | return &Logger{ 18 | Logger: log.New(out, prefix, flag), 19 | level: level, 20 | } 21 | } 22 | 23 | var ( 24 | std = New(os.Stdout, DebugPrefix, log.LstdFlags, LogDebug) 25 | info = New(os.Stdout, InfoPrefix, log.LstdFlags, LogInfo) 26 | warn = New(os.Stdout, WarnPrefix, log.LstdFlags, LogWarn) 27 | ) 28 | 29 | type LogLevel int 30 | 31 | const ( 32 | // LogDebug 调试模式 33 | LogDebug LogLevel = iota 34 | 35 | // Info 36 | LogInfo 37 | 38 | // Warn 39 | LogWarn 40 | ) 41 | 42 | const ( 43 | InfoPrefix = "[I] " 44 | DebugPrefix = "[D] " 45 | WarnPrefix = "[W] " 46 | ) 47 | 48 | func (l *Logger) Info(v ...interface{}) { 49 | l.output(LogInfo, v...) 50 | } 51 | 52 | func (l *Logger) output(level LogLevel, v ...interface{}) { 53 | if l.level <= level { 54 | l.Logger.Println(v...) 55 | } 56 | } 57 | 58 | func (l *Logger) Debug(v ...interface{}) { 59 | l.output(LogDebug, v...) 60 | } 61 | 62 | func (l *Logger) Warn(v ...interface{}) { 63 | l.output(LogWarn, v...) 64 | } 65 | 66 | func Debug(v ...interface{}) { 67 | std.Debug(v...) 68 | } 69 | 70 | func Info(v ...interface{}) { 71 | info.Info(v...) 72 | } 73 | 74 | func Warn(v ...interface{}) { 75 | warn.Warn(v...) 76 | } 77 | -------------------------------------------------------------------------------- /internal/log/logger_test.go: -------------------------------------------------------------------------------- 1 | //go:build unit 2 | // +build unit 3 | 4 | package log 5 | 6 | import ( 7 | "bytes" 8 | "log" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func TestLogger(t *testing.T) { 14 | b := bytes.Buffer{} 15 | logger := New(&b, InfoPrefix, log.LstdFlags, LogInfo) 16 | 17 | logger.Info("hello world") 18 | 19 | splits := strings.Split(b.String(), " ") 20 | if splits[0] != strings.Trim(InfoPrefix, " ") { 21 | t.Errorf("got prefix: %q, want: %q\n", splits[0], InfoPrefix) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /internal/uplog/dns1.12.go: -------------------------------------------------------------------------------- 1 | //go:build !1.13 2 | // +build !1.13 3 | 4 | package uplog 5 | 6 | import "net" 7 | 8 | func isDnsNotFoundError(dnsError *net.DNSError) bool { 9 | return !dnsError.IsTemporary 10 | } 11 | -------------------------------------------------------------------------------- /internal/uplog/dns1.13.go: -------------------------------------------------------------------------------- 1 | //go:build 1.13 2 | // +build 1.13 3 | 4 | package uplog 5 | 6 | import "net" 7 | 8 | func isDnsNotFoundError(dnsError *net.DNSError) bool { 9 | return dnsError.IsNotFound 10 | } 11 | -------------------------------------------------------------------------------- /internal/uplog/uplog_prod_env.go: -------------------------------------------------------------------------------- 1 | //go:build !unit && !integration 2 | // +build !unit,!integration 3 | 4 | package uplog 5 | 6 | const testRuntime = false 7 | -------------------------------------------------------------------------------- /internal/uplog/uplog_test_env.go: -------------------------------------------------------------------------------- 1 | //go:build unit || integration 2 | // +build unit integration 3 | 4 | package uplog 5 | 6 | const testRuntime = true 7 | -------------------------------------------------------------------------------- /internal/uplog/utils.go: -------------------------------------------------------------------------------- 1 | package uplog 2 | 3 | import "unicode/utf8" 4 | 5 | const maxFieldValueLength = 1024 6 | 7 | func truncate(s string, l int) string { 8 | if len(s) <= l { 9 | return s 10 | } else { 11 | i := l 12 | for ; i < len(s) && !utf8.ValidString(s[:i]); i++ { 13 | } 14 | return s[:i] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /linking/conf.go: -------------------------------------------------------------------------------- 1 | package linking 2 | 3 | // APIHost 指定了 API 服务器的地址 4 | var APIHost = "linking.qiniuapi.com/v1" 5 | 6 | // APIHTTPScheme 指定了在请求 API 服务器时使用的 HTTP 模式. 7 | var APIHTTPScheme = "http://" 8 | -------------------------------------------------------------------------------- /linking/deviceHistoryActivity.go: -------------------------------------------------------------------------------- 1 | package linking 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "net/url" 7 | ) 8 | 9 | // ----------------------------------------------------------------------------- 10 | // 历史记录 11 | type DeviceHistoryItem struct { 12 | LoginAt int64 `json:"loginAt"` 13 | LogoutAt int64 `json:"logoutAt"` 14 | RemoteIp string `json:"remoteIp,omitempty"` 15 | LogoutReason string `json:"logoutReason,omitempty"` 16 | } 17 | 18 | // 查询指定时间段内设备的在线记录 19 | func (manager *Manager) ListDeviceHistoryactivity(appid, dev string, start, end int, marker string, limit int) ([]DeviceHistoryItem, string, error) { 20 | ret := struct { 21 | Items []DeviceHistoryItem `json:"items"` 22 | Marker string `json:"marker"` 23 | }{} 24 | dev = base64.URLEncoding.EncodeToString([]byte(dev)) 25 | query := url.Values{} 26 | if limit > 0 { 27 | setQuery(query, "limit", limit) 28 | } 29 | if marker != "" { 30 | setQuery(query, "marker", marker) 31 | } 32 | setQuery(query, "start", start) 33 | setQuery(query, "end", end) 34 | err := manager.client.Call(context.Background(), &ret, "GET", manager.url("/apps/%s/devices/%s/historyactivity?%v", appid, dev, query.Encode()), nil) 35 | if err != nil { 36 | return nil, "", err 37 | } 38 | return ret.Items, ret.Marker, nil 39 | } 40 | -------------------------------------------------------------------------------- /linking/deviceHistoryActivity_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package linking 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | // 这个测试case需要保证最近1个小时设备有上下线操作 13 | func TestHistoryActivity(t *testing.T) { 14 | if skipTest() { 15 | t.SkipNow() 16 | } 17 | c := getTestManager() 18 | // delete if exist 19 | device := "sdk-testHistActivityDevice" 20 | defer c.DeleteDevice(testApp, device) 21 | dev := &Device{ 22 | Device: device, 23 | SegmentExpireDays: 7, 24 | } 25 | _, err := c.AddDevice(testApp, dev) 26 | noError(t, err) 27 | end := time.Now().Unix() 28 | start := time.Now().Add(-time.Hour).Unix() 29 | segs, marker, err := c.ListDeviceHistoryactivity(testApp, device, int(start), int(end), "", 1000) 30 | noError(t, err) 31 | fmt.Printf("segs = %#v, marker = %#v", segs, marker) 32 | } 33 | -------------------------------------------------------------------------------- /linking/manager.go: -------------------------------------------------------------------------------- 1 | package linking 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/url" 7 | 8 | "github.com/qiniu/go-sdk/v7/auth" 9 | "github.com/qiniu/go-sdk/v7/client" 10 | ) 11 | 12 | // Manager 代表一个 linking 用户的客户端 13 | type Manager struct { 14 | client *client.Client 15 | mac *auth.Credentials 16 | } 17 | 18 | // New 初始化 Client. 19 | func NewManager(mac *auth.Credentials, tr http.RoundTripper) *Manager { 20 | client := client.DefaultClient 21 | client.Transport = newTransport(mac, nil) 22 | return &Manager{ 23 | client: &client, 24 | mac: mac, 25 | } 26 | } 27 | 28 | func setQuery(q url.Values, key string, v interface{}) { 29 | q.Set(key, fmt.Sprint(v)) 30 | } 31 | 32 | func (manager *Manager) url(format string, args ...interface{}) string { 33 | return APIHTTPScheme + APIHost + fmt.Sprintf(format, args...) 34 | } 35 | -------------------------------------------------------------------------------- /linking/manager_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package linking 5 | 6 | import ( 7 | "runtime/debug" 8 | "testing" 9 | 10 | "github.com/qiniu/go-sdk/v7/auth" 11 | ) 12 | 13 | var ( 14 | testAccessKey = "" 15 | testSecretKey = "" 16 | testApp = "2xenzvm26zx2b" 17 | ) 18 | 19 | func skipTest() bool { 20 | return testAccessKey == "" || testSecretKey == "" || testApp == "" 21 | } 22 | 23 | func getTestManager() *Manager { 24 | mac := auth.New(testAccessKey, testSecretKey) 25 | return NewManager(mac, nil) 26 | } 27 | func noError(t *testing.T, err error) { 28 | if err != nil { 29 | debug.PrintStack() 30 | t.Fatalf("should be nil, err = %s", err.Error()) 31 | } 32 | } 33 | 34 | func shouldBeEqual(t *testing.T, a interface{}, b interface{}) { 35 | if a != b { 36 | debug.PrintStack() 37 | t.Fatalf("should be equal, expect = %#v, but got = %#v", a, b) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /linking/util.go: -------------------------------------------------------------------------------- 1 | package linking 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/qiniu/go-sdk/v7/auth" 7 | ) 8 | 9 | // --------------------------------------------------------------------------------------- 10 | 11 | type transport struct { 12 | http.RoundTripper 13 | mac *auth.Credentials 14 | } 15 | 16 | func newTransport(mac *auth.Credentials, tr http.RoundTripper) *transport { 17 | if tr == nil { 18 | tr = http.DefaultTransport 19 | } 20 | return &transport{tr, mac} 21 | } 22 | 23 | func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { 24 | token, err := t.mac.SignRequestV2(req) 25 | if err != nil { 26 | return 27 | } 28 | req.Header.Set("Authorization", "Qiniu "+token) 29 | return t.RoundTripper.RoundTrip(req) 30 | } 31 | -------------------------------------------------------------------------------- /linking/vod_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package linking 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | // 这个测试case需要保证最近1个小时ts文件在上传 13 | func TestSegments(t *testing.T) { 14 | if skipTest() { 15 | t.SkipNow() 16 | } 17 | c := getTestManager() 18 | // delete if exist 19 | device := "sdk-testSegmentsDevice" 20 | defer c.DeleteDevice(testApp, device) 21 | dev := &Device{ 22 | Device: device, 23 | SegmentExpireDays: 7, 24 | } 25 | _, err := c.AddDevice(testApp, dev) 26 | noError(t, err) 27 | 28 | end := time.Now().Unix() 29 | start := time.Now().Add(-time.Hour).Unix() 30 | segs, marker, err := c.Segments(testApp, device, int(start), int(end), "", 1000) 31 | noError(t, err) 32 | fmt.Printf("segs = %#v, marker = %#v", segs, marker) 33 | } 34 | 35 | // 这个测试case需要保证最近1个小时ts文件在上传 36 | func TestSaveas(t *testing.T) { 37 | if skipTest() { 38 | t.SkipNow() 39 | } 40 | c := getTestManager() 41 | // delete if exist 42 | device := "sdk-testSaveasDevice" 43 | defer c.DeleteDevice(testApp, device) 44 | dev := &Device{ 45 | Device: device, 46 | SegmentExpireDays: 7, 47 | } 48 | _, err := c.AddDevice(testApp, dev) 49 | noError(t, err) 50 | 51 | end := time.Now().Unix() 52 | start := time.Now().Add(-time.Hour).Unix() 53 | saveasReply, _ := c.Saveas(testApp, device, int(start), int(end), "testSaveas.mp4", "mp4") 54 | fmt.Printf("saveas reply = %#v", saveasReply) 55 | } 56 | -------------------------------------------------------------------------------- /media/apis/apis.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | package apis 4 | 5 | import ( 6 | httpclient "github.com/qiniu/go-sdk/v7/storagev2/http_client" 7 | region "github.com/qiniu/go-sdk/v7/storagev2/region" 8 | ) 9 | 10 | // API 客户端 11 | type Media struct { 12 | client *httpclient.Client 13 | } 14 | 15 | // 创建 API 客户端 16 | func NewMedia(options *httpclient.Options) *Media { 17 | return &Media{client: httpclient.NewClient(options)} 18 | } 19 | 20 | // API 客户端选项 21 | type Options struct { 22 | OverwrittenBucketHosts region.EndpointsProvider 23 | OverwrittenBucketName string 24 | OverwrittenEndpoints region.EndpointsProvider 25 | OverwrittenRegion region.RegionsProvider 26 | OnRequestProgress func(uint64, uint64) 27 | } 28 | -------------------------------------------------------------------------------- /media/doc.go: -------------------------------------------------------------------------------- 1 | // media 包提供了数据处理等功能。 2 | package media 3 | 4 | //go:generate go run ../internal/api-generator -- --api-specs=../api-specs/media --output=apis/ --struct-name=Media --api-package=github.com/qiniu/go-sdk/v7/media/apis 5 | //go:generate go build ./apis/... 6 | -------------------------------------------------------------------------------- /pili/auth.go: -------------------------------------------------------------------------------- 1 | package pili 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/qiniu/go-sdk/v7/auth" 7 | ) 8 | 9 | type transport struct { 10 | http.RoundTripper 11 | mac *auth.Credentials 12 | } 13 | 14 | // newTransport 将鉴权签算逻辑放在transport中进行处理 15 | func newTransport(mac *auth.Credentials, tr http.RoundTripper) *transport { 16 | if tr == nil { 17 | tr = http.DefaultTransport 18 | } 19 | return &transport{tr, mac} 20 | } 21 | 22 | func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { 23 | token, err := t.mac.SignRequestV2(req) 24 | if err != nil { 25 | return 26 | } 27 | req.Header.Set("Authorization", "Qiniu "+token) 28 | return t.RoundTripper.RoundTrip(req) 29 | } 30 | -------------------------------------------------------------------------------- /pili/error.go: -------------------------------------------------------------------------------- 1 | package pili 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/qiniu/go-sdk/v7/client" 7 | ) 8 | 9 | var ( 10 | ErrInvalidArgs = &client.ErrorInfo{Code: http.StatusBadRequest, Err: "invalid args"} 11 | ErrInvalidRule = &client.ErrorInfo{Code: http.StatusBadRequest, Err: "invalid rule"} 12 | ErrUnsupportedSecurityType = &client.ErrorInfo{Code: http.StatusBadRequest, Err: "unsupported security type"} 13 | ) 14 | 15 | func ErrInfo(code int, err string) *client.ErrorInfo { 16 | return &client.ErrorInfo{ 17 | Code: code, 18 | Err: err, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pili/manager.go: -------------------------------------------------------------------------------- 1 | package pili 2 | 3 | import ( 4 | "github.com/qiniu/go-sdk/v7/auth" 5 | "github.com/qiniu/go-sdk/v7/client" 6 | ) 7 | 8 | // Manager 提供了 Qiniu PILI Service API 相关功能 9 | type Manager struct { 10 | apiHost string 11 | apiHTTPScheme string 12 | client *client.Client 13 | mac *auth.Credentials 14 | } 15 | 16 | // NewManager 用于构建一个新的 Manager 17 | func NewManager(conf ManagerConfig) *Manager { 18 | if len(conf.APIHost) == 0 { 19 | conf.APIHost = APIHost 20 | } 21 | if len(conf.APIHTTPScheme) == 0 { 22 | conf.APIHTTPScheme = APIHTTPScheme 23 | } 24 | SetAppName(conf.AppName) 25 | mac := auth.New(conf.AccessKey, conf.SecretKey) 26 | client := client.DefaultClient 27 | client.Transport = newTransport(mac, conf.Transport) 28 | return &Manager{ 29 | apiHost: conf.APIHost, 30 | apiHTTPScheme: conf.APIHTTPScheme, 31 | mac: mac, 32 | client: &client, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pili/manager_test.go: -------------------------------------------------------------------------------- 1 | //go:build uint || integration 2 | // +build uint integration 3 | 4 | package pili 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/qiniu/go-sdk/v7/client" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | var ( 14 | ApiHost = os.Getenv("apiHost") 15 | AccessKey = os.Getenv("accessKey") 16 | SecretKey = os.Getenv("secretKey") 17 | IAMAccessKey = os.Getenv("iamAccessKey") 18 | IAMSecretKey = os.Getenv("iamSecretKey") 19 | 20 | // 部分单元测试需配置额外参数 21 | TestHub = os.Getenv("QINIU_TEST_HUB") 22 | TestDomain = os.Getenv("QINIU_TEST_DOMAIN") 23 | TestVodDomain = os.Getenv("QINIU_TEST_VOD_DOMAIN") 24 | TestCertName = os.Getenv("QINIU_TEST_CERT_NAME") 25 | TestStream = os.Getenv("QINIU_TEST_STREAM") 26 | TestBucket = os.Getenv("QINIU_TEST_BUCKET") 27 | ) 28 | 29 | func init() { 30 | client.DebugMode = true 31 | } 32 | 33 | func SkipTest() bool { 34 | return AccessKey == "" || SecretKey == "" || TestHub == "" 35 | } 36 | 37 | func CheckErr(ast *assert.Assertions, err error, errMsg string) { 38 | if errMsg == "" { 39 | ast.Nil(err) 40 | } else { 41 | ast.EqualError(err, errMsg) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pili/validate.go: -------------------------------------------------------------------------------- 1 | package pili 2 | 3 | import ( 4 | "net/http" 5 | "reflect" 6 | "sync" 7 | 8 | "github.com/go-playground/validator/v10" 9 | ) 10 | 11 | const ( 12 | TagName = "validate" 13 | ) 14 | 15 | var defaultValidator = &Validator{} 16 | 17 | type Validator struct { 18 | once sync.Once 19 | validate *validator.Validate 20 | } 21 | 22 | // Validate 参数验证 23 | func (v *Validator) Validate(obj interface{}) error { 24 | if obj == nil { 25 | return nil 26 | } 27 | value := reflect.ValueOf(obj) 28 | switch value.Kind() { 29 | case reflect.Ptr: 30 | return v.Validate(value.Elem().Interface()) 31 | case reflect.Slice, reflect.Array: 32 | for i := 0; i < value.Len(); i++ { 33 | if err := v.Validate(value.Index(i).Interface()); err != nil { 34 | return err 35 | } 36 | } 37 | case reflect.Struct: 38 | v.lazyInit() 39 | if err := v.validate.Struct(obj); err != nil { 40 | return ErrInfo(http.StatusBadRequest, err.Error()) 41 | } 42 | } 43 | 44 | return nil 45 | } 46 | 47 | // lazyInit 延迟初始化 48 | func (v *Validator) lazyInit() { 49 | v.once.Do(func() { 50 | v.validate = validator.New() 51 | v.validate.SetTagName(TagName) 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /pili/validate_test.go: -------------------------------------------------------------------------------- 1 | //go:build unit 2 | // +build unit 3 | 4 | package pili 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestValidator_Validate(t *testing.T) { 13 | ast := assert.New(t) 14 | 15 | type Foo struct { 16 | Num int `validate:"gt=0,lte=10"` 17 | String string `validate:"required"` 18 | } 19 | 20 | // struct 21 | struct1 := Foo{} 22 | struct2 := Foo{Num: 1, String: "bar"} 23 | err := defaultValidator.Validate(struct1) 24 | ast.NotNil(err) 25 | err = defaultValidator.Validate(struct2) 26 | ast.Nil(err) 27 | 28 | // struct ptr 29 | err = defaultValidator.Validate(&struct1) 30 | ast.NotNil(err) 31 | err = defaultValidator.Validate(&struct2) 32 | ast.Nil(err) 33 | 34 | // slice/array struct 35 | slice1 := []Foo{{}, {}} 36 | slice2 := []Foo{{Num: 1, String: "bar"}, {Num: 2, String: "bar2"}} 37 | err = defaultValidator.Validate(slice1) 38 | ast.NotNil(err) 39 | err = defaultValidator.Validate(slice2) 40 | ast.Nil(err) 41 | 42 | // not dive 43 | type NotDive struct { 44 | Foos []Foo 45 | } 46 | notDive := NotDive{Foos: []Foo{{}, {}}} 47 | err = defaultValidator.Validate(notDive) 48 | ast.Nil(err) 49 | 50 | // dive 51 | type Dive struct { 52 | Foos []Foo `validate:"dive"` 53 | } 54 | dive1 := Dive{Foos: []Foo{{}, {}}} 55 | dive2 := Dive{Foos: []Foo{{Num: 1, String: "bar"}, {Num: 2, String: "bar2"}}} 56 | err = defaultValidator.Validate(dive1) 57 | ast.NotNil(err) 58 | err = defaultValidator.Validate(dive2) 59 | ast.Nil(err) 60 | } 61 | -------------------------------------------------------------------------------- /qvs/conf.go: -------------------------------------------------------------------------------- 1 | package qvs 2 | 3 | // APIHost 指定了 API 服务器的地址 4 | var APIHost = "qvs.qiniuapi.com/v1" 5 | 6 | // APIHTTPScheme 指定了在请求 API 服务器时使用的 HTTP 模式. 7 | var APIHTTPScheme = "http://" 8 | -------------------------------------------------------------------------------- /qvs/manager.go: -------------------------------------------------------------------------------- 1 | package qvs 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/url" 7 | 8 | "github.com/qiniu/go-sdk/v7/auth" 9 | "github.com/qiniu/go-sdk/v7/client" 10 | ) 11 | 12 | // Manager 代表一个 qvs 用户的客户端 13 | type Manager struct { 14 | client *client.Client 15 | mac *auth.Credentials 16 | } 17 | 18 | // New 初始化 Client. 19 | func NewManager(mac *auth.Credentials, tr http.RoundTripper) *Manager { 20 | client := client.DefaultClient 21 | client.Transport = newTransport(mac, nil) 22 | return &Manager{ 23 | client: &client, 24 | mac: mac, 25 | } 26 | } 27 | 28 | func setQuery(q url.Values, key string, v interface{}) { 29 | q.Set(key, fmt.Sprint(v)) 30 | } 31 | 32 | func (manager *Manager) url(format string, args ...interface{}) string { 33 | return APIHTTPScheme + APIHost + fmt.Sprintf(format, args...) 34 | } 35 | -------------------------------------------------------------------------------- /qvs/manager_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package qvs 5 | 6 | import ( 7 | "runtime/debug" 8 | "testing" 9 | 10 | "github.com/qiniu/go-sdk/v7/auth" 11 | ) 12 | 13 | var ( 14 | testAccessKey = "" 15 | testSecretKey = "" 16 | ) 17 | 18 | func skipTest() bool { 19 | return testAccessKey == "" || testSecretKey == "" 20 | } 21 | 22 | func getTestManager() *Manager { 23 | mac := auth.New(testAccessKey, testSecretKey) 24 | return NewManager(mac, nil) 25 | } 26 | func noError(t *testing.T, err error) { 27 | if err != nil { 28 | t.Helper() 29 | debug.PrintStack() 30 | t.Fatalf("should be nil, err = %s", err.Error()) 31 | } 32 | } 33 | 34 | func shouldBeEqual(t *testing.T, a interface{}, b interface{}) { 35 | if a != b { 36 | t.Helper() 37 | debug.PrintStack() 38 | t.Fatalf("should be equal, expect = %#v, but got = %#v", a, b) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /qvs/record_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package qvs 5 | 6 | import ( 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestOndemandRecordAndSnap(t *testing.T) { 12 | 13 | if skipTest() { 14 | t.SkipNow() 15 | } 16 | c := getTestManager() 17 | 18 | err := c.OndemandSnap("2xenzw5o81ods", "31011500991320000356") 19 | noError(t, err) 20 | err = c.StartRecord("2xenzw5o81ods", "31011500991320000356") 21 | noError(t, err) 22 | time.Sleep(15 * 60 * time.Second) 23 | err = c.StopRecord("2xenzw5o81ods", "31011500991320000356") 24 | noError(t, err) 25 | } 26 | 27 | func TestRecordClipsSaveasAndDeleteRecord(t *testing.T) { 28 | if skipTest() { 29 | t.SkipNow() 30 | } 31 | c := getTestManager() 32 | 33 | ret, err := c.RecordClipsSaveas("2xenzw5o81ods", "31011500991320000356", &SaveasArgs{ 34 | Format: "m3u8", 35 | Start: 1604989846, 36 | End: 1604990735, 37 | }) 38 | noError(t, err) 39 | shouldBeEqual(t, ret.Fname, "record/2xenzw5o81ods/31011500991320000356/1604989846152-1604990735281-852640.m3u8") 40 | 41 | err = c.DeleteStreamRecordHistories("2xenzw5o81ods", "31011500991320000356", []string{"record/2xenzw5o81ods/31011500991320000356/1604989846152-1604990735281-852640.m3u8"}) 42 | noError(t, err) 43 | } 44 | -------------------------------------------------------------------------------- /qvs/stats.go: -------------------------------------------------------------------------------- 1 | package qvs 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type FlowBadwidthData struct { 8 | Time []int64 `json:"time"` 9 | Data struct { 10 | Up []int64 `json:"up"` 11 | Down []int64 `json:"down"` 12 | } `json:"data"` 13 | } 14 | 15 | /* 16 | 查询流量数据 17 | */ 18 | func (manager *Manager) QueryFlow(nsId, streamId, tu string, start, end int) (*FlowBadwidthData, error) { 19 | var ret FlowBadwidthData 20 | err := manager.client.Call(context.Background(), &ret, "GET", manager.url("/stats/flow?nsId=%s&streamId=%s&start=%d&end=%d&tu=%s", nsId, streamId, start, end, tu), nil) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &ret, nil 25 | } 26 | 27 | /* 28 | 查询带宽数据 29 | */ 30 | func (manager *Manager) QueryBandwidth(nsId, streamId, tu string, start, end int) (*FlowBadwidthData, error) { 31 | var ret FlowBadwidthData 32 | err := manager.client.Call(context.Background(), &ret, "GET", manager.url("/stats/bandwidth?nsId=%s&stream=%s&start=%d&end=%d&tu=%s", nsId, streamId, start, end, tu), nil) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return &ret, nil 37 | } 38 | -------------------------------------------------------------------------------- /qvs/stats_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package qvs 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | ) 10 | 11 | func TestQueryStats(t *testing.T) { 12 | if skipTest() { 13 | t.SkipNow() 14 | } 15 | c := getTestManager() 16 | ret, err := c.QueryFlow("", "", "5min", 20200901, 20200902) 17 | noError(t, err) 18 | fmt.Println(*ret) 19 | 20 | ret, err = c.QueryBandwidth("", "", "hour", 20200901, 20200902) 21 | noError(t, err) 22 | fmt.Println(*ret) 23 | } 24 | -------------------------------------------------------------------------------- /qvs/util.go: -------------------------------------------------------------------------------- 1 | package qvs 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/qiniu/go-sdk/v7/auth" 7 | ) 8 | 9 | // --------------------------------------------------------------------------------------- 10 | 11 | type transport struct { 12 | http.RoundTripper 13 | mac *auth.Credentials 14 | } 15 | 16 | func newTransport(mac *auth.Credentials, tr http.RoundTripper) *transport { 17 | if tr == nil { 18 | tr = http.DefaultTransport 19 | } 20 | return &transport{tr, mac} 21 | } 22 | 23 | func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { 24 | token, err := t.mac.SignRequestV2(req) 25 | if err != nil { 26 | return 27 | } 28 | req.Header.Set("Authorization", "Qiniu "+token) 29 | return t.RoundTripper.RoundTrip(req) 30 | } 31 | -------------------------------------------------------------------------------- /reqid/reqid.go: -------------------------------------------------------------------------------- 1 | package reqid 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type reqidKey struct{} 8 | 9 | // WithReqid 把reqid加入context中 10 | func WithReqid(ctx context.Context, reqid string) context.Context { 11 | return context.WithValue(ctx, reqidKey{}, reqid) 12 | } 13 | 14 | // ReqidFromContext 从context中获取reqid 15 | func ReqidFromContext(ctx context.Context) (reqid string, ok bool) { 16 | reqid, ok = ctx.Value(reqidKey{}).(string) 17 | return 18 | } 19 | 20 | // -------------------------------------------------------------------- 21 | -------------------------------------------------------------------------------- /rtc/doc.go: -------------------------------------------------------------------------------- 1 | // Qiniu RTC Server API 为七牛实时音视频云提供权限验证和房间管理功能,API 均采用 REST 接口。 2 | // 提供 app 操作接口,包含 CreateApp、GetApp、DeleteApp、UpdateApp ; 3 | // 提供 room 操作接口,包含 ListUser、KickUser、ListActiveRoom 以及 4 | // RoomToken 的计算 5 | 6 | package rtc 7 | -------------------------------------------------------------------------------- /sms/bytes/bytes_test.go: -------------------------------------------------------------------------------- 1 | //go:build unit 2 | // +build unit 3 | 4 | package bytes 5 | 6 | import ( 7 | "io" 8 | "testing" 9 | ) 10 | 11 | // --------------------------------------------------- 12 | 13 | func TestBuffer(t *testing.T) { 14 | 15 | b := NewBuffer() 16 | n, err := b.WriteStringAt("Hello", 4) 17 | if n != 5 || err != nil { 18 | t.Fatal("WriteStringAt failed:", n, err) 19 | } 20 | if b.Len() != 9 { 21 | t.Fatal("Buffer.Len invalid (9 is required):", b.Len()) 22 | } 23 | 24 | buf := make([]byte, 10) 25 | n, err = b.ReadAt(buf, 50) 26 | if n != 0 || err != io.EOF { 27 | t.Fatal("ReadAt failed:", n, err) 28 | } 29 | 30 | n, err = b.ReadAt(buf, 6) 31 | if n != 3 || err != io.EOF || string(buf[:n]) != "llo" { 32 | t.Fatal("ReadAt failed:", n, err, string(buf[:n])) 33 | } 34 | 35 | n, err = b.WriteAt([]byte("Hi h"), 1) 36 | if n != 4 || err != nil { 37 | t.Fatal("WriteAt failed:", n, err) 38 | } 39 | if b.Len() != 9 { 40 | t.Fatal("Buffer.Len invalid (9 is required):", b.Len()) 41 | } 42 | 43 | n, err = b.ReadAt(buf, 0) 44 | if n != 9 || err != io.EOF || string(buf[:n]) != "\x00Hi hello" { 45 | t.Fatal("ReadAt failed:", n, err) 46 | } 47 | 48 | n, err = b.WriteStringAt("LO world!", 7) 49 | if n != 9 || err != nil { 50 | t.Fatal("WriteStringAt failed:", n, err) 51 | } 52 | if b.Len() != 16 { 53 | t.Fatal("Buffer.Len invalid (16 is required):", b.Len()) 54 | } 55 | 56 | buf = make([]byte, 17) 57 | n, err = b.ReadAt(buf, 0) 58 | if n != 16 || err != io.EOF || string(buf[:n]) != "\x00Hi helLO world!" { 59 | t.Fatal("ReadAt failed:", n, err, string(buf[:n])) 60 | } 61 | } 62 | 63 | // --------------------------------------------------- 64 | -------------------------------------------------------------------------------- /sms/client/transport.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/base64" 5 | "net/http" 6 | ) 7 | 8 | // Mac qiniu mac type 9 | type Mac struct { 10 | AccessKey string 11 | SecretKey []byte 12 | } 13 | 14 | // Transport with qiniu mac 15 | type Transport struct { 16 | mac Mac 17 | Transport http.RoundTripper 18 | } 19 | 20 | // RoundTrip transport round trip method 21 | func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { 22 | 23 | sign, err := SignRequest(t.mac.SecretKey, req) 24 | if err != nil { 25 | return 26 | } 27 | 28 | auth := "Qiniu " + t.mac.AccessKey + ":" + base64.URLEncoding.EncodeToString(sign) 29 | req.Header.Set("Authorization", auth) 30 | return t.Transport.RoundTrip(req) 31 | } 32 | 33 | // NestedObject return transport 34 | func (t *Transport) NestedObject() interface{} { 35 | 36 | return t.Transport 37 | } 38 | 39 | // NewTransport return transport with qiniu mac 40 | func NewTransport(mac *Mac, transport http.RoundTripper) *Transport { 41 | 42 | if transport == nil { 43 | transport = http.DefaultTransport 44 | } 45 | 46 | t := &Transport{Transport: transport} 47 | t.mac = *mac 48 | 49 | return t 50 | } 51 | 52 | // NewClient return qiniu mac client 53 | func NewClient(mac *Mac, transport http.RoundTripper) *http.Client { 54 | 55 | t := NewTransport(mac, transport) 56 | return &http.Client{Transport: t} 57 | } 58 | -------------------------------------------------------------------------------- /sms/doc.go: -------------------------------------------------------------------------------- 1 | // Package sms 七牛云短信服务 SDK 2 | // 官网地址: http://www.qiniu.com/products/sms 3 | // 开发者文档: https://developer.qiniu.com/sms 4 | package sms 5 | -------------------------------------------------------------------------------- /sms/main_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package sms_test 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/qiniu/go-sdk/v7/auth" 10 | 11 | "github.com/qiniu/go-sdk/v7/sms" 12 | ) 13 | 14 | var manager *sms.Manager 15 | 16 | func init() { 17 | accessKey := os.Getenv("accessKey") 18 | secretKey := os.Getenv("secretKey") 19 | 20 | mac := auth.New(accessKey, secretKey) 21 | manager = sms.NewManager(mac) 22 | } 23 | -------------------------------------------------------------------------------- /sms/manager.go: -------------------------------------------------------------------------------- 1 | package sms 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/qiniu/go-sdk/v7/auth" 7 | "github.com/qiniu/go-sdk/v7/sms/client" 8 | "github.com/qiniu/go-sdk/v7/sms/rpc" 9 | ) 10 | 11 | var ( 12 | // Host 为 Qiniu SMS Server API 服务域名 13 | Host = "https://sms.qiniuapi.com" 14 | ) 15 | 16 | // Manager 提供了 Qiniu SMS Server API 相关功能 17 | type Manager struct { 18 | mac *auth.Credentials 19 | client rpc.Client 20 | } 21 | 22 | // NewManager 用来构建一个新的 Manager 23 | func NewManager(mac *auth.Credentials) (manager *Manager) { 24 | 25 | manager = &Manager{} 26 | 27 | if mac == nil { 28 | mac = auth.Default() 29 | } 30 | mac1 := &client.Mac{ 31 | AccessKey: mac.AccessKey, 32 | SecretKey: mac.SecretKey, 33 | } 34 | 35 | transport := client.NewTransport(mac1, nil) 36 | manager.client = rpc.Client{Client: &http.Client{Transport: transport}} 37 | 38 | return 39 | } 40 | -------------------------------------------------------------------------------- /sms/message.go: -------------------------------------------------------------------------------- 1 | package sms 2 | 3 | import "fmt" 4 | 5 | // MessagesRequest 短信消息 6 | type MessagesRequest struct { 7 | SignatureID string `json:"signature_id"` 8 | TemplateID string `json:"template_id"` 9 | Mobiles []string `json:"mobiles"` 10 | Parameters map[string]interface{} `json:"parameters"` 11 | } 12 | 13 | // MessagesResponse 发送短信响应 14 | type MessagesResponse struct { 15 | JobID string `json:"job_id"` 16 | } 17 | 18 | // SendMessage 发送短信 19 | func (m *Manager) SendMessage(args MessagesRequest) (ret MessagesResponse, err error) { 20 | url := fmt.Sprintf("%s%s", Host, "/v1/message") 21 | err = m.client.CallWithJSON(&ret, url, args) 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /sms/rpc/transport.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | // DefaultDailTimeout 默认超时时间: 5秒 10 | const DefaultDailTimeout = time.Duration(5) * time.Second 11 | 12 | // DefaultTransport 默认 HTTP Transport 13 | var DefaultTransport = NewTransportTimeout(DefaultDailTimeout, 0) 14 | 15 | // NewTransportTimeout 返回指定超时时间的 Transport 对象 16 | func NewTransportTimeout(dial, resp time.Duration) http.RoundTripper { 17 | t := &http.Transport{ // DefaultTransport 18 | Proxy: http.ProxyFromEnvironment, 19 | TLSHandshakeTimeout: 10 * time.Second, 20 | } 21 | t.Dial = (&net.Dialer{ 22 | Timeout: dial, 23 | KeepAlive: 30 * time.Second, 24 | }).Dial 25 | t.ResponseHeaderTimeout = resp 26 | return t 27 | } 28 | 29 | // NewTransportTimeoutWithConnsPool 返回指定超时时间和最大连接主机数的 Transport 对象 30 | func NewTransportTimeoutWithConnsPool(dial, resp time.Duration, poolSize int) http.RoundTripper { 31 | 32 | t := &http.Transport{ // DefaultTransport 33 | Proxy: http.ProxyFromEnvironment, 34 | TLSHandshakeTimeout: 10 * time.Second, 35 | MaxIdleConnsPerHost: poolSize, 36 | } 37 | t.Dial = (&net.Dialer{ 38 | Timeout: dial, 39 | KeepAlive: 30 * time.Second, 40 | }).Dial 41 | t.ResponseHeaderTimeout = resp 42 | return t 43 | } 44 | -------------------------------------------------------------------------------- /sms/signature_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package sms_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/qiniu/go-sdk/v7/sms" 10 | ) 11 | 12 | func TestSignature(t *testing.T) { 13 | 14 | // CreateSignature 15 | args := sms.SignatureRequest{ 16 | Signature: "Test", 17 | Source: sms.Website, 18 | } 19 | 20 | ret, err := manager.CreateSignature(args) 21 | 22 | if err != nil { 23 | t.Fatalf("CreateSignature() error: %v\n", err) 24 | } 25 | 26 | if len(ret.SignatureID) == 0 { 27 | t.Fatal("CreateSignature() error: The signature ID cannot be empty") 28 | } 29 | 30 | // QuerySignature 31 | query := sms.QuerySignatureRequest{} 32 | 33 | pagination, err := manager.QuerySignature(query) 34 | 35 | if err != nil { 36 | t.Fatalf("QuerySignature() error: %v\n", err) 37 | } 38 | 39 | if len(pagination.Items) == 0 { 40 | t.Fatal("QuerySignature() error: signatures cannot be empty") 41 | } 42 | 43 | if pagination.Total == 0 { 44 | t.Fatal("QuerySignature() error: total cannot be 0") 45 | } 46 | 47 | // UpdateSignature 48 | update := sms.SignatureRequest{ 49 | Signature: "test", 50 | } 51 | 52 | err = manager.UpdateSignature(ret.SignatureID, update) 53 | if err != nil { 54 | t.Fatalf("UpdateSignature() error: %v\n", err) 55 | } 56 | 57 | // DeleteSignature 58 | err = manager.DeleteSignature(ret.SignatureID) 59 | if err != nil { 60 | t.Fatalf("DeleteSignature() error: %v\n", err) 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /sms/template_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package sms_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/qiniu/go-sdk/v7/sms" 10 | ) 11 | 12 | func TestTemplate(t *testing.T) { 13 | 14 | // CreateTemplate 15 | args := sms.TemplateRequest{ 16 | Name: "Test", 17 | Type: sms.VerificationType, 18 | Template: "您的验证码是 ${{code}}, 5分钟内有效", 19 | } 20 | 21 | ret, err := manager.CreateTemplate(args) 22 | 23 | if err != nil { 24 | t.Fatalf("CreateTemplate() error: %v\n", err) 25 | } 26 | 27 | if len(ret.TemplateID) == 0 { 28 | t.Fatal("CreateTemplate() error: The template ID cannot be empty") 29 | } 30 | 31 | // QueryTemplate 32 | query := sms.QueryTemplateRequest{} 33 | 34 | pagination, err := manager.QueryTemplate(query) 35 | 36 | if err != nil { 37 | t.Fatalf("QueryTemplate() error: %v\n", err) 38 | } 39 | 40 | if len(pagination.Items) == 0 { 41 | t.Fatal("QueryTemplate() error: templates cannot be empty") 42 | } 43 | 44 | if pagination.Total == 0 { 45 | t.Fatal("QueryTemplate() error: total cannot be 0") 46 | } 47 | 48 | // UpdateTemplate 49 | update := sms.TemplateRequest{ 50 | Name: "test", 51 | } 52 | 53 | err = manager.UpdateTemplate(ret.TemplateID, update) 54 | if err != nil { 55 | t.Fatalf("UpdateTemplate() error: %v\n", err) 56 | } 57 | 58 | // DeleteTemplate 59 | err = manager.DeleteTemplate(ret.TemplateID) 60 | if err != nil { 61 | t.Fatalf("DeleteTemplate() error: %v\n", err) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /storage/backward_compatible.go: -------------------------------------------------------------------------------- 1 | // 原来rpc.go包含了客户端的信息,这个部分被调整到了"github.com/qiniu/go-sdk/v7/client" 2 | // 这个文件的内容不应该再被使用 3 | // 客户端应该是所有服务公用的,包括kodo, cdn, dora, atlab等,不应该放在storage下 4 | 5 | // 这个文件兼容保留了原来storage暴露出去的变量,函数等 6 | package storage 7 | 8 | import ( 9 | "fmt" 10 | "runtime" 11 | 12 | "github.com/qiniu/go-sdk/v7/client" 13 | "github.com/qiniu/go-sdk/v7/conf" 14 | ) 15 | 16 | var DefaultClient = client.DefaultClient 17 | var UserAgent = client.UserAgent 18 | 19 | type Client = client.Client 20 | type ErrorInfo = client.ErrorInfo 21 | 22 | var ResponseError = client.ResponseError 23 | var CallRet = client.CallRet 24 | 25 | // var SetAppName = client.SetAppName 26 | // SetAppName设置的是全局的变量,如果再这个包引入var SetAppName, 那么设置的实际上是 27 | // client包中的UserAgent, 所以为了兼容性重复定义了该函数 28 | 29 | // userApp should be [A-Za-z0-9_\ \-\.]* 30 | func SetAppName(userApp string) error { 31 | UserAgent = fmt.Sprintf( 32 | "QiniuGo/%s (%s; %s; %s) %s", conf.Version, runtime.GOOS, runtime.GOARCH, userApp, runtime.Version()) 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /storage/backward_compatible_test.go: -------------------------------------------------------------------------------- 1 | //go:build unit 2 | // +build unit 3 | 4 | package storage 5 | 6 | import ( 7 | "fmt" 8 | "runtime" 9 | "testing" 10 | 11 | "github.com/qiniu/go-sdk/v7/conf" 12 | ) 13 | 14 | func TestVariable(t *testing.T) { 15 | appName := "test" 16 | 17 | SetAppName(appName) 18 | 19 | want := fmt.Sprintf("QiniuGo/%s (%s; %s; %s) %s", conf.Version, runtime.GOOS, runtime.GOARCH, appName, runtime.Version()) 20 | 21 | if UserAgent != want { 22 | t.Fail() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /storage/bucket_list_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package storage 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | ) 10 | 11 | func TestList(t *testing.T) { 12 | ret, _, err := bucketManager.ListFilesWithContext(context.Background(), testBucket, 13 | ListInputOptionsLimit(1000), 14 | ListInputOptionsNeedParts(false), 15 | ) 16 | if err != nil { 17 | t.Fatalf("List bucket files error: %v\n", err) 18 | } 19 | 20 | hasParts := false 21 | for _, item := range ret.Items { 22 | if len(item.Parts) > 0 { 23 | hasParts = true 24 | } 25 | } 26 | if hasParts { 27 | t.Fatal("list files: should no parts") 28 | } 29 | 30 | ret, _, err = bucketManager.ListFilesWithContext(context.Background(), testBucket, 31 | ListInputOptionsLimit(1000), 32 | ListInputOptionsNeedParts(true), 33 | ) 34 | if err != nil { 35 | t.Fatalf("List bucket files error: %v\n", err) 36 | } 37 | 38 | hasParts = false 39 | for _, item := range ret.Items { 40 | if len(item.Parts) > 0 { 41 | hasParts = true 42 | } 43 | } 44 | if !hasParts { 45 | t.Fatal("list files: should parts") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /storage/buckets_v4.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import "time" 4 | 5 | type ( 6 | // BucketV4 查询条件 7 | BucketV4Input struct { 8 | // 指定区域 ID,如果传入空字符串,则查询所有区域 9 | Region string 10 | // 最多获取的空间数,如果传入 0,则查询 20 个空间 11 | Limit uint64 12 | // 获取下一页的标记 13 | Marker string 14 | } 15 | 16 | // BucketV4 返回的空间信息 17 | BucketsV4Output struct { 18 | // 下页开始的 Marker 19 | NextMarker string `json:"next_marker"` 20 | // 列举是否被阶段,如果为 true,则表示还有下一页 21 | IsTruncated bool `json:"is_truncated"` 22 | // 空间列表 23 | Buckets []BucketV4Output `json:"buckets"` 24 | } 25 | 26 | // BucketV4 返回的空间信息 27 | BucketV4Output struct { 28 | // 空间名称 29 | Name string `json:"name"` 30 | // 空间区域 ID 31 | Region string `json:"region"` 32 | // 空间是否私有 33 | Private bool `json:"private"` 34 | // 空间创建时间 35 | Ctime time.Time `json:"ctime"` 36 | } 37 | ) 38 | -------------------------------------------------------------------------------- /storage/config_test.go: -------------------------------------------------------------------------------- 1 | //go:build unit 2 | // +build unit 3 | 4 | package storage 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestReqHost(t *testing.T) { 11 | zoneHuadong := Zone{ 12 | SrcUpHosts: []string{ 13 | "up.qiniup.com", 14 | "up-nb.qiniup.com", 15 | "up-xs.qiniup.com", 16 | }, 17 | CdnUpHosts: []string{ 18 | "upload.qiniup.com", 19 | "upload-nb.qiniup.com", 20 | "upload-xs.qiniup.com", 21 | }, 22 | RsHost: "rs.qbox.me", 23 | RsfHost: "rsf.qbox.me", 24 | ApiHost: "api.qiniu.com", 25 | IovipHost: "iovip.qbox.me", 26 | } 27 | cfgs := []Config{ 28 | {UseHTTPS: true, Zone: &zoneHuadong}, 29 | {UseHTTPS: true, RsHost: "http://rshost.com"}, 30 | {UseHTTPS: true, RsHost: "https://rshost.com"}, 31 | {UseHTTPS: true, Zone: &zoneHuadong, RsHost: "http://rshost.com"}, 32 | {UseHTTPS: false, Zone: &zoneHuadong}, 33 | {UseHTTPS: false, RsHost: "http://rshost.com"}, 34 | {UseHTTPS: false, RsHost: "https://rshost.com"}, 35 | {UseHTTPS: false, Zone: &zoneHuadong, RsHost: "http://rshost.com"}, 36 | {UseHTTPS: false, Region: &zoneHuadong, RsHost: "http://rshost.com"}, 37 | } 38 | wantRsHosts := []string{ 39 | "https://rs.qbox.me", 40 | "http://rshost.com", 41 | "https://rshost.com", 42 | "https://rs.qbox.me", 43 | "http://rs.qbox.me", 44 | "http://rshost.com", 45 | "https://rshost.com", 46 | "http://rs.qbox.me", 47 | "http://rs.qbox.me", 48 | } 49 | 50 | for ind, cfg := range cfgs { 51 | got := cfg.RsReqHost() 52 | want := wantRsHosts[ind] 53 | if got != want { 54 | t.Errorf("ind = %d, want = %q, got = %q\n", ind, want, got) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /storage/doc.go: -------------------------------------------------------------------------------- 1 | // storage 包提供了资源的上传,管理,数据处理等功能。其中资源的上传又提供了表单上传的方式以及分片上传的方式,其中分片上传的方式还支持断点续传。 2 | // 3 | // 该包中提供了 BucketManager 用来进行资源管理,比如获取文件信息,文件复制,删除,重命名等,以及很多高级功能如修改文件类型, 4 | // 修改文件的生命周期,修改文件的存储类型等。 5 | // 6 | // 该包中还提供了 FormUploader 和 ResumeUploader 来分别支持表单上传和分片上传,断点续传等功能,对于较大的文件,比如100MB以上的文件,一般 7 | // 建议采用分片上传的方式来保证上传的效率和可靠性。 8 | // 9 | // 对于数据处理,则提供了 OperationManager,可以使用它来发送持久化数据处理请求,及查询数据处理的状态。 10 | package storage 11 | -------------------------------------------------------------------------------- /storage/endpoint_test.go: -------------------------------------------------------------------------------- 1 | //go:build unit 2 | // +build unit 3 | 4 | package storage 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestEndpoint(t *testing.T) { 11 | type input struct { 12 | UseHttps bool 13 | Host string 14 | } 15 | testInputs := []input{ 16 | {UseHttps: true, Host: "rs.qiniu.com"}, 17 | {UseHttps: false, Host: "rs.qiniu.com"}, 18 | {UseHttps: true, Host: ""}, 19 | {UseHttps: false, Host: ""}, 20 | {UseHttps: true, Host: "https://rs.qiniu.com"}, 21 | {UseHttps: false, Host: "https://rs.qiniu.com"}, 22 | {UseHttps: false, Host: "http://rs.qiniu.com"}, 23 | } 24 | testWants := []string{ 25 | "https://rs.qiniu.com", 26 | "http://rs.qiniu.com", 27 | "", 28 | "", 29 | "https://rs.qiniu.com", 30 | "https://rs.qiniu.com", 31 | "http://rs.qiniu.com", 32 | } 33 | 34 | for ind, testInput := range testInputs { 35 | testGot := endpoint(testInput.UseHttps, testInput.Host) 36 | testWant := testWants[ind] 37 | if testGot != testWant { 38 | t.Fatalf("index:%d Got:%s Want:%s", ind, testGot, testWant) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /storage/errs.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // ErrBucketNotExist 用户存储空间不存在 9 | ErrBucketNotExist = errors.New("bucket not exist") 10 | 11 | // ErrNoSuchFile 文件已经存在 12 | //lint:ignore ST1005 历史问题,需要兼容 13 | ErrNoSuchFile = errors.New("No such file or directory") 14 | ) 15 | -------------------------------------------------------------------------------- /storage/pfop_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package storage 5 | 6 | import ( 7 | "encoding/base64" 8 | "fmt" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | var ( 14 | testVideoKey = "qiniu.mp4" 15 | ) 16 | 17 | func TestPfop(t *testing.T) { 18 | saveBucket := testBucket 19 | 20 | fopAvthumb := fmt.Sprintf("avthumb/mp4/s/480x320/vb/500k|saveas/%s", 21 | EncodedEntry(saveBucket, "pfop_test_qiniu.mp4")) 22 | fopVframe := fmt.Sprintf("vframe/jpg/offset/10|saveas/%s", 23 | EncodedEntry(saveBucket, "pfop_test_qiniu.jpg")) 24 | fopVsample := fmt.Sprintf("vsample/jpg/interval/20/pattern/%s", 25 | base64.URLEncoding.EncodeToString([]byte("pfop_test_$(count).jpg"))) 26 | 27 | fopBatch := []string{fopAvthumb, fopVframe, fopVsample} 28 | fops := strings.Join(fopBatch, ";") 29 | 30 | force := true 31 | notifyURL := "" 32 | pid, err := operationManager.Pfop(testBucket, testVideoKey, fops, 33 | testPipeline, notifyURL, force) 34 | if err != nil { 35 | t.Fatalf("Pfop() error, %s", err) 36 | } 37 | t.Logf("persistentId: %s", pid) 38 | 39 | prefopRet, err := operationManager.Prefop(pid) 40 | if err != nil { 41 | t.Fatalf("Prefop() error, %s", err) 42 | } 43 | t.Logf("%s", prefopRet.String()) 44 | } 45 | -------------------------------------------------------------------------------- /storage/region_group.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import "sync" 4 | 5 | type RegionGroup struct { 6 | locker sync.RWMutex 7 | currentRegionIndex int 8 | regions []*Region 9 | } 10 | 11 | func NewRegionGroup(region ...*Region) *RegionGroup { 12 | return &RegionGroup{ 13 | locker: sync.RWMutex{}, 14 | currentRegionIndex: 0, 15 | regions: region, 16 | } 17 | } 18 | 19 | func (g *RegionGroup) GetRegion() *Region { 20 | g.locker.RLock() 21 | defer g.locker.RUnlock() 22 | if g.currentRegionIndex >= len(g.regions) { 23 | return nil 24 | } 25 | return g.regions[g.currentRegionIndex] 26 | } 27 | 28 | func (g *RegionGroup) CouldSwitchRegion() bool { 29 | g.locker.RLock() 30 | defer g.locker.RUnlock() 31 | return len(g.regions) > (g.currentRegionIndex + 1) 32 | } 33 | 34 | func (g *RegionGroup) SwitchRegion() bool { 35 | g.locker.Lock() 36 | defer g.locker.Unlock() 37 | if len(g.regions) <= (g.currentRegionIndex + 1) { 38 | return false 39 | } 40 | g.currentRegionIndex++ 41 | return true 42 | } 43 | 44 | func (g *RegionGroup) clone() *RegionGroup { 45 | g.locker.RLock() 46 | defer g.locker.RUnlock() 47 | return &RegionGroup{ 48 | locker: sync.RWMutex{}, 49 | currentRegionIndex: g.currentRegionIndex, 50 | regions: g.regions, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /storage/region_uc_v2_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | // +build integration 3 | 4 | package storage 5 | 6 | import ( 7 | "os" 8 | "strings" 9 | "sync" 10 | "testing" 11 | 12 | clientV1 "github.com/qiniu/go-sdk/v7/client" 13 | ) 14 | 15 | func TestUCRetry(t *testing.T) { 16 | clientV1.DebugMode = true 17 | clientV1.DeepDebugInfo = true 18 | defer func() { 19 | clientV1.DebugMode = false 20 | clientV1.DeepDebugInfo = false 21 | }() 22 | 23 | SetUcHosts("aaa.aaa.com", "uc.qbox.me") 24 | defer SetUcHosts("uc.qbox.me") 25 | 26 | _ = os.Remove(regionV2CachePath) 27 | regionV2Cache = sync.Map{} 28 | 29 | r, err := GetRegion(testAK, testBucket) 30 | if err != nil { 31 | t.Fatalf("GetRegion error:%v", err) 32 | } 33 | 34 | if !strings.Contains(r.SrcUpHosts[0], "up-") { 35 | t.Fatal("GetRegion: SrcUpHosts error") 36 | } 37 | 38 | if !strings.Contains(r.CdnUpHosts[0], "upload-") { 39 | t.Fatal("GetRegion: CdnUpHosts error") 40 | } 41 | 42 | if !strings.Contains(r.RsHost, "rs-") { 43 | t.Fatal("GetRegion: RsHost error") 44 | } 45 | 46 | if !strings.Contains(r.RsfHost, "rsf-") { 47 | t.Fatal("GetRegion: RsfHost error") 48 | } 49 | 50 | if !strings.Contains(r.ApiHost, "api-") { 51 | t.Fatal("GetRegion: ApiHost error") 52 | } 53 | 54 | if !strings.Contains(r.IovipHost, "iovip-") { 55 | t.Fatal("GetRegion: IovipHost error") 56 | } 57 | 58 | if !strings.Contains(r.IoSrcHost, testBucket) { 59 | t.Fatal("GetRegion: IoSrcHost error") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /storage/zone.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | // Zone 是Region的别名 4 | // 兼容保留 5 | type Zone = Region 6 | 7 | // GetZone 用来根据ak和bucket来获取空间相关的机房信息 8 | // 新版本使用GetRegion, 这个函数用来保持兼容 9 | func GetZone(ak, bucket string) (zone *Zone, err error) { 10 | return GetRegion(ak, bucket) 11 | } 12 | 13 | var ( 14 | // 华东机房 15 | // 兼容保留 16 | ZoneHuadong, _ = GetRegionByID(RIDHuadong) 17 | 18 | // 华北机房 19 | // 兼容保留 20 | ZoneHuabei, _ = GetRegionByID(RIDHuabei) 21 | 22 | // 华南机房 23 | // 兼容保留 24 | ZoneHuanan, _ = GetRegionByID(RIDHuanan) 25 | 26 | // 北美机房 27 | // 兼容保留 28 | ZoneBeimei, _ = GetRegionByID(RIDNorthAmerica) 29 | 30 | // 新加坡机房 31 | // 兼容保留 32 | ZoneXinjiapo, _ = GetRegionByID(RIDSingapore) 33 | 34 | // 华东浙江 2 区 35 | ZoneHuadongZheJiang2, _ = GetRegionByID(RIDHuadongZheJiang2) 36 | 37 | // 兼容保留 38 | Zone_z0 = ZoneHuadong 39 | // 兼容保留 40 | Zone_z1 = ZoneHuabei 41 | // 兼容保留 42 | Zone_z2 = ZoneHuanan 43 | // 兼容保留 44 | Zone_na0 = ZoneBeimei 45 | // 兼容保留 46 | Zone_as0 = ZoneXinjiapo 47 | ) 48 | -------------------------------------------------------------------------------- /storagev2/apis/add_bucket_event_rule/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 增加存储空间事件通知规则 4 | package add_bucket_event_rule 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Bucket string // 存储空间名称 11 | Name string // 规则名称,bucket 内唯一,长度小于 50,不能为空,只能为字母、数字、下划线 12 | Prefix string // 匹配文件前缀 13 | Suffix string // 匹配文件后缀 14 | EventTypes []string // 事件类型,可以指定多个,包括 put,mkfile,delete,copy,move,append,disable,enable,deleteMarkerCreate,predelete(删除之前触发),restore:completed 15 | CallbackUrls []string // 回调地址,可以指定多个 16 | AccessKey string // 设置的话会对通知请求用对应的 accessKey,secretKey 进行签名 17 | Host string // 通知请求的 Host 18 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 19 | } 20 | 21 | // 获取 API 所用的响应 22 | type Response struct{} 23 | -------------------------------------------------------------------------------- /storagev2/apis/add_bucket_rules/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 增加空间规则 4 | package add_bucket_rules 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 11 | Bucket string // 空间名称 12 | Name string // 规则名称空间内唯一,长度小于50,不能为空,只能为字母、数字、下划线 13 | Prefix string // 匹配的对象名称前缀,同一个空间内前缀不能重复 14 | DeleteAfterDays int64 // 指定上传文件多少天后删除,指定为 0 表示不删除,大于 0 表示多少天后删除 15 | ToIaAfterDays int64 // 指定文件上传多少天后转低频存储。指定为 0 表示不转低频存储 16 | ToArchiveAfterDays int64 // 指定文件上传多少天后转归档存储。指定为 0 表示不转归档存储 17 | ToDeepArchiveAfterDays int64 // 指定文件上传多少天后转深度归档存储。指定为 0 表示不转深度归档存储 18 | ToArchiveIrAfterDays int64 // 指定文件上传多少天后转归档直读存储。指定为 0 表示不转归档直读存储 19 | } 20 | 21 | // 获取 API 所用的响应 22 | type Response struct{} 23 | -------------------------------------------------------------------------------- /storagev2/apis/apis.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | package apis 4 | 5 | import ( 6 | httpclient "github.com/qiniu/go-sdk/v7/storagev2/http_client" 7 | region "github.com/qiniu/go-sdk/v7/storagev2/region" 8 | ) 9 | 10 | // API 客户端 11 | type Storage struct { 12 | client *httpclient.Client 13 | } 14 | 15 | // 创建 API 客户端 16 | func NewStorage(options *httpclient.Options) *Storage { 17 | return &Storage{client: httpclient.NewClient(options)} 18 | } 19 | 20 | // API 客户端选项 21 | type Options struct { 22 | OverwrittenBucketHosts region.EndpointsProvider 23 | OverwrittenBucketName string 24 | OverwrittenEndpoints region.EndpointsProvider 25 | OverwrittenRegion region.RegionsProvider 26 | OnRequestProgress func(uint64, uint64) 27 | } 28 | -------------------------------------------------------------------------------- /storagev2/apis/check_share/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 检查目录分享 4 | package check_share 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | ShareId string // 分享 ID 11 | Token string // 分享 Token 12 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 13 | } 14 | 15 | // 获取 API 所用的响应 16 | type Response struct{} 17 | -------------------------------------------------------------------------------- /storagev2/apis/copy_object/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 将源空间的指定对象复制到目标空间 4 | package copy_object 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | SrcEntry string // 指定源对象空间与源对象名称,格式为 <源对象空间>:<源对象名称> 11 | DestEntry string // 指定目标对象空间与目标对象名称,格式为 <目标对象空间>:<目标对象名称> 12 | IsForce bool // 如果目标对象名已被占用,则返回错误码 614,且不做任何覆盖操作;如果指定为 true,会强制覆盖目标对象 13 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 14 | } 15 | 16 | // 获取 API 所用的响应 17 | type Response struct{} 18 | -------------------------------------------------------------------------------- /storagev2/apis/create_bucket/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 创建一个新的存储空间 4 | package create_bucket 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Bucket string // 空间名称,要求在对象存储系统范围内唯一,由 3~63 个字符组成,支持小写字母、短划线-和数字,且必须以小写字母或数字开头和结尾 11 | Region string // 存储区域 ID,默认 z0 12 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 13 | } 14 | 15 | // 获取 API 所用的响应 16 | type Response struct{} 17 | -------------------------------------------------------------------------------- /storagev2/apis/delete_bucket/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 删除指定的存储空间 4 | package delete_bucket 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Bucket string // 需要删除的目标空间名 11 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 12 | } 13 | 14 | // 获取 API 所用的响应 15 | type Response struct{} 16 | -------------------------------------------------------------------------------- /storagev2/apis/delete_bucket_event_rule/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 删除存储空间事件通知规则 4 | package delete_bucket_event_rule 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Bucket string // 存储空间名称 11 | Name string // 规则名称 12 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 13 | } 14 | 15 | // 获取 API 所用的响应 16 | type Response struct{} 17 | -------------------------------------------------------------------------------- /storagev2/apis/delete_bucket_rules/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 删除空间规则 4 | package delete_bucket_rules 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 11 | Bucket string // 空间名称 12 | Name string // 要删除的规则名称 13 | } 14 | 15 | // 获取 API 所用的响应 16 | type Response struct{} 17 | -------------------------------------------------------------------------------- /storagev2/apis/delete_bucket_taggings/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 一键删除指定存储空间的所有标签 4 | package delete_bucket_taggings 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | BucketName string // 空间名称 11 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 12 | } 13 | 14 | // 获取 API 所用的响应 15 | type Response struct{} 16 | -------------------------------------------------------------------------------- /storagev2/apis/delete_object/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 删除指定对象 4 | package delete_object 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Entry string // 指定目标对象空间与目标对象名称,格式为 <目标对象空间>:<目标对象名称> 11 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 12 | } 13 | 14 | // 获取 API 所用的响应 15 | type Response struct{} 16 | -------------------------------------------------------------------------------- /storagev2/apis/delete_object_after_days/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 更新文件生命周期 4 | package delete_object_after_days 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Entry string // 指定目标对象空间与目标对象名称,格式为 <目标对象空间>:<目标对象名称> 11 | DeleteAfterDays int64 // 指定文件上传后在设置的 DeleteAfterDays 过期删除,删除后不可恢复,设置为 0 表示取消已设置的过期删除的生命周期规则 12 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 13 | } 14 | 15 | // 获取 API 所用的响应 16 | type Response struct{} 17 | -------------------------------------------------------------------------------- /storagev2/apis/disable_bucket_index_page/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 禁用存储空间 index.html(或 index.htm) 页面 4 | package disable_bucket_index_page 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Bucket string // 存储空间名称 11 | NoIndexPage int64 // 是否禁用 index.html(或 index.htm) 页面 12 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 13 | } 14 | 15 | // 获取 API 所用的响应 16 | type Response struct{} 17 | -------------------------------------------------------------------------------- /storagev2/apis/get_bucket_domains/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 获取存储空间的域名列表 4 | package get_bucket_domains 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | ) 10 | 11 | // 调用 API 所用的请求 12 | type Request struct { 13 | BucketName string // 要获取域名列表的目标空间名称 14 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 15 | } 16 | 17 | // 获取 API 所用的响应 18 | type Response struct { 19 | Domains Domains // 存储空间的域名列表 20 | } 21 | 22 | // 存储空间的域名列表 23 | type Domains []string 24 | 25 | func (j *Response) MarshalJSON() ([]byte, error) { 26 | return json.Marshal(j.Domains) 27 | } 28 | func (j *Response) UnmarshalJSON(data []byte) error { 29 | var array Domains 30 | if err := json.Unmarshal(data, &array); err != nil { 31 | return err 32 | } 33 | j.Domains = array 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /storagev2/apis/get_bucket_quota/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 获取用户存储空间配额限制 4 | package get_bucket_quota 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | ) 10 | 11 | // 调用 API 所用的请求 12 | type Request struct { 13 | Bucket string // 指定存储空间 14 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 15 | } 16 | 17 | // 获取 API 所用的响应 18 | type Response struct { 19 | Size int64 // 空间存储量配额 20 | Count int64 // 空间文件数配额 21 | } 22 | 23 | // Bucket 配额信息 24 | type BucketQuota = Response 25 | type jsonResponse struct { 26 | Size int64 `json:"size,omitempty"` // 空间存储量配额 27 | Count int64 `json:"count,omitempty"` // 空间文件数配额 28 | } 29 | 30 | func (j *Response) MarshalJSON() ([]byte, error) { 31 | if err := j.validate(); err != nil { 32 | return nil, err 33 | } 34 | return json.Marshal(&jsonResponse{Size: j.Size, Count: j.Count}) 35 | } 36 | func (j *Response) UnmarshalJSON(data []byte) error { 37 | var nj jsonResponse 38 | if err := json.Unmarshal(data, &nj); err != nil { 39 | return err 40 | } 41 | j.Size = nj.Size 42 | j.Count = nj.Count 43 | return nil 44 | } 45 | func (j *Response) validate() error { 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /storagev2/apis/get_buckets/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 获取拥有的所有存储空间列表 4 | package get_buckets 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | ) 10 | 11 | // 调用 API 所用的请求 12 | type Request struct { 13 | Shared string // 包含共享存储空间,如果为 "rd" 则包含具有读权限空间,如果为 "rw" 则包含读写权限空间 14 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 15 | } 16 | 17 | // 获取 API 所用的响应 18 | type Response struct { 19 | BucketNames BucketNames // 存储空间列表 20 | } 21 | 22 | // 存储空间列表 23 | type BucketNames []string 24 | 25 | func (j *Response) MarshalJSON() ([]byte, error) { 26 | return json.Marshal(j.BucketNames) 27 | } 28 | func (j *Response) UnmarshalJSON(data []byte) error { 29 | var array BucketNames 30 | if err := json.Unmarshal(data, &array); err != nil { 31 | return err 32 | } 33 | j.BucketNames = array 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /storagev2/apis/get_objects_v2/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 列举指定存储空间里的所有对象条目 4 | package get_objects_v2 5 | 6 | import ( 7 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 8 | "io" 9 | ) 10 | 11 | // 调用 API 所用的请求 12 | type Request struct { 13 | Bucket string // 指定存储空间 14 | Marker string // 上一次列举返回的位置标记,作为本次列举的起点信息 15 | Limit int64 // 本次列举的条目数,范围为 1-1000 16 | Prefix string // 指定前缀,只有资源名匹配该前缀的资源会被列出 17 | Delimiter string // 指定目录分隔符,列出所有公共前缀(模拟列出目录效果) 18 | NeedParts bool // 如果文件是通过分片上传的,是否返回对应的分片信息 19 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 20 | } 21 | 22 | // 获取 API 所用的响应 23 | type Response struct { 24 | Body io.ReadCloser 25 | } 26 | -------------------------------------------------------------------------------- /storagev2/apis/modify_object_life_cycle/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 修改已上传对象的生命周期 4 | package modify_object_life_cycle 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Entry string // 指定目标对象空间与目标对象名称,格式为 <目标对象空间>:<目标对象名称> 11 | ToIaAfterDays int64 // 指定文件上传后在设置的 ToIAAfterDays 转换到低频存储类型,设置为 -1 表示取消已设置的转低频存储的生命周期规则 12 | ToArchiveAfterDays int64 // 指定文件上传后在设置的 toArchiveAfterDays 转换到归档存储类型, 设置为 -1 表示取消已设置的转归档存储的生命周期规则 13 | ToDeepArchiveAfterDays int64 // 指定文件上传后在设置的 toDeepArchiveAfterDays 转换到深度归档存储类型, 设置为 -1 表示取消已设置的转深度归档存储的生命周期规则 14 | ToArchiveIrAfterDays int64 // 指定文件上传后在设置的 toArchiveIRAfterDays 转换到归档直读存储类型, 设置为 -1 表示取消已设置的转归档直读存储的生命周期规则 15 | DeleteAfterDays int64 // 指定文件上传后在设置的 DeleteAfterDays 过期删除,删除后不可恢复,设置为 -1 表示取消已设置的过期删除的生命周期规则 16 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 17 | } 18 | 19 | // 获取 API 所用的响应 20 | type Response struct{} 21 | -------------------------------------------------------------------------------- /storagev2/apis/modify_object_metadata/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 修改文件元信息 4 | package modify_object_metadata 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Entry string // 指定目标对象空间与目标对象名称,格式为 <目标对象空间>:<目标对象名称> 11 | MimeType string // 新的 MIME 类型 12 | Condition string // 条件匹配,当前支持设置 hash、mime、fsize、putTime 条件,只有条件匹配才会执行修改操作,格式为 condKey1=condVal1&condKey2=condVal2 13 | MetaData map[string]string // 对象存储元信息,键可以自定义,它可以由字母、数字、下划线、减号组成,必须以 x-qn-meta- 为前缀,且长度小于等于 50,单个文件键和值总和大小不能超过 1024 字节,可以同时修改多个键 14 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 15 | } 16 | 17 | // 获取 API 所用的响应 18 | type Response struct{} 19 | -------------------------------------------------------------------------------- /storagev2/apis/modify_object_status/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 修改文件的存储状态,即禁用状态和启用状态间的的互相转换 4 | package modify_object_status 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Entry string // 指定目标对象空间与目标对象名称,格式为 <目标对象空间>:<目标对象名称> 11 | Status int64 // `0` 表示启用;`1` 表示禁用 12 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 13 | } 14 | 15 | // 获取 API 所用的响应 16 | type Response struct{} 17 | -------------------------------------------------------------------------------- /storagev2/apis/move_object/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 将源空间的指定对象移动到目标空间,或在同一空间内对对象重命名 4 | package move_object 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | SrcEntry string // 指定源对象空间与源对象名称,格式为 <源对象空间>:<源对象名称> 11 | DestEntry string // 指定目标对象空间与目标对象名称,格式为 <目标对象空间>:<目标对象名称> 12 | IsForce bool // 如果目标对象名已被占用,则返回错误码 614,且不做任何覆盖操作;如果指定为 true,会强制覆盖目标对象 13 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 14 | } 15 | 16 | // 获取 API 所用的响应 17 | type Response struct{} 18 | -------------------------------------------------------------------------------- /storagev2/apis/post_object/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 在一次 HTTP 会话中上传单一的一个文件 4 | package post_object 5 | 6 | import ( 7 | "encoding/json" 8 | httpclient "github.com/qiniu/go-sdk/v7/storagev2/http_client" 9 | uptoken "github.com/qiniu/go-sdk/v7/storagev2/uptoken" 10 | ) 11 | 12 | // 调用 API 所用的请求 13 | type Request struct { 14 | ObjectName *string 15 | UploadToken uptoken.Provider 16 | Crc32 int64 17 | File httpclient.MultipartFormBinaryData 18 | CustomData map[string]string 19 | ResponseBody interface{} // 响应体,如果为空,则 Response.Body 的类型由 encoding/json 库决定 20 | } 21 | 22 | // 获取 API 所用的响应 23 | type Response struct { 24 | Body interface{} 25 | } 26 | 27 | func (j *Response) MarshalJSON() ([]byte, error) { 28 | return json.Marshal(j.Body) 29 | } 30 | func (j *Response) UnmarshalJSON(data []byte) error { 31 | return json.Unmarshal(data, &j.Body) 32 | } 33 | -------------------------------------------------------------------------------- /storagev2/apis/prefetch_object/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 对于设置了镜像存储的空间,从镜像源站抓取指定名称的对象并存储到该空间中,如果该空间中已存在该名称的对象,则会将镜像源站的对象覆盖空间中相同名称的对象 4 | package prefetch_object 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Entry string // 指定目标对象空间与目标对象名称,格式为 <目标对象空间>:<目标对象名称> 11 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 12 | } 13 | 14 | // 获取 API 所用的响应 15 | type Response struct{} 16 | -------------------------------------------------------------------------------- /storagev2/apis/restore_archived_object/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 解冻归档存储类型的文件,可设置解冻有效期1~7天,完成解冻任务通常需要1~5分钟 4 | package restore_archived_object 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Entry string // 指定目标对象空间与目标对象名称,格式为 <目标对象空间>:<目标对象名称> 11 | FreezeAfterDays int64 // 解冻有效时长,取值范围 1~7 12 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 13 | } 14 | 15 | // 获取 API 所用的响应 16 | type Response struct{} 17 | -------------------------------------------------------------------------------- /storagev2/apis/resumable_upload_v1_make_file/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 将上传好的所有数据块按指定顺序合并成一个资源文件 4 | package resumable_upload_v1_make_file 5 | 6 | import ( 7 | "encoding/json" 8 | io "github.com/qiniu/go-sdk/v7/internal/io" 9 | uptoken "github.com/qiniu/go-sdk/v7/storagev2/uptoken" 10 | ) 11 | 12 | // 调用 API 所用的请求 13 | type Request struct { 14 | Size int64 // 对象大小 15 | ObjectName *string // 对象名称 16 | FileName string // 文件名称,若未指定,则魔法变量中无法使用fname,ext,fprefix 17 | MimeType string // 文件 MIME 类型,若未指定,则根据文件内容自动检测 MIME 类型 18 | CustomData map[string]string // 自定义元数据(需要以 `x-qn-meta-` 作为前缀)或自定义变量(需要以 `x:` 作为前缀) 19 | UpToken uptoken.Provider // 上传凭证,如果为空,则使用 HTTPClientOptions 中的 UpToken 20 | Body io.ReadSeekCloser // 请求体 21 | ResponseBody interface{} // 响应体,如果为空,则 Response.Body 的类型由 encoding/json 库决定 22 | } 23 | 24 | // 获取 API 所用的响应 25 | type Response struct { 26 | Body interface{} 27 | } 28 | 29 | func (j *Response) MarshalJSON() ([]byte, error) { 30 | return json.Marshal(j.Body) 31 | } 32 | func (j *Response) UnmarshalJSON(data []byte) error { 33 | return json.Unmarshal(data, &j.Body) 34 | } 35 | -------------------------------------------------------------------------------- /storagev2/apis/resumable_upload_v2_abort_multipart_upload/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 根据 UploadId 终止 Multipart Upload 4 | package resumable_upload_v2_abort_multipart_upload 5 | 6 | import uptoken "github.com/qiniu/go-sdk/v7/storagev2/uptoken" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | BucketName string // 存储空间名称 11 | ObjectName *string // 对象名称 12 | UploadId string // 在服务端申请的 Multipart Upload 任务 id 13 | UpToken uptoken.Provider // 上传凭证,如果为空,则使用 HTTPClientOptions 中的 UpToken 14 | } 15 | 16 | // 获取 API 所用的响应 17 | type Response struct{} 18 | -------------------------------------------------------------------------------- /storagev2/apis/set_bucket_access_mode/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 设置存储空间的原图保护 4 | package set_bucket_access_mode 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Bucket string // 空间名称 11 | Mode int64 // 1 表示开启原图保护,0 表示关闭原图保护 12 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 13 | } 14 | 15 | // 获取 API 所用的响应 16 | type Response struct{} 17 | -------------------------------------------------------------------------------- /storagev2/apis/set_bucket_image/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 设置源站镜像回源 4 | package set_bucket_image 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Bucket string // 存储空间名称 11 | Url string // 回源 URL 12 | Host string // 从指定源站下载数据时使用的 Host 13 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 14 | } 15 | 16 | // 获取 API 所用的响应 17 | type Response struct{} 18 | -------------------------------------------------------------------------------- /storagev2/apis/set_bucket_max_age/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 设置存储空间的 cache-control: max-age 响应头 4 | package set_bucket_max_age 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Bucket string // 空间名称 11 | MaxAge int64 // maxAge 为 0 或者负数表示为默认值(31536000) 12 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 13 | } 14 | 15 | // 获取 API 所用的响应 16 | type Response struct{} 17 | -------------------------------------------------------------------------------- /storagev2/apis/set_bucket_private/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 设置存储空间的访问权限 4 | package set_bucket_private 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 11 | Bucket string // 空间名称 12 | IsPrivate int64 // `0`: 公开,`1`: 私有 13 | } 14 | 15 | // 获取 API 所用的响应 16 | type Response struct{} 17 | -------------------------------------------------------------------------------- /storagev2/apis/set_bucket_quota/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 设置用户存储空间配额限制 4 | package set_bucket_quota 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Bucket string // 指定存储空间 11 | Size int64 // 空间存储量配额,参数传入 0 或不传表示不更改当前配置,传入 -1 表示取消限额 12 | Count int64 // 空间文件数配额,参数传入 0 或不传表示不更改当前配置,传入 -1 表示取消限额 13 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 14 | } 15 | 16 | // 获取 API 所用的响应 17 | type Response struct{} 18 | -------------------------------------------------------------------------------- /storagev2/apis/set_bucket_refer_anti_leech/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 设置存储空间的防盗链模式 4 | package set_bucket_refer_anti_leech 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Bucket string // 存储空间名称 11 | Mode int64 // 设置防盗链模式,0:表示关闭 Referer(使用此选项将会忽略以下参数并将恢复默认值); 1:表示设置 Referer 白名单; 2:表示设置 Referer 黑名单 12 | Pattern string // 规则字符串,当前允许格式分为三种:一种为空主机头域名,比如 `foo.com`; 一种是泛域名,比如 `*.bar.com`; 一种是完全通配符,即一个 `*`; 多个规则之间用`;`隔开,比如:`foo.com;*.bar.com;sub.foo.com;*.sub.bar.com` 13 | AllowEmptyReferer int64 // 0:表示不允许空 Refer 访问; 1:表示允许空 Refer 访问 14 | SourceEnabled int64 // 源站是否支持,默认为 0 只给 CDN 配置, 设置为 1 表示开启源站防盗链 15 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 16 | } 17 | 18 | // 获取 API 所用的响应 19 | type Response struct{} 20 | -------------------------------------------------------------------------------- /storagev2/apis/set_bucket_remark/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 设置空间备注 4 | package set_bucket_remark 5 | 6 | import ( 7 | "encoding/json" 8 | credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 9 | errors "github.com/qiniu/go-sdk/v7/storagev2/errors" 10 | ) 11 | 12 | // 调用 API 所用的请求 13 | type Request struct { 14 | Bucket string // 空间名称 15 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 16 | Remark string // 空间备注信息, 字符长度不能超过 100, 允许为空 17 | } 18 | 19 | // 空间备注信息 20 | type BucketRemark = Request 21 | type jsonRequest struct { 22 | Remark string `json:"remark"` // 空间备注信息, 字符长度不能超过 100, 允许为空 23 | } 24 | 25 | func (j *Request) MarshalJSON() ([]byte, error) { 26 | if err := j.validate(); err != nil { 27 | return nil, err 28 | } 29 | return json.Marshal(&jsonRequest{Remark: j.Remark}) 30 | } 31 | func (j *Request) UnmarshalJSON(data []byte) error { 32 | var nj jsonRequest 33 | if err := json.Unmarshal(data, &nj); err != nil { 34 | return err 35 | } 36 | j.Remark = nj.Remark 37 | return nil 38 | } 39 | func (j *Request) validate() error { 40 | if j.Remark == "" { 41 | return errors.MissingRequiredFieldError{Name: "Remark"} 42 | } 43 | return nil 44 | } 45 | 46 | // 获取 API 所用的响应 47 | type Response struct{} 48 | -------------------------------------------------------------------------------- /storagev2/apis/set_buckets_mirror/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 设置存储空间的镜像源 4 | package set_buckets_mirror 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Bucket string // 需要设定镜像源的目标空间名 11 | SrcSiteUrl string // 镜像源的访问域名,必须设置为形如 `http(s)://source.com` 或 `http(s)://114.114.114.114` 的字符串 12 | Host string // 回源时使用的 `Host` 头部值 13 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 14 | } 15 | 16 | // 获取 API 所用的响应 17 | type Response struct{} 18 | -------------------------------------------------------------------------------- /storagev2/apis/set_object_file_type/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 修改文件的存储类型信息,可以实现标准存储、低频存储和归档存储之间的互相转换 4 | package set_object_file_type 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Entry string // 指定目标对象空间与目标对象名称,格式为 <目标对象空间>:<目标对象名称> 11 | Type int64 // `0` 表示标准存储;`1` 表示低频存储;`2` 表示归档存储 12 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 13 | } 14 | 15 | // 获取 API 所用的响应 16 | type Response struct{} 17 | -------------------------------------------------------------------------------- /storagev2/apis/unset_bucket_image/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 取消源站镜像回源 4 | package unset_bucket_image 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Bucket string // 存储空间名称 11 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 12 | } 13 | 14 | // 获取 API 所用的响应 15 | type Response struct{} 16 | -------------------------------------------------------------------------------- /storagev2/apis/update_bucket_event_rule/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 修改存储空间事件通知规则 4 | package update_bucket_event_rule 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Bucket string // 存储空间名称 11 | Name string // 规则名称,bucket 内唯一,长度小于 50,不能为空,只能为字母、数字、下划线 12 | Prefix string // 匹配文件前缀 13 | Suffix string // 匹配文件后缀 14 | EventTypes []string // 事件类型,可以指定多个,包括 put,mkfile,delete,copy,move,append,disable,enable,deleteMarkerCreate,predelete(删除之前触发),restore:completed 15 | CallbackUrls []string // 回调地址,可以指定多个 16 | AccessKey string // 设置的话会对通知请求用对应的 accessKey,secretKey 进行签名 17 | Host string // 通知请求的 Host 18 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 19 | } 20 | 21 | // 获取 API 所用的响应 22 | type Response struct{} 23 | -------------------------------------------------------------------------------- /storagev2/apis/update_bucket_rules/api.go: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED BY api-generator, DO NOT EDIT DIRECTLY! 2 | 3 | // 更新空间规则 4 | package update_bucket_rules 5 | 6 | import credentials "github.com/qiniu/go-sdk/v7/storagev2/credentials" 7 | 8 | // 调用 API 所用的请求 9 | type Request struct { 10 | Credentials credentials.CredentialsProvider // 鉴权参数,用于生成鉴权凭证,如果为空,则使用 HTTPClientOptions 中的 CredentialsProvider 11 | Bucket string // 空间名称 12 | Name string // 要修改的规则名称 13 | Prefix string // 指定匹配的对象名称前缀 14 | DeleteAfterDays int64 // 指定上传文件多少天后删除,指定为 0 表示不删除,大于 0 表示多少天后删除 15 | ToIaAfterDays int64 // 指定文件上传多少天后转低频存储。指定为 0 表示不转低频存储 16 | ToArchiveAfterDays int64 // 指定文件上传多少天后转归档存储。指定为 0 表示不转归档存储 17 | ToDeepArchiveAfterDays int64 // 指定文件上传多少天后转深度归档存储。指定为 0 表示不转深度归档存储 18 | ToArchiveIrAfterDays int64 // 指定文件上传多少天后转归档直读存储。指定为 0 表示不转归档直读存储 19 | } 20 | 21 | // 获取 API 所用的响应 22 | type Response struct{} 23 | -------------------------------------------------------------------------------- /storagev2/chooser/chooser.go: -------------------------------------------------------------------------------- 1 | package chooser 2 | 3 | import ( 4 | "context" 5 | "net" 6 | ) 7 | 8 | type ( 9 | // ChooseOptions 选择器的选项 10 | ChooseOptions struct { 11 | // Domain 是待选择的域名 12 | Domain string 13 | 14 | // 如果找不到合适的域名就直接返回空 15 | FailFast bool 16 | } 17 | 18 | // FeedbackOptions 反馈的选项 19 | FeedbackOptions struct { 20 | // Domain 是待反馈的域名 21 | Domain string 22 | } 23 | 24 | // Chooser 选择器接口 25 | Chooser interface { 26 | // Choose 从给定的 IP 地址列表中选择一批 IP 地址用于发送请求 27 | Choose(context.Context, []net.IP, *ChooseOptions) []net.IP 28 | 29 | // FeedbackGood 反馈一批 IP 地址请求成功 30 | FeedbackGood(context.Context, []net.IP, *FeedbackOptions) 31 | 32 | // FeedbackBad 反馈一批 IP 地址请求失败 33 | FeedbackBad(context.Context, []net.IP, *FeedbackOptions) 34 | } 35 | 36 | directChooser struct{} 37 | ) 38 | 39 | // NewDirectChooser 创建直接选择器 40 | func NewDirectChooser() Chooser { 41 | return &directChooser{} 42 | } 43 | 44 | func (chooser *directChooser) Choose(_ context.Context, ips []net.IP, _ *ChooseOptions) []net.IP { 45 | return ips 46 | } 47 | 48 | func (chooser *directChooser) FeedbackGood(_ context.Context, _ []net.IP, _ *FeedbackOptions) { 49 | // do nothing 50 | } 51 | 52 | func (chooser *directChooser) FeedbackBad(_ context.Context, _ []net.IP, _ *FeedbackOptions) { 53 | // do nothing 54 | } 55 | 56 | func makeSet(ips []net.IP, domain string, makeKey func(string, net.IP) string) map[string]struct{} { 57 | m := make(map[string]struct{}, len(ips)) 58 | for _, ip := range ips { 59 | m[makeKey(domain, ip)] = struct{}{} 60 | } 61 | return m 62 | } 63 | -------------------------------------------------------------------------------- /storagev2/chooser/chooser_test.go: -------------------------------------------------------------------------------- 1 | //go:build unit 2 | // +build unit 3 | 4 | package chooser_test 5 | 6 | import ( 7 | "context" 8 | "net" 9 | "testing" 10 | 11 | "github.com/qiniu/go-sdk/v7/storagev2/chooser" 12 | ) 13 | 14 | func TestDirectChooser(t *testing.T) { 15 | cs := chooser.NewDirectChooser() 16 | ips := cs.Choose(context.Background(), []net.IP{net.IPv4(1, 2, 3, 4), net.IPv4(5, 6, 7, 8)}, &chooser.ChooseOptions{ 17 | Domain: "www.qiniu.com", 18 | }) 19 | assertIPs(t, ips, []net.IP{net.IPv4(1, 2, 3, 4), net.IPv4(5, 6, 7, 8)}) 20 | } 21 | 22 | func assertIPs(t *testing.T, ips []net.IP, expected []net.IP) { 23 | if len(ips) != len(expected) { 24 | t.Fatalf("unexpected ips count: actual=%v, expected=%v", ips, expected) 25 | } 26 | for i := range ips { 27 | if !ips[i].Equal(expected[i]) { 28 | t.Fatalf("unexpected ip: actual=%v, expected=%v", ips, expected) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /storagev2/chooser/shuffle_chooser.go: -------------------------------------------------------------------------------- 1 | package chooser 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | "net" 7 | ) 8 | 9 | type shuffleChooser struct { 10 | chooser Chooser 11 | } 12 | 13 | // NewShuffleChooser 创建随机混淆选择器 14 | func NewShuffleChooser(chooser Chooser) Chooser { 15 | return &shuffleChooser{chooser: chooser} 16 | } 17 | 18 | func (chooser *shuffleChooser) Choose(ctx context.Context, ips []net.IP, options *ChooseOptions) []net.IP { 19 | chosen_ips := chooser.chooser.Choose(ctx, ips, options) 20 | rand.Shuffle(len(chosen_ips), func(i, j int) { 21 | chosen_ips[i], chosen_ips[j] = chosen_ips[j], chosen_ips[i] 22 | }) 23 | return chosen_ips 24 | } 25 | 26 | func (chooser *shuffleChooser) FeedbackGood(ctx context.Context, ips []net.IP, options *FeedbackOptions) { 27 | chooser.chooser.FeedbackGood(ctx, ips, options) 28 | } 29 | 30 | func (chooser *shuffleChooser) FeedbackBad(ctx context.Context, ips []net.IP, options *FeedbackOptions) { 31 | chooser.chooser.FeedbackBad(ctx, ips, options) 32 | } 33 | -------------------------------------------------------------------------------- /storagev2/chooser/smart_ip_chooser.go: -------------------------------------------------------------------------------- 1 | package chooser 2 | 3 | import ( 4 | "context" 5 | "net" 6 | ) 7 | 8 | type ( 9 | smartIPChooser struct { 10 | ipChooser, subnetChooser Chooser 11 | } 12 | 13 | // SmartIPChooserConfig 智能 IP 选择器的选项 14 | SmartIPChooserConfig IPChooserConfig 15 | ) 16 | 17 | // NewSmartIPChooser 创建智能 IP 选择器 18 | func NewSmartIPChooser(options *SmartIPChooserConfig) Chooser { 19 | return &smartIPChooser{ 20 | ipChooser: NewIPChooser((*IPChooserConfig)(options)), 21 | subnetChooser: NewSubnetChooser((*SubnetChooserConfig)(options)), 22 | } 23 | } 24 | 25 | func (chooser *smartIPChooser) Choose(ctx context.Context, ips []net.IP, options *ChooseOptions) []net.IP { 26 | if chooser.allInSingleSubnet(ips) { 27 | return chooser.ipChooser.Choose(ctx, ips, options) 28 | } else { 29 | return chooser.subnetChooser.Choose(ctx, ips, options) 30 | } 31 | } 32 | 33 | func (chooser *smartIPChooser) FeedbackGood(ctx context.Context, ips []net.IP, options *FeedbackOptions) { 34 | chooser.ipChooser.FeedbackGood(ctx, ips, options) 35 | chooser.subnetChooser.FeedbackGood(ctx, ips, options) 36 | } 37 | 38 | func (chooser *smartIPChooser) FeedbackBad(ctx context.Context, ips []net.IP, options *FeedbackOptions) { 39 | chooser.ipChooser.FeedbackBad(ctx, ips, options) 40 | chooser.subnetChooser.FeedbackBad(ctx, ips, options) 41 | } 42 | 43 | func (chooser *smartIPChooser) allInSingleSubnet(ips []net.IP) bool { 44 | var subnet net.IP 45 | for i, ip := range ips { 46 | if i == 0 { 47 | subnet = makeSubnet(ip) 48 | } else if !subnet.Equal(makeSubnet(ip)) { 49 | return false 50 | } 51 | } 52 | return true 53 | } 54 | -------------------------------------------------------------------------------- /storagev2/doc.go: -------------------------------------------------------------------------------- 1 | // storagev2 包提供了资源管理等功能。 2 | package storagev2 3 | 4 | //go:generate go run ../internal/api-generator -- --api-specs=../api-specs/storage --api-specs=internal/api-specs --output=apis/ --struct-name=Storage --api-package=github.com/qiniu/go-sdk/v7/storagev2/apis 5 | //go:generate go build ./apis/... 6 | -------------------------------------------------------------------------------- /storagev2/downloader/interceptor.go: -------------------------------------------------------------------------------- 1 | package downloader 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/qiniu/go-sdk/v7/internal/clientv2" 7 | ) 8 | 9 | type ( 10 | retryWhenTokenOutOfDateInterceptor struct{} 11 | urlsIterContextKey struct{} 12 | ) 13 | 14 | func (interceptor retryWhenTokenOutOfDateInterceptor) Priority() clientv2.InterceptorPriority { 15 | return clientv2.InterceptorPriorityAuth 16 | } 17 | 18 | func (interceptor retryWhenTokenOutOfDateInterceptor) Intercept(req *http.Request, handler clientv2.Handler) (resp *http.Response, err error) { 19 | if urlsIter, ok := req.Context().Value(urlsIterContextKey{}).(URLsIter); ok { 20 | if _, err = urlsIter.Peek(req.URL); err != nil { 21 | return 22 | } 23 | } 24 | resp, err = handler(req) 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /storagev2/downloader/interfaces.go: -------------------------------------------------------------------------------- 1 | package downloader 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "net/url" 7 | "time" 8 | 9 | "github.com/qiniu/go-sdk/v7/storagev2/downloader/destination" 10 | ) 11 | 12 | type ( 13 | // 获取 URL 迭代器 14 | URLsIter interface { 15 | // 获取首个 URL 16 | Peek(*url.URL) (bool, error) 17 | // 切换到下一个 URL 18 | Next() 19 | // 重置迭代器 20 | Reset() 21 | // 复制迭代器 22 | Clone() URLsIter 23 | } 24 | 25 | // 获取对象下载 URL 接口 26 | DownloadURLsProvider interface { 27 | GetURLsIter(context.Context, string, *GenerateOptions) (URLsIter, error) 28 | } 29 | 30 | // 对下载 URL 签名 31 | Signer interface { 32 | Sign(context.Context, *url.URL, *SignOptions) error 33 | } 34 | 35 | // 下载进度 36 | DownloadingProgress struct { 37 | Downloaded uint64 // 已经下载的数据量,单位为字节 38 | TotalSize uint64 // 总数据量,单位为字节 39 | } 40 | 41 | // 目标下载选项 42 | DestinationDownloadOptions struct { 43 | // 对象下载附加 HTTP Header 44 | Header http.Header 45 | // 对象下载进度 46 | OnDownloadingProgress func(*DownloadingProgress) 47 | // 对象 Header 获取回调 48 | OnResponseHeader func(http.Header) 49 | } 50 | 51 | // 目标下载器 52 | DestinationDownloader interface { 53 | Download(context.Context, URLsIter, destination.Destination, *DestinationDownloadOptions) (uint64, error) 54 | } 55 | 56 | // 对象下载 URL 生成选项 57 | GenerateOptions struct { 58 | // 空间名称,可选 59 | BucketName string 60 | 61 | // 文件处理命令,可选 62 | Command string 63 | 64 | // 是否使用 HTTP 协议,默认为不使用 65 | UseInsecureProtocol bool 66 | } 67 | 68 | // 对象签名选项 69 | SignOptions struct { 70 | // 签名有效期,如果不填写,默认为 3 分钟 71 | TTL time.Duration 72 | } 73 | ) 74 | -------------------------------------------------------------------------------- /storagev2/downloader/resumable_recorder/dummy.go: -------------------------------------------------------------------------------- 1 | package resumablerecorder 2 | 3 | import "time" 4 | 5 | type dummyResumableRecorder struct{} 6 | 7 | // 创建假的可恢复记录仪 8 | func NewDummyResumableRecorder() ResumableRecorder { 9 | return dummyResumableRecorder{} 10 | } 11 | 12 | func (dummyResumableRecorder) OpenForReading(*ResumableRecorderOpenArgs) ReadableResumableRecorderMedium { 13 | return nil 14 | } 15 | 16 | func (dummyResumableRecorder) OpenForAppending(*ResumableRecorderOpenArgs) WriteableResumableRecorderMedium { 17 | return nil 18 | } 19 | 20 | func (dummyResumableRecorder) OpenForCreatingNew(*ResumableRecorderOpenArgs) WriteableResumableRecorderMedium { 21 | return nil 22 | } 23 | 24 | func (dummyResumableRecorder) Delete(*ResumableRecorderOpenArgs) error { 25 | return nil 26 | } 27 | 28 | func (dummyResumableRecorder) ClearOutdated(createdBefore time.Duration) error { 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /storagev2/downloader/resumable_recorder/resumable_recorder.go: -------------------------------------------------------------------------------- 1 | package resumablerecorder 2 | 3 | import ( 4 | "io" 5 | "time" 6 | ) 7 | 8 | type ( 9 | // 可恢复记录仪选项 10 | ResumableRecorderOpenArgs struct { 11 | // 数据源 ETag 12 | ETag string 13 | 14 | // 数据目标 ID 15 | DestinationID string 16 | 17 | // 分片大小 18 | PartSize uint64 19 | 20 | // 数据源大小 21 | TotalSize uint64 22 | 23 | // 数据源偏移量 24 | Offset uint64 25 | } 26 | 27 | // 可恢复记录仪接口 28 | ResumableRecorder interface { 29 | // 打开记录仪介质以读取记录 30 | OpenForReading(*ResumableRecorderOpenArgs) ReadableResumableRecorderMedium 31 | 32 | // 打开记录仪介质以追加记录 33 | OpenForAppending(*ResumableRecorderOpenArgs) WriteableResumableRecorderMedium 34 | 35 | // 新建记录仪介质以追加记录 36 | OpenForCreatingNew(*ResumableRecorderOpenArgs) WriteableResumableRecorderMedium 37 | 38 | // 删除记录仪介质 39 | Delete(*ResumableRecorderOpenArgs) error 40 | 41 | // 清理过期的记录仪介质 42 | ClearOutdated(createdBefore time.Duration) error 43 | } 44 | 45 | // 只读的可恢复记录仪介质接口 46 | ReadableResumableRecorderMedium interface { 47 | io.Closer 48 | 49 | // 读取下一条记录 50 | Next(*ResumableRecord) error 51 | } 52 | 53 | // 只追家的可恢复记录仪介质接口 54 | WriteableResumableRecorderMedium interface { 55 | io.Closer 56 | 57 | // 写入下一条记录 58 | Write(*ResumableRecord) error 59 | } 60 | 61 | // 可恢复记录 62 | ResumableRecord struct { 63 | // 分片偏移量 64 | Offset uint64 65 | 66 | // 分片大小 67 | PartSize uint64 68 | 69 | // 分片写入量 70 | PartWritten uint64 71 | } 72 | ) 73 | -------------------------------------------------------------------------------- /storagev2/downloader/signers.go: -------------------------------------------------------------------------------- 1 | package downloader 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/url" 7 | "strings" 8 | "time" 9 | 10 | "github.com/qiniu/go-sdk/v7/storagev2/credentials" 11 | ) 12 | 13 | type credentialsSigner struct { 14 | credentials credentials.CredentialsProvider 15 | } 16 | 17 | // 创建基于七牛鉴权的下载 URL 签名 18 | func NewCredentialsSigner(credentials credentials.CredentialsProvider) Signer { 19 | return &credentialsSigner{credentials} 20 | } 21 | 22 | func (signer credentialsSigner) Sign(ctx context.Context, u *url.URL, options *SignOptions) error { 23 | if options == nil { 24 | options = &SignOptions{} 25 | } 26 | ttl := options.TTL 27 | if ttl == 0 { 28 | ttl = 3 * time.Minute 29 | } 30 | 31 | cred, err := signer.credentials.Get(ctx) 32 | if err != nil { 33 | return err 34 | } 35 | u.RawQuery += signURL(u.String(), cred, time.Now().Add(ttl).Unix()) 36 | return nil 37 | } 38 | 39 | func signURL(url string, cred *credentials.Credentials, deadline int64) string { 40 | var appendUrl string 41 | 42 | if isURLSigned(url) { 43 | return "" 44 | } 45 | 46 | urlToSign := url 47 | if strings.Contains(url, "?") { 48 | appendUrl = fmt.Sprintf("&e=%d", deadline) 49 | urlToSign += appendUrl 50 | } else { 51 | appendUrl = fmt.Sprintf("e=%d", deadline) 52 | urlToSign += "?" 53 | urlToSign += appendUrl 54 | } 55 | token := cred.Sign([]byte(urlToSign)) 56 | return fmt.Sprintf("%s&token=%s", appendUrl, token) 57 | } 58 | 59 | func isURLSigned(url string) bool { 60 | return (strings.Contains(url, "&e=") || strings.Contains(url, "?e=")) && 61 | strings.Contains(url, "&token=") 62 | } 63 | -------------------------------------------------------------------------------- /storagev2/downloader/urls_provider_utils.go: -------------------------------------------------------------------------------- 1 | package downloader 2 | 3 | import ( 4 | "context" 5 | "net/url" 6 | ) 7 | 8 | // GetURLs 从 DownloadURLsProvider 读取出所有 URL 返回 9 | func GetURLs(ctx context.Context, provider DownloadURLsProvider, objectName string, options *GenerateOptions) ([]*url.URL, error) { 10 | var ok bool 11 | 12 | iter, err := provider.GetURLsIter(ctx, objectName, options) 13 | if err != nil { 14 | return nil, err 15 | } 16 | urls := make([]*url.URL, 0, 16) 17 | for { 18 | u := new(url.URL) 19 | ok, err = iter.Peek(u) 20 | if err != nil || !ok { 21 | break 22 | } 23 | urls = append(urls, u) 24 | iter.Next() 25 | } 26 | return urls, nil 27 | } 28 | 29 | // GetURLStrings 从 DownloadURLsProvider 读取出所有 URL 并转换成字符串返回 30 | func GetURLStrings(ctx context.Context, provider DownloadURLsProvider, objectName string, options *GenerateOptions) ([]string, error) { 31 | urls, err := GetURLs(ctx, provider, objectName, options) 32 | if err != nil { 33 | return nil, err 34 | } 35 | strs := make([]string, len(urls)) 36 | for i, u := range urls { 37 | strs[i] = u.String() 38 | } 39 | return strs, nil 40 | } 41 | -------------------------------------------------------------------------------- /storagev2/errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import "fmt" 4 | 5 | type ( 6 | MissingRequiredFieldError struct { 7 | Name string 8 | } 9 | ) 10 | 11 | func (err MissingRequiredFieldError) Error() string { 12 | return fmt.Sprintf("missing required field `%s`", err.Name) 13 | } 14 | -------------------------------------------------------------------------------- /storagev2/http_client/context.go: -------------------------------------------------------------------------------- 1 | package http_client 2 | 3 | import "context" 4 | 5 | type noSignatureContextKey struct{} 6 | 7 | func WithoutSignature(ctx context.Context) context.Context { 8 | return context.WithValue(ctx, noSignatureContextKey{}, struct{}{}) 9 | } 10 | 11 | func isSignatureDisabled(ctx context.Context) bool { 12 | _, ok := ctx.Value(noSignatureContextKey{}).(struct{}) 13 | return ok 14 | } 15 | -------------------------------------------------------------------------------- /storagev2/http_client/multipart.go: -------------------------------------------------------------------------------- 1 | package http_client 2 | 3 | import compatible_io "github.com/qiniu/go-sdk/v7/internal/io" 4 | 5 | type MultipartFormBinaryData struct { 6 | Data compatible_io.ReadSeekCloser 7 | Name string 8 | ContentType string 9 | } 10 | -------------------------------------------------------------------------------- /storagev2/internal/api-specs/add_bucket_event_rule.yml: -------------------------------------------------------------------------------- 1 | method: post 2 | service_names: 3 | - uc 4 | base_path: /events/add 5 | documentation: 增加存储空间事件通知规则 6 | request: 7 | authorization: qiniu 8 | query_names: 9 | - field_name: bucket 10 | query_name: bucket 11 | query_type: string 12 | documentation: 存储空间名称 13 | service_bucket: plain_text 14 | - field_name: name 15 | query_name: name 16 | query_type: string 17 | documentation: 规则名称,bucket 内唯一,长度小于 50,不能为空,只能为字母、数字、下划线 18 | - field_name: prefix 19 | query_name: prefix 20 | query_type: string 21 | documentation: 匹配文件前缀 22 | optional: omitempty 23 | - field_name: suffix 24 | query_name: suffix 25 | query_type: string 26 | documentation: 匹配文件后缀 27 | optional: omitempty 28 | - field_name: event_types 29 | query_name: event 30 | query_type: string 31 | multiple: true 32 | documentation: 事件类型,可以指定多个,包括 put,mkfile,delete,copy,move,append,disable,enable,deleteMarkerCreate,predelete(删除之前触发),restore:completed 33 | - field_name: callback_urls 34 | query_name: callbackURL 35 | query_type: string 36 | multiple: true 37 | documentation: 回调地址,可以指定多个 38 | - field_name: access_key 39 | query_name: accessKey 40 | query_type: string 41 | documentation: 设置的话会对通知请求用对应的 accessKey,secretKey 进行签名 42 | optional: omitempty 43 | - field_name: host 44 | query_name: host 45 | query_type: string 46 | documentation: 通知请求的 Host 47 | optional: omitempty 48 | -------------------------------------------------------------------------------- /storagev2/internal/api-specs/delete_bucket_event_rule.yml: -------------------------------------------------------------------------------- 1 | method: post 2 | service_names: 3 | - uc 4 | base_path: /events/delete 5 | documentation: 删除存储空间事件通知规则 6 | request: 7 | authorization: qiniu 8 | query_names: 9 | - field_name: bucket 10 | query_name: bucket 11 | query_type: string 12 | documentation: 存储空间名称 13 | service_bucket: plain_text 14 | - field_name: name 15 | query_name: name 16 | query_type: string 17 | documentation: 规则名称 18 | -------------------------------------------------------------------------------- /storagev2/internal/api-specs/disable_bucket_index_page.yml: -------------------------------------------------------------------------------- 1 | method: post 2 | service_names: 3 | - uc 4 | base_path: /noIndexPage 5 | documentation: 禁用存储空间 index.html(或 index.htm) 页面 6 | request: 7 | authorization: qiniu 8 | query_names: 9 | - field_name: bucket 10 | query_name: bucket 11 | query_type: string 12 | documentation: 存储空间名称 13 | service_bucket: plain_text 14 | - field_name: no_index_page 15 | query_name: noIndexPage 16 | query_type: integer 17 | documentation: 是否禁用 index.html(或 index.htm) 页面 18 | optional: keepempty 19 | -------------------------------------------------------------------------------- /storagev2/internal/api-specs/get_bucket_domains_v3.yml: -------------------------------------------------------------------------------- 1 | method: get 2 | service_names: 3 | - uc 4 | base_path: /v3/domains 5 | documentation: 获取存储空间的域名列表 6 | request: 7 | authorization: qiniu 8 | query_names: 9 | - field_name: bucket_name 10 | query_name: tbl 11 | query_type: string 12 | documentation: 要获取域名列表的目标空间名称 13 | service_bucket: plain_text 14 | response: 15 | body: 16 | json: 17 | array: 18 | name: DomainInfos 19 | documentation: 存储空间的域名信息列表 20 | type: 21 | struct: 22 | name: DomainInfo 23 | documentation: 存储空间的域名信息 24 | fields: 25 | - field_name: domain 26 | key: domain 27 | type: string 28 | documentation: 域名 29 | - field_name: bucket 30 | key: tbl 31 | type: string 32 | documentation: 存储空间名称 33 | - field_name: owner_id 34 | key: uid 35 | type: integer 36 | documentation: 用户 UID 37 | - field_name: auto_refresh 38 | key: refresh 39 | type: boolean 40 | documentation: 是否自动刷新 41 | - field_name: created_time 42 | key: ctime 43 | type: integer 44 | documentation: 域名创建时间 45 | - field_name: updated_time 46 | key: utime 47 | type: integer 48 | documentation: 域名更新时间 49 | -------------------------------------------------------------------------------- /storagev2/internal/api-specs/set_bucket_image.yml: -------------------------------------------------------------------------------- 1 | method: post 2 | service_names: 3 | - uc 4 | base_path: /image 5 | documentation: 设置源站镜像回源 6 | request: 7 | authorization: qiniu 8 | path_params: 9 | named: 10 | - field_name: bucket 11 | type: string 12 | documentation: 存储空间名称 13 | service_bucket: plain_text 14 | - path_segment: from 15 | field_name: url 16 | type: string 17 | documentation: 回源 URL 18 | encode: url_safe_base64 19 | - path_segment: host 20 | field_name: host 21 | type: string 22 | documentation: 从指定源站下载数据时使用的 Host 23 | optional: omitempty 24 | -------------------------------------------------------------------------------- /storagev2/internal/api-specs/set_bucket_refer_anti_leech.yml: -------------------------------------------------------------------------------- 1 | method: post 2 | service_names: 3 | - uc 4 | base_path: /referAntiLeech 5 | documentation: 设置存储空间的防盗链模式 6 | request: 7 | authorization: qiniu 8 | query_names: 9 | - field_name: bucket 10 | query_name: bucket 11 | query_type: string 12 | documentation: 存储空间名称 13 | service_bucket: plain_text 14 | - field_name: mode 15 | query_name: mode 16 | query_type: integer 17 | documentation: 设置防盗链模式,0:表示关闭 Referer(使用此选项将会忽略以下参数并将恢复默认值); 1:表示设置 Referer 白名单; 2:表示设置 Referer 黑名单 18 | optional: keepempty 19 | - field_name: pattern 20 | query_name: pattern 21 | query_type: string 22 | documentation: 规则字符串,当前允许格式分为三种:一种为空主机头域名,比如 `foo.com`; 一种是泛域名,比如 `*.bar.com`; 一种是完全通配符,即一个 `*`; 多个规则之间用`;`隔开,比如:`foo.com;*.bar.com;sub.foo.com;*.sub.bar.com` 23 | optional: keepempty 24 | - field_name: allow_empty_referer 25 | query_name: norefer 26 | query_type: integer 27 | documentation: 0:表示不允许空 Refer 访问; 1:表示允许空 Refer 访问 28 | optional: keepempty 29 | - field_name: source_enabled 30 | query_name: source_enabled 31 | query_type: integer 32 | documentation: 源站是否支持,默认为 0 只给 CDN 配置, 设置为 1 表示开启源站防盗链 33 | optional: keepempty 34 | -------------------------------------------------------------------------------- /storagev2/internal/api-specs/unset_bucket_image.yml: -------------------------------------------------------------------------------- 1 | method: post 2 | service_names: 3 | - uc 4 | base_path: /unimage 5 | documentation: 取消源站镜像回源 6 | request: 7 | authorization: qiniu 8 | path_params: 9 | named: 10 | - field_name: bucket 11 | type: string 12 | documentation: 存储空间名称 13 | service_bucket: plain_text 14 | -------------------------------------------------------------------------------- /storagev2/internal/api-specs/update_bucket_event_rule.yml: -------------------------------------------------------------------------------- 1 | method: post 2 | service_names: 3 | - uc 4 | base_path: /events/update 5 | documentation: 修改存储空间事件通知规则 6 | request: 7 | authorization: qiniu 8 | query_names: 9 | - field_name: bucket 10 | query_name: bucket 11 | query_type: string 12 | documentation: 存储空间名称 13 | service_bucket: plain_text 14 | - field_name: name 15 | query_name: name 16 | query_type: string 17 | documentation: 规则名称,bucket 内唯一,长度小于 50,不能为空,只能为字母、数字、下划线 18 | - field_name: prefix 19 | query_name: prefix 20 | query_type: string 21 | documentation: 匹配文件前缀 22 | optional: omitempty 23 | - field_name: suffix 24 | query_name: suffix 25 | query_type: string 26 | documentation: 匹配文件后缀 27 | optional: omitempty 28 | - field_name: event_types 29 | query_name: event 30 | query_type: string 31 | multiple: true 32 | documentation: 事件类型,可以指定多个,包括 put,mkfile,delete,copy,move,append,disable,enable,deleteMarkerCreate,predelete(删除之前触发),restore:completed 33 | - field_name: callback_urls 34 | query_name: callbackURL 35 | query_type: string 36 | multiple: true 37 | documentation: 回调地址,可以指定多个 38 | - field_name: access_key 39 | query_name: accessKey 40 | query_type: string 41 | documentation: 设置的话会对通知请求用对应的 accessKey,secretKey 进行签名 42 | optional: omitempty 43 | - field_name: host 44 | query_name: host 45 | query_type: string 46 | documentation: 通知请求的 Host 47 | optional: omitempty 48 | -------------------------------------------------------------------------------- /storagev2/objects/bucket.go: -------------------------------------------------------------------------------- 1 | package objects 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | ) 7 | 8 | type ( 9 | // 存储空间 10 | Bucket struct { 11 | name string 12 | objectsManager *ObjectsManager 13 | } 14 | 15 | // 列举对象选项 16 | ListObjectsOptions struct { 17 | Limit *uint64 // 最大列举数量 18 | Prefix string // 前缀 19 | Marker string // 标记 20 | NeedParts bool // 是否需要分片信息 21 | } 22 | ) 23 | 24 | // 存储空间名称 25 | func (bucket *Bucket) Name() string { 26 | return bucket.name 27 | } 28 | 29 | // 获取存储空间对象 30 | func (bucket *Bucket) Object(name string) *Object { 31 | return &Object{bucket, name} 32 | } 33 | 34 | // 获取存储空间目录 35 | func (bucket *Bucket) Directory(prefix, pathSeparator string) *Directory { 36 | if pathSeparator == "" { 37 | pathSeparator = "/" 38 | } 39 | if prefix != "" && !strings.HasSuffix(prefix, pathSeparator) { 40 | prefix += pathSeparator 41 | } 42 | return &Directory{bucket, prefix, pathSeparator} 43 | } 44 | 45 | // 列举对象 46 | func (bucket *Bucket) List(ctx context.Context, options *ListObjectsOptions) Lister { 47 | if options == nil { 48 | options = &ListObjectsOptions{} 49 | } 50 | 51 | switch bucket.objectsManager.listerVersion { 52 | case ListerVersionV1: 53 | fallthrough 54 | default: 55 | return newListerV1(ctx, bucket, options) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /storagev2/retrier/dns1.12.go: -------------------------------------------------------------------------------- 1 | //go:build !1.13 2 | // +build !1.13 3 | 4 | package retrier 5 | 6 | import "net" 7 | 8 | func isDnsNotFoundError(dnsError *net.DNSError) bool { 9 | return !dnsError.IsTemporary 10 | } 11 | -------------------------------------------------------------------------------- /storagev2/retrier/dns1.13.go: -------------------------------------------------------------------------------- 1 | //go:build 1.13 2 | // +build 1.13 3 | 4 | package retrier 5 | 6 | import "net" 7 | 8 | func isDnsNotFoundError(dnsError *net.DNSError) bool { 9 | return dnsError.IsNotFound 10 | } 11 | -------------------------------------------------------------------------------- /storagev2/uploader/resumable_recorder/dummy.go: -------------------------------------------------------------------------------- 1 | package resumablerecorder 2 | 3 | type dummyResumableRecorder struct{} 4 | 5 | // 创建假的可恢复记录仪 6 | func NewDummyResumableRecorder() ResumableRecorder { 7 | return dummyResumableRecorder{} 8 | } 9 | 10 | func (dummyResumableRecorder) OpenForReading(*ResumableRecorderOpenArgs) ReadableResumableRecorderMedium { 11 | return nil 12 | } 13 | 14 | func (dummyResumableRecorder) OpenForAppending(*ResumableRecorderOpenArgs) WriteableResumableRecorderMedium { 15 | return nil 16 | } 17 | 18 | func (dummyResumableRecorder) OpenForCreatingNew(*ResumableRecorderOpenArgs) WriteableResumableRecorderMedium { 19 | return nil 20 | } 21 | 22 | func (dummyResumableRecorder) Delete(*ResumableRecorderOpenArgs) error { 23 | return nil 24 | } 25 | 26 | func (dummyResumableRecorder) ClearExpired() error { 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net/http" 7 | 8 | internal_io "github.com/qiniu/go-sdk/v7/internal/io" 9 | ) 10 | 11 | // BytesFromRequest 读取 http.Request.Body 的内容到 slice 中 12 | func BytesFromRequest(r *http.Request) ([]byte, error) { 13 | if bytesNopCloser, ok := r.Body.(*internal_io.BytesNopCloser); ok { 14 | return bytesNopCloser.Bytes(), nil 15 | } 16 | buf := bytes.NewBuffer(make([]byte, 0, int(r.ContentLength)+1024)) 17 | _, err := io.Copy(buf, r.Body) 18 | if err != nil { 19 | return nil, err 20 | } 21 | return buf.Bytes(), nil 22 | } 23 | 24 | // SeekerLen 通过 io.Seeker 获取数据大小 25 | func SeekerLen(s io.Seeker) (int64, error) { 26 | 27 | curOffset, err := s.Seek(0, io.SeekCurrent) 28 | if err != nil { 29 | return 0, err 30 | } 31 | 32 | endOffset, err := s.Seek(0, io.SeekEnd) 33 | if err != nil { 34 | return 0, err 35 | } 36 | 37 | _, err = s.Seek(curOffset, io.SeekStart) 38 | if err != nil { 39 | return 0, err 40 | } 41 | 42 | return endOffset - curOffset, nil 43 | } 44 | --------------------------------------------------------------------------------