Skip to content
This repository was archived by the owner on Jul 21, 2024. It is now read-only.

Commit a1d714f

Browse files
committed
🔖 APP 4.1.0 Support terminal qrcode login
1 parent ac503d1 commit a1d714f

File tree

7 files changed

+255
-12
lines changed

7 files changed

+255
-12
lines changed

README.md

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,15 @@
4040

4141
- Windows
4242
```powershell
43+
chcp 65001
4344
aliyundrive-webdav-windows-amd64.exe
4445
```
45-
- Linux(X64)
46+
- Linux
4647
```bash
47-
./aliyundrive-webdav-linux-amd64
48+
./aliyundrive-webdav-linux-*
4849
```
49-
- Linux(ARM64)
50-
```bash
51-
./aliyundrive-webdav-linux-arm64
52-
```
53-
- macOS(Intel)
54-
> 最新的native编译工具链目前有问题, 请先用jar版替代
55-
>
56-
> ~~./aliyundrive-webdav-darwin-x86_64~~
50+
- macOS
51+
> ./aliyundrive-webdav-darwin-*
5752
```bash
5853
java -jar ./aliyundrive-webdav.jar
5954
```

aliyundrive-sdk-openapi/src/main/java/net/xdow/aliyundrive/AliyunDriveConstant.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ public class AliyunDriveConstant {
1010
public static final String API_QRCODE_GENERATE = API_HOST + "/oauth/authorize/qrcode";
1111
public static final String API_QRCODE_IMAGE = API_HOST + "/oauth/qrcode/%s";
1212
public static final String API_QRCODE_QUERY_STATUS = API_HOST + "/oauth/qrcode/%s/status";
13+
public static final String API_QRCODE_AUTHORIZE = REFERER + "o/oauth/authorize?sid=%s";
14+
1315
public static final String API_USER_INFO = API_HOST + "/oauth/users/info";
1416
public static final String API_USER_GET_SPACE_INFO = API_HOST + "/adrive/v1.0/user/getSpaceInfo";
1517
public static final String API_USER_GET_DRIVE_INFO = API_HOST + "/adrive/v1.0/user/getDriveInfo";

aliyundrive-webdav-internal/src/main/java/com/github/zxbu/webdavteambition/config/AliyunDriveProperties.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public class AliyunDriveProperties {
3535
private transient String authorizationCode;
3636
private transient String aliyunAccessTokenUrl = "https://adrive.xdow.net/oauth/access_token?code=%s&refresh_token=%s&ver=2.0.0";
3737
private transient String aliyunAuthorizeUrl = "https://adrive.xdow.net/oauth/authorize?redirect_uri=%s";
38+
private transient String aliyunQrGenerateUrl = "https://adrive.xdow.net/oauth/authorize/qrcode";
3839

3940
private transient Auth auth = new Auth();
4041
private transient Driver driver = Driver.OpenApi;

aliyundrive-webdav-internal/src/main/java/com/github/zxbu/webdavteambition/store/AliyunDriveClientService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -805,7 +805,7 @@ public void clearCacheAll() {
805805
tFileListCache.invalidateAll();
806806
}
807807

808-
private boolean login(String refreshToken) {
808+
public boolean login(String refreshToken) {
809809
if (StringUtils.isEmpty(refreshToken)) {
810810
return false;
811811
}

build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ plugins {
66
}
77

88
group 'com.github'
9-
version '4.0.6'
9+
version '4.1.0'
1010
sourceCompatibility = '17'
1111

1212
configurations {
@@ -37,6 +37,8 @@ dependencies {
3737
implementation 'com.squareup.okhttp3:okhttp:3.12.13' //api19
3838
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.13' //api19
3939
implementation 'com.google.guava:guava:20.0' //java7
40+
implementation 'com.github.auties00:qr-terminal:2.1'
41+
implementation 'com.google.zxing:core:3.5.3'
4042
implementation project(':aliyundrive-webdav-internal')
4143
implementation project(':aliyundrive-sdk-openapi')
4244
implementation project(':aliyundrive-sdk-webapi')

src/main/java/com/github/zxbu/webdavteambition/WebdavApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@
8080
AliyunDriveResponse.FileMoveToTrashInfo.class,
8181
AliyunDriveResponse.FileDeleteInfo.class,
8282
AliyunDriveResponse.GenericMessageInfo.class,
83+
AliyunDriveResponse.QrCodeGenerateInfo.class,
84+
AliyunDriveResponse.QrCodeQueryStatusInfo.class,
8385
AliyunDriveUploadedPartInfo.class,
8486
AliyunDriveMediaMetaData.class,
8587
AliyunDriveWebResponse.ShareTokenInfo.class,
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package com.github.zxbu.webdavteambition.component;
2+
3+
import com.github.zxbu.webdavteambition.bean.AFileReqInfo;
4+
import com.github.zxbu.webdavteambition.config.AliyunDriveProperties;
5+
import com.github.zxbu.webdavteambition.store.AliyunDriveClientService;
6+
import com.github.zxbu.webdavteambition.util.AliyunDriveClientServiceHolder;
7+
import com.google.zxing.*;
8+
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
9+
import com.google.zxing.common.BitMatrix;
10+
import com.google.zxing.common.HybridBinarizer;
11+
import it.auties.qr.QrTerminal;
12+
import lombok.extern.slf4j.Slf4j;
13+
import net.xdow.aliyundrive.AliyunDriveConstant;
14+
import net.xdow.aliyundrive.IAliyunDrive;
15+
import net.xdow.aliyundrive.bean.AliyunDriveFileInfo;
16+
import net.xdow.aliyundrive.bean.AliyunDriveResponse;
17+
import net.xdow.aliyundrive.exception.NotAuthenticatedException;
18+
import net.xdow.aliyundrive.impl.AliyunDriveOpenApiImplV1;
19+
import net.xdow.aliyundrive.net.AliyunDriveCall;
20+
import net.xdow.aliyundrive.util.JsonUtils;
21+
import okhttp3.Call;
22+
import okhttp3.OkHttpClient;
23+
import okhttp3.Request;
24+
import okhttp3.Response;
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.boot.web.context.WebServerInitializedEvent;
27+
import org.springframework.context.ApplicationListener;
28+
import org.springframework.scheduling.TaskScheduler;
29+
import org.springframework.stereotype.Component;
30+
31+
import javax.imageio.ImageIO;
32+
import java.awt.image.BufferedImage;
33+
import java.io.ByteArrayInputStream;
34+
import java.io.IOException;
35+
import java.time.Instant;
36+
import java.util.Locale;
37+
38+
@Slf4j
39+
@Component
40+
public class QrLoginOnStartupComponent implements ApplicationListener<WebServerInitializedEvent> {
41+
42+
private final static int QRCODE_CHECK_RETRY_COUNT = 60; //最大3分钟有效期
43+
private final static long QRCODE_CHECK_DELAY_MILLISECONDS = 2500L;
44+
45+
@Autowired
46+
private TaskScheduler mTaskScheduler;
47+
48+
@Autowired
49+
private AliyunDriveClientServiceHolder mAliyunDriveClientServiceHolder;
50+
51+
private boolean mReLoginShowed = false;
52+
53+
private AliyunDriveResponse.QrCodeGenerateInfo mQrCodeGenerateDataInfo = null;
54+
55+
@Override
56+
public void onApplicationEvent(WebServerInitializedEvent event) {
57+
AliyunDriveClientService service = mAliyunDriveClientServiceHolder.getAliyunDriveClientService();
58+
if (service.getAliyunDrive() instanceof AliyunDriveOpenApiImplV1) {
59+
60+
} else {
61+
// Not Supported
62+
return;
63+
}
64+
mTaskScheduler.schedule(() -> {
65+
try {
66+
AliyunDriveFileInfo root = service.getTFileByPath("/");
67+
AFileReqInfo rootInfo = AFileReqInfo.from(root);
68+
service.getTFileListCached(rootInfo);
69+
} catch (Throwable e) {
70+
if (e.getCause() instanceof NotAuthenticatedException) {
71+
if (!mReLoginShowed) {
72+
mReLoginShowed = true;
73+
doReLogin();
74+
}
75+
return;
76+
}
77+
}
78+
}, Instant.now().plusSeconds(6));
79+
}
80+
81+
private void doReLogin() {
82+
IAliyunDrive aliyunDrive = mAliyunDriveClientServiceHolder.getAliyunDriveClientService().getAliyunDrive();
83+
if (aliyunDrive instanceof AliyunDriveOpenApiImplV1 openApiDrive) {
84+
85+
} else {
86+
throw new IllegalStateException("doReLogin not supported " + aliyunDrive);
87+
}
88+
openApiDrive.qrCodeGenerate(mAliyunDriveClientServiceHolder.getAliyunDriveClientService().getProperties().getAliyunQrGenerateUrl()).enqueue(new AliyunDriveCall.Callback<AliyunDriveResponse.QrCodeGenerateInfo>() {
89+
@Override
90+
public void onResponse(Call call, Response response, AliyunDriveResponse.QrCodeGenerateInfo res) {
91+
if (res.isError()) {
92+
log.error("Error on qrCodeGenerate onResponse: {}", JsonUtils.toJson(res));
93+
return;
94+
}
95+
mQrCodeGenerateDataInfo = res;
96+
// processQrCodeUrl(res.getQrCodeUrl());
97+
processQrCodeSid(res.getSid());
98+
}
99+
100+
@Override
101+
public void onFailure(Call call, Throwable t, AliyunDriveResponse.QrCodeGenerateInfo res) {
102+
log.error("Error on qrCodeGenerate onFailure: {}", JsonUtils.toJson(res), t);
103+
log.error("", t);
104+
}
105+
});
106+
107+
}
108+
109+
private void processQrCodeSid(String sid) {
110+
try {
111+
String url = String.format(Locale.getDefault(), AliyunDriveConstant.API_QRCODE_AUTHORIZE, sid);
112+
BitMatrix bitMatrix = new MultiFormatWriter().encode(url, BarcodeFormat.QR_CODE, 33, 33);
113+
QrTerminal.print(bitMatrix, true);
114+
115+
log.info("请使用阿里云盘APP扫码登录, 或进入管理页面登录.");
116+
log.info("Please scan the QR code using the Aliyun Drive app, or log in through the management page.");
117+
query(QRCODE_CHECK_RETRY_COUNT);
118+
} catch (Throwable t) {
119+
log.error("Error on qrCodeGenerate onResponse", t);
120+
}
121+
}
122+
123+
// https://github.com/oracle/graal/issues/4124
124+
private void processQrCodeUrl(String qrCodeUrl) {
125+
126+
OkHttpClient client = new OkHttpClient();
127+
128+
// Create a request object
129+
Request request = new Request.Builder()
130+
.url(qrCodeUrl)
131+
.build();
132+
try {
133+
// Execute the request and get the response
134+
Response response = client.newCall(request).execute();
135+
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
136+
137+
// Read the image data
138+
byte[] imageData = response.body().bytes();
139+
140+
// Convert byte array to BufferedImage
141+
BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(imageData));
142+
143+
// Convert BufferedImage to BitMatrix
144+
BitMatrix bitMatrix = convertToBitMatrix(bufferedImage);
145+
146+
// Print BitMatrix
147+
QrTerminal.print(bitMatrix, true);
148+
149+
log.info("请使用阿里云盘APP扫码登录, 或进入管理页面登录.");
150+
log.info("Please scan the QR code using the Aliyun Drive app, or log in through the management page.");
151+
query(QRCODE_CHECK_RETRY_COUNT);
152+
} catch (Throwable t) {
153+
log.error("Error on qrCodeGenerate onResponse", t);
154+
}
155+
}
156+
157+
private static BitMatrix convertToBitMatrix(BufferedImage bufferedImage) throws NotFoundException, WriterException {
158+
BufferedImageLuminanceSource luminanceSource = new BufferedImageLuminanceSource(bufferedImage);
159+
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(luminanceSource));
160+
String text = new MultiFormatReader().decode(binaryBitmap).getText();
161+
return new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, 33, 33);
162+
}
163+
private void query(int countDown) {
164+
if (countDown <= 0) {
165+
log.info("二维码已过期, 如需二维码登录, 请重启应用程序.");
166+
log.info("The QR code has expired. Please restart the application if you need to log in with a QR code.");
167+
return;
168+
}
169+
if (mQrCodeGenerateDataInfo != null) {
170+
AliyunDriveCall.Callback<AliyunDriveResponse.QrCodeQueryStatusInfo> callback = new AliyunDriveCall.Callback<AliyunDriveResponse.QrCodeQueryStatusInfo>() {
171+
@Override
172+
public void onResponse(Call call, okhttp3.Response response, AliyunDriveResponse.QrCodeQueryStatusInfo res) {
173+
if (res.isError()) {
174+
onErrorCallback(String.format("%s (%s)", res.getMessage(), res.getCode()));
175+
return;
176+
}
177+
switch (res.getStatus()) {
178+
case WaitLogin:
179+
log.debug("请使用阿里云网盘APP扫码.");
180+
log.debug("Please use the Aliyun Drive app to scan the QR code.");
181+
break;
182+
case ScanSuccess:
183+
log.info("扫描成功!请在手机上根据提示确认登录.");
184+
log.info("Scan successful! Please confirm login on your mobile device as prompted.");
185+
break;
186+
case LoginSuccess:
187+
log.info("已确认, 认证中...");
188+
log.info("Confirmed, authenticating...");
189+
if (res.getAuthCode().isEmpty()) {
190+
onErrorCallback("authCode is empty");
191+
return;
192+
}
193+
mQrCodeGenerateDataInfo = null;
194+
AliyunDriveProperties properties = mAliyunDriveClientServiceHolder.getAliyunDriveClientService().getProperties();
195+
String url = String.format(Locale.getDefault(), properties.getAliyunAccessTokenUrl(), res.getAuthCode(), "");
196+
mAliyunDriveClientServiceHolder.getAliyunDriveClientService().getAliyunDrive().getAccessToken(url).enqueue(new AliyunDriveCall.Callback<AliyunDriveResponse.AccessTokenInfo>() {
197+
@Override
198+
public void onResponse(Call call, okhttp3.Response response, AliyunDriveResponse.AccessTokenInfo res) {
199+
if (res.isError()) {
200+
onErrorCallback(String.format("%s (%s)", res.getMessage(), res.getCode()));
201+
return;
202+
}
203+
if (mAliyunDriveClientServiceHolder.getAliyunDriveClientService().login(res.getRefreshToken())) {
204+
log.info("登录成功.");
205+
log.info("Login successful.");
206+
}
207+
}
208+
209+
@Override
210+
public void onFailure(Call call, Throwable t, AliyunDriveResponse.AccessTokenInfo res) {
211+
onErrorCallback(String.format("%s (%s)", res.getMessage(), res.getCode()));
212+
}
213+
});
214+
return;
215+
case QrCodeExpired:
216+
log.info("二维码已经过期.");
217+
log.info("The QR code has expired.");
218+
return;
219+
default:
220+
log.info("未知错误: 可能二维码已经过期.");
221+
log.info("Unknown error: The QR code may have expired.");
222+
break;
223+
}
224+
mTaskScheduler.schedule(() -> query(countDown - 1), Instant.now().plusMillis(QRCODE_CHECK_DELAY_MILLISECONDS));
225+
}
226+
227+
@Override
228+
public void onFailure(Call call, Throwable t, AliyunDriveResponse.QrCodeQueryStatusInfo res) {
229+
onErrorCallback(String.format("%s (%s)", res.getMessage(), res.getCode()));
230+
}
231+
};
232+
mAliyunDriveClientServiceHolder.getAliyunDriveClientService().getAliyunDrive().qrCodeQueryStatus(mQrCodeGenerateDataInfo.getSid()).enqueue(callback);
233+
} else {
234+
mTaskScheduler.schedule(() -> query(countDown - 1), Instant.now().plusMillis(QRCODE_CHECK_DELAY_MILLISECONDS));
235+
}
236+
}
237+
238+
private void onErrorCallback(String response) {
239+
log.error(response);
240+
}
241+
}

0 commit comments

Comments
 (0)