k3cloud-sdk 0.4.6 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 148509b4c220bf006114ffc6d60dec5b540abe2e33761dad837ef395e3553923
4
- data.tar.gz: 93a6ae34b81c0998a1b02cc9405404ddae16eb4238497bf042cbd00fe53e5726
3
+ metadata.gz: 5dfedb0c66eddf2de607406e2cf083b98f54350412d9cbb2699d177eb66c347a
4
+ data.tar.gz: b73a7c6847c615b4d2ec9fcb0dbacce4e69078ba24e204fa2cf850974a28404c
5
5
  SHA512:
6
- metadata.gz: b2c060e153416fd6bb8fcf9d3b3c06127a4beb9fde7a66d276742d60b422d929d2aa6d1f7dbfdf8cd8cc17175f1c48197f3c022707fed89bd73601829713f931
7
- data.tar.gz: 9505afcb1b6ff35e631abec3d9f97db35c8add981c6046ddfbac17560c9aaea5ffd29b6c62d548fd0e51e98e039f0c82a141f15e96e2d7e24a8b14cc6a2fe3d6
6
+ metadata.gz: 14bba40c28f9a4909551df05847b713ffce4029928fc5b9a82b020c95d7f1f155fff0a2eccce316b54c261c3bdcc3e085e823702eb3a8bf11787e181c0f52a6e
7
+ data.tar.gz: 1339bf2093aad7ee854a1d342d50341f0f0bcaf376daf68e620914271d3576fbcbdfc7ee41f08d8925192a150038809298c06614646042a205a68306ed9971b1
data/.rubocop.yml CHANGED
@@ -1,5 +1,13 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.0
2
+ TargetRubyVersion: 2.5
3
+ NewCops: enable
4
+ SuggestExtensions: false
5
+ Exclude:
6
+ - "**/*.md"
7
+ - "ChangeLog*"
8
+ - "CHANGELOG*"
9
+ - "vendor/**/*"
10
+ - "pkg/**/*"
3
11
 
4
12
  Style/StringLiterals:
5
13
  Enabled: true
@@ -11,3 +19,37 @@ Style/StringLiteralsInInterpolation:
11
19
 
12
20
  Layout/LineLength:
13
21
  Max: 120
22
+
23
+ # 本仓库既有签名/加密逻辑偏过程式,文档与度量类 cops 放宽以避免大改业务逻辑。
24
+ Style/Documentation:
25
+ Enabled: false
26
+
27
+ Gemspec/RequireMFA:
28
+ Enabled: false
29
+
30
+ Style/FormatStringToken:
31
+ Enabled: false
32
+
33
+ Style/FormatString:
34
+ EnforcedStyle: format
35
+
36
+ Naming/FileName:
37
+ Exclude:
38
+ - "lib/k3cloud-sdk.rb"
39
+
40
+ Metrics/AbcSize:
41
+ Max: 40
42
+
43
+ Metrics/MethodLength:
44
+ Max: 30
45
+
46
+ Metrics/BlockLength:
47
+ Exclude:
48
+ - "spec/**/*"
49
+ - "k3cloud.gemspec"
50
+
51
+ Metrics/CyclomaticComplexity:
52
+ Max: 12
53
+
54
+ Metrics/ParameterLists:
55
+ Max: 8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,101 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
1
8
  ## [Unreleased]
2
9
 
