pindo 5.2.4 → 5.3.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/base/aeshelper.rb +23 -2
- data/lib/pindo/base/pindocontext.rb +259 -0
- data/lib/pindo/client/pgyer_feishu_oauth_cli.rb +343 -80
- data/lib/pindo/client/pgyerclient.rb +30 -20
- data/lib/pindo/command/android/autobuild.rb +52 -22
- data/lib/pindo/command/android/build.rb +27 -16
- data/lib/pindo/command/android/debug.rb +25 -15
- data/lib/pindo/command/dev/debug.rb +2 -51
- data/lib/pindo/command/dev/feishu.rb +19 -2
- data/lib/pindo/command/ios/adhoc.rb +2 -1
- data/lib/pindo/command/ios/autobuild.rb +35 -8
- data/lib/pindo/command/ios/debug.rb +2 -132
- data/lib/pindo/command/lib/lint.rb +24 -1
- data/lib/pindo/command/setup.rb +24 -4
- data/lib/pindo/command/unity/apk.rb +15 -0
- data/lib/pindo/command/unity/ipa.rb +16 -0
- data/lib/pindo/command.rb +13 -2
- data/lib/pindo/module/android/android_build_config_helper.rb +425 -0
- data/lib/pindo/module/android/apk_helper.rb +23 -25
- data/lib/pindo/module/android/base_helper.rb +572 -0
- data/lib/pindo/module/android/build_helper.rb +8 -318
- data/lib/pindo/module/android/gp_compliance_helper.rb +668 -0
- data/lib/pindo/module/android/gradle_helper.rb +746 -3
- data/lib/pindo/module/appselect.rb +18 -5
- data/lib/pindo/module/build/buildhelper.rb +120 -29
- data/lib/pindo/module/build/unityhelper.rb +675 -18
- data/lib/pindo/module/build/versionhelper.rb +146 -0
- data/lib/pindo/module/cert/certhelper.rb +33 -2
- data/lib/pindo/module/cert/xcodecerthelper.rb +3 -1
- data/lib/pindo/module/pgyer/pgyerhelper.rb +114 -31
- data/lib/pindo/module/xcode/xcodebuildconfig.rb +232 -0
- data/lib/pindo/module/xcode/xcodebuildhelper.rb +0 -1
- data/lib/pindo/version.rb +356 -86
- data/lib/pindo.rb +72 -3
- metadata +5 -1
@@ -20,14 +20,15 @@ module Pindo
|
|
20
20
|
def initialize(client_id)
|
21
21
|
@client_id = client_id
|
22
22
|
@feishu_auth_url = 'https://passport.feishu.cn/suite/passport/oauth/authorize?'
|
23
|
-
|
23
|
+
|
24
24
|
# 保持原有的redirect_uri,飞书OAuth流程中仍使用这个URL
|
25
25
|
# 前端UI会检测state=terminal_login并自动跳转到localhost:8899
|
26
26
|
@redirect_uri = 'https://pgyerapps.com/login'
|
27
27
|
@pgyer_api_endpoint = 'https://api.pgyerapps.com/api/user/lark_qr_login'
|
28
|
-
|
28
|
+
|
29
29
|
@state = 'terminal_login'
|
30
|
-
|
30
|
+
@server_port = 8899
|
31
|
+
|
31
32
|
@larkScopeList = [
|
32
33
|
'task:task:write',
|
33
34
|
'task:section:write',
|
@@ -44,53 +45,120 @@ module Pindo
|
|
44
45
|
def authorize
|
45
46
|
# 不再检查已存储的令牌,直接开始授权流程
|
46
47
|
authorization_uri = build_authorization_uri
|
47
|
-
puts "正在打开浏览器进行飞书OAuth授权..."
|
48
|
-
puts "
|
49
|
-
puts "
|
50
|
-
|
48
|
+
puts "🚀 正在打开浏览器进行飞书 OAuth 授权..."
|
49
|
+
puts "\n📋 授权链接(如浏览器未自动打开,请手动复制访问):"
|
50
|
+
puts "-" * 60
|
51
|
+
puts authorization_uri
|
52
|
+
puts "-" * 60
|
53
|
+
|
51
54
|
# 在浏览器中打开授权URL
|
52
55
|
open_browser(authorization_uri)
|
53
|
-
|
56
|
+
|
54
57
|
# 启动本地服务器处理回调
|
55
58
|
code = start_callback_server
|
56
|
-
|
59
|
+
|
57
60
|
# 如果自动获取失败,提示用户手动输入
|
58
61
|
if code.nil?
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
62
|
+
# 停止任何可能的 spinner 动画
|
63
|
+
if defined?(Funlog) && Funlog.instance.instance_variable_get(:@spinner_log)
|
64
|
+
Funlog.instance.instance_variable_set(:@spinner_log, nil)
|
65
|
+
end
|
66
|
+
|
67
|
+
loop do
|
68
|
+
puts "\n⚠️ 自动获取授权码失败,请选择操作:"
|
69
|
+
puts " 1. 输入授权码(复制 code= 后的内容)"
|
70
|
+
puts " 2. 输入完整回调 URL"
|
71
|
+
puts " 3. 重新打开授权网页"
|
72
|
+
puts " 4. 退出登录"
|
73
|
+
print "请选择 (1-4): "
|
74
|
+
STDOUT.flush # 确保提示符立即显示
|
75
|
+
|
67
76
|
begin
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
if
|
72
|
-
puts "
|
77
|
+
choice = STDIN.gets&.chomp
|
78
|
+
|
79
|
+
# 处理 Ctrl+C 中断
|
80
|
+
if choice.nil?
|
81
|
+
puts "\n\n🚪 用户中断操作"
|
82
|
+
return :user_cancelled
|
73
83
|
end
|
74
|
-
|
75
|
-
|
84
|
+
|
85
|
+
case choice
|
86
|
+
when "1"
|
87
|
+
puts "\n请输入授权码:"
|
88
|
+
print "🔑 "
|
89
|
+
STDOUT.flush # 确保提示符立即显示
|
90
|
+
code_input = STDIN.gets&.chomp
|
91
|
+
if code_input.nil?
|
92
|
+
puts "\n🚪 用户中断操作"
|
93
|
+
return :user_cancelled
|
94
|
+
elsif !code_input.empty?
|
95
|
+
code = code_input
|
96
|
+
break
|
97
|
+
else
|
98
|
+
puts "⚠️ 授权码不能为空,请重新选择"
|
99
|
+
end
|
100
|
+
when "2"
|
101
|
+
puts "\n请输入完整回调 URL:"
|
102
|
+
print "🔗 "
|
103
|
+
STDOUT.flush # 确保提示符立即显示
|
104
|
+
url_input = STDIN.gets&.chomp
|
105
|
+
if url_input.nil?
|
106
|
+
puts "\n🚪 用户中断操作"
|
107
|
+
return :user_cancelled
|
108
|
+
elsif url_input.start_with?("http")
|
109
|
+
# 尝试从 URL 中提取 code
|
110
|
+
begin
|
111
|
+
uri = URI(url_input)
|
112
|
+
query_params = URI.decode_www_form(uri.query || '').to_h
|
113
|
+
code = query_params['code']
|
114
|
+
if code
|
115
|
+
puts "✅ 成功从 URL 提取授权码"
|
116
|
+
break
|
117
|
+
else
|
118
|
+
puts "❌ URL 中未找到授权码,请重新选择"
|
119
|
+
end
|
120
|
+
rescue => e
|
121
|
+
puts "❌ 解析 URL 失败: #{e.message}"
|
122
|
+
end
|
123
|
+
else
|
124
|
+
puts "❌ 无效的 URL 格式,请重新选择"
|
125
|
+
end
|
126
|
+
when "3"
|
127
|
+
# 重新打开授权网页
|
128
|
+
puts "\n🔄 重新打开授权网页..."
|
129
|
+
open_browser(authorization_uri)
|
130
|
+
# 重新启动服务器尝试获取授权码
|
131
|
+
code = start_callback_server
|
132
|
+
if code
|
133
|
+
break
|
134
|
+
end
|
135
|
+
# 如果还是失败,继续循环让用户选择
|
136
|
+
when "4"
|
137
|
+
puts "\n🚪 已取消登录"
|
138
|
+
return :user_cancelled
|
139
|
+
else
|
140
|
+
puts "❌ 无效的选择,请输入 1-4"
|
141
|
+
end
|
142
|
+
rescue Interrupt
|
143
|
+
puts "\n\n🚪 用户中断操作"
|
144
|
+
return :user_cancelled
|
76
145
|
end
|
77
|
-
else
|
78
|
-
# 将输入直接作为code
|
79
|
-
code = input unless input.empty?
|
80
146
|
end
|
81
147
|
end
|
82
|
-
|
148
|
+
|
83
149
|
if code
|
84
|
-
puts "
|
85
|
-
|
150
|
+
puts "\n✅ 成功获取授权码"
|
151
|
+
print "🔄 正在验证飞书身份..."
|
152
|
+
STDOUT.flush
|
86
153
|
if login_pgyer_with_feishu(code:code)
|
87
|
-
puts "
|
154
|
+
puts " ✅"
|
155
|
+
puts "🎉 登录成功!欢迎使用 Pgyer"
|
88
156
|
# 登录成功后,返回true,外部程序可以通过访问access_token属性获取token
|
89
157
|
return true
|
90
158
|
end
|
91
159
|
return false
|
92
160
|
else
|
93
|
-
puts "授权失败"
|
161
|
+
puts "\n❌ 授权失败"
|
94
162
|
return false
|
95
163
|
end
|
96
164
|
end
|
@@ -132,12 +200,9 @@ module Pindo
|
|
132
200
|
scope: @larkScopeList.join(' ')
|
133
201
|
}
|
134
202
|
|
135
|
-
puts "请求Pgyer API: #{@pgyer_api_endpoint}"
|
136
|
-
puts "请求参数: #{body_params.to_json}"
|
137
|
-
|
138
203
|
# 使用HttpClient发送请求
|
139
204
|
con = HttpClient.create_instance_with_proxy
|
140
|
-
|
205
|
+
|
141
206
|
begin
|
142
207
|
res = con.post do |req|
|
143
208
|
req.url @pgyer_api_endpoint
|
@@ -145,8 +210,6 @@ module Pindo
|
|
145
210
|
req.body = body_params.to_json
|
146
211
|
end
|
147
212
|
|
148
|
-
puts "API响应状态码: #{res.status}"
|
149
|
-
|
150
213
|
# 处理响应
|
151
214
|
result = nil
|
152
215
|
if !res.body.nil?
|
@@ -162,26 +225,39 @@ module Pindo
|
|
162
225
|
return true
|
163
226
|
else
|
164
227
|
error_msg = result['meta'] && result['meta']['message'] ? result['meta']['message'] : '未知错误'
|
165
|
-
puts "
|
228
|
+
puts " ❌"
|
229
|
+
puts "登录失败: #{error_msg}"
|
166
230
|
return false
|
167
231
|
end
|
168
232
|
rescue => e
|
169
|
-
puts "
|
170
|
-
puts "
|
233
|
+
puts " ❌"
|
234
|
+
puts "响应解析失败: #{e.message}"
|
171
235
|
return false
|
172
236
|
end
|
173
237
|
else
|
174
|
-
puts "
|
238
|
+
puts " ❌"
|
239
|
+
puts "服务器返回空响应"
|
175
240
|
return false
|
176
241
|
end
|
177
242
|
rescue => e
|
178
|
-
puts "
|
243
|
+
puts " ❌"
|
244
|
+
puts "网络请求失败: #{e.message}"
|
179
245
|
return false
|
180
246
|
end
|
181
247
|
end
|
182
248
|
|
183
249
|
private
|
184
250
|
|
251
|
+
# 获取跨平台的 null 设备路径
|
252
|
+
def get_null_device
|
253
|
+
case RbConfig::CONFIG['host_os']
|
254
|
+
when /mswin|mingw|cygwin/
|
255
|
+
'NUL'
|
256
|
+
else
|
257
|
+
'/dev/null'
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
185
261
|
|
186
262
|
# 构建授权URI
|
187
263
|
def build_authorization_uri
|
@@ -201,11 +277,13 @@ module Pindo
|
|
201
277
|
|
202
278
|
# 在浏览器中打开URL
|
203
279
|
def open_browser(url)
|
204
|
-
|
205
|
-
|
206
|
-
|
280
|
+
case RbConfig::CONFIG['host_os']
|
281
|
+
when /mswin|mingw|cygwin/
|
282
|
+
# Windows: 使用双引号包围URL避免参数解析问题
|
283
|
+
system("start \"\" \"#{url}\"")
|
284
|
+
when /darwin/
|
207
285
|
system("open", url)
|
208
|
-
|
286
|
+
when /linux|bsd/
|
209
287
|
system("xdg-open", url)
|
210
288
|
else
|
211
289
|
puts "无法自动打开浏览器,请手动访问: #{url}"
|
@@ -215,15 +293,38 @@ module Pindo
|
|
215
293
|
# 启动本地服务器处理回调
|
216
294
|
def start_callback_server
|
217
295
|
code = nil
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
296
|
+
|
297
|
+
# 检查并处理端口占用
|
298
|
+
unless ensure_port_available(@server_port)
|
299
|
+
puts "✗ 无法使用端口 #{@server_port},登录失败"
|
300
|
+
return nil
|
301
|
+
end
|
302
|
+
|
303
|
+
puts "\n🌐 本地服务器已启动(端口: #{@server_port})"
|
304
|
+
puts "💡 提示: 按 Ctrl+C 可中断并选择其他操作"
|
305
|
+
puts "🔄 等待飞书授权回调..."
|
306
|
+
|
307
|
+
begin
|
308
|
+
# 使用本地8899端口处理回调,不管redirect_uri配置如何
|
309
|
+
server = WEBrick::HTTPServer.new(
|
310
|
+
Port: @server_port,
|
311
|
+
BindAddress: '127.0.0.1',
|
312
|
+
Logger: WEBrick::Log.new(get_null_device),
|
313
|
+
AccessLog: []
|
314
|
+
)
|
315
|
+
rescue Errno::EADDRINUSE
|
316
|
+
puts "✗ 端口 #{@server_port} 仍被占用,无法启动服务器"
|
317
|
+
return nil
|
318
|
+
rescue Errno::ENOENT
|
319
|
+
puts "✗ 启动服务器失败: 系统找不到指定的路径或文件"
|
320
|
+
return nil
|
321
|
+
rescue => e
|
322
|
+
puts "✗ 启动服务器失败: #{e.message}"
|
323
|
+
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
324
|
+
puts " 提示: 如果是Windows系统,请确保防火墙允许Ruby访问网络"
|
325
|
+
end
|
326
|
+
return nil
|
327
|
+
end
|
227
328
|
|
228
329
|
# 处理根路径的请求
|
229
330
|
server.mount_proc '/' do |req, res|
|
@@ -234,9 +335,10 @@ module Pindo
|
|
234
335
|
# 安全解析请求参数
|
235
336
|
begin
|
236
337
|
query_params = URI.decode_www_form(req.query_string || '').to_h
|
237
|
-
|
338
|
+
# 只在有code时输出日志,避免多余信息
|
339
|
+
# puts "解析的参数: #{query_params.inspect}" if query_params['code']
|
238
340
|
rescue => e
|
239
|
-
puts "解析请求参数失败: #{e.message}"
|
341
|
+
# puts "解析请求参数失败: #{e.message}"
|
240
342
|
query_params = {}
|
241
343
|
end
|
242
344
|
|
@@ -246,12 +348,13 @@ module Pindo
|
|
246
348
|
# res.body = "状态不匹配,可能存在CSRF攻击风险"
|
247
349
|
# elsif query_params['error']
|
248
350
|
if query_params['error']
|
249
|
-
puts "授权错误: #{query_params['error']}"
|
351
|
+
# puts "授权错误: #{query_params['error']}"
|
250
352
|
res.content_type = "text/html; charset=UTF-8"
|
251
353
|
res.body = "授权错误: #{query_params['error']}"
|
252
354
|
elsif query_params['code']
|
253
355
|
code = query_params['code']
|
254
|
-
|
356
|
+
# 成功获取时不输出,避免重复
|
357
|
+
# puts "成功获取授权码: #{code}"
|
255
358
|
res.content_type = "text/html; charset=UTF-8"
|
256
359
|
res.body = <<-HTML
|
257
360
|
<!DOCTYPE html>
|
@@ -349,30 +452,26 @@ module Pindo
|
|
349
452
|
</html>
|
350
453
|
HTML
|
351
454
|
else
|
352
|
-
|
455
|
+
# 忽略没有参数的请求(如favicon.ico等)
|
456
|
+
# puts "未获取到授权码"
|
353
457
|
res.content_type = "text/html; charset=UTF-8"
|
354
|
-
res.body = "
|
458
|
+
res.body = "等待授权中..."
|
355
459
|
end
|
356
460
|
|
357
|
-
#
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
461
|
+
# 只在获取到code时关闭服务器
|
462
|
+
if code
|
463
|
+
Thread.new do
|
464
|
+
sleep 4 # 给用户时间看到成功页面
|
465
|
+
server.shutdown
|
466
|
+
end
|
362
467
|
end
|
363
468
|
rescue => e
|
364
|
-
|
365
|
-
puts e.
|
366
|
-
|
469
|
+
# 静默处理错误,避免干扰用户
|
470
|
+
# puts "处理请求时出错: #{e.class} - #{e.message}"
|
471
|
+
|
367
472
|
# 确保即使出错也返回有效的响应
|
368
473
|
res.content_type = "text/html; charset=UTF-8"
|
369
474
|
res.body = "处理请求时出错"
|
370
|
-
|
371
|
-
# 出错时也关闭服务器
|
372
|
-
Thread.new do
|
373
|
-
sleep 4 # 与正常情况保持一致
|
374
|
-
server.shutdown
|
375
|
-
end
|
376
475
|
end
|
377
476
|
end
|
378
477
|
|
@@ -391,15 +490,179 @@ module Pindo
|
|
391
490
|
server.shutdown
|
392
491
|
end
|
393
492
|
|
394
|
-
|
395
|
-
|
493
|
+
# 返回获取到的code(如果有的话)
|
494
|
+
code
|
495
|
+
end
|
496
|
+
|
497
|
+
# 确保端口可用,处理端口占用问题
|
498
|
+
def ensure_port_available(port)
|
499
|
+
return true unless port_occupied?(port)
|
500
|
+
|
501
|
+
puts "⚠️ 端口 #{port} 被占用,正在检查占用进程..."
|
502
|
+
|
503
|
+
# 获取占用端口的进程信息
|
504
|
+
process_info = get_port_process_info(port)
|
505
|
+
|
506
|
+
if process_info.nil?
|
507
|
+
puts "✗ 无法获取端口占用信息"
|
508
|
+
return false
|
509
|
+
end
|
510
|
+
|
511
|
+
puts "占用进程信息:"
|
512
|
+
puts " PID: #{process_info[:pid]}"
|
513
|
+
puts " 进程名: #{process_info[:name]}"
|
514
|
+
puts " 命令: #{process_info[:command]}"
|
515
|
+
|
516
|
+
# 检查是否是自己的进程(可能是之前未正常关闭的实例)
|
517
|
+
if process_info[:name].include?('ruby') && (process_info[:command].include?('pindo') || process_info[:command].include?('pgyer'))
|
518
|
+
puts "检测到可能是 Pindo/Pgyer 之前的遗留进程"
|
519
|
+
if ask_user_kill_process?
|
520
|
+
return kill_process_by_pid(process_info[:pid])
|
521
|
+
end
|
396
522
|
else
|
397
|
-
puts "
|
523
|
+
puts "端口被其他应用程序占用"
|
524
|
+
if ask_user_kill_process?
|
525
|
+
return kill_process_by_pid(process_info[:pid])
|
526
|
+
end
|
398
527
|
end
|
399
528
|
|
400
|
-
|
529
|
+
return false
|
530
|
+
end
|
531
|
+
|
532
|
+
# 检查端口是否被占用
|
533
|
+
def port_occupied?(port)
|
534
|
+
require 'socket'
|
535
|
+
|
536
|
+
begin
|
537
|
+
server = TCPServer.new('127.0.0.1', port)
|
538
|
+
server.close
|
539
|
+
false # 端口未被占用
|
540
|
+
rescue Errno::EADDRINUSE
|
541
|
+
true # 端口被占用
|
542
|
+
rescue => e
|
543
|
+
puts "⚠️ 检查端口时出错: #{e.message}"
|
544
|
+
true # 出错时假设端口被占用,避免冲突
|
545
|
+
end
|
401
546
|
end
|
402
547
|
|
548
|
+
# 获取占用指定端口的进程信息
|
549
|
+
def get_port_process_info(port)
|
550
|
+
begin
|
551
|
+
# macOS/Linux 使用 lsof 命令
|
552
|
+
if system('which lsof > /dev/null 2>&1')
|
553
|
+
output = `lsof -ti :#{port} 2>/dev/null`.strip
|
554
|
+
return nil if output.empty?
|
555
|
+
|
556
|
+
pid = output.split("\n").first
|
557
|
+
return nil if pid.nil? || pid.empty?
|
558
|
+
|
559
|
+
# 获取进程详细信息
|
560
|
+
ps_output = `ps -p #{pid} -o pid,comm,args 2>/dev/null`.lines
|
561
|
+
return nil if ps_output.length < 2
|
562
|
+
|
563
|
+
process_line = ps_output[1].strip
|
564
|
+
parts = process_line.split(/\s+/, 3)
|
565
|
+
|
566
|
+
return {
|
567
|
+
pid: parts[0],
|
568
|
+
name: parts[1] || 'unknown',
|
569
|
+
command: parts[2] || 'unknown'
|
570
|
+
}
|
571
|
+
end
|
572
|
+
|
573
|
+
# Windows 使用 netstat 命令
|
574
|
+
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
575
|
+
output = `netstat -ano | findstr :#{port}`.strip
|
576
|
+
return nil if output.empty?
|
577
|
+
|
578
|
+
lines = output.split("\n")
|
579
|
+
listening_line = lines.find { |line| line.include?('LISTENING') }
|
580
|
+
return nil unless listening_line
|
581
|
+
|
582
|
+
pid = listening_line.split.last
|
583
|
+
return nil if pid.nil? || pid.empty?
|
584
|
+
|
585
|
+
# 获取进程名称
|
586
|
+
task_output = `tasklist /FI "PID eq #{pid}" /FO CSV /NH 2>nul`.strip
|
587
|
+
if !task_output.empty?
|
588
|
+
process_name = task_output.split(',').first.gsub('"', '')
|
589
|
+
return {
|
590
|
+
pid: pid,
|
591
|
+
name: process_name,
|
592
|
+
command: process_name
|
593
|
+
}
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
return nil
|
598
|
+
rescue => e
|
599
|
+
puts "获取进程信息时出错: #{e.message}"
|
600
|
+
return nil
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
# 询问用户是否终止占用端口的进程
|
605
|
+
def ask_user_kill_process?
|
606
|
+
puts "\n是否终止占用端口的进程?"
|
607
|
+
puts " y/yes - 终止进程并继续 (可能影响其他应用)"
|
608
|
+
puts " n/no - 取消登录 (默认)"
|
609
|
+
print "> "
|
610
|
+
|
611
|
+
begin
|
612
|
+
response = STDIN.gets&.chomp&.downcase
|
613
|
+
return ['y', 'yes'].include?(response)
|
614
|
+
rescue Interrupt
|
615
|
+
puts "\n\n用户中断操作"
|
616
|
+
return false
|
617
|
+
rescue => e
|
618
|
+
puts "无法读取用户输入: #{e.message}"
|
619
|
+
return false
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
# 根据 PID 终止进程
|
624
|
+
def kill_process_by_pid(pid)
|
625
|
+
return false if pid.nil? || pid.empty?
|
626
|
+
|
627
|
+
begin
|
628
|
+
puts "正在终止进程 #{pid}..."
|
629
|
+
|
630
|
+
# 跨平台的进程终止命令
|
631
|
+
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
632
|
+
# Windows
|
633
|
+
success = system("taskkill /PID #{pid} /F >nul 2>&1")
|
634
|
+
else
|
635
|
+
# macOS/Linux - 先尝试优雅终止,再强制终止
|
636
|
+
success = system("kill -TERM #{pid} 2>/dev/null")
|
637
|
+
sleep(2)
|
638
|
+
# 检查进程是否还存在
|
639
|
+
if system("kill -0 #{pid} 2>/dev/null")
|
640
|
+
puts "优雅终止失败,使用强制终止..."
|
641
|
+
success = system("kill -KILL #{pid} 2>/dev/null")
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
if success
|
646
|
+
puts "✓ 进程 #{pid} 已终止"
|
647
|
+
sleep(1) # 等待端口释放
|
648
|
+
|
649
|
+
# 再次检查端口是否可用
|
650
|
+
unless port_occupied?(@server_port)
|
651
|
+
puts "✓ 端口 #{@server_port} 现在可用"
|
652
|
+
return true
|
653
|
+
else
|
654
|
+
puts "⚠️ 端口仍被占用,可能需要等待更长时间"
|
655
|
+
return false
|
656
|
+
end
|
657
|
+
else
|
658
|
+
puts "✗ 终止进程失败,可能需要管理员权限"
|
659
|
+
return false
|
660
|
+
end
|
661
|
+
rescue => e
|
662
|
+
puts "终止进程时出错: #{e.message}"
|
663
|
+
return false
|
664
|
+
end
|
665
|
+
end
|
403
666
|
|
404
667
|
end
|
405
668
|
|
@@ -48,8 +48,8 @@ module Pindo
|
|
48
48
|
if @token["expires_at"] && Time.now.to_i < @token["expires_at"]
|
49
49
|
need_req_login = false
|
50
50
|
else
|
51
|
-
|
52
|
-
|
51
|
+
# 令牌过期,需要重新登录,但不显示错误消息
|
52
|
+
need_req_login = true
|
53
53
|
end
|
54
54
|
else
|
55
55
|
need_req_login = true
|
@@ -58,12 +58,22 @@ module Pindo
|
|
58
58
|
if need_req_login
|
59
59
|
login_handle = PgyerFeishuOAuthCLI.new("cli_a7db8213883ed00d")
|
60
60
|
result = login_handle.authorize
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
61
|
+
|
62
|
+
# 处理用户取消的情况
|
63
|
+
if result == :user_cancelled
|
64
|
+
Funlog.instance.fancyinfo_error("用户取消了登录操作")
|
65
|
+
login_success = false
|
66
|
+
elsif result == true && login_handle.access_token
|
67
|
+
@token = {}
|
68
|
+
@token["token"] = login_handle.access_token
|
69
|
+
@token["username"] = login_handle.username
|
70
|
+
@token["expires_at"] = login_handle.expires_at
|
71
|
+
store_token(token:@token)
|
72
|
+
login_success = true
|
73
|
+
else
|
74
|
+
Funlog.instance.fancyinfo_error("登录失败,未能获取有效token")
|
75
|
+
login_success = false
|
76
|
+
end
|
67
77
|
else
|
68
78
|
login_success = true
|
69
79
|
end
|
@@ -75,7 +85,7 @@ module Pindo
|
|
75
85
|
def load_token()
|
76
86
|
|
77
87
|
@token = nil
|
78
|
-
|
88
|
+
# 静默读取token,不显示过程
|
79
89
|
if File.exist?(@pgyer_token_file)
|
80
90
|
begin
|
81
91
|
data = File.read(@pgyer_token_file)
|
@@ -86,24 +96,23 @@ module Pindo
|
|
86
96
|
if !temp_token.nil? && !temp_token["token"].nil? && !temp_token["username"].nil?
|
87
97
|
# 检查token是否已过期
|
88
98
|
if temp_token["expires_at"] && Time.now.to_i > temp_token["expires_at"]
|
89
|
-
|
99
|
+
# 过期时静默处理,将在登录时提示
|
90
100
|
return nil
|
91
101
|
end
|
92
|
-
|
102
|
+
|
93
103
|
@token = temp_token
|
94
|
-
|
104
|
+
# 成功读取时不显示,保持界面清洁
|
95
105
|
end
|
96
106
|
rescue => error
|
97
|
-
|
98
|
-
# puts "加载pgyer token 失败!!! "
|
107
|
+
# 静默处理错误
|
99
108
|
if File.exist?(@pgyer_token_file)
|
100
109
|
FileUtils.rm_rf(@pgyer_token_file)
|
101
110
|
end
|
102
|
-
|
103
|
-
@token = nil
|
111
|
+
@token = nil
|
104
112
|
end
|
105
113
|
else
|
106
|
-
|
114
|
+
# token文件不存在时静默处理,将在需要时自动登录
|
115
|
+
@token = nil
|
107
116
|
end
|
108
117
|
# puts "token: #{@token}"
|
109
118
|
return @token
|
@@ -118,10 +127,11 @@ module Pindo
|
|
118
127
|
File.open(@pgyer_token_file, "w") do |f|
|
119
128
|
f.write(data_string)
|
120
129
|
end
|
121
|
-
|
122
|
-
#
|
130
|
+
# 静默存储,不显示消息
|
131
|
+
# Funlog.instance.fancyinfo_success("token 存储成功!")
|
123
132
|
rescue => error
|
124
|
-
|
133
|
+
# 存储失败时静默处理,避免干扰用户
|
134
|
+
# Funlog.instance.fancyinfo_error("token存储失败!")
|
125
135
|
end
|
126
136
|
end
|
127
137
|
|