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.
- checksums.yaml +4 -4
- data/lib/jpsclient/api/bug.rb +26 -0
- data/lib/jpsclient/api/category.rb +16 -206
- data/lib/jpsclient/api/lark_task.rb +71 -0
- data/lib/jpsclient/api/material.rb +353 -0
- data/lib/jpsclient/api/project_package.rb +2 -2
- data/lib/jpsclient/auth/auth.rb +194 -579
- data/lib/jpsclient/auth/endpoints.rb +46 -0
- data/lib/jpsclient/auth/token.rb +157 -114
- data/lib/jpsclient/base/client.rb +12 -6
- data/lib/jpsclient/http/http_client.rb +25 -11
- data/lib/jpsclient/upload/upload_client.rb +5 -5
- data/lib/jpsclient/utils/debug.rb +20 -0
- data/lib/jpsclient/utils/logger.rb +1 -1
- data/lib/jpsclient/version.rb +1 -1
- data/lib/jpsclient.rb +3 -0
- metadata +11 -10
|
@@ -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
|
data/lib/jpsclient/auth/token.rb
CHANGED
|
@@ -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
|
-
#
|
|
12
|
-
#
|
|
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 :
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
#
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
token_data =
|
|
59
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
#
|
|
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
|
-
@
|
|
156
|
-
@
|
|
148
|
+
@avatar = nil
|
|
149
|
+
@token_expire_timestamp = 0
|
|
150
|
+
end
|
|
157
151
|
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
#
|
|
163
|
-
def
|
|
164
|
-
|
|
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'
|
|
168
|
-
'
|
|
169
|
-
'
|
|
170
|
-
'
|
|
171
|
-
'
|
|
172
|
-
'
|
|
173
|
-
'
|
|
174
|
-
'
|
|
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
|
-
|
|
200
|
+
def valid_login?
|
|
201
|
+
!@token.to_s.empty? && !@user_id.to_s.empty?
|
|
202
|
+
end
|
|
179
203
|
|
|
180
|
-
#
|
|
181
|
-
def
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
#
|
|
267
|
+
# 本地按 tokenExpireTimestamp 预判过期,最终失效以服务端 401 / 业务码 20001 为准
|
|
262
268
|
def do_login(force_login: false)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
#
|
|
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
|
-
|
|
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
|
|
130
|
-
puts "[
|
|
131
|
-
puts "[
|
|
132
|
-
puts "[
|
|
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
|
-
|
|
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
|
|
149
|
-
puts "[
|
|
150
|
-
|
|
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
|
|
157
|
-
puts "[
|
|
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
|
|
97
|
-
puts "[
|
|
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
|
|
312
|
-
puts "[
|
|
311
|
+
if JPSClient.debug?
|
|
312
|
+
puts "[JPS_DEBUG] 分片 ##{part_no} 原始 URL: #{original_url}"
|
|
313
313
|
if @enable_cloudflare
|
|
314
|
-
puts "[
|
|
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
|
data/lib/jpsclient/version.rb
CHANGED
data/lib/jpsclient.rb
CHANGED
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
|
+
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://
|
|
280
|
+
homepage: https://gitee.com/goodtools/JPSClient
|
|
278
281
|
licenses:
|
|
279
282
|
- MIT
|
|
280
283
|
metadata:
|
|
281
|
-
homepage_uri: https://
|
|
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:
|
|
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: []
|