Initial commit
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
114
.gitignore
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
# User-specific stuff
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
target/
|
||||
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
.flattened-pom.xml
|
||||
|
||||
# Common working directory
|
||||
run/
|
98
pom.xml
Normal file
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>site.deercloud</groupId>
|
||||
<artifactId>IdentityVerification</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>IdentityVerification</name>
|
||||
|
||||
<description>实名认证添加白名单、正版玩家邀请非正版注册管理。</description>
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<url>https://blog.deercloud.site</url>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>16</source>
|
||||
<target>16</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spigotmc-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>sonatype</id>
|
||||
<url>https://oss.sonatype.org/content/groups/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.13-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.39.4.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>2.0.19</version>
|
||||
</dependency>
|
||||
<!--JavaMail基本包-->
|
||||
<dependency>
|
||||
<groupId>javax.mail</groupId>
|
||||
<artifactId>mail</artifactId>
|
||||
<version>1.4.7</version>
|
||||
</dependency>
|
||||
<!--邮件发送的扩展包-->
|
||||
<dependency>
|
||||
<groupId>javax.activation</groupId>
|
||||
<artifactId>activation</artifactId>
|
||||
<version>1.1.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,30 @@
|
||||
package site.deercloud.identityverification;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import site.deercloud.identityverification.Controller.InviteCodeManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Commands implements TabExecutor {
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
switch (args[0]) {
|
||||
case "reload":
|
||||
IdentityVerification.getInstance().getConfigManager().reload(sender);
|
||||
case "genCode":
|
||||
// TODO: 需要校验玩家是否符合活跃度要求
|
||||
InviteCodeManager.CreateCode(sender);
|
||||
case "myCodes":
|
||||
InviteCodeManager.ListCodesOf(sender);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package site.deercloud.identityverification.Controller;
|
||||
|
||||
import site.deercloud.identityverification.HttpServer.model.GameSession;
|
||||
import site.deercloud.identityverification.IdentityVerification;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AFKTracker {
|
||||
|
||||
|
||||
public static final long IGNORES_AFK = -1L;
|
||||
|
||||
private final Set<UUID> usedAFKCommand;
|
||||
|
||||
private Long afkThresholdMs;
|
||||
private final IdentityVerification plugin;
|
||||
|
||||
public AFKTracker(IdentityVerification plugin) {
|
||||
this.plugin = plugin;
|
||||
usedAFKCommand = new HashSet<>();
|
||||
}
|
||||
|
||||
public long getAfkThreshold() {
|
||||
if (afkThresholdMs == null) {
|
||||
afkThresholdMs = 1000L * 10L;
|
||||
}
|
||||
return afkThresholdMs;
|
||||
}
|
||||
|
||||
public void hasIgnorePermission(UUID playerUUID) {
|
||||
storeLastMovement(playerUUID, IGNORES_AFK);
|
||||
}
|
||||
|
||||
private void storeLastMovement(UUID playerUUID, long time) {
|
||||
GameSessionCache.getCacheSession(playerUUID)
|
||||
.ifPresent(gameSession -> gameSession.setLastMovement(time));
|
||||
}
|
||||
|
||||
private long getLastMovement(UUID playerUUID, long time) {
|
||||
return getLastMovement(playerUUID)
|
||||
.orElse(time);
|
||||
}
|
||||
|
||||
private Optional<Long> getLastMovement(UUID playerUUID) {
|
||||
return GameSessionCache.getCacheSession(playerUUID)
|
||||
.map(GameSession::getLastMovement);
|
||||
}
|
||||
|
||||
public void usedAfkCommand(UUID playerUUID, long time) {
|
||||
long lastMoved = getLastMovement(playerUUID, time);
|
||||
if (lastMoved == IGNORES_AFK) {
|
||||
return;
|
||||
}
|
||||
usedAFKCommand.add(playerUUID);
|
||||
storeLastMovement(playerUUID, time - getAfkThreshold());
|
||||
}
|
||||
|
||||
public long performedAction(UUID playerUUID, long time) {
|
||||
long lastMoved = getLastMovement(playerUUID, time);
|
||||
// Ignore afk permission
|
||||
if (lastMoved == IGNORES_AFK) {
|
||||
return 0L;
|
||||
}
|
||||
storeLastMovement(playerUUID, time);
|
||||
|
||||
try {
|
||||
if (time - lastMoved < getAfkThreshold()) {
|
||||
// Threshold not crossed, no action required.
|
||||
return 0L;
|
||||
}
|
||||
|
||||
long removeAfkCommandEffect = usedAFKCommand.contains(playerUUID) ? getAfkThreshold() : 0;
|
||||
long timeAFK = time - lastMoved - removeAfkCommandEffect;
|
||||
|
||||
GameSessionCache.getCacheSession(playerUUID)
|
||||
.ifPresent(gameSession -> gameSession.addAfkTime(timeAFK));
|
||||
return timeAFK;
|
||||
} finally {
|
||||
usedAFKCommand.remove(playerUUID);
|
||||
}
|
||||
}
|
||||
|
||||
public long loggedOut(UUID uuid, long time) {
|
||||
long timeAFK = performedAction(uuid, time);
|
||||
usedAFKCommand.remove(uuid);
|
||||
return timeAFK;
|
||||
}
|
||||
|
||||
public boolean isAfk(UUID playerUUID) {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
Optional<Long> lastMoved = getLastMovement(playerUUID);
|
||||
if (lastMoved.isEmpty() || lastMoved.get() == IGNORES_AFK) {
|
||||
return false;
|
||||
}
|
||||
return time - lastMoved.get() > getAfkThreshold();
|
||||
}
|
||||
}
|
@ -0,0 +1,260 @@
|
||||
package site.deercloud.identityverification.Controller;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import site.deercloud.identityverification.IdentityVerification;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ConfigManager {
|
||||
|
||||
public ConfigManager(IdentityVerification plugin) {
|
||||
this.plugin = plugin;
|
||||
reload();
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
plugin.saveDefaultConfig();
|
||||
plugin.reloadConfig();
|
||||
config = plugin.getConfig();
|
||||
|
||||
m_debug = config.getBoolean("Debug");
|
||||
m_ServerName = config.getString("Web.ServerName");
|
||||
m_ImplementationName = config.getString("Web.ImplementationName");
|
||||
m_ImplementationVersion = config.getString("Web.ImplementationVersion");
|
||||
m_HomePageUrl = config.getString("Web.HomePageUrl");
|
||||
m_RegisterUrl = m_HomePageUrl + "/register";
|
||||
m_WebHost = config.getString("Web.Host");
|
||||
m_WebPort = config.getInt("Web.Port");
|
||||
m_YagHost = config.getString("Yggdrasil.Host");
|
||||
m_YagPort = config.getInt("Yggdrasil.Port");
|
||||
m_SkinDomains = (ArrayList<String>) config.getStringList("Yggdrasil.SkinDomains");
|
||||
m_RsaPublicKeyFileName = config.getString("Yggdrasil.RsaPublicKey");
|
||||
m_RsaPrivateKeyFileName = config.getString("Yggdrasil.RsaPrivateKey");
|
||||
m_EmailHost = config.getString("Email.Host");
|
||||
m_EmailPort = config.getString("Email.Port");
|
||||
m_EmailUsername = config.getString("Email.Username");
|
||||
m_EmailPassword = config.getString("Email.Password");
|
||||
m_EmailFrom = config.getString("Email.From");
|
||||
// 加载配置文件夹下的RSA公钥 私钥
|
||||
File publicKeyFile = new File(plugin.getDataFolder(), m_RsaPublicKeyFileName);
|
||||
File privateKeyFile = new File(plugin.getDataFolder(), m_RsaPrivateKeyFileName);
|
||||
MyLogger.info("配置文件加载完成。");
|
||||
}
|
||||
|
||||
public void reload(CommandSender sender) {
|
||||
if (sender instanceof Player) {
|
||||
if (sender.isOp()) {
|
||||
reload();
|
||||
MyLogger.info(sender,"配置文件已重新加载。");
|
||||
} else {
|
||||
MyLogger.warn(sender,"尝试执行未授权的命令。");
|
||||
}
|
||||
} else {
|
||||
reload();
|
||||
MyLogger.info("配置文件已重新加载。");
|
||||
}
|
||||
}
|
||||
|
||||
public String getServerName() {
|
||||
return m_ServerName;
|
||||
}
|
||||
public void setServerName(String serverName) {
|
||||
m_ServerName = serverName;
|
||||
config.set("Web.ServerName", serverName);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
public String getImplementationName() {
|
||||
return m_ImplementationName;
|
||||
}
|
||||
public void setImplementationName(String implementationName) {
|
||||
m_ImplementationName = implementationName;
|
||||
config.set("Web.ImplementationName", implementationName);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
public String getImplementationVersion() {
|
||||
return m_ImplementationVersion;
|
||||
}
|
||||
public void setImplementationVersion(String implementationVersion) {
|
||||
m_ImplementationVersion = implementationVersion;
|
||||
config.set("Web.ImplementationVersion", implementationVersion);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
public String getHomePageUrl() {
|
||||
return m_HomePageUrl;
|
||||
}
|
||||
public void setHomePageUrl(String homePageUrl) {
|
||||
m_HomePageUrl = homePageUrl;
|
||||
config.set("Web.HomePageUrl", homePageUrl);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
public String getRegisterUrl() {
|
||||
return m_RegisterUrl;
|
||||
}
|
||||
|
||||
public String getWebHost() {
|
||||
return m_WebHost;
|
||||
}
|
||||
public void setWebHost(String host) {
|
||||
m_WebHost = host;
|
||||
config.set("Web.Host", host);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
public int getWebPort() {
|
||||
return m_WebPort;
|
||||
}
|
||||
public void setWebPort(int port) {
|
||||
m_WebPort = port;
|
||||
config.set("Web.Port", port);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
public String getYagHost() {
|
||||
return m_YagHost;
|
||||
}
|
||||
public void setYagHost(String host) {
|
||||
m_YagHost = host;
|
||||
config.set("Yggdrasil.Host", host);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
public int getYagPort() {
|
||||
return m_YagPort;
|
||||
}
|
||||
public void setYagPort(int port) {
|
||||
m_YagPort = port;
|
||||
config.set("Yggdrasil.Port", port);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
public ArrayList<String> getSkinDomains() {
|
||||
return m_SkinDomains;
|
||||
}
|
||||
public void setSkinDomains(ArrayList<String> skinDomains) {
|
||||
m_SkinDomains = skinDomains;
|
||||
config.set("Yggdrasil.SkinDomains", skinDomains);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
public String getSignaturePublicKey() {
|
||||
return m_SignaturePublicKey;
|
||||
}
|
||||
public void setSignaturePublicKey(String signaturePublicKey) {
|
||||
m_SignaturePublicKey = signaturePublicKey;
|
||||
config.set("Yggdrasil.SignaturePublicKey", signaturePublicKey);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
public void setPublicKeyFileName(String signaturePublicKey) {
|
||||
m_RsaPublicKeyFileName = signaturePublicKey;
|
||||
config.set("Yggdrasil.RsaPublicKey", signaturePublicKey);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
public String getPublicKeyFileName() {
|
||||
return m_RsaPublicKeyFileName;
|
||||
}
|
||||
|
||||
public String getSignaturePrivateKey() {
|
||||
return m_SignaturePrivateKey;
|
||||
}
|
||||
public void setSignaturePrivateKey(String signaturePrivateKey) {
|
||||
m_SignaturePrivateKey = signaturePrivateKey;
|
||||
config.set("Yggdrasil.SignaturePrivateKey", signaturePrivateKey);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
public void setPrivateKeyFileName(String signaturePrivateKey) {
|
||||
m_RsaPrivateKeyFileName = signaturePrivateKey;
|
||||
config.set("Yggdrasil.RsaPrivateKey", signaturePrivateKey);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
public String getPrivateKeyFileName() {
|
||||
return m_RsaPrivateKeyFileName;
|
||||
}
|
||||
|
||||
public Boolean getDebug() {
|
||||
return m_debug;
|
||||
}
|
||||
public void setDebug(Boolean debug) {
|
||||
m_debug = debug;
|
||||
config.set("Debug", debug);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
public String getEmailHost() {
|
||||
return m_EmailHost;
|
||||
}
|
||||
public void setEmailHost(String emailHost) {
|
||||
m_EmailHost = emailHost;
|
||||
config.set("Email.Host", emailHost);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
public String getEmailPort() {
|
||||
return m_EmailPort;
|
||||
}
|
||||
public void setEmailPort(String emailPort) {
|
||||
m_EmailPort = emailPort;
|
||||
config.set("Email.Port", emailPort);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
public String getEmailUsername() {
|
||||
return m_EmailUsername;
|
||||
}
|
||||
public void setEmailUsername(String emailUsername) {
|
||||
m_EmailUsername = emailUsername;
|
||||
config.set("Email.Username", emailUsername);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
public String getEmailPassword() {
|
||||
return m_EmailPassword;
|
||||
}
|
||||
public void setEmailPassword(String emailPassword) {
|
||||
m_EmailPassword = emailPassword;
|
||||
config.set("Email.Password", emailPassword);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
public String getEmailFrom() {
|
||||
return m_EmailFrom;
|
||||
}
|
||||
public void setEmailFrom(String emailFrom) {
|
||||
m_EmailFrom = emailFrom;
|
||||
config.set("Email.From", emailFrom);
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
private String m_ServerName;
|
||||
private String m_ImplementationName;
|
||||
private String m_ImplementationVersion;
|
||||
private String m_HomePageUrl;
|
||||
private String m_RegisterUrl;
|
||||
private String m_WebHost;
|
||||
private Integer m_WebPort;
|
||||
private String m_YagHost;
|
||||
private Integer m_YagPort;
|
||||
|
||||
private ArrayList<String> m_SkinDomains;
|
||||
private String m_RsaPublicKeyFileName;
|
||||
private String m_RsaPrivateKeyFileName;
|
||||
private String m_SignaturePublicKey;
|
||||
private String m_SignaturePrivateKey;
|
||||
private Boolean m_debug;
|
||||
|
||||
private String m_EmailHost;
|
||||
private String m_EmailPort;
|
||||
private String m_EmailUsername;
|
||||
private String m_EmailPassword;
|
||||
private String m_EmailFrom;
|
||||
|
||||
IdentityVerification plugin;
|
||||
FileConfiguration config;
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package site.deercloud.identityverification.Controller;
|
||||
|
||||
import site.deercloud.identityverification.HttpServer.model.EmailCode;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class EmailCodeCache {
|
||||
public static Map<String, EmailCode> EMAIL_CODE_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
public static void addEmailCode(String email, String code) {
|
||||
EMAIL_CODE_CACHE.put(email, new EmailCode(email, code));
|
||||
}
|
||||
|
||||
public static Optional<EmailCode> getEmailCode(String email) {
|
||||
return Optional.ofNullable(EMAIL_CODE_CACHE.get(email));
|
||||
}
|
||||
|
||||
public static void removeEmailCode(String email) {
|
||||
EMAIL_CODE_CACHE.remove(email);
|
||||
}
|
||||
|
||||
public static boolean isEmailCodeExpired(String email) {
|
||||
return getEmailCode(email).map(EmailCode::isExpired).orElse(true);
|
||||
}
|
||||
|
||||
public static boolean isEmailCodeValid(String email, String code) {
|
||||
return getEmailCode(email).map(emailCode -> emailCode.code.equals(code)).orElse(false);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package site.deercloud.identityverification.Controller;
|
||||
|
||||
|
||||
import site.deercloud.identityverification.HttpServer.model.GameSession;
|
||||
import site.deercloud.identityverification.IdentityVerification;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class GameSessionCache {
|
||||
private static final Map<UUID, GameSession> ACTIVE_SESSIONS = new ConcurrentHashMap<>();
|
||||
|
||||
public static Optional<GameSession> getCacheSession(UUID playerUUID) {
|
||||
return Optional.ofNullable(ACTIVE_SESSIONS.get(playerUUID));
|
||||
}
|
||||
|
||||
public static void addSession(UUID playerUUID) {
|
||||
ACTIVE_SESSIONS.put(playerUUID, new GameSession());
|
||||
}
|
||||
|
||||
public static void removeSession(UUID playerUUID) {
|
||||
// TODO: save to database
|
||||
|
||||
ACTIVE_SESSIONS.remove(playerUUID);
|
||||
}
|
||||
|
||||
public GameSessionCache(IdentityVerification plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private final IdentityVerification plugin;
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package site.deercloud.identityverification.Controller;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import site.deercloud.identityverification.HttpServer.model.InviteCode;
|
||||
import site.deercloud.identityverification.HttpServer.model.User;
|
||||
import site.deercloud.identityverification.SQLite.InviteCodeDAO;
|
||||
import site.deercloud.identityverification.SQLite.SqlManager;
|
||||
import site.deercloud.identityverification.SQLite.UserDAO;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
import site.deercloud.identityverification.Utils.RandomCode;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public class InviteCodeManager {
|
||||
// TODO: 邀请码管理器
|
||||
// TODO: 1.生成邀请码
|
||||
// TODO: 2.资格验证
|
||||
// TODO: 3.把邀请码验证功能从数据移到这里
|
||||
|
||||
public static void CreateCode(CommandSender sender) {
|
||||
String code = RandomCode.NewCodeWithAlphabet(8);
|
||||
if (sender instanceof Player) {
|
||||
if (sender.isOp()) {
|
||||
Connection connection = SqlManager.getConnection();
|
||||
try {
|
||||
User console = UserDAO.selectByEmail(connection, "console@mc.com");
|
||||
InviteCodeDAO.insert(connection, code, console.uuid, false, 0);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
MyLogger.info(sender, "邀请码为:" + code);
|
||||
} else {
|
||||
MyLogger.warn(sender,"你没有的达到申请邀请码的要求!");
|
||||
}
|
||||
} else {
|
||||
Connection connection = SqlManager.getConnection();
|
||||
try {
|
||||
User console = UserDAO.selectByEmail(connection, "console@mc.com");
|
||||
|
||||
InviteCodeDAO.insert(connection, code, console.uuid, false, 0);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
MyLogger.info("邀请码为:" + code);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateCode(User user) {
|
||||
|
||||
}
|
||||
|
||||
public static void ListCodesOf(CommandSender sender) {
|
||||
try {
|
||||
String uuid;
|
||||
Connection connection = SqlManager.getConnection();
|
||||
if (sender instanceof Player) {
|
||||
uuid = ((Player) sender).getUniqueId().toString();
|
||||
} else {
|
||||
uuid = Objects.requireNonNull(UserDAO.selectByEmail(connection, "console@mc.com")).uuid;
|
||||
}
|
||||
Set<InviteCode> codes = InviteCodeDAO.selectByInviter(connection, uuid);
|
||||
|
||||
sender.sendMessage("| 邀请码 | 创建时间 |");
|
||||
for (InviteCode code : codes) {
|
||||
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String date = sdf.format(code.createTime);
|
||||
sender.sendMessage("| " + code.code + " | " + date + " |");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package site.deercloud.identityverification;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerMoveEvent;
|
||||
import site.deercloud.identityverification.Controller.AFKTracker;
|
||||
import site.deercloud.identityverification.Controller.GameSessionCache;
|
||||
import site.deercloud.identityverification.SQLite.BanListDAO;
|
||||
import site.deercloud.identityverification.SQLite.SqlManager;
|
||||
import site.deercloud.identityverification.SQLite.WhiteListDAO;
|
||||
import site.deercloud.identityverification.Controller.ConfigManager;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
public class Events implements Listener {
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) throws SQLException {
|
||||
Player player = event.getPlayer();
|
||||
String uuid = player.getUniqueId().toString();
|
||||
Connection connection = SqlManager.getConnection();
|
||||
if (!WhiteListDAO.isUuidInWhiteList(connection, uuid)) {
|
||||
player.kickPlayer("你没有完成白名单实名认证,请前往 " + configManager.getHomePageUrl() + " 进行认证。");
|
||||
}
|
||||
Integer ban_record_id = BanListDAO.isBanned(connection, uuid);
|
||||
if (ban_record_id > 0) {
|
||||
String ban_reason = BanListDAO.getBanReasonById(connection, ban_record_id);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String ban_time = sdf.format(BanListDAO.getBanTimeById(connection, ban_record_id));
|
||||
player.kickPlayer("你已被封禁,请联系管理员。原因:[ " + ban_reason + " ]" + " 至:" + ban_time);
|
||||
}
|
||||
GameSessionCache.addSession(event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerJoinEvent event) throws SQLException {
|
||||
GameSessionCache.removeSession(event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerMove(PlayerMoveEvent event) throws SQLException {
|
||||
Player player = event.getPlayer();
|
||||
IdentityVerification.getAfkTracker().performedAction(player.getUniqueId(), System.currentTimeMillis());
|
||||
}
|
||||
|
||||
ConfigManager configManager = IdentityVerification.getInstance().getConfigManager();
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package site.deercloud.identityverification.HttpServer.Api;
|
||||
|
||||
import com.alibaba.fastjson.*;
|
||||
import com.sun.net.httpserver.*;
|
||||
import site.deercloud.identityverification.IdentityVerification;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import static site.deercloud.identityverification.HttpServer.HttpServerManager.jsonResponse;
|
||||
import static site.deercloud.identityverification.Utils.Utils.*;
|
||||
|
||||
public class Ban implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange){
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("POST")){
|
||||
jsonResponse(exchange, 405, "Method Not Allowed", null);
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject jsonObject = JSONObject.parseObject(exchange.getRequestBody().toString());
|
||||
String uuid = jsonObject.getString("uuid");
|
||||
String reason = jsonObject.getString("reason");
|
||||
Integer time = jsonObject.getInteger("time");
|
||||
Boolean with_inviter = jsonObject.getBoolean("with_inviter");
|
||||
|
||||
} catch (IOException e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package site.deercloud.identityverification.HttpServer.Api;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.IdentityVerification;
|
||||
import site.deercloud.identityverification.SQLite.InviteRelationDAO;
|
||||
import site.deercloud.identityverification.SQLite.UserDAO;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
import static site.deercloud.identityverification.HttpServer.HttpServerManager.jsonResponse;
|
||||
import static site.deercloud.identityverification.SQLite.SqlManager.getConnection;
|
||||
import static site.deercloud.identityverification.Utils.Utils.*;
|
||||
|
||||
public class GetInviter implements HttpHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) {
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("GET")) {
|
||||
jsonResponse(exchange, 405, "Method Not Allowed", null);
|
||||
return;
|
||||
}
|
||||
|
||||
String query = exchange.getRequestURI().getQuery();
|
||||
Map<String, String> params = ParseQueryString(query);
|
||||
|
||||
Connection connection = getConnection();
|
||||
String uuid = params.get("uuid");
|
||||
String inviter_uuid = InviteRelationDAO.getInviterUUID(connection, uuid);
|
||||
if (inviter_uuid == null) {
|
||||
jsonResponse(exchange, 400, "找不到邀请者,这可能是个正版玩家。", null);
|
||||
}
|
||||
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("inviter_uuid", inviter_uuid);
|
||||
jsonObject.put("inviter_name", UserDAO.selectByUuid(connection, inviter_uuid).uuid);
|
||||
|
||||
jsonResponse(exchange, 200, "OK", jsonObject);
|
||||
|
||||
} catch (IOException | SQLException e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package site.deercloud.identityverification.HttpServer.Api;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.HttpServer.model.Profile;
|
||||
import site.deercloud.identityverification.HttpServer.model.Response;
|
||||
import site.deercloud.identityverification.SQLite.ProfileDAO;
|
||||
import site.deercloud.identityverification.SQLite.SqlManager;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class GetProfiles implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) {
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("POST")) {
|
||||
Response.err_method_not_allowed(exchange);
|
||||
return;
|
||||
}
|
||||
JSONArray profiles_json = JSONArray.parseArray(exchange.getRequestBody().toString());
|
||||
if (profiles_json.size() > 5) {
|
||||
Response.success_no_content(exchange);
|
||||
return;
|
||||
}
|
||||
Connection connection = SqlManager.getConnection();
|
||||
ArrayList<Profile> profiles = new ArrayList<>();
|
||||
for (int i = 0; i < profiles_json.size(); i++) {
|
||||
Profile profile = ProfileDAO.selectByName(connection, profiles_json.getString(i));
|
||||
if (profile != null) {
|
||||
profiles.add(profile);
|
||||
}
|
||||
}
|
||||
JSONArray response = new JSONArray();
|
||||
for (Profile profile : profiles) {
|
||||
response.add(profile.serialToJSONObject(false, false));
|
||||
}
|
||||
Response.success_200(exchange, response);
|
||||
|
||||
} catch (IOException | SQLException e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package site.deercloud.identityverification.HttpServer.Api;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.HttpServer.model.Response;
|
||||
import site.deercloud.identityverification.HttpServer.model.User;
|
||||
import site.deercloud.identityverification.SQLite.SqlManager;
|
||||
import site.deercloud.identityverification.SQLite.UserDAO;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static site.deercloud.identityverification.HttpServer.HttpServerManager.jsonResponse;
|
||||
|
||||
public class Login implements HttpHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("POST")) {
|
||||
Response.err_method_not_allowed(exchange);
|
||||
return;
|
||||
}
|
||||
JSONObject request = JSONObject.parseObject(exchange.getRequestBody().toString());
|
||||
String username = request.getString("username");
|
||||
String password = request.getString("password");
|
||||
|
||||
if (username == null || password == null) {
|
||||
jsonResponse(exchange, 500, "用户名或密码不能为空", null);
|
||||
return;
|
||||
}
|
||||
|
||||
Connection connection = SqlManager.getConnection();
|
||||
User user = UserDAO.selectByEmail(connection, username);
|
||||
if (user == null) {
|
||||
jsonResponse(exchange, 500, "用户不存在", null);
|
||||
return;
|
||||
}
|
||||
if (!user.password.equals(password)) {
|
||||
jsonResponse(exchange, 500, "用户名或密码错误", null);
|
||||
return;
|
||||
}
|
||||
// TODO: 2021/1/17 生成令牌
|
||||
|
||||
|
||||
} catch (IOException | SQLException e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package site.deercloud.identityverification.HttpServer.Api;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static site.deercloud.identityverification.HttpServer.HttpServerManager.jsonResponse;
|
||||
|
||||
public class MetaData implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange){
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("GET")){
|
||||
exchange.sendResponseHeaders(405, 0);
|
||||
exchange.close();
|
||||
return;
|
||||
}
|
||||
String title = "A Minecraft Server";
|
||||
String description = "Minecraft is a 3D sandbox game developed by Mojang Studios where players interact with a fully modifiable three-dimensional environment made of blocks and entities. ";
|
||||
Boolean use_yag = true; // 是否使用外置登录
|
||||
Boolean use_genuine = true; // 是否使用正版验证
|
||||
Boolean need_invite = true; // 是否需要邀请码
|
||||
Boolean use_whitelist = true; // 是否使用实名认证白名单
|
||||
|
||||
JSONArray links = new JSONArray();
|
||||
JSONObject link = new JSONObject();
|
||||
link.put("key", "author");
|
||||
link.put("value", "https://blog.deercloud.site");
|
||||
links.add(link);
|
||||
link.put("key", "github");
|
||||
link.put("value", "https://github.com/ColdeZhang");
|
||||
links.add(link);
|
||||
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("title", title);
|
||||
jsonObject.put("description", description);
|
||||
jsonObject.put("links", links);
|
||||
jsonObject.put("use_yag", use_yag);
|
||||
jsonObject.put("use_genuine", use_genuine);
|
||||
jsonObject.put("need_invite", need_invite);
|
||||
jsonObject.put("use_whitelist", use_whitelist);
|
||||
|
||||
jsonResponse(exchange, 200, "OK", jsonObject);
|
||||
} catch (IOException e) {
|
||||
exchange.close();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package site.deercloud.identityverification.HttpServer.Api.Register;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.net.httpserver.*;
|
||||
import site.deercloud.identityverification.IdentityVerification;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import static site.deercloud.identityverification.HttpServer.HttpServerManager.jsonResponse;
|
||||
import static site.deercloud.identityverification.Utils.Utils.*;
|
||||
|
||||
public class GetOnlineProfile implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange){
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("GET")){
|
||||
jsonResponse(exchange, 405, "Method Not Allowed", null);
|
||||
return;
|
||||
}
|
||||
|
||||
String query = exchange.getRequestURI().getQuery();
|
||||
Map<String, String> params = ParseQueryString(query);
|
||||
|
||||
String name = params.get("name");
|
||||
String UUID = getUUIDFromRemote(name);
|
||||
if (UUID == null){
|
||||
jsonResponse(exchange, 400, "ID不存在,请检查输入。", null);
|
||||
return;
|
||||
}
|
||||
JSONObject profile = getProfileFromRemote(UUID);
|
||||
|
||||
jsonResponse(exchange, 200, "获取正版信息成功!", profile);
|
||||
|
||||
} catch (IOException e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package site.deercloud.identityverification.HttpServer.Api.Register;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.Controller.EmailCodeCache;
|
||||
import site.deercloud.identityverification.HttpServer.model.Profile;
|
||||
import site.deercloud.identityverification.HttpServer.model.Texture;
|
||||
import site.deercloud.identityverification.HttpServer.model.User;
|
||||
import site.deercloud.identityverification.SQLite.InviteCodeDAO;
|
||||
import site.deercloud.identityverification.SQLite.InviteRelationDAO;
|
||||
import site.deercloud.identityverification.SQLite.ProfileDAO;
|
||||
import site.deercloud.identityverification.SQLite.UserDAO;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.UUID;
|
||||
|
||||
import static site.deercloud.identityverification.HttpServer.HttpServerManager.jsonResponse;
|
||||
import static site.deercloud.identityverification.SQLite.SqlManager.getConnection;
|
||||
import static site.deercloud.identityverification.Utils.Utils.*;
|
||||
|
||||
public class Registration implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange){
|
||||
try {
|
||||
// 设置响应头
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("POST")){
|
||||
jsonResponse(exchange, 405, "Method Not Allowed", null);
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject jsonObject = JSONObject.parseObject(exchange.getRequestBody().toString());
|
||||
|
||||
String email = jsonObject.getString("email");
|
||||
String password = jsonObject.getString("password");
|
||||
String active_code = jsonObject.getString("email_code"); // 邮箱验证码
|
||||
|
||||
String inviteCode = jsonObject.getString("invite_code");
|
||||
|
||||
String profile_name = jsonObject.getString("profile_name");
|
||||
|
||||
if (EmailCodeCache.isEmailCodeExpired(email)) {
|
||||
jsonResponse(exchange, 500, "验证码无效,请重新获取。", null);
|
||||
return;
|
||||
}
|
||||
if (!EmailCodeCache.isEmailCodeValid(email, active_code)) {
|
||||
jsonResponse(exchange, 500, "验证码错误!", null);
|
||||
return;
|
||||
}
|
||||
if (getUUIDFromRemote(profile_name) != null){
|
||||
jsonResponse(exchange, 400, "此昵称已有正版玩家使用,为避免ID碰撞请改名。", null);
|
||||
return;
|
||||
}
|
||||
Connection connection = getConnection();
|
||||
if (!InviteCodeDAO.isValid(connection, inviteCode)) {
|
||||
jsonResponse(exchange, 400, "邀请码不存在或已被使用!", null);
|
||||
return;
|
||||
}
|
||||
String new_uuid = UUID.randomUUID().toString();
|
||||
String inviteCodeOwner = InviteCodeDAO.getInviterUUID(connection, inviteCode);
|
||||
// 创建邀请关系
|
||||
InviteRelationDAO.insert(connection, new_uuid, inviteCodeOwner, System.currentTimeMillis());
|
||||
// 标记邀请码已使用
|
||||
InviteCodeDAO.setUsed(connection, inviteCode, true, System.currentTimeMillis());
|
||||
|
||||
User user = new User();
|
||||
user.uuid = new_uuid;
|
||||
user.email = email;
|
||||
user.password = password;
|
||||
UserDAO.insert(connection, user);
|
||||
|
||||
Profile profile = new Profile();
|
||||
profile.name = profile_name;
|
||||
profile.uuid = UUID.randomUUID().toString();
|
||||
profile.belongTo = user.uuid;
|
||||
Texture texture = Texture.newDefault(profile.uuid);
|
||||
profile.textures = texture.serialWithBase64();
|
||||
profile.textures_signature = texture.sign();
|
||||
ProfileDAO.insert(connection, profile);
|
||||
|
||||
jsonResponse(exchange, 200, "注册成功!", null);
|
||||
} catch (Exception e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package site.deercloud.identityverification.HttpServer.Api.Register;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.Controller.EmailCodeCache;
|
||||
import site.deercloud.identityverification.Utils.EmailSender;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
import site.deercloud.identityverification.Utils.RandomCode;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static site.deercloud.identityverification.HttpServer.HttpServerManager.jsonResponse;
|
||||
|
||||
public class SendEmailCode implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) {
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("POST")){
|
||||
jsonResponse(exchange, 405, "Method Not Allowed", null);
|
||||
return;
|
||||
}
|
||||
JSONObject request = JSONObject.parseObject(exchange.getRequestBody().toString());
|
||||
String email = request.getString("email");
|
||||
|
||||
if (!EmailCodeCache.isEmailCodeExpired(email)) {
|
||||
jsonResponse(exchange, 500, "禁止频繁操作!", null);
|
||||
return;
|
||||
}
|
||||
|
||||
String code = RandomCode.NewCodeOnlyNumber(6);
|
||||
EmailSender.sendCodeEmail(email, code);
|
||||
EmailCodeCache.addEmailCode(email, code);
|
||||
|
||||
jsonResponse(exchange, 200, "发送成功,请在五分钟内完成注册。", null);
|
||||
} catch (IOException e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package site.deercloud.identityverification.HttpServer.Api.Register;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.IdentityVerification;
|
||||
import site.deercloud.identityverification.SQLite.InviteCodeDAO;
|
||||
import site.deercloud.identityverification.SQLite.SqlManager;
|
||||
import site.deercloud.identityverification.SQLite.UserDAO;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static site.deercloud.identityverification.HttpServer.HttpServerManager.jsonResponse;
|
||||
import static site.deercloud.identityverification.Utils.Utils.ParseQueryString;
|
||||
|
||||
public class VerifyCode implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange){
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("GET")){
|
||||
jsonResponse(exchange, 405, "Method Not Allowed", null);
|
||||
return;
|
||||
}
|
||||
String query = exchange.getRequestURI().getQuery();
|
||||
Map<String, String> params = ParseQueryString(query);
|
||||
String code = params.get("code");
|
||||
Connection conn = SqlManager.getConnection();
|
||||
|
||||
if (!InviteCodeDAO.isValid(conn, code)){
|
||||
jsonResponse(exchange, 400, "邀请码不可用!", null);
|
||||
} else {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String inviter_uuid = InviteCodeDAO.getInviterUUID(conn, code);
|
||||
String inviter_name = UserDAO.selectByUuid(conn, inviter_uuid).email;
|
||||
// 返回邀请者的名字
|
||||
jsonObject.put("inviter_name", inviter_name);
|
||||
jsonResponse(exchange, 200, "OK", jsonObject);
|
||||
}
|
||||
} catch (IOException | SQLException e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package site.deercloud.identityverification.HttpServer.Api;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.HttpServer.model.Profile;
|
||||
import site.deercloud.identityverification.IdentityVerification;
|
||||
import site.deercloud.identityverification.SQLite.ProfileDAO;
|
||||
import site.deercloud.identityverification.SQLite.UserDAO;
|
||||
import site.deercloud.identityverification.SQLite.WhiteListDAO;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.util.Objects;
|
||||
|
||||
import static site.deercloud.identityverification.HttpServer.HttpServerManager.jsonResponse;
|
||||
import static site.deercloud.identityverification.SQLite.SqlManager.getConnection;
|
||||
import static site.deercloud.identityverification.Utils.Utils.*;
|
||||
|
||||
public class SignWhiteList implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("POST")){
|
||||
jsonResponse(exchange, 405, "Method Not Allowed", null);
|
||||
return;
|
||||
}
|
||||
JSONObject jsonObject = JSONObject.parseObject(exchange.getRequestBody().toString());
|
||||
Connection connection = getConnection();
|
||||
|
||||
String id = jsonObject.getString("id"); // 身份证(实名认证用)
|
||||
String name = jsonObject.getString("name"); // 真实姓名(实名认证用)
|
||||
String profile_name = jsonObject.getString("username"); // 昵称
|
||||
Boolean is_genuine = jsonObject.getBoolean("is_genuine"); // 是否正版
|
||||
String uuid;
|
||||
if (is_genuine) {
|
||||
// 正版玩家 - 从Mojiang服务器获取uuid
|
||||
uuid = getUUIDFromRemote(profile_name);
|
||||
} else {
|
||||
// 离线玩家 - 从本地数据库获取uuid
|
||||
Profile profile = ProfileDAO.selectByName(connection, profile_name);
|
||||
if (profile == null) {
|
||||
jsonResponse(exchange, 400, "玩家不存在", null);
|
||||
return;
|
||||
}
|
||||
uuid = UserDAO.selectByUuid(connection, profile.belongTo).uuid;
|
||||
}
|
||||
if (uuid == null) {
|
||||
jsonResponse(exchange, 400, "UUID校验不通过,请检查后输入。", null);
|
||||
return;
|
||||
}
|
||||
if (WhiteListDAO.isIdInWhiteList(connection, id.hashCode())) {
|
||||
jsonResponse(exchange, 400, "请勿重复添加白名单,本服一人一号。", null);
|
||||
return;
|
||||
}
|
||||
// TODO: 调实名认证接口 不通过直接返回错误
|
||||
//
|
||||
//
|
||||
WhiteListDAO.insert(connection, uuid, is_genuine, id.hashCode());
|
||||
jsonResponse(exchange, 200, "添加白名单成功!", null);
|
||||
} catch (Exception e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package site.deercloud.identityverification.HttpServer.Api;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Test implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange httpExchange) throws IOException {
|
||||
/*
|
||||
* PS: 必须按顺序设置响应: 添加响应头, 发送响应码和内容长度, 写出响应内容, 关闭处理器
|
||||
*/
|
||||
// 响应内容
|
||||
byte[] respContents = "Hello World".getBytes("UTF-8");
|
||||
|
||||
// 设置响应头
|
||||
httpExchange.getResponseHeaders().add("Content-Type", "text/html; charset=UTF-8");
|
||||
// 设置响应code和内容长度
|
||||
httpExchange.sendResponseHeaders(200, respContents.length);
|
||||
// 设置响应内容
|
||||
httpExchange.getResponseBody().write(respContents);
|
||||
|
||||
// 关闭处理器, 同时将关闭请求和响应的输入输出流(如果还没关闭)
|
||||
httpExchange.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package site.deercloud.identityverification.HttpServer;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import site.deercloud.identityverification.HttpServer.Api.Register.GetOnlineProfile;
|
||||
import site.deercloud.identityverification.HttpServer.Api.Register.Registration;
|
||||
import site.deercloud.identityverification.HttpServer.Api.Register.VerifyCode;
|
||||
import site.deercloud.identityverification.HttpServer.Web.*;
|
||||
import site.deercloud.identityverification.HttpServer.Api.*;
|
||||
import site.deercloud.identityverification.HttpServer.Yggdrasil.MetaData;
|
||||
import site.deercloud.identityverification.HttpServer.Yggdrasil.authserver.*;
|
||||
import site.deercloud.identityverification.HttpServer.Yggdrasil.sessionserver.GetProfile;
|
||||
import site.deercloud.identityverification.HttpServer.Yggdrasil.sessionserver.HasJoined;
|
||||
import site.deercloud.identityverification.HttpServer.Yggdrasil.sessionserver.Join;
|
||||
import site.deercloud.identityverification.HttpServer.Yggdrasil.sessionserver.SessionTokenCache;
|
||||
import site.deercloud.identityverification.IdentityVerification;
|
||||
import site.deercloud.identityverification.Controller.ConfigManager;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class HttpServerManager {
|
||||
|
||||
public HttpServerManager(IdentityVerification plugin) {
|
||||
configManager = plugin.getConfigManager();
|
||||
// 注册路由
|
||||
try {
|
||||
|
||||
String web_host = configManager.getWebHost();
|
||||
int web_port = configManager.getWebPort();
|
||||
String yag_host = configManager.getYagHost();
|
||||
int yag_port = configManager.getYagPort();
|
||||
|
||||
webServer = HttpServer.create(new InetSocketAddress(web_host, web_port), 0);
|
||||
yagServer = HttpServer.create(new InetSocketAddress(yag_host, yag_port), 0);
|
||||
|
||||
webServer.createContext("/", new WebServer());
|
||||
|
||||
webServer.createContext("/api/test", new Test());
|
||||
// 获取正版用户信息
|
||||
webServer.createContext("/api/getOnlineProfile", new GetOnlineProfile());
|
||||
// 验证邀请码 返回邀请者信息用于确认
|
||||
webServer.createContext("/api/verifyCode", new VerifyCode());
|
||||
// 封禁玩家
|
||||
webServer.createContext("/api/ban", new Ban());
|
||||
// 离线用户注册
|
||||
webServer.createContext("/api/registration", new Registration());
|
||||
// 获取邀请者信息
|
||||
webServer.createContext("/api/getInviter", new GetInviter());
|
||||
// 登记白名单
|
||||
webServer.createContext("/api/signWhiteList", new SignWhiteList());
|
||||
|
||||
// Yggdrasil API 元数据
|
||||
yagServer.createContext("/", new MetaData());
|
||||
// 登录 - 使用密码进行身份验证,并分配一个新的令牌
|
||||
yagServer.createContext("/authserver/authenticate", new Authenticate());
|
||||
// 刷新 - 吊销原令牌,并颁发一个新的令牌
|
||||
yagServer.createContext("/authserver/refresh", new Refresh());
|
||||
// 验证 - 验证令牌是否有效
|
||||
yagServer.createContext("/authserver/validate", new Validate());
|
||||
// 吊销 - 吊销令牌
|
||||
yagServer.createContext("/authserver/invalidate", new Invalidate());
|
||||
|
||||
// 客户端进入服务器 - 客户端使用令牌加入游戏
|
||||
yagServer.createContext("/sessionserver/session/minecraft/join", new Join());
|
||||
// 服务端验证客户端 - 服务端使用令牌验证客户端
|
||||
yagServer.createContext("/sessionserver/session/minecraft/hasJoined", new HasJoined());
|
||||
// 查询角色属性 - 查询指定的角色属性
|
||||
yagServer.createContext("/sessionserver/session/minecraft/profile/", new GetProfile());
|
||||
// 按名称批量查询角色 - 不包含属性 最大5
|
||||
yagServer.createContext("/api/profiles/minecraft", new GetProfiles());
|
||||
} catch (IOException e) {
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
this.startHttpServer();
|
||||
}
|
||||
|
||||
public void startHttpServer(){
|
||||
// 设置线程池
|
||||
Executor executor = new ThreadPoolExecutor(10, 20, 60, TimeUnit.SECONDS, new java.util.concurrent.ArrayBlockingQueue<>(10));
|
||||
webServer.setExecutor(executor);
|
||||
yagServer.setExecutor(executor);
|
||||
webServer.start();
|
||||
yagServer.start();
|
||||
MyLogger.info("网页服务启动在: " + configManager.getWebHost() + ":" + configManager.getWebPort());
|
||||
MyLogger.info("外置登录启动在: " + configManager.getYagHost() + ":" + configManager.getYagPort());
|
||||
}
|
||||
|
||||
public void stopHttpServer(){
|
||||
if (webServer != null){
|
||||
webServer.stop(0);
|
||||
}
|
||||
if (yagServer != null){
|
||||
yagServer.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void jsonResponse(HttpExchange exchange, int code, String message, JSONObject data) throws IOException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("code", code);
|
||||
jsonObject.put("msg", message);
|
||||
jsonObject.put("data", data);
|
||||
exchange.sendResponseHeaders(200, jsonObject.toString().getBytes().length);
|
||||
exchange.getResponseBody().write(jsonObject.toString().getBytes());
|
||||
exchange.getResponseBody().close();
|
||||
}
|
||||
|
||||
public static SessionTokenCache getSessionCache() {
|
||||
return sessionTokenCache;
|
||||
}
|
||||
|
||||
private HttpServer webServer;
|
||||
private HttpServer yagServer;
|
||||
private ConfigManager configManager;
|
||||
private static SessionTokenCache sessionTokenCache = new SessionTokenCache();
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package site.deercloud.identityverification.HttpServer.Web;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.IdentityVerification;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class WebServer implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) {
|
||||
try {
|
||||
String uri = exchange.getRequestURI().toString();
|
||||
if (uri.equals("/")){
|
||||
uri = "/index.html";
|
||||
}
|
||||
String file_type = uri.substring(uri.lastIndexOf(".") + 1);
|
||||
|
||||
InputStream file = IdentityVerification.getInstance().getResource("web" + uri);
|
||||
if (file == null){
|
||||
MyLogger.debug("File not found: " + uri);
|
||||
exchange.sendResponseHeaders(404, 0);
|
||||
exchange.close();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (file_type) {
|
||||
case "js" -> exchange.getResponseHeaders().add("Content-Type", "application/javascript; charset=UTF-8");
|
||||
case "css" -> exchange.getResponseHeaders().add("Content-Type", "text/css; charset=UTF-8");
|
||||
case "html" -> exchange.getResponseHeaders().add("Content-Type", "text/html; charset=UTF-8");
|
||||
case "png" -> exchange.getResponseHeaders().add("Content-Type", "image/png; charset=UTF-8");
|
||||
case "jpg" -> exchange.getResponseHeaders().add("Content-Type", "image/jpeg; charset=UTF-8");
|
||||
case "gif" -> exchange.getResponseHeaders().add("Content-Type", "image/gif; charset=UTF-8");
|
||||
default -> exchange.getResponseHeaders().add("Content-Type", "text/plain; charset=UTF-8");
|
||||
}
|
||||
|
||||
exchange.sendResponseHeaders(200, 0);
|
||||
exchange.getResponseBody().write(file.readAllBytes());
|
||||
exchange.close();
|
||||
} catch (IOException e) {
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package site.deercloud.identityverification.HttpServer.Web;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.IdentityVerification;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class WebTest implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange){
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "text/html; charset=UTF-8");
|
||||
String uri = exchange.getRequestURI().toString();
|
||||
// 按照uri从resource中读取文件返回
|
||||
|
||||
InputStream file = IdentityVerification.getInstance().getResource("web" + uri);
|
||||
if (file == null){
|
||||
MyLogger.debug("File not found: " + uri);
|
||||
exchange.sendResponseHeaders(404, 0);
|
||||
exchange.close();
|
||||
return;
|
||||
}
|
||||
exchange.sendResponseHeaders(200, 0);
|
||||
exchange.getResponseBody().write(file.readAllBytes());
|
||||
exchange.close();
|
||||
} catch (IOException e) {
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package site.deercloud.identityverification.HttpServer.Yggdrasil;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.HttpServer.model.Response;
|
||||
import site.deercloud.identityverification.IdentityVerification;
|
||||
import site.deercloud.identityverification.Controller.ConfigManager;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MetaData implements HttpHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("GET")) {
|
||||
Response.err_method_not_allowed(exchange);
|
||||
return;
|
||||
}
|
||||
ConfigManager configManager = IdentityVerification.getInstance().getConfigManager();
|
||||
JSONObject meta_json = new JSONObject();
|
||||
meta_json.put("serverName", configManager.getServerName());
|
||||
meta_json.put("implementationName", configManager.getImplementationName());
|
||||
meta_json.put("implementationVersion", configManager.getImplementationVersion());
|
||||
JSONObject links_json = new JSONObject();
|
||||
links_json.put("homepage", configManager.getHomePageUrl());
|
||||
links_json.put("register", configManager.getRegisterUrl());
|
||||
meta_json.put("links", links_json);
|
||||
JsonArray skinDomains_json = new JsonArray();
|
||||
for (String domain : configManager.getSkinDomains()) {
|
||||
skinDomains_json.add(domain);
|
||||
}
|
||||
JSONObject response_json = new JSONObject();
|
||||
response_json.put("meta", meta_json);
|
||||
response_json.put("skinDomains", skinDomains_json);
|
||||
response_json.put("signaturePublickey", configManager.getSignaturePublicKey());
|
||||
Response.success_200(exchange, response_json);
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package site.deercloud.identityverification.HttpServer.Yggdrasil.authserver;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.HttpServer.model.Profile;
|
||||
import site.deercloud.identityverification.HttpServer.model.Response;
|
||||
import site.deercloud.identityverification.HttpServer.model.Token;
|
||||
import site.deercloud.identityverification.HttpServer.model.User;
|
||||
import site.deercloud.identityverification.SQLite.ProfileDAO;
|
||||
import site.deercloud.identityverification.SQLite.TokenDAO;
|
||||
import site.deercloud.identityverification.SQLite.UserDAO;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
import static site.deercloud.identityverification.SQLite.SqlManager.getConnection;
|
||||
import static site.deercloud.identityverification.HttpServer.model.Response.*;
|
||||
|
||||
public class Authenticate implements HttpHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange exchange){
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("POST")){
|
||||
Response.err_method_not_allowed(exchange);
|
||||
return;
|
||||
}
|
||||
JSONObject body = JSONObject.parseObject(exchange.getRequestBody().toString());
|
||||
String username = body.getString("username"); // 邮箱(或其他凭证)
|
||||
String password = body.getString("password"); // 密码
|
||||
String clientToken = body.getString("clientToken"); // 由客户端指定的令牌的 clientToken(可选)
|
||||
Boolean requestUser = body.getBoolean("requestUser"); // 是否在响应中包含用户信息,默认 false
|
||||
JSONObject agent = body.getJSONObject("agent");
|
||||
String agent_name = agent.getString("name");
|
||||
String agent_version = agent.getString("version");
|
||||
|
||||
Connection connection = getConnection();
|
||||
|
||||
if (!UserDAO.checkPassword(connection, username, password)) {
|
||||
err_password_wrong(exchange, "密码错误,或短时间内多次登录失败而被暂时禁止登录");
|
||||
}
|
||||
User user = UserDAO.selectByEmail(connection, username);
|
||||
|
||||
if (clientToken == null) {
|
||||
clientToken = UUID.randomUUID().toString();
|
||||
}
|
||||
String accessToken = UUID.randomUUID().toString();
|
||||
|
||||
// 颁发令牌
|
||||
Token token = new Token();
|
||||
token.accessToken = accessToken;
|
||||
token.clientToken = clientToken;
|
||||
token.userUUID = user.uuid;
|
||||
token.expiresAt = System.currentTimeMillis() + 432000000;
|
||||
|
||||
Profile profile = null;
|
||||
// 若用户没有任何角色,则为空;
|
||||
// 若用户仅有一个角色,那么通常绑定到该角色;
|
||||
// 若用户有多个角色,通常为空,以便客户端进行选择。也就是说如果绑定的角色为空,则需要客户端进行角色选择。
|
||||
ArrayList<Profile> profiles = ProfileDAO.selectAllByBelongTo(connection, user.uuid);
|
||||
if (profiles.size() == 1) {
|
||||
profile = profiles.get(0);
|
||||
token.profileUUID = profile.uuid;
|
||||
} else {
|
||||
token.profileUUID = "";
|
||||
}
|
||||
TokenDAO.insert(connection, token);
|
||||
|
||||
JSONArray availableProfiles = new JSONArray();
|
||||
for (Profile p : profiles) {
|
||||
availableProfiles.add(p.serialToJSONObject(false, true));
|
||||
}
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("accessToken", accessToken);
|
||||
response.put("clientToken", clientToken);
|
||||
response.put("availableProfiles", availableProfiles);
|
||||
if (profile != null) {
|
||||
response.put("selectedProfile", profile.serialToJSONObject(false, true));
|
||||
}
|
||||
if (requestUser) {
|
||||
response.put("user", user.serialToJSONObject());
|
||||
}
|
||||
Response.success_200(exchange, response);
|
||||
} catch (IOException | SQLException e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package site.deercloud.identityverification.HttpServer.Yggdrasil.authserver;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.HttpServer.model.Response;
|
||||
import site.deercloud.identityverification.SQLite.SqlManager;
|
||||
import site.deercloud.identityverification.SQLite.TokenDAO;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class Invalidate implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) {
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("POST")) {
|
||||
Response.err_method_not_allowed(exchange);
|
||||
return;
|
||||
}
|
||||
JSONObject request = JSONObject.parseObject(exchange.getRequestBody().toString());
|
||||
String accessToken = request.getString("accessToken");
|
||||
String clientToken = request.getString("clientToken");
|
||||
|
||||
Connection connection = SqlManager.getConnection();
|
||||
|
||||
TokenDAO.deleteByAccessToken(connection, accessToken);
|
||||
|
||||
Response.success_no_content(exchange);
|
||||
} catch (IOException | SQLException e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package site.deercloud.identityverification.HttpServer.Yggdrasil.authserver;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.HttpServer.model.Response;
|
||||
import site.deercloud.identityverification.HttpServer.model.Token;
|
||||
import site.deercloud.identityverification.HttpServer.model.User;
|
||||
import site.deercloud.identityverification.SQLite.SqlManager;
|
||||
import site.deercloud.identityverification.SQLite.TokenDAO;
|
||||
import site.deercloud.identityverification.SQLite.UserDAO;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Refresh implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) {
|
||||
try {
|
||||
// 设置响应头
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("POST")) {
|
||||
Response.err_method_not_allowed(exchange);
|
||||
return;
|
||||
}
|
||||
Connection connection = SqlManager.getConnection();
|
||||
JSONObject body = JSONObject.parseObject(exchange.getRequestBody().toString());
|
||||
String accessToken = body.getString("accessToken");
|
||||
String clientToken = body.getString("clientToken");
|
||||
Boolean requestUser = body.getBoolean("requestUser");
|
||||
|
||||
if (TokenDAO.isAccessTokenExpired(connection, accessToken)) {
|
||||
Response.err_invalid_token(exchange, "无效的令牌", "无效的令牌");
|
||||
return;
|
||||
}
|
||||
if (clientToken != null && TokenDAO.isClientTokenExpired(connection, clientToken)) {
|
||||
Response.err_invalid_token(exchange, "无效的令牌", "无效的令牌");
|
||||
return;
|
||||
}
|
||||
Token new_token = TokenDAO.selectByAccessToken(connection, accessToken);
|
||||
if (new_token == null) {
|
||||
Response.err_invalid_token(exchange, "无效的令牌", "无效的令牌");
|
||||
return;
|
||||
}
|
||||
|
||||
// 删除原先的令牌 并生成新的令牌
|
||||
TokenDAO.deleteByAccessToken(connection, accessToken);
|
||||
new_token.accessToken = UUID.randomUUID().toString();
|
||||
new_token.expiresAt = System.currentTimeMillis() + 432000000;
|
||||
// 选择角色的操作 要求原令牌所绑定的角色为空
|
||||
if (!body.getJSONObject("selectedProfile").isEmpty()) {
|
||||
if (!Objects.equals(new_token.profileUUID, "")) {
|
||||
Response.err_token_has_bind(exchange);
|
||||
return;
|
||||
}
|
||||
// TODO: 校验角色是否存在
|
||||
// 如果为空则绑定角色
|
||||
new_token.profileUUID = body.getJSONObject("selectedProfile").getString("id");
|
||||
}
|
||||
// 新令牌存库
|
||||
TokenDAO.insert(connection, new_token);
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("accessToken", new_token.accessToken);
|
||||
response.put("clientToken", new_token.clientToken);
|
||||
if (requestUser) {
|
||||
User user = UserDAO.selectByUuid(connection, new_token.userUUID);
|
||||
response.put("user", user.serialToJSONObject());
|
||||
}
|
||||
if (!body.getJSONObject("selectedProfile").isEmpty()) {
|
||||
response.put("selectedProfile", body.getJSONObject("selectedProfile"));
|
||||
}
|
||||
Response.success_200(exchange, response);
|
||||
} catch (IOException | SQLException e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package site.deercloud.identityverification.HttpServer.Yggdrasil.authserver;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.HttpServer.model.Profile;
|
||||
import site.deercloud.identityverification.HttpServer.model.Response;
|
||||
import site.deercloud.identityverification.HttpServer.model.User;
|
||||
import site.deercloud.identityverification.SQLite.ProfileDAO;
|
||||
import site.deercloud.identityverification.SQLite.SqlManager;
|
||||
import site.deercloud.identityverification.SQLite.TokenDAO;
|
||||
import site.deercloud.identityverification.SQLite.UserDAO;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class SignOut implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) {
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("POST")) {
|
||||
Response.err_method_not_allowed(exchange);
|
||||
return;
|
||||
}
|
||||
JSONObject request = JSONObject.parseObject(exchange.getRequestBody().toString());
|
||||
String username = request.getString("username");
|
||||
String password = request.getString("password");
|
||||
Connection connection = SqlManager.getConnection();
|
||||
|
||||
if (!UserDAO.checkPassword(connection, username, password)) {
|
||||
Response.err_password_wrong(exchange, null);
|
||||
return;
|
||||
}
|
||||
User user = UserDAO.selectByEmail(connection, username);
|
||||
// 吊销用户的所有令牌
|
||||
ArrayList<Profile> profiles = ProfileDAO.selectAllByBelongTo(connection, user.uuid);
|
||||
for (Profile profile : profiles) {
|
||||
TokenDAO.deleteByProfileUUID(connection, profile.uuid);
|
||||
}
|
||||
Response.success_no_content(exchange);
|
||||
} catch(SQLException | IOException e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package site.deercloud.identityverification.HttpServer.Yggdrasil.authserver;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.HttpServer.model.Response;
|
||||
import site.deercloud.identityverification.HttpServer.model.Token;
|
||||
import site.deercloud.identityverification.SQLite.SqlManager;
|
||||
import site.deercloud.identityverification.SQLite.TokenDAO;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Validate implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("POST")){
|
||||
Response.err_method_not_allowed(exchange);
|
||||
return;
|
||||
}
|
||||
Connection connection = SqlManager.getConnection();
|
||||
JSONObject request = JSONObject.parseObject(exchange.getRequestBody().toString());
|
||||
String accessToken = request.getString("accessToken");
|
||||
String clientToken = request.getString("clientToken");
|
||||
Token token = TokenDAO.selectByAccessToken(connection, accessToken);
|
||||
// 检查令牌是否存在
|
||||
if (token == null){
|
||||
Response.err_invalid_token(exchange, "无效的令牌", "无效的令牌");
|
||||
return;
|
||||
}
|
||||
// 检查 accessToken 和 clientToken 是否匹配
|
||||
if (clientToken != null && !Objects.equals(token.clientToken, clientToken)){
|
||||
Response.err_invalid_token(exchange, "无效的令牌", "无效的令牌");
|
||||
return;
|
||||
}
|
||||
// 检查令牌是否过期
|
||||
if (TokenDAO.isAccessTokenExpired(connection, accessToken)) {
|
||||
TokenDAO.deleteByAccessToken(connection, accessToken); // 如果过期则删除令牌
|
||||
Response.err_invalid_token(exchange, "令牌过期", "令牌过期,请重新登录");
|
||||
return;
|
||||
}
|
||||
Response.success_no_content(exchange);
|
||||
} catch (IOException | SQLException e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package site.deercloud.identityverification.HttpServer.Yggdrasil.sessionserver;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.HttpServer.model.Profile;
|
||||
import site.deercloud.identityverification.HttpServer.model.Response;
|
||||
import site.deercloud.identityverification.SQLite.ProfileDAO;
|
||||
import site.deercloud.identityverification.SQLite.SqlManager;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
import static site.deercloud.identityverification.Utils.Utils.ParseQueryString;
|
||||
|
||||
public class GetProfile implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) {
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("GET")) {
|
||||
Response.err_method_not_allowed(exchange);
|
||||
return;
|
||||
}
|
||||
String uuid = exchange.getRequestURI().getPath().split("/")[4];
|
||||
Map<String, String> request = ParseQueryString(exchange.getRequestURI().getQuery());
|
||||
boolean unsigned = true;
|
||||
if (request.containsKey("unsigned")) {
|
||||
unsigned = Boolean.parseBoolean(request.get("unsigned"));
|
||||
}
|
||||
Connection connection = SqlManager.getConnection();
|
||||
Profile profile = ProfileDAO.selectByUuid(connection, uuid);
|
||||
if (profile == null) {
|
||||
Response.success_no_content(exchange);
|
||||
return;
|
||||
}
|
||||
Response.success_200(exchange, profile.serialToJSONObject(true, unsigned));
|
||||
} catch (IOException | SQLException e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package site.deercloud.identityverification.HttpServer.Yggdrasil.sessionserver;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.HttpServer.HttpServerManager;
|
||||
import site.deercloud.identityverification.HttpServer.model.Profile;
|
||||
import site.deercloud.identityverification.HttpServer.model.Response;
|
||||
import site.deercloud.identityverification.HttpServer.model.Token;
|
||||
import site.deercloud.identityverification.SQLite.ProfileDAO;
|
||||
import site.deercloud.identityverification.SQLite.SqlManager;
|
||||
import site.deercloud.identityverification.SQLite.TokenDAO;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static site.deercloud.identityverification.Utils.Utils.ParseQueryString;
|
||||
|
||||
public class HasJoined implements HttpHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) {
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("GET")) {
|
||||
Response.err_method_not_allowed(exchange);
|
||||
return;
|
||||
}
|
||||
Map<String, String> query = ParseQueryString(exchange.getRequestURI().getQuery());
|
||||
String username = query.get("username");
|
||||
String serverId = query.get("serverId");
|
||||
String ip = query.get("ip");
|
||||
|
||||
Connection connection = SqlManager.getConnection();
|
||||
|
||||
// 验证缓存令牌有效性
|
||||
if (HttpServerManager.getSessionCache().verifyIsExpire(serverId, ip)) {
|
||||
Response.success_no_content(exchange);
|
||||
return;
|
||||
}
|
||||
// 获取缓存令牌对应令牌
|
||||
String accessToken = HttpServerManager.getSessionCache().getToken(serverId);
|
||||
Token token = TokenDAO.selectByAccessToken(connection, accessToken);
|
||||
if (token == null || token.profileUUID == null || !token.profileUUID.equals(username)) {
|
||||
Response.success_no_content(exchange);
|
||||
return;
|
||||
}
|
||||
// 获取令牌对应的角色
|
||||
Profile profile = ProfileDAO.selectByUuid(connection, token.profileUUID);
|
||||
if (profile == null) {
|
||||
Response.success_no_content(exchange);
|
||||
return;
|
||||
}
|
||||
// 检查角色名是否一致
|
||||
if (!Objects.equals(username, profile.name)) {
|
||||
Response.success_no_content(exchange);
|
||||
return;
|
||||
}
|
||||
Response.success_200(exchange, profile.serialToJSONObject(true, true));
|
||||
} catch (Exception e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package site.deercloud.identityverification.HttpServer.Yggdrasil.sessionserver;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import site.deercloud.identityverification.HttpServer.HttpServerManager;
|
||||
import site.deercloud.identityverification.HttpServer.model.Response;
|
||||
import site.deercloud.identityverification.HttpServer.model.Token;
|
||||
import site.deercloud.identityverification.SQLite.SqlManager;
|
||||
import site.deercloud.identityverification.SQLite.TokenDAO;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class Join implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange){
|
||||
try {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
|
||||
if (!exchange.getRequestMethod().equals("POST")){
|
||||
Response.err_method_not_allowed(exchange);
|
||||
return;
|
||||
}
|
||||
JSONObject request = JSONObject.parseObject(exchange.getRequestBody().toString());
|
||||
String accessToken = request.getString("accessToken");
|
||||
String selectedProfile = request.getString("selectedProfile");
|
||||
String serverId = request.getString("serverId");
|
||||
|
||||
Connection connection = SqlManager.getConnection();
|
||||
Token token = TokenDAO.selectByAccessToken(connection, accessToken);
|
||||
if (token == null || token.profileUUID == null || !token.profileUUID.equals(selectedProfile)){
|
||||
Response.err_invalid_token(exchange, "无效的令牌", "无效的令牌");
|
||||
return;
|
||||
}
|
||||
// 缓存到内存中
|
||||
HttpServerManager.getSessionCache().add(serverId, token.accessToken, exchange.getRemoteAddress().getHostString());
|
||||
|
||||
Response.success_no_content(exchange);
|
||||
} catch (IOException | SQLException e) {
|
||||
exchange.close();
|
||||
MyLogger.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package site.deercloud.identityverification.HttpServer.Yggdrasil.sessionserver;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class SessionTokenCache {
|
||||
private Map<String, String> serverID_token;
|
||||
private Map<String, String> serverID_ip;
|
||||
private Map<String, Long> serverID_time;
|
||||
|
||||
public void add(String serverID, String token, String ip){
|
||||
serverID_token.put(serverID, token);
|
||||
serverID_ip.put(serverID, ip);
|
||||
serverID_time.put(serverID, System.currentTimeMillis() + 30000);
|
||||
}
|
||||
|
||||
public boolean verifyIsExpire(String serverID, String ip){
|
||||
if (!serverID_token.containsKey(serverID)){
|
||||
return true;
|
||||
}
|
||||
if (serverID_time.get(serverID) < System.currentTimeMillis()){
|
||||
serverID_token.remove(serverID);
|
||||
serverID_ip.remove(serverID);
|
||||
serverID_time.remove(serverID);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getToken(String serverID){
|
||||
return serverID_token.get(serverID);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package site.deercloud.identityverification.HttpServer.model;
|
||||
|
||||
public class EmailCode {
|
||||
public String email;
|
||||
public String code;
|
||||
public Long expireTime;
|
||||
|
||||
public EmailCode(String email, String code) {
|
||||
this.email = email;
|
||||
this.code = code;
|
||||
this.expireTime = System.currentTimeMillis() + 1000 * 60 * 5;
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return System.currentTimeMillis() > expireTime;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package site.deercloud.identityverification.HttpServer.model;
|
||||
|
||||
public class GameSession {
|
||||
public GameSession() {
|
||||
joinTime = System.currentTimeMillis();
|
||||
lastActionTime = joinTime;
|
||||
afkTime = 0L;
|
||||
}
|
||||
|
||||
public void addAfkTime(long time) {
|
||||
afkTime += time;
|
||||
}
|
||||
|
||||
public void setLastMovement(long time) {
|
||||
lastActionTime = time;
|
||||
}
|
||||
|
||||
public long getLastMovement() {
|
||||
return lastActionTime;
|
||||
}
|
||||
public Long joinTime;
|
||||
public Long lastActionTime;
|
||||
public Long afkTime;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package site.deercloud.identityverification.HttpServer.model;
|
||||
|
||||
public class InviteCode {
|
||||
public String code;
|
||||
public String inviter;
|
||||
public Long createTime;
|
||||
public Boolean isUsed;
|
||||
public Long usedTime;
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package site.deercloud.identityverification.HttpServer.model;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
public class Profile {
|
||||
public String uuid;
|
||||
public String belongTo;
|
||||
|
||||
public String name;
|
||||
public String textures;
|
||||
public String textures_signature;
|
||||
public String uploadableTextures;
|
||||
public String uploadableTextures_signature;
|
||||
|
||||
public JSONObject serialToJSONObject(Boolean with_properties,Boolean unsigned) {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("id", uuid);
|
||||
jsonObject.put("name", name);
|
||||
if (!with_properties) {
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
JSONArray properties = new JSONArray();
|
||||
JSONObject property = new JSONObject();
|
||||
|
||||
if (textures != null) {
|
||||
property.put("name", "textures");
|
||||
property.put("value", textures);
|
||||
if (!unsigned) {
|
||||
property.put("signature", textures_signature);
|
||||
}
|
||||
properties.add(property);
|
||||
}
|
||||
if (uploadableTextures != null) {
|
||||
property.put("name", "uploadableTextures");
|
||||
property.put("value", uploadableTextures);
|
||||
if (!unsigned) {
|
||||
property.put("signature", uploadableTextures_signature);
|
||||
}
|
||||
properties.add(property);
|
||||
}
|
||||
jsonObject.put("properties", properties);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
public Profile(JSONObject jsonObject) {
|
||||
uuid = jsonObject.getString("id");
|
||||
name = jsonObject.getString("name");
|
||||
JSONArray properties = jsonObject.getJSONArray("properties");
|
||||
for (int i = 0; i < properties.size(); i++) {
|
||||
JSONObject property = properties.getJSONObject(i);
|
||||
if (property.containsKey("textures")) {
|
||||
textures = property.getString("textures");
|
||||
textures_signature = property.getString("textures_signature");
|
||||
}
|
||||
if (property.containsKey("uploadableTextures")) {
|
||||
uploadableTextures = property.getString("uploadableTextures");
|
||||
uploadableTextures_signature = property.getString("uploadableTextures_signature");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Profile() {
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package site.deercloud.identityverification.HttpServer.model;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Response {
|
||||
public static void err_invalid_token(HttpExchange exchange, String msg, String cause) throws IOException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("error", "ForbiddenOperationException");
|
||||
jsonObject.put("errorMessage", "Invalid token. " + msg);
|
||||
jsonObject.put("cause", cause);
|
||||
exchange.sendResponseHeaders(403, jsonObject.toString().getBytes().length);
|
||||
exchange.getResponseBody().write(jsonObject.toString().getBytes());
|
||||
exchange.getResponseBody().close();
|
||||
}
|
||||
|
||||
public static void err_password_wrong(HttpExchange exchange, String cause) throws IOException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("error", "ForbiddenOperationException");
|
||||
jsonObject.put("errorMessage", "密码错误,或短时间内多次登录失败而被暂时禁止登录");
|
||||
jsonObject.put("cause", cause);
|
||||
exchange.sendResponseHeaders(403, jsonObject.toString().getBytes().length);
|
||||
exchange.getResponseBody().write(jsonObject.toString().getBytes());
|
||||
exchange.getResponseBody().close();
|
||||
}
|
||||
|
||||
public static void success_200(HttpExchange exchange, JSONObject data) throws IOException {
|
||||
exchange.sendResponseHeaders(200, data.toString().getBytes().length);
|
||||
exchange.getResponseBody().write(data.toString().getBytes());
|
||||
exchange.getResponseBody().close();
|
||||
}
|
||||
|
||||
public static void success_200(HttpExchange exchange, JSONArray data) throws IOException {
|
||||
exchange.sendResponseHeaders(200, data.toString().getBytes().length);
|
||||
exchange.getResponseBody().write(data.toString().getBytes());
|
||||
exchange.getResponseBody().close();
|
||||
}
|
||||
|
||||
public static void err_method_not_allowed(HttpExchange exchange) throws IOException {
|
||||
exchange.sendResponseHeaders(405, "Method Not Allowed".getBytes().length);
|
||||
exchange.getResponseBody().write("Method Not Allowed".getBytes());
|
||||
exchange.getResponseBody().close();
|
||||
}
|
||||
|
||||
public static void err_token_has_bind(HttpExchange exchange) throws IOException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("error", "IllegalArgumentException");
|
||||
jsonObject.put("errorMessage", "IAccess token already has a profile assigned.");
|
||||
jsonObject.put("cause", "试图向一个已经绑定了角色的令牌指定其要绑定的角色");
|
||||
exchange.sendResponseHeaders(400, jsonObject.toString().getBytes().length);
|
||||
exchange.getResponseBody().write(jsonObject.toString().getBytes());
|
||||
exchange.getResponseBody().close();
|
||||
}
|
||||
|
||||
public static void success_no_content(HttpExchange exchange) throws IOException {
|
||||
exchange.sendResponseHeaders(204, "No Content".getBytes().length);
|
||||
exchange.getResponseBody().write("No Content".getBytes());
|
||||
exchange.getResponseBody().close();
|
||||
}
|
||||
|
||||
public static void err_invalid_profile(HttpExchange exchange) throws IOException {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("error", "ForbiddenOperationException");
|
||||
jsonObject.put("errorMessage", "Invalid token.");
|
||||
jsonObject.put("cause", "试图使用一个不存在的角色UUID来登录");
|
||||
exchange.sendResponseHeaders(403, jsonObject.toString().getBytes().length);
|
||||
exchange.getResponseBody().write(jsonObject.toString().getBytes());
|
||||
exchange.getResponseBody().close();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package site.deercloud.identityverification.HttpServer.model;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import site.deercloud.identityverification.IdentityVerification;
|
||||
import site.deercloud.identityverification.Utils.SignatureUtil;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Texture {
|
||||
public String profileId;
|
||||
public String profileName;
|
||||
public Long timestamp;
|
||||
public String skinUrl;
|
||||
public String capeUrl;
|
||||
|
||||
public static Texture newDefault(String Name) {
|
||||
Texture texture = new Texture();
|
||||
texture.profileName = Name;
|
||||
texture.profileId = UUID.randomUUID().toString();
|
||||
texture.timestamp = System.currentTimeMillis();
|
||||
texture.skinUrl = "https://textures.minecraft.net/texture/60a5bd016b3c9a1b9272e4929e30827a67be4ebb219017adbbc4a4d22ebd5b1";
|
||||
texture.capeUrl = null;
|
||||
return texture;
|
||||
}
|
||||
|
||||
public String serialWithBase64() {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("timestamp", timestamp);
|
||||
jsonObject.put("profileId", profileId);
|
||||
jsonObject.put("profileName", profileName);
|
||||
JSONObject textures = new JSONObject();
|
||||
JSONObject skin = new JSONObject();
|
||||
skin.put("url", skinUrl);
|
||||
textures.put("SKIN", skin);
|
||||
if (capeUrl != null) {
|
||||
JSONObject cape = new JSONObject();
|
||||
cape.put("url", capeUrl);
|
||||
textures.put("CAPE", cape);
|
||||
}
|
||||
jsonObject.put("textures", textures);
|
||||
return Base64.getEncoder().encodeToString(jsonObject.toJSONString().getBytes());
|
||||
}
|
||||
|
||||
public String sign() throws Exception {
|
||||
// 使用SHA1withRSA算法签名
|
||||
String private_key_str = IdentityVerification.getInstance().getConfigManager().getSignaturePrivateKey();
|
||||
return SignatureUtil.sign(serialWithBase64(), private_key_str);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package site.deercloud.identityverification.HttpServer.model;
|
||||
|
||||
public class Token {
|
||||
public String accessToken;
|
||||
public String clientToken;
|
||||
public String profileUUID;
|
||||
|
||||
public String userUUID;
|
||||
public Long expiresAt;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package site.deercloud.identityverification.HttpServer.model;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
public class User {
|
||||
public String uuid;
|
||||
public String password;
|
||||
public String email;
|
||||
public Long createTime;
|
||||
public Long updateTime;
|
||||
|
||||
public JSONObject serialToJSONObject() {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("id", uuid);
|
||||
JSONArray properties = new JSONArray();
|
||||
JSONObject property = new JSONObject();
|
||||
property.put("name", "preferredLanguage");
|
||||
property.put("value", "zh_CN");
|
||||
properties.add(property);
|
||||
jsonObject.put("properties", properties);
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package site.deercloud.identityverification;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import site.deercloud.identityverification.HttpServer.HttpServerManager;
|
||||
import site.deercloud.identityverification.HttpServer.model.User;
|
||||
import site.deercloud.identityverification.SQLite.SqlManager;
|
||||
import site.deercloud.identityverification.SQLite.UserDAO;
|
||||
import site.deercloud.identityverification.Controller.AFKTracker;
|
||||
import site.deercloud.identityverification.Controller.ConfigManager;
|
||||
import site.deercloud.identityverification.Utils.MyLogger;
|
||||
import site.deercloud.identityverification.Controller.GameSessionCache;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class IdentityVerification extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
// Plugin startup logic
|
||||
instance = this;
|
||||
configManager = new ConfigManager(this);
|
||||
httpServerManager = new HttpServerManager(this);
|
||||
gameSessionCache = new GameSessionCache(this);
|
||||
afkTracker = new AFKTracker(this);
|
||||
|
||||
// 初始化数据表
|
||||
try {
|
||||
Connection connection = SqlManager.getConnection();
|
||||
SqlManager.createTables(connection);
|
||||
if (!UserDAO.isEmailExist(connection, "console@mc.com")) {
|
||||
// 生成控制台用户
|
||||
User consoleUser = new User();
|
||||
consoleUser.email = "console@mc.com";
|
||||
consoleUser.uuid = UUID.randomUUID().toString();
|
||||
consoleUser.password = String.valueOf("123456".hashCode());
|
||||
UserDAO.insert(connection, consoleUser);
|
||||
}
|
||||
connection.close();
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
// 初始化RSA
|
||||
String publicKey = new File(this.getDataFolder(), configManager.getPublicKeyFileName()).toString();
|
||||
String privateKey = new File(this.getDataFolder(), configManager.getPrivateKeyFileName()).toString();
|
||||
if (publicKey == null || privateKey == null) {
|
||||
MyLogger.error("RSA文件不存在, 插件退出。");
|
||||
this.getServer().getPluginManager().disablePlugin(this);
|
||||
}else {
|
||||
privateKey = privateKey.replace("-----BEGIN OPENSSH PRIVATE KEY-----", "");
|
||||
privateKey = privateKey.replace("-----END OPENSSH PRIVATE KEY-----", "");
|
||||
configManager.setSignaturePublicKey(publicKey);
|
||||
configManager.setSignaturePrivateKey(privateKey);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
// Plugin shutdown logic
|
||||
if (httpServerManager != null) {
|
||||
httpServerManager.stopHttpServer();
|
||||
}
|
||||
}
|
||||
|
||||
public ConfigManager getConfigManager() {
|
||||
return configManager;
|
||||
}
|
||||
public static IdentityVerification getInstance() {
|
||||
return instance;
|
||||
}
|
||||
public static GameSessionCache getSessionCache() {
|
||||
return gameSessionCache;
|
||||
}
|
||||
public static AFKTracker getAfkTracker() {
|
||||
return afkTracker;
|
||||
}
|
||||
|
||||
private HttpServerManager httpServerManager;
|
||||
private ConfigManager configManager;
|
||||
private static GameSessionCache gameSessionCache;
|
||||
private static AFKTracker afkTracker;
|
||||
private static IdentityVerification instance;
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package site.deercloud.identityverification.SQLite;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
public class BanListDAO {
|
||||
public static void createTable(Connection con) throws SQLException {
|
||||
String sql = "CREATE TABLE IF NOT EXISTS ban_list (\n"
|
||||
+ " id integer PRIMARY KEY AUTOINCREMENT ,\n" // ID
|
||||
+ " uuid text NOT NULL ,\n" // UUID
|
||||
+ " name text NOT NULL,\n" // 玩家名字
|
||||
+ " ban_time integer NOT NULL,\n" // 封禁时间
|
||||
+ " ban_reason text NOT NULL,\n" // 封禁原因
|
||||
+ " create_time integer NOT NULL\n" // 数据创建时间
|
||||
+ ");";
|
||||
Statement stat = null;
|
||||
stat = con.createStatement();
|
||||
stat.executeUpdate(sql);
|
||||
}
|
||||
|
||||
public static void insert(Connection con, String uuid, String name, long banTime, String banReason, long createTime) throws SQLException {
|
||||
String sql = "INSERT INTO ban_list(uuid,name,ban_time,ban_reason,create_time) VALUES(?,?,?,?,?)";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, uuid);
|
||||
prep.setString(2, name);
|
||||
prep.setLong(3, banTime);
|
||||
prep.setString(4, banReason);
|
||||
prep.setLong(5, createTime);
|
||||
prep.executeUpdate();
|
||||
}
|
||||
|
||||
public static Integer isBanned(Connection con, String uuid) throws SQLException {
|
||||
String sql = "SELECT ban_time FROM ban_list WHERE uuid = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, uuid);
|
||||
ResultSet rs = prep.executeQuery();
|
||||
while (rs.next()) {
|
||||
if(rs.getLong("ban_time") > System.currentTimeMillis()) {
|
||||
return rs.getInt("id");
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static String getBanReasonById(Connection con, Integer id) throws SQLException {
|
||||
String sql = "SELECT ban_reason FROM ban_list WHERE id = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setInt(1, id);
|
||||
ResultSet rs = prep.executeQuery();
|
||||
if (rs.next()) {
|
||||
return rs.getString("ban_reason");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static long getBanTimeById(Connection con, Integer id) throws SQLException {
|
||||
String sql = "SELECT ban_time FROM ban_list WHERE id = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setInt(1, id);
|
||||
ResultSet rs = prep.executeQuery();
|
||||
if (rs.next()) {
|
||||
return rs.getLong("ban_time");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static String getNameById(Connection con, Integer id) throws SQLException {
|
||||
String sql = "SELECT name FROM ban_list WHERE id = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setInt(1, id);
|
||||
ResultSet rs = prep.executeQuery();
|
||||
if (rs.next()) {
|
||||
return rs.getString("name");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package site.deercloud.identityverification.SQLite;
|
||||
|
||||
import site.deercloud.identityverification.HttpServer.model.InviteCode;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.Set;
|
||||
|
||||
public class InviteCodeDAO {
|
||||
public static void createTable(Connection con) throws SQLException {
|
||||
String sql = "CREATE TABLE IF NOT EXISTS invite_code (\n"
|
||||
+ " code text PRIMARY KEY,\n" // 邀请码
|
||||
+ " inviter_uuid text NOT NULL,\n" // 所有者
|
||||
+ " create_time integer NOT NULL,\n" // 创建时间
|
||||
+ " is_used integer NOT NULL,\n" // 是否被使用
|
||||
+ " use_time integer NOT NULL\n" // 使用时间
|
||||
+ ");";
|
||||
Statement stat = null;
|
||||
stat = con.createStatement();
|
||||
stat.executeUpdate(sql);
|
||||
}
|
||||
|
||||
public static void insert(Connection con, String code, String inviterUUID, boolean isUsed, long useTime) throws SQLException {
|
||||
String sql = "INSERT INTO invite_code(code,inviter_uuid,create_time,is_used,use_time) VALUES(?,?,?,?,?)";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, code);
|
||||
prep.setString(2, inviterUUID);
|
||||
prep.setLong(3, System.currentTimeMillis());
|
||||
prep.setBoolean(4, isUsed);
|
||||
prep.setLong(5, useTime);
|
||||
prep.executeUpdate();
|
||||
}
|
||||
|
||||
public static String getInviterUUID(Connection con, String code) throws SQLException {
|
||||
String sql = "SELECT inviter_uuid FROM invite_code WHERE code = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, code);
|
||||
ResultSet rs = prep.executeQuery();
|
||||
if (rs.next()) {
|
||||
return rs.getString("inviter_uuid");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isValid(Connection con, String code) throws SQLException {
|
||||
String sql = "SELECT is_used FROM invite_code WHERE code = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, code);
|
||||
ResultSet rs = prep.executeQuery();
|
||||
if (rs.next()) {
|
||||
return !rs.getBoolean("is_used");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void setUsed(Connection con, String code, boolean isUsed, long useTime) throws SQLException {
|
||||
String sql = "UPDATE invite_code SET is_used = ?,use_time = ? WHERE code = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setBoolean(1, isUsed);
|
||||
prep.setLong(2, useTime);
|
||||
prep.setString(3, code);
|
||||
prep.executeUpdate();
|
||||
}
|
||||
|
||||
public static long getUseTime(Connection con, String code) throws SQLException {
|
||||
String sql = "SELECT use_time FROM invite_code WHERE code = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, code);
|
||||
ResultSet rs = prep.executeQuery();
|
||||
if (rs.next()) {
|
||||
return rs.getLong("use_time");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static Set<InviteCode> selectByInviter(Connection con, String inviterUUID) throws SQLException {
|
||||
String sql = "SELECT * FROM invite_code WHERE inviter_uuid = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, inviterUUID);
|
||||
ResultSet rs = prep.executeQuery();
|
||||
Set<InviteCode> inviteCodes = null;
|
||||
while (rs.next()) {
|
||||
InviteCode inviteCode = new InviteCode();
|
||||
inviteCode.code = rs.getString("code");
|
||||
inviteCode.inviter = rs.getString("inviter_uuid");
|
||||
inviteCode.createTime = rs.getLong("create_time");
|
||||
inviteCode.isUsed = rs.getBoolean("is_used");
|
||||
inviteCode.usedTime = rs.getLong("use_time");
|
||||
inviteCodes.add(inviteCode);
|
||||
}
|
||||
return inviteCodes;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package site.deercloud.identityverification.SQLite;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
public class InviteRelationDAO {
|
||||
public static void createTable(Connection con) throws SQLException {
|
||||
String sql = "CREATE TABLE IF NOT EXISTS invite_relation (\n"
|
||||
+ " invitee_uuid text PRIMARY KEY,\n" // 被邀请人UUID
|
||||
+ " inviter_uuid text NOT NULL,\n" // 邀请人UUID
|
||||
+ " invite_time integer NOT NULL\n" // 邀请时间
|
||||
+ ");";
|
||||
Statement stat = null;
|
||||
stat = con.createStatement();
|
||||
stat.executeUpdate(sql);
|
||||
}
|
||||
|
||||
public static void insert(Connection con, String inviteeUUID, String inviterUUID, long inviteTime) throws SQLException {
|
||||
String sql = "INSERT INTO invite_relation(invitee_uuid,inviter_uuid,invite_time) VALUES(?,?,?)";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, inviteeUUID);
|
||||
prep.setString(2, inviterUUID);
|
||||
prep.setLong(3, inviteTime);
|
||||
prep.executeUpdate();
|
||||
}
|
||||
|
||||
public static String getInviterUUID(Connection con, String inviteeUUID) throws SQLException {
|
||||
String sql = "SELECT inviter_uuid FROM invite_relation WHERE invitee_uuid = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, inviteeUUID);
|
||||
ResultSet rs = prep.executeQuery();
|
||||
if (rs.next()) {
|
||||
return rs.getString("inviter_uuid");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,107 @@
|
||||
package site.deercloud.identityverification.SQLite;
|
||||
|
||||
import site.deercloud.identityverification.HttpServer.model.Profile;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ProfileDAO {
|
||||
public static void createTable(Connection connection) throws SQLException {
|
||||
String sql = "CREATE TABLE IF NOT EXISTS profile (\n"
|
||||
+ " uuid text PRIMARY KEY,\n" // UUID
|
||||
+ " belong_to text NOT NULL,\n" // 所属User
|
||||
+ " name text NOT NULL,\n" // 玩家名字
|
||||
+ " textures text NOT NULL,\n" // 材质
|
||||
+ " textures_signature text NOT NULL,\n" // 材质签名
|
||||
+ " uploadableTextures text NOT NULL,\n" // 可上传的材质
|
||||
+ " uploadableTextures_signature text NOT NULL,\n" // 可上传的材质签名
|
||||
+ " update_time integer NOT NULL\n" // 数据更新时间
|
||||
+ ");";
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
preparedStatement.execute();
|
||||
}
|
||||
|
||||
public static void insert(Connection connection, Profile profile) throws SQLException {
|
||||
String sql = "INSERT INTO profile(uuid,belong_to,name,textures,textures_signature,uploadableTextures,uploadableTextures_signature,update_time) VALUES(?,?,?,?,?,?,?,?)";
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
preparedStatement.setString(1, profile.uuid);
|
||||
preparedStatement.setString(2, profile.belongTo);
|
||||
preparedStatement.setString(3, profile.name);
|
||||
preparedStatement.setString(4, profile.textures);
|
||||
preparedStatement.setString(5, profile.textures_signature);
|
||||
preparedStatement.setString(6, profile.uploadableTextures);
|
||||
preparedStatement.setString(7, profile.uploadableTextures_signature);
|
||||
preparedStatement.setLong(8, System.currentTimeMillis());
|
||||
preparedStatement.executeUpdate();
|
||||
}
|
||||
|
||||
public static void update(Connection connection, Profile profile) throws SQLException {
|
||||
String sql = "UPDATE profile SET name = ?, textures = ?, textures_signature = ?, uploadableTextures = ?, uploadableTextures_signature = ?, update_time = ? WHERE uuid = ?";
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
preparedStatement.setString(1, profile.name);
|
||||
preparedStatement.setString(2, profile.textures);
|
||||
preparedStatement.setString(3, profile.textures_signature);
|
||||
preparedStatement.setString(4, profile.uploadableTextures);
|
||||
preparedStatement.setString(5, profile.uploadableTextures_signature);
|
||||
preparedStatement.setLong(6, System.currentTimeMillis());
|
||||
preparedStatement.setString(7, profile.uuid);
|
||||
preparedStatement.executeUpdate();
|
||||
}
|
||||
|
||||
public static Profile selectByUuid(Connection connection, String uuid) throws SQLException {
|
||||
String sql = "SELECT * FROM profile WHERE uuid = ?";
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
preparedStatement.setString(1, uuid);
|
||||
Profile profile = new Profile();
|
||||
if (preparedStatement.executeQuery().next()) {
|
||||
profile.uuid = preparedStatement.getResultSet().getString("uuid");
|
||||
profile.belongTo = preparedStatement.getResultSet().getString("belong_to");
|
||||
profile.name = preparedStatement.getResultSet().getString("name");
|
||||
profile.textures = preparedStatement.getResultSet().getString("textures");
|
||||
profile.textures_signature = preparedStatement.getResultSet().getString("textures_signature");
|
||||
profile.uploadableTextures = preparedStatement.getResultSet().getString("uploadableTextures");
|
||||
profile.uploadableTextures_signature = preparedStatement.getResultSet().getString("uploadableTextures_signature");
|
||||
return profile;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ArrayList<Profile> selectAllByBelongTo(Connection connection, String belongTo) throws SQLException {
|
||||
String sql = "SELECT * FROM profile WHERE belong_to = ?";
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
preparedStatement.setString(1, belongTo);
|
||||
ArrayList<Profile> profiles = new ArrayList<>();
|
||||
while (preparedStatement.executeQuery().next()) {
|
||||
Profile profile = new Profile();
|
||||
profile.uuid = preparedStatement.executeQuery().getString("uuid");
|
||||
profile.belongTo = belongTo;
|
||||
profile.name = preparedStatement.executeQuery().getString("name");
|
||||
profile.textures = preparedStatement.executeQuery().getString("textures");
|
||||
profile.textures_signature = preparedStatement.executeQuery().getString("textures_signature");
|
||||
profile.uploadableTextures = preparedStatement.executeQuery().getString("uploadableTextures");
|
||||
profile.uploadableTextures_signature = preparedStatement.executeQuery().getString("uploadableTextures_signature");
|
||||
profiles.add(profile);
|
||||
}
|
||||
return profiles;
|
||||
}
|
||||
|
||||
public static Profile selectByName(Connection connection, String name) throws SQLException {
|
||||
String sql = "SELECT * FROM profile WHERE name = ?";
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
preparedStatement.setString(1, name);
|
||||
Profile profile = new Profile();
|
||||
if (preparedStatement.executeQuery().next()) {
|
||||
profile.uuid = preparedStatement.getResultSet().getString("uuid");
|
||||
profile.belongTo = preparedStatement.getResultSet().getString("belong_to");
|
||||
profile.name = name;
|
||||
profile.textures = preparedStatement.getResultSet().getString("textures");
|
||||
profile.textures_signature = preparedStatement.getResultSet().getString("textures_signature");
|
||||
profile.uploadableTextures = preparedStatement.getResultSet().getString("uploadableTextures");
|
||||
profile.uploadableTextures_signature = preparedStatement.getResultSet().getString("uploadableTextures_signature");
|
||||
return profile;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package site.deercloud.identityverification.SQLite;
|
||||
|
||||
import org.sqlite.SQLiteConfig;
|
||||
import org.sqlite.SQLiteDataSource;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
public class SqlManager {
|
||||
|
||||
public static Connection getConnection(){
|
||||
try {
|
||||
SQLiteConfig config = new SQLiteConfig();
|
||||
config.setSharedCache(true);
|
||||
config.enableRecursiveTriggers(true);
|
||||
SQLiteDataSource ds = new SQLiteDataSource(config);
|
||||
String url = System.getProperty("user.dir"); // 获取工作目录
|
||||
ds.setUrl("jdbc:sqlite:"+url+"/plugins/IdentityVerification/"+"IV-Database.db");
|
||||
return ds.getConnection();
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void createTables(Connection connection) {
|
||||
try {
|
||||
BanListDAO.createTable(connection);
|
||||
InviteCodeDAO.createTable(connection);
|
||||
UserDAO.createTable(connection);
|
||||
InviteRelationDAO.createTable(connection);
|
||||
ProfileDAO.createTable(connection);
|
||||
WhiteListDAO.createTable(connection);
|
||||
TokenDAO.createTable(connection);
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package site.deercloud.identityverification.SQLite;
|
||||
|
||||
import site.deercloud.identityverification.HttpServer.model.Token;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
public class TokenDAO {
|
||||
public static void createTable(Connection connection) throws SQLException {
|
||||
String sql = "CREATE TABLE IF NOT EXISTS token (\n"
|
||||
+ " access_token text PRIMARY KEY,\n"
|
||||
+ " client_token text NOT NULL,\n"
|
||||
+ " user_uuid text NOT NULL,\n"
|
||||
+ " profile_uuid text NOT NULL,\n"
|
||||
+ " expires_at integer NOT NULL\n"
|
||||
+ ");";
|
||||
Statement statement = connection.createStatement();
|
||||
statement.execute(sql);
|
||||
}
|
||||
|
||||
public static void insert(Connection connection, Token token) throws SQLException {
|
||||
String sql = "INSERT INTO token(access_token,client_token,user_uuid,profile_uuid,expires_at) VALUES(?,?,?,?,?)";
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
preparedStatement.setString(1, token.accessToken);
|
||||
preparedStatement.setString(2, token.clientToken);
|
||||
preparedStatement.setString(3, token.userUUID);
|
||||
preparedStatement.setString(4, token.profileUUID);
|
||||
preparedStatement.setLong(5, token.expiresAt);
|
||||
preparedStatement.executeUpdate();
|
||||
}
|
||||
|
||||
public static Boolean isAccessTokenExpired(Connection connection, String accessToken) throws SQLException {
|
||||
String sql = "SELECT * FROM token WHERE access_token = ?";
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
preparedStatement.setString(1, accessToken);
|
||||
ResultSet resultSet = preparedStatement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
return resultSet.getLong("expires_at") < System.currentTimeMillis();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static Boolean isClientTokenExpired(Connection connection, String clientToken) throws SQLException {
|
||||
String sql = "SELECT * FROM token WHERE client_token = ?";
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
preparedStatement.setString(1, clientToken);
|
||||
ResultSet resultSet = preparedStatement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
return resultSet.getLong("expires_at") < System.currentTimeMillis();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static Boolean isTokenBindProfile(Connection connection, String accessToken) throws SQLException {
|
||||
String sql = "SELECT * FROM token WHERE access_token = ?";
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
preparedStatement.setString(1, accessToken);
|
||||
ResultSet resultSet = preparedStatement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
return resultSet.getString("profile_uuid") != null;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static Token selectByAccessToken(Connection connection, String accessToken) throws SQLException {
|
||||
String sql = "SELECT * FROM token WHERE access_token = ?";
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
preparedStatement.setString(1, accessToken);
|
||||
ResultSet resultSet = preparedStatement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
Token token = new Token();
|
||||
token.accessToken = resultSet.getString("access_token");
|
||||
token.clientToken = resultSet.getString("client_token");
|
||||
token.userUUID = resultSet.getString("user_uuid");
|
||||
token.profileUUID = resultSet.getString("profile_uuid");
|
||||
token.expiresAt = resultSet.getLong("expires_at");
|
||||
return token;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteByAccessToken(Connection connection, String accessToken) throws SQLException {
|
||||
String sql = "DELETE FROM token WHERE access_token = ?";
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
preparedStatement.setString(1, accessToken);
|
||||
preparedStatement.executeUpdate();
|
||||
}
|
||||
|
||||
public static void deleteByProfileUUID(Connection connection, String profileUUID) throws SQLException {
|
||||
String sql = "DELETE FROM token WHERE profile_uuid = ?";
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
preparedStatement.setString(1, profileUUID);
|
||||
preparedStatement.executeUpdate();
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package site.deercloud.identityverification.SQLite;
|
||||
|
||||
import site.deercloud.identityverification.HttpServer.model.User;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
public class UserDAO {
|
||||
public static void createTable(Connection con) throws SQLException {
|
||||
String sql = "CREATE TABLE IF NOT EXISTS user (\n"
|
||||
+ " uuid text PRIMARY KEY,\n" // UUID
|
||||
+ " email text NOT NULL,\n" // 邮箱
|
||||
+ " password text NOT NULL,\n" // 密码
|
||||
+ " create_time integer NOT NULL,\n" // 数据创建时间
|
||||
+ " update_time integer NOT NULL\n" // 数据更新时间
|
||||
+ ");";
|
||||
Statement stat;
|
||||
stat = con.createStatement();
|
||||
stat.executeUpdate(sql);
|
||||
}
|
||||
|
||||
public static void insert(Connection con, User user) throws SQLException {
|
||||
String sql = "INSERT INTO user(uuid, email, password, create_time, update_time) VALUES(?,?,?,?,?,?)";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, user.uuid);
|
||||
prep.setString(2, user.email);
|
||||
prep.setString(3, user.password);
|
||||
prep.setLong(4, System.currentTimeMillis());
|
||||
prep.setLong(5, System.currentTimeMillis());
|
||||
prep.executeUpdate();
|
||||
}
|
||||
|
||||
public static void update(Connection con, User user) throws SQLException {
|
||||
String sql = "UPDATE user SET email = ?, password = ?, update_time = ? WHERE uuid = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, user.email);
|
||||
prep.setString(2, user.password);
|
||||
prep.setLong(3, System.currentTimeMillis());
|
||||
prep.setString(4, user.uuid);
|
||||
prep.executeUpdate();
|
||||
}
|
||||
|
||||
public static void delete(Connection con, String uuid) throws SQLException {
|
||||
String sql = "DELETE FROM user WHERE uuid = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, uuid);
|
||||
prep.executeUpdate();
|
||||
}
|
||||
|
||||
public static User selectByUuid(Connection con, String uuid) throws SQLException {
|
||||
String sql = "SELECT * FROM user WHERE uuid = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, uuid);
|
||||
User user = new User();
|
||||
user.uuid = uuid;
|
||||
user.email = prep.executeQuery().getString("email");
|
||||
user.password = prep.executeQuery().getString("password");
|
||||
user.createTime = prep.executeQuery().getLong("create_time");
|
||||
user.updateTime = prep.executeQuery().getLong("update_time");
|
||||
return user;
|
||||
}
|
||||
|
||||
public static User selectByEmail(Connection con, String email) throws SQLException {
|
||||
String sql = "SELECT * FROM user WHERE email = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, email);
|
||||
if (prep.executeQuery().next()) {
|
||||
User user = new User();
|
||||
user.uuid = prep.executeQuery().getString("uuid");
|
||||
user.email = email;
|
||||
user.password = prep.executeQuery().getString("password");
|
||||
user.createTime = prep.executeQuery().getLong("create_time");
|
||||
user.updateTime = prep.executeQuery().getLong("update_time");
|
||||
return user;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Boolean checkPassword(Connection con, String email, String password) throws SQLException {
|
||||
String sql = "SELECT * FROM user WHERE email = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
if (prep.executeQuery().next()) {
|
||||
return prep.executeQuery().getString("password").equals(password);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Boolean isEmailExist(Connection con, String email) throws SQLException {
|
||||
String sql = "SELECT * FROM user WHERE email = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
return prep.executeQuery().next();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package site.deercloud.identityverification.SQLite;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
public class WhiteListDAO {
|
||||
public static void createTable(Connection con) throws SQLException {
|
||||
String sql = "CREATE TABLE IF NOT EXISTS white_list (\n"
|
||||
+ " uuid text PRIMARY KEY,\n" // UUID
|
||||
+ " is_genuine integer NOT NULL,\n" // 是否为正版玩家
|
||||
+ " id_hash integer NOT NULL,\n" // 身份证号哈希值
|
||||
+ " create_time integer NOT NULL\n" // 数据创建时间
|
||||
+ ");";
|
||||
Statement stat;
|
||||
stat = con.createStatement();
|
||||
stat.executeUpdate(sql);
|
||||
}
|
||||
|
||||
public static void insert(Connection con, String uuid, boolean isGenuine, Integer idHash) throws SQLException {
|
||||
String sql = "INSERT INTO white_list(uuid,is_genuine,id_hash,create_time) VALUES(?,?,?,?)";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, uuid);
|
||||
prep.setBoolean(2, isGenuine);
|
||||
prep.setInt(3, idHash);
|
||||
prep.setLong(4, System.currentTimeMillis());
|
||||
prep.executeUpdate();
|
||||
}
|
||||
|
||||
public static boolean isUuidInWhiteList(Connection con, String uuid) throws SQLException {
|
||||
String sql = "SELECT * FROM white_list WHERE uuid = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setString(1, uuid);
|
||||
return prep.executeQuery().next();
|
||||
}
|
||||
|
||||
public static boolean isIdInWhiteList(Connection con, Integer idHash) throws SQLException {
|
||||
String sql = "SELECT * FROM white_list WHERE id_hash = ?";
|
||||
PreparedStatement prep = con.prepareStatement(sql);
|
||||
prep.setInt(1, idHash);
|
||||
return prep.executeQuery().next();
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package site.deercloud.identityverification.Utils;
|
||||
|
||||
import site.deercloud.identityverification.IdentityVerification;
|
||||
|
||||
import javax.mail.*;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
public class EmailSender {
|
||||
|
||||
static String host = IdentityVerification.getInstance().getConfigManager().getEmailHost(); // smtp服务器
|
||||
static String port = IdentityVerification.getInstance().getConfigManager().getEmailPort(); // 端口
|
||||
static String from = IdentityVerification.getInstance().getConfigManager().getEmailFrom(); // 发件人的email
|
||||
static String account = IdentityVerification.getInstance().getConfigManager().getEmailUsername(); // 账号
|
||||
static String password = IdentityVerification.getInstance().getConfigManager().getEmailPassword(); // 密码
|
||||
|
||||
public static boolean sendTestEmail(String to) {
|
||||
String subject = "测试邮件";
|
||||
String content = "这是一封测试邮件";
|
||||
return sendEmail(to, subject, content);
|
||||
}
|
||||
|
||||
public static boolean sendResetPasswordEmail(String to, String password) {
|
||||
String subject = "[IV] 重置密码";
|
||||
String content = "您的新密码为:" + password + ",请尽快登录并修改密码";
|
||||
return sendEmail(to, subject, content);
|
||||
}
|
||||
|
||||
public static boolean sendCodeEmail(String to, String code) {
|
||||
String subject = "[IV] 验证码";
|
||||
String content = "您的验证码为:" + code + ",请在5分钟内输入";
|
||||
return sendEmail(to, subject, content);
|
||||
}
|
||||
|
||||
private static boolean sendEmail(String to, String subject, String content) {
|
||||
try {
|
||||
// 1. 创建参数配置, 用于连接邮件服务器的参数配置
|
||||
Properties props = new Properties(); // 参数配置
|
||||
props.setProperty("mail.transport.protocol", "smtp"); // 使用的协议(JavaMail规范要求)
|
||||
props.setProperty("mail.smtp.host", host); // 发件人的邮箱的 SMTP 服务器地址
|
||||
props.setProperty("mail.smtp.auth", "true"); // 需要请求认证
|
||||
props.setProperty("mail.smtp.port", port);
|
||||
props.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
|
||||
props.setProperty("mail.smtp.socketFactory.fallback", "false");
|
||||
props.setProperty("mail.smtp.socketFactory.port", port);
|
||||
|
||||
// 2. 根据配置创建会话对象, 用于和邮件服务器交互
|
||||
Session session = Session.getInstance(props);
|
||||
session.setDebug(IdentityVerification.getInstance().getConfigManager().getDebug()); // 设置为debug模式, 可以查看详细的发送 log
|
||||
|
||||
// 3. 创建一封邮件
|
||||
MimeMessage message = new MimeMessage(session);
|
||||
message.setFrom(new InternetAddress(from, "IdentityVerification", "UTF-8")); // 发件人
|
||||
message.setRecipient(Message.RecipientType.TO, new InternetAddress(to, "no-reply", "UTF-8")); // 收件人
|
||||
message.setSubject(subject, "UTF-8"); // 邮件主题
|
||||
message.setContent(content, "text/html;charset=UTF-8"); // 邮件正文(可以使用html标签)
|
||||
message.setSentDate(new Date()); // 设置发件时间
|
||||
message.saveChanges(); // 保存设置
|
||||
|
||||
// 4. 根据 Session 获取邮件传输对象
|
||||
Transport transport = session.getTransport();
|
||||
|
||||
// 5. 使用 邮箱账号 和 密码 连接邮件服务器
|
||||
// 这里认证的邮箱必须与 message 中的发件人邮箱一致,否则报错
|
||||
transport.connect(account, password);
|
||||
|
||||
// 6. 发送邮件, 发到所有的收件地址, message.getAllRecipients() 获取到的是在创建邮件对象时添加的所有收件人, 抄送人, 密送人
|
||||
transport.sendMessage(message, message.getAllRecipients());
|
||||
|
||||
// 7. 关闭连接
|
||||
transport.close();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
MyLogger.debug(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package site.deercloud.identityverification.Utils;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import site.deercloud.identityverification.IdentityVerification;
|
||||
|
||||
public class MyLogger {
|
||||
private static IdentityVerification plugin = IdentityVerification.getInstance();
|
||||
|
||||
public static void info(String message) {
|
||||
plugin.getLogger().info(ChatColor.GREEN + "| " +message);
|
||||
}
|
||||
|
||||
public static void warn(String message) {
|
||||
plugin.getLogger().warning(ChatColor.YELLOW + "| " +message);
|
||||
}
|
||||
|
||||
public static void error(String message) {
|
||||
plugin.getLogger().severe(ChatColor.RED + "| " +message);
|
||||
}
|
||||
|
||||
public static void debug(String message) {
|
||||
if (plugin.getConfigManager().getDebug()) {
|
||||
plugin.getLogger().info(ChatColor.AQUA + "| " +message);
|
||||
}
|
||||
}
|
||||
|
||||
public static void debug(Exception e) {
|
||||
if (plugin.getConfigManager().getDebug()) {
|
||||
plugin.getLogger().info(ChatColor.AQUA + "| " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void info(CommandSender sender, String message) {
|
||||
plugin.getLogger().info(ChatColor.GREEN + "[ 来自 " + sender.getName() + " 的消息 ] " + message);
|
||||
sender.sendMessage(ChatColor.GREEN + "| " +message);
|
||||
}
|
||||
|
||||
public static void warn(CommandSender sender, String message) {
|
||||
plugin.getLogger().warning(ChatColor.YELLOW + "[ 来自 " + sender.getName() + " 的消息 ] " + message);
|
||||
sender.sendMessage(ChatColor.YELLOW + "| " +message);
|
||||
}
|
||||
|
||||
public static void error(CommandSender sender, String message) {
|
||||
plugin.getLogger().severe(ChatColor.RED + "[ 来自 " + sender.getName() + " 的消息 ] " + message);
|
||||
sender.sendMessage(ChatColor.RED + "| " +message);
|
||||
}
|
||||
|
||||
public static void debug(CommandSender sender, String message) {
|
||||
if (plugin.getConfigManager().getDebug()) {
|
||||
plugin.getLogger().info(ChatColor.AQUA + "[ 来自 " + sender.getName() + " 的消息 ] " + message);
|
||||
sender.sendMessage(ChatColor.AQUA + "| " +message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package site.deercloud.identityverification.Utils;
|
||||
|
||||
public class RandomCode {
|
||||
public static String NewCodeWithAlphabet(int length) {
|
||||
StringBuilder code = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
int random = (int) (Math.random() * 36);
|
||||
if (random < 10) {
|
||||
code.append(random);
|
||||
} else {
|
||||
code.append((char) (random + 87));
|
||||
}
|
||||
}
|
||||
return code.toString();
|
||||
}
|
||||
|
||||
public static String NewCodeOnlyNumber(int length) {
|
||||
StringBuilder code = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
int random = (int) (Math.random() * 10);
|
||||
code.append(random);
|
||||
}
|
||||
return code.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package site.deercloud.identityverification.Utils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringWriter;
|
||||
import java.security.*;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
public class SignatureUtil {
|
||||
private final static String SIGN_TYPE_RSA = "RSA";
|
||||
private final static String SIGN_ALGORITHMS = "SHA1WithRSA";
|
||||
private final static String CHARSETTING = "UTF-8";
|
||||
|
||||
/**
|
||||
* 获取私钥PKCS8格式(需base64)
|
||||
* @param algorithm
|
||||
* @param priKey
|
||||
* @return PrivateKey
|
||||
* @throws Exception
|
||||
*/
|
||||
public static PrivateKey getPrivateKeyFromPKCS8(String algorithm, String priKey) throws Exception {
|
||||
if (algorithm == null || "".equals(algorithm) || priKey == null || "".equals(priKey))
|
||||
return null;
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
|
||||
|
||||
byte[] encodedKey = StreamUtil.readText(new ByteArrayInputStream(priKey.getBytes())).getBytes();
|
||||
encodedKey = Base64.getDecoder().decode(priKey.getBytes());
|
||||
|
||||
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过证书获取公钥(需BASE64,X509为通用证书标准)
|
||||
* @param algorithm
|
||||
* @param pubKey
|
||||
* @return PublicKey
|
||||
* @throws Exception
|
||||
*/
|
||||
public static PublicKey getPublicKeyFromX509(String algorithm, String pubKey) throws Exception {
|
||||
|
||||
if (algorithm == null || "".equals(algorithm) || pubKey == null || "".equals(pubKey))
|
||||
return null;
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
|
||||
StringWriter writer = new StringWriter();
|
||||
StreamUtil.io(new InputStreamReader(new ByteArrayInputStream(pubKey.getBytes())), writer);
|
||||
|
||||
byte[] encodeByte = writer.toString().getBytes();
|
||||
encodeByte = Base64.getDecoder().decode(pubKey.getBytes());
|
||||
|
||||
return keyFactory.generatePublic(new X509EncodedKeySpec(encodeByte));
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用私钥对字符进行签名
|
||||
* @param plain 内容体
|
||||
* @param prikey 私钥
|
||||
* @return String
|
||||
* @throws Exception
|
||||
*/
|
||||
public static String sign(String plain, String prikey) throws Exception {
|
||||
if (plain == null || "".equals(plain) || prikey == null || "".equals(prikey))
|
||||
return null;
|
||||
|
||||
PrivateKey privatekey = getPrivateKeyFromPKCS8(SIGN_TYPE_RSA, prikey);
|
||||
Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
|
||||
signature.initSign(privatekey);
|
||||
signature.update(plain.getBytes(CHARSETTING));
|
||||
byte[] signed = signature.sign();
|
||||
|
||||
return new String(Base64.getEncoder().encode(signed));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将内容体、签名信息、及对方公钥进行验签
|
||||
* @param plain 内容体
|
||||
* @param sign 签名信息
|
||||
* @param pubkey 对方公钥
|
||||
* @return boolean
|
||||
* @throws Exception
|
||||
*/
|
||||
public static boolean virefy(String plain, String sign, String pubkey) throws Exception {
|
||||
if (plain == null || "".equals(plain) || sign == null || "".equals(sign) || pubkey == null || "".equals(pubkey))
|
||||
return false;
|
||||
|
||||
PublicKey publicKey = getPublicKeyFromX509(SIGN_TYPE_RSA, pubkey);
|
||||
Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
|
||||
signature.initVerify(publicKey);
|
||||
signature.update(plain.getBytes(CHARSETTING));
|
||||
|
||||
return signature.verify(Base64.getDecoder().decode(sign.getBytes()));
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package site.deercloud.identityverification.Utils;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class StreamUtil {
|
||||
private static final int DEFAULT_BUFFER_SIZE = 8192;
|
||||
|
||||
public static void io(InputStream in, OutputStream out) throws IOException {
|
||||
io(in, out, -1);
|
||||
}
|
||||
|
||||
public static void io(InputStream in, OutputStream out, int bufferSize) throws IOException {
|
||||
if (bufferSize == -1) {
|
||||
bufferSize = DEFAULT_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
int amount;
|
||||
|
||||
while ((amount = in.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, amount);
|
||||
}
|
||||
}
|
||||
|
||||
public static void io(Reader in, Writer out) throws IOException {
|
||||
io(in, out, -1);
|
||||
}
|
||||
|
||||
public static void io(Reader in, Writer out, int bufferSize) throws IOException {
|
||||
if (bufferSize == -1) {
|
||||
bufferSize = DEFAULT_BUFFER_SIZE >> 1;
|
||||
}
|
||||
|
||||
char[] buffer = new char[bufferSize];
|
||||
int amount;
|
||||
|
||||
while ((amount = in.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, amount);
|
||||
}
|
||||
}
|
||||
|
||||
public static String readText(InputStream in) throws IOException {
|
||||
return readText(in, null, -1);
|
||||
}
|
||||
|
||||
public static String readText(InputStream in, String encoding) throws IOException {
|
||||
return readText(in, encoding, -1);
|
||||
}
|
||||
|
||||
public static String readText(InputStream in, String encoding, int bufferSize)
|
||||
throws IOException {
|
||||
Reader reader = (encoding == null) ? new InputStreamReader(in) : new InputStreamReader(in,
|
||||
encoding);
|
||||
|
||||
return readText(reader, bufferSize);
|
||||
}
|
||||
|
||||
public static String readText(Reader reader) throws IOException {
|
||||
return readText(reader, -1);
|
||||
}
|
||||
|
||||
public static String readText(Reader reader, int bufferSize) throws IOException {
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
io(reader, writer, bufferSize);
|
||||
return writer.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
package site.deercloud.identityverification.Utils;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.*;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static Map<String,String> ParseQueryString(String formData ) {
|
||||
Map<String,String> result = new HashMap<>();
|
||||
if(formData== null || formData.trim().length() == 0) {
|
||||
return result;
|
||||
}
|
||||
final String[] items = formData.split("&");
|
||||
Arrays.stream(items).forEach(item ->{
|
||||
final String[] keyAndVal = item.split("=");
|
||||
if( keyAndVal.length == 2) {
|
||||
try{
|
||||
final String key = URLDecoder.decode( keyAndVal[0],"utf8");
|
||||
final String val = URLDecoder.decode( keyAndVal[1],"utf8");
|
||||
result.put(key,val);
|
||||
}catch (UnsupportedEncodingException ignored) {}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据玩家名字从Mojiang服务器获取UUID,如果不存在则返回null
|
||||
* @param name 玩家名字
|
||||
*/
|
||||
public static String getUUIDFromRemote(String name) throws IOException {
|
||||
String url = "https://".concat("api.")
|
||||
.concat("mojang.")
|
||||
.concat("com/")
|
||||
.concat("users/")
|
||||
.concat("profiles/")
|
||||
.concat("minecraft/")
|
||||
.concat(name);
|
||||
String reply = sendGet(url);
|
||||
if (reply == null){
|
||||
return null;
|
||||
}
|
||||
JSONObject jsonObject = JSONObject.parseObject(reply);
|
||||
return jsonObject.getString("id");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据UUID从Mojiang服务器获取玩家正版信息 如果失败则返回null
|
||||
* @param uuid 玩家UUID
|
||||
*/
|
||||
public static JSONObject getProfileFromRemote(String uuid) throws IOException {
|
||||
String url = "https://".concat("sessionserver.")
|
||||
.concat("mojang.")
|
||||
.concat("com/")
|
||||
.concat("session/")
|
||||
.concat("minecraft/")
|
||||
.concat("profile/")
|
||||
.concat(uuid);
|
||||
if (!isUUIDExist(uuid)) {
|
||||
return null;
|
||||
}
|
||||
JSONObject jsonObject = JSONObject.parseObject(sendGet(url));
|
||||
if (jsonObject == null){
|
||||
return null;
|
||||
}
|
||||
String profile_base64 = jsonObject.getJSONArray("properties").getObject(0, JSONObject.class).getString("value");
|
||||
String profile = new String(Base64.getDecoder().decode(profile_base64));
|
||||
return JSONObject.parseObject(profile);
|
||||
}
|
||||
|
||||
public static Boolean isUUIDExist(String uuid) throws IOException {
|
||||
String url = "https://".concat("sessionserver.")
|
||||
.concat("mojang.")
|
||||
.concat("com/")
|
||||
.concat("session/")
|
||||
.concat("minecraft/")
|
||||
.concat("profile/")
|
||||
.concat(uuid);
|
||||
String reply = sendGet(url);
|
||||
if (reply == null){
|
||||
return false;
|
||||
}
|
||||
return reply.contains("id");
|
||||
}
|
||||
|
||||
public static byte[] getSkinFromRemote(String uuid) throws IOException {
|
||||
JSONObject jsonObject = getProfileFromRemote(uuid);
|
||||
String skin_base64 = jsonObject.getJSONObject("textures").getJSONObject("SKIN").getString("url");
|
||||
return Base64.getDecoder().decode(skin_base64);
|
||||
}
|
||||
|
||||
public static String sendGet(String url) throws IOException {
|
||||
URL obj = new URL(url);
|
||||
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
|
||||
|
||||
//默认值GET
|
||||
con.setRequestMethod("GET");
|
||||
|
||||
//添加请求头
|
||||
con.setRequestProperty("User-Agent", "Mozilla/5.0");
|
||||
|
||||
int responseCode = con.getResponseCode();
|
||||
if (responseCode != 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
|
||||
String inputLine;
|
||||
StringBuilder response = new StringBuilder();
|
||||
|
||||
while ((inputLine = in.readLine()) != null) {
|
||||
response.append(inputLine);
|
||||
}
|
||||
in.close();
|
||||
|
||||
return response.toString();
|
||||
}
|
||||
|
||||
public static boolean isUUIDMatchName(String uuid, String name) throws IOException {
|
||||
String reply = getUUIDFromRemote(name);
|
||||
if (reply == null) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(reply, uuid);
|
||||
}
|
||||
}
|
41
src/main/resources/config.yml
Normal file
@ -0,0 +1,41 @@
|
||||
# 服务器基本信息
|
||||
Web:
|
||||
Host: "127.0.0.1"
|
||||
Port: 11520
|
||||
# 展示在网站的标题
|
||||
ServerName: "我的世界"
|
||||
# 服务器核心
|
||||
ImplementationName: "Bukkit"
|
||||
# 服务器版本
|
||||
ImplementationVersion: "1.19.2"
|
||||
# 用于展示的主页地址
|
||||
HomePageUrl: "https://example.com/"
|
||||
|
||||
|
||||
# Yggdrasil服务配置
|
||||
Yggdrasil:
|
||||
Host: "127.0.0.1"
|
||||
Port: 11521
|
||||
# 允许的皮肤站域名
|
||||
SkinDomains:
|
||||
- "example.com"
|
||||
- "example.org"
|
||||
# RSA 公钥文件路径
|
||||
RsaPublicKey: "id_rsa.pub"
|
||||
# RSA 私钥文件路径
|
||||
RsaPrivateKey: "id_rsa"
|
||||
|
||||
# 邮箱配置
|
||||
Email:
|
||||
# 邮箱服务器地址
|
||||
Host: "smtp.example.com"
|
||||
# 邮箱服务器端口
|
||||
Port: 465
|
||||
# 邮箱用户名
|
||||
Username: ""
|
||||
# 邮箱密码
|
||||
Password: ""
|
||||
# 邮箱发件人
|
||||
From: ""
|
||||
|
||||
Debug: true
|
9
src/main/resources/plugin.yml
Normal file
@ -0,0 +1,9 @@
|
||||
name: IdentityVerification
|
||||
version: '${project.version}'
|
||||
main: site.deercloud.identityverification.IdentityVerification
|
||||
api-version: 1.13
|
||||
prefix: IV
|
||||
load: STARTUP
|
||||
authors: [ Luming ]
|
||||
description: 实名认证添加白名单、正版玩家邀请非正版注册管理。
|
||||
website: https://blog.deercloud.site
|
408
src/main/resources/web/1234-1.css
Normal file
@ -0,0 +1,408 @@
|
||||
.u-section-1 {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.u-section-1 .u-sheet-1 {
|
||||
min-height: 1108px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-1 {
|
||||
text-transform: uppercase;
|
||||
font-size: 1.25rem;
|
||||
--animation-custom_in-scale: 1;
|
||||
--animation-custom_in-translate_x: 0px;
|
||||
--animation-custom_in-translate_y: -300px;
|
||||
margin: 90px 405px 0 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-2 {
|
||||
font-size: 4rem;
|
||||
--animation-custom_in-translate_x: 0px;
|
||||
--animation-custom_in-translate_y: -300px;
|
||||
--animation-custom_in-opacity: 0;
|
||||
--animation-custom_in-rotate: 0deg;
|
||||
--animation-custom_in-scale: 1;
|
||||
font-weight: 500;
|
||||
margin: 42px 335px 0 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-3 {
|
||||
line-height: 2;
|
||||
--animation-custom_in-translate_x: 0px;
|
||||
--animation-custom_in-translate_y: 300px;
|
||||
--animation-custom_in-opacity: 0;
|
||||
--animation-custom_in-rotate: 0deg;
|
||||
--animation-custom_in-scale: 1;
|
||||
margin: 20px 327px 0 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-4 {
|
||||
--animation-custom_in-scale: 1;
|
||||
--animation-custom_in-translate_x: 0px;
|
||||
--animation-custom_in-translate_y: 300px;
|
||||
margin: 35px 503px 0 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-btn-1 {
|
||||
margin-left: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-list-1 {
|
||||
margin-top: 52px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-repeater-1 {
|
||||
grid-template-columns: repeat(4, calc(25% - 22.5px));
|
||||
min-height: 273px;
|
||||
grid-gap: 30px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-list-item-1 {
|
||||
background-image: none;
|
||||
--animation-custom_in-translate_x: 0px;
|
||||
--animation-custom_in-translate_y: 300px;
|
||||
--animation-custom_in-opacity: 0;
|
||||
--animation-custom_in-rotate: 0deg;
|
||||
--animation-custom_in-scale: 1;
|
||||
box-shadow: 5px 5px 29px 0 rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-1 {
|
||||
padding: 50px 30px 30px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-icon-1 {
|
||||
/* height: 59px; */
|
||||
width: 80%;
|
||||
background-image: none;
|
||||
--animation-custom_in-translate_x: 0px;
|
||||
--animation-custom_in-translate_y: 0px;
|
||||
--animation-custom_in-opacity: 0;
|
||||
--animation-custom_in-rotate: 180deg;
|
||||
--animation-custom_in-scale: 1;
|
||||
margin: 0 auto 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-5 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 400;
|
||||
margin: 31px 0 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-btn-2 {
|
||||
font-size: 0.875rem;
|
||||
background-image: none;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-list-item-2 {
|
||||
--animation-custom_in-translate_x: 0px;
|
||||
--animation-custom_in-translate_y: 300px;
|
||||
--animation-custom_in-opacity: 0;
|
||||
--animation-custom_in-rotate: 0deg;
|
||||
--animation-custom_in-scale: 1;
|
||||
box-shadow: 5px 5px 29px 0 rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-2 {
|
||||
padding: 50px 30px 30px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-icon-2 {
|
||||
/* height: 59px; */
|
||||
width: 80%;
|
||||
background-image: none;
|
||||
--animation-custom_in-translate_x: 0px;
|
||||
--animation-custom_in-translate_y: 0px;
|
||||
--animation-custom_in-opacity: 0;
|
||||
--animation-custom_in-rotate: 180deg;
|
||||
--animation-custom_in-scale: 1;
|
||||
margin: 0 auto 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-6 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 400;
|
||||
margin: 31px 0 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-btn-3 {
|
||||
font-size: 0.875rem;
|
||||
background-image: none;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-list-item-3 {
|
||||
--animation-custom_in-translate_x: 0px;
|
||||
--animation-custom_in-translate_y: 300px;
|
||||
--animation-custom_in-opacity: 0;
|
||||
--animation-custom_in-rotate: 0deg;
|
||||
--animation-custom_in-scale: 1;
|
||||
box-shadow: 5px 5px 29px 0 rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-3 {
|
||||
padding: 50px 30px 30px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-icon-3 {
|
||||
/* height: 59px; */
|
||||
width: 80%;
|
||||
--animation-custom_in-translate_x: 0px;
|
||||
--animation-custom_in-translate_y: 0px;
|
||||
--animation-custom_in-opacity: 0;
|
||||
--animation-custom_in-rotate: 180deg;
|
||||
--animation-custom_in-scale: 1;
|
||||
margin: 0 auto 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-7 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 400;
|
||||
margin: 31px 0 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-btn-4 {
|
||||
font-size: 0.875rem;
|
||||
background-image: none;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-list-item-4 {
|
||||
--animation-custom_in-translate_x: 0px;
|
||||
--animation-custom_in-translate_y: 300px;
|
||||
--animation-custom_in-opacity: 0;
|
||||
--animation-custom_in-rotate: 0deg;
|
||||
--animation-custom_in-scale: 1;
|
||||
box-shadow: 5px 5px 29px 0px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-4 {
|
||||
padding: 50px 30px 30px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-icon-4 {
|
||||
/* height: 59px; */
|
||||
width: 80%;
|
||||
background-image: none;
|
||||
--animation-custom_in-translate_x: 0px;
|
||||
--animation-custom_in-translate_y: 0px;
|
||||
--animation-custom_in-opacity: 0;
|
||||
--animation-custom_in-rotate: 180deg;
|
||||
--animation-custom_in-scale: 1;
|
||||
margin: 0 auto 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-8 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 400;
|
||||
margin: 31px 0 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-btn-5 {
|
||||
font-size: 0.875rem;
|
||||
background-image: none;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-group-1 {
|
||||
min-height: 200px;
|
||||
margin-top: 52px;
|
||||
margin-bottom: 60px;
|
||||
height: auto;
|
||||
--animation-custom_in-translate_x: 0px;
|
||||
--animation-custom_in-translate_y: 300px;
|
||||
--animation-custom_in-opacity: 0;
|
||||
--animation-custom_in-rotate: 0deg;
|
||||
--animation-custom_in-scale: 1;
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-5 {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-9 {
|
||||
font-size: 3rem;
|
||||
margin: 0 auto 0 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-btn-6 {
|
||||
border-style: solid;
|
||||
letter-spacing: 0px;
|
||||
font-size: 0.875rem;
|
||||
align-self: center;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
margin: 15px auto 0 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1199px) {
|
||||
.u-section-1 .u-text-1 {
|
||||
margin-right: 205px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-2 {
|
||||
margin-right: 135px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-3 {
|
||||
margin-right: 127px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-4 {
|
||||
margin-right: 303px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-repeater-1 {
|
||||
min-height: 221px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-1 {
|
||||
padding-top: 40px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-group-1 {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.u-section-1 .u-sheet-1 {
|
||||
min-height: 1439px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-1 {
|
||||
margin-top: 60px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-2 {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-3 {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-4 {
|
||||
margin-right: 83px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-repeater-1 {
|
||||
grid-template-columns: repeat(2, calc(50% - 15px));
|
||||
min-height: 557px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-1 {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-2 {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-3 {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-4 {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-group-1 {
|
||||
min-height: 302px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.u-section-1 .u-text-4 {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.u-section-1 .u-repeater-1 {
|
||||
row-gap: 30px;
|
||||
column-gap: 30px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-2 {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-3 {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-4 {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-5 {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-9 {
|
||||
font-size: 2.25rem;
|
||||
}
|
||||
|
||||
.u-section-1 .u-btn-6 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
.u-section-1 .u-text-2 {
|
||||
font-size: 2.25rem;
|
||||
}
|
||||
|
||||
.u-section-1 .u-repeater-1 {
|
||||
grid-template-columns: 100%;
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-2 {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-3 {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-container-layout-4 {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.u-section-1 .u-text-9 {
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
}
|
BIN
src/main/resources/web/images/3159310-2d4dee0c.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
src/main/resources/web/images/3159310.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
src/main/resources/web/images/4425412-7414f85f.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
src/main/resources/web/images/4425412.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/main/resources/web/images/4552640-12d08546.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
src/main/resources/web/images/4552640.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
src/main/resources/web/images/8087597-10b05fb6.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
src/main/resources/web/images/8087597.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/main/resources/web/images/DeerLogo.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
src/main/resources/web/images/bbs.png
Normal file
After Width: | Height: | Size: 900 KiB |
BIN
src/main/resources/web/images/blog.png
Normal file
After Width: | Height: | Size: 3.9 MiB |
BIN
src/main/resources/web/images/default-logo.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
src/main/resources/web/images/image_bed.png
Normal file
After Width: | Height: | Size: 1002 KiB |
BIN
src/main/resources/web/images/mc.png
Normal file
After Width: | Height: | Size: 7.2 MiB |
BIN
src/main/resources/web/images/pan.png
Normal file
After Width: | Height: | Size: 2.8 MiB |
99
src/main/resources/web/index.html
Normal file
@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="font-size: 16px;" lang="en"><head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta charset="utf-8">
|
||||
<meta name="keywords" content="Hello!">
|
||||
<meta name="description" content="">
|
||||
<title>Deer Portal</title>
|
||||
<link rel="stylesheet" href="nicepage.css" media="screen">
|
||||
<link rel="stylesheet" href="1234-1.css" media="screen">
|
||||
<script class="u-script" type="text/javascript" src="jquery.js" defer=""></script>
|
||||
<script class="u-script" type="text/javascript" src="nicepage.js" defer=""></script>
|
||||
<meta name="generator" content="Nicepage 4.20.3, nicepage.com">
|
||||
<link id="u-theme-google-font" rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat:100,100i,200,200i,300,300i,400,400i,500,500i,600,600i,700,700i,800,800i,900,900i|Open+Sans:300,300i,400,400i,500,500i,600,600i,700,700i,800,800i">
|
||||
|
||||
|
||||
<script type="application/ld+json">{
|
||||
"@context": "http://schema.org",
|
||||
"@type": "Organization",
|
||||
"name": ""
|
||||
}</script>
|
||||
<meta name="theme-color" content="#0066ff">
|
||||
<meta property="og:title" content="1234 1">
|
||||
<meta property="og:type" content="website">
|
||||
</head>
|
||||
<body data-home-page="" data-home-page-title="" class="u-body u-xl-mode" data-lang="en">
|
||||
<section class="u-align-left u-clearfix u-grey-5 u-section-1" id="carousel_0852">
|
||||
<div class="u-clearfix u-sheet u-valign-middle-md u-valign-middle-sm u-valign-middle-xs u-sheet-1">
|
||||
<h4 class="u-text u-text-1" data-animation-name="customAnimationIn" data-animation-duration="1500"> Deer Cloud Station Portal</h4>
|
||||
<h2 class="u-text u-text-2" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="500"> 👏欢迎来到鹿鸣导航站🧭 </h2>
|
||||
<p class="u-text u-text-3" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="500"> 因为一些神秘力量,为了便于访问,所有鹿站现尽量通过此门户页面跳转。各站真实地址不定期进行更新,或许一段时间后可以恢复直链访问吧。🤔 </p>
|
||||
<p class="u-text u-text-4" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="500">如果跳转错误 <a href="" class="u-border-1 u-border-active-palette-2-base u-border-black u-border-hover-palette-2-base u-border-no-left u-border-no-right u-border-no-top u-bottom-left-radius-0 u-bottom-right-radius-0 u-btn u-button-link u-button-style u-none u-radius-0 u-text-body-color u-top-left-radius-0 u-top-right-radius-0 u-btn-1" target="_blank">点我反馈</a>
|
||||
</p>
|
||||
<div class="u-expanded-width u-list u-list-1">
|
||||
<div class="u-repeater u-repeater-1">
|
||||
<div class="u-align-left u-border-10 u-border-no-left u-border-no-right u-border-no-top u-border-palette-2-base u-container-style u-custom-item u-list-item u-repeater-item u-shape-rectangle u-white u-list-item-1" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="250">
|
||||
<div class="u-container-layout u-similar-container u-valign-top u-container-layout-1"><span class="u-custom-item u-file-icon u-icon u-text-palette-2-base u-icon-1" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="750"><img src="images/blog.png" alt=""></span>
|
||||
<h4 class="u-text u-text-5"> 鹿鸣的博客</h4>
|
||||
<a href="https://sslbackend.deercloud.site:446" class="u-align-center u-border-2 u-border-black u-btn u-button-style u-hover-black u-none u-text-active-palette-1-base u-text-black u-text-hover-white u-btn-6" data-animation-name="" data-animation-duration="0" data-animation-delay="0" data-animation-direction="">前往 -></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-align-left u-border-10 u-border-no-left u-border-no-right u-border-no-top u-border-palette-3-base u-container-style u-custom-item u-list-item u-repeater-item u-shape-rectangle u-video-cover u-white u-list-item-2" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="250">
|
||||
<div class="u-container-layout u-similar-container u-valign-top u-container-layout-2"><span class="u-custom-item u-file-icon u-icon u-text-palette-5-base u-icon-1" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="750"><img src="images/mc.png" alt=""></span>
|
||||
<h4 class="u-text u-text-6"> 白鹿原官网</h4>
|
||||
<a href="https://sslbackend.deercloud.site:447" class="u-align-center u-border-2 u-border-black u-btn u-button-style u-hover-black u-none u-text-active-palette-1-base u-text-black u-text-hover-white u-btn-6" data-animation-name="" data-animation-duration="0" data-animation-delay="0" data-animation-direction="">前往 -></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-align-left u-border-10 u-border-no-left u-border-no-right u-border-no-top u-border-palette-5-base u-container-style u-custom-item u-list-item u-repeater-item u-shape-rectangle u-video-cover u-white u-list-item-2" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="250">
|
||||
<div class="u-container-layout u-similar-container u-valign-top u-container-layout-2"><span class="u-custom-item u-file-icon u-icon u-text-palette-5-base u-icon-1" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="750"><img src="images/image_bed.png" alt=""></span>
|
||||
<h4 class="u-text u-text-6"> 鹿的图床站</h4>
|
||||
<a href="https://sslbackend.deercloud.site:450" class="u-align-center u-border-2 u-border-black u-btn u-button-style u-hover-black u-none u-text-active-palette-1-base u-text-black u-text-hover-white u-btn-6" data-animation-name="" data-animation-duration="0" data-animation-delay="0" data-animation-direction="">前往 -></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="u-align-left u-border-10 u-border-no-left u-border-no-right u-border-no-top u-border-palette-4-base u-container-style u-custom-item u-list-item u-repeater-item u-shape-rectangle u-video-cover u-white u-list-item-2" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="250">
|
||||
<div class="u-container-layout u-similar-container u-valign-top u-container-layout-2"><span class="u-custom-item u-file-icon u-icon u-text-palette-5-base u-icon-1" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="750"><img src="images/pan.png" alt=""></span>
|
||||
<h4 class="u-text u-text-6"> 云盘</h4>
|
||||
<a href="https://sslbackend.deercloud.site:452" class="u-align-center u-border-2 u-border-black u-btn u-button-style u-hover-black u-none u-text-active-palette-1-base u-text-black u-text-hover-white u-btn-6" data-animation-name="" data-animation-duration="0" data-animation-delay="0" data-animation-direction="">前往 -></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="u-align-left u-border-10 u-border-no-left u-border-no-right u-border-no-top u-border-palette-2-base u-container-style u-custom-item u-list-item u-repeater-item u-shape-rectangle u-video-cover u-white u-list-item-2" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="250">
|
||||
<div class="u-container-layout u-similar-container u-valign-top u-container-layout-2"><span class="u-custom-item u-file-icon u-icon u-text-palette-5-base u-icon-1" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="750"><img src="images/bbs.png" alt=""></span>
|
||||
<h4 class="u-text u-text-6"> 论坛 - (alpha)</h4>
|
||||
<a href="https://sslbackend.deercloud.site:449" class="u-align-center u-border-2 u-border-black u-btn u-button-style u-hover-black u-none u-text-active-palette-1-base u-text-black u-text-hover-white u-btn-6" data-animation-name="" data-animation-duration="0" data-animation-delay="0" data-animation-direction="">前往 -></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- <div class="u-align-left u-border-10 u-border-no-left u-border-no-right u-border-no-top u-border-palette-3-base u-container-style u-custom-item u-list-item u-repeater-item u-shape-rectangle u-video-cover u-white u-list-item-4" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="250">
|
||||
<div class="u-container-layout u-similar-container u-valign-top u-container-layout-4"><span class="u-custom-item u-file-icon u-icon u-text-palette-3-base u-icon-1" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="750"><img src="images/8087597-10b05fb6.png" alt=""></span>
|
||||
<h4 class="u-text u-text-8"> Application</h4>
|
||||
<a href="https://nicepage.com/k/presentation-html-templates" class="u-border-1 u-border-active-grey-70 u-border-black u-border-hover-grey-70 u-border-no-left u-border-no-right u-border-no-top u-bottom-left-radius-0 u-bottom-right-radius-0 u-btn u-button-style u-custom-item u-none u-radius-0 u-text-active-palette-3-base u-text-body-color u-text-hover-palette-3-base u-top-left-radius-0 u-top-right-radius-0 u-btn-5">more</a>
|
||||
</div>
|
||||
</div> -->
|
||||
<!-- <div class="u-align-left u-border-10 u-border-no-left u-border-no-right u-border-no-top u-border-palette-3-base u-container-style u-custom-item u-list-item u-repeater-item u-shape-rectangle u-video-cover u-white u-list-item-4" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="250">
|
||||
<div class="u-container-layout u-similar-container u-valign-top u-container-layout-4"><span class="u-custom-item u-file-icon u-icon u-text-palette-3-base u-icon-1" data-animation-name="customAnimationIn" data-animation-duration="1500" data-animation-delay="750"><img src="images/8087597-10b05fb6.png" alt=""></span>
|
||||
<h4 class="u-text u-text-8"> Application</h4>
|
||||
<a href="https://nicepage.com/k/presentation-html-templates" class="u-border-1 u-border-active-grey-70 u-border-black u-border-hover-grey-70 u-border-no-left u-border-no-right u-border-no-top u-bottom-left-radius-0 u-bottom-right-radius-0 u-btn u-button-style u-custom-item u-none u-radius-0 u-text-active-palette-3-base u-text-body-color u-text-hover-palette-3-base u-top-left-radius-0 u-top-right-radius-0 u-btn-5">more</a>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-align-left u-container-style u-expanded-width u-group u-palette-1-light-3 u-group-1" data-animation-name="customAnimationIn" data-animation-duration="1750" data-animation-delay="500">
|
||||
<div class="u-container-layout u-valign-middle u-container-layout-5">
|
||||
<h3 class="u-text u-text-default u-text-9"> 🪧广告位招租~</h3>
|
||||
<a href="" class="u-border-1 u-border-active-grey-70 u-border-black u-border-hover-grey-70 u-border-no-left u-border-no-right u-border-no-top u-bottom-left-radius-0 u-bottom-right-radius-0 u-btn u-button-style u-custom-item u-none u-radius-0 u-text-active-palette-5-base u-text-body-color u-text-hover-palette-5-base u-top-left-radius-0 u-top-right-radius-0 u-btn-3">了解详情</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<section class="u-backlink u-clearfix u-grey-80">
|
||||
Copyright © All rights reserved | <a href="https://deercloud.site" target="_blank">成都市鹿月网络科技有限公司</a> | <a href="http://beian.miit.gov.cn/">蜀ICP备2022004667号-1</a>
|
||||
</section>
|
||||
|
||||
</body></html>
|
5
src/main/resources/web/jquery-1.9.1.min.js
vendored
Normal file
38310
src/main/resources/web/nicepage.css
Normal file
42
src/main/resources/web/nicepage.js
Normal file
28
src/web/.gitignore
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
46
src/web/README.md
Normal file
@ -0,0 +1,46 @@
|
||||
# web
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
|
||||
|
||||
1. Disable the built-in TypeScript Extension
|
||||
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
||||
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
||||
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Run Unit Tests with [Vitest](https://vitest.dev/)
|
||||
|
||||
```sh
|
||||
npm run test:unit
|
||||
```
|
1
src/web/env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
13
src/web/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
5646
src/web/package-lock.json
generated
Normal file
33
src/web/package.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check build-only",
|
||||
"preview": "vite preview",
|
||||
"test:unit": "vitest --environment jsdom --root src/",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false"
|
||||
},
|
||||
"dependencies": {
|
||||
"unplugin-auto-import": "^0.12.0",
|
||||
"vue": "^3.2.45",
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsdom": "^20.0.1",
|
||||
"@types/node": "^18.11.9",
|
||||
"@vitejs/plugin-vue": "^3.2.0",
|
||||
"@vue/test-utils": "^2.2.4",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"jsdom": "^20.0.3",
|
||||
"naive-ui": "^2.34.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"typescript": "~4.7.4",
|
||||
"vfonts": "^0.0.3",
|
||||
"vite": "^3.2.4",
|
||||
"vitest": "^0.25.3",
|
||||
"vue-tsc": "^1.0.9"
|
||||
}
|
||||
}
|
BIN
src/web/public/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
39
src/web/src/App.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<header>
|
||||
<RouterLink to="/">主页</RouterLink> |
|
||||
<RouterLink to="/reg">注册</RouterLink>
|
||||
</header>
|
||||
<main>
|
||||
<RouterView />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { RouterLink, RouterView } from 'vue-router'
|
||||
import { NMenu, NGrid, NGridItem, NLayout, NLayoutHeader, NLayoutContent, NLayoutFooter } from 'naive-ui';
|
||||
import type { MenuOption } from 'naive-ui'
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
header {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0%;
|
||||
color: rgb(105, 105, 105);
|
||||
padding: 1em;
|
||||
padding-left: 5rem;
|
||||
background-color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
main {
|
||||
margin: auto;
|
||||
padding: 1em;
|
||||
position: flex;
|
||||
}
|
||||
|
||||
</style>
|
74
src/web/src/assets/base.css
Normal file
@ -0,0 +1,74 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition: color 0.5s, background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||
Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
1
src/web/src/assets/logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69" xmlns:v="https://vecta.io/nano"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
After Width: | Height: | Size: 308 B |
36
src/web/src/assets/main.css
Normal file
@ -0,0 +1,36 @@
|
||||
@import './base.css';
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 0rem;
|
||||
margin-top: 5rem;
|
||||
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
a,
|
||||
.green {
|
||||
text-decoration: none;
|
||||
color: hsla(160, 100%, 37%, 1);
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
a:hover {
|
||||
background-color: hsla(160, 100%, 37%, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
margin: auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
}
|
71
src/web/src/components/Registration.vue
Normal file
@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<n-form>
|
||||
<n-grid :x-gap="12">
|
||||
<n-grid-item span="24">
|
||||
<n-form-item label="电子邮箱">
|
||||
<n-input placeholder="" v-model:value="email" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item span="12">
|
||||
<n-form-item label="验证码">
|
||||
<n-input placeholder="" v-model:value="email_code" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item span="12">
|
||||
<n-form-item label=" ">
|
||||
<n-button @click="send_email_code" style="width: 100%">{{send_code_text}}</n-button>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item span="24">
|
||||
<n-form-item label="密码">
|
||||
<n-input type="password" show-password-on="click" placeholder="" v-model:value="password" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item span="12">
|
||||
<n-form-item label="邀请码">
|
||||
<n-input placeholder="" v-model:value="invite_code" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item span="12">
|
||||
<n-form-item label=" ">
|
||||
<n-button @click="send_email_code" style="width: 100%">{{verify_invite_code_text}}</n-button>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item span="24">
|
||||
<n-form-item>
|
||||
<n-button type="primary" @click="onSubmit" style="width: 100%;">注册外置登录</n-button>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { NButton, NForm, NFormItem, NInput, NGrid, NGridItem } from 'naive-ui'
|
||||
|
||||
const email = ref('')
|
||||
const email_code = ref('')
|
||||
const password = ref('')
|
||||
const invite_code = ref('')
|
||||
|
||||
const send_code_text = ref('发送验证码')
|
||||
const verify_invite_code_text = ref('验证邀请码')
|
||||
|
||||
|
||||
const onSubmit = () => {
|
||||
console.log(email.value, email_code.value, password.value)
|
||||
}
|
||||
|
||||
const send_email_code = () => {
|
||||
console.log('send email code')
|
||||
}
|
||||
|
||||
|
||||
</script>
|
26
src/web/src/components/Steps.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<n-steps :current="(current as number)" :status="currentStatus">
|
||||
<n-step
|
||||
title="注册账号"
|
||||
description="如果你有正版账号可以跳过这一步"
|
||||
/>
|
||||
<n-step
|
||||
title="实名认证"
|
||||
description="实名认证以登记服务器白名单"
|
||||
/>
|
||||
<n-step
|
||||
title="完成!"
|
||||
description="准备就绪,欢迎加入我们~"
|
||||
/>
|
||||
</n-steps>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import { NStep, NSteps } from 'naive-ui';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const current = ref(1)
|
||||
const currentStatus = ref('process')
|
||||
|
||||
</script>
|
11
src/web/src/components/__tests__/HelloWorld.spec.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
|
||||
import { mount } from '@vue/test-utils'
|
||||
import HelloWorld from '../HelloWorld.vue'
|
||||
|
||||
describe('HelloWorld', () => {
|
||||
it('renders properly', () => {
|
||||
const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
|
||||
expect(wrapper.text()).toContain('Hello Vitest')
|
||||
})
|
||||
})
|
7
src/web/src/components/icons/IconCommunity.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
7
src/web/src/components/icons/IconDocumentation.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
||||
<path
|
||||
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
7
src/web/src/components/icons/IconEcosystem.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
7
src/web/src/components/icons/IconSupport.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
19
src/web/src/components/icons/IconTooling.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
class="iconify iconify--mdi"
|
||||
width="24"
|
||||
height="24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
12
src/web/src/main.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import naive from 'naive-ui'
|
||||
|
||||
import './assets/main.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|