mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-16 12:23:03 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f8259d772 | ||
|
|
e8ba451d18 | ||
|
|
77758db463 | ||
|
|
6c58598a8e | ||
|
|
3ac35230a3 | ||
|
|
ca91302d28 | ||
|
|
e07272a5dc | ||
|
|
461305e1df | ||
|
|
8e8ab10a0f | ||
|
|
e754326925 | ||
|
|
4c92994c6f | ||
|
|
66c57f47ac | ||
|
|
ec689eadd8 | ||
|
|
c1e15709a7 | ||
|
|
4460659210 |
22
README.md
22
README.md
@@ -3,7 +3,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml"><img src="https://img.shields.io/github/actions/workflow/status/qaiu/netdisk-fast-download/maven.yml?branch=v0.1.9b2&style=flat"></a>
|
||||
<a href="https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml"><img src="https://img.shields.io/github/actions/workflow/status/qaiu/netdisk-fast-download/maven.yml?branch=main&style=flat"></a>
|
||||
<a href="https://www.oracle.com/cn/java/technologies/downloads"><img src="https://img.shields.io/badge/jdk-%3E%3D17-blue"></a>
|
||||
<a href="https://vertx-china.github.io"><img src="https://img.shields.io/badge/vert.x-4.5.6-blue?style=flat"></a>
|
||||
<a href="https://raw.githubusercontent.com/qaiu/netdisk-fast-download/master/LICENSE"><img src="https://img.shields.io/github/license/qaiu/netdisk-fast-download?style=flat"></a>
|
||||
@@ -18,8 +18,9 @@ QQ群:1017480890
|
||||
|
||||
netdisk-fast-download网盘直链云解析(nfd云解析)能把网盘分享下载链接转化为直链,支持多款云盘,已支持蓝奏云/蓝奏云优享/奶牛快传/移动云云空间/小飞机盘/亿方云/123云盘/Cloudreve等,支持加密分享,以及部分网盘文件夹分享。
|
||||
|
||||
[预览地址1](https://lz.qaiu.top)
|
||||
[预览地址2](http://www.722shop.top:6401)
|
||||
[预览地址1](https://lz.qaiu.top)
|
||||
[预览地址2](http://www.722shop.top:6401)
|
||||
[天翼云盘大文件解析限时开放](https://189.qaiu.top)
|
||||
|
||||
main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/netdisk-fast-download/tree/main-jdk11)
|
||||
**0.1.8及以上版本json接口格式有调整 参考json返回数据格式示例**
|
||||
@@ -44,7 +45,8 @@ main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/
|
||||
- [118网盘(已停服)-p118](https://www.118pan.com/)
|
||||
- [文叔叔-ws](https://www.wenshushu.cn/)
|
||||
- [联想乐云-le](https://lecloud.lenovo.com/)
|
||||
- [QQ邮箱文件中转站-qq](https://mail.qq.com/)
|
||||
- [QQ邮箱云盘-qqw](https://mail.qq.com/)
|
||||
- [QQ闪传-qqsc](https://nutty.qq.com/nutty/ssr/26797.html)
|
||||
- [城通网盘-ct](https://www.ctfile.com)
|
||||
- [网易云音乐分享链接-mnes](https://music.163.com)
|
||||
- [酷狗音乐分享链接-mkgs](https://www.kugou.com)
|
||||
@@ -268,15 +270,15 @@ mkdir -p netdisk-fast-download
|
||||
cd netdisk-fast-download
|
||||
|
||||
# 拉取镜像
|
||||
docker pull ghcr.io/qaiu/netdisk-fast-download:lastest
|
||||
docker pull ghcr.io/qaiu/netdisk-fast-download:latest
|
||||
|
||||
# 复制配置文件(或下载仓库web-service\src\main\resources)
|
||||
docker create --name netdisk-fast-download ghcr.io/qaiu/netdisk-fast-download:lastest
|
||||
docker create --name netdisk-fast-download ghcr.io/qaiu/netdisk-fast-download:latest
|
||||
docker cp netdisk-fast-download:/app/resources ./resources
|
||||
docker rm netdisk-fast-download
|
||||
|
||||
# 启动容器
|
||||
docker run -d -it --name netdisk-fast-download -p 6401:6401 --restart unless-stopped -e TZ=Asia/Shanghai -v ./resources:/app/resources -v ./db:/app/db -v ./logs:/app/logs ghcr.io/qaiu/netdisk-fast-download:lastest
|
||||
docker run -d -it --name netdisk-fast-download -p 6401:6401 --restart unless-stopped -e TZ=Asia/Shanghai -v ./resources:/app/resources -v ./db:/app/db -v ./logs:/app/logs ghcr.io/qaiu/netdisk-fast-download:latest
|
||||
|
||||
# 反代6401端口
|
||||
|
||||
@@ -291,15 +293,15 @@ mkdir -p netdisk-fast-download
|
||||
cd netdisk-fast-download
|
||||
|
||||
# 拉取镜像
|
||||
docker pull ghcr.nju.edu.cn/qaiu/netdisk-fast-download:lastest
|
||||
docker pull ghcr.nju.edu.cn/qaiu/netdisk-fast-download:latest
|
||||
|
||||
# 复制配置文件(或下载仓库web-service\src\main\resources)
|
||||
docker create --name netdisk-fast-download ghcr.nju.edu.cn/qaiu/netdisk-fast-download:lastest
|
||||
docker create --name netdisk-fast-download ghcr.nju.edu.cn/qaiu/netdisk-fast-download:latest
|
||||
docker cp netdisk-fast-download:/app/resources ./resources
|
||||
docker rm netdisk-fast-download
|
||||
|
||||
# 启动容器
|
||||
docker run -d -it --name netdisk-fast-download -p 6401:6401 --restart unless-stopped -e TZ=Asia/Shanghai -v ./resources:/app/resources -v ./db:/app/db -v ./logs:/app/logs ghcr.nju.edu.cn/qaiu/netdisk-fast-download:lastest
|
||||
docker run -d -it --name netdisk-fast-download -p 6401:6401 --restart unless-stopped -e TZ=Asia/Shanghai -v ./resources:/app/resources -v ./db:/app/db -v ./logs:/app/logs ghcr.nju.edu.cn/qaiu/netdisk-fast-download:latest
|
||||
|
||||
# 反代6401端口
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.12.0</version>
|
||||
<version>3.18.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
|
||||
@@ -12,7 +12,8 @@ import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
|
||||
public class ResponseUtil {
|
||||
|
||||
public static void redirect(HttpServerResponse response, String url) {
|
||||
response.putHeader(HttpHeaders.LOCATION, url).setStatusCode(302).end();
|
||||
response.putHeader(CONTENT_TYPE, "text/html; charset=utf-8")
|
||||
.putHeader(HttpHeaders.LOCATION, url).setStatusCode(302).end();
|
||||
}
|
||||
|
||||
public static void redirect(HttpServerResponse response, String url, Promise<?> promise) {
|
||||
|
||||
@@ -202,17 +202,18 @@ public abstract class PanBase implements IPanTool {
|
||||
try {
|
||||
log.error(decompressGzip((Buffer) res.body()));
|
||||
fail(decompressGzip((Buffer) res.body()));
|
||||
throw new RuntimeException("响应不是JSON格式");
|
||||
//throw new RuntimeException("响应不是JSON格式");
|
||||
} catch (IOException ex) {
|
||||
log.error("响应gzip解压失败");
|
||||
fail("响应gzip解压失败: {}", ex.getMessage());
|
||||
throw new RuntimeException("响应gzip解压失败", ex);
|
||||
//throw new RuntimeException("响应gzip解压失败", ex);
|
||||
}
|
||||
} else {
|
||||
log.error("解析失败: json格式异常: {}", res.bodyAsString());
|
||||
fail("解析失败: json格式异常: {}", res.bodyAsString());
|
||||
throw new RuntimeException("解析失败: json格式异常");
|
||||
//throw new RuntimeException("解析失败: json格式异常");
|
||||
}
|
||||
return JsonObject.of();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,8 +234,9 @@ public abstract class PanBase implements IPanTool {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
fail("解析失败: res格式异常");
|
||||
throw new RuntimeException("解析失败: res格式异常");
|
||||
//throw new RuntimeException("解析失败: res格式异常");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void complete(String url) {
|
||||
|
||||
@@ -23,9 +23,88 @@ import static java.util.regex.Pattern.compile;
|
||||
public enum PanDomainTemplate {
|
||||
|
||||
|
||||
// https://www.ilanzou.com/s/
|
||||
IZ("蓝奏云优享",
|
||||
compile("https://www\\.ilanzou\\.com/s/(?<KEY>.+)"),
|
||||
"https://www.ilanzou.com/s/{shareKey}",
|
||||
IzTool.class),
|
||||
// 网盘定义
|
||||
/*
|
||||
lanzoul.com
|
||||
lanzouh.com
|
||||
lanosso.com
|
||||
lanpv.com
|
||||
bakstotre.com
|
||||
lanzouo.com
|
||||
lanzov.com
|
||||
lanpw.com
|
||||
ulanzou.com
|
||||
lanzouf.com
|
||||
lanzn.com
|
||||
lanzouj.com
|
||||
lanzouk.com
|
||||
lanzouq.com
|
||||
lanzouv.com
|
||||
lanzoue.com
|
||||
lanzouw.com
|
||||
lanzoub.com
|
||||
lanzouu.com
|
||||
lanwp.com
|
||||
lanzouy.com
|
||||
lanzoup.com
|
||||
woozooo.com
|
||||
lanzv.com
|
||||
dmpdmp.com
|
||||
lanrar.com
|
||||
webgetstore.com
|
||||
lanzb.com
|
||||
lanzoux.com
|
||||
lanzout.com
|
||||
lanzouc.com
|
||||
ilanzou.com
|
||||
lanzoui.com
|
||||
lanzoug.com
|
||||
lanzoum.com
|
||||
t-is.cn
|
||||
*/
|
||||
LZ("蓝奏云",
|
||||
compile("https://(?:[a-zA-Z\\d-]+\\.)?((lanzou[a-z])|(lanzn))\\.com/(.+/)?(?<KEY>.+)"),
|
||||
compile("https://(?:[a-zA-Z\\d-]+\\.)?(" +
|
||||
"lanzoul|" +
|
||||
"lanzouh|" +
|
||||
"lanosso|" +
|
||||
"lanpv|" +
|
||||
"bakstotre|" +
|
||||
"lanzouo|" +
|
||||
"lanzov|" +
|
||||
"lanpw|" +
|
||||
"ulanzou|" +
|
||||
"lanzouf|" +
|
||||
"lanzn|" +
|
||||
"lanzouj|" +
|
||||
"lanzouk|" +
|
||||
"lanzouq|" +
|
||||
"lanzouv|" +
|
||||
"lanzoue|" +
|
||||
"lanzouw|" +
|
||||
"lanzoub|" +
|
||||
"lanzouu|" +
|
||||
"lanwp|" +
|
||||
"lanzouy|" +
|
||||
"lanzoup|" +
|
||||
"woozooo|" +
|
||||
"lanzv|" +
|
||||
"dmpdmp|" +
|
||||
"lanrar|" +
|
||||
"webgetstore|" +
|
||||
"lanzb|" +
|
||||
"lanzoux|" +
|
||||
"lanzout|" +
|
||||
"lanzouc|" +
|
||||
// "ilanzou|" +
|
||||
"lanzoui|" +
|
||||
"lanzoug|" +
|
||||
"lanzoum" +
|
||||
")\\.com/(.+/)?(?<KEY>.+)"),
|
||||
"https://lanzoux.com/{shareKey}",
|
||||
LzTool.class),
|
||||
|
||||
@@ -48,11 +127,6 @@ public enum PanDomainTemplate {
|
||||
"https://v2.fangcloud.com/s/{shareKey}",
|
||||
"https://www.fangcloud.com/",
|
||||
FcTool.class),
|
||||
// https://www.ilanzou.com/s/
|
||||
IZ("蓝奏云优享",
|
||||
compile("https://www\\.ilanzou\\.com/s/(?<KEY>.+)"),
|
||||
"https://www.ilanzou.com/s/{shareKey}",
|
||||
IzTool.class),
|
||||
// https://wx.mail.qq.com/ftn/download?
|
||||
QQ("QQ邮箱中转站",
|
||||
compile("https://i?wx\\.mail\\.qq\\.com/ftn/download\\?(?<KEY>.+)"),
|
||||
@@ -65,14 +139,68 @@ public enum PanDomainTemplate {
|
||||
"https://wx.mail.qq.com/s?k={shareKey}",
|
||||
"https://mail.qq.com",
|
||||
QQwTool.class),
|
||||
// https://qfile.qq.com/q/xxx
|
||||
QQSC("QQ闪传",
|
||||
compile("https://qfile\\.qq\\.com/q/(?<KEY>.+)"),
|
||||
"https://qfile.qq.com/q/{shareKey}",
|
||||
QQscTool.class),
|
||||
// https://f.ws59.cn/f/或者https://www.wenshushu.cn/f/
|
||||
WS("文叔叔",
|
||||
compile("https://(f\\.ws(\\d{2})\\.cn|www\\.wenshushu\\.cn)/f/(?<KEY>.+)"),
|
||||
"https://www.wenshushu.cn/f/{shareKey}",
|
||||
WsTool.class),
|
||||
// https://www.123pan.com/s/
|
||||
/*
|
||||
123254.com
|
||||
123957.com
|
||||
123295.com
|
||||
123panpay.com
|
||||
123860.com
|
||||
123pan.com
|
||||
123245.com
|
||||
123278.com
|
||||
123842.com
|
||||
123294.com
|
||||
123865.com
|
||||
123773.com
|
||||
123624.com
|
||||
123684.com
|
||||
123641.com
|
||||
123259.com
|
||||
123912.com
|
||||
123952.com
|
||||
123652.com
|
||||
123pan.cn
|
||||
123635.com
|
||||
123242.com
|
||||
123795.com
|
||||
*/
|
||||
YE("123网盘",
|
||||
compile("https://www\\.(123pan\\.com|123865\\.com|123684\\.com|123912\\.com|123pan\\.cn)/s/(?<KEY>.+)(.html)?"),
|
||||
compile("https://www\\.(" +
|
||||
"123254\\.com|" +
|
||||
"123957\\.com|" +
|
||||
"123295\\.com|" +
|
||||
"123panpay\\.com|" +
|
||||
"123860\\.com|" +
|
||||
"123pan\\.com|" +
|
||||
"123245\\.com|" +
|
||||
"123278\\.com|" +
|
||||
"123842\\.com|" +
|
||||
"123294\\.com|" +
|
||||
"123865\\.com|" +
|
||||
"123773\\.com|" +
|
||||
"123624\\.com|" +
|
||||
"123684\\.com|" +
|
||||
"123641\\.com|" +
|
||||
"123259\\.com|" +
|
||||
"123912\\.com|" +
|
||||
"123952\\.com|" +
|
||||
"123652\\.com|" +
|
||||
"123pan\\.cn|" +
|
||||
"123635\\.com|" +
|
||||
"123242\\.com|" +
|
||||
"123795\\.com" +
|
||||
")/s/(?<KEY>.+)(.html)?"),
|
||||
"https://www.123pan.com/s/{shareKey}",
|
||||
YeTool.class),
|
||||
// https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={code}&isShare=1
|
||||
@@ -143,7 +271,7 @@ public enum PanDomainTemplate {
|
||||
// http://163cn.tv/xxx
|
||||
MNES("网易云音乐分享",
|
||||
compile("http(s)?://163cn\\.tv/(?<KEY>.+)"),
|
||||
"http://163cn.tv/{shareKey}",
|
||||
"https://163cn.tv/{shareKey}",
|
||||
MnesTool.class),
|
||||
// https://music.163.com/#/song?id=xxx
|
||||
MNE("网易云音乐歌曲详情",
|
||||
|
||||
@@ -31,18 +31,20 @@ public class P118Tool extends PanBase {
|
||||
Pattern compile = Pattern.compile("href=\"([^\"]+)\"");
|
||||
Matcher matcher = compile.matcher(res.bodyAsString());
|
||||
if (matcher.find()) {
|
||||
System.out.println(matcher.group(1));
|
||||
complete(matcher.group(1));
|
||||
//c: 0x63
|
||||
//o: 0x6F
|
||||
//m: 0x6D
|
||||
//1: 0x31
|
||||
///: 0x2F
|
||||
char[] chars1 = new char[]{99, 111, 109, 49, 47};
|
||||
char[] chars2 = new char[]{99, 111, 109, 47};
|
||||
String group = matcher.group(1).replace(String.valueOf(chars1), String.valueOf(chars2));
|
||||
System.out.println(group);
|
||||
complete(group);
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}).onFailure(handleFail(""));
|
||||
return future();
|
||||
}
|
||||
|
||||
// public static void main(String[] args) {
|
||||
// String s = new P118Tool(ShareLinkInfo.newBuilder().shareUrl("https://xiguage.118pan.com/b11848261").shareKey(
|
||||
// "11848261").build()).parseSync();
|
||||
// System.out.println(s);
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.WebClientVertxInit;
|
||||
import cn.qaiu.entity.FileInfo;
|
||||
import cn.qaiu.entity.ShareLinkInfo;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import cn.qaiu.util.HeaderUtils;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
import io.vertx.uritemplate.UriTemplate;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 微雨云
|
||||
*/
|
||||
@@ -14,6 +20,10 @@ public class PvyyTool extends PanBase {
|
||||
private static final String API_URL_PREFIX1 = "https://www.vyuyun.com/apiv1/share/file/{key}?password={pwd}";
|
||||
private static final String API_URL_PREFIX2 = "https://www.vyuyun.com/apiv1/share/getShareDownUrl/{key}/{id}?password={pwd}";
|
||||
|
||||
byte[] hexArray = {
|
||||
0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x31, 0x36, 0x2e, 0x32, 0x30, 0x35, 0x2e,
|
||||
0x39, 0x36, 0x2e, 0x31, 0x39, 0x38, 0x3a, 0x33, 0x30, 0x30, 0x30, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x2f
|
||||
};
|
||||
|
||||
private static final MultiMap header = HeaderUtils.parseHeaders("""
|
||||
accept-language: zh-CN,zh;q=0.9,en;q=0.8
|
||||
@@ -32,12 +42,33 @@ public class PvyyTool extends PanBase {
|
||||
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
|
||||
""");
|
||||
|
||||
private String api;
|
||||
public PvyyTool(ShareLinkInfo shareLinkInfo) {
|
||||
super(shareLinkInfo);
|
||||
api = new String(hexArray);
|
||||
System.out.println(api);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<String> parse() {
|
||||
//
|
||||
// 请求downcode
|
||||
WebClient.create(WebClientVertxInit.get())
|
||||
.getAbs(api + shareLinkInfo.getShareKey())
|
||||
.send()
|
||||
.onSuccess(res -> {
|
||||
if (res.statusCode() == 200) {
|
||||
String code = res.bodyAsString();
|
||||
log.info("vyy url:{}, code:{}", shareLinkInfo.getStandardUrl(), code);
|
||||
String downApi = API_URL_PREFIX2 + "&downcode=" + code;
|
||||
getDownUrl(downApi);
|
||||
} else {
|
||||
fail("code获取失败");
|
||||
}
|
||||
}).onFailure(handleFail("code服务异常"));
|
||||
return future();
|
||||
}
|
||||
|
||||
private void getDownUrl(String apiUrl) {
|
||||
client.getAbs(UriTemplate.of(API_URL_PREFIX1))
|
||||
.setTemplateParam("key", shareLinkInfo.getShareKey())
|
||||
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
|
||||
@@ -46,7 +77,7 @@ public class PvyyTool extends PanBase {
|
||||
try {
|
||||
String id = asJson(res).getJsonObject("data").getJsonObject("data").getString("id");
|
||||
|
||||
client.getAbs(UriTemplate.of(API_URL_PREFIX2))
|
||||
client.getAbs(UriTemplate.of(apiUrl))
|
||||
.setTemplateParam("key", shareLinkInfo.getShareKey())
|
||||
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
|
||||
.setTemplateParam("id", id)
|
||||
@@ -61,10 +92,100 @@ public class PvyyTool extends PanBase {
|
||||
}
|
||||
});
|
||||
} catch (Exception ignored) {
|
||||
fail(asJson(res).encodePrettily());
|
||||
fail();
|
||||
}
|
||||
});
|
||||
|
||||
return future();
|
||||
}
|
||||
|
||||
private static final String DIR_API = "https://www.vyuyun.com/apiv1/share/folders/809Pt6/bMjnUg?sort=created_at&direction=DESC&password={pwd}";
|
||||
private static final String SHARE_TYPE_API = "https://www.vyuyun.com/apiv1/share/info/{key}?password={pwd}";
|
||||
//
|
||||
// @Override
|
||||
// public Future<List<FileInfo>> parseFileList() {
|
||||
// Promise<List<FileInfo>> promise = Promise.promise();
|
||||
// client.getAbs(UriTemplate.of(SHARE_TYPE_API))
|
||||
// .setTemplateParam("key", shareLinkInfo.getShareKey())
|
||||
// .setTemplateParam("pwd", shareLinkInfo.getSharePassword()).send().onSuccess(res -> {
|
||||
// // "data" -> "attributes"->type
|
||||
// String type = asJson(res).getJsonObject("data").getJsonObject("attributes").getString("type");
|
||||
// if ("folder".equals(type)) {
|
||||
// // 文件夹
|
||||
// client.getAbs(UriTemplate.of(DIR_API))
|
||||
// .setTemplateParam("key", shareLinkInfo.getShareKey())
|
||||
// .setTemplateParam("pwd", shareLinkInfo.getSharePassword())
|
||||
// .send().onSuccess(res2 -> { try {
|
||||
//
|
||||
// try {
|
||||
// // 新的解析逻辑
|
||||
// var arr = asJson(res2).getJsonObject("data").getJsonArray("data");
|
||||
// List<FileInfo> list = arr.stream().map(o -> {
|
||||
// FileInfo fileInfo = new FileInfo();
|
||||
// var jo = ((io.vertx.core.json.JsonObject) o).getJsonObject("data");
|
||||
// String fileType = jo.getString("type");
|
||||
// fileInfo.setFileId(jo.getString("id"));
|
||||
// fileInfo.setFileName(jo.getJsonObject("attributes").getString("name"));
|
||||
// // 文件大小可能为null或字符串
|
||||
// Object sizeObj = jo.getJsonObject("attributes").getValue("filesize");
|
||||
// if (sizeObj instanceof Number) {
|
||||
// fileInfo.setSize(((Number) sizeObj).longValue());
|
||||
// } else if (sizeObj instanceof String sizeStr) {
|
||||
// try {
|
||||
// getSize(fileInfo, sizeStr);
|
||||
// } catch (Exception e) {
|
||||
// fileInfo.setSize(0L);
|
||||
// }
|
||||
// } else {
|
||||
// fileInfo.setSize(0L);
|
||||
// }
|
||||
// fileInfo.setFileType("folder".equals(fileType) ? "folder" : "file");
|
||||
// return fileInfo;
|
||||
// }).toList();
|
||||
// promise.complete(list);
|
||||
// } catch (Exception ignored) {
|
||||
// promise.fail(asJson(res2).encodePrettily());
|
||||
// }
|
||||
// }).onFailure(t->{
|
||||
// promise.fail("获取文件夹内容失败: " + t.getMessage());
|
||||
// });
|
||||
// } else if ("file".equals(type)) {
|
||||
// // 单文件
|
||||
// FileInfo fileInfo = new FileInfo();
|
||||
// var jo = asJson(res).getJsonObject("data").getJsonObject("attributes");
|
||||
// fileInfo.setFileId(asJson(res).getJsonObject("data").getString("id"));
|
||||
// fileInfo.setFileName(jo.getString("name"));
|
||||
// Object sizeObj = jo.getValue("filesize");
|
||||
// if (sizeObj instanceof Number) {
|
||||
// fileInfo.setSize(((Number) sizeObj).longValue());
|
||||
// } else if (sizeObj instanceof String sizeStr) {
|
||||
// try {
|
||||
// getSize(fileInfo, sizeStr);
|
||||
// } catch (Exception e) {
|
||||
// fileInfo.setSize(0L);
|
||||
// }
|
||||
// } else {
|
||||
// fileInfo.setSize(0L);
|
||||
// }
|
||||
// fileInfo.setFileType("file");
|
||||
// promise.complete(List.of(fileInfo));
|
||||
// } else {
|
||||
// promise.fail("未知的分享类型");
|
||||
// }
|
||||
// });
|
||||
// return promise.future();
|
||||
// }
|
||||
//
|
||||
// private void getSize(FileInfo fileInfo, String sizeStr) {
|
||||
// if (sizeStr.endsWith("KB")) {
|
||||
// fileInfo.setSize(Long.parseLong(sizeStr.replace("KB", "").trim()) * 1024);
|
||||
// } else if (sizeStr.endsWith("MB")) {
|
||||
// fileInfo.setSize(Long.parseLong(sizeStr.replace("MB", "").trim()) * 1024 * 1024);
|
||||
// } else {
|
||||
// fileInfo.setSize(Long.parseLong(sizeStr));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Future<String> parseById() {
|
||||
// return super.parseById();
|
||||
// }
|
||||
}
|
||||
|
||||
171
parser/src/main/java/cn/qaiu/parser/impl/QQscTool.java
Normal file
171
parser/src/main/java/cn/qaiu/parser/impl/QQscTool.java
Normal file
@@ -0,0 +1,171 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.entity.FileInfo;
|
||||
import cn.qaiu.entity.ShareLinkInfo;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import cn.qaiu.util.HeaderUtils;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* QQ闪传 <br>
|
||||
* 只能客户端上传 支持Android QQ 9.2.5, MACOS QQ 6.9.78,可生成分享链接,通过浏览器下载,支持超大文件,有效期默认7天(暂时没找到续期方法)。<br>
|
||||
*/
|
||||
public class QQscTool extends PanBase {
|
||||
|
||||
Logger LOG = LoggerFactory.getLogger(QQscTool.class);
|
||||
|
||||
private static final String API_URL = "https://qfile.qq.com/http2rpc/gotrpc/noauth/trpc.qqntv2.richmedia.InnerProxy/BatchDownload";
|
||||
|
||||
private static final MultiMap HEADERS = HeaderUtils.parseHeaders("""
|
||||
Accept-Encoding: gzip, deflate
|
||||
Accept-Language: zh-CN,zh;q=0.9
|
||||
Connection: keep-alive
|
||||
Cookie: uin=9000002; p_uin=9000002
|
||||
DNT: 1
|
||||
Origin: https://qfile.qq.com
|
||||
Referer: https://qfile.qq.com/q/Xolxtv5b4O
|
||||
Sec-Fetch-Dest: empty
|
||||
Sec-Fetch-Mode: cors
|
||||
Sec-Fetch-Site: same-origin
|
||||
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0
|
||||
accept: application/json
|
||||
content-type: application/json
|
||||
sec-ch-ua: "Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"
|
||||
sec-ch-ua-mobile: ?0
|
||||
sec-ch-ua-platform: "macOS"
|
||||
x-oidb: {"uint32_command":"0x9248", "uint32_service_type":"4"}
|
||||
""");
|
||||
|
||||
public QQscTool(ShareLinkInfo shareLinkInfo) {
|
||||
super(shareLinkInfo);
|
||||
}
|
||||
|
||||
public Future<String> parse() {
|
||||
String jsonTemplate = """
|
||||
{"req_head":{"agent":8},"download_info":[{"batch_id":"%s","scene":{"business_type":4,"app_type":22,"scene_type":5},"index_node":{"file_uuid":"%s"},"url_type":2,"download_scene":0}],"scene_type":103}
|
||||
""";
|
||||
|
||||
client.getAbs(shareLinkInfo.getShareUrl()).send(result -> {
|
||||
if (result.succeeded()) {
|
||||
String htmlJs = result.result().bodyAsString();
|
||||
LOG.debug("获取到的HTML内容: {}", htmlJs);
|
||||
String fileUUID = getFileUUID(htmlJs);
|
||||
String fileName = extractFileNameFromTitle(htmlJs);
|
||||
if (fileName != null) {
|
||||
LOG.info("提取到的文件名: {}", fileName);
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
fileInfo.setFileName(fileName);
|
||||
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
|
||||
} else {
|
||||
LOG.warn("未能提取到文件名");
|
||||
}
|
||||
if (fileUUID != null) {
|
||||
LOG.info("提取到的文件UUID: {}", fileUUID);
|
||||
String formatted = jsonTemplate.formatted(fileUUID, fileUUID);
|
||||
JsonObject entries = new JsonObject(formatted);
|
||||
client.postAbs(API_URL)
|
||||
.putHeaders(HEADERS)
|
||||
.sendJsonObject(entries)
|
||||
.onSuccess(result2 -> {
|
||||
if (result2.statusCode() == 200) {
|
||||
JsonObject body = asJson(result2);
|
||||
LOG.debug("API响应内容: {}", body.encodePrettily());
|
||||
// {
|
||||
// "retcode": 0,
|
||||
// "cost": 132,
|
||||
// "message": "",
|
||||
// "error": {
|
||||
// "message": "",
|
||||
// "code": 0
|
||||
// },
|
||||
// "data": {
|
||||
// "download_rsp": [{
|
||||
|
||||
// 取 download_rsp
|
||||
if (!body.containsKey("retcode") || body.getInteger("retcode") != 0) {
|
||||
promise.fail("API请求失败,错误信息: " + body.encodePrettily());
|
||||
return;
|
||||
}
|
||||
JsonArray downloadRsp = body.getJsonObject("data").getJsonArray("download_rsp");
|
||||
if (downloadRsp != null && !downloadRsp.isEmpty()) {
|
||||
String url = downloadRsp.getJsonObject(0).getString("url");
|
||||
if (fileName != null) {
|
||||
url = url + "&filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8);
|
||||
}
|
||||
promise.complete(url);
|
||||
} else {
|
||||
promise.fail("API响应中缺少 download_rsp");
|
||||
}
|
||||
} else {
|
||||
promise.fail("API请求失败,状态码: " + result2.statusCode());
|
||||
}
|
||||
}).onFailure(e -> {
|
||||
LOG.error("API请求异常", e);
|
||||
promise.fail(e);
|
||||
});
|
||||
} else {
|
||||
LOG.error("未能提取到文件UUID");
|
||||
promise.fail("未能提取到文件UUID");
|
||||
}
|
||||
} else {
|
||||
LOG.error("请求失败: {}", result.cause().getMessage());
|
||||
promise.fail(result.cause());
|
||||
}
|
||||
});
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
String getFileUUID(String htmlJs) {
|
||||
String keyword = "\"download_limit_status\"";
|
||||
String marker = "},\"";
|
||||
|
||||
int startIndex = htmlJs.indexOf(keyword);
|
||||
if (startIndex != -1) {
|
||||
int markerIndex = htmlJs.indexOf(marker, startIndex);
|
||||
if (markerIndex != -1) {
|
||||
int quoteStart = markerIndex + marker.length();
|
||||
int quoteEnd = htmlJs.indexOf("\"", quoteStart);
|
||||
if (quoteEnd != -1) {
|
||||
String extracted = htmlJs.substring(quoteStart, quoteEnd);
|
||||
LOG.debug("提取结果: {}", extracted);
|
||||
return extracted;
|
||||
} else {
|
||||
LOG.error("未找到结束引号: {}", marker);
|
||||
}
|
||||
} else {
|
||||
LOG.error("未找到标记: {} 在关键字: {} 之后", marker, keyword);
|
||||
}
|
||||
} else {
|
||||
LOG.error("未找到关键字: {}", keyword);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String extractFileNameFromTitle(String content) {
|
||||
// 匹配<title>和</title>之间的内容
|
||||
Pattern pattern = Pattern.compile("<title>(.*?)</title>");
|
||||
Matcher matcher = pattern.matcher(content);
|
||||
if (matcher.find()) {
|
||||
String fullTitle = matcher.group(1);
|
||||
// 按 "|" 分割,取前半部分
|
||||
int sepIndex = fullTitle.indexOf("|");
|
||||
if (sepIndex != -1) {
|
||||
return fullTitle.substring(0, sepIndex);
|
||||
}
|
||||
return fullTitle; // 如果没有分隔符,就返回全部
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -29,7 +29,7 @@
|
||||
<org.reflections.version>0.10.2</org.reflections.version>
|
||||
<lombok.version>1.18.38</lombok.version>
|
||||
<slf4j.version>2.0.5</slf4j.version>
|
||||
<commons-lang3.version>3.12.0</commons-lang3.version>
|
||||
<commons-lang3.version>3.18.0</commons-lang3.version>
|
||||
<commons-beanutils2.version>2.0.0</commons-beanutils2.version>
|
||||
<jackson.version>2.14.2</jackson.version>
|
||||
<logback.version>1.5.8</logback.version>
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
// 修改自 https://github.com/syhyz1990/panAI
|
||||
|
||||
const util = {
|
||||
|
||||
isMobile: (() => !!navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone|HarmonyOS|MicroMessenger)/i))(),
|
||||
|
||||
}
|
||||
|
||||
let opt = {
|
||||
// 'baidu': {
|
||||
@@ -293,6 +288,12 @@
|
||||
host: /mail\.qq\.com/,
|
||||
name: 'QQ邮箱中转站'
|
||||
},
|
||||
QQsc: {
|
||||
// qfile.qq.com
|
||||
reg: /https:\/\/qfile\.qq\.com\/q\/.+/,
|
||||
host: /qfile\.qq\.com/,
|
||||
name: 'QQ闪传'
|
||||
},
|
||||
pan118: {
|
||||
reg: /https:\/\/(?:[a-zA-Z\d-]+\.)?118pan\.com\/b.+/,
|
||||
host: /118pan\.com/,
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
</el-dialog> -->
|
||||
<!-- 顶部反馈栏(小号、灰色、无红边框) -->
|
||||
<div class="feedback-bar">
|
||||
<a href="https://github.com/qaiu/lz.qaiu.top/issues" target="_blank" rel="noopener" class="feedback-link mini">
|
||||
<a href="https://github.com/qaiu/netdisk-fast-download/issues" target="_blank" rel="noopener" class="feedback-link mini">
|
||||
<i class="fas fa-bug feedback-icon"></i>
|
||||
反馈
|
||||
</a>
|
||||
<a href="https://github.com/qaiu/lz.qaiu.top" target="_blank" rel="noopener" class="feedback-link mini">
|
||||
<a href="https://github.com/qaiu/netdisk-fast-download" target="_blank" rel="noopener" class="feedback-link mini">
|
||||
<i class="fab fa-github feedback-icon"></i>
|
||||
GitHub
|
||||
源码
|
||||
</a>
|
||||
<a href="https://blog.qaiu.top" target="_blank" rel="noopener" class="feedback-link mini">
|
||||
<i class="fas fa-blog feedback-icon"></i>
|
||||
@@ -48,9 +48,9 @@
|
||||
</div>
|
||||
<!-- 项目简介移到卡片内 -->
|
||||
<div class="project-intro">
|
||||
<div class="intro-title">NFD网盘直链解析0.1.9_bate7</div>
|
||||
<div class="intro-title">NFD网盘直链解析0.1.9_bate8</div>
|
||||
<div class="intro-desc">
|
||||
<div>支持网盘:蓝奏云、蓝奏云优享、小飞机盘、123云盘、奶牛快传、移动云空间、亿方云、文叔叔、QQ邮箱文件中转站等</div>
|
||||
<div>支持网盘:蓝奏云、蓝奏云优享、小飞机盘、123云盘、奶牛快传、移动云空间、QQ邮箱云盘、QQ闪传等 <el-link style="color:#606cf5" href="https://github.com/qaiu/netdisk-fast-download?tab=readme-ov-file#%E7%BD%91%E7%9B%98%E6%94%AF%E6%8C%81%E6%83%85%E5%86%B5" target="_blank"> >> </el-link></div>
|
||||
<div>文件夹解析支持:蓝奏云、蓝奏云优享、小飞机盘、123云盘</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -102,7 +102,7 @@
|
||||
<div v-if="downloadUrl" class="file-meta-info-card">
|
||||
<div class="file-meta-row">
|
||||
<span class="file-meta-label">下载链接:</span>
|
||||
<a :href="downloadUrl" target="_blank" class="file-meta-link">{{ downloadUrl }}</a>
|
||||
<a :href="downloadUrl" target="_blank" class="file-meta-link" rel="noreferrer noopener">点击下载</a>
|
||||
</div>
|
||||
<div class="file-meta-row" v-if="parseResult.data?.downloadShortUrl">
|
||||
<span class="file-meta-label">下载短链:</span>
|
||||
@@ -170,6 +170,30 @@
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 错误时显示小按钮 -->
|
||||
<div v-if="errorButtonVisible" style="text-align: center; margin-top: 10px;">
|
||||
<el-button type="text" @click="errorDialogVisible = true"> 反馈错误详情>> </el-button>
|
||||
</div>
|
||||
|
||||
<!-- 错误 JSON 弹窗 -->
|
||||
<el-dialog
|
||||
v-model="errorDialogVisible"
|
||||
width="60%">
|
||||
<template #title>
|
||||
错误详情
|
||||
<el-link
|
||||
@click.prevent="copyErrorDetails"
|
||||
target="_blank"
|
||||
style="margin-left:8px"
|
||||
type="primary">
|
||||
复制当前错误信息,提交Issue
|
||||
</el-link>
|
||||
</template>
|
||||
<json-viewer :value="errorDetail" :expand-depth="5" copyable boxed sort />
|
||||
<template #footer>
|
||||
<el-button @click="errorDialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 目录树组件 -->
|
||||
<div v-if="showDirectoryTree" class="directory-tree-container">
|
||||
<div style="margin-bottom: 10px; text-align: right;">
|
||||
@@ -203,7 +227,9 @@
|
||||
<!-- </template>-->
|
||||
<!-- </el-input>-->
|
||||
<!-- </div>-->
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -257,6 +283,10 @@ export default {
|
||||
showRiskDialog: false,
|
||||
baseUrl: location.origin,
|
||||
showListLink: '',
|
||||
|
||||
errorDialogVisible: false,
|
||||
errorDetail: null,
|
||||
errorButtonVisible: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -288,6 +318,7 @@ export default {
|
||||
|
||||
// 统一API调用
|
||||
async callAPI(endpoint, params = {}) {
|
||||
this.errorButtonVisible = false
|
||||
try {
|
||||
this.isLoading = true
|
||||
const response = await axios.get(`${this.baseAPI}${endpoint}`, { params })
|
||||
@@ -296,6 +327,9 @@ export default {
|
||||
// this.$message.success(response.data.msg || '操作成功')
|
||||
return response.data
|
||||
} else {
|
||||
// 在页面右下角显示一个“查看详情”按钮 可以查看原json
|
||||
this.errorDetail = response?.data
|
||||
this.errorButtonVisible = true
|
||||
throw new Error(response.data.msg || '操作失败')
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -508,6 +542,18 @@ export default {
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
ElMessage.success('目录分享链接已复制!')
|
||||
})
|
||||
},
|
||||
|
||||
copyErrorDetails() {
|
||||
const text = `分享链接:${this.link}
|
||||
分享密码:${this.password || ''}
|
||||
错误信息:${JSON.stringify(this.errorDetail, null, 2)}`;
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
this.$message.success('已复制分享信息和错误详情');
|
||||
window.open('https://github.com/qaiu/netdisk-fast-download/issues/new', '_blank');
|
||||
}).catch(() => {
|
||||
this.$message.error('复制失败');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -826,4 +872,8 @@ hr {
|
||||
padding-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.jv-container.jv-light .jv-item.jv-object {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="file-meta-info-card">
|
||||
<div class="file-meta-row">
|
||||
<span class="file-meta-label">下载链接:</span>
|
||||
<a :href="downloadUrl" target="_blank" class="file-meta-link">{{ downloadUrl }}</a>
|
||||
<a :href="downloadUrl" target="_blank" class="file-meta-link">点击下载</a>
|
||||
</div>
|
||||
<div class="file-meta-row">
|
||||
<span class="file-meta-label">文件名:</span>{{ fileTypeUtils.extractFileNameAndExt(downloadUrl).name }}
|
||||
|
||||
185
web-service/src/main/java/cn/qaiu/lz/common/util/JwtUtil.java
Normal file
185
web-service/src/main/java/cn/qaiu/lz/common/util/JwtUtil.java
Normal file
@@ -0,0 +1,185 @@
|
||||
package cn.qaiu.lz.common.util;
|
||||
|
||||
import cn.qaiu.lz.web.model.SysUser;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* JWT工具类,用于生成和验证JWT token
|
||||
*/
|
||||
public class JwtUtil {
|
||||
|
||||
private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000; // token过期时间,24小时
|
||||
private static final String SECRET_KEY = "netdisk-fast-download-jwt-secret-key"; // 密钥
|
||||
private static final String ALGORITHM = "HmacSHA256";
|
||||
|
||||
/**
|
||||
* 生成JWT token
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return JWT token
|
||||
*/
|
||||
public static String generateToken(SysUser user) {
|
||||
long expireTime = getExpireTime();
|
||||
|
||||
// Header
|
||||
JsonObject header = new JsonObject()
|
||||
.put("alg", "HS256")
|
||||
.put("typ", "JWT");
|
||||
|
||||
// Payload
|
||||
JsonObject payload = new JsonObject()
|
||||
.put("id", user.getId())
|
||||
.put("username", user.getUsername())
|
||||
.put("role", user.getRole())
|
||||
.put("exp", expireTime)
|
||||
.put("iat", System.currentTimeMillis())
|
||||
.put("iss", "netdisk-fast-download");
|
||||
|
||||
// Base64 encode header and payload
|
||||
String encodedHeader = Base64.getUrlEncoder().withoutPadding().encodeToString(header.encode().getBytes(StandardCharsets.UTF_8));
|
||||
String encodedPayload = Base64.getUrlEncoder().withoutPadding().encodeToString(payload.encode().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Create signature
|
||||
String signature = hmacSha256(encodedHeader + "." + encodedPayload, SECRET_KEY);
|
||||
|
||||
// Combine to form JWT
|
||||
return encodedHeader + "." + encodedPayload + "." + signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用HMAC-SHA256算法生成签名
|
||||
*
|
||||
* @param data 要签名的数据
|
||||
* @param key 密钥
|
||||
* @return 签名
|
||||
*/
|
||||
private static String hmacSha256(String data, String key) {
|
||||
try {
|
||||
Mac sha256Hmac = Mac.getInstance(ALGORITHM);
|
||||
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM);
|
||||
sha256Hmac.init(secretKey);
|
||||
byte[] signedBytes = sha256Hmac.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(signedBytes);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
throw new RuntimeException("Error creating HMAC SHA256 signature", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证JWT token
|
||||
*
|
||||
* @param token JWT token
|
||||
* @return 如果token有效返回true,否则返回false
|
||||
*/
|
||||
public static boolean validateToken(String token) {
|
||||
try {
|
||||
String[] parts = token.split("\\.");
|
||||
if (parts.length != 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String encodedHeader = parts[0];
|
||||
String encodedPayload = parts[1];
|
||||
String signature = parts[2];
|
||||
|
||||
// 验证签名
|
||||
String expectedSignature = hmacSha256(encodedHeader + "." + encodedPayload, SECRET_KEY);
|
||||
if (!expectedSignature.equals(signature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证过期时间
|
||||
String payload = new String(Base64.getUrlDecoder().decode(encodedPayload), StandardCharsets.UTF_8);
|
||||
JsonObject payloadJson = new JsonObject(payload);
|
||||
long expTime = payloadJson.getLong("exp", 0L);
|
||||
|
||||
return System.currentTimeMillis() < expTime;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从token中获取用户ID
|
||||
*
|
||||
* @param token JWT token
|
||||
* @return 用户ID
|
||||
*/
|
||||
public static String getUserIdFromToken(String token) {
|
||||
String[] parts = token.split("\\.");
|
||||
if (parts.length != 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Base64解码
|
||||
String payload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8);
|
||||
JsonObject jsonObject = new JsonObject(payload);
|
||||
return jsonObject.getString("id");
|
||||
}
|
||||
|
||||
/**
|
||||
* 从token中获取用户名
|
||||
*
|
||||
* @param token JWT token
|
||||
* @return 用户名
|
||||
*/
|
||||
public static String getUsernameFromToken(String token) {
|
||||
String[] parts = token.split("\\.");
|
||||
if (parts.length != 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Base64解码
|
||||
String payload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8);
|
||||
JsonObject jsonObject = new JsonObject(payload);
|
||||
return jsonObject.getString("username");
|
||||
}
|
||||
|
||||
/**
|
||||
* 从token中获取用户角色
|
||||
*
|
||||
* @param token JWT token
|
||||
* @return 用户角色
|
||||
*/
|
||||
public static String getRoleFromToken(String token) {
|
||||
String[] parts = token.split("\\.");
|
||||
if (parts.length != 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Base64解码
|
||||
String payload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8);
|
||||
JsonObject jsonObject = new JsonObject(payload);
|
||||
return jsonObject.getString("role");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取过期时间
|
||||
*
|
||||
* @return 过期时间戳
|
||||
*/
|
||||
private static long getExpireTime() {
|
||||
return System.currentTimeMillis() + EXPIRE_TIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将过期时间戳转换为LocalDateTime
|
||||
*
|
||||
* @param expireTime 过期时间戳
|
||||
* @return LocalDateTime
|
||||
*/
|
||||
public static LocalDateTime getExpireTimeAsLocalDateTime(long expireTime) {
|
||||
return LocalDateTime.ofInstant(Instant.ofEpochMilli(expireTime), ZoneId.systemDefault());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package cn.qaiu.lz.common.util;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* 密码加密工具类
|
||||
* 使用SHA-256算法加盐进行密码加密和验证
|
||||
*/
|
||||
public class PasswordUtil {
|
||||
|
||||
private static final String ALGORITHM = "SHA-256";
|
||||
private static final int SALT_LENGTH = 16; // 盐的长度
|
||||
private static final String DELIMITER = ":"; // 用于分隔盐和哈希值的分隔符
|
||||
|
||||
/**
|
||||
* 对密码进行加密
|
||||
*
|
||||
* @param plainPassword 明文密码
|
||||
* @return 加密后的密码(格式:salt:hash)
|
||||
*/
|
||||
public static String hashPassword(String plainPassword) {
|
||||
if (plainPassword == null || plainPassword.isEmpty()) {
|
||||
throw new IllegalArgumentException("密码不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 生成随机盐
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] salt = new byte[SALT_LENGTH];
|
||||
random.nextBytes(salt);
|
||||
|
||||
// 计算哈希值
|
||||
MessageDigest md = MessageDigest.getInstance(ALGORITHM);
|
||||
md.update(salt);
|
||||
byte[] hashedPassword = md.digest(plainPassword.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// 将盐和哈希值编码为Base64并拼接
|
||||
String saltBase64 = Base64.getEncoder().encodeToString(salt);
|
||||
String hashBase64 = Base64.getEncoder().encodeToString(hashedPassword);
|
||||
|
||||
// 返回格式:salt:hash
|
||||
return saltBase64 + DELIMITER + hashBase64;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("加密算法不可用", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码是否正确
|
||||
*
|
||||
* @param plainPassword 明文密码
|
||||
* @param hashedPassword 加密后的密码(格式:salt:hash)
|
||||
* @return 如果密码匹配返回true,否则返回false
|
||||
*/
|
||||
public static boolean checkPassword(String plainPassword, String hashedPassword) {
|
||||
if (plainPassword == null || hashedPassword == null || hashedPassword.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 分割盐和哈希值
|
||||
String[] parts = hashedPassword.split(DELIMITER);
|
||||
if (parts.length != 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String saltBase64 = parts[0];
|
||||
String hashBase64 = parts[1];
|
||||
|
||||
// 解码盐
|
||||
byte[] salt = Base64.getDecoder().decode(saltBase64);
|
||||
|
||||
// 使用相同的盐计算哈希值
|
||||
MessageDigest md = MessageDigest.getInstance(ALGORITHM);
|
||||
md.update(salt);
|
||||
byte[] calculatedHash = md.digest(plainPassword.getBytes(StandardCharsets.UTF_8));
|
||||
String calculatedHashBase64 = Base64.getEncoder().encodeToString(calculatedHash);
|
||||
|
||||
// 比较计算出的哈希值和存储的哈希值
|
||||
return hashBase64.equals(calculatedHashBase64);
|
||||
} catch (Exception e) {
|
||||
// 如果发生异常(例如格式不正确),返回false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package cn.qaiu.lz.web.model;
|
||||
import cn.qaiu.db.ddl.Table;
|
||||
import cn.qaiu.lz.common.ToJson;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.vertx.codegen.annotations.DataObject;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import lombok.Data;
|
||||
@@ -14,12 +15,27 @@ import java.time.format.DateTimeFormatter;
|
||||
@Data
|
||||
@DataObject
|
||||
@NoArgsConstructor
|
||||
@Table("t_user")
|
||||
@Table("sys_user")
|
||||
public class SysUser implements ToJson {
|
||||
private String id;
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
private String email;
|
||||
private String phone;
|
||||
private String avatar;
|
||||
|
||||
// 用户状态:0-禁用,1-正常
|
||||
private Integer status;
|
||||
|
||||
// 用户角色:admin-管理员,user-普通用户
|
||||
private String role;
|
||||
|
||||
// 最后登录时间
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||
private LocalDateTime lastLoginTime;
|
||||
|
||||
private Integer age;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
@@ -28,9 +44,17 @@ public class SysUser implements ToJson {
|
||||
this.id = json.getString("id");
|
||||
this.username = json.getString("username");
|
||||
this.password = json.getString("password");
|
||||
this.email = json.getString("email");
|
||||
this.phone = json.getString("phone");
|
||||
this.avatar = json.getString("avatar");
|
||||
this.status = json.getInteger("status");
|
||||
this.role = json.getString("role");
|
||||
this.age = json.getInteger("age");
|
||||
if (json.getString("createTime") != null) {
|
||||
this.createTime = LocalDateTime.parse(json.getString("createTime"));
|
||||
}
|
||||
if (json.getString("lastLoginTime") != null) {
|
||||
this.lastLoginTime = LocalDateTime.parse(json.getString("lastLoginTime"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,48 @@ import cn.qaiu.lz.web.model.SysUser;
|
||||
import cn.qaiu.vx.core.base.BaseAsyncService;
|
||||
import io.vertx.codegen.annotations.ProxyGen;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
|
||||
/**
|
||||
* lz-web
|
||||
* 用户服务接口
|
||||
* <br>Create date 2021/8/27 14:06
|
||||
*
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
*/
|
||||
@ProxyGen
|
||||
public interface UserService extends BaseAsyncService {
|
||||
Future<SysUser> login(SysUser user);
|
||||
/**
|
||||
* 用户登录
|
||||
* @param user 包含用户名和密码的用户对象
|
||||
* @return 登录成功返回用户信息和token,失败返回错误信息
|
||||
*/
|
||||
Future<JsonObject> login(SysUser user);
|
||||
|
||||
/**
|
||||
* 根据用户名获取用户信息
|
||||
* @param username 用户名
|
||||
* @return 用户信息
|
||||
*/
|
||||
Future<SysUser> getUserByUsername(String username);
|
||||
|
||||
/**
|
||||
* 创建新用户
|
||||
* @param user 用户信息
|
||||
* @return 创建成功返回用户信息,失败返回错误信息
|
||||
*/
|
||||
Future<SysUser> createUser(SysUser user);
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
* @param user 用户信息
|
||||
* @return 更新成功返回用户信息,失败返回错误信息
|
||||
*/
|
||||
Future<SysUser> updateUser(SysUser user);
|
||||
|
||||
/**
|
||||
* 验证token
|
||||
* @param token JWT token
|
||||
* @return 验证成功返回用户信息,失败返回错误信息
|
||||
*/
|
||||
Future<JsonObject> validateToken(String token);
|
||||
}
|
||||
|
||||
@@ -1,29 +1,414 @@
|
||||
package cn.qaiu.lz.web.service.impl;
|
||||
|
||||
import cn.qaiu.db.pool.JDBCPoolInit;
|
||||
import cn.qaiu.lz.common.util.JwtUtil;
|
||||
import cn.qaiu.lz.common.util.PasswordUtil;
|
||||
import cn.qaiu.lz.web.model.SysUser;
|
||||
import cn.qaiu.lz.web.service.UserService;
|
||||
import cn.qaiu.vx.core.annotaions.Service;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.jdbcclient.JDBCPool;
|
||||
import io.vertx.sqlclient.Row;
|
||||
import io.vertx.sqlclient.RowSet;
|
||||
import io.vertx.sqlclient.Tuple;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* lz-web
|
||||
* 用户服务实现类
|
||||
* <br>Create date 2021/8/27 14:09
|
||||
*
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class UserServiceImpl implements UserService {
|
||||
|
||||
@Override
|
||||
public Future<SysUser> login(SysUser user) {
|
||||
private final JDBCPool jdbcPool = JDBCPoolInit.instance().getPool();
|
||||
|
||||
// try {
|
||||
// TimeUnit.SECONDS.sleep(2);
|
||||
// } catch (InterruptedException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
return Future.succeededFuture(user);
|
||||
// 初始化方法,确保管理员用户存在
|
||||
public void init() {
|
||||
// 检查管理员用户是否存在
|
||||
getUserByUsername("admin")
|
||||
.onSuccess(user -> {
|
||||
log.info("管理员用户已存在");
|
||||
})
|
||||
.onFailure(err -> {
|
||||
// 创建管理员用户
|
||||
SysUser admin = new SysUser();
|
||||
admin.setId(UUID.randomUUID().toString());
|
||||
admin.setUsername("admin");
|
||||
admin.setPassword(PasswordUtil.hashPassword("admin123"));
|
||||
admin.setEmail("admin@example.com");
|
||||
admin.setRole("admin");
|
||||
admin.setStatus(1);
|
||||
admin.setCreateTime(LocalDateTime.now());
|
||||
|
||||
createUser(admin)
|
||||
.onSuccess(result -> log.info("管理员用户创建成功"))
|
||||
.onFailure(error -> log.error("管理员用户创建失败", error));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 新增一个工具方法来过滤敏感信息
|
||||
private SysUser filterSensitiveInfo(SysUser user) {
|
||||
if (user != null) {
|
||||
SysUser filtered = new SysUser();
|
||||
// 复制除密码外的所有字段
|
||||
filtered.setId(user.getId());
|
||||
filtered.setUsername(user.getUsername());
|
||||
filtered.setEmail(user.getEmail());
|
||||
filtered.setPhone(user.getPhone());
|
||||
filtered.setAvatar(user.getAvatar());
|
||||
filtered.setRole(user.getRole());
|
||||
filtered.setStatus(user.getStatus());
|
||||
filtered.setCreateTime(user.getCreateTime());
|
||||
filtered.setLastLoginTime(user.getLastLoginTime());
|
||||
return filtered;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 将Row转换为SysUser对象
|
||||
private SysUser rowToUser(Row row) {
|
||||
if (row == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SysUser user = new SysUser();
|
||||
user.setId(row.getString("id"));
|
||||
user.setUsername(row.getString("username"));
|
||||
user.setPassword(row.getString("password"));
|
||||
user.setEmail(row.getString("email"));
|
||||
user.setPhone(row.getString("phone"));
|
||||
user.setAvatar(row.getString("avatar"));
|
||||
user.setRole(row.getString("role"));
|
||||
user.setStatus(row.getInteger("status"));
|
||||
|
||||
// 处理日期时间字段
|
||||
LocalDateTime createTime = row.getLocalDateTime("create_time");
|
||||
if (createTime != null) {
|
||||
user.setCreateTime(createTime);
|
||||
}
|
||||
|
||||
LocalDateTime lastLoginTime = row.getLocalDateTime("last_login_time");
|
||||
if (lastLoginTime != null) {
|
||||
user.setLastLoginTime(lastLoginTime);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<JsonObject> login(SysUser user) {
|
||||
// 参数校验
|
||||
if (user == null || user.getUsername() == null || user.getPassword() == null) {
|
||||
return Future.succeededFuture(new JsonObject()
|
||||
.put("success", false)
|
||||
.put("message", "用户名和密码不能为空"));
|
||||
}
|
||||
|
||||
Promise<JsonObject> promise = Promise.promise();
|
||||
|
||||
// 查询用户
|
||||
String sql = "SELECT * FROM sys_user WHERE username = ?";
|
||||
|
||||
jdbcPool.preparedQuery(sql)
|
||||
.execute(Tuple.of(user.getUsername()))
|
||||
.onSuccess(rows -> {
|
||||
if (rows.size() == 0) {
|
||||
promise.complete(new JsonObject()
|
||||
.put("success", false)
|
||||
.put("message", "用户不存在"));
|
||||
return;
|
||||
}
|
||||
|
||||
Row row = rows.iterator().next();
|
||||
SysUser existUser = rowToUser(row);
|
||||
|
||||
// 验证密码
|
||||
if (!PasswordUtil.checkPassword(user.getPassword(), existUser.getPassword())) {
|
||||
promise.complete(new JsonObject()
|
||||
.put("success", false)
|
||||
.put("message", "密码错误"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新最后登录时间
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
existUser.setLastLoginTime(now);
|
||||
|
||||
// 更新数据库中的最后登录时间
|
||||
String updateSql = "UPDATE sys_user SET last_login_time = ? WHERE username = ?";
|
||||
jdbcPool.preparedQuery(updateSql)
|
||||
.execute(Tuple.of(
|
||||
Timestamp.from(now.atZone(ZoneId.systemDefault()).toInstant()),
|
||||
existUser.getUsername()
|
||||
))
|
||||
.onFailure(err -> log.error("更新最后登录时间失败", err));
|
||||
|
||||
// 生成token
|
||||
String token = JwtUtil.generateToken(existUser);
|
||||
|
||||
// 返回用户信息和token
|
||||
JsonObject value = JsonObject.mapFrom(existUser);
|
||||
value.remove("password");
|
||||
promise.complete(new JsonObject()
|
||||
.put("success", true)
|
||||
.put("message", "登录成功")
|
||||
.put("token", token)
|
||||
.put("user", value));
|
||||
})
|
||||
.onFailure(err -> {
|
||||
log.error("登录查询失败", err);
|
||||
promise.complete(new JsonObject()
|
||||
.put("success", false)
|
||||
.put("message", "登录失败: " + err.getMessage()));
|
||||
});
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<SysUser> getUserByUsername(String username) {
|
||||
if (username == null || username.isEmpty()) {
|
||||
return Future.failedFuture("用户名不能为空");
|
||||
}
|
||||
|
||||
Promise<SysUser> promise = Promise.promise();
|
||||
|
||||
String sql = "SELECT * FROM sys_user WHERE username = ?";
|
||||
|
||||
jdbcPool.preparedQuery(sql)
|
||||
.execute(Tuple.of(username))
|
||||
.onSuccess(rows -> {
|
||||
if (rows.size() == 0) {
|
||||
promise.fail("用户不存在");
|
||||
return;
|
||||
}
|
||||
|
||||
Row row = rows.iterator().next();
|
||||
SysUser user = rowToUser(row);
|
||||
promise.complete(filterSensitiveInfo(user));
|
||||
})
|
||||
.onFailure(err -> {
|
||||
log.error("查询用户失败", err);
|
||||
promise.fail("查询用户失败: " + err.getMessage());
|
||||
});
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<SysUser> createUser(SysUser user) {
|
||||
// 参数校验
|
||||
if (user == null || user.getUsername() == null || user.getPassword() == null) {
|
||||
return Future.failedFuture("用户名和密码不能为空");
|
||||
}
|
||||
|
||||
Promise<SysUser> promise = Promise.promise();
|
||||
|
||||
// 先检查用户是否已存在
|
||||
String checkSql = "SELECT COUNT(*) as count FROM sys_user WHERE username = ?";
|
||||
|
||||
jdbcPool.preparedQuery(checkSql)
|
||||
.execute(Tuple.of(user.getUsername()))
|
||||
.onSuccess(rows -> {
|
||||
Row row = rows.iterator().next();
|
||||
long count = row.getLong("count");
|
||||
|
||||
if (count > 0) {
|
||||
promise.fail("用户名已存在");
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置用户ID和创建时间
|
||||
if (user.getId() == null) {
|
||||
user.setId(UUID.randomUUID().toString());
|
||||
}
|
||||
if (user.getCreateTime() == null) {
|
||||
user.setCreateTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
// 设置默认角色和状态
|
||||
if (user.getRole() == null) {
|
||||
user.setRole("user");
|
||||
}
|
||||
if (user.getStatus() == null) {
|
||||
user.setStatus(1);
|
||||
}
|
||||
|
||||
// 对密码进行加密
|
||||
String plainPassword = user.getPassword();
|
||||
user.setPassword(PasswordUtil.hashPassword(plainPassword));
|
||||
|
||||
// 插入用户
|
||||
String insertSql = "INSERT INTO sys_user (id, username, password, email, phone, avatar, role, status, create_time) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
jdbcPool.preparedQuery(insertSql)
|
||||
.execute(Tuple.of(
|
||||
user.getId(),
|
||||
user.getUsername(),
|
||||
user.getPassword(),
|
||||
user.getEmail(),
|
||||
user.getPhone(),
|
||||
user.getAvatar(),
|
||||
user.getRole(),
|
||||
user.getStatus(),
|
||||
Timestamp.from(user.getCreateTime().atZone(ZoneId.systemDefault()).toInstant())
|
||||
))
|
||||
.onSuccess(result -> {
|
||||
promise.complete(filterSensitiveInfo(user));
|
||||
})
|
||||
.onFailure(err -> {
|
||||
log.error("创建用户失败", err);
|
||||
promise.fail("创建用户失败: " + err.getMessage());
|
||||
});
|
||||
})
|
||||
.onFailure(err -> {
|
||||
log.error("检查用户是否存在失败", err);
|
||||
promise.fail("创建用户失败: " + err.getMessage());
|
||||
});
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<SysUser> updateUser(SysUser user) {
|
||||
// 参数校验
|
||||
if (user == null || user.getUsername() == null) {
|
||||
return Future.failedFuture("用户名不能为空");
|
||||
}
|
||||
|
||||
Promise<SysUser> promise = Promise.promise();
|
||||
|
||||
// 先检查用户是否存在
|
||||
String checkSql = "SELECT * FROM sys_user WHERE username = ?";
|
||||
|
||||
jdbcPool.preparedQuery(checkSql)
|
||||
.execute(Tuple.of(user.getUsername()))
|
||||
.onSuccess(rows -> {
|
||||
if (rows.size() == 0) {
|
||||
promise.fail("用户不存在");
|
||||
return;
|
||||
}
|
||||
|
||||
Row row = rows.iterator().next();
|
||||
SysUser existUser = rowToUser(row);
|
||||
|
||||
// 构建更新SQL
|
||||
StringBuilder updateSql = new StringBuilder("UPDATE sys_user SET ");
|
||||
Tuple params = Tuple.tuple();
|
||||
|
||||
if (user.getEmail() != null) {
|
||||
updateSql.append("email = ?, ");
|
||||
params.addValue(user.getEmail());
|
||||
}
|
||||
|
||||
if (user.getPhone() != null) {
|
||||
updateSql.append("phone = ?, ");
|
||||
params.addValue(user.getPhone());
|
||||
}
|
||||
|
||||
if (user.getAvatar() != null) {
|
||||
updateSql.append("avatar = ?, ");
|
||||
params.addValue(user.getAvatar());
|
||||
}
|
||||
|
||||
if (user.getStatus() != null) {
|
||||
updateSql.append("status = ?, ");
|
||||
params.addValue(user.getStatus());
|
||||
}
|
||||
|
||||
if (user.getRole() != null) {
|
||||
updateSql.append("role = ?, ");
|
||||
params.addValue(user.getRole());
|
||||
}
|
||||
|
||||
if (user.getPassword() != null) {
|
||||
updateSql.append("password = ?, ");
|
||||
params.addValue(PasswordUtil.hashPassword(user.getPassword()));
|
||||
}
|
||||
|
||||
// 移除最后的逗号和空格
|
||||
String sql = updateSql.toString();
|
||||
if (sql.endsWith(", ")) {
|
||||
sql = sql.substring(0, sql.length() - 2);
|
||||
}
|
||||
|
||||
// 如果没有要更新的字段,直接返回
|
||||
if (params.size() == 0) {
|
||||
promise.complete(filterSensitiveInfo(existUser));
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加WHERE条件
|
||||
sql += " WHERE username = ?";
|
||||
params.addValue(user.getUsername());
|
||||
|
||||
// 执行更新
|
||||
jdbcPool.preparedQuery(sql)
|
||||
.execute(params)
|
||||
.onSuccess(result -> {
|
||||
// 重新查询用户信息
|
||||
getUserByUsername(user.getUsername())
|
||||
.onSuccess(promise::complete)
|
||||
.onFailure(promise::fail);
|
||||
})
|
||||
.onFailure(err -> {
|
||||
log.error("更新用户失败", err);
|
||||
promise.fail("更新用户失败: " + err.getMessage());
|
||||
});
|
||||
})
|
||||
.onFailure(err -> {
|
||||
log.error("查询用户失败", err);
|
||||
promise.fail("更新用户失败: " + err.getMessage());
|
||||
});
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<JsonObject> validateToken(String token) {
|
||||
if (token == null || token.isEmpty()) {
|
||||
return Future.succeededFuture(new JsonObject()
|
||||
.put("success", false)
|
||||
.put("message", "Token不能为空"));
|
||||
}
|
||||
|
||||
// 验证token
|
||||
boolean isValid = JwtUtil.validateToken(token);
|
||||
if (!isValid) {
|
||||
return Future.succeededFuture(new JsonObject()
|
||||
.put("success", false)
|
||||
.put("message", "Token无效或已过期"));
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
String username = JwtUtil.getUsernameFromToken(token);
|
||||
|
||||
Promise<JsonObject> promise = Promise.promise();
|
||||
|
||||
getUserByUsername(username)
|
||||
.onSuccess(user -> {
|
||||
promise.complete(new JsonObject()
|
||||
.put("success", true)
|
||||
.put("message", "Token有效")
|
||||
.put("user", JsonObject.mapFrom(user)));
|
||||
})
|
||||
.onFailure(err -> {
|
||||
promise.complete(new JsonObject()
|
||||
.put("success", false)
|
||||
.put("message", "用户不存在"));
|
||||
});
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
}
|
||||
@@ -36,15 +36,15 @@ custom:
|
||||
- ^cn\.qaiu\.lz\.web\.model\..*
|
||||
|
||||
# 限流配置
|
||||
rateLimit:
|
||||
# 是否启用限流
|
||||
enable: true
|
||||
# 限流的请求数
|
||||
limit: 10
|
||||
# 限流的时间窗口(单位秒)
|
||||
timeWindow: 10
|
||||
# 路径匹配规则
|
||||
pathReg: ^/v2/.*
|
||||
#rateLimit:
|
||||
# # 是否启用限流
|
||||
# enable: true
|
||||
# # 限流的请求数
|
||||
# limit: 10
|
||||
# # 限流的时间窗口(单位秒)
|
||||
# timeWindow: 10
|
||||
# # 路径匹配规则
|
||||
# pathReg: ^/v2/.*
|
||||
|
||||
|
||||
# 数据源配置
|
||||
|
||||
Reference in New Issue
Block a user