jpsclient 2.4.1 → 2.7.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.
@@ -0,0 +1,46 @@
1
+ require 'uri'
2
+
3
+ module JPSClient
4
+ # 登录/凭证相关的 host 与本地路径解析。
5
+ #
6
+ # 对齐 jps-cli / nbskills / 桌面端三端契约(见 JPS 中心化登录 OIDC 接入文档 §5.3):
7
+ # 凭证落 ~/.jps_auth_info(测试服 ~/.jps_cli/test_auth_info),由 JPS_CLI_TEST 切换。
8
+ # host 从 config 推导,回退到固定正式服地址。
9
+ module Endpoints
10
+ TEST_ENV_VAR = 'JPS_CLI_TEST'.freeze
11
+ TEST_TRUE_VALUES = %w[1 true yes on].freeze
12
+
13
+ # 固定正式服地址(config 未提供时回退用)
14
+ DEFAULT_API_BASE = 'https://jps-api.devtestapp.com'.freeze
15
+ DEFAULT_WEB_ORIGIN = 'https://jps-new.devtestapp.com'.freeze
16
+
17
+ module_function
18
+
19
+ # JPS_CLI_TEST 为真值(1/true/yes/on,大小写不敏感)→ 走测试服
20
+ def test_env?
21
+ TEST_TRUE_VALUES.include?(ENV[TEST_ENV_VAR].to_s.strip.downcase)
22
+ end
23
+
24
+ # 当前生效的凭证文件路径(与 jps-cli 逐字一致,属跨端硬契约,不走 config 覆盖)
25
+ def auth_file
26
+ home = File.expand_path('~')
27
+ if test_env?
28
+ File.join(home, '.jps_cli', 'test_auth_info')
29
+ else
30
+ File.join(home, '.jps_auth_info')
31
+ end
32
+ end
33
+
34
+ # 取 URL 的 origin(scheme://host[:port]);空/非法时返回 fallback
35
+ def origin_of(url, fallback = nil)
36
+ return fallback if url.nil? || url.to_s.empty?
37
+ u = URI.parse(url.to_s)
38
+ return fallback unless u.scheme && u.host
39
+ origin = "#{u.scheme}://#{u.host}"
40
+ origin += ":#{u.port}" if u.port && ![80, 443].include?(u.port)
41
+ origin
42
+ rescue URI::InvalidURIError
43
+ fallback
44
+ end
45
+ end
46
+ end
@@ -3,136 +3,105 @@
3
3
  require 'json'
4
4
  require 'fileutils'
5
5
  require 'jpsclient/utils/aes'
6
+ require 'jpsclient/utils/debug'
6
7
  require 'jpsclient/utils/logger'
8
+ require 'jpsclient/auth/endpoints'
7
9
 
8
10
  module JPSClient
9
11
 
10
12
  # Token 管理类
11
- # 负责 token 的本地存储、加载和清除
12
- # token 有效性由服务端 401 响应判断,本地不做过期检查
13
+ #
14
+ # 负责登录态的本地存储、加载和清除,与 jps-cli / nbskills / 桌面端三端共享同一份加密凭证
15
+ # (~/.jps_auth_info,测试服 ~/.jps_cli/test_auth_info)。
16
+ #
17
+ # 存储格式(跨端硬契约,见 OIDC 接入文档 §5.3):
18
+ # camelCase 紧凑 JSON → AES-128-ECB + PKCS7 → Base64 → 原子写文本文件(0600)
19
+ #
20
+ # 本地过期以 tokenExpireTimestamp(毫秒)预判,省一次后端 401 往返;最终失效以后端 20001 为权威。
13
21
  class Token
14
22
  attr_reader :token, :username
15
23
  attr_reader :user_id, :permissions, :lark_user_id, :tenant_manager
16
- attr_reader :expires_at, :created_at
17
-
18
- def initialize(config)
24
+ attr_reader :token_expire_timestamp, :avatar
25
+
26
+ # 明文 JSON 字段(camelCase)↔ 内部属性(snake)。顺序仅利于人眼比对,与 jps-cli 一致。
27
+ CAMEL_TO_ATTR = {
28
+ 'token' => :token,
29
+ 'tokenExpireTimestamp' => :token_expire_timestamp,
30
+ 'username' => :username,
31
+ 'userId' => :user_id,
32
+ 'permissions' => :permissions,
33
+ 'larkUserId' => :lark_user_id,
34
+ 'tenantManager' => :tenant_manager,
35
+ 'avatar' => :avatar
36
+ }.freeze
37
+
38
+ def initialize(config = nil)
19
39
  @config = config
