mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-04-11 11:26:55 +00:00
Compare commits
14 Commits
ed8fd66d1e
...
copilot/cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24025e0ae6 | ||
|
|
d8bffc19ce | ||
|
|
a420bad305 | ||
|
|
6ef6e47580 | ||
|
|
94f83ec296 | ||
|
|
702569c701 | ||
|
|
d4940ca9ee | ||
|
|
dbd1c138ca | ||
|
|
0b49c55cf3 | ||
|
|
b1ec3b2eea | ||
|
|
9ea89feee7 | ||
|
|
4a843194a3 | ||
|
|
03503115fd | ||
|
|
1870aef60e |
19
README.md
19
README.md
@@ -1,5 +1,17 @@
|
|||||||
|
<div align="center" style="display:flex; justify-content:center; gap:10px; align-items:flex-start;">
|
||||||
|
<img
|
||||||
|
src="https://github.com/user-attachments/assets/bf266d0a-aaf8-4772-9231-e38a4b7bb6cb"
|
||||||
|
alt="image1"
|
||||||
|
style="width:300px; max-width:300px; flex:none;"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="https://github.com/user-attachments/assets/bb7a85f0-c256-4b4a-a11b-3ceb55afc302"
|
||||||
|
alt="image2"
|
||||||
|
style="width:300px; max-width:300px; flex:none;"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://github.com/user-attachments/assets/87401aae-b0b6-4ffb-bbeb-44756404d26f" alt="项目预览图" />
|
<a href="https://trendshift.io/repositories/12101" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12101" alt="qaiu%2Fnetdisk-fast-download | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -8,10 +20,6 @@
|
|||||||
<a href="https://vertx-china.github.io"><img src="https://img.shields.io/badge/vert.x-4.5.22-blue?style=flat"></a>
|
<a href="https://vertx-china.github.io"><img src="https://img.shields.io/badge/vert.x-4.5.22-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>
|
<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>
|
||||||
<a href="https://github.com/qaiu/netdisk-fast-download/releases/"><img src="https://img.shields.io/github/v/release/qaiu/netdisk-fast-download?style=flat"></a>
|
<a href="https://github.com/qaiu/netdisk-fast-download/releases/"><img src="https://img.shields.io/github/v/release/qaiu/netdisk-fast-download?style=flat"></a>
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# netdisk-fast-download 网盘分享链接云解析服务
|
# netdisk-fast-download 网盘分享链接云解析服务
|
||||||
QQ交流群:1017480890
|
QQ交流群:1017480890
|
||||||
@@ -61,7 +69,6 @@ main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/
|
|||||||
|
|
||||||
- [蓝奏云-lz](https://pc.woozooo.com/)
|
- [蓝奏云-lz](https://pc.woozooo.com/)
|
||||||
- [蓝奏云优享-iz](https://www.ilanzou.com/)
|
- [蓝奏云优享-iz](https://www.ilanzou.com/)
|
||||||
- [奶牛快传-cow](https://cowtransfer.com/)
|
|
||||||
- [移动云云空间-ec](https://www.ecpan.cn/web)
|
- [移动云云空间-ec](https://www.ecpan.cn/web)
|
||||||
- [小飞机网盘-fj](https://www.feijipan.com/)
|
- [小飞机网盘-fj](https://www.feijipan.com/)
|
||||||
- [亿方云-fc](https://www.fangcloud.com/)
|
- [亿方云-fc](https://www.fangcloud.com/)
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ public enum PanDomainTemplate {
|
|||||||
|
|
||||||
// https://v2.fangcloud.com/s/
|
// https://v2.fangcloud.com/s/
|
||||||
FC("亿方云",
|
FC("亿方云",
|
||||||
compile("https://v2\\.fangcloud\\.(com|cn)/(s|sharing)/(?<KEY>.+)"),
|
compile("https://v2\\.fangcloud\\.(com|cn)/(s|share|sharing)/(?<KEY>.+)"),
|
||||||
"https://v2.fangcloud.com/s/{shareKey}",
|
"https://v2.fangcloud.com/s/{shareKey}",
|
||||||
"https://www.fangcloud.com/",
|
"https://www.fangcloud.com/",
|
||||||
FcTool.class),
|
FcTool.class),
|
||||||
@@ -142,9 +142,41 @@ public enum PanDomainTemplate {
|
|||||||
compile("https://qfile\\.qq\\.com/q/(?<KEY>.+)"),
|
compile("https://qfile\\.qq\\.com/q/(?<KEY>.+)"),
|
||||||
"https://qfile.qq.com/q/{shareKey}",
|
"https://qfile.qq.com/q/{shareKey}",
|
||||||
QQscTool.class),
|
QQscTool.class),
|
||||||
// https://f.ws59.cn/f/或者https://www.wenshushu.cn/f/
|
// https://f.ws59.cn/f/ 或者 https://www.wenshushu.cn/f/ 等多个镜像域名
|
||||||
|
/*
|
||||||
|
f.wsNN.cn (如 f.ws59.cn, f.ws28.cn 等)
|
||||||
|
www.wenshushu.cn
|
||||||
|
新增域名:
|
||||||
|
www.wenxiaozhan.net
|
||||||
|
www.wenxiaozhan.cn
|
||||||
|
www.wss.show
|
||||||
|
www.ws28.cn
|
||||||
|
www.wss.email
|
||||||
|
www.wss1.cn
|
||||||
|
www.ws59.cn
|
||||||
|
www.wss.cc
|
||||||
|
www.wss.pet
|
||||||
|
www.wss.ink
|
||||||
|
www.wenxiaozhan.com
|
||||||
|
www.wenshushu.com
|
||||||
|
www.wss.zone
|
||||||
|
*/
|
||||||
WS("文叔叔",
|
WS("文叔叔",
|
||||||
compile("https://(f\\.ws(\\d{2})\\.cn|www\\.wenshushu\\.cn)/f/(?<KEY>.+)"),
|
compile("https://(f\\.ws(\\d{2})\\.cn|" +
|
||||||
|
"www\\.wenxiaozhan\\.net|" +
|
||||||
|
"www\\.wenxiaozhan\\.cn|" +
|
||||||
|
"www\\.wss\\.show|" +
|
||||||
|
"www\\.ws28\\.cn|" +
|
||||||
|
"www\\.wss\\.email|" +
|
||||||
|
"www\\.wss1\\.cn|" +
|
||||||
|
"www\\.ws59\\.cn|" +
|
||||||
|
"www\\.wss\\.cc|" +
|
||||||
|
"www\\.wss\\.pet|" +
|
||||||
|
"www\\.wss\\.ink|" +
|
||||||
|
"www\\.wenxiaozhan\\.com|" +
|
||||||
|
"www\\.wenshushu\\.com|" +
|
||||||
|
"www\\.wss\\.zone|" +
|
||||||
|
"www\\.wenshushu\\.cn)/f/(?<KEY>.+)"),
|
||||||
"https://www.wenshushu.cn/f/{shareKey}",
|
"https://www.wenshushu.cn/f/{shareKey}",
|
||||||
WsTool.class),
|
WsTool.class),
|
||||||
// https://www.123pan.com/s/
|
// https://www.123pan.com/s/
|
||||||
@@ -198,7 +230,7 @@ public enum PanDomainTemplate {
|
|||||||
"123635\\.com|" +
|
"123635\\.com|" +
|
||||||
"123242\\.com|" +
|
"123242\\.com|" +
|
||||||
"123795\\.com" +
|
"123795\\.com" +
|
||||||
")/s/(?<KEY>.+)(.html)?"),
|
")/s/(?<KEY>[a-zA-Z0-9_-]+)(?:\\.html)?"),
|
||||||
"https://www.123pan.com/s/{shareKey}",
|
"https://www.123pan.com/s/{shareKey}",
|
||||||
Ye2Tool.class),
|
Ye2Tool.class),
|
||||||
// https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={code}&isShare=1
|
// https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={code}&isShare=1
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ import java.util.Arrays;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static java.util.regex.Pattern.compile;
|
import static java.util.regex.Pattern.compile;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="https://qaiu.top">QAIU</a>
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
@@ -77,6 +80,55 @@ public class PanDomainTemplateTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWsPatternMatching() {
|
||||||
|
Pattern wsPattern = PanDomainTemplate.WS.getPattern();
|
||||||
|
|
||||||
|
// 历史域名
|
||||||
|
String[] positiveUrls = {
|
||||||
|
"https://f.ws59.cn/f/f25625rv6p6",
|
||||||
|
"https://f.ws28.cn/f/somekey123",
|
||||||
|
"https://www.wenshushu.cn/f/abc123",
|
||||||
|
// 新增域名
|
||||||
|
"https://www.wenxiaozhan.net/f/testkey1",
|
||||||
|
"https://www.wenxiaozhan.cn/f/testkey2",
|
||||||
|
"https://www.wss.show/f/testkey3",
|
||||||
|
"https://www.ws28.cn/f/testkey4",
|
||||||
|
"https://www.wss.email/f/testkey5",
|
||||||
|
"https://www.wss1.cn/f/testkey6",
|
||||||
|
"https://www.ws59.cn/f/testkey7",
|
||||||
|
"https://www.wss.cc/f/testkey8",
|
||||||
|
"https://www.wss.pet/f/testkey9",
|
||||||
|
"https://www.wss.ink/f/testkey10",
|
||||||
|
"https://www.wenxiaozhan.com/f/testkey11",
|
||||||
|
"https://www.wenshushu.com/f/testkey12",
|
||||||
|
"https://www.wss.zone/f/testkey13",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (String url : positiveUrls) {
|
||||||
|
Matcher m = wsPattern.matcher(url);
|
||||||
|
assertTrue("WS pattern should match: " + url, m.matches());
|
||||||
|
assertNotNull("KEY group should not be null for: " + url, m.group("KEY"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证 KEY 提取正确性
|
||||||
|
Matcher m1 = wsPattern.matcher("https://f.ws59.cn/f/f25625rv6p6");
|
||||||
|
assertTrue(m1.matches());
|
||||||
|
assertEquals("f25625rv6p6", m1.group("KEY"));
|
||||||
|
|
||||||
|
Matcher m2 = wsPattern.matcher("https://www.wenshushu.cn/f/abc123");
|
||||||
|
assertTrue(m2.matches());
|
||||||
|
assertEquals("abc123", m2.group("KEY"));
|
||||||
|
|
||||||
|
// 负例:错误路径不匹配
|
||||||
|
assertFalse("Wrong path should not match",
|
||||||
|
wsPattern.matcher("https://www.wenshushu.cn/x/abc123").matches());
|
||||||
|
|
||||||
|
// 负例:非白名单域名不匹配
|
||||||
|
assertFalse("Non-whitelisted domain should not match",
|
||||||
|
wsPattern.matcher("https://www.evil.com/f/abc123").matches());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void verifyDuplicates() {
|
public void verifyDuplicates() {
|
||||||
|
|
||||||
|
|||||||
@@ -274,7 +274,7 @@
|
|||||||
name: '联想乐云'
|
name: '联想乐云'
|
||||||
},
|
},
|
||||||
fangcloud: {
|
fangcloud: {
|
||||||
reg: /https:\/\/v2\.fangcloud\.(com|cn)\/(s|sharing)\/.+/,
|
reg: /https:\/\/v2\.fangcloud\.(com|cn)\/(s|share|sharing)\/.+/,
|
||||||
host: /fangcloud\.(com|cn)/,
|
host: /fangcloud\.(com|cn)/,
|
||||||
name: '亿方云'
|
name: '亿方云'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,185 +0,0 @@
|
|||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
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;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@DataObject
|
|
||||||
@NoArgsConstructor
|
|
||||||
@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;
|
|
||||||
|
|
||||||
public SysUser(JsonObject json) {
|
|
||||||
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"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package cn.qaiu.lz.web.service;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户服务接口
|
|
||||||
* <br>Create date 2021/8/27 14:06
|
|
||||||
*
|
|
||||||
* @author <a href="https://qaiu.top">QAIU</a>
|
|
||||||
*/
|
|
||||||
@ProxyGen
|
|
||||||
public interface UserService extends BaseAsyncService {
|
|
||||||
/**
|
|
||||||
* 用户登录
|
|
||||||
* @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,414 +0,0 @@
|
|||||||
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.sql.Timestamp;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户服务实现类
|
|
||||||
* <br>Create date 2021/8/27 14:09
|
|
||||||
*
|
|
||||||
* @author <a href="https://qaiu.top">QAIU</a>
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Service
|
|
||||||
public class UserServiceImpl implements UserService {
|
|
||||||
|
|
||||||
private final JDBCPool jdbcPool = JDBCPoolInit.instance().getPool();
|
|
||||||
|
|
||||||
// 初始化方法,确保管理员用户存在
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user