10
+ ## [0.5.0] - 2026-07-17
11
+
12
+ ### ⚠ Breaking Changes
13
+
14
+ - 最低 Ruby 版本提升至 `>= 2.5.0`。
15
+ - 默认启用 HTTPS 证书校验;私有云自签证书需显式设置 `Configuration#verify_ssl = false`。
16
+ - `Configuration` 为 `lcid`、`connect_timeout`(30s)、`request_timeout`(60s)、`verify_ssl` 提供默认值。
17
+ - 工具类与常量迁移至 `K3cloud` 命名空间(`K3cloud::Base64Utils`、`K3cloud::MD5Utils`、`K3cloud::ConstDefine`),不再保留无命名空间别名。
18
+ - `Http` 构造参数改为关键字参数。
19
+
20
+ ### Security
21
+
22
+ - 构造签名头失败时不再静默返回空 header,改为抛出异常。
23
+ - 签名密钥为空时 `MD5Utils.hash_mac` 改为抛出 `ArgumentError`。
24
+
25
+ ### Fixed
26
+
27
+ - 修复 `MD5Utils.encrypt` 在 Ruby 下始终返回空字符串的 bug。
28
+ - 修复公有云请求 URL 出现双斜杠的问题。
29
+
30
+ ### Added
31
+
32
+ - 日志输出支持可配置格式:`Configuration#log_format` 取 `:text`(默认,常规 Logger 格式)或 `:json`(每行一个 JSON,便于采集与检索)。
33
+ - 支持注入自定义 logger(`K3cloud.logger = Rails.logger`)与重建默认 logger(`K3cloud.reset_logger!`)。
34
+
35
+ ## [0.4.7] - 2025-02-18
36
+
37
+ ### Fixed
38
+
39
+ - 修复 `execute_bill_query` 查询成功时误返回空数组、丢失业务数据的问题。
40
+
41
+ ## [0.4.6] - 2025-02-18
42
+
43
+ ### Fixed
44
+
45
+ - 修复 `execute_bill_query` 在空结果时误判为错误的问题。
46
+
47
+ ## [0.4.5] - 2025-02-18
48
+
49
+ ### Changed
50
+
51
+ - 自定义错误类型统一改为抛出 `K3cloud::ResponseError`。
52
+ - `execute_bill_query` 增加对错误返回结构的识别:当首行首列为 Hash 时记录日志并返回空数组。
53
+ - User-Agent 携带 SDK 版本与运行环境信息。
54
+
55
+ ## [0.4.4] - 2024-07-19
56
+
57
+ ### Added
58
+
59
+ - HTTP 请求超时自动重试(最多 5 次,间隔 5 秒)。
60
+
61
+ ## [0.4.3] - 2023-11-29
62
+
63
+ ### Added
64
+
65
+ - 新增统一异常类 `K3cloud::ResponseError`、`K3cloud::K3cloudError`。
66
+
67
+ ## [0.4.2] - 2023-11-29
68
+
69
+ ### Fixed
70
+
71
+ - 修复非 200/206 响应被误判为失败的问题(`response.code` 为字符串比较恒为假)。
72
+
73
+ ## [0.4.1] - 2023-11-28
74
+
75
+ ### Changed
76
+
77
+ - HTTP 请求失败时改为抛出异常,不再静默返回 `nil`。
78
+
79
+ ## [0.4.0] - 2023-11-17
80
+
81
+ ### Changed
82
+
83
+ - 兼容更低版本 Ruby 语法。
84
+
85
+ ## [0.3.0] - 2023-11-14
86
+
87
+ ### Added
88
+
89
+ - 多账号配置支持:通过 `K3cloud.new_api(config)` 创建多账号客户端实例。
90
+
91
+ ## [0.2.0] - 2023-10-09
92
+
93
+ ### Fixed
94
+
95
+ - 修复入口文件大小写问题导致部分平台上 `require "k3cloud-sdk"` 失败。
96
+
3
97
  ## [0.1.0] - 2023-09-26
4
98
 
5
- - Initial release
99
+ ### Added
100
+
101
+ - 初始版本:封装金蝶云星空 OpenApi,涵盖暂存/保存/提交/审核/查看/查询/删除/下推/分配等单据操作及附件、工作流、报表等共 26 个业务方法。
data/Gemfile CHANGED
@@ -6,5 +6,9 @@ source "https://rubygems.org"
6
6
  gemspec
7
7
 
8
8
  gem "rake", "~> 13.0"
9
-
10
9
  gem "rspec", "~> 3.0"