20
40
  @aes_key = config.aes_key if config
21
41
 
22
- # 从配置中获取 token 存储路径,如果配置中没有则使用默认值
23
- if config && config.token_dir && !config.token_dir.empty?
24
- @token_dir = File.expand_path(config.token_dir)
25
- else
26
- @token_dir = File.expand_path('~') # 默认目录
27
- end
42
+ # 凭证文件路径为跨端硬契约,统一由 Endpoints 解析(忽略 config 的 token_dir/token_file_name)
43
+ @token_file = Endpoints.auth_file
28
44
 
29
- # 从配置中获取 token 文件名,如果配置中没有则使用默认值
30
- if config && config.token_file_name && !config.token_file_name.empty?
31
- @token_file = File.join(@token_dir, config.token_file_name)
32
- else
33
- @token_file = File.join(@token_dir, '.jps_auth_token') # 默认文件名
34
- end
45
+ reset_fields
35
46
 
36
- # token 数据
37
- @token = nil
38
- @username = nil
39
- @user_id = nil
40
- @permissions = nil
41
- @lark_user_id = nil
42
- @tenant_manager = false
43
- @expires_at = nil
44
- @created_at = nil
45
-
46
- # 调试模式
47
- @verbose = ENV['PINDO_DEBUG'] == 'true'
47
+ @verbose = JPSClient.debug?
48
48
  end
49
49
 
50
- # 加载 token
50
+ # 加载 token;成功且未过期返回 true,否则 false
51
51
  def load
52
52
  return false unless File.exist?(@token_file)
53
53
 
54
54
  begin
55
- file_content = File.read(@token_file)
56
-
57
- # 根据是否有 AES 密钥决定解密方式
58
- token_data = if @aes_key
59
- begin
60
- aes = AES.new(@aes_key)
61
- decrypted = aes.decrypt(file_content)
62
- JSON.parse(decrypted)
63
- rescue => e
64
- # 解密失败,可能是明文,尝试直接解析
65
- puts "尝试解密失败,作为明文读取: #{e.message}" if @verbose
66
- JSON.parse(file_content)
67
- end
68
- else
69
- JSON.parse(file_content)
70
- end
55
+ content = File.read(@token_file).strip
56
+ return false if content.empty?
57
+
58
+ token_data = decrypt_content(content)
59
+ apply_fields(token_data)
71
60
 
72
- @token = token_data['token']
73
- @username = token_data['username']
74
- @user_id = token_data['user_id']
75
- @permissions = token_data['permissions']
76
- @lark_user_id = token_data['lark_user_id']
77
- @tenant_manager = token_data.key?('tenant_manager') ? token_data['tenant_manager'] : false
78
- @expires_at = token_data['expires_at']
79
- @created_at = token_data['created_at']
80
-
81
- # 旧版 token 文件缺少 user_id,视为无效,需重新登录获取完整字段
82
- unless @token && @user_id
61
+ # token + user_id 齐全才算有效登录。~/.jps_auth_info 为三端共享,load 失败只重置内存、
62
+ # 不删文件(删除仅保留给显式 clear / force-login),避免误删他端有效登录态
63
+ unless valid_login?
83
64
  puts "Token 文件缺少必要字段,需要重新登录" if @verbose
84
- clear
65
+ reset_fields
66
+ return false
67
+ end
68
+
69
+ # 本地过期预判:直接当未登录,省一次后端 401 往返
70
+ if token_expired?
71
+ puts "Token 已过期(tokenExpireTimestamp),需要重新登录" if @verbose
72
+ reset_fields
85
73
  return false
86
74
  end
87
75
 
88
76
  return true
89
77
  rescue => e
90
78
  puts "读取 token 失败: #{e.message}" if @verbose
91
- clear_corrupted_file
79
+ reset_fields
92
80
  end
93
81
 
94
82
  false
95
83
  end
96
84
 
97
- # 保存 token
98
- # 传入完整数据 Hash
85
+ # 保存 token。token_data 为内部 snake 键的 Hash(见 Auth#get_token_data)
99
86
  def save(token_data)
100
- return false unless token_data.is_a?(Hash) && token_data['token']
101
-
102
- @token = token_data['token']
103
- @username = token_data['username']
104
- @user_id = token_data['user_id']
105
- @permissions = token_data['permissions']
106
- @lark_user_id = token_data['lark_user_id']
107
- @tenant_manager = token_data.key?('tenant_manager') ? token_data['tenant_manager'] : false
108
- @created_at = Time.now.to_i
109
- @expires_at = @created_at + 6 * 24 * 60 * 60 # 6天后过期
110
-
111
- # 确保目录存在
112
- FileUtils.mkdir_p(@token_dir) unless Dir.exist?(@token_dir)
113
-
114
- save_data = {
115
- 'token' => @token,
116
- 'username' => @username,
117
- 'user_id' => @user_id,
118
- 'permissions' => @permissions,
119
- 'lark_user_id' => @lark_user_id,
120
- 'tenant_manager' => @tenant_manager,
121
- 'expires_at' => @expires_at,
122
- 'created_at' => @created_at
123
- }
87
+ return false unless token_data.is_a?(Hash) && (token_data['token'] || token_data[:token])
124
88
 
125
- # 根据是否有 AES 密钥决定加密方式
126
- content = if @aes_key
127
- aes = AES.new(@aes_key)
128
- aes.encrypt(save_data.to_json)
129
- else
130
- save_data.to_json
89
+ # ~/.jps_auth_info 为三端共享的加密凭证:缺少 AES 密钥时拒绝写入,绝不明文落盘
90
+ # (明文会泄露 SESSION,且会让 jps-cli / 桌面端把该文件判为损坏)
91
+ unless @aes_key
92
+ puts "✗ 缺少 AES 密钥,拒绝写入共享凭证 #{@token_file}(需配置 aes_key)"
93
+ return false
131
94
  end
132
95
 
133
- File.write(@token_file, content)
134
- puts "✓ Token 已保存到 #{@token_file}" if @verbose
96
+ apply_fields(token_data)
97
+
98
+ FileUtils.mkdir_p(File.dirname(@token_file))
99
+
100
+ plaintext = JSON.generate(to_camel_hash)
101
+ content = AES.new(@aes_key).encrypt(plaintext)
135
102
 
103
+ atomic_write(@token_file, content)
104
+ puts "✓ Token 已保存到 #{@token_file}" if @verbose
136
105
  true
137
106
  rescue => e
138
107
  puts "保存 token 失败: #{e.message}"
@@ -144,43 +113,117 @@ module JPSClient
144
113
  !@token.nil? && !@token.empty?
145
114
  end
146
115
 
147
- # 清除 token
116
+ # 清除内存与磁盘上的 token
148
117
  def clear
118
+ reset_fields
119
+ FileUtils.rm_f(@token_file) if File.exist?(@token_file)
120
+ puts "✓ Token 已清除" if @verbose
121
+ end
122
+
123
+ # 转换为内部 snake 键 Hash(供 Client 使用)
124
+ def to_h
125
+ return nil unless @token
126
+
127
+ {
128
+ 'token' => @token,
129
+ 'username' => @username,
130
+ 'user_id' => @user_id,
131
+ 'permissions' => @permissions,
132
+ 'lark_user_id' => @lark_user_id,
133
+ 'tenant_manager' => @tenant_manager,
134
+ 'avatar' => @avatar,
135
+ 'token_expire_timestamp' => @token_expire_timestamp
136
+ }
137
+ end
138
+
139
+ private
140
+
141
+ def reset_fields
149
142
  @token = nil
150
143
  @username = nil
151
144
  @user_id = nil
152
145
  @permissions = nil
153
146
  @lark_user_id = nil
154
147
  @tenant_manager = false
155
- @expires_at = nil
156
- @created_at = nil
148
+ @avatar = nil
149
+ @token_expire_timestamp = 0
150
+ end
157
151
 
158
- FileUtils.rm_f(@token_file) if File.exist?(@token_file)
159
- puts "✓ Token 已清除" if @verbose
152
+ # 兼容加密与明文(明文仅为容错,共享文件恒为加密)
153
+ # AES.decrypt 返回 ASCII-8BIT,需强制 UTF-8 —— 三端以 ensure_ascii=False 写入,用户名等可含中文
154
+ def decrypt_content(content)
155
+ if @aes_key
156
+ begin
157
+ JSON.parse(AES.new(@aes_key).decrypt(content).force_encoding('UTF-8'))
158
+ rescue => e
159
+ puts "尝试解密失败,作为明文读取: #{e.message}" if @verbose
160
+ JSON.parse(content)
161
+ end
162
+ else
163
+ JSON.parse(content)
164
+ end
160
165
  end
161
166
 
162
- # 转换为 Hash
163
- def to_h
164
- return nil unless @token
167
+ # 接受 camelCase(文件)或 snake(内部 token_data)两种键
168
+ def apply_fields(data)
169
+ @token = pick(data, 'token', :token)
170
+ @username = pick(data, 'username', :username)
171
+ @user_id = pick(data, 'userId', 'user_id', :user_id)
172
+ @permissions = pick(data, 'permissions', :permissions)
173
+ @lark_user_id = pick(data, 'larkUserId', 'lark_user_id', :lark_user_id)
174
+ @tenant_manager = !!pick(data, 'tenantManager', 'tenant_manager', :tenant_manager)
175
+ @avatar = pick(data, 'avatar', :avatar)
176
+ @token_expire_timestamp = (pick(data, 'tokenExpireTimestamp', 'token_expire_timestamp', :token_expire_timestamp) || 0).to_i
177
+ end
178
+
179
+ def pick(data, *keys)
180
+ keys.each do |k|
181
+ return data[k] if data.key?(k) && !data[k].nil?
182
+ end
183
+ nil
184
+ end
165
185
 
186
+ # 写盘用的 camelCase Hash,字段顺序对齐 jps-cli
187
+ def to_camel_hash
166
188
  {
167
- 'token' => @token,
168
- 'username' => @username,
169
- 'user_id' => @user_id,
170
- 'permissions' => @permissions,
171
- 'lark_user_id' => @lark_user_id,
172
- 'tenant_manager' => @tenant_manager,
173
- 'expires_at' => @expires_at,
174
- 'created_at' => @created_at
189
+ 'token' => @token.to_s,
190
+ 'tokenExpireTimestamp' => @token_expire_timestamp.to_i,
191
+ 'username' => @username.to_s,
192
+ 'userId' => @user_id.to_s,
193
+ 'permissions' => @permissions.to_s,
194
+ 'larkUserId' => @lark_user_id.to_s,
195
+ 'tenantManager' => !!@tenant_manager,
196
+ 'avatar' => @avatar.to_s
175
197
  }
176
198
  end
177
199
 
178
- private
200
+ def valid_login?
201
+ !@token.to_s.empty? && !@user_id.to_s.empty?
202
+ end
179
203
 
180
- # 清除损坏的文件
181
- def clear_corrupted_file
182
- FileUtils.rm_f(@token_file) if File.exist?(@token_file)
183
- puts "已清除损坏的 token 文件" if @verbose
204
+ # tokenExpireTimestamp(毫秒):0/缺失 → 无从判断(false);有值且 <= 当前时刻 → 已过期
205
+ def token_expired?
206
+ @token_expire_timestamp.to_i > 0 && @token_expire_timestamp.to_i <= (Time.now.to_f * 1000).to_i
207
+ end
208
+
209
+ # 原子写 + 0600:~/.jps_auth_info 三端共享,避免并发读到半截 base64 或留全用户可读窗口
210
+ def atomic_write(path, content)
211
+ tmp = "#{path}.tmp#{Process.pid}"
212
+ begin
213
+ File.open(tmp, File::WRONLY | File::CREAT | File::TRUNC, 0o600) do |f|
214
+ f.write(content)
215
+ end
216
+ begin
217
+ File.rename(tmp, path)
218
+ rescue Errno::EEXIST, Errno::EACCES
219
+ # Windows 的 rename 不能覆盖已存在文件,先删目标再重命名(牺牲一点原子性换跨平台可用)
220
+ FileUtils.rm_f(path)
221
+ File.rename(tmp, path)
222
+ end
223
+ rescue => e
224
+ FileUtils.rm_f(tmp)
225
+ raise e
226
+ end
184
227
  end
185
228
  end
186
229
  end
@@ -60,6 +60,7 @@ require 'jpsclient/api/lark_department'
60
60
  require 'jpsclient/api/lark_leave_approval'
61
61
  require 'jpsclient/api/login'
62
62
  require 'jpsclient/api/m3u8'
63
+ require 'jpsclient/api/material'
63
64
  require 'jpsclient/api/menu'
64
65
  # 添加新的核心API
65
66
  require 'jpsclient/api/permission'
@@ -150,6 +151,7 @@ module JPSClient
150
151
  include API::LarkLeaveApproval
151
152
  include API::Login
152
153
  include API::M3u8
154
+ include API::Material
153
155
  include API::Menu
154
156
  # 添加新的核心API模块
155
157
  include API::Permission
@@ -222,6 +224,9 @@ module JPSClient
222
224
  # 加载 JPS 登录配置
223
225
  @jps_config = LoginConfig.from_json(auth_config)
224
226
 
227
+ # WEB 域(Origin/Referer 用),从 redirect_uri 推导,回退固定正式服
228
+ @web_origin = Endpoints.origin_of(@jps_config.redirect_uri, Endpoints::DEFAULT_WEB_ORIGIN)
229
+
225
230
  # 初始化 token 管理器
226
231
  @token_manager = Token.new(@jps_config)
227
232
 
@@ -239,7 +244,8 @@ module JPSClient
239
244
  @http_client = HttpClient.new(
240
245
  base_url: @baseurl,
241
246
  default_timeout: default_timeout,
242
- max_retry_times: max_retry_times
247
+ max_retry_times: max_retry_times,
248
+ web_origin: @web_origin
243
249
  )
244
250
  @http_client.update_token(@token["token"]) if @token && @token["token"]
245
251
 
@@ -258,12 +264,12 @@ module JPSClient
258
264
  end
259
265
 
260
266
  # 核心登录功能
261
- # token 有效性由服务端 401 响应判断,本地不做过期检查
267
+ # 本地按 tokenExpireTimestamp 预判过期,最终失效以服务端 401 / 业务码 20001 为准
262
268
  def do_login(force_login: false)
263
- if force_login
264
- @token_manager.clear
265
- else
266
- # 本地存在 token,直接使用
269
+ # force_login 不预删共享凭证(~/.jps_auth_info 三端共享):避免用户取消或登录失败时
270
+ # 连带清掉原本仍有效的登录态;成功登录会直接覆盖旧文件
271
+ unless force_login
272
+ # 本地存在有效(未过期)token,直接复用
267
273
  if @token_manager.load && @token_manager.loaded?
268
274
  update_token_everywhere
269
275
  return true
@@ -81,11 +81,13 @@ module JPSClient
81
81
  # @param token [String] 认证 token
82
82
  # @param default_timeout [Integer] 默认超时时间(秒),默认 60 秒
83
83
  # @param max_retry_times [Integer] 最大重试次数,默认 3
84
- def initialize(base_url: nil, token: nil, default_timeout: nil, max_retry_times: 3)
84
+ # @param web_origin [String] WEB 域,用于 Origin / Referer 头(BFF CORS 需具体 origin)
85
+ def initialize(base_url: nil, token: nil, default_timeout: nil, max_retry_times: 3, web_origin: nil)
85
86
  @base_url = base_url
86
87
  @token = token
87
88
  @default_timeout = default_timeout || 60
88
89
  @max_retry_times = max_retry_times || 3
90
+ @web_origin = web_origin
89
91
  @connection = nil
90
92
  end
91
93
 
@@ -126,17 +128,28 @@ module JPSClient
126
128
  actual_timeout = timeout || @default_timeout
127
129
 
128
130
  # 调试输出:请求信息
129
- if ENV['JPS_CLIENT_DEBUG']
130
- puts "[JPS_CLIENT_DEBUG] HTTP #{method.upcase} #{url}"
131
- puts "[JPS_CLIENT_DEBUG] Params: #{params.inspect}" if params
132
- puts "[JPS_CLIENT_DEBUG] Timeout: #{actual_timeout}s" if actual_timeout
131
+ if JPSClient.debug?
132
+ puts "[JPS_DEBUG] HTTP #{method.upcase} #{url}"
133
+ puts "[JPS_DEBUG] Params: #{params.inspect}" if params
134
+ puts "[JPS_DEBUG] Timeout: #{actual_timeout}s" if actual_timeout
133
135
  end
134
136
 
135
137
  begin
136
138
  response = connection.send(method) do |req|
137
139
  req.url url
138
140
  req.headers['Content-Type'] = 'application/json'
139
- req.headers['token'] = @token if @token
141
+ # BFF 中心化登录:token SESSION Cookie 鉴权(旧 token 头后端已忽略,保留仅便于人眼识别)
142
+ if @token
143
+ req.headers['Cookie'] = "SESSION=#{@token}"
144
+ req.headers['token'] = @token
145
+ end
146
+ # 不发 Disable-Snake:现有 API 模块按后端默认的 snake_case 解析响应(如 data.upload_id),
147
+ # 发了会翻转成 camelCase 破坏解析。登录轮询单独在 auth.rb 里发(那里配套读 camelCase)
148
+ # BFF CORS 要求具体 origin
149
+ if @web_origin
150
+ req.headers['Origin'] = @web_origin
151
+ req.headers['Referer'] = "#{@web_origin}/"
152
+ end
140
153
  req.params = params if params
141
154
  req.body = body.to_json if body && [:post, :put, :patch].include?(method)
142
155
  req.options.timeout = actual_timeout if actual_timeout
@@ -145,16 +158,17 @@ module JPSClient
145
158
  result = parse_response(response)
146
159
 
147
160
  # 调试输出:响应信息
148
- if ENV['JPS_CLIENT_DEBUG']
149
- puts "[JPS_CLIENT_DEBUG] HTTP Response: code=#{result[:code]}, success=#{result[:success]}"
150
- puts "[JPS_CLIENT_DEBUG] Response body: #{result[:body].inspect}" if result[:body]
161
+ if JPSClient.debug?
162
+ puts "[JPS_DEBUG] HTTP Response: code=#{result[:code]}, success=#{result[:success]}"
163
+ # 直接打印服务器原始响应串(本就是 JSON,无需再序列化,合法可复制)
164
+ puts "[JPS_DEBUG] Response body: #{response.body}" if response.body && !response.body.empty?
151
165
  end
152
166
 
153
167
  result
154
168
  rescue Faraday::Error => e
155
169
  # 调试输出:错误信息
156
- if ENV['JPS_CLIENT_DEBUG']
157
- puts "[JPS_CLIENT_DEBUG] HTTP Error: #{e.class} - #{e.message}"
170
+ if JPSClient.debug?
171
+ puts "[JPS_DEBUG] HTTP Error: #{e.class} - #{e.message}"
158
172
  end
159
173
 
160
174
  # 处理网络错误
@@ -93,8 +93,8 @@ module JPSClient
93
93
  )
94
94
 
95
95
  # Debug 模式下打印初始化结果
96
- if ENV['PINDO_DEBUG'] == '1'
97
- puts "[PINDO_DEBUG] init_file_multipart 返回: #{init_result.inspect}"
96
+ if JPSClient.debug?
97
+ puts "[JPS_DEBUG] init_file_multipart 返回: #{init_result.inspect}"
98
98
  end
99
99
 
100
100
  if init_result.nil? || !init_result.dig("data", "upload_id")
@@ -308,10 +308,10 @@ module JPSClient
308
308
  end
309
309
 
310
310
  # Debug 模式下打印加速前后 URL 对比
311
- if ENV['PINDO_DEBUG'] == '1'
312
- puts "[PINDO_DEBUG] 分片 ##{part_no} 原始 URL: #{original_url}"
311
+ if JPSClient.debug?
312
+ puts "[JPS_DEBUG] 分片 ##{part_no} 原始 URL: #{original_url}"
313
313
  if @enable_cloudflare
314
- puts "[PINDO_DEBUG] 分片 ##{part_no} 加速 URL: #{upload_url}"
314
+ puts "[JPS_DEBUG] 分片 ##{part_no} 加速 URL: #{upload_url}"
315
315
  end
316
316
  end
317
317
 
@@ -0,0 +1,20 @@
1
+ module JPSClient
2
+ # 统一调试开关
3
+ #
4
+ # jpsclient 的调试日志统一由环境变量 JPS_DEBUG 控制,各模块一律通过 JPSClient.debug?
5
+ # 判断,避免出现多个变量、多种判断值('true' / '1' / 存在性)并存的混乱。
6
+ #
7
+ # 真值定义:JPS_DEBUG 已设置、非空、且不是 '0' / 'false' / 'no'(大小写不敏感)。
8
+ # 调用方(如 pindo)若想带出 jpsclient 日志,只需设置 JPS_DEBUG。
9
+ DEBUG_ENV_KEY = 'JPS_DEBUG'.freeze
10
+ DEBUG_FALSE_VALUES = %w[0 false no].freeze
11
+
12
+ class << self
13
+ # @return [Boolean] 是否开启调试日志
14
+ def debug?
15
+ value = ENV[DEBUG_ENV_KEY]
16
+ return false if value.nil? || value.empty?
17
+ !DEBUG_FALSE_VALUES.include?(value.downcase)
18
+ end
19
+ end
20
+ end
@@ -6,7 +6,7 @@ module JPSClient
6
6
  end
7
7
 
8
8
  def initialize
9
- @verbose = ENV['JPS_DEBUG'] == 'true'
9
+ @verbose = JPSClient.debug?
10
10
  end
11
11
 
12
12
  def fancyinfo_start(message)
@@ -1,3 +1,3 @@
1
1
  module JPSClient
2
- VERSION = '2.4.1'
2
+ VERSION = '2.7.0'
3
3
  end
data/lib/jpsclient.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # 主入口文件
2
2
  require 'jpsclient/version'
3
3
 
4
+ # 统一调试开关(需在其他模块之前定义,供各模块运行期调用 JPSClient.debug?)
5
+ require 'jpsclient/utils/debug'
6
+
4
7
  # 基础模块
5
8
  require 'jpsclient/base/exception'
6
9
  require 'jpsclient/base/api_config'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jpsclient
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.1
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Your Name
@@ -83,16 +83,16 @@ dependencies:
83
83
  name: logger
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
- - - ">="
86
+ - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '0'
88
+ version: '1.0'
89
89
  type: :runtime
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - ">="
93
+ - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: '0'
95
+ version: '1.0'
96
96
  - !ruby/object:Gem::Dependency
97
97
  name: bundler
98
98
  requirement: !ruby/object:Gem::Requirement
@@ -235,6 +235,7 @@ files:
235
235
  - lib/jpsclient/api/lazy_client.rb
236
236
  - lib/jpsclient/api/login.rb
237
237
  - lib/jpsclient/api/m3u8.rb
238
+ - lib/jpsclient/api/material.rb
238
239
  - lib/jpsclient/api/menu.rb
239
240
  - lib/jpsclient/api/modular_client.rb
240
241
  - lib/jpsclient/api/nuget.rb
@@ -262,6 +263,7 @@ files:
262
263
  - lib/jpsclient/api/webhook.rb
263
264
  - lib/jpsclient/api/workflow.rb
264
265
  - lib/jpsclient/auth/auth.rb
266
+ - lib/jpsclient/auth/endpoints.rb
265
267
  - lib/jpsclient/auth/token.rb
266
268
  - lib/jpsclient/base/api_config.rb
267
269
  - lib/jpsclient/base/client.rb
@@ -272,15 +274,14 @@ files:
272
274
  - lib/jpsclient/upload/upload_media_client.rb
273
275
  - lib/jpsclient/upload/upload_progress.rb
274
276
  - lib/jpsclient/utils/aes.rb
277
+ - lib/jpsclient/utils/debug.rb
275
278
  - lib/jpsclient/utils/logger.rb
276
279
  - lib/jpsclient/version.rb
277
- homepage: https://github.com/yourusername/jpsclient
280
+ homepage: https://gitee.com/goodtools/JPSClient
278
281
  licenses:
279
282
  - MIT
280
283
  metadata:
281
- homepage_uri: https://github.com/yourusername/jpsclient
282
- source_code_uri: https://github.com/yourusername/jpsclient
283
- changelog_uri: https://github.com/yourusername/jpsclient/blob/main/CHANGELOG.md
284
+ homepage_uri: https://gitee.com/goodtools/JPSClient
284
285
  rdoc_options: []
285
286
  require_paths:
286
287
  - lib
@@ -295,7 +296,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
295
296
  - !ruby/object:Gem::Version
296
297
  version: '0'
297
298
  requirements: []
298
- rubygems_version: 3.6.9
299
+ rubygems_version: 4.0.11
299
300
  specification_version: 4
300
301
  summary: JPS Platform API Client with Full API Support
301
302
  test_files: []