k3cloud-sdk 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -2
- data/CHANGELOG.md +23 -3
- data/Gemfile.lock +1 -1
- data/README.md +240 -51
- data/k3cloud.gemspec +2 -0
- data/lib/k3cloud/configuration.rb +25 -1
- data/lib/k3cloud/errors/configuration_error.rb +6 -0
- data/lib/k3cloud/errors/k3cloud_error.rb +1 -1
- data/lib/k3cloud/http.rb +67 -19
- data/lib/k3cloud/k3cloud_api.rb +45 -12
- data/lib/k3cloud/version.rb +1 -1
- data/lib/k3cloud/web_api_client.rb +26 -5
- data/lib/k3cloud.rb +19 -10
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac60c158c0d8bb63fa78e7412f1a7207ce7317f6bbaa1cbe751556740987770d
|
|
4
|
+
data.tar.gz: 8ab6ad5bff05101523fc21ac786d4c7611713bc5283f0cce6e6dd2ea61b5c030
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '01281a40210d978bdda71290370b0e05a1504ee23f923ab54935e2858e6e66c5ae56ade375f9d674fb077d755fa043091208a4e681045aa9d9acc02befc2170f'
|
|
7
|
+
data.tar.gz: da96968ea62312e524f5abe5bad731506a23b07221060e6aef64751c653bbbd4ae45e8ae6e44f5240567858fa6a20e4bbca92ef831d50e2b4a37a493ba744260
|
data/.rubocop.yml
CHANGED
|
@@ -25,7 +25,7 @@ Style/Documentation:
|
|
|
25
25
|
Enabled: false
|
|
26
26
|
|
|
27
27
|
Gemspec/RequireMFA:
|
|
28
|
-
Enabled:
|
|
28
|
+
Enabled: true
|
|
29
29
|
|
|
30
30
|
Style/FormatStringToken:
|
|
31
31
|
Enabled: false
|
|
@@ -52,4 +52,7 @@ Metrics/CyclomaticComplexity:
|
|
|
52
52
|
Max: 12
|
|
53
53
|
|
|
54
54
|
Metrics/ParameterLists:
|
|
55
|
-
Max:
|
|
55
|
+
Max: 10
|
|
56
|
+
|
|
57
|
+
Metrics/ClassLength:
|
|
58
|
+
Max: 150
|
data/CHANGELOG.md
CHANGED
|
@@ -5,14 +5,33 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.6.0]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- 请求重试:`Configuration#retry_max` / `retry_interval` / `retry_backoff`,仅对超时与 5xx 生效,默认重试 3 次指数退避。
|
|
13
|
+
- trace 日志:DEBUG 级记录每次请求的耗时(`elapsed_ms`)、状态码、重试次数、响应字节数。
|
|
14
|
+
- 单据查询分页封装 `execute_bill_query_all(data, limit:)`,自动按 `StartRow`/`Limit` 拉取全部单据。
|
|
15
|
+
- 配置校验:必填字段缺失时抛出 `K3cloud::ConfigurationError`,给出友好提示。
|
|
16
|
+
- 新增 `SECURITY.md` 漏洞上报流程与安全说明。
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- 默认启用请求重试(`retry_max` 默认 3,原 0.5.0 不重试);如需保持不重试请设 `retry_max = 0`。
|
|
21
|
+
- `verify_ssl` 文档明确仅 https 生效(http URL 自动跳过)。
|
|
22
|
+
- 日志调用统一为字符串:text 模式输出干净字符串,json 模式包装为 `{msg, level, time}`。
|
|
23
|
+
- `handle_query_result` 错误识别更鲁棒:兼容顶层 Hash 与行内任意列含 `Message`/`InnerExWrapper`/`InnerException` 的错误对象。
|
|
24
|
+
- `WebApiClient#url_path` 对非 http(s) URL 抛 `ArgumentError`,避免签名错误。
|
|
25
|
+
- gemspec 启用 `metadata["rubygems_mfa_required"] = "true"`,提升发布安全。
|
|
26
|
+
- 异常与日志消息英文化。
|
|
27
|
+
- README 重写为英文,按目录组织并标注全部可调用方法。
|
|
9
28
|
|
|
10
29
|
## [0.5.0] - 2026-07-17
|
|
11
30
|
|
|
12
31
|
### ⚠ Breaking Changes
|
|
13
32
|
|
|
14
33
|
- 最低 Ruby 版本提升至 `>= 2.5.0`。
|
|
15
|
-
- 默认启用 HTTPS
|
|
34
|
+
- 默认启用 HTTPS 证书校验;私有云 https 自签证书需显式设置 `Configuration#verify_ssl = false`。
|
|
16
35
|
- `Configuration` 为 `lcid`、`connect_timeout`(30s)、`request_timeout`(60s)、`verify_ssl` 提供默认值。
|
|
17
36
|
- 工具类与常量迁移至 `K3cloud` 命名空间(`K3cloud::Base64Utils`、`K3cloud::MD5Utils`、`K3cloud::ConstDefine`),不再保留无命名空间别名。
|
|
18
37
|
- `Http` 构造参数改为关键字参数。
|
|
@@ -21,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
21
40
|
|
|
22
41
|
- 构造签名头失败时不再静默返回空 header,改为抛出异常。
|
|
23
42
|
- 签名密钥为空时 `MD5Utils.hash_mac` 改为抛出 `ArgumentError`。
|
|
43
|
+
- `K3cloudError.parse` 对空索引做判空保护,避免 `NoMethodError`。
|
|
24
44
|
|
|
25
45
|
### Fixed
|
|
26
46
|
|
|
@@ -98,4 +118,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
98
118
|
|
|
99
119
|
### Added
|
|
100
120
|
|
|
101
|
-
- 初始版本:封装金蝶云星空 OpenApi,涵盖暂存/保存/提交/审核/查看/查询/删除/下推/分配等单据操作及附件、工作流、报表等共 26 个业务方法。
|
|
121
|
+
- 初始版本:封装金蝶云星空 OpenApi,涵盖暂存/保存/提交/审核/查看/查询/删除/下推/分配等单据操作及附件、工作流、报表等共 26 个业务方法。
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
|
-
# K3cloud
|
|
1
|
+
# K3cloud SDK
|
|
2
2
|
|
|
3
|
-
Ruby Gem for
|
|
3
|
+
Ruby Gem for Kingdee K3Cloud OpenApi. Uses cryptographic signature technology
|
|
4
|
+
to avoid plaintext transmission of keys and supports automatic login.
|
|
5
|
+
|
|
6
|
+
## Table of Contents
|
|
7
|
+
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Configuration](#configuration)
|
|
10
|
+
- [Single Account](#single-account)
|
|
11
|
+
- [Multi Account](#multi-account)
|
|
12
|
+
- [Optional Settings](#optional-settings)
|
|
13
|
+
- [Error Handling](#error-handling)
|
|
14
|
+
- [In Scheduled Jobs](#in-scheduled-jobs-sidekiq--cron--rake)
|
|
15
|
+
- [Available API Methods](#available-api-methods)
|
|
16
|
+
- [Bill Operations](#bill-operations)
|
|
17
|
+
- [Query Operations](#query-operations)
|
|
18
|
+
- [Group Operations](#group-operations)
|
|
19
|
+
- [Attachment Operations](#attachment-operations)
|
|
20
|
+
- [Metadata & Report Operations](#metadata--report-operations)
|
|
21
|
+
- [Workflow & Organization Operations](#workflow--organization-operations)
|
|
22
|
+
- [Logger](#logger)
|
|
23
|
+
- [Development](#development)
|
|
24
|
+
- [Contributing](#contributing)
|
|
25
|
+
- [License](#license)
|
|
26
|
+
- [Code of Conduct](#code-of-conduct)
|
|
4
27
|
|
|
5
28
|
## Installation
|
|
6
29
|
|
|
7
|
-
Install the gem and add to
|
|
30
|
+
Install the gem and add it to a Rails application's Gemfile:
|
|
8
31
|
|
|
9
32
|
$ bundle add k3cloud-sdk
|
|
10
33
|
|
|
@@ -12,85 +35,251 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
|
12
35
|
|
|
13
36
|
$ gem install k3cloud-sdk
|
|
14
37
|
|
|
15
|
-
##
|
|
38
|
+
## Configuration
|
|
16
39
|
|
|
17
|
-
|
|
40
|
+
### Single Account
|
|
18
41
|
|
|
19
|
-
|
|
42
|
+
Configure it in `config/initializers/k3cloud.rb`:
|
|
20
43
|
|
|
21
44
|
```ruby
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
45
|
+
K3cloud.configure do |config|
|
|
46
|
+
config.acct_id = ENV["K3CLOUD_ACCT_ID"]
|
|
47
|
+
config.user_name = ENV["K3CLOUD_USERNAME"]
|
|
48
|
+
config.app_id = ENV["K3CLOUD_APP_ID"]
|
|
49
|
+
config.app_secret = ENV["K3CLOUD_APP_SECRET"]
|
|
50
|
+
config.server_url = ENV["K3CLOUD_SERVER_URL"] # blank for public cloud
|
|
51
|
+
end
|
|
29
52
|
```
|
|
30
53
|
|
|
31
|
-
Then
|
|
54
|
+
Then call the K3Cloud API directly:
|
|
32
55
|
|
|
33
56
|
```ruby
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
57
|
+
data = {
|
|
58
|
+
FormId: "PRD_MO",
|
|
59
|
+
FieldKeys: "FMaterialId.FNumber, FNoStockInQty",
|
|
60
|
+
FilterString: "FStatus not in (6, 7)"
|
|
61
|
+
}
|
|
62
|
+
K3cloud.execute_bill_query(data)
|
|
40
63
|
```
|
|
41
64
|
|
|
42
|
-
|
|
65
|
+
### Multi Account
|
|
43
66
|
|
|
44
|
-
|
|
67
|
+
Create multiple `K3cloud::Configuration` instances and build clients for each:
|
|
45
68
|
|
|
46
69
|
```ruby
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
70
|
+
config1 = K3cloud::Configuration.new do |c|
|
|
71
|
+
c.acct_id = ENV["K3CLOUD1_ACCT_ID"]
|
|
72
|
+
c.user_name = ENV["K3CLOUD1_USERNAME"]
|
|
73
|
+
c.app_id = ENV["K3CLOUD1_APP_ID"]
|
|
74
|
+
c.app_secret = ENV["K3CLOUD1_APP_SECRET"]
|
|
75
|
+
c.server_url = ENV["K3CLOUD1_SERVER_URL"]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
config2 = K3cloud::Configuration.new do |c|
|
|
79
|
+
c.acct_id = ENV["K3CLOUD2_ACCT_ID"]
|
|
80
|
+
c.user_name = ENV["K3CLOUD2_USERNAME"]
|
|
81
|
+
c.app_id = ENV["K3CLOUD2_APP_ID"]
|
|
82
|
+
c.app_secret = ENV["K3CLOUD2_APP_SECRET"]
|
|
83
|
+
c.server_url = ENV["K3CLOUD2_SERVER_URL"]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
client1 = K3cloud.new_api(config1)
|
|
87
|
+
client2 = K3cloud.new_api(config2)
|
|
54
88
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
89
|
+
client1.execute_bill_query(data)
|
|
90
|
+
client2.execute_bill_query(data)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Optional Settings
|
|
94
|
+
|
|
95
|
+
| Option | Default | Description |
|
|
96
|
+
| --- | --- | --- |
|
|
97
|
+
| `verify_ssl` | `true` | HTTPS certificate verification. Only affects `https` URLs (http URLs skip SSL automatically). Set `false` for private cloud self-signed certificates. |
|
|
98
|
+
| `connect_timeout` | `30` | Connection timeout in seconds. |
|
|
99
|
+
| `request_timeout` | `60` | Request read timeout in seconds. |
|
|
100
|
+
| `retry_max` | `3` | Max retries on timeout / 5xx. `0` disables retry. |
|
|
101
|
+
| `retry_interval` | `1` | First retry interval in seconds, then exponential backoff. |
|
|
102
|
+
| `retry_backoff` | `2` | Backoff multiplier between retries. |
|
|
103
|
+
| `log_format` | `:text` | Logger output format: `:text` or `:json`. |
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
K3cloud.configure do |config|
|
|
107
|
+
# ... required fields above ...
|
|
108
|
+
config.verify_ssl = true
|
|
109
|
+
config.connect_timeout = 30
|
|
110
|
+
config.request_timeout = 60
|
|
111
|
+
config.retry_max = 3
|
|
112
|
+
config.retry_interval = 1
|
|
113
|
+
config.log_format = :json
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Error Handling
|
|
118
|
+
|
|
119
|
+
All request failures raise `K3cloud::ResponseError`. A missing or invalid
|
|
120
|
+
configuration raises `K3cloud::ConfigurationError` on the first request. Wrap
|
|
121
|
+
calls accordingly:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
begin
|
|
125
|
+
K3cloud.save("PRD_MO", data)
|
|
126
|
+
rescue K3cloud::ConfigurationError => e
|
|
127
|
+
# missing config: app_id / app_secret / acct_id / user_name
|
|
128
|
+
logger.error("Config error: #{e.message}")
|
|
129
|
+
rescue K3cloud::ResponseError => e
|
|
130
|
+
# network or Kingdee business error
|
|
131
|
+
logger.error("Request failed: #{e.message}")
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### In Scheduled Jobs (Sidekiq / cron / rake)
|
|
136
|
+
|
|
137
|
+
The SDK raises `K3cloud::ResponseError` after retries are exhausted. Always
|
|
138
|
+
`rescue` explicitly in scheduled jobs to prevent the exception from bubbling up
|
|
139
|
+
and failing the container/process:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
class SyncK3cloudJob
|
|
143
|
+
def perform
|
|
144
|
+
rows = K3cloud.execute_bill_query_all({ FormId: "PRD_MO" }, limit: 1000)
|
|
145
|
+
# ... process rows ...
|
|
146
|
+
rescue K3cloud::ConfigurationError => e
|
|
147
|
+
logger.error("[k3cloud] config error: #{e.message}")
|
|
148
|
+
rescue K3cloud::ResponseError => e
|
|
149
|
+
logger.error("[k3cloud] request failed: #{e.message}")
|
|
150
|
+
# Re-raise so the scheduler (e.g. Sidekiq) can retry the job
|
|
151
|
+
raise e
|
|
61
152
|
end
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
> Tip: In scheduled jobs, set a small `retry_max` (e.g. 0 or 1) and let the
|
|
157
|
+
> job scheduler decide the next attempt, instead of sleeping inside the SDK
|
|
158
|
+
> and occupying the worker thread. For example:
|
|
159
|
+
> `K3cloud.configure { |c| c.retry_max = 1 }`.
|
|
160
|
+
|
|
161
|
+
## Available API Methods
|
|
162
|
+
|
|
163
|
+
All methods are available on both:
|
|
164
|
+
- `K3cloud.<method>` — uses the default account (delegates to `K3cloud.default_api`).
|
|
165
|
+
- `K3cloud.new_api(config).<method>` — uses a specific account configuration.
|
|
166
|
+
|
|
167
|
+
### Bill Operations
|
|
168
|
+
|
|
169
|
+
| Method | Signature | Description |
|
|
170
|
+
| --- | --- | --- |
|
|
171
|
+
| `draft` | `draft(form_id, data)` | Temporarily save a bill (暂存). |
|
|
172
|
+
| `save` | `save(form_id, data)` | Save a bill (保存). |
|
|
173
|
+
| `batch_save` | `batch_save(form_id, data)` | Batch save bills (批量保存). |
|
|
174
|
+
| `submit` | `submit(form_id, data)` | Submit a bill (提交). |
|
|
175
|
+
| `audit` | `audit(form_id, data)` | Audit a bill (审核). |
|
|
176
|
+
| `un_audit` | `un_audit(form_id, data)` | Un-audit a bill (反审核). |
|
|
177
|
+
| `view` | `view(form_id, data)` | View a bill detail (查看). |
|
|
178
|
+
| `delete` | `delete(form_id, data)` | Delete a bill (删除). |
|
|
179
|
+
| `cancel_assign` | `cancel_assign(form_id, data)` | Cancel assignment (撤销). |
|
|
180
|
+
| `push` | `push(form_id, data)` | Push down a bill (下推). |
|
|
181
|
+
| `execute_operation` | `execute_operation(form_id, op_number, data)` | Status transition: close/reopen/void/unvoid etc. (状态转换). |
|
|
182
|
+
| `allocate` | `allocate(form_id, data)` | Allocate a bill (分配). |
|
|
183
|
+
| `cancel_allocate` | `cancel_allocate(form_id, data)` | Cancel allocation (取消分配). |
|
|
184
|
+
| `disassembly` | `disassembly(form_id, data)` | Disassembly operation (拆单). |
|
|
185
|
+
| `flex_save` | `flex_save(form_id, data)` | Flex save (弹性域保存). |
|
|
186
|
+
| `send_msg` | `send_msg(form_id, data)` | Send message (发送消息). |
|
|
187
|
+
|
|
188
|
+
### Query Operations
|
|
189
|
+
|
|
190
|
+
| Method | Signature | Description |
|
|
191
|
+
| --- | --- | --- |
|
|
192
|
+
| `execute_bill_query` | `execute_bill_query(data)` | Query bills with parameters `{ FormId:, FieldKeys:, FilterString: }` (单据查询). Returns a 2D array; returns `[]` on error. |
|
|
193
|
+
| `execute_bill_query_all` | `execute_bill_query_all(data, limit: 1000)` | Paginated query that fetches **all** bill rows by auto-injecting `StartRow` and `Limit` until a page returns fewer than `limit` rows (分页批量查询). Returns the merged 2D array. |
|
|
194
|
+
|
|
195
|
+
Example:
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
# Single-page query (you handle pagination manually)
|
|
199
|
+
rows = K3cloud.execute_bill_query(
|
|
200
|
+
FormId: "PRD_MO",
|
|
201
|
+
FieldKeys: "FMaterialId.FNumber, FNoStockInQty",
|
|
202
|
+
FilterString: "FStatus not in (6, 7)",
|
|
203
|
+
StartRow: 0,
|
|
204
|
+
Limit: 100
|
|
205
|
+
)
|
|
62
206
|
|
|
63
|
-
|
|
64
|
-
|
|
207
|
+
# Paginated query (SDK handles pagination for you)
|
|
208
|
+
all_rows = K3cloud.execute_bill_query_all(
|
|
209
|
+
{ FormId: "PRD_MO", FieldKeys: "FMaterialId.FNumber", FilterString: "" },
|
|
210
|
+
limit: 1000
|
|
211
|
+
)
|
|
65
212
|
```
|
|
66
213
|
|
|
67
|
-
|
|
214
|
+
### Group Operations
|
|
215
|
+
|
|
216
|
+
| Method | Signature | Description |
|
|
217
|
+
| --- | --- | --- |
|
|
218
|
+
| `group_save` | `group_save(form_id, data)` | Save a group (分组保存). |
|
|
219
|
+
| `query_group_info` | `query_group_info(data)` | Query group info (分组信息查询). |
|
|
220
|
+
| `group_delete` | `group_delete(data)` | Delete a group (分组删除). |
|
|
221
|
+
|
|
222
|
+
### Attachment Operations
|
|
223
|
+
|
|
224
|
+
| Method | Signature | Description |
|
|
225
|
+
| --- | --- | --- |
|
|
226
|
+
| `attachment_upload` | `attachment_upload(data)` | Upload an attachment (附件上传). |
|
|
227
|
+
| `attachment_download` | `attachment_download(data)` | Download an attachment (附件下载). |
|
|
228
|
+
|
|
229
|
+
### Metadata & Report Operations
|
|
230
|
+
|
|
231
|
+
| Method | Signature | Description |
|
|
232
|
+
| --- | --- | --- |
|
|
233
|
+
| `query_business_info` | `query_business_info(data)` | Query business metadata (元数据查询). |
|
|
234
|
+
| `get_sys_report_data` | `get_sys_report_data(form_id, data)` | Query system report data (查询报表数据). |
|
|
235
|
+
|
|
236
|
+
### Workflow & Organization Operations
|
|
237
|
+
|
|
238
|
+
| Method | Signature | Description |
|
|
239
|
+
| --- | --- | --- |
|
|
240
|
+
| `workflow_audit` | `workflow_audit(data)` | Workflow audit (工作流审核). |
|
|
241
|
+
| `switch_org` | `switch_org(data)` | Switch organization (切换组织). |
|
|
242
|
+
|
|
243
|
+
## Logger
|
|
244
|
+
|
|
245
|
+
By default, logs are written to `$stderr` in plain text. You can switch to
|
|
246
|
+
JSON-per-line format (useful for log aggregation) or inject your own logger.
|
|
68
247
|
|
|
69
248
|
```ruby
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
client2.execute_bill_query(data)
|
|
249
|
+
# JSON format
|
|
250
|
+
K3cloud.configure { |c| c.log_format = :json }
|
|
251
|
+
K3cloud.reset_logger! # rebuild the default logger after changing log_format
|
|
252
|
+
|
|
253
|
+
# Inject a custom logger (e.g. Rails.logger)
|
|
254
|
+
K3cloud.logger = Rails.logger
|
|
77
255
|
```
|
|
78
256
|
|
|
79
257
|
## Development
|
|
80
258
|
|
|
81
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
259
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
260
|
+
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
|
261
|
+
prompt that will allow you to experiment.
|
|
82
262
|
|
|
83
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
263
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
264
|
+
release a new version, update the version number in `version.rb`, and then run
|
|
265
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
|
266
|
+
git commits and the created tag, and push the `.gem` file to
|
|
267
|
+
[rubygems.org](https://rubygems.org).
|
|
84
268
|
|
|
85
269
|
## Contributing
|
|
86
270
|
|
|
87
|
-
Bug reports and pull requests are welcome on GitHub at
|
|
88
|
-
This project is intended to be a safe,
|
|
271
|
+
Bug reports and pull requests are welcome on GitHub at
|
|
272
|
+
https://github.com/zevinto/k3cloud-sdk. This project is intended to be a safe,
|
|
273
|
+
welcoming space for collaboration, and contributors are expected to adhere to
|
|
274
|
+
the [code of conduct](https://github.com/zevinto/k3cloud-sdk/blob/main/CODE_OF_CONDUCT.md).
|
|
89
275
|
|
|
90
276
|
## License
|
|
91
277
|
|
|
92
|
-
The gem is available as open source under the terms of the
|
|
278
|
+
The gem is available as open source under the terms of the
|
|
279
|
+
[MIT License](https://opensource.org/licenses/MIT).
|
|
93
280
|
|
|
94
281
|
## Code of Conduct
|
|
95
282
|
|
|
96
|
-
Everyone interacting in the K3cloud-sdk project's codebases, issue trackers,
|
|
283
|
+
Everyone interacting in the K3cloud-sdk project's codebases, issue trackers,
|
|
284
|
+
chat rooms and mailing lists is expected to follow the
|
|
285
|
+
[code of conduct](https://github.com/zevinto/k3cloud-sdk/blob/main/CODE_OF_CONDUCT.md).
|
data/k3cloud.gemspec
CHANGED
|
@@ -18,6 +18,8 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
spec.metadata["source_code_uri"] = spec.homepage
|
|
19
19
|
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
20
20
|
spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
|
|
21
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
22
|
+
# spec.metadata["rubygems_mfa_required"] = "true"
|
|
21
23
|
|
|
22
24
|
# Specify which files should be added to the gem when it is released.
|
|
23
25
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "k3cloud/errors/configuration_error"
|
|
4
|
+
|
|
3
5
|
module K3cloud
|
|
4
6
|
class Configuration
|
|
5
7
|
# 服务Url地址(私有云必须配置金蝶云星空产品地址,K3Cloud/结尾。若为公有云则必须置空)
|
|
@@ -29,16 +31,26 @@ module K3cloud
|
|
|
29
31
|
# 组织编码,启用多组织时配置对应的组织编码才有效
|
|
30
32
|
attr_accessor :org_num
|
|
31
33
|
|
|
32
|
-
#
|
|
34
|
+
# 连接/请求超时(秒)。
|
|
35
|
+
# verify_ssl 仅对 https 生效(http URL 自动跳过 SSL 校验);
|
|
36
|
+
# 私有云 https 自签证书时可设为 false 关闭校验,默认 true。
|
|
33
37
|
attr_accessor :connect_timeout, :request_timeout, :verify_ssl
|
|
34
38
|
|
|
35
39
|
# 日志输出格式::text(默认,常规 Logger 格式)或 :json(每行一个 JSON)
|
|
36
40
|
attr_accessor :log_format
|
|
37
41
|
|
|
42
|
+
# 请求重试:对超时与 5xx 进行重试。retry_max=0 表示不重试
|
|
43
|
+
attr_accessor :retry_max, :retry_interval, :retry_backoff
|
|
44
|
+
|
|
38
45
|
DEFAULT_LCID = 2052
|
|
39
46
|
DEFAULT_CONNECT_TIMEOUT = 30
|
|
40
47
|
DEFAULT_REQUEST_TIMEOUT = 60
|
|
41
48
|
DEFAULT_LOG_FORMAT = :text
|
|
49
|
+
DEFAULT_RETRY_MAX = 3
|
|
50
|
+
DEFAULT_RETRY_INTERVAL = 1
|
|
51
|
+
DEFAULT_RETRY_BACKOFF = 2
|
|
52
|
+
|
|
53
|
+
REQUIRED_FIELDS = %i[acct_id user_name app_id app_secret].freeze
|
|
42
54
|
|
|
43
55
|
def initialize(options = {})
|
|
44
56
|
@acct_id = options[:acct_id]
|
|
@@ -53,10 +65,22 @@ module K3cloud
|
|
|
53
65
|
@request_timeout = options.fetch(:request_timeout, DEFAULT_REQUEST_TIMEOUT)
|
|
54
66
|
@verify_ssl = options.fetch(:verify_ssl, true)
|
|
55
67
|
@log_format = options.fetch(:log_format, DEFAULT_LOG_FORMAT)
|
|
68
|
+
@retry_max = options.fetch(:retry_max, DEFAULT_RETRY_MAX)
|
|
69
|
+
@retry_interval = options.fetch(:retry_interval, DEFAULT_RETRY_INTERVAL)
|
|
70
|
+
@retry_backoff = options.fetch(:retry_backoff, DEFAULT_RETRY_BACKOFF)
|
|
56
71
|
|
|
57
72
|
yield(self) if block_given?
|
|
58
73
|
end
|
|
59
74
|
|
|
75
|
+
# 校验必填字段。nil 视为未配置,空串视为兼容历史不阻断。
|
|
76
|
+
def validate!
|
|
77
|
+
missing = REQUIRED_FIELDS.select { |f| public_send(f).nil? }
|
|
78
|
+
return if missing.empty?
|
|
79
|
+
|
|
80
|
+
raise K3cloud::ConfigurationError,
|
|
81
|
+
"K3cloud configuration missing: #{missing.join(", ")}. Please set them in K3cloud.configure."
|
|
82
|
+
end
|
|
83
|
+
|
|
60
84
|
class << self
|
|
61
85
|
def default
|
|
62
86
|
@default ||= Configuration.new
|
|
@@ -29,7 +29,7 @@ module K3cloud
|
|
|
29
29
|
kd_error.inner_exception = inner_exception ? K3cloudError.parse(inner_exception.to_json) : nil
|
|
30
30
|
kd_error
|
|
31
31
|
rescue StandardError => e
|
|
32
|
-
K3cloud.logger.error(
|
|
32
|
+
K3cloud.logger.error("Failed to parse exception message: #{e.message}")
|
|
33
33
|
nil
|
|
34
34
|
end
|
|
35
35
|
end
|
data/lib/k3cloud/http.rb
CHANGED
|
@@ -10,47 +10,87 @@ module K3cloud
|
|
|
10
10
|
# HTTP Client
|
|
11
11
|
class Http
|
|
12
12
|
attr_accessor :url, :header, :body, :connect_timeout, :request_timeout, :verify_ssl, :status_code
|
|
13
|
+
attr_reader :retry_count
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
# retry_max/interval/backoff: 失败重试参数,仅对超时与 5xx 生效。
|
|
16
|
+
def initialize(
|
|
17
|
+
url:, header:, body:,
|
|
18
|
+
connect_timeout: Configuration::DEFAULT_CONNECT_TIMEOUT,
|
|
19
|
+
request_timeout: Configuration::DEFAULT_REQUEST_TIMEOUT, verify_ssl: true,
|
|
20
|
+
retry_max: 0, retry_interval: 1, retry_backoff: 2
|
|
21
|
+
)
|
|
16
22
|
@url = url
|
|
17
23
|
@header = header || {}
|
|
18
24
|
@body = body
|
|
19
25
|
@connect_timeout = connect_timeout
|
|
20
26
|
@request_timeout = request_timeout
|
|
21
27
|
@verify_ssl = verify_ssl
|
|
28
|
+
@retry_max = retry_max.to_i
|
|
29
|
+
@retry_interval = retry_interval.to_f
|
|
30
|
+
@retry_backoff = retry_backoff.to_f
|
|
31
|
+
@retry_count = 0
|
|
22
32
|
end
|
|
23
33
|
|
|
24
34
|
def post
|
|
25
35
|
uri = URI.parse(@url)
|
|
26
36
|
http = build_http(uri)
|
|
37
|
+
request = build_request(uri)
|
|
38
|
+
body_str = perform_with_retry(http, request)
|
|
39
|
+
log_trace(body_str)
|
|
40
|
+
body_str
|
|
41
|
+
end
|
|
27
42
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def perform_with_retry(http, request)
|
|
46
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
47
|
+
begin
|
|
48
|
+
send_request(http, request)
|
|
49
|
+
rescue RetryableError => e
|
|
50
|
+
if @retry_count < @retry_max
|
|
51
|
+
@retry_count += 1
|
|
52
|
+
wait = @retry_interval * (@retry_backoff**(@retry_count - 1))
|
|
53
|
+
K3cloud.logger.debug("Request failed, retrying #{@retry_count}/#{@retry_max} " \
|
|
54
|
+
"after #{wait}s: #{e.message} url=#{@url}")
|
|
55
|
+
sleep(wait)
|
|
56
|
+
retry
|
|
57
|
+
end
|
|
58
|
+
raise K3cloud::ResponseError, "Request failed after #{@retry_count} retries: #{e.message}"
|
|
59
|
+
ensure
|
|
60
|
+
@last_elapsed_ms = elapsed_ms_since(started_at)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
33
63
|
|
|
64
|
+
def send_request(http, request)
|
|
34
65
|
response = http.request(request)
|
|
35
66
|
@status_code = response.code.to_i
|
|
67
|
+
raise RetryableError, "server error status=#{@status_code}" if @status_code >= 500
|
|
68
|
+
|
|
36
69
|
raise K3cloud::ResponseError, "status: #{response.code}, desc: #{response.body}" if @status_code >= 400
|
|
37
70
|
|
|
38
71
|
response.body
|
|
39
|
-
rescue
|
|
40
|
-
raise K3cloud::ResponseError, "Request timed out: #{e.message}"
|
|
41
|
-
rescue K3cloud::ResponseError
|
|
72
|
+
rescue RetryableError, K3cloud::ResponseError
|
|
42
73
|
raise
|
|
74
|
+
rescue Errno::ETIMEDOUT, Net::OpenTimeout, Net::ReadTimeout => e
|
|
75
|
+
raise RetryableError, "timeout: #{e.message}"
|
|
43
76
|
rescue Net::HTTPServerError => e
|
|
44
|
-
raise
|
|
77
|
+
raise RetryableError, "server error: #{e.message}"
|
|
45
78
|
rescue StandardError => e
|
|
46
79
|
raise K3cloud::ResponseError, "Unexpected error: #{e.message}"
|
|
47
80
|
end
|
|
48
81
|
|
|
49
|
-
|
|
82
|
+
def build_request(uri)
|
|
83
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
|
84
|
+
request.initialize_http_header(@header)
|
|
85
|
+
request["Content-Type"] = "application/json"
|
|
86
|
+
request["User-Agent"] = generate_user_agent
|
|
87
|
+
request.body = @body.is_a?(String) ? @body : @body.to_json
|
|
88
|
+
request
|
|
89
|
+
end
|
|
50
90
|
|
|
51
91
|
def build_http(uri)
|
|
52
|
-
http =
|
|
53
|
-
configure_ssl!(http
|
|
92
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
93
|
+
configure_ssl!(http) if uri.scheme == "https"
|
|
54
94
|
http.open_timeout = @connect_timeout
|
|
55
95
|
http.read_timeout = @request_timeout
|
|
56
96
|
http.write_timeout = @request_timeout if http.respond_to?(:write_timeout=)
|
|
@@ -58,19 +98,27 @@ module K3cloud
|
|
|
58
98
|
http
|
|
59
99
|
end
|
|
60
100
|
|
|
61
|
-
|
|
101
|
+
# 仅 https 生效。http URL 不调用此方法,verify_ssl 配置对其无意义。
|
|
102
|
+
def configure_ssl!(http)
|
|
62
103
|
http.use_ssl = true
|
|
63
104
|
http.verify_mode = @verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
|
64
105
|
end
|
|
65
106
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
107
|
+
def log_trace(body_str)
|
|
108
|
+
size = body_str ? body_str.bytesize : 0
|
|
109
|
+
K3cloud.logger.debug("HTTP completed POST #{@url} status=#{@status_code} \
|
|
110
|
+
elapsed_ms=#{(@last_elapsed_ms || 0).round(2)} retries=#{@retry_count} resp_bytes=#{size}")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def elapsed_ms_since(started_at)
|
|
114
|
+
(Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000.0
|
|
69
115
|
end
|
|
70
116
|
|
|
71
|
-
# Generate User-Agent string with client info
|
|
72
117
|
def generate_user_agent
|
|
73
118
|
"Kingdee/Ruby WebApi SDK(v#{K3cloud::VERSION}) (Ruby #{RUBY_VERSION}; #{RUBY_PLATFORM})"
|
|
74
119
|
end
|
|
120
|
+
|
|
121
|
+
# 内部异常:可重试的瞬时错误(超时/5xx),区别于最终抛出的 ResponseError。
|
|
122
|
+
class RetryableError < StandardError; end
|
|
75
123
|
end
|
|
76
124
|
end
|
data/lib/k3cloud/k3cloud_api.rb
CHANGED
|
@@ -46,6 +46,24 @@ module K3cloud
|
|
|
46
46
|
handle_query_result(rows)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
# 分页拉取全部单据。data 为基础查询参数(FormId/FieldKeys/FilterString 等),
|
|
50
|
+
# 自动注入 StartRow与 Limit,按分页连续请求直到返回行数少于 limit。
|
|
51
|
+
# 返回合并后的二维数组。limit 建议不超过 1000。
|
|
52
|
+
def execute_bill_query_all(data, limit: 1000)
|
|
53
|
+
base = data.dup
|
|
54
|
+
all = []
|
|
55
|
+
start_row = 0
|
|
56
|
+
loop do
|
|
57
|
+
page = base.merge("StartRow" => start_row, "Limit" => limit)
|
|
58
|
+
rows = execute_bill_query(page)
|
|
59
|
+
all.concat(rows)
|
|
60
|
+
break if rows.nil? || rows.size < limit
|
|
61
|
+
|
|
62
|
+
start_row += limit
|
|
63
|
+
end
|
|
64
|
+
all
|
|
65
|
+
end
|
|
66
|
+
|
|
49
67
|
# 删除
|
|
50
68
|
def delete(form_id, data)
|
|
51
69
|
execute("Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.Delete", [form_id, data])
|
|
@@ -135,19 +153,34 @@ module K3cloud
|
|
|
135
153
|
|
|
136
154
|
private
|
|
137
155
|
|
|
138
|
-
# 金蝶 ExecuteBillQuery
|
|
139
|
-
#
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
[]
|
|
148
|
-
else
|
|
149
|
-
rows
|
|
156
|
+
# 金蝶 ExecuteBillQuery 正常返回二维数组;出错时返回值结构不一:
|
|
157
|
+
# 可能是首行首列放置一个含 Message/InnerEx 的 Hash,也可能是顶层 Hash。
|
|
158
|
+
# 这里在多种形态下识别错误,避免把错误对象当作业务数据返回。
|
|
159
|
+
def handle_query_result(result)
|
|
160
|
+
return result if result.nil? || (result.respond_to?(:empty?) && result.empty?)
|
|
161
|
+
|
|
162
|
+
if error_result?(result)
|
|
163
|
+
K3cloud.logger.error("ExecuteBillQuery error: #{result.inspect}")
|
|
164
|
+
return []
|
|
150
165
|
end
|
|
166
|
+
result
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def error_result?(result)
|
|
170
|
+
return true if error_hash?(result)
|
|
171
|
+
|
|
172
|
+
first_row = result.is_a?(Array) ? result[0] : nil
|
|
173
|
+
return false unless first_row.is_a?(Array)
|
|
174
|
+
|
|
175
|
+
candidate = first_row.find { |cell| cell.is_a?(Hash) }
|
|
176
|
+
candidate && error_hash?(candidate)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def error_hash?(obj)
|
|
180
|
+
return false unless obj.is_a?(Hash)
|
|
181
|
+
|
|
182
|
+
# 错误对象通常含 Message / InnerExWrapper / InnerException 等字段
|
|
183
|
+
ERROR_MARKERS.any? { |k| obj.key?(k) }
|
|
151
184
|
end
|
|
152
185
|
end
|
|
153
186
|
end
|
data/lib/k3cloud/version.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "cgi"
|
|
3
4
|
require "k3cloud/errors/k3cloud_error"
|
|
5
|
+
require "k3cloud/errors/configuration_error"
|
|
4
6
|
require "k3cloud/errors/response_error"
|
|
5
7
|
require "k3cloud/utils/md5_utils"
|
|
6
8
|
require "k3cloud/utils/base64_utils"
|
|
@@ -10,13 +12,22 @@ require "k3cloud/http"
|
|
|
10
12
|
|
|
11
13
|
module K3cloud
|
|
12
14
|
class WebApiClient
|
|
15
|
+
# 金蝶 ExecuteBillQuery/ResponseError 中标识错误的关键字
|
|
16
|
+
ERROR_MARKERS = %w[Message InnerExWrapper InnerException].freeze
|
|
17
|
+
|
|
18
|
+
# 金蝶 app_id 内嵌 secret 的 XOR 密钥,协议固定值,来源见 README。
|
|
19
|
+
# https://vip.kingdee.com/article/163905337846351872?productLineId=1
|
|
20
|
+
XOR_SEC_KEY = "0054f397c6234378b09ca7d3e5debce7"
|
|
21
|
+
|
|
13
22
|
attr_accessor :config
|
|
14
23
|
|
|
15
24
|
def initialize(config = Configuration.default)
|
|
16
25
|
@config = config
|
|
26
|
+
@validated = false
|
|
17
27
|
end
|
|
18
28
|
|
|
19
29
|
def execute(service_name, parameters)
|
|
30
|
+
ensure_configured!
|
|
20
31
|
result = execute_json(service_name, parameters)
|
|
21
32
|
if result.start_with?("response_error:")
|
|
22
33
|
error = K3cloudError.parse(result)
|
|
@@ -37,6 +48,13 @@ module K3cloud
|
|
|
37
48
|
|
|
38
49
|
private
|
|
39
50
|
|
|
51
|
+
def ensure_configured!
|
|
52
|
+
return if @validated
|
|
53
|
+
|
|
54
|
+
@config.validate!
|
|
55
|
+
@validated = true
|
|
56
|
+
end
|
|
57
|
+
|
|
40
58
|
def execute_json(service_name, parameters)
|
|
41
59
|
url = build_url(service_name)
|
|
42
60
|
K3cloud.logger.info("Request URL: #{url}")
|
|
@@ -49,7 +67,10 @@ module K3cloud
|
|
|
49
67
|
body: body,
|
|
50
68
|
connect_timeout: @config.connect_timeout,
|
|
51
69
|
request_timeout: @config.request_timeout,
|
|
52
|
-
verify_ssl: @config.verify_ssl
|
|
70
|
+
verify_ssl: @config.verify_ssl,
|
|
71
|
+
retry_max: @config.retry_max,
|
|
72
|
+
retry_interval: @config.retry_interval,
|
|
73
|
+
retry_backoff: @config.retry_backoff
|
|
53
74
|
)
|
|
54
75
|
request.post
|
|
55
76
|
end
|
|
@@ -67,11 +88,12 @@ module K3cloud
|
|
|
67
88
|
end
|
|
68
89
|
end
|
|
69
90
|
|
|
91
|
+
# 仅 https/http 的 URL 才能切片出签名用 path,否则说明 URL 构造异常。
|
|
70
92
|
def url_path(url)
|
|
71
|
-
|
|
93
|
+
raise ArgumentError, "Unsupported URL scheme: #{url.inspect}" unless url.start_with?("http")
|
|
72
94
|
|
|
73
95
|
index = url.index("/", 10)
|
|
74
|
-
index ? url[index..-1] :
|
|
96
|
+
index ? url[index..-1] : "/"
|
|
75
97
|
end
|
|
76
98
|
|
|
77
99
|
# @note: https://vip.kingdee.com/knowledge/specialDetail/229961573895771136?category=229964512944566016&id=423060878259269120&productLineId=1
|
|
@@ -113,8 +135,7 @@ module K3cloud
|
|
|
113
135
|
end
|
|
114
136
|
|
|
115
137
|
def x_or_sec(buffer)
|
|
116
|
-
|
|
117
|
-
pwd = sec_key.encode("UTF-8").bytes
|
|
138
|
+
pwd = XOR_SEC_KEY.encode("UTF-8").bytes
|
|
118
139
|
|
|
119
140
|
buffer.each_with_index do |byte, i|
|
|
120
141
|
buffer[i] = byte ^ pwd[i % pwd.length]
|
data/lib/k3cloud.rb
CHANGED
|
@@ -46,9 +46,16 @@ module K3cloud
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
API_METHODS = %i[
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
execute_bill_query execute_bill_query_all view
|
|
50
|
+
draft save batch_save submit delete
|
|
51
|
+
audit un_audit
|
|
52
|
+
cancel_assign push execute_operation
|
|
53
|
+
allocate cancel_allocate
|
|
54
|
+
group_save query_group_info group_delete
|
|
55
|
+
attachment_upload attachment_download
|
|
56
|
+
query_business_info get_sys_report_data
|
|
57
|
+
flex_save send_msg disassembly workflow_audit
|
|
58
|
+
switch_org
|
|
52
59
|
].freeze
|
|
53
60
|
|
|
54
61
|
def_delegators :default_api, *API_METHODS
|
|
@@ -71,16 +78,18 @@ module K3cloud
|
|
|
71
78
|
end
|
|
72
79
|
|
|
73
80
|
def format_log_json(severity, datetime, msg)
|
|
74
|
-
|
|
75
|
-
payload[:level] = severity
|
|
76
|
-
payload[:time] = datetime.strftime("%Y-%m-%dT%H:%M:%S.%L")
|
|
77
|
-
"#{payload.to_json}\n"
|
|
81
|
+
"#{{ msg: msg.to_s, level: severity, time: datetime.strftime("%Y-%m-%dT%H:%M:%S.%L") }.to_json}\n"
|
|
78
82
|
end
|
|
79
83
|
|
|
80
84
|
def format_log_text(severity, datetime, msg)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
format(
|
|
86
|
+
"%s, [%s#%d] %5s -- %s\n",
|
|
87
|
+
severity[0..0],
|
|
88
|
+
datetime.strftime("%Y-%m-%dT%H:%M:%S.%L"),
|
|
89
|
+
Process.pid,
|
|
90
|
+
severity,
|
|
91
|
+
msg.to_s
|
|
92
|
+
)
|
|
84
93
|
end
|
|
85
94
|
end
|
|
86
95
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: k3cloud-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- zevinto
|
|
@@ -34,6 +34,7 @@ files:
|
|
|
34
34
|
- lib/k3cloud-sdk.rb
|
|
35
35
|
- lib/k3cloud.rb
|
|
36
36
|
- lib/k3cloud/configuration.rb
|
|
37
|
+
- lib/k3cloud/errors/configuration_error.rb
|
|
37
38
|
- lib/k3cloud/errors/k3cloud_error.rb
|
|
38
39
|
- lib/k3cloud/errors/response_error.rb
|
|
39
40
|
- lib/k3cloud/http.rb
|
|
@@ -51,6 +52,7 @@ metadata:
|
|
|
51
52
|
source_code_uri: https://github.com/zevinto/k3cloud-sdk
|
|
52
53
|
changelog_uri: https://github.com/zevinto/k3cloud-sdk/blob/main/CHANGELOG.md
|
|
53
54
|
bug_tracker_uri: https://github.com/zevinto/k3cloud-sdk/issues
|
|
55
|
+
rubygems_mfa_required: 'true'
|
|
54
56
|
rdoc_options: []
|
|
55
57
|
require_paths:
|
|
56
58
|
- lib
|