实现了挂机时间的追踪以及活跃度计算

修改了Yag部分接口存在的bug

目前可以实现注册、登录游戏、生成邀请码、验证邀请码、统计在线时间活跃度
(注册时的身份证信息暂时没有做实名认证,仅仅进行存储与重复校验)

下一步计划加入实名认证接口及相关配置
This commit is contained in:
张宇衡 2022-12-09 15:16:53 +08:00
parent ab7afe3fc0
commit ed74cf9aca
44 changed files with 1923 additions and 1530 deletions

View File

@ -1,9 +1,24 @@
# IdentityVerification
外置登录与实名认证带web后台
## 功能介绍 TODO List
## 🔏 隐私声明
### 外置登录服务器
1. 本系统不会收集任何用户的个人信息包括但不限于姓名、身份证号、密码、IP地址、设备信息、浏览器信息、操作系统信息、地理位置信息等。
2. 实名认证调用由[苏州云亿互通信息服务有限公司](https://yunyidata.com)提供的身份证实名认证查询校验接口。
3. 本系统不会在后台保存任何身份证信息,仅对身份证号做哈希计算后(无法逆向得到真实身份证号码)存库用于唯一性验证。(可参见源码的[SignWhiteList接口](https://github.com/ColdeZhang/IdentityVerification/blob/main/src/main/java/site/deercloud/identityverification/HttpServer/Api/SignWhiteList.java)设计)
4. 任何使用本系统的个人或组织都有义务保证用户的个人信息安全,不得明文存储用户个人信息或将用户的个人信息泄露给任何第三方。
5. 使用本系统请确保从本项目的Release页面下载并校验md5或自行拉取源码编译不要下载任何第三方修改的版本否则可能会造成信息泄露。
## 🧾 功能介绍 TODO List
### 系统功能部分
- [x] 非白名单拦截
- [x] 黑名单(封禁)拦截
- [x] 活跃度计算
### 外置登录服务部分
- [x] 用户登录
- [x] 刷新令牌
@ -16,10 +31,11 @@
- [ ] 材质(皮肤)上传/删除
- [ ] 多角色支持
### Web服务
### Web服务部分
- [x] 用户注册
- [ ] 邀请码生成
- [x] 邀请码生成、管理
- [x] 邮箱验证码
- [ ] 实名认证白名单
- [ ] 用户管理
- [ ] 用户登录

View File

@ -14,7 +14,7 @@ public class Commands implements TabExecutor {
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
switch (args[0]) {
case "reload":
IdentityVerification.getInstance().getConfigManager().reload(sender);
IdentityVerification.configManager.reload(sender);
break;
case "genCode":
// TODO: 需要校验玩家是否符合活跃度要求

View File

@ -1,8 +1,12 @@
package site.deercloud.identityverification.Controller;
import org.bukkit.entity.Player;
import site.deercloud.identityverification.HttpServer.model.GameSession;
import site.deercloud.identityverification.IdentityVerification;
import site.deercloud.identityverification.Utils.MyLogger;
import site.deercloud.identityverification.Utils.UnsignedUUID;
import java.text.SimpleDateFormat;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@ -24,74 +28,75 @@ public class AFKTracker {
public long getAfkThreshold() {
if (afkThresholdMs == null) {
afkThresholdMs = 1000L * 10L;
afkThresholdMs = 1000L * 30L;
}
return afkThresholdMs;
}
public void hasIgnorePermission(String playerUUID) {
storeLastMovement(playerUUID, IGNORES_AFK);
public void hasIgnorePermission(Player player) {
storeLastMovement(player, IGNORES_AFK);
}
private void storeLastMovement(String playerUUID, long time) {
GameSessionCache.getCacheSession(playerUUID)
.ifPresent(gameSession -> gameSession.setLastMovement(time));
private void storeLastMovement(Player player, long time) {
String playerUUID = UnsignedUUID.UnUUIDof(player);
GameSessionCache.getCacheSession(playerUUID).ifPresent(gameSession -> gameSession.setLastMovement(time));
}
private long getLastMovement(String playerUUID, long time) {
return getLastMovement(playerUUID)
.orElse(time);
private long getLastMovement(Player player, long time) {
return getLastMovement(player).orElse(time);
}
private Optional<Long> getLastMovement(String playerUUID) {
return GameSessionCache.getCacheSession(playerUUID)
.map(GameSession::getLastMovement);
private Optional<Long> getLastMovement(Player player) {
String playerUUID = UnsignedUUID.UnUUIDof(player);
return GameSessionCache.getCacheSession(playerUUID).map(GameSession::getLastMovement);
}
public void usedAfkCommand(String playerUUID, long time) {
long lastMoved = getLastMovement(playerUUID, time);
public void usedAfkCommand(Player player, long time) {
String playerUUID = UnsignedUUID.UnUUIDof(player);
long lastMoved = getLastMovement(player, time);
if (lastMoved == IGNORES_AFK) {
return;
}
usedAFKCommand.add(playerUUID);
storeLastMovement(playerUUID, time - getAfkThreshold());
storeLastMovement(player, time - getAfkThreshold());
}
public long performedAction(String playerUUID, long time) {
long lastMoved = getLastMovement(playerUUID, time);
public long performedAction(Player player, long time) {
String playerUUID = UnsignedUUID.UnUUIDof(player);
long lastMoved = getLastMovement(player, time);
// Ignore afk permission
if (lastMoved == IGNORES_AFK) {
return 0L;
}
storeLastMovement(playerUUID, time);
storeLastMovement(player, 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));
MyLogger.debug(player.getName() + " 本次挂机时间:" + timeAFK / 1000 / 60 + "" + time / 1000 % 60 + "");
GameSessionCache.getCacheSession(playerUUID).ifPresent(gameSession -> gameSession.addAfkTime(timeAFK));
return timeAFK;
} finally {
usedAFKCommand.remove(playerUUID);
}
}
public long loggedOut(String uuid, long time) {
long timeAFK = performedAction(uuid, time);
usedAFKCommand.remove(uuid);
public long loggedOut(Player player, long time) {
long timeAFK = performedAction(player, time);
String playerUUID = UnsignedUUID.UnUUIDof(player);
usedAFKCommand.remove(playerUUID);
return timeAFK;
}
public boolean isAfk(String playerUUID) {
public boolean isAfk(Player player) {
long time = System.currentTimeMillis();
Optional<Long> lastMoved = getLastMovement(playerUUID);
Optional<Long> lastMoved = getLastMovement(player);
if (lastMoved.isEmpty() || lastMoved.get() == IGNORES_AFK) {
return false;
}

View File

@ -0,0 +1,108 @@
package site.deercloud.identityverification.Controller;
import org.bukkit.entity.Player;
import site.deercloud.identityverification.HttpServer.model.ActiveIndex;
import site.deercloud.identityverification.HttpServer.model.PlayTimeStatics;
import site.deercloud.identityverification.SQLite.ActiveIndexDAO;
import site.deercloud.identityverification.SQLite.PlayTimeDAO;
import site.deercloud.identityverification.Utils.MyLogger;
import site.deercloud.identityverification.Utils.UnsignedUUID;
import java.sql.SQLException;
import java.util.ArrayList;
public class ActiveIndexManager {
public ActiveIndexManager() {
try {
ArrayList<ActiveIndex> activityRecords = ActiveIndexDAO.selectActivityRecords();
if (activityRecords.size() == 0) {
// 没有活跃玩家则默认每周游戏时间2小时
playtimeMsThreshold = 1000L * 60 * 60 * 2;
} else {
// 计算活跃玩家的平均每周游戏时间
Long sum = 0L;
for (ActiveIndex record : activityRecords) {
sum += record.recentWeek;
}
playtimeMsThreshold = sum / activityRecords.size();
}
} catch (SQLException e) {
MyLogger.debug(e);
}
}
private Long playtimeMsThreshold = 1000L * 60 * 30;
public static final double VERY_ACTIVE = 3.75;
public static final double ACTIVE = 3.0;
public static final double REGULAR = 2.0;
public static final double IRREGULAR = 1.0;
public double getActiveIndex(Player player) {
try {
String uuid = UnsignedUUID.UnUUIDof(player);
ActiveIndex activeIndex = ActiveIndexDAO.query(uuid);
if (activeIndex == null) {
return 0.0;
} else {
return activeIndex.index;
}
} catch (SQLException e) {
MyLogger.debug(e);
}
return 0.0;
}
public double updateIndex(Player player) {
String uuid = UnsignedUUID.UnUUIDof(player);
long current = System.currentTimeMillis();
long sevenDays = 1000L * 60 * 60 * 24 * 7;
long weekAgo = current - sevenDays;
long twoWeeksAgo = weekAgo - sevenDays;
long threeWeeksAgo = twoWeeksAgo - sevenDays;
try {
if (PlayTimeDAO.countsOfPlayer(uuid, threeWeeksAgo, current) <= 0) {
return 0.0;
}
ArrayList<PlayTimeStatics> weekOne = PlayTimeDAO.selectByTime(uuid, weekAgo, current);
ArrayList<PlayTimeStatics> weekTwo = PlayTimeDAO.selectByTime(uuid, twoWeeksAgo, weekAgo);
ArrayList<PlayTimeStatics> weekThree = PlayTimeDAO.selectByTime(uuid, threeWeeksAgo, twoWeeksAgo);
Long playtime1 = PlayTimeStatics.totalActivityTimeOf(weekOne);
Long playtime2 = PlayTimeStatics.totalActivityTimeOf(weekTwo);
Long playtime3 = PlayTimeStatics.totalActivityTimeOf(weekThree);
double indexW1 = 1.0 / (Math.PI / 2.0 * ((double) playtime1 / playtimeMsThreshold) + 1.0);
double indexW2 = 1.0 / (Math.PI / 2.0 * ((double) playtime2 / playtimeMsThreshold) + 1.0);
double indexW3 = 1.0 / (Math.PI / 2.0 * ((double) playtime3 / playtimeMsThreshold) + 1.0);
double average = (indexW1 + indexW2 + indexW3) / 3.0;
ActiveIndex activeIndex = new ActiveIndex();
activeIndex.playerUuid = uuid;
activeIndex.recentWeek = playtime1;
activeIndex.oneWeekAgo = playtime2;
activeIndex.twoWeekAgo = playtime3;
activeIndex.index = Math.round((5.0 - (5.0 * average)) * 100) / 100.0;
MyLogger.debug("\t" + player.getName() + "当前的活跃度为" + activeIndex.index);
if (ActiveIndexDAO.query(uuid) == null) {
ActiveIndexDAO.insert(activeIndex);
} else {
ActiveIndexDAO.update(activeIndex);
}
return 5.0 - (5.0 * average);
} catch (SQLException e) {
MyLogger.debug(e);
}
return 0.0;
}
}

View File

@ -45,6 +45,7 @@ public class ConfigManager {
m_Description = config.getString("Web.Description");
m_YagdrasilUrl = config.getString("Web.YagdrasilUrl");
m_ServerUrl = config.getString("Web.ServerUrl");
m_WhiteList = config.getBoolean("WhiteList");
MyLogger.info("配置文件加载完成。");
}
@ -258,6 +259,15 @@ public class ConfigManager {
plugin.saveConfig();
}
public Boolean getWhiteList() {
return m_WhiteList;
}
public void setWhiteList(Boolean whiteList) {
m_WhiteList = whiteList;
config.set("WhiteList", whiteList);
plugin.saveConfig();
}
private String m_ServerName;
private String m_ImplementationName;
private String m_ImplementationVersion;
@ -283,6 +293,7 @@ public class ConfigManager {
private String m_Description;
private String m_YagdrasilUrl;
private String m_ServerUrl;
private Boolean m_WhiteList;
IdentityVerification plugin;
FileConfiguration config;

View File

@ -23,12 +23,12 @@ public class EmailCodeCache {
}
public static boolean isEmailCodeExpired(String email) {
if (IdentityVerification.getInstance().getConfigManager().getDebug()) return false;
if (IdentityVerification.configManager.getDebug()) return false;
return getEmailCode(email).map(EmailCode::isExpired).orElse(true);
}
public static boolean isEmailCodeValid(String email, String code) {
if (IdentityVerification.getInstance().getConfigManager().getDebug()) return true;
if (IdentityVerification.configManager.getDebug()) return true;
return getEmailCode(email).map(emailCode -> emailCode.code.equals(code)).orElse(false);
}
}

View File

@ -2,8 +2,12 @@ package site.deercloud.identityverification.Controller;
import site.deercloud.identityverification.HttpServer.model.GameSession;
import site.deercloud.identityverification.HttpServer.model.PlayTimeStatics;
import site.deercloud.identityverification.IdentityVerification;
import site.deercloud.identityverification.SQLite.PlayTimeDAO;
import site.deercloud.identityverification.Utils.MyLogger;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@ -15,12 +19,32 @@ public class GameSessionCache {
}
public static void addSession(String playerUUID) {
MyLogger.debug("添加缓存会话,开始记录活动状态:" + playerUUID);
ACTIVE_SESSIONS.put(playerUUID, new GameSession());
}
public static void removeSession(String playerUUID) {
// TODO: save to database
// 存库 (活跃时间挂机时间)
PlayTimeStatics statics = new PlayTimeStatics();
if (getCacheSession(playerUUID).isEmpty()) {
return;
}
GameSession session = getCacheSession(playerUUID).get();
statics.joinTime = session.joinTime;
statics.quitTime = System.currentTimeMillis();
statics.onlineTime = statics.quitTime - statics.joinTime;
statics.afkTime = session.afkTime;
statics.activeTime = statics.onlineTime - statics.afkTime;
statics.playerUuid = playerUUID;
MyLogger.debug(playerUUID + "\n\t本次游戏在线时间: " + statics.onlineTime / 1000 / 60 + "min" + statics.onlineTime / 1000 % 60 + "s"
+ "\n\t本次游戏挂机时间: " + statics.afkTime / 1000 / 60 + "min" + statics.afkTime / 1000 % 60 + "s"
+ "\n\t本次游戏活跃时间: " + statics.activeTime / 1000 / 60 + "min" + statics.activeTime / 1000 % 60 + "s"
+ "\n\t本次游戏挂机率: " + (double) Math.round((double) statics.afkTime / statics.onlineTime * 10000) / 100 + "%");
try {
PlayTimeDAO.insert(statics);
} catch (SQLException e) {
MyLogger.debug(e);
}
ACTIVE_SESSIONS.remove(playerUUID);
}

View File

@ -3,8 +3,7 @@ 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 org.bukkit.event.player.*;
import site.deercloud.identityverification.Controller.GameSessionCache;
import site.deercloud.identityverification.SQLite.BanListDAO;
import site.deercloud.identityverification.SQLite.WhiteListDAO;
@ -21,8 +20,9 @@ public class Events implements Listener {
Player player = event.getPlayer();
String uuid = UnsignedUUID.UnUUIDof(player);
MyLogger.debug(uuid);
if (!WhiteListDAO.isUuidInWhiteList(uuid)) {
if (!WhiteListDAO.isUuidInWhiteList(uuid) && IdentityVerification.configManager.getWhiteList()) {
player.kickPlayer("你没有完成白名单实名认证,请前往 " + configManager.getHomePageUrl() + " 进行认证。");
return;
}
Integer ban_record_id = BanListDAO.isBanned(uuid);
if (ban_record_id > 0) {
@ -30,20 +30,27 @@ public class Events implements Listener {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String ban_time = sdf.format(BanListDAO.getBanTimeById(ban_record_id));
player.kickPlayer("你已被封禁,请联系管理员。原因:[ " + ban_reason + " ]" + " 至:" + ban_time);
return;
}
GameSessionCache.addSession(UnsignedUUID.UnUUIDof(event.getPlayer()));
IdentityVerification.activeIndexManager.updateIndex(player);
}
@EventHandler
public void onPlayerQuit(PlayerJoinEvent event) throws SQLException {
public void onPlayerQuit(PlayerQuitEvent event) {
IdentityVerification.afkTracker.performedAction(event.getPlayer(), System.currentTimeMillis());
GameSessionCache.removeSession(UnsignedUUID.UnUUIDof(event.getPlayer()));
}
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) throws SQLException {
Player player = event.getPlayer();
IdentityVerification.getAfkTracker().performedAction(UnsignedUUID.UnUUIDof(event.getPlayer()), System.currentTimeMillis());
public void onPlayerMove(PlayerMoveEvent event) {
IdentityVerification.afkTracker.performedAction(event.getPlayer(), System.currentTimeMillis());
}
ConfigManager configManager = IdentityVerification.getInstance().getConfigManager();
@EventHandler
public void onPlayerChat(AsyncPlayerChatEvent event) {
IdentityVerification.afkTracker.performedAction(event.getPlayer(), System.currentTimeMillis());
}
ConfigManager configManager = IdentityVerification.configManager;
}

View File

@ -32,7 +32,7 @@ public class GetEmailCode implements HttpHandler {
jsonResponse(exchange, 400, "此邮箱已被注册。", null);
return;
}
if (!EmailCodeCache.isEmailCodeExpired(email) && !IdentityVerification.getInstance().getConfigManager().getDebug()) {
if (!EmailCodeCache.isEmailCodeExpired(email) && !IdentityVerification.configManager.getDebug()) {
jsonResponse(exchange, 500, "禁止频繁操作!", null);
return;
}

View File

@ -16,11 +16,11 @@ public class WebMeta implements HttpHandler {
public void handle(HttpExchange exchange){
try {
requestHeader(exchange, "GET");
String title = IdentityVerification.getInstance().getConfigManager().getServerName();
String description = IdentityVerification.getInstance().getConfigManager().getDescription();
String version = IdentityVerification.getInstance().getConfigManager().getImplementationName() + "-" + IdentityVerification.getInstance().getConfigManager().getImplementationVersion();
String yagUrl = IdentityVerification.getInstance().getConfigManager().getYagdrasilUrl();
String serverUrl = IdentityVerification.getInstance().getConfigManager().getServerUrl();
String title = IdentityVerification.configManager.getServerName();
String description = IdentityVerification.configManager.getDescription();
String version = IdentityVerification.configManager.getImplementationName() + "-" + IdentityVerification.configManager.getImplementationVersion();
String yagUrl = IdentityVerification.configManager.getYagdrasilUrl();
String serverUrl = IdentityVerification.configManager.getServerUrl();
Boolean use_yag = true; // 是否使用外置登录
Boolean use_genuine = true; // 是否使用正版验证
Boolean need_invite = true; // 是否需要邀请码

View File

@ -37,7 +37,7 @@ import java.util.concurrent.TimeUnit;
public class HttpServerManager {
public HttpServerManager(IdentityVerification plugin) {
configManager = plugin.getConfigManager();
configManager = IdentityVerification.configManager;
// 注册路由
try {

View File

@ -18,15 +18,16 @@ public class WebServer implements HttpHandler {
}
String file_type = uri.substring(uri.lastIndexOf(".") + 1);
InputStream file = IdentityVerification.getInstance().getResource("web" + uri);
InputStream file = IdentityVerification.instance.getResource("web" + uri);
if (file == null){
MyLogger.debug("File not found: " + uri);
exchange.sendResponseHeaders(404, 0);
exchange.close();
return;
}
String file_content = new String(file.readAllBytes()).replace("127.0.0.1:11520",
IdentityVerification.getInstance().getConfigManager().getHomePageUrl());
String file_content = new String(file.readAllBytes()).replace("http://127.0.0.1:11520",
IdentityVerification.configManager.getHomePageUrl()).
replace("XXXXX_Server", IdentityVerification.configManager.getServerName());
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");

View File

@ -16,7 +16,7 @@ public class WebTest implements HttpHandler {
String uri = exchange.getRequestURI().toString();
// 按照uri从resource中读取文件返回
InputStream file = IdentityVerification.getInstance().getResource("web" + uri);
InputStream file = IdentityVerification.instance.getResource("web" + uri);
if (file == null){
MyLogger.debug("File not found: " + uri);
exchange.sendResponseHeaders(404, 0);

View File

@ -22,7 +22,7 @@ public class MetaData implements HttpHandler {
Response.err_method_not_allowed(exchange);
return;
}
ConfigManager configManager = IdentityVerification.getInstance().getConfigManager();
ConfigManager configManager = IdentityVerification.configManager;
JSONObject meta_json = new JSONObject();
meta_json.put("serverName", configManager.getServerName());
meta_json.put("implementationName", configManager.getImplementationName());

View File

@ -24,6 +24,7 @@ public class Authenticate implements HttpHandler {
@Override
public void handle(HttpExchange exchange){
try {
MyLogger.debug("Authenticate post 接口触发");
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
if (!exchange.getRequestMethod().equals("POST")){
Response.err_method_not_allowed(exchange);

View File

@ -18,6 +18,7 @@ public class Invalidate implements HttpHandler {
@Override
public void handle(HttpExchange exchange) {
try {
MyLogger.debug("Invalidate post 接口触发");
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
if (!exchange.getRequestMethod().equals("POST")) {
Response.err_method_not_allowed(exchange);

View File

@ -23,6 +23,7 @@ public class Refresh implements HttpHandler {
@Override
public void handle(HttpExchange exchange) {
try {
MyLogger.debug("Refresh post 接口触发");
// 设置响应头
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
if (!exchange.getRequestMethod().equals("POST")) {

View File

@ -23,6 +23,7 @@ public class SignOut implements HttpHandler {
@Override
public void handle(HttpExchange exchange) {
try {
MyLogger.debug("SignOut post 接口触发");
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
if (!exchange.getRequestMethod().equals("POST")) {
Response.err_method_not_allowed(exchange);

View File

@ -20,6 +20,7 @@ public class Validate implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
try {
MyLogger.debug("Validate post 接口触发");
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
if (!exchange.getRequestMethod().equals("POST")){
Response.err_method_not_allowed(exchange);

View File

@ -19,22 +19,27 @@ public class GetProfile implements HttpHandler {
@Override
public void handle(HttpExchange exchange) {
try {
MyLogger.debug("GetProfile get 接口触发");
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];
String uuid = exchange.getRequestURI().getPath().split("/")[5];
MyLogger.debug("GetProfile Path: " + exchange.getRequestURI().getPath());
Map<String, String> request = getQuery(exchange);
boolean unsigned = true;
if (request.containsKey("unsigned")) {
unsigned = Boolean.parseBoolean(request.get("unsigned"));
}
MyLogger.debug("uuid: " + uuid);
MyLogger.debug("unsigned: " + unsigned);
Profile profile = ProfileDAO.selectByUuid(uuid);
if (profile == null) {
Response.success_no_content(exchange);
return;
}
MyLogger.debug("Profile 信息: " + profile.serialToJSONObject(true, unsigned).toString());
Response.success_200(exchange, profile.serialToJSONObject(true, unsigned));
} catch (IOException | SQLException e) {
exchange.close();

View File

@ -22,6 +22,7 @@ public class HasJoined implements HttpHandler {
@Override
public void handle(HttpExchange exchange) {
try {
MyLogger.debug("HasJoined get 接口触发");
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
if (!exchange.getRequestMethod().equals("GET")) {
Response.err_method_not_allowed(exchange);
@ -31,31 +32,38 @@ public class HasJoined implements HttpHandler {
String username = query.get("username");
String serverId = query.get("serverId");
String ip = query.get("ip");
if (username == null || serverId == null) {
Response.success_no_content(exchange);
return;
}
// 验证缓存令牌有效性
if (HttpServerManager.getSessionCache().verifyIsExpire(serverId, ip)) {
MyLogger.debug("缓存令牌已过期");
Response.success_no_content(exchange);
return;
}
// 获取缓存令牌对应令牌
String accessToken = HttpServerManager.getSessionCache().getToken(serverId);
Token token = TokenDAO.selectByAccessToken(accessToken);
if (token == null || token.profileUUID == null || !token.profileUUID.equals(username)) {
if (token == null) {
MyLogger.debug("找不到令牌:" + accessToken);
Response.success_no_content(exchange);
return;
}
// 获取令牌对应的角色
Profile profile = ProfileDAO.selectByUuid(token.profileUUID);
if (profile == null) {
MyLogger.debug("找不到角色:" + token.profileUUID);
Response.success_no_content(exchange);
return;
}
// 检查角色名是否一致
if (!Objects.equals(username, profile.name)) {
if (!profile.name.equals(username)) {
MyLogger.debug("角色名不匹配:" + profile.name + " " + username);
Response.success_no_content(exchange);
return;
}
MyLogger.debug("HasJoined 信息: " + profile.serialToJSONObject(true, true).toString());
Response.success_200(exchange, profile.serialToJSONObject(true, true));
} catch (Exception e) {
exchange.close();

View File

@ -20,6 +20,7 @@ public class Join implements HttpHandler {
@Override
public void handle(HttpExchange exchange){
try {
MyLogger.debug("Join post 接口触发");
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
if (!exchange.getRequestMethod().equals("POST")){
Response.err_method_not_allowed(exchange);
@ -29,15 +30,19 @@ public class Join implements HttpHandler {
String accessToken = request.getString("accessToken");
String selectedProfile = request.getString("selectedProfile");
String serverId = request.getString("serverId");
MyLogger.debug("获取到的信息: accessToken: " + accessToken + " selectedProfile: " + selectedProfile + " serverId: " + serverId);
Token token = TokenDAO.selectByAccessToken(accessToken);
if (token == null || token.profileUUID == null || !token.profileUUID.equals(selectedProfile)){
MyLogger.debug("令牌无效:" + selectedProfile);
Response.err_invalid_token(exchange, "无效的令牌", "无效的令牌");
return;
}
// 缓存到内存中
HttpServerManager.getSessionCache().add(serverId, token.accessToken, exchange.getRemoteAddress().getHostString());
MyLogger.debug("缓存令牌:" + token.accessToken + " serverID:" + serverId);
MyLogger.debug("准备返回204");
Response.success_no_content(exchange);
} catch (IOException | SQLException e) {
exchange.close();

View File

@ -1,14 +1,18 @@
package site.deercloud.identityverification.HttpServer.Yggdrasil.sessionserver;
import site.deercloud.identityverification.Utils.MyLogger;
import java.util.HashMap;
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;
private final Map<String, String> serverID_token = new HashMap<>();
private final Map<String, String> serverID_ip = new HashMap<>();
private final Map<String, Long> serverID_time = new HashMap<>();
public void add(String serverID, String token, String ip){
MyLogger.debug("添加缓存令牌:" + serverID + " " + token + " " + ip);
serverID_token.put(serverID, token);
serverID_ip.put(serverID, ip);
serverID_time.put(serverID, System.currentTimeMillis() + 30000);

View File

@ -0,0 +1,32 @@
package site.deercloud.identityverification.HttpServer.model;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
public class ActiveIndex {
public String playerUuid;
public Long recentWeek = 0L;
public Long oneWeekAgo = 0L;
public Long twoWeekAgo = 0L;
public Double index = 0.0;
public Long update_time;
public static ArrayList<ActiveIndex> fillListWithSqlRes(ResultSet res) throws SQLException {
ArrayList<ActiveIndex> activeIndexArrayList = new ArrayList<>();
while (res.next()) {
ActiveIndex activeIndex = new ActiveIndex();
activeIndex.playerUuid = res.getString("player_uuid");
activeIndex.recentWeek = res.getLong("recent_week");
activeIndex.oneWeekAgo = res.getLong("one_week_ago");
activeIndex.twoWeekAgo = res.getLong("two_week_ago");
activeIndex.index = res.getDouble("index");
activeIndex.update_time = res.getLong("update_time");
activeIndexArrayList.add(activeIndex);
}
return activeIndexArrayList;
}
}

View File

@ -1,5 +1,7 @@
package site.deercloud.identityverification.HttpServer.model;
import site.deercloud.identityverification.Utils.MyLogger;
public class GameSession {
public GameSession() {
joinTime = System.currentTimeMillis();
@ -8,6 +10,7 @@ public class GameSession {
}
public void addAfkTime(long time) {
MyLogger.debug("addAfkTime:" + time);
afkTime += time;
}

View File

@ -0,0 +1,30 @@
package site.deercloud.identityverification.HttpServer.model;
import java.util.ArrayList;
public class PlayTimeStatics {
public Integer id;
public String playerUuid;
public Long afkTime;
public Long activeTime;
public Long onlineTime;
public Long joinTime;
public Long quitTime;
public Long recordTime;
public static Long totalActivityTimeOf(ArrayList<PlayTimeStatics> list) {
Long total = 0L;
for (PlayTimeStatics item : list) {
total += item.activeTime;
}
return total;
}
}

View File

@ -3,6 +3,7 @@ package site.deercloud.identityverification.HttpServer.model;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.sun.net.httpserver.HttpExchange;
import site.deercloud.identityverification.Utils.MyLogger;
import java.io.IOException;
@ -56,6 +57,7 @@ public class Response {
}
public static void success_no_content(HttpExchange exchange) throws IOException {
MyLogger.debug("返回204");
exchange.sendResponseHeaders(204, -1);
exchange.getResponseBody().close();
}

View File

@ -44,7 +44,7 @@ public class Texture {
public String sign() throws Exception {
// 使用SHA1withRSA算法签名
String private_key_str = IdentityVerification.getInstance().getConfigManager().getSignaturePrivateKey();
String private_key_str = IdentityVerification.configManager.getSignaturePrivateKey();
return SignatureUtil.sign(serialWithBase64(), private_key_str);
}
}

View File

@ -3,18 +3,15 @@ package site.deercloud.identityverification;
import java.io.File;
import org.bukkit.plugin.java.JavaPlugin;
import site.deercloud.identityverification.Controller.ActiveIndexManager;
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.FileToString;
import site.deercloud.identityverification.Utils.MyLogger;
import site.deercloud.identityverification.Controller.GameSessionCache;
import site.deercloud.identityverification.Utils.UnsignedUUID;
import java.sql.SQLException;
import java.util.Objects;
public final class IdentityVerification extends JavaPlugin {
@ -27,9 +24,10 @@ public final class IdentityVerification extends JavaPlugin {
httpServerManager = new HttpServerManager(this);
gameSessionCache = new GameSessionCache(this);
afkTracker = new AFKTracker(this);
// 初始化数据表
sqlManager = new SqlManager();
// 初始化活跃度管理器
activeIndexManager = new ActiveIndexManager();
// 初始化RSA
File publicKeyPath = new File(this.getDataFolder(), configManager.getPublicKeyFileName());
@ -39,10 +37,7 @@ public final class IdentityVerification extends JavaPlugin {
if (pubKeyContent == null || priKeyContent == null) {
MyLogger.error("RSA文件不存在, 插件退出。");
this.getServer().getPluginManager().disablePlugin(this);
}else {
// pubKeyContent = pubKeyContent.replace("-----BEGIN PUBLIC KEY-----", "")
// .replace("-----END PUBLIC KEY-----", "")
// .replace("\n", "");
} else {
priKeyContent = priKeyContent.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\n", "");
@ -67,26 +62,11 @@ public final class IdentityVerification extends JavaPlugin {
}
}
public ConfigManager getConfigManager() {
return configManager;
}
public static IdentityVerification getInstance() {
return instance;
}
public static GameSessionCache getSessionCache() {
return gameSessionCache;
}
public static AFKTracker getAfkTracker() {
return afkTracker;
}
public static SqlManager getSqlManager() {
return sqlManager;
}
private HttpServerManager httpServerManager;
private ConfigManager configManager;
private static SqlManager sqlManager;
private static GameSessionCache gameSessionCache;
private static AFKTracker afkTracker;
private static IdentityVerification instance;
public static HttpServerManager httpServerManager;
public static ConfigManager configManager;
public static SqlManager sqlManager;
public static GameSessionCache gameSessionCache;
public static AFKTracker afkTracker;
public static IdentityVerification instance;
public static ActiveIndexManager activeIndexManager;
}

View File

@ -0,0 +1,69 @@
package site.deercloud.identityverification.SQLite;
import site.deercloud.identityverification.HttpServer.model.ActiveIndex;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
public class ActiveIndexDAO {
public static void createTable() throws SQLException {
String sql = "CREATE TABLE IF NOT EXISTS active_index (\n"
+ " player_uuid text NOT NULL,\n"
+ " recent_week integer NOT NULL,\n"
+ " one_week_ago integer NOT NULL,\n"
+ " two_week_ago integer NOT NULL,\n"
+ " 'index' double NOT NULL,\n"
+ " update_time integer NOT NULL,\n"
+ " PRIMARY KEY(player_uuid)\n"
+ ");";
PreparedStatement preparedStatement = SqlManager.session.prepareStatement(sql);
preparedStatement.execute();
}
public static void insert(ActiveIndex activeIndex) throws SQLException {
String sql = "INSERT INTO active_index(player_uuid,recent_week,one_week_ago,two_week_ago,'index',update_time) VALUES(?,?,?,?,?,?)";
PreparedStatement preparedStatement = SqlManager.session.prepareStatement(sql);
preparedStatement.setString(1, activeIndex.playerUuid);
preparedStatement.setLong(2, activeIndex.recentWeek);
preparedStatement.setLong(3, activeIndex.oneWeekAgo);
preparedStatement.setLong(4, activeIndex.twoWeekAgo);
preparedStatement.setDouble(5, activeIndex.index);
preparedStatement.setLong(6, System.currentTimeMillis());
preparedStatement.execute();
}
public static ActiveIndex query(String playerUuid) throws SQLException {
String sql = "SELECT * FROM active_index WHERE player_uuid = ?";
PreparedStatement preparedStatement = SqlManager.session.prepareStatement(sql);
preparedStatement.setString(1, playerUuid);
ResultSet res = preparedStatement.executeQuery();
ArrayList<ActiveIndex> list = ActiveIndex.fillListWithSqlRes(res);
if (list.size() == 1) {
return list.get(0);
}
return null;
}
public static void update(ActiveIndex activeIndex) throws SQLException {
String sql = "UPDATE active_index SET recent_week = ?,one_week_ago = ?,two_week_ago = ?,'index' = ?,update_time = ? WHERE player_uuid = ?";
PreparedStatement preparedStatement = SqlManager.session.prepareStatement(sql);
preparedStatement.setLong(1, activeIndex.recentWeek);
preparedStatement.setLong(2, activeIndex.oneWeekAgo);
preparedStatement.setLong(3, activeIndex.twoWeekAgo);
preparedStatement.setDouble(4, activeIndex.index);
preparedStatement.setLong(5, System.currentTimeMillis());
preparedStatement.setString(6, activeIndex.playerUuid);
preparedStatement.execute();
}
public static ArrayList<ActiveIndex> selectActivityRecords() throws SQLException {
long current_time = System.currentTimeMillis();
String sql = "SELECT * FROM active_index WHERE 'index' < 3.75 AND 'index' > 3.0 AND update_time > ?";
PreparedStatement preparedStatement = SqlManager.session.prepareStatement(sql);
preparedStatement.setLong(1, current_time - 1000 * 60 * 60 * 24 * 7);
ResultSet res = preparedStatement.executeQuery();
return ActiveIndex.fillListWithSqlRes(res);
}
}

View File

@ -0,0 +1,73 @@
package site.deercloud.identityverification.SQLite;
import site.deercloud.identityverification.HttpServer.model.PlayTimeStatics;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
public class PlayTimeDAO {
public static void createTable() throws SQLException {
String sql = "CREATE TABLE IF NOT EXISTS play_time_static (\n"
+ " id integer PRIMARY KEY,\n" // ID
+ " player_uuid text NOT NULL,\n" // 玩家UUID
+ " afk_time integer NOT NULL,\n" // AFK时间
+ " active_time integer NOT NULL,\n" // 活跃时间
+ " online_time integer NOT NULL,\n" // 在线时间
+ " join_time integer NOT NULL,\n" // 加入时间
+ " quit_time integer NOT NULL,\n" // 离开时间
+ " record_time integer NOT NULL\n" // 记录创建时间
+ ");";
PreparedStatement preparedStatement = SqlManager.session.prepareStatement(sql);
preparedStatement.execute();
}
public static void insert(PlayTimeStatics statics) throws SQLException {
String sql = "INSERT INTO play_time_static(player_uuid,afk_time,active_time,online_time,join_time,quit_time,record_time) VALUES(?,?,?,?,?,?,?)";
PreparedStatement preparedStatement = SqlManager.session.prepareStatement(sql);
preparedStatement.setString(1, statics.playerUuid);
preparedStatement.setLong(2, statics.afkTime);
preparedStatement.setLong(3, statics.activeTime);
preparedStatement.setLong(4, statics.onlineTime);
preparedStatement.setLong(5, statics.joinTime);
preparedStatement.setLong(6, statics.quitTime);
preparedStatement.setLong(7, System.currentTimeMillis());
preparedStatement.execute();
}
public static ArrayList<PlayTimeStatics> selectByTime(String uuid, Long from, Long to) throws SQLException {
String sql = "SELECT * FROM play_time_static WHERE player_uuid = ? AND record_time >= ? AND record_time <= ?";
PreparedStatement preparedStatement = SqlManager.session.prepareStatement(sql);
preparedStatement.setString(1, uuid);
preparedStatement.setLong(2, from);
preparedStatement.setLong(3, to);
ResultSet res = preparedStatement.executeQuery();
ArrayList<PlayTimeStatics> returnList = new ArrayList<>();
while (res.next()) {
PlayTimeStatics statics = new PlayTimeStatics();
statics.id = res.getInt("id");
statics.playerUuid = res.getString("player_uuid");
statics.afkTime = res.getLong("afk_time");
statics.activeTime = res.getLong("active_time");
statics.onlineTime = res.getLong("online_time");
statics.joinTime = res.getLong("join_time");
statics.quitTime = res.getLong("quit_time");
statics.recordTime = res.getLong("record_time");
returnList.add(statics);
}
return returnList;
}
public static Integer countsOfPlayer(String uuid, Long from, Long to) throws SQLException {
String sql = "SELECT COUNT(*) FROM play_time_static WHERE player_uuid = ? AND record_time >= ? AND record_time <= ?";
PreparedStatement preparedStatement = SqlManager.session.prepareStatement(sql);
preparedStatement.setString(1, uuid);
preparedStatement.setLong(2, from);
preparedStatement.setLong(3, to);
ResultSet res = preparedStatement.executeQuery();
return res.getInt(1);
}
}

View File

@ -5,7 +5,6 @@ import org.bukkit.entity.Player;
import org.sqlite.SQLiteConfig;
import org.sqlite.SQLiteDataSource;
import site.deercloud.identityverification.HttpServer.model.User;
import site.deercloud.identityverification.IdentityVerification;
import site.deercloud.identityverification.Utils.MyLogger;
import site.deercloud.identityverification.Utils.UnsignedUUID;
@ -20,7 +19,7 @@ public class SqlManager {
config.enableRecursiveTriggers(true);
SQLiteDataSource ds = new SQLiteDataSource(config);
String url = System.getProperty("user.dir"); // 获取工作目录
ds.setUrl("jdbc:sqlite:"+url+"/plugins/IdentityVerification/"+"IV-Database.db");
ds.setUrl("jdbc:sqlite:"+url+"/plugins/IdentityVerification/"+"iv_database.db");
session = ds.getConnection();
createTables();
@ -52,6 +51,8 @@ public class SqlManager {
ProfileDAO.createTable();
WhiteListDAO.createTable();
TokenDAO.createTable();
PlayTimeDAO.createTable();
ActiveIndexDAO.createTable();
} catch (SQLException e) {
MyLogger.debug(e);
}

View File

@ -10,11 +10,11 @@ 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(); // 密码
static String host = IdentityVerification.configManager.getEmailHost(); // smtp服务器
static String port = IdentityVerification.configManager.getEmailPort(); // 端口
static String from = IdentityVerification.configManager.getEmailFrom(); // 发件人的email
static String account = IdentityVerification.configManager.getEmailUsername(); // 账号
static String password = IdentityVerification.configManager.getEmailPassword(); // 密码
public static boolean sendTestEmail(String to) {
String subject = "测试邮件";
@ -52,7 +52,7 @@ public class EmailSender {
// 2. 根据配置创建会话对象, 用于和邮件服务器交互
Session session = Session.getInstance(props);
session.setDebug(IdentityVerification.getInstance().getConfigManager().getDebug()); // 设置为debug模式, 可以查看详细的发送 log
session.setDebug(IdentityVerification.configManager.getDebug()); // 设置为debug模式, 可以查看详细的发送 log
// 3. 创建一封邮件
MimeMessage message = new MimeMessage(session);
@ -78,7 +78,7 @@ public class EmailSender {
return true;
} catch (Exception e) {
MyLogger.debug(e);
MyLogger.error("请检查邮箱配置是否正确,可能的问题解决方案:\n\t1.通常来说国内的邮箱服务要求发信人与账号保持一致,请检查是否一致;\n\t2.前往java的安装路径找到jre/lib/security/java.security删去 jdk.tls.disabledAlgorithms 部分的SSLv3, TLSv1, TLSv1.1");
MyLogger.error("请检查邮箱配置是否正确,可能的问题解决方案:\n\t1.通常来说国内的邮箱服务要求发信人与账号保持一致,请检查是否一致;\n\t2.前往java的安装路径找到conf/security/java.security删去 jdk.tls.disabledAlgorithms 部分的SSLv3, TLSv1, TLSv1.1");
return false;
}

View File

@ -5,51 +5,50 @@ 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);
IdentityVerification.instance.getLogger().info(ChatColor.GREEN + "| " +message);
}
public static void warn(String message) {
plugin.getLogger().warning(ChatColor.YELLOW + "| " +message);
IdentityVerification.instance.getLogger().warning(ChatColor.YELLOW + "| " +message);
}
public static void error(String message) {
plugin.getLogger().severe(ChatColor.RED + "| " +message);
IdentityVerification.instance.getLogger().severe(ChatColor.RED + "| " +message);
}
public static void debug(String message) {
if (plugin.getConfigManager().getDebug()) {
plugin.getLogger().info(ChatColor.AQUA + "| " +message);
if (IdentityVerification.configManager.getDebug()) {
IdentityVerification.instance.getLogger().info(ChatColor.AQUA + "| " +message);
}
}
public static void debug(Exception e) {
if (plugin.getConfigManager().getDebug()) {
plugin.getLogger().info(ChatColor.AQUA + "| " + e.getMessage());
if (IdentityVerification.configManager.getDebug()) {
IdentityVerification.instance.getLogger().info(ChatColor.AQUA + "| " + e.getMessage());
e.printStackTrace();
}
}
public static void info(CommandSender sender, String message) {
plugin.getLogger().info(ChatColor.GREEN + "[ 来自 " + sender.getName() + " 的消息 ] " + message);
IdentityVerification.instance.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);
IdentityVerification.instance.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);
IdentityVerification.instance.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);
if (IdentityVerification.configManager.getDebug()) {
IdentityVerification.instance.getLogger().info(ChatColor.AQUA + "[ 来自 " + sender.getName() + " 的消息 ] " + message);
sender.sendMessage(ChatColor.AQUA + "| " +message);
}
}

View File

@ -31,6 +31,7 @@ Yggdrasil:
# RSA 私钥文件路径
RsaPrivateKey: id_rsa
# 邮箱配置
Email:
# 邮箱服务器地址
@ -46,4 +47,7 @@ Email:
# 实名认证API配置
# 是否启用白名单
WhiteList: true
# 调试模式(如果出现了问题 请打开调试并将报错复制以提交ISSUE
Debug: false

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
header[data-v-121f739e]{position:fixed;width:100%;top:-10px;left:0%;height:65px;color:#130a0a;background-color:#ffffffe6;padding:1em;z-index:100;filter:blur(5px)}headerText[data-v-121f739e]{position:fixed;width:100%;top:0;left:0%;color:#130a0a;padding:1em;padding-left:5rem;z-index:101}menuBtn[data-v-121f739e]{top:0;left:0;padding:2em;font-size:larger;font-weight:700}main[data-v-121f739e]{margin:auto;padding:1em;position:flex;margin-top:5rem}: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, .29);--vt-c-divider-light-2: rgba(60, 60, 60, .12);--vt-c-divider-dark-1: rgba(84, 84, 84, .65);--vt-c-divider-dark-2: rgba(84, 84, 84, .48);--vt-c-text-light-1: var(--vt-c-indigo);--vt-c-text-light-2: rgba(60, 60, 60, .66);--vt-c-text-dark-1: var(--vt-c-white);--vt-c-text-dark-2: rgba(235, 235, 235, .64)}: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:400}body{min-height:100vh;color:var(--color-text);background:var(--color-background);transition:color .5s,background-color .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}#app{max-width:1280px;margin:0 auto;padding:0rem;margin-top:5rem;font-weight:400}a,.green{text-decoration:none;color:#45b791;transition:.4s}@media (hover: hover){a:hover{background-color:#00bd7e33}}body{display:flex;place-items:center}@media (min-width: 1024px){body{display:flex;place-items:center}#app{display:flex;margin:auto;padding:0 2rem}}
header[data-v-cabd705f]{position:fixed;width:100%;top:-10px;left:0%;height:65px;color:#130a0a;background-color:#ffffffe6;padding:1em;z-index:100;filter:blur(5px)}headerText[data-v-cabd705f]{position:fixed;width:100%;top:0;left:0%;color:#130a0a;padding:1em;padding-left:2rem;z-index:101}menuBtn[data-v-cabd705f]{top:0;left:0;padding:1em;font-size:larger;font-weight:700}main[data-v-cabd705f]{margin:auto;padding:1em;position:flex;margin-top:5rem}: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, .29);--vt-c-divider-light-2: rgba(60, 60, 60, .12);--vt-c-divider-dark-1: rgba(84, 84, 84, .65);--vt-c-divider-dark-2: rgba(84, 84, 84, .48);--vt-c-text-light-1: var(--vt-c-indigo);--vt-c-text-light-2: rgba(60, 60, 60, .66);--vt-c-text-dark-1: var(--vt-c-white);--vt-c-text-dark-2: rgba(235, 235, 235, .64)}: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:400}body{min-height:100vh;color:var(--color-text);background:var(--color-background);transition:color .5s,background-color .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}#app{max-width:1280px;margin:0 auto;padding:0rem;margin-top:5rem;font-weight:400}a,.green{text-decoration:none;color:#45b791;transition:.4s}@media (hover: hover){a:hover{background-color:#00bd7e33}}body{display:flex;place-items:center}@media (min-width: 1024px){body{display:flex;place-items:center}#app{display:flex;margin:auto;padding:0 2rem}}

File diff suppressed because one or more lines are too long

View File

@ -4,9 +4,9 @@
<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>
<script type="module" crossorigin src="/assets/index.dd68120c.js"></script>
<link rel="stylesheet" href="/assets/index.ccc73206.css">
<title>XXXXX_Server</title>
<script type="module" crossorigin src="/assets/index.09896eef.js"></script>
<link rel="stylesheet" href="/assets/index.a6ce675f.css">
</head>
<body>
<div id="app"></div>

View File

@ -4,7 +4,7 @@
<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>
<title>XXXXX_Server</title>
</head>
<body>
<div id="app"></div>

View File

@ -4,12 +4,16 @@
<header>
</header>
<headerText>
|<RouterLink to="/"> <menuBtn>主页</menuBtn></RouterLink>|
<RouterLink to="/newjoin"><menuBtn>注册外置登录</menuBtn></RouterLink>|
<RouterLink to="/newjoin"><menuBtn>实名认证白名单</menuBtn></RouterLink>|
<n-grid x-gap="12">
<n-grid-item :span="6">
<RouterLink to="/">主页</RouterLink>
</n-grid-item>
<n-grid-item :span="6">
<RouterLink to="/newjoin">注册</RouterLink>
</n-grid-item>
</n-grid>
</headerText>
<main>
<n-dialog-provider>
<n-notification-provider>
<RouterView />
@ -23,7 +27,7 @@
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
import { NDialogProvider, NNotificationProvider, NConfigProvider, useOsTheme, darkTheme, NH2 } from 'naive-ui';
import { NDialogProvider, NNotificationProvider, NConfigProvider, useOsTheme, darkTheme, NH2, NGrid, NGridItem } from 'naive-ui';
import axios from "axios";
import { computed } from 'vue';
@ -56,14 +60,14 @@ headerText {
left: 0%;
color: rgb(19, 10, 10);
padding: 1em;
padding-left: 5rem;
padding-left: 2rem;
z-index: 101;
}
menuBtn{
menuBtn {
top: 0;
left: 0;
padding: 2em;
padding: 1em;
font-size: larger;
font-weight: bold;
}

View File

@ -5,7 +5,7 @@
<n-h3>{{server_url}}</n-h3>
<n-h2>{{server_description}}</n-h2>
<n-button @click="join" strong secondary type="primary" size="large" >加入游戏</n-button>
<n-button @click="$router.push('/newjoin')" strong secondary type="primary" size="large" >加入游戏</n-button>
</template>
@ -30,8 +30,5 @@ onMounted(() => {
});
});
function join(){
window.location.href = '/newjoin'
}
</script>