10
+ gem "rubocop", require: false
11
+ gem "webmock", "~> 3.0", group: :test
12
+ # Ruby 3.x: base64 became a bundled gem, declare explicitly so vendored bundle resolves it.
13
+ gem "base64", require: false
14
+ gem "json", require: false
data/Gemfile.lock CHANGED
@@ -1,13 +1,35 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- k3cloud-sdk (0.4.6)
4
+ k3cloud-sdk (0.5.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
+ addressable (2.9.0)
10
+ public_suffix (>= 2.0.2, < 8.0)
11
+ ast (2.4.3)
12
+ base64 (0.3.0)
13
+ bigdecimal (4.1.2)
14
+ crack (1.0.1)
15
+ bigdecimal
16
+ rexml
9
17
  diff-lcs (1.5.0)
18
+ hashdiff (1.2.1)
19
+ json (2.21.1)
20
+ language_server-protocol (3.17.0.6)
21
+ lint_roller (1.1.0)
22
+ parallel (2.1.0)
23
+ parser (3.3.12.0)
24
+ ast (~> 2.4.1)
25
+ racc
26
+ prism (1.9.0)
27
+ public_suffix (7.0.5)
28
+ racc (1.8.1)
29
+ rainbow (3.1.1)
10
30
  rake (13.0.6)
31
+ regexp_parser (2.12.0)
32
+ rexml (3.4.4)
11
33
  rspec (3.12.0)
12
34
  rspec-core (~> 3.12.0)
13
35
  rspec-expectations (~> 3.12.0)
@@ -21,15 +43,42 @@ GEM
21
43
  diff-lcs (>= 1.2.0, < 2.0)
22
44
  rspec-support (~> 3.12.0)
23
45
  rspec-support (3.12.1)
46
+ rubocop (1.88.2)
47
+ json (~> 2.3)
48
+ language_server-protocol (~> 3.17.0.2)
49
+ lint_roller (~> 1.1.0)
50
+ parallel (>= 1.10)
51
+ parser (>= 3.3.0.2)
52
+ rainbow (>= 2.2.2, < 4.0)
53
+ regexp_parser (>= 2.9.3, < 3.0)
54
+ rubocop-ast (>= 1.49.0, < 2.0)
55
+ ruby-progressbar (~> 1.7)
56
+ unicode-display_width (>= 2.4.0, < 4.0)
57
+ rubocop-ast (1.50.0)
58
+ parser (>= 3.3.7.2)
59
+ prism (~> 1.7)
60
+ ruby-progressbar (1.13.0)
61
+ unicode-display_width (3.2.0)
62
+ unicode-emoji (~> 4.1)
63
+ unicode-emoji (4.2.0)
64
+ webmock (3.26.2)
65
+ addressable (>= 2.8.0)
66
+ crack (>= 0.3.2)
67
+ hashdiff (>= 0.4.0, < 2.0.0)
24
68
 
25
69
  PLATFORMS
26
70
  arm64-darwin-22
27
71
  arm64-darwin-23
72
+ arm64-darwin-25
28
73
 
29
74
  DEPENDENCIES
75
+ base64
76
+ json
30
77
  k3cloud-sdk!
31
78
  rake (~> 13.0)
32
79
  rspec (~> 3.0)
80
+ rubocop
81
+ webmock (~> 3.0)
33
82
 
34
83
  BUNDLED WITH
35
84
  2.4.14
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # K3cloud-sdk
2
+
2
3
  Ruby Gem for K3cloud OpenApi that uses cryptographic signature technology to avoid plaintext transmission of keys and enables automatic login.
3
4
 
4
5
  ## Installation
@@ -14,6 +15,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
14
15
  ## Usage
15
16
 
16
17
  #### 1. Single Account Configuration:
18
+
17
19
  You'll need to configure it in `config/initializers/k3cloud.rb`:
18
20
 
19
21
  ```ruby
@@ -27,6 +29,7 @@ You'll need to configure it in `config/initializers/k3cloud.rb`:
27
29
  ```
28
30
 
29
31
  Then you can call the k3cloud API using the following code:
32
+
30
33
  ```ruby
31
34
  data = {
32
35
  FormId: "",
@@ -37,6 +40,7 @@ Then you can call the k3cloud API using the following code:
37
40
  ```
38
41
 
39
42
  #### 2. Multi Account Configuration:
43
+
40
44
  You'll need to configure it in `config/initializers/k3cloud.rb`:
41
45
 
42
46
  ```ruby
@@ -56,19 +60,20 @@ You'll need to configure it in `config/initializers/k3cloud.rb`:
56
60
  c.server_url = ENV['K3CLOUD2_SERVER_URL']
57
61
  end
58
62
 
59
- K3cloud1 = K3cloud.new_api(config1)
60
- K3cloud2 = K3cloud.new_api(config2)
63
+ client1 = K3cloud.new_api(config1)
64
+ client2 = K3cloud.new_api(config2)
61
65
  ```
62
66
 
63
67
  Then you can call the k3cloud API using the following code:
68
+
64
69
  ```ruby
65
70
  data = {
66
71
  FormId: "",
67
72
  FieldKeys: "",
68
73
  FilterString: ""
69
74
  }
70
- K3cloud1.execute_bill_query(data)
71
- K3cloud2.execute_bill_query(data)
75
+ client1.execute_bill_query(data)
76
+ client2.execute_bill_query(data)
72
77
  ```
73
78
 
74
79
  ## Development
@@ -79,7 +84,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
79
84
 
80
85
  ## Contributing
81
86
 
82
- Bug reports and pull requests are welcome on GitHub at https://github.com/zevinto/k3cloud-sdk.
87
+ Bug reports and pull requests are welcome on GitHub at https://github.com/zevinto/k3cloud-sdk.
83
88
  This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/zevinto/k3cloud-sdk/blob/main/CODE_OF_CONDUCT.md).
84
89
 
85
90
  ## License
data/k3cloud.gemspec CHANGED
@@ -9,11 +9,15 @@ Gem::Specification.new do |spec|
9
9
  spec.email = ["zevinto@163.com"]
10
10
 
11
11
  spec.summary = "Ruby Gem for K3cloud API."
12
- spec.description = "Ruby Gem for K3cloud OpenApi that uses cryptographic signature technology to avoid plaintext transmission of keys and enables automatic login."
12
+ spec.description = "Ruby Gem for K3cloud OpenApi. Uses cryptographic signature technology to avoid " \
13
+ "plaintext transmission of keys and enables automatic login."
13
14
  spec.homepage = "https://github.com/zevinto/k3cloud-sdk"
14
15
  spec.license = "MIT"
15
- spec.required_ruby_version = ">= 2.0.0"
16
+ spec.required_ruby_version = ">= 2.5.0"
16
17
  spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
+ spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
17
21
 
18
22
  # Specify which files should be added to the gem when it is released.
19
23
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -29,7 +29,16 @@ module K3cloud
29
29
  # 组织编码,启用多组织时配置对应的组织编码才有效
30
30
  attr_accessor :org_num
31
31
 
32
- attr_accessor :connect_timeout, :request_timeout
32
+ # 连接/请求超时(秒)。私有云自签证书时可设 verify_ssl=false 跳过校验
33
+ attr_accessor :connect_timeout, :request_timeout, :verify_ssl
34
+
35
+ # 日志输出格式::text(默认,常规 Logger 格式)或 :json(每行一个 JSON)
36
+ attr_accessor :log_format
37
+
38
+ DEFAULT_LCID = 2052
39
+ DEFAULT_CONNECT_TIMEOUT = 30
40
+ DEFAULT_REQUEST_TIMEOUT = 60
41
+ DEFAULT_LOG_FORMAT = :text
33
42
 
34
43
  def initialize(options = {})
35
44
  @acct_id = options[:acct_id]
@@ -39,14 +48,24 @@ module K3cloud
39
48
  @app_secret = options[:app_secret]
40
49
  @server_url = options[:server_url]
41
50
  @org_num = options[:org_num]
42
- @connect_timeout = options[:connect_timeout]
43
- @request_timeout = options[:request_timeout]
51
+ @lcid = options.fetch(:lcid, DEFAULT_LCID)
52
+ @connect_timeout = options.fetch(:connect_timeout, DEFAULT_CONNECT_TIMEOUT)
53
+ @request_timeout = options.fetch(:request_timeout, DEFAULT_REQUEST_TIMEOUT)
54
+ @verify_ssl = options.fetch(:verify_ssl, true)
55
+ @log_format = options.fetch(:log_format, DEFAULT_LOG_FORMAT)
44
56
 
45
57
  yield(self) if block_given?
46
58
  end
47
59
 
48
- def self.default
49
- @@default ||= Configuration.new
60
+ class << self
61
+ def default
62
+ @default ||= Configuration.new
63
+ end
64
+
65
+ # 测试或重新配置时重置默认配置
66
+ def reset_default!
67
+ @default = nil
68
+ end
50
69
  end
51
70
  end
52
71
  end
@@ -14,17 +14,22 @@ module K3cloud
14
14
  end
15
15
 
16
16
  def self.parse(json)
17
+ return nil if json.nil? || json.to_s.empty?
18
+
19
+ json = json.to_s
17
20
  index = json.index("{")
18
- json = json[index..-1] if index >= 0
21
+ json = json[index..-1] if index
19
22
 
20
23
  parsed_json = JSON.parse(json)
21
24
  kd_error = K3cloudError.new
22
25
  kd_error.message = parsed_json["Message"]
23
- kd_error.inner_ex_wrapper = K3cloudError.parse(parsed_json["InnerExWrapper"].to_json)
24
- kd_error.inner_exception = K3cloudError.parse(parsed_json["InnerException"].to_json)
26
+ inner_wrapper = parsed_json["InnerExWrapper"]
27
+ inner_exception = parsed_json["InnerException"]
28
+ kd_error.inner_ex_wrapper = inner_wrapper ? K3cloudError.parse(inner_wrapper.to_json) : nil
29
+ kd_error.inner_exception = inner_exception ? K3cloudError.parse(inner_exception.to_json) : nil
25
30
  kd_error
26
31
  rescue StandardError => e
27
- K3cloud.logger.error({ errmsg: "Failed to parse exception message. #{e.message}", backtrace: "#{e.backtrace}", type: 'error', lever: 'ERROR' })
32
+ K3cloud.logger.error({ msg: "Failed to parse exception message. #{e.message}", type: "error", level: "ERROR" })
28
33
  nil
29
34
  end
30
35
  end
data/lib/k3cloud/http.rb CHANGED
@@ -9,27 +9,22 @@ require "json"
9
9
  module K3cloud
10
10
  # HTTP Client
11
11
  class Http
12
- attr_accessor :url, :header, :body, :connect_timeout, :request_timeout, :status_code
12
+ attr_accessor :url, :header, :body, :connect_timeout, :request_timeout, :verify_ssl, :status_code
13
13
 
14
- def initialize(url, header, body, connect_timeout = 120, request_timeout = 120)
14
+ def initialize(url:, header:, body:, connect_timeout: Configuration::DEFAULT_CONNECT_TIMEOUT,
15
+ request_timeout: Configuration::DEFAULT_REQUEST_TIMEOUT, verify_ssl: true)
15
16
  @url = url
16
17
  @header = header || {}
17
18
  @body = body
18
19
  @connect_timeout = connect_timeout
19
20
  @request_timeout = request_timeout
21
+ @verify_ssl = verify_ssl
20
22
  end
21
23
 
22
24
  def post
23
25
  uri = URI.parse(@url)
24
- http = Net::HTTP.new(uri.host, uri.port)
25
- # SSL/TLS configuration
26
- if uri.scheme == "https"
27
- http.use_ssl = true
28
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
29
- end
26
+ http = build_http(uri)
30
27
 
31
- http.open_timeout = @connect_timeout
32
- http.read_timeout = @request_timeout
33
28
  request = Net::HTTP::Post.new(uri.request_uri)
34
29
  request.initialize_http_header(@header)
35
30
  request["Content-Type"] = "application/json"
@@ -43,14 +38,36 @@ module K3cloud
43
38
  response.body
44
39
  rescue Errno::ETIMEDOUT, Net::OpenTimeout, Net::ReadTimeout => e
45
40
  raise K3cloud::ResponseError, "Request timed out: #{e.message}"
41
+ rescue K3cloud::ResponseError
42
+ raise
46
43
  rescue Net::HTTPServerError => e
47
44
  raise K3cloud::ResponseError, "Server error: #{e.message}"
48
- rescue => e
45
+ rescue StandardError => e
49
46
  raise K3cloud::ResponseError, "Unexpected error: #{e.message}"
50
47
  end
51
48
 
52
49
  private
53
50
 
51
+ def build_http(uri)
52
+ http = connection(uri) || Net::HTTP.new(uri.host, uri.port)
53
+ configure_ssl!(http, uri) if uri.scheme == "https"
54
+ http.open_timeout = @connect_timeout
55
+ http.read_timeout = @request_timeout
56
+ http.write_timeout = @request_timeout if http.respond_to?(:write_timeout=)
57
+ http.ssl_timeout = @request_timeout if http.respond_to?(:ssl_timeout=) && uri.scheme == "https"
58
+ http
59
+ end
60
+
61
+ def configure_ssl!(http, _uri)
62
+ http.use_ssl = true
63
+ http.verify_mode = @verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
64
+ end
65
+
66
+ # 子类可覆盖以注入复用的连接(如 Net::HTTP::Persistent)。
67
+ def connection(_uri)
68
+ nil
69
+ end
70
+
54
71
  # Generate User-Agent string with client info
55
72
  def generate_user_agent
56
73
  "Kingdee/Ruby WebApi SDK(v#{K3cloud::VERSION}) (Ruby #{RUBY_VERSION}; #{RUBY_PLATFORM})"
@@ -135,12 +135,18 @@ module K3cloud
135
135
 
136
136
  private
137
137
 
138
+ # 金蝶 ExecuteBillQuery 在出错时会把错误信息放在首行首列(一个 Hash),
139
+ # 正常查询则返回二维数组。这里识别并记录错误,避免把错误对象当业务数据返回。
138
140
  def handle_query_result(rows)
139
- if !rows[0].nil? && !rows[0].empty? && (result = rows[0][0]).is_a?(Hash)
140
- K3cloud.logger.error({ errmsg: result, type: 'error', lever: 'ERROR' })
141
+ return rows if rows.nil? || rows.empty?
142
+
143
+ first_row = rows[0]
144
+ result = first_row.nil? || first_row.empty? ? nil : first_row[0]
145
+ if result.is_a?(Hash)
146
+ K3cloud.logger.error({ msg: result, type: "error", level: "ERROR" })
141
147
  []
142
148
  else
143
- []
149
+ rows
144
150
  end
145
151
  end
146
152
  end
@@ -2,18 +2,16 @@
2
2
 
3
3
  require "base64"
4
4
 
5
- module Base64Utils
6
- def self.encoding_to_base64(buffer)
7
- b64buffer = Base64.strict_encode64(buffer.pack("C*"))
8
- b64buffer.force_encoding("UTF-8")
9
- rescue StandardError => e
10
- raise e
11
- end
5
+ module K3cloud
6
+ module Base64Utils
7
+ def self.encoding_to_base64(buffer)
8
+ b64buffer = Base64.strict_encode64(buffer.pack("C*"))
9
+ b64buffer.force_encoding("UTF-8")
10
+ end
12
11
 
13
- def self.decoding_from_base64(base64)
14
- buffer = base64.encode("UTF-8")
15
- Base64.strict_decode64(buffer).bytes
16
- rescue StandardError => e
17
- raise e
12
+ def self.decoding_from_base64(base64)
13
+ buffer = base64.encode("UTF-8")
14
+ Base64.strict_decode64(buffer).bytes
15
+ end
18
16
  end
19
17
  end
@@ -1,43 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ConstDefine
4
- X_API_CLIENT_ID = "X-Api-ClientID"
3
+ module K3cloud
4
+ module ConstDefine
5
+ X_API_CLIENT_ID = "X-Api-ClientID"
5
6
 
6
- X_API_AUTH_VERSION = "X-Api-Auth-Version"
7
+ X_API_AUTH_VERSION = "X-Api-Auth-Version"
7
8
 
8
- X_API_TIMESTAMP = "x-api-timestamp"
9
+ X_API_TIMESTAMP = "x-api-timestamp"
9
10
 
10
- X_API_NONCE = "x-api-nonce"
11
+ X_API_NONCE = "x-api-nonce"
11
12
 
12
- X_API_SIGN_HEADERS = "x-api-signheaders"
13
+ X_API_SIGN_HEADERS = "x-api-signheaders"
13
14
 
14
- X_API_SIGNATURE = "X-Api-Signature"
15
+ X_API_SIGNATURE = "X-Api-Signature"
15
16
 
16
- X_KD_APP_KEY = "X-Kd-Appkey"
17
+ X_KD_APP_KEY = "X-Kd-Appkey"
17
18
 
18
- X_KD_APP_DATA = "X-Kd-Appdata"
19
+ X_KD_APP_DATA = "X-Kd-Appdata"
19
20
 
20
- X_KD_SIGNATURE = "X-Kd-Signature"
21
+ X_KD_SIGNATURE = "X-Kd-Signature"
21
22
 
22
- KD_SERVICE_SESSIONID = "kdservice-sessionid"
23
+ KD_SERVICE_SESSIONID = "kdservice-sessionid"
23
24
 
24
- SESSION_ID = "ASP.NET_SessionId"
25
+ SESSION_ID = "ASP.NET_SessionId"
25
26
 
26
- X_KD_SID = "SID"
27
+ X_KD_SID = "SID"
27
28
 
28
- COOKIE_SET = "Set-Cookie"
29
+ COOKIE_SET = "Set-Cookie"
29
30
 
30
- BEGIN_METHOD_HEADER = "beginmethod"
31
+ BEGIN_METHOD_HEADER = "beginmethod"
31
32
 
32
- QUERY_METHOD_HEADER = "querymethod"
33
+ QUERY_METHOD_HEADER = "querymethod"
33
34
 
34
- BEGIN_METHOD_METHOD = "BeginQueryImpl"
35
+ BEGIN_METHOD_METHOD = "BeginQueryImpl"
35
36
 
36
- QUERY_METHOD_METHOD = "QueryAsyncResult"
37
+ QUERY_METHOD_METHOD = "QueryAsyncResult"
37
38
 
38
- QUERY_INVODE_STATE_PENDING = 0
39
+ QUERY_INVODE_STATE_PENDING = 0
39
40
 
40
- QUERY_INVODE_STATE_RUNNING = 1
41
+ QUERY_INVODE_STATE_RUNNING = 1
41
42
 
42
- QUERY_INVODE_STATE_COMPLETE = 2
43
+ QUERY_INVODE_STATE_COMPLETE = 2
44
+ end
43
45
  end
@@ -3,33 +3,26 @@
3
3
  require "digest"
4
4
  require "base64"
5
5
 
6
- module MD5Utils
7
- def self.encrypt(data_str)
8
- m = Digest::MD5.new
9
- m.update(data_str.encode("UTF-8"))
10
- s = m.digest
11
- result = ""
6
+ module K3cloud
7
+ module MD5Utils
8
+ UNSECURE_KEY = "0054f397c6234378b09ca7d3e5debce7"
12
9
 
13
- s.each_byte do |byte|
14
- result += (byte & 0xFF | -256).to_s(16)[6..-1]
10
+ # 返回 32 位小写十六进制 MD5。原来的逐字节位运算 hack 在 Ruby 下会得到 `nil`,
11
+ # 且被 rescue 吞掉返回空串,故直接使用 Digest::MD5.hexdigest。
12
+ def self.encrypt(data_str)
13
+ Digest::MD5.hexdigest(data_str.encode("UTF-8"))
15
14
  end
16
15
 
17
- result
18
- rescue StandardError => e
19
- e.backtrace
20
- ""
21
- end
16
+ def self.hash_mac(data, secret)
17
+ raise ArgumentError, "secret is required for HMAC" if secret.nil? || secret.to_s.empty?
22
18
 
23
- def self.hash_mac(data, secret)
24
- kd_mac = OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), secret, data)
25
- hex_string = bytes_to_hex(kd_mac.bytes)
26
- Base64.strict_encode64(hex_string)
27
- rescue StandardError => e
28
- e.backtrace
29
- nil
30
- end
19
+ kd_mac = OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), secret, data)
20
+ hex_string = bytes_to_hex(kd_mac.bytes)
21
+ Base64.strict_encode64(hex_string)
22
+ end
31
23
 
32
- def self.bytes_to_hex(bytes)
33
- bytes.map { |byte| byte.to_s(16).rjust(2, "0") }.join
24
+ def self.bytes_to_hex(bytes)
25
+ bytes.map { |byte| byte.to_s(16).rjust(2, "0") }.join
26
+ end
34
27
  end
35
28
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module K3cloud
4
- VERSION = "0.4.6"
4
+ VERSION = "0.5.0"
5
5
  end
@@ -22,7 +22,8 @@ module K3cloud
22
22
  error = K3cloudError.parse(result)
23
23
  raise K3cloud::ResponseError, result if error.nil?
24
24
 
25
- message = error.inner_ex_wrapper.nil? ? error.message : "#{error.message} --> #{error.inner_ex_wrapper&.message}"
25
+ inner_msg = error.inner_ex_wrapper&.message
26
+ message = inner_msg ? "#{error.message} --> #{inner_msg}" : error.message
26
27
  raise K3cloud::ResponseError, message
27
28
 
28
29
  else
@@ -42,28 +43,37 @@ module K3cloud
42
43
 
43
44
  header = build_header(url_path(url))
44
45
  body = { parameters: parameters }
45
- request = Http.new(url, header, body, @config.connect_timeout, @config.request_timeout)
46
+ request = Http.new(
47
+ url: url,
48
+ header: header,
49
+ body: body,
50
+ connect_timeout: @config.connect_timeout,
51
+ request_timeout: @config.request_timeout,
52
+ verify_ssl: @config.verify_ssl
53
+ )
46
54
  request.post
47
55
  end
48
56
 
49
57
  def build_url(service_name)
50
- base_url = if @config.server_url.nil? || @config.server_url.empty?
51
- "https://api.kingdee.com/galaxyapi/"
52
- else
53
- @config.server_url.to_s.strip.chomp("/")
54
- end
55
58
  "#{base_url}/#{service_name}.common.kdsvc"
56
59
  end
57
60
 
58
- def url_path(url)
59
- if url.start_with?("http")
60
- index = url.index("/", 10)
61
- index > -1 ? url[index..-1] : url
61
+ def base_url
62
+ server = @config.server_url
63
+ if server.nil? || server.empty?
64
+ "https://api.kingdee.com/galaxyapi"
62
65
  else
63
- url
66
+ server.to_s.strip.chomp("/")
64
67
  end
65
68
  end
66
69
 
70
+ def url_path(url)
71
+ return url unless url.start_with?("http")
72
+
73
+ index = url.index("/", 10)
74
+ index ? url[index..-1] : url
75
+ end
76
+
67
77
  # @note: https://vip.kingdee.com/knowledge/specialDetail/229961573895771136?category=229964512944566016&id=423060878259269120&productLineId=1
68
78
  def build_header(url)
69
79
  header = {}
@@ -94,9 +104,6 @@ module K3cloud
94
104
  header[ConstDefine::X_KD_SIGNATURE] = kd_signature
95
105
  end
96
106
  header
97
- rescue StandardError => e
98
- K3cloud.logger.error({ errmsg: "Build header exception.", backtrace: "#{e.backtrace}", type: 'error', lever: 'ERROR' })
99
- {}
100
107
  end
101
108
 
102
109
  def decode_sec(sec)
data/lib/k3cloud.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "k3cloud/version"
4
4
  require "logger"
5
+ require "json"
5
6
  require "k3cloud/configuration"
6
7
  require "k3cloud/k3cloud_api"
7
8
  require "forwardable"
@@ -19,20 +20,67 @@ module K3cloud
19
20
  end
20
21
 
21
22
  def logger
22
- @logger ||= ::Logger.new($stderr)
23
+ @logger ||= build_logger
24
+ end
25
+
26
+ # 注入自定义 logger(例如 Rails.logger)。设为 nil 可触发下次重建。
27
+ attr_writer :logger
28
+
29
+ # 重建默认 logger(切换 log_format 后调用)
30
+ def reset_logger!
31
+ @logger = nil
23
32
  end
24
33
 
25
34
  def new_api(config = nil)
26
- if config
27
- K3cloud::K3cloudApi.new(config)
35
+ config ? K3cloud::K3cloudApi.new(config) : default_api
36
+ end
37
+
38
+ # 默认账号客户端(带缓存;传入新 config 不会污染此处)
39
+ def default_api
40
+ @default_api ||= K3cloud::K3cloudApi.new
41
+ end
42
+
43
+ # 测试/重新配置时重置默认客户端
44
+ def reset_default_api!
45
+ @default_api = nil
46
+ end
47
+
48
+ API_METHODS = %i[
49
+ draft save batch_save submit audit un_audit view execute_bill_query delete cancel_assign push execute_operation
50
+ allocate cancel_allocate group_save query_group_info group_delete attachment_upload attachment_download
51
+ query_business_info get_sys_report_data flex_save send_msg disassembly workflow_audit switch_org
52
+ ].freeze
53
+
54
+ def_delegators :default_api, *API_METHODS
55
+
56
+ private
57
+
58
+ def build_logger
59
+ logger = ::Logger.new($stderr)
60
+ logger.formatter = method(:format_log)
61
+ logger
62
+ end
63
+
64
+ # 根据 Configuration.default.log_format 决定输出::text(默认)或 :json
65
+ def format_log(severity, datetime, _progname, msg)
66
+ if Configuration.default.log_format == :json
67
+ format_log_json(severity, datetime, msg)
28
68
  else
29
- @default_api ||= K3cloud::K3cloudApi.new
69
+ format_log_text(severity, datetime, msg)
30
70
  end
31
71
  end
32
72
 
33
- def_delegators :new_api, :draft, :save, :batch_save, :submit, :audit, :un_audit, :view, :execute_bill_query, :delete,
34
- :cancel_assign, :push, :execute_operation, :allocate, :cancel_allocate, :group_save, :query_group_info,
35
- :group_delete, :attachment_upload, :attachment_download, :query_business_info, :get_sys_report_data,
36
- :flex_save, :send_msg, :disassembly, :workflow_audit, :switch_org
73
+ def format_log_json(severity, datetime, msg)
74
+ payload = msg.is_a?(Hash) ? msg.dup : { msg: msg.to_s }
75
+ payload[:level] = severity
76
+ payload[:time] = datetime.strftime("%Y-%m-%dT%H:%M:%S.%L")
77
+ "#{payload.to_json}\n"
78
+ end
79
+
80
+ def format_log_text(severity, datetime, msg)
81
+ body = msg.is_a?(Hash) ? msg.inspect : msg.to_s
82
+ format("%s, [%s#%d] %5s -- %s\n", severity[0..0],
83
+ datetime.strftime("%Y-%m-%dT%H:%M:%S.%L"), Process.pid, severity, body)
84
+ end
37
85
  end
38
86
  end
metadata CHANGED
@@ -1,16 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: k3cloud-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.6
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - zevinto
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-02-18 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
- description: Ruby Gem for K3cloud OpenApi that uses cryptographic signature technology
12
+ description: Ruby Gem for K3cloud OpenApi. Uses cryptographic signature technology
14
13
  to avoid plaintext transmission of keys and enables automatic login.
15
14
  email:
16
15
  - zevinto@163.com
@@ -49,7 +48,9 @@ licenses:
49
48
  - MIT
50
49
  metadata:
51
50
  homepage_uri: https://github.com/zevinto/k3cloud-sdk
52
- post_install_message:
51
+ source_code_uri: https://github.com/zevinto/k3cloud-sdk
52
+ changelog_uri: https://github.com/zevinto/k3cloud-sdk/blob/main/CHANGELOG.md
53
+ bug_tracker_uri: https://github.com/zevinto/k3cloud-sdk/issues
53
54
  rdoc_options: []
54
55
  require_paths:
55
56
  - lib
@@ -57,15 +58,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
57
58
  requirements:
58
59
  - - ">="
59
60
  - !ruby/object:Gem::Version
60
- version: 2.0.0
61
+ version: 2.5.0
61
62
  required_rubygems_version: !ruby/object:Gem::Requirement
62
63
  requirements:
63
64
  - - ">="
64
65
  - !ruby/object:Gem::Version
65
66
  version: '0'
66
67
  requirements: []
67
- rubygems_version: 3.5.18
68
- signing_key:
68
+ rubygems_version: 3.6.9
69
69
  specification_version: 4
70
70
  summary: Ruby Gem for K3cloud API.
71
71
  test_files: []