pindo 5.0.5 → 5.0.7
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/pindo/client/bossconfigclient.rb +3 -4
- data/lib/pindo/client/pgyer_feishu_oauth_cli.rb +412 -0
- data/lib/pindo/client/pgyerclient.rb +36 -111
- data/lib/pindo/client/pgyeruploadclient.rb +275 -153
- data/lib/pindo/version.rb +1 -1
- metadata +43 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd1d664dc77bc75c43a1816a8316fafb10351c9133a4e9dabf403f010bd1882a
|
4
|
+
data.tar.gz: 7e4387a7ad42c6830898bd9466a7ea8131ab92429b78ed5ed61479b07eebe44f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 305e2b32210bf046c6d56a82cff9f799e9bf73672e812aa28e3c78e378b32bc8c4700f5f4f45cfe39e3301427632f6074c2f6b419778c51a21de15dc19d8a676
|
7
|
+
data.tar.gz: 991b426167216c8c2f95fbd99400314fe807f892a6975300d145ff1e07684611547f6a89df7fbabd634d5f0f076839928fb89a1787977b313e4b1e332029a997
|
@@ -49,10 +49,9 @@ module Pindo
|
|
49
49
|
|
50
50
|
def do_login()
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
password = "HIsdfUHfwdh"
|
52
|
+
username = ask('请输入Boss网站的用户名:')
|
53
|
+
password = ask('请输入Boss网站的密码:')
|
54
|
+
|
56
55
|
|
57
56
|
do_login_req(username:username, password:password)
|
58
57
|
end
|
@@ -0,0 +1,412 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'webrick'
|
4
|
+
require 'json'
|
5
|
+
require 'net/http'
|
6
|
+
require 'uri'
|
7
|
+
require 'securerandom'
|
8
|
+
require 'digest'
|
9
|
+
require 'base64'
|
10
|
+
require 'fileutils'
|
11
|
+
require 'pindo/base/aeshelper'
|
12
|
+
require 'pindo/config/pindouserlocalconfig'
|
13
|
+
require 'pindo/client/httpclient'
|
14
|
+
|
15
|
+
module Pindo
|
16
|
+
|
17
|
+
class PgyerFeishuOAuthCLI
|
18
|
+
attr_reader :access_token, :username, :expires_at
|
19
|
+
|
20
|
+
def initialize(client_id)
|
21
|
+
@client_id = client_id
|
22
|
+
@feishu_auth_url = 'https://passport.feishu.cn/suite/passport/oauth/authorize?'
|
23
|
+
|
24
|
+
# 保持原有的redirect_uri,飞书OAuth流程中仍使用这个URL
|
25
|
+
# 前端UI会检测state=terminal_login并自动跳转到localhost:8899
|
26
|
+
@redirect_uri = 'https://pgyerapps.com/login'
|
27
|
+
@pgyer_api_endpoint = 'https://api.pgyerapps.com/api/user/lark_qr_login'
|
28
|
+
|
29
|
+
@state = 'terminal_login'
|
30
|
+
|
31
|
+
@larkScopeList = [
|
32
|
+
'task:task:write',
|
33
|
+
'task:section:write',
|
34
|
+
'task:custom_field:write',
|
35
|
+
'task:tasklist:write'
|
36
|
+
];
|
37
|
+
|
38
|
+
@access_token = nil
|
39
|
+
@username = nil
|
40
|
+
@expires_at = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# 启动授权流程
|
44
|
+
def authorize
|
45
|
+
# 不再检查已存储的令牌,直接开始授权流程
|
46
|
+
authorization_uri = build_authorization_uri
|
47
|
+
puts "正在打开浏览器进行飞书OAuth授权..."
|
48
|
+
puts "授权URI: #{authorization_uri}"
|
49
|
+
puts "注意:授权成功后,网页将自动跳转至本地http://localhost:8899"
|
50
|
+
|
51
|
+
# 在浏览器中打开授权URL
|
52
|
+
open_browser(authorization_uri)
|
53
|
+
|
54
|
+
# 启动本地服务器处理回调
|
55
|
+
code = start_callback_server
|
56
|
+
|
57
|
+
# 如果自动获取失败,提示用户手动输入
|
58
|
+
if code.nil?
|
59
|
+
puts "自动获取授权码失败,您可以手动输入:"
|
60
|
+
puts "1. 授权码 (直接复制'code='后面的内容)"
|
61
|
+
puts "2. 完整回调URL (例如: http://localhost:8899/?code=xxxx...)"
|
62
|
+
print "> "
|
63
|
+
input = STDIN.gets.chomp
|
64
|
+
|
65
|
+
if input.start_with?("http")
|
66
|
+
# 尝试从URL中提取code
|
67
|
+
begin
|
68
|
+
uri = URI(input)
|
69
|
+
query_params = URI.decode_www_form(uri.query || '').to_h
|
70
|
+
code = query_params['code']
|
71
|
+
if code
|
72
|
+
puts "从URL中成功提取授权码"
|
73
|
+
end
|
74
|
+
rescue => e
|
75
|
+
puts "无法从URL中提取授权码: #{e.message}"
|
76
|
+
end
|
77
|
+
else
|
78
|
+
# 将输入直接作为code
|
79
|
+
code = input unless input.empty?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
if code
|
84
|
+
puts "成功获取授权码!正在使用飞书身份登录Pgyer..."
|
85
|
+
puts "code: #{code}"
|
86
|
+
if login_pgyer_with_feishu(code:code)
|
87
|
+
puts "Pgyer登录成功!"
|
88
|
+
# 登录成功后,返回true,外部程序可以通过访问access_token属性获取token
|
89
|
+
return true
|
90
|
+
end
|
91
|
+
return false
|
92
|
+
else
|
93
|
+
puts "授权失败"
|
94
|
+
return false
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# 验证Pgyer令牌
|
99
|
+
def validate_pgyer_token(token = nil, expires_at = nil)
|
100
|
+
token_to_check = token || @access_token
|
101
|
+
expiration_time = expires_at || @expires_at
|
102
|
+
|
103
|
+
# 首先检查token是否存在
|
104
|
+
return false unless token_to_check
|
105
|
+
|
106
|
+
# 然后检查token是否过期
|
107
|
+
if expiration_time && Time.now.to_i > expiration_time
|
108
|
+
puts "令牌已过期,需要重新登录"
|
109
|
+
return false
|
110
|
+
end
|
111
|
+
|
112
|
+
# 最后验证token是否有效
|
113
|
+
uri = URI("https://www.pgyer.com/api/user/profile")
|
114
|
+
request = Net::HTTP::Get.new(uri)
|
115
|
+
request['Authorization'] = "Bearer #{token_to_check}"
|
116
|
+
|
117
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
118
|
+
http.request(request)
|
119
|
+
end
|
120
|
+
|
121
|
+
return response.code == '200'
|
122
|
+
end
|
123
|
+
|
124
|
+
# 使用code登录Pgyer, 并且Pgyer返回token
|
125
|
+
def login_pgyer_with_feishu(code: nil)
|
126
|
+
return false unless code
|
127
|
+
|
128
|
+
# 构造请求体
|
129
|
+
body_params = {
|
130
|
+
code: code,
|
131
|
+
redirectUri: @redirect_uri,
|
132
|
+
scope: @larkScopeList.join(' ')
|
133
|
+
}
|
134
|
+
|
135
|
+
puts "请求Pgyer API: #{@pgyer_api_endpoint}"
|
136
|
+
puts "请求参数: #{body_params.to_json}"
|
137
|
+
|
138
|
+
# 使用HttpClient发送请求
|
139
|
+
con = HttpClient.create_instance_with_proxy
|
140
|
+
|
141
|
+
begin
|
142
|
+
res = con.post do |req|
|
143
|
+
req.url @pgyer_api_endpoint
|
144
|
+
req.headers['Content-Type'] = 'application/json'
|
145
|
+
req.body = body_params.to_json
|
146
|
+
end
|
147
|
+
|
148
|
+
puts "API响应状态码: #{res.status}"
|
149
|
+
|
150
|
+
# 处理响应
|
151
|
+
result = nil
|
152
|
+
if !res.body.nil?
|
153
|
+
begin
|
154
|
+
result = JSON.parse(res.body)
|
155
|
+
# puts "解析后的响应: #{result.inspect}"
|
156
|
+
|
157
|
+
if result['code'] == 200 && !result['data'].nil? && !result['data']['token'].nil?
|
158
|
+
@access_token = result['data']['token']
|
159
|
+
@username = result['data']['username'] if result['data']['username']
|
160
|
+
# 设置token有效期为7天后
|
161
|
+
@expires_at = Time.now.to_i + 6 * 24 * 60 * 60 # 7天的秒数
|
162
|
+
return true
|
163
|
+
else
|
164
|
+
error_msg = result['meta'] && result['meta']['message'] ? result['meta']['message'] : '未知错误'
|
165
|
+
puts "Pgyer登录失败: #{error_msg}"
|
166
|
+
return false
|
167
|
+
end
|
168
|
+
rescue => e
|
169
|
+
puts "解析响应失败: #{e.message}"
|
170
|
+
puts "原始响应: #{res.body[0..200]}"
|
171
|
+
return false
|
172
|
+
end
|
173
|
+
else
|
174
|
+
puts "请求返回空响应"
|
175
|
+
return false
|
176
|
+
end
|
177
|
+
rescue => e
|
178
|
+
puts "请求过程中出错: #{e.class} - #{e.message}"
|
179
|
+
return false
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
|
186
|
+
# 构建授权URI
|
187
|
+
def build_authorization_uri
|
188
|
+
uri = URI(@feishu_auth_url)
|
189
|
+
|
190
|
+
params = {
|
191
|
+
'client_id' => @client_id,
|
192
|
+
'redirect_uri' => @redirect_uri,
|
193
|
+
'response_type' => 'code',
|
194
|
+
'state' => @state,
|
195
|
+
'scope' => @larkScopeList.join(' ')
|
196
|
+
}
|
197
|
+
|
198
|
+
uri.query = URI.encode_www_form(params)
|
199
|
+
uri.to_s
|
200
|
+
end
|
201
|
+
|
202
|
+
# 在浏览器中打开URL
|
203
|
+
def open_browser(url)
|
204
|
+
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
205
|
+
system("start", url)
|
206
|
+
elsif RbConfig::CONFIG['host_os'] =~ /darwin/
|
207
|
+
system("open", url)
|
208
|
+
elsif RbConfig::CONFIG['host_os'] =~ /linux|bsd/
|
209
|
+
system("xdg-open", url)
|
210
|
+
else
|
211
|
+
puts "无法自动打开浏览器,请手动访问: #{url}"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# 启动本地服务器处理回调
|
216
|
+
def start_callback_server
|
217
|
+
code = nil
|
218
|
+
|
219
|
+
puts "启动本地服务器,监听端口8899..."
|
220
|
+
|
221
|
+
# 使用本地8899端口处理回调,不管redirect_uri配置如何
|
222
|
+
server = WEBrick::HTTPServer.new(
|
223
|
+
Port: 8899,
|
224
|
+
Logger: WEBrick::Log.new("/dev/null"),
|
225
|
+
AccessLog: []
|
226
|
+
)
|
227
|
+
|
228
|
+
# 处理根路径的请求
|
229
|
+
server.mount_proc '/' do |req, res|
|
230
|
+
begin
|
231
|
+
# puts "接收到请求: #{req.request_line}"
|
232
|
+
# puts "请求参数: #{req.query_string}"
|
233
|
+
|
234
|
+
# 安全解析请求参数
|
235
|
+
begin
|
236
|
+
query_params = URI.decode_www_form(req.query_string || '').to_h
|
237
|
+
puts "解析的参数: #{query_params.inspect}"
|
238
|
+
rescue => e
|
239
|
+
puts "解析请求参数失败: #{e.message}"
|
240
|
+
query_params = {}
|
241
|
+
end
|
242
|
+
|
243
|
+
# if query_params['state'] != @state
|
244
|
+
# puts "状态不匹配: 接收到 #{query_params['state']},期望 #{@state}"
|
245
|
+
# res.content_type = "text/html; charset=UTF-8"
|
246
|
+
# res.body = "状态不匹配,可能存在CSRF攻击风险"
|
247
|
+
# elsif query_params['error']
|
248
|
+
if query_params['error']
|
249
|
+
puts "授权错误: #{query_params['error']}"
|
250
|
+
res.content_type = "text/html; charset=UTF-8"
|
251
|
+
res.body = "授权错误: #{query_params['error']}"
|
252
|
+
elsif query_params['code']
|
253
|
+
code = query_params['code']
|
254
|
+
puts "成功获取授权码: #{code}"
|
255
|
+
res.content_type = "text/html; charset=UTF-8"
|
256
|
+
res.body = <<-HTML
|
257
|
+
<!DOCTYPE html>
|
258
|
+
<html>
|
259
|
+
<head>
|
260
|
+
<meta charset="UTF-8">
|
261
|
+
<title>飞书授权成功</title>
|
262
|
+
<style>
|
263
|
+
body { font-family: Arial, sans-serif; text-align: center; padding: 40px; }
|
264
|
+
.container { max-width: 600px; margin: 0 auto; }
|
265
|
+
.success { color: #4CAF50; }
|
266
|
+
.code { font-family: monospace; background: #f5f5f5; padding: 10px; border-radius: 4px; word-break: break-all; }
|
267
|
+
.countdown { font-weight: bold; color: #FF5722; }
|
268
|
+
.autoclose-banner {
|
269
|
+
background-color: #333; color: white; padding: 10px;
|
270
|
+
position: fixed; top: 0; left: 0; right: 0;
|
271
|
+
display: flex; justify-content: space-between; align-items: center;
|
272
|
+
}
|
273
|
+
.close-btn {
|
274
|
+
background: #f44336; color: white; border: none;
|
275
|
+
padding: 5px 10px; cursor: pointer; border-radius: 3px;
|
276
|
+
}
|
277
|
+
</style>
|
278
|
+
<script>
|
279
|
+
// 尝试多种方法关闭窗口
|
280
|
+
function attemptClose() {
|
281
|
+
try {
|
282
|
+
// 方法1: 最基本的关闭尝试
|
283
|
+
window.close();
|
284
|
+
|
285
|
+
// 方法2: 对于某些浏览器需要历史记录操作
|
286
|
+
window.history.back();
|
287
|
+
|
288
|
+
// 方法3: 空白页替换
|
289
|
+
window.location.href = "about:blank";
|
290
|
+
|
291
|
+
// 方法4: 尝试使用opener关系
|
292
|
+
if (window.opener) {
|
293
|
+
window.opener.focus();
|
294
|
+
window.close();
|
295
|
+
}
|
296
|
+
|
297
|
+
// 如果以上方法都失败了,显示手动关闭提示
|
298
|
+
setTimeout(function() {
|
299
|
+
document.getElementById('close-message').style.display = 'block';
|
300
|
+
document.getElementById('countdown-container').style.display = 'none';
|
301
|
+
}, 1000);
|
302
|
+
} catch (e) {
|
303
|
+
console.error("关闭窗口失败:", e);
|
304
|
+
document.getElementById('close-message').style.display = 'block';
|
305
|
+
document.getElementById('countdown-container').style.display = 'none';
|
306
|
+
}
|
307
|
+
}
|
308
|
+
|
309
|
+
// 倒计时函数
|
310
|
+
var secondsLeft = 3;
|
311
|
+
function updateCountdown() {
|
312
|
+
document.getElementById('countdown').innerText = secondsLeft;
|
313
|
+
if (secondsLeft <= 0) {
|
314
|
+
attemptClose();
|
315
|
+
} else {
|
316
|
+
secondsLeft -= 1;
|
317
|
+
setTimeout(updateCountdown, 1000);
|
318
|
+
}
|
319
|
+
}
|
320
|
+
|
321
|
+
// 初始化
|
322
|
+
window.onload = function() {
|
323
|
+
updateCountdown();
|
324
|
+
}
|
325
|
+
</script>
|
326
|
+
</head>
|
327
|
+
<body>
|
328
|
+
<div class="autoclose-banner">
|
329
|
+
<span>授权成功! 此窗口将自动关闭 (<span id="countdown" class="countdown">3</span>)</span>
|
330
|
+
<button class="close-btn" onclick="attemptClose()">立即关闭</button>
|
331
|
+
</div>
|
332
|
+
|
333
|
+
<div class="container">
|
334
|
+
<h1 class="success">飞书授权成功!</h1>
|
335
|
+
<p>已获取授权码,正在返回命令行...</p>
|
336
|
+
|
337
|
+
<div id="countdown-container">
|
338
|
+
<p>此窗口将在 <span id="countdown-text" class="countdown">3</span> 秒后自动关闭</p>
|
339
|
+
</div>
|
340
|
+
|
341
|
+
<div id="close-message" style="display: none;">
|
342
|
+
<p>自动关闭失败,请手动关闭此窗口</p>
|
343
|
+
<button class="close-btn" onclick="attemptClose()">尝试再次关闭</button>
|
344
|
+
</div>
|
345
|
+
|
346
|
+
<p>授权码:</p>
|
347
|
+
<div class="code">#{code}</div>
|
348
|
+
</div>
|
349
|
+
|
350
|
+
<script>
|
351
|
+
// 同步倒计时显示
|
352
|
+
document.getElementById('countdown-text').innerText = secondsLeft;
|
353
|
+
</script>
|
354
|
+
</body>
|
355
|
+
</html>
|
356
|
+
HTML
|
357
|
+
else
|
358
|
+
puts "未获取到授权码"
|
359
|
+
res.content_type = "text/html; charset=UTF-8"
|
360
|
+
res.body = "未获取到授权码"
|
361
|
+
end
|
362
|
+
|
363
|
+
# 给用户更多时间看到页面内容,并让JavaScript倒计时完成
|
364
|
+
puts "4秒后关闭服务器..."
|
365
|
+
Thread.new do
|
366
|
+
sleep 4 # 设置为4秒,比JavaScript的3秒稍长一点,确保客户端有足够时间执行
|
367
|
+
server.shutdown
|
368
|
+
end
|
369
|
+
rescue => e
|
370
|
+
puts "处理请求时出错: #{e.class} - #{e.message}"
|
371
|
+
puts e.backtrace.join("\n")
|
372
|
+
|
373
|
+
# 确保即使出错也返回有效的响应
|
374
|
+
res.content_type = "text/html; charset=UTF-8"
|
375
|
+
res.body = "处理请求时出错"
|
376
|
+
|
377
|
+
# 出错时也关闭服务器
|
378
|
+
Thread.new do
|
379
|
+
sleep 4 # 与正常情况保持一致
|
380
|
+
server.shutdown
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
# 捕获Ctrl+C以允许用户中断
|
386
|
+
trap('INT') { server.shutdown }
|
387
|
+
|
388
|
+
# puts "正在监听http://localhost:8899等待飞书重定向..."
|
389
|
+
# puts "您也可以手动访问: http://localhost:8899/?code=YOUR_CODE&state=terminal_login"
|
390
|
+
|
391
|
+
# 在线程中运行服务器,最多等待3分钟
|
392
|
+
thread = Thread.new { server.start }
|
393
|
+
begin
|
394
|
+
thread.join(180) # 最多等待3分钟
|
395
|
+
rescue Timeout::Error
|
396
|
+
puts "授权超时"
|
397
|
+
server.shutdown
|
398
|
+
end
|
399
|
+
|
400
|
+
if code
|
401
|
+
puts "成功获取授权码: #{code}"
|
402
|
+
else
|
403
|
+
puts "未获取到授权码"
|
404
|
+
end
|
405
|
+
|
406
|
+
code
|
407
|
+
end
|
408
|
+
|
409
|
+
|
410
|
+
end
|
411
|
+
|
412
|
+
end
|
@@ -1,9 +1,8 @@
|
|
1
|
-
|
2
1
|
require 'uri'
|
3
2
|
require 'pindo/base/aeshelper'
|
4
3
|
require 'pindo/config/pindouserlocalconfig'
|
5
4
|
require 'pindo/client/httpclient'
|
6
|
-
|
5
|
+
require 'pindo/client/pgyer_feishu_oauth_cli'
|
7
6
|
|
8
7
|
module Pindo
|
9
8
|
|
@@ -35,124 +34,44 @@ module Pindo
|
|
35
34
|
|
36
35
|
end
|
37
36
|
|
38
|
-
def get_faraday_instance
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
|
43
37
|
def do_login(force_login:false)
|
44
38
|
|
39
|
+
login_success = false
|
40
|
+
need_req_login = true
|
45
41
|
|
46
|
-
|
47
|
-
|
48
|
-
@token = load_token()
|
49
|
-
if !@token.nil? && !@token["token"].nil? && !force_login
|
50
|
-
login_success = true
|
51
|
-
|
52
|
-
# puts "用户#{@token["username"]}登录pgyer成功!!!"
|
53
|
-
# puts
|
54
|
-
|
55
|
-
else
|
56
|
-
username = nil
|
57
|
-
if !@token.nil? && !@token["username"].nil?
|
58
|
-
username = @token["username"]
|
59
|
-
checksum_password = nil
|
60
|
-
if !@token["password"].nil?
|
61
|
-
checksum_password = @token["password"]
|
62
|
-
end
|
63
|
-
end
|
64
|
-
token = do_login_req(username:username, checksum_password:checksum_password)
|
65
|
-
if !token.nil? && !token["token"].nil?
|
66
|
-
@token = token
|
67
|
-
login_success = true
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
return login_success
|
72
|
-
end
|
73
|
-
|
74
|
-
def do_login_req(username:nil, checksum_password:nil)
|
75
|
-
|
76
|
-
|
77
|
-
login_name = username
|
78
|
-
if login_name.nil? || login_name.empty?
|
79
|
-
login_name = ask('请输入pgger网站的usernmae:') || nil
|
80
|
-
end
|
81
|
-
|
82
|
-
checksum_pass = checksum_password
|
83
|
-
if checksum_pass.nil? || checksum_pass.empty?
|
84
|
-
login_passwork = ask('请输入pgger网站的密码:') || nil
|
85
|
-
checksum_pass = Digest::MD5.hexdigest(login_passwork)
|
86
|
-
end
|
87
|
-
|
88
|
-
if !login_name.nil? && !login_name.empty? && !checksum_pass.nil? && !checksum_pass.empty?
|
89
|
-
|
42
|
+
if force_login
|
43
|
+
need_req_login = true
|
90
44
|
else
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
username:login_name,
|
104
|
-
phoneCode:result_data["msg"],
|
105
|
-
password:checksum_pass
|
106
|
-
}
|
107
|
-
|
108
|
-
# puts JSON.pretty_generate(body_params)
|
109
|
-
|
110
|
-
login_response_data = nil
|
111
|
-
|
112
|
-
begin
|
113
|
-
|
114
|
-
con = HttpClient.create_instance_with_proxy
|
115
|
-
res = con.post do |req|
|
116
|
-
req.url boss_url
|
117
|
-
req.headers['Content-Type'] = 'application/json'
|
118
|
-
req.body = body_params.to_json
|
45
|
+
@token = load_token()
|
46
|
+
if !@token.nil? && !@token["token"].nil? && !@token["username"].nil?
|
47
|
+
# 检查token是否已过期(超过7天)
|
48
|
+
if @token["expires_at"] && Time.now.to_i < @token["expires_at"]
|
49
|
+
need_req_login = false
|
50
|
+
else
|
51
|
+
Funlog.instance.fancyinfo_error("令牌已过期,需要重新登录...")
|
52
|
+
login_success = true
|
53
|
+
end
|
54
|
+
else
|
55
|
+
need_req_login = true
|
56
|
+
end
|
119
57
|
end
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
58
|
+
if need_req_login
|
59
|
+
login_handle = PgyerFeishuOAuthCLI.new("cli_a7db8213883ed00d")
|
60
|
+
result = login_handle.authorize
|
61
|
+
@token = {}
|
62
|
+
@token["token"] = login_handle.access_token
|
63
|
+
@token["username"] = login_handle.username
|
64
|
+
@token["expires_at"] = login_handle.expires_at
|
65
|
+
store_token(token:@token)
|
66
|
+
login_success = true
|
67
|
+
else
|
68
|
+
login_success = true
|
124
69
|
end
|
125
70
|
|
126
|
-
|
127
|
-
Funlog.instance.fancyinfo_error("pgyer登录失败!")
|
128
|
-
puts "登录失败,请重试!!!"
|
129
|
-
end
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
# puts JSON.pretty_generate(login_response_data)
|
134
|
-
|
135
|
-
if !login_response_data.nil? && !login_response_data["code"].nil? && login_response_data["code"].to_s.eql?("200")
|
136
|
-
|
137
|
-
token = {}
|
138
|
-
token= login_response_data["data"]
|
139
|
-
token["password"] = checksum_pass
|
140
|
-
# puts JSON.pretty_generate(token)
|
141
|
-
store_token(token:token)
|
142
|
-
|
143
|
-
Funlog.instance.fancyinfo_success("用户:#{@token["username"]}登录pgyer成功!")
|
144
|
-
|
145
|
-
else
|
146
|
-
if File.exist?(@pgyer_token_file)
|
147
|
-
FileUtils.rm_rf(@pgyer_token_file)
|
148
|
-
end
|
149
|
-
Funlog.instance.fancyinfo_error("pgyer登录失败!")
|
150
|
-
end
|
151
|
-
|
152
|
-
return token
|
153
|
-
|
71
|
+
return login_success
|
154
72
|
end
|
155
73
|
|
74
|
+
|
156
75
|
def load_token()
|
157
76
|
|
158
77
|
@token = nil
|
@@ -165,6 +84,12 @@ module Pindo
|
|
165
84
|
temp_token = data_string
|
166
85
|
temp_token = JSON.parse(data_string)
|
167
86
|
if !temp_token.nil? && !temp_token["token"].nil? && !temp_token["username"].nil?
|
87
|
+
# 检查token是否已过期
|
88
|
+
if temp_token["expires_at"] && Time.now.to_i > temp_token["expires_at"]
|
89
|
+
Funlog.instance.fancyinfo_error("令牌已过期,需要重新登录...")
|
90
|
+
return nil
|
91
|
+
end
|
92
|
+
|
168
93
|
@token = temp_token
|
169
94
|
Funlog.instance.fancyinfo_success("读取pgyer token成功!")
|
170
95
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
|
2
1
|
require 'uri'
|
3
2
|
require 'json'
|
4
3
|
require 'faraday'
|
5
4
|
require 'securerandom'
|
6
5
|
require 'pindo/base/aeshelper'
|
7
6
|
require 'typhoeus'
|
7
|
+
require 'thread'
|
8
8
|
|
9
9
|
module Pindo
|
10
10
|
|
@@ -44,6 +44,13 @@ module Pindo
|
|
44
44
|
@pgyer_aes_key = config_json["pgyerapps_aes_key"]
|
45
45
|
|
46
46
|
@token = load_token
|
47
|
+
|
48
|
+
# 添加互斥锁用于线程安全
|
49
|
+
@upload_eTags_mutex = Mutex.new
|
50
|
+
@tasks_queue_mutex = Mutex.new
|
51
|
+
@active_tasks_mutex = Mutex.new
|
52
|
+
@upload_failed_mutex = Mutex.new # 为上传失败状态添加互斥锁
|
53
|
+
@upload_failed = false
|
47
54
|
|
48
55
|
rescue => error
|
49
56
|
raise Informative, "PgyerUploadClient 初始化失败!"
|
@@ -72,9 +79,12 @@ module Pindo
|
|
72
79
|
|
73
80
|
def upload_file(binary_file:nil, isAttach:false)
|
74
81
|
|
82
|
+
raise Informative, "上传文件不能为空" if binary_file.nil? || !File.exist?(binary_file)
|
83
|
+
|
75
84
|
@upload_binary_file = binary_file
|
76
85
|
@file_size = File.size(@upload_binary_file)
|
77
86
|
@progress_bar = PgyerUploadProgressBar.new(upload_total_size:@file_size)
|
87
|
+
@upload_failed = false # 重置上传失败标志
|
78
88
|
|
79
89
|
extension = File.extname(@upload_binary_file)
|
80
90
|
filename = File.basename(@upload_binary_file)
|
@@ -104,135 +114,231 @@ module Pindo
|
|
104
114
|
|
105
115
|
upload_result = nil
|
106
116
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
117
|
+
begin
|
118
|
+
file_size_param = 1.00 * @file_size / 1024 /1024
|
119
|
+
result_data = post_upload_url_req(upload_path_key:upload_path_key, file_ceil_size:file_size_param.ceil)
|
120
|
+
|
121
|
+
if result_data.nil? || !result_data.has_key?("data") || !result_data["data"].has_key?("uploadId")
|
122
|
+
raise Informative, "获取上传ID失败,请检查网络或服务器状态"
|
123
|
+
end
|
124
|
+
|
125
|
+
upload_id = result_data["data"]["uploadId"]
|
126
|
+
# 创建统一的任务队列
|
127
|
+
@tasks_queue = Queue.new
|
128
|
+
@worker_threads = []
|
129
|
+
upload_params_list = result_data["data"]["uploadParamsList"]
|
130
|
+
upload_item_num = upload_params_list.length
|
131
|
+
@expected_parts = upload_item_num # 保存预期的分片数量
|
132
|
+
task_num = upload_item_num
|
133
|
+
retry_count = 5
|
134
|
+
|
135
|
+
# 合理限制线程数
|
136
|
+
if task_num < 2
|
137
|
+
task_num = 2
|
138
|
+
end
|
128
139
|
|
129
|
-
|
140
|
+
if task_num > 30
|
141
|
+
task_num = 30
|
142
|
+
end
|
130
143
|
|
144
|
+
# 根据系统CPU核心数自动调整线程数
|
145
|
+
available_cores = Etc.respond_to?(:nprocessors) ? Etc.nprocessors : 4
|
146
|
+
task_num = [task_num, available_cores * 2].min
|
147
|
+
|
148
|
+
# 设置重试次数并将所有任务加入队列
|
149
|
+
upload_params_list.each do |item|
|
150
|
+
item["retryCount"] = retry_count
|
151
|
+
@tasks_queue.push(item)
|
152
|
+
end
|
131
153
|
|
132
|
-
|
154
|
+
puts "切分个数: #{upload_item_num}"
|
155
|
+
puts "线程个数: #{task_num}"
|
156
|
+
puts "重试次数: #{retry_count}"
|
157
|
+
puts
|
158
|
+
|
159
|
+
Funlog.instance.fancyinfo_start("开始上传...")
|
160
|
+
@upload_eTags = []
|
161
|
+
@active_tasks = 0 # 跟踪活动任务数量
|
162
|
+
|
163
|
+
continuous_upload_data_req(concurrency:task_num)
|
164
|
+
|
165
|
+
# 检查上传是否全部成功
|
166
|
+
if upload_failed? || @upload_eTags.length != @expected_parts
|
167
|
+
upload_result = nil
|
168
|
+
Funlog.instance.fancyinfo_error("文件#{@upload_binary_file} 上传失败! 😭😭😭")
|
169
|
+
return upload_result
|
170
|
+
end
|
133
171
|
|
134
|
-
|
172
|
+
result_data = post_upload_finish_req(upload_path_key:upload_path_key, upload_id:upload_id, eTags:@upload_eTags)
|
135
173
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
174
|
+
if result_data && result_data["code"] == 200
|
175
|
+
upload_result = upload_path_key
|
176
|
+
Funlog.instance.fancyinfo_success("文件#{@upload_binary_file} 上传成功! 😎😎😎")
|
177
|
+
else
|
178
|
+
upload_result = nil
|
179
|
+
error_msg = result_data && result_data["msg"] ? result_data["msg"] : "未知错误"
|
180
|
+
Funlog.instance.fancyinfo_error("文件#{@upload_binary_file} 上传失败: #{error_msg} 😭😭😭")
|
181
|
+
end
|
182
|
+
|
183
|
+
rescue => e
|
184
|
+
upload_result = nil
|
185
|
+
Funlog.instance.fancyinfo_error("文件上传过程发生异常: #{e.message} 😭😭😭")
|
186
|
+
ensure
|
187
|
+
# 确保所有工作线程都被清理
|
188
|
+
cleanup_worker_threads
|
142
189
|
end
|
143
190
|
|
144
191
|
return upload_result
|
145
192
|
|
146
193
|
end
|
147
194
|
|
148
|
-
|
149
|
-
def
|
150
|
-
|
151
|
-
for i in 0..@upload_params_list.length-1 do
|
152
|
-
@upload_params_list[i]["retryCount"] = retry_count
|
153
|
-
end
|
154
|
-
|
155
|
-
while @upload_params_list.size > 0
|
156
|
-
upload_params_list_temp = []
|
157
|
-
#每次最大5个线程上传
|
158
|
-
for i in 1..task_num do
|
159
|
-
upload_params_list_temp << @upload_params_list.shift
|
160
|
-
end
|
161
|
-
hydra = Typhoeus::Hydra.new
|
162
|
-
while upload_params_list_temp.size > 0
|
163
|
-
upload_params_item = upload_params_list_temp.shift
|
164
|
-
unless upload_params_item.nil?
|
165
|
-
single_task_upload_part_data_req(upload_params_item:upload_params_item, hydra_handle:hydra)
|
166
|
-
end
|
167
|
-
end
|
168
|
-
hydra.run
|
169
|
-
end
|
170
|
-
|
195
|
+
# 安全地检查上传失败状态
|
196
|
+
def upload_failed?
|
197
|
+
@upload_failed_mutex.synchronize { @upload_failed }
|
171
198
|
end
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
@
|
199
|
+
|
200
|
+
# 安全地设置上传失败状态
|
201
|
+
def set_upload_failed(error_msg = nil)
|
202
|
+
@upload_failed_mutex.synchronize do
|
203
|
+
@upload_failed = true
|
204
|
+
Funlog.instance.fancyinfo_error("上传失败: #{error_msg}") if error_msg
|
177
205
|
end
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
206
|
+
end
|
207
|
+
|
208
|
+
# 清理所有工作线程
|
209
|
+
def cleanup_worker_threads
|
210
|
+
@worker_threads.each do |thread|
|
211
|
+
# 尝试安全终止线程
|
212
|
+
thread.exit if thread.alive?
|
182
213
|
end
|
183
|
-
|
184
|
-
|
185
|
-
return @eTags
|
214
|
+
@worker_threads.clear
|
186
215
|
end
|
187
216
|
|
188
|
-
def
|
189
|
-
|
217
|
+
def continuous_upload_data_req(concurrency:1)
|
218
|
+
# 初始化活动任务计数和条件变量
|
219
|
+
@active_tasks = 0
|
220
|
+
@task_complete_cv = ConditionVariable.new
|
221
|
+
|
222
|
+
# 初始化连续上传,最多启动concurrency个并发任务
|
223
|
+
start_tasks = [concurrency, @tasks_queue.size].min
|
224
|
+
start_tasks.times { schedule_next_task }
|
225
|
+
|
226
|
+
# 设置超时保护
|
227
|
+
timeout_seconds = 300 # 5分钟超时
|
228
|
+
start_time = Time.now
|
229
|
+
|
230
|
+
# 等待所有任务完成
|
231
|
+
@tasks_queue_mutex.synchronize do
|
232
|
+
while (@active_tasks > 0 || !@tasks_queue.empty?) && !upload_failed?
|
233
|
+
# 添加超时保护
|
234
|
+
remaining_time = timeout_seconds - (Time.now - start_time)
|
235
|
+
if remaining_time <= 0
|
236
|
+
set_upload_failed("上传任务超时")
|
237
|
+
break
|
238
|
+
end
|
239
|
+
|
240
|
+
# 等待任务完成通知,最多等待30秒
|
241
|
+
@task_complete_cv.wait(@tasks_queue_mutex, [remaining_time, 30].min)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# 检查所有分片是否都上传成功
|
246
|
+
if @upload_eTags.length != @expected_parts && !upload_failed?
|
247
|
+
set_upload_failed("部分分片上传失败,已上传#{@upload_eTags.length}/#{@expected_parts}")
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def schedule_next_task
|
252
|
+
# 检查是否应该停止调度
|
253
|
+
return if upload_failed?
|
254
|
+
|
255
|
+
# 尝试从队列中获取下一个任务
|
256
|
+
@tasks_queue_mutex.synchronize do
|
257
|
+
unless @tasks_queue.empty?
|
258
|
+
upload_params_item = @tasks_queue.pop
|
259
|
+
@active_tasks_mutex.synchronize { @active_tasks += 1 }
|
260
|
+
|
261
|
+
# 异步处理任务,不阻塞主线程
|
262
|
+
worker_thread = Thread.new do
|
263
|
+
begin
|
264
|
+
process_upload_task(upload_params_item)
|
265
|
+
rescue => e
|
266
|
+
# 捕获并记录任务处理过程中的异常
|
267
|
+
set_upload_failed("处理分片#{upload_params_item["partNo"]}时出错: #{e.message}")
|
268
|
+
ensure
|
269
|
+
# 任务完成后,减少活动任务计数并通知等待线程
|
270
|
+
@active_tasks_mutex.synchronize { @active_tasks -= 1 }
|
271
|
+
|
272
|
+
# 如果队列不为空,调度下一个任务
|
273
|
+
schedule_next_task if !upload_failed?
|
274
|
+
|
275
|
+
# 通知等待线程任务已完成
|
276
|
+
@tasks_queue_mutex.synchronize { @task_complete_cv.broadcast }
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
# 保存线程引用以便后续清理
|
281
|
+
@worker_threads << worker_thread
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def process_upload_task(upload_params_item)
|
190
287
|
upload_url = upload_params_item["signedUrl"]
|
191
288
|
part_no = upload_params_item["partNo"]
|
192
|
-
|
289
|
+
|
193
290
|
file_size_ele = 1024 * 1024 * 5 #5M
|
194
|
-
start_position = file_size_ele * (part_no -1)
|
291
|
+
start_position = file_size_ele * (part_no - 1)
|
195
292
|
if part_no * file_size_ele > @file_size
|
196
|
-
|
293
|
+
read_length = @file_size - start_position
|
197
294
|
else
|
198
|
-
|
295
|
+
read_length = file_size_ele
|
199
296
|
end
|
200
|
-
|
297
|
+
|
201
298
|
file = File.open(@upload_binary_file, "rb")
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
299
|
+
begin
|
300
|
+
file.seek(start_position)
|
301
|
+
put_data = file.read(read_length)
|
302
|
+
|
303
|
+
request = create_req(upload_url:upload_url, body_data:put_data, read_length:read_length)
|
304
|
+
|
305
|
+
# 设置上传进度回调
|
306
|
+
upload_size_last = 0
|
307
|
+
request.on_progress do |dltotal, dlnow, ultotal, ulnow|
|
308
|
+
if ulnow && ulnow > upload_size_last
|
309
|
+
upload_size_last = ulnow
|
310
|
+
@progress_bar.update_upload_index(upload_part:part_no, upload_size:ulnow)
|
311
|
+
@progress_bar.update_upload_progress()
|
312
|
+
end
|
210
313
|
end
|
211
|
-
|
212
|
-
|
213
|
-
|
314
|
+
|
315
|
+
# 设置请求超时
|
316
|
+
request.options[:timeout] = 300 # 5分钟超时
|
317
|
+
|
318
|
+
# 执行请求并等待完成
|
319
|
+
response = request.run
|
320
|
+
|
321
|
+
# 处理响应结果
|
214
322
|
if response.success?
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
323
|
+
@progress_bar.complete_upload_index(upload_part:part_no, complete_size:read_length)
|
324
|
+
etag = response.headers["ETag"]
|
325
|
+
if etag.nil? || etag.empty?
|
326
|
+
raise "服务器返回的ETag为空"
|
327
|
+
end
|
328
|
+
eTag_item = { partNumber: part_no, tag: etag}
|
329
|
+
@upload_eTags_mutex.synchronize { @upload_eTags << eTag_item }
|
219
330
|
else
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
end
|
331
|
+
@progress_bar.delete_upload_index(upload_part:part_no)
|
332
|
+
upload_params_item["retryCount"] = upload_params_item["retryCount"] - 1
|
333
|
+
if upload_params_item["retryCount"] > 0
|
334
|
+
# 重试任务
|
335
|
+
@tasks_queue_mutex.synchronize { @tasks_queue.push(upload_params_item) }
|
336
|
+
else
|
337
|
+
set_upload_failed("文件#{@upload_binary_file} 分片#{part_no}上传失败: HTTP #{response.code}")
|
338
|
+
end
|
229
339
|
end
|
230
|
-
|
231
|
-
|
232
|
-
if hydra_handle.nil?
|
233
|
-
request.run
|
234
|
-
else
|
235
|
-
hydra_handle.queue(request)
|
340
|
+
ensure
|
341
|
+
file.close
|
236
342
|
end
|
237
343
|
end
|
238
344
|
|
@@ -284,26 +390,30 @@ module Pindo
|
|
284
390
|
tags:eTags
|
285
391
|
}
|
286
392
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
393
|
+
begin
|
394
|
+
con = HttpClient.create_instance_with_proxy
|
395
|
+
res = con.post do |req|
|
396
|
+
req.url boss_url
|
397
|
+
req.headers['Content-Type'] = 'application/json'
|
398
|
+
req.headers['token'] = @token["token"]
|
399
|
+
req.body = body_params.to_json
|
400
|
+
req.options.timeout = 120 # 设置2分钟超时
|
401
|
+
end
|
294
402
|
|
403
|
+
result_data = nil
|
404
|
+
if !res.body.nil?
|
405
|
+
result_data = JSON.parse(res.body)
|
406
|
+
end
|
295
407
|
|
296
|
-
|
297
|
-
|
298
|
-
|
408
|
+
return result_data
|
409
|
+
rescue => e
|
410
|
+
Funlog.instance.fancyinfo_error("完成上传请求失败: #{e.message}")
|
411
|
+
return nil
|
299
412
|
end
|
300
|
-
|
301
|
-
return result_data
|
302
|
-
|
303
413
|
end
|
304
414
|
|
305
415
|
|
306
|
-
|
416
|
+
def post_upload_url_req(upload_path_key:nil, file_ceil_size:nil)
|
307
417
|
|
308
418
|
boss_url = @baseurl + @request_config["multi_signed_url_upload"]
|
309
419
|
|
@@ -313,22 +423,26 @@ module Pindo
|
|
313
423
|
fileSize:file_ceil_size
|
314
424
|
}
|
315
425
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
426
|
+
begin
|
427
|
+
con = HttpClient.create_instance_with_proxy
|
428
|
+
res = con.post do |req|
|
429
|
+
req.url boss_url
|
430
|
+
req.headers['Content-Type'] = 'application/json'
|
431
|
+
req.headers['token'] = @token["token"]
|
432
|
+
req.body = body_params.to_json
|
433
|
+
req.options.timeout = 60 # 设置1分钟超时
|
434
|
+
end
|
323
435
|
|
436
|
+
result_data = nil
|
437
|
+
if !res.body.nil?
|
438
|
+
result_data = JSON.parse(res.body)
|
439
|
+
end
|
324
440
|
|
325
|
-
|
326
|
-
|
327
|
-
|
441
|
+
return result_data
|
442
|
+
rescue => e
|
443
|
+
Funlog.instance.fancyinfo_error("获取上传URL失败: #{e.message}")
|
444
|
+
return nil
|
328
445
|
end
|
329
|
-
|
330
|
-
return result_data
|
331
|
-
|
332
446
|
end
|
333
447
|
|
334
448
|
class PgyerUploadProgressBar
|
@@ -341,50 +455,58 @@ module Pindo
|
|
341
455
|
attr_accessor :is_done
|
342
456
|
|
343
457
|
def initialize(upload_total_size:nil, draw_char:'>')
|
344
|
-
|
345
|
-
|
346
458
|
@upload_total_size = upload_total_size
|
347
459
|
@draw_char = draw_char
|
348
460
|
@last_update_time = (Time.now.to_f * 1000).to_i #毫秒
|
349
461
|
|
350
462
|
@complete_size = 0
|
351
463
|
@update_ing_size = {}
|
352
|
-
|
353
464
|
@is_done = false
|
465
|
+
|
466
|
+
# 添加互斥锁来保护进度条更新
|
467
|
+
@mutex = Mutex.new
|
354
468
|
end
|
355
469
|
|
356
470
|
def update_upload_index(upload_part:nil, upload_size:nil)
|
357
|
-
@
|
471
|
+
@mutex.synchronize do
|
472
|
+
@update_ing_size[upload_part] = upload_size
|
473
|
+
end
|
358
474
|
end
|
359
475
|
|
360
476
|
def delete_upload_index(upload_part:nil)
|
361
|
-
@
|
477
|
+
@mutex.synchronize do
|
478
|
+
@update_ing_size[upload_part] = 0
|
479
|
+
end
|
362
480
|
end
|
363
481
|
|
364
482
|
def complete_upload_index(upload_part:nil, complete_size:nil)
|
365
|
-
@
|
366
|
-
|
483
|
+
@mutex.synchronize do
|
484
|
+
@complete_size = @complete_size + complete_size
|
485
|
+
@update_ing_size[upload_part] = 0
|
486
|
+
end
|
367
487
|
end
|
368
488
|
|
369
489
|
def update_upload_progress()
|
370
490
|
time_now = (Time.now.to_f * 1000).to_i #毫秒
|
371
491
|
if time_now - @last_update_time > 80
|
372
|
-
@
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
492
|
+
@mutex.synchronize do
|
493
|
+
@last_update_time = time_now
|
494
|
+
total_num = @upload_total_size
|
495
|
+
index_num = @complete_size
|
496
|
+
@update_ing_size.each do |key, value|
|
497
|
+
index_num = index_num + value
|
498
|
+
end
|
499
|
+
|
500
|
+
progress_str = sprintf("%.2f", 100.0 * index_num / total_num )
|
501
|
+
total_size = sprintf("%.2f", 1.00 * total_num / 1024 /1024 )
|
502
|
+
upload_size = sprintf("%.2f", 1.00 * index_num / 1024 /1024 )
|
503
|
+
index = 40.0 * index_num / total_num
|
504
|
+
upload_message = "已上传:#{upload_size}MB|#{progress_str}\%【" + (@draw_char * (index/1).floor).ljust(40.0, '_') + "】Total:#{total_size}MB"
|
505
|
+
Funlog.instance.fancyinfo_update(upload_message)
|
506
|
+
if index_num == total_num && !@is_done
|
507
|
+
@is_done = true
|
508
|
+
Funlog.instance.fancyinfo_success(upload_message)
|
509
|
+
end
|
388
510
|
end
|
389
511
|
end
|
390
512
|
end
|
data/lib/pindo/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pindo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.0.
|
4
|
+
version: 5.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- wade
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-04-16 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: claide
|
@@ -133,16 +133,22 @@ dependencies:
|
|
133
133
|
name: faraday-retry
|
134
134
|
requirement: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '1.0'
|
136
139
|
- - ">="
|
137
140
|
- !ruby/object:Gem::Version
|
138
|
-
version:
|
141
|
+
version: 1.0.3
|
139
142
|
type: :runtime
|
140
143
|
prerelease: false
|
141
144
|
version_requirements: !ruby/object:Gem::Requirement
|
142
145
|
requirements:
|
146
|
+
- - "~>"
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '1.0'
|
143
149
|
- - ">="
|
144
150
|
- !ruby/object:Gem::Version
|
145
|
-
version:
|
151
|
+
version: 1.0.3
|
146
152
|
- !ruby/object:Gem::Dependency
|
147
153
|
name: typhoeus
|
148
154
|
requirement: !ruby/object:Gem::Requirement
|
@@ -183,6 +189,26 @@ dependencies:
|
|
183
189
|
- - ">="
|
184
190
|
- !ruby/object:Gem::Version
|
185
191
|
version: 1.15.4
|
192
|
+
- !ruby/object:Gem::Dependency
|
193
|
+
name: webrick
|
194
|
+
requirement: !ruby/object:Gem::Requirement
|
195
|
+
requirements:
|
196
|
+
- - "~>"
|
197
|
+
- !ruby/object:Gem::Version
|
198
|
+
version: '1.8'
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: 1.8.1
|
202
|
+
type: :runtime
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - "~>"
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '1.8'
|
209
|
+
- - ">="
|
210
|
+
- !ruby/object:Gem::Version
|
211
|
+
version: 1.8.1
|
186
212
|
- !ruby/object:Gem::Dependency
|
187
213
|
name: bundler
|
188
214
|
requirement: !ruby/object:Gem::Requirement
|
@@ -207,30 +233,36 @@ dependencies:
|
|
207
233
|
name: rake
|
208
234
|
requirement: !ruby/object:Gem::Requirement
|
209
235
|
requirements:
|
236
|
+
- - "~>"
|
237
|
+
- !ruby/object:Gem::Version
|
238
|
+
version: '13.0'
|
210
239
|
- - ">="
|
211
240
|
- !ruby/object:Gem::Version
|
212
|
-
version:
|
241
|
+
version: 13.0.6
|
213
242
|
type: :development
|
214
243
|
prerelease: false
|
215
244
|
version_requirements: !ruby/object:Gem::Requirement
|
216
245
|
requirements:
|
246
|
+
- - "~>"
|
247
|
+
- !ruby/object:Gem::Version
|
248
|
+
version: '13.0'
|
217
249
|
- - ">="
|
218
250
|
- !ruby/object:Gem::Version
|
219
|
-
version:
|
251
|
+
version: 13.0.6
|
220
252
|
- !ruby/object:Gem::Dependency
|
221
253
|
name: rspec
|
222
254
|
requirement: !ruby/object:Gem::Requirement
|
223
255
|
requirements:
|
224
|
-
- - "
|
256
|
+
- - "~>"
|
225
257
|
- !ruby/object:Gem::Version
|
226
|
-
version: '
|
258
|
+
version: '3.12'
|
227
259
|
type: :development
|
228
260
|
prerelease: false
|
229
261
|
version_requirements: !ruby/object:Gem::Requirement
|
230
262
|
requirements:
|
231
|
-
- - "
|
263
|
+
- - "~>"
|
232
264
|
- !ruby/object:Gem::Version
|
233
|
-
version: '
|
265
|
+
version: '3.12'
|
234
266
|
description: easy work for deploy, dev
|
235
267
|
email:
|
236
268
|
- wade@gmail.com
|
@@ -258,6 +290,7 @@ files:
|
|
258
290
|
- lib/pindo/client/feishuclient.rb
|
259
291
|
- lib/pindo/client/giteeclient.rb
|
260
292
|
- lib/pindo/client/httpclient.rb
|
293
|
+
- lib/pindo/client/pgyer_feishu_oauth_cli.rb
|
261
294
|
- lib/pindo/client/pgyerclient.rb
|
262
295
|
- lib/pindo/client/pgyeruploadclient.rb
|
263
296
|
- lib/pindo/client/tgateclient.rb
|