From ba5c07746a7b239be609ebe050e40018866fc3d1 Mon Sep 17 00:00:00 2001 From: Hydrogen <1620682458@qq.com> Date: Mon, 18 Aug 2025 00:15:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(gui,login):=20=E5=AE=8C=E6=88=90=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 9 +- build.gradle | 2 + gradle.properties | 1 + .../innovators/box/AxisInnovatorsBox.java | 58 +- .../innovators/box/tools/LoadResource.java | 42 ++ .../box/util/UserLocalInformation.java | 47 -- .../box/verification/CasdoorServer.java | 102 ++++ .../box/verification/LoginData.java | 21 + .../box/verification/LoginResult.java | 28 + .../box/verification/OnlineVerification.java | 33 -- .../innovators/box/verification/Result.java | 25 + .../innovators/box/verification/UserTags.java | 46 -- .../box/verification/VerificationService.java | 38 -- .../box/verification/api/UserApi.java | 89 +++ .../box/verification/api/UserApiImpl.java | 65 +++ .../box/verification/api/UserApiResult.java | 25 + .../box/window/CasdoorLoginWindow.java | 268 +++++++++ .../innovators/box/window/LoginWindow.java | 539 ------------------ src/main/java/config/CasdoorConfig.java | 17 + src/main/resources/cert/casdoor_cert.pem | 29 + state/toolbox.properties | 0 21 files changed, 761 insertions(+), 723 deletions(-) create mode 100644 gradle.properties create mode 100644 src/main/java/com/axis/innovators/box/tools/LoadResource.java delete mode 100644 src/main/java/com/axis/innovators/box/util/UserLocalInformation.java create mode 100644 src/main/java/com/axis/innovators/box/verification/CasdoorServer.java create mode 100644 src/main/java/com/axis/innovators/box/verification/LoginData.java create mode 100644 src/main/java/com/axis/innovators/box/verification/LoginResult.java delete mode 100644 src/main/java/com/axis/innovators/box/verification/OnlineVerification.java create mode 100644 src/main/java/com/axis/innovators/box/verification/Result.java delete mode 100644 src/main/java/com/axis/innovators/box/verification/UserTags.java delete mode 100644 src/main/java/com/axis/innovators/box/verification/VerificationService.java create mode 100644 src/main/java/com/axis/innovators/box/verification/api/UserApi.java create mode 100644 src/main/java/com/axis/innovators/box/verification/api/UserApiImpl.java create mode 100644 src/main/java/com/axis/innovators/box/verification/api/UserApiResult.java create mode 100644 src/main/java/com/axis/innovators/box/window/CasdoorLoginWindow.java delete mode 100644 src/main/java/com/axis/innovators/box/window/LoginWindow.java create mode 100644 src/main/java/config/CasdoorConfig.java create mode 100644 src/main/resources/cert/casdoor_cert.pem create mode 100644 state/toolbox.properties diff --git a/.gitignore b/.gitignore index b63da45..968cfb8 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,11 @@ bin/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store + +### logs ### +*.log +logs/ + +### JCEF Dlls ### +library/jcef/ \ No newline at end of file diff --git a/build.gradle b/build.gradle index 3c9ee76..6f8a5a8 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,7 @@ configurations { proguardLib } + // JDK 版本检查 def requiredJavaVersion = 20 def currentJavaVersion = JavaVersion.current().majorVersion.toInteger() @@ -113,6 +114,7 @@ dependencies { implementation 'jflac:jflac:1.3' implementation 'com.github.axet:TarsosDSP:2.4' implementation 'org.json:json:20231013' + implementation 'org.casbin:casdoor-java-sdk:1.37.0' } configurations.all { diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..195297f --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.home=E:\\Softwares\\Java\\jdk-20.0.2 \ No newline at end of file diff --git a/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java b/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java index 33467b6..d204f06 100644 --- a/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java +++ b/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java @@ -14,8 +14,9 @@ import com.axis.innovators.box.register.RegistrationTopic; import com.axis.innovators.box.tools.*; import com.axis.innovators.box.util.PythonResult; import com.axis.innovators.box.util.Tray; -import com.axis.innovators.box.util.UserLocalInformation; -import com.axis.innovators.box.verification.UserTags; +import com.axis.innovators.box.verification.LoginResult; +import com.axis.innovators.box.verification.CasdoorServer; +import com.axis.innovators.box.verification.LoginData; import com.formdev.flatlaf.themes.FlatMacDarkLaf; import com.formdev.flatlaf.themes.FlatMacLightLaf; import com.sun.management.HotSpotDiagnosticMXBean; @@ -31,6 +32,7 @@ import org.apache.logging.log4j.core.appender.FileAppender; import org.apache.logging.log4j.core.appender.RollingFileAppender; import org.apache.logging.log4j.core.config.Configuration; import org.api.dog.agent.VirtualMachine; +import org.casbin.casdoor.entity.User; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -58,7 +60,8 @@ public class AxisInnovatorsBox { private static final Logger logger = LogManager.getLogger(AxisInnovatorsBox.class); private static final String VERSIONS = "0.1.2"; private static final String[] AUTHOR = new String[]{ - "tzdwindows 7" + "tzdwindows 7", + "lyxyz5223", }; /** 我是总任务数 **/ @@ -77,10 +80,11 @@ public class AxisInnovatorsBox { private final RegistrationTopic registrationTopic = new RegistrationTopic(this); private final List windowsJDialogList = new ArrayList<>(); private final StateManager stateManager = new StateManager(); - private UserTags userTags; private final boolean isDebug; private static DebugWindow debugWindow; + private LoginData loginData; + public AxisInnovatorsBox(String[] args, boolean isDebug) { this.args = args; this.isDebug = isDebug; @@ -88,6 +92,37 @@ public class AxisInnovatorsBox { organizingCrashReports(throwable instanceof Exception ? (Exception) throwable : new Exception(throwable)); }); + + // 加载登录信息,如果没有,弹出登录弹窗,后续可以删掉默认弹出 + // TODO: login window + try { + StateManager stateManager = new StateManager(); + String token = stateManager.getState("loginToken"); + if (token == null || token.isEmpty()) { + LoginResult loginResult = CasdoorLoginWindow.showLoginDialogAndGetLoginResult(); + if (loginResult == null) { + // 用户取消登录 + JOptionPane.showMessageDialog(null, "取消登录", "登录", + JOptionPane.INFORMATION_MESSAGE); + } else if (loginResult.success()) { + loginData = loginResult.loginData(); + stateManager.saveState("loginToken", loginResult.token()); + logger.info("Login result: token: " + loginResult.token() + ", user: " + loginResult.user()); + JOptionPane.showMessageDialog(null, "登录成功", "登录", + JOptionPane.INFORMATION_MESSAGE); + } else { + // 登录失败,弹出错误提醒,这里只是输出登录错误信息 + logger.error("Login error: " + loginResult.message()); + JOptionPane.showMessageDialog(null, "登录失败: \n" + loginResult.message(), "登录失败", JOptionPane.ERROR_MESSAGE); + } + } else { + CasdoorServer casdoorServer = new CasdoorServer(); + User user = casdoorServer.parseJwtToken(token); + loginData = new LoginData(token, user); + } + } catch (Exception e) { + logger.error("Failed to load login information", e); + } } /** @@ -959,14 +994,6 @@ public class AxisInnovatorsBox { main.thread = new Thread(() -> { try { - UserLocalInformation userLocalInformation = new UserLocalInformation(main); - main.userTags = userLocalInformation.getUserTags(); - if (main.userTags == null) { - // 登录窗口 - main.userTags = LoginWindow.createAndShow(); - userLocalInformation.setUserTags(main.userTags); - } - // 主任务1:加载插件 logger.info("Loaded plugins Started"); main.progressBarManager.updateMainProgress(++main.completedTasks); @@ -1092,13 +1119,6 @@ public class AxisInnovatorsBox { return AUTHOR; } - /** - * 获取用户标签 - * @return 用户标签 - */ - public UserTags getUserTags() { - return userTags; - } /** * 获取状态管理器 diff --git a/src/main/java/com/axis/innovators/box/tools/LoadResource.java b/src/main/java/com/axis/innovators/box/tools/LoadResource.java new file mode 100644 index 0000000..e97b082 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/tools/LoadResource.java @@ -0,0 +1,42 @@ +package com.axis.innovators.box.tools; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.apache.logging.log4j.LogManager; + +public class LoadResource { + + public static String readString(String srcPath) { + try { + java.net.URL url = LoadResource.class.getResource(srcPath); + if (url == null) { + LogManager.getLogger(LoadResource.class).error("资源文件未找到: " + srcPath); + return null; + } + java.nio.file.Path path = java.nio.file.Paths.get(url.toURI()); + return new String(Files.readAllBytes(path), StandardCharsets.UTF_8); + } catch (Exception e) { + LogManager.getLogger(LoadResource.class).error("读取资源文件失败", e); + return null; + } + } + + public static String loadString(Class clazz, String srcPath) { + try { + java.net.URL url = clazz.getResource(srcPath); + if (url == null) { + LogManager.getLogger(LoadResource.class).error("资源文件未找到: " + srcPath); + return null; + } + java.nio.file.Path path = java.nio.file.Paths.get(url.toURI()); + return new String(Files.readAllBytes(path), StandardCharsets.UTF_8); + } catch (Exception e) { + LogManager.getLogger(LoadResource.class).error("读取资源文件失败", e); + return null; + } + + } +} diff --git a/src/main/java/com/axis/innovators/box/util/UserLocalInformation.java b/src/main/java/com/axis/innovators/box/util/UserLocalInformation.java deleted file mode 100644 index 91ea57e..0000000 --- a/src/main/java/com/axis/innovators/box/util/UserLocalInformation.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.axis.innovators.box.util; - -import com.axis.innovators.box.AxisInnovatorsBox; -import com.axis.innovators.box.verification.OnlineVerification; -import com.axis.innovators.box.verification.UserTags; -import com.axis.innovators.box.verification.VerificationService; - -/** - * 用于存储用户信息 - * @author tzdwindows 7 - */ -public class UserLocalInformation { - private final AxisInnovatorsBox main; - - public UserLocalInformation(AxisInnovatorsBox main){ - this.main = main; - } - - /** - * 设置用户信息 - * @param userTags 用户信息 - */ - public void setUserTags(UserTags userTags){ - OnlineVerification onlineVerification = userTags.getUser(); - main.getStateManager().saveState("password", onlineVerification.password); - main.getStateManager().saveState("verification", onlineVerification.onlineVerification); - } - - /** - * 获取用户信息 - * @return 用户信息 - */ - public UserTags getUserTags(){ - String verification = main.getStateManager().getState("verification"); - String password = main.getStateManager().getState("password"); - if (verification == null || password == null){ - return null; - } - OnlineVerification onlineVerification = OnlineVerification.validateLogin( - verification, - password); - if (onlineVerification == null){ - return null; - } - return VerificationService.determineUserType(onlineVerification); - } -} diff --git a/src/main/java/com/axis/innovators/box/verification/CasdoorServer.java b/src/main/java/com/axis/innovators/box/verification/CasdoorServer.java new file mode 100644 index 0000000..8dc05bc --- /dev/null +++ b/src/main/java/com/axis/innovators/box/verification/CasdoorServer.java @@ -0,0 +1,102 @@ +package com.axis.innovators.box.verification; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.casbin.casdoor.config.Config; +import org.casbin.casdoor.entity.User; +import org.casbin.casdoor.service.AuthService; + +import com.axis.innovators.box.tools.LoadResource; + +import config.CasdoorConfig; + +public class CasdoorServer { + private static final Logger logger = LogManager.getLogger(CasdoorServer.class); + + private final AuthService authService; + private final Config config; + private final String certificate; + + public CasdoorServer() { + this.certificate = LoadResource.readString("/cert/casdoor_cert.pem"); + this.config = new Config( + CasdoorConfig.CASDOOR_API_URL, + CasdoorConfig.CASDOOR_CLIENT_ID, + CasdoorConfig.CASDOOR_CLIENT_SECRET, + this.certificate, + CasdoorConfig.CASDOOR_ORGANIZATION_NAME, + CasdoorConfig.CASDOOR_APPLICATION_NAME); + this.authService = new AuthService(this.config); + } + + public AuthService getAuthService() { + return authService; + } + + public Config getConfig() { + return config; + } + + public String getCertificate() { + return certificate; + } + + /** + * 获取登录 URL + * @return 登录 URL + */ + public String getSigninUrl() { + return authService.getSigninUrl(CasdoorConfig.CASDOOR_LOGIN_REDIRECT_URI); + } + + /** + * 获取注册 URL + * @return 注册 URL + */ + public String getSignupUrl() { + String redirectUrl = CasdoorConfig.CASDOOR_SIGNUP_REDIRECT_URI; + if (redirectUrl == null) { + redirectUrl = getSigninUrl(); // 如果没有设置注册回调地址,则使用登录回调地址 + } + return authService.getSignupUrl(redirectUrl); + } + + /** + * 获取 OAuth Token + * @param code 登录回调的 code 参数 + * @param state 登录回调的 state 参数 + * @return + */ + public String getOAuthToken(String code, String state) { + try { + return authService.getOAuthToken(code, state); + } catch (Exception e) { + logger.error("获取 OAuth Token 失败: " + e.getMessage()); + return null; + } + } + + /** + * 解析 JWT Token + * @param token 令牌 + * @return User 用户信息 + */ + public User parseJwtToken(String token) { + try { + return authService.parseJwtToken(token); + } catch (Exception e) { + logger.error("解析 JWT Token 失败: " + e.getMessage()); + return null; + } + } + + /** + * 获取用户信息,同 parseJwtToken + * @param token 令牌 + * @return User 用户信息 + */ + public User getUserInfo(String token) { + return parseJwtToken(token); + } + +} diff --git a/src/main/java/com/axis/innovators/box/verification/LoginData.java b/src/main/java/com/axis/innovators/box/verification/LoginData.java new file mode 100644 index 0000000..2f0fee1 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/verification/LoginData.java @@ -0,0 +1,21 @@ +package com.axis.innovators.box.verification; + +import org.casbin.casdoor.entity.User; + +public class LoginData { + private String token; + private User user; + + public LoginData(String token, User user) { + this.token = token; + this.user = user; + } + + public String getToken() { + return token; + } + + public User getUser() { + return user; + } +} diff --git a/src/main/java/com/axis/innovators/box/verification/LoginResult.java b/src/main/java/com/axis/innovators/box/verification/LoginResult.java new file mode 100644 index 0000000..b3400af --- /dev/null +++ b/src/main/java/com/axis/innovators/box/verification/LoginResult.java @@ -0,0 +1,28 @@ +package com.axis.innovators.box.verification; + +import org.casbin.casdoor.entity.User; + +public class LoginResult extends Result { + + public LoginResult(boolean success, String message, LoginData data) { + super(success, message, data); + } + + public String token() { + LoginData loginData = (LoginData)this.m_data; + return loginData != null ? loginData.getToken() : null; + } + + public User user() { + LoginData loginData = (LoginData) this.m_data; + return loginData != null ? loginData.getUser() : null; + } + + public LoginData loginData() { + return (LoginData) this.m_data; + } + + public LoginData data() { + return (LoginData) this.m_data; + } +} diff --git a/src/main/java/com/axis/innovators/box/verification/OnlineVerification.java b/src/main/java/com/axis/innovators/box/verification/OnlineVerification.java deleted file mode 100644 index 56febc3..0000000 --- a/src/main/java/com/axis/innovators/box/verification/OnlineVerification.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.axis.innovators.box.verification; - -/** - * 在线验证用户身份 - * 用于报错用户的验证信息 - * @author tzdwindows 7 - */ -public class OnlineVerification { - public String onlineVerification; - public String password; - /* 我是错误信息,要返回错误请修改我 */ - public static String errorMessage = "用户不存在"; - - /** - * 验证登录 - * @param identifier 账号 - * @param password 密码 - */ - OnlineVerification(String identifier, String password){ - this.onlineVerification = identifier; - this.password = password; - } - - /** - * 验证登录 - * @param identifier 账号 - * @param password 密码 - * @return 验证结果,如果返回null则表示验证失败,使用errorMessage获取验证失败的原因 - */ - public static OnlineVerification validateLogin(String identifier, String password){ - return new OnlineVerification(identifier, password); - } -} diff --git a/src/main/java/com/axis/innovators/box/verification/Result.java b/src/main/java/com/axis/innovators/box/verification/Result.java new file mode 100644 index 0000000..71319f4 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/verification/Result.java @@ -0,0 +1,25 @@ +package com.axis.innovators.box.verification; + +public class Result { + protected final boolean m_success; + protected final String m_message; + protected final Object m_data; + + public Result(boolean success, String message, Object data) { + this.m_success = success; + this.m_message = message; + this.m_data = data; + } + + public boolean success() { + return m_success; + } + + public String message() { + return m_message; + } + + public Object data() { + return m_data; + } +} diff --git a/src/main/java/com/axis/innovators/box/verification/UserTags.java b/src/main/java/com/axis/innovators/box/verification/UserTags.java deleted file mode 100644 index 2d5d5f4..0000000 --- a/src/main/java/com/axis/innovators/box/verification/UserTags.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.axis.innovators.box.verification; - -/** - * 用户标签组 - * @author tzdwindows 7 - */ -public enum UserTags { - /** - * 没有登录的标签 - */ - None, - /** - * 普通用户标签 - */ - RegularUsers, - /** - * 管理员标签 - */ - AdminUsers, - /** - * VIP用户标签 - */ - VipUsers, - /** - * SVip用户标签 - */ - SVipUsers, - /** - * 企业用户标签 - */ - EnterpriseUsers; - - private OnlineVerification onlineVerification; - - /** - * 设置用户组信息 - * @param onlineVerification 用户验证结果信息 - */ - void setUser(OnlineVerification onlineVerification) { - this.onlineVerification = onlineVerification; - } - - public OnlineVerification getUser() { - return onlineVerification; - } -} diff --git a/src/main/java/com/axis/innovators/box/verification/VerificationService.java b/src/main/java/com/axis/innovators/box/verification/VerificationService.java deleted file mode 100644 index 0dd038f..0000000 --- a/src/main/java/com/axis/innovators/box/verification/VerificationService.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.axis.innovators.box.verification; - -/** - * @author tzdwindows 7 - */ -public class VerificationService { - - /** - * 确定用户类型 - * @param identifier 用户 - * @return 用户类型 - */ - public static UserTags determineUserType(OnlineVerification identifier) { - UserTags userTags = UserTags.RegularUsers; - userTags.setUser(identifier); - return userTags; - } - - /** - * 发送密码重置链接给用户 - * @param text - * @return - */ - public static boolean sendPasswordReset(String text) { - return true; - } - - /** - * 注册用户 - * @param text - * @param text1 - * @param pwd - * @return - */ - public static boolean registerUser(String text, String text1, String pwd) { - return true; - } -} diff --git a/src/main/java/com/axis/innovators/box/verification/api/UserApi.java b/src/main/java/com/axis/innovators/box/verification/api/UserApi.java new file mode 100644 index 0000000..c706f55 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/verification/api/UserApi.java @@ -0,0 +1,89 @@ +package com.axis.innovators.box.verification.api; + +import java.util.concurrent.CompletableFuture; + +/** + * 用户API接口 + * @author lyxyz5223 + */ +public interface UserApi { + /** + * 发送验证码 + * @param email 用户邮箱 + * @return 发送结果 + */ + CompletableFuture sendVerificationCode(String email); + + /** + * 验证验证码 + * @param email 用户邮箱 + * @param verificationCode 验证码 + * @return 验证结果 + */ + CompletableFuture verifyCode(String email, String verificationCode); + + /** + * 检验登录token有效性,用于检验登录状态 + * @param token 登录token + * @return 检验结果 + */ + CompletableFuture checkTokenValidity(String token); + + /** + * 申请新的token,可以定期申请新的token保证安全性 + * @param oldToken 旧的登录token + * @return 申请结果 + */ + CompletableFuture applyNewToken(String oldToken); + + /** + * 登录接口 + * @param username 用户名 + * @param password 密码(加密) + * @return { token: String, expiresIn: Number } 登录结果(含token和有效期,临近到期需要手动申请新的token,每次登陆服务器将更新token有效期) + */ + CompletableFuture login(String username, String password); + + /** + * 登出接口 + * @param username 用户名 + * @return 登出结果 + */ + CompletableFuture logout(String username); + + /** + * 注册接口 + * @param username 用户名 + * @param password 密码(加密) + * @param email 邮箱 + * @return 注册结果 + */ + CompletableFuture register(String username, String password, String email); + + /** + * 申请重置密码接口 + * @param email 用户邮箱 + * @return { resetToken: String, expiresIn: Number } 重置结果(包含有效期内的重置token和有效期,24小时内有效) + */ + CompletableFuture postResetPasswordRequest(String email); + + /** + * 重置密码接口 + * @param email 用户邮箱 + * @param resetToken 重置token(有效期24小时内) + * @param newPassword 新密码(加密) + * @return 重置结果 + */ + CompletableFuture resetPassword(String email, String resetToken, String newPassword); + + /** + * 修改密码接口 + * @param username 用户名 + * @param oldPassword 旧密码(加密) + * @param newPassword 新密码(加密) + * @return 修改结果 + */ + CompletableFuture changePassword(String username, String oldPassword, String newPassword); + + +} diff --git a/src/main/java/com/axis/innovators/box/verification/api/UserApiImpl.java b/src/main/java/com/axis/innovators/box/verification/api/UserApiImpl.java new file mode 100644 index 0000000..fdda647 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/verification/api/UserApiImpl.java @@ -0,0 +1,65 @@ +package com.axis.innovators.box.verification.api; + +import java.util.concurrent.CompletableFuture; + +public class UserApiImpl implements UserApi { + @Override + public CompletableFuture sendVerificationCode(String email) { + // Implementation here + return CompletableFuture.completedFuture(new UserApiResult(true, "Code sent", null)); + } + + @Override + public CompletableFuture verifyCode(String email, String verificationCode) { + // Implementation here + return CompletableFuture.completedFuture(new UserApiResult(true, "Code verified", null)); + } + + @Override + public CompletableFuture checkTokenValidity(String token) { + // Implementation here + return CompletableFuture.completedFuture(new UserApiResult(true, "Token is valid", null)); + } + + @Override + public CompletableFuture applyNewToken(String oldToken) { + // Implementation here + return CompletableFuture.completedFuture(new UserApiResult(true, "New token applied", null)); + } + + @Override + public CompletableFuture login(String username, String password) { + // Implementation here + return CompletableFuture.completedFuture(new UserApiResult(true, "Login successful", null)); + } + + @Override + public CompletableFuture logout(String username) { + // Implementation here + return CompletableFuture.completedFuture(new UserApiResult(true, "Logout successful", null)); + } + + @Override + public CompletableFuture register(String username, String password, String email) { + // Implementation here + return CompletableFuture.completedFuture(new UserApiResult(true, "Registration successful", null)); + } + + @Override + public CompletableFuture postResetPasswordRequest(String email) { + // Implementation here + return CompletableFuture.completedFuture(new UserApiResult(true, "Reset password request sent", null)); + } + + @Override + public CompletableFuture resetPassword(String email, String resetToken, String newPassword) { + // Implementation here + return CompletableFuture.completedFuture(new UserApiResult(true, "Password reset successful", null)); + } + + @Override + public CompletableFuture changePassword(String username, String oldPassword, String newPassword) { + // Implementation here + return CompletableFuture.completedFuture(new UserApiResult(true, "Password changed successfully", null)); + } +} diff --git a/src/main/java/com/axis/innovators/box/verification/api/UserApiResult.java b/src/main/java/com/axis/innovators/box/verification/api/UserApiResult.java new file mode 100644 index 0000000..3d075ee --- /dev/null +++ b/src/main/java/com/axis/innovators/box/verification/api/UserApiResult.java @@ -0,0 +1,25 @@ +package com.axis.innovators.box.verification.api; + +public class UserApiResult { + private final boolean m_success; + private final String m_message; + private final Object m_data; + + public UserApiResult(boolean success, String message, Object data) { + this.m_success = success; + this.m_message = message; + this.m_data = data; + } + + public boolean success() { + return m_success; + } + + public String message() { + return m_message; + } + + public Object data() { + return m_data; + } +} diff --git a/src/main/java/com/axis/innovators/box/window/CasdoorLoginWindow.java b/src/main/java/com/axis/innovators/box/window/CasdoorLoginWindow.java new file mode 100644 index 0000000..70de0ca --- /dev/null +++ b/src/main/java/com/axis/innovators/box/window/CasdoorLoginWindow.java @@ -0,0 +1,268 @@ +package com.axis.innovators.box.window; + +import com.axis.innovators.box.browser.CefAppManager; +import com.axis.innovators.box.tools.LoadResource; +import com.axis.innovators.box.verification.CasdoorServer; +import com.axis.innovators.box.verification.LoginData; +import com.axis.innovators.box.verification.LoginResult; +import com.axis.innovators.box.verification.Result; +import com.sun.net.httpserver.HttpServer; +import com.sun.net.httpserver.HttpExchange; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.awt.Desktop; +import java.awt.FlowLayout; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.casbin.casdoor.config.Config; +import org.casbin.casdoor.entity.User; +import org.casbin.casdoor.service.AuthService; + +import javax.swing.*; +import config.CasdoorConfig; + +import org.cef.CefApp; +import org.cef.CefClient; +import org.cef.browser.CefBrowser; + + +public class CasdoorLoginWindow { + private final Logger logger = LogManager.getLogger(CasdoorLoginWindow.class); + private final CasdoorServer casdoorServer; + + private CefBrowser browser; // 便于复用 + private HttpServer server; // 后端处理登陆成功后的跳转 + + private JDialog dialog; + + private LoginResult loginResult = null; + + private boolean windowVisible = true; + private boolean isModal = true; + + public CasdoorLoginWindow() { + casdoorServer = new CasdoorServer(); + } + + // 启动本地 HTTP 服务监听 Casdoor 回调 + private void startLocalCallbackServer() { + try { + server = HttpServer.create(new java.net.InetSocketAddress(CasdoorConfig.CASDOOR_WEB_SERVER_PORT), 0); + server.createContext("/casdoor/callback", this::handleCallback); + server.setExecutor(java.util.concurrent.Executors.newSingleThreadExecutor()); + server.start(); + } catch (IOException e) { + System.err.println("本地回调服务启动失败: " + e.getMessage()); + } + } + + // 处理回调请求,自动填充 code 和 state + private void handleCallback(HttpExchange exchange) throws IOException { + String query = exchange.getRequestURI().getQuery(); + AtomicReference code = new AtomicReference<>(""); + AtomicReference state = new AtomicReference<>(""); + if (query != null) { + for (String param : query.split("&")) { + String[] kv = param.split("="); + if (kv.length == 2) { + if (kv[0].equals("code")) + code.set(kv[1]); + if (kv[0].equals("state")) + state.set(kv[1]); + logger.info("Received callback with code: " + code.get() + ", state: " + state.get()); + loginResult = parseUserInfo(code.get(), state.get()); + String response = "Login success, please close this window."; + exchange.sendResponseHeaders(200, response.getBytes().length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes()); + } + + } + } + } + } + + private void initUI() { + dialog = new JDialog(); + dialog.setTitle("AXIS 认证"); + dialog.setSize(500, 750); + dialog.setLocationRelativeTo(null); // 居中显示 + dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); + + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); + + + JButton backButton = new JButton(""); + backButton.setIcon(LoadIcon.loadIcon("back.png", 24)); + backButton.addActionListener(e -> browser.goBack()); + backButton.setFocusPainted(false); + backButton.setBorderPainted(false); + JButton forwardButton = new JButton(""); + forwardButton.setIcon(LoadIcon.loadIcon("forward.png", 24)); + forwardButton.addActionListener(e -> browser.goForward()); + forwardButton.setFocusPainted(false); + forwardButton.setBorderPainted(false); + JButton refreshButton = new JButton(""); + refreshButton.setIcon(LoadIcon.loadIcon("refresh.png", 24)); + refreshButton.addActionListener(e -> browser.reload()); + refreshButton.setFocusPainted(false); + refreshButton.setBorderPainted(false); + // 将按钮面板添加到主panel顶部 + buttonPanel.add(backButton); + buttonPanel.add(forwardButton); + buttonPanel.add(refreshButton); + panel.add(buttonPanel, 0); + + if (browser == null) { + // 初始化浏览器 + CefApp cefApp = CefAppManager.getInstance(); + CefClient client = cefApp.createClient(); + browser = client.createBrowser( + casdoorServer.getSigninUrl(), + false, + false); + } + + panel.add(browser.getUIComponent()); + dialog.add(panel); + dialog.addWindowListener(new java.awt.event.WindowAdapter() { + @Override + public void windowClosed(java.awt.event.WindowEvent e) { + if (server != null) { + server.stop(0); + server = null; + } + } + }); + } + + private void openLoginAndListen() { + if (server == null) { + startLocalCallbackServer(); + } + browser.loadURL(casdoorServer.getSigninUrl()); + } + + /** + * 在默认浏览器打开 Casdoor 登录页面 + */ + private void openCasdoorLoginPageInDefaultBrowser() { + String loginUrl = casdoorServer.getSigninUrl(); + try { + Desktop.getDesktop().browse(new URI(loginUrl)); + } catch (Exception ex) { + JOptionPane.showMessageDialog(dialog, "无法打开浏览器: " + ex.getMessage()); + } + } + + private LoginResult parseUserInfo(String code, String state) { + if (code.isEmpty() || state.isEmpty()) { + return new LoginResult(false, "Login failed with error: Invalid code or state.", null); + } + try { + String token = casdoorServer.getOAuthToken(code, state); + User user = casdoorServer.parseJwtToken(token); + return new LoginResult(true, "Login successful.", new LoginData(token, user)); + } catch (Exception ex) { + logger.error("解析登录信息失败: " + ex.getMessage()); + return new LoginResult(false, "Login failed with error: " + ex.getMessage(), null); + } + } + + private void resetResult() { + loginResult = null; + } + + /** + * 以阻塞形式显示(除非setVisible为false)窗口,同时在退出时获取返回值 + * @return 返回登录结果 + */ + public LoginResult exec() { + resetResult(); + initUI(); // 每次显示窗口都先调用一次 + openLoginAndListen(); + dialog.setModal(true); + isModal = true; + dialog.setVisible(windowVisible); + return getLoginResult(); + } + + /** + * 非模态形式显示窗口(与setVisible无关) + */ + public CompletableFuture show() { + resetResult(); + initUI(); // 每次显示窗口都先调用一次 + CompletableFuture future = new CompletableFuture<>(); + dialog.addWindowListener(new java.awt.event.WindowAdapter() { + @Override + public void windowClosed(java.awt.event.WindowEvent e) { + future.complete(getLoginResult()); + } + }); + openLoginAndListen(); + dialog.setModal(false); + isModal = false; + dialog.setVisible(true); + + return future; + } + + /** + * 窗口是否模态窗口 + * @return true or false + */ + public boolean isModal() { + return isModal; + } + + /** + * 设置窗口是否可见 + * @param b 是否可见 + */ + public void setVisible(boolean b) { + this.windowVisible = b; + dialog.setVisible(b); + + } + + /** + * 获取登录结果 + * @return 登录结果 + */ + public LoginResult getLoginResult() { + return loginResult; + } + + public static LoginResult showLoginDialogAndGetLoginResult() throws InterruptedException, InvocationTargetException { + AtomicReference result = new AtomicReference<>(); + SwingUtilities.invokeAndWait(() -> { + CasdoorLoginWindow window = new CasdoorLoginWindow(); + result.set(window.exec()); + }); + return result.get(); + } + + + public static void main(String[] args) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + Result result = showLoginDialogAndGetLoginResult(); + System.out.println("Login result: " + result); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/axis/innovators/box/window/LoginWindow.java b/src/main/java/com/axis/innovators/box/window/LoginWindow.java deleted file mode 100644 index e0e6664..0000000 --- a/src/main/java/com/axis/innovators/box/window/LoginWindow.java +++ /dev/null @@ -1,539 +0,0 @@ -package com.axis.innovators.box.window; - -import com.axis.innovators.box.verification.OnlineVerification; -import com.axis.innovators.box.verification.UserTags; -import com.axis.innovators.box.verification.VerificationService; -import com.formdev.flatlaf.FlatDarculaLaf; - -import javax.swing.*; -import javax.swing.border.LineBorder; -import javax.swing.text.JTextComponent; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.lang.reflect.InvocationTargetException; -import java.util.concurrent.atomic.AtomicReference; - -/** - * 重构后的现代化单窗口登录/注册/找回密码界面(带平滑切换动画) - * 保留原有验证逻辑接口调用(OnlineVerification / VerificationService) - * - * 说明: - * - 单窗口(JDialog)内使用滑动动画切换视图(仿微软登录体验)。 - * - 所有子界面(登录/注册/找回密码)都在同一容器中切换,不再弹新窗口。 - * - 按钮与输入框固定宽度,避免被挤压变形。 - * - * 注意:需要 flatlaf 依赖以呈现更现代的外观。 - */ -public class LoginWindow { - private static final AtomicReference loginResult = new AtomicReference<>(UserTags.None); - - private final JDialog dialog; - private final JLayeredPane layeredPane; - private final int DIALOG_WIDTH = 460; - private final int DIALOG_HEIGHT = 560; - - // 登录面板中的控件需要在类域以便访问 - private JTextField loginEmailField; - private JPasswordField loginPasswordField; - - public static UserTags createAndShow() throws InterruptedException, InvocationTargetException { - AtomicReference result = new AtomicReference<>(UserTags.None); - SwingUtilities.invokeAndWait(() -> { - LoginWindow window = new LoginWindow(); - window.dialog.setVisible(true); - result.set(loginResult.get()); - if (result.get() == UserTags.None) { - System.exit(0); - } - }); - return result.get(); - } - - public LoginWindow() { - setupLookAndFeel(); - dialog = new JDialog((Frame) null, "AXIS 安全认证", true); - dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - dialog.setSize(DIALOG_WIDTH, DIALOG_HEIGHT); - dialog.setResizable(false); - dialog.setLocationRelativeTo(null); - - // 根容器:深色背景并居中卡片 - JPanel root = new JPanel(new GridBagLayout()); - root.setBackground(new Color(0x202225)); - root.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); - dialog.setContentPane(root); - - // 卡片容器(居中) - JPanel cardWrapper = new JPanel(null) { - @Override - public Dimension getPreferredSize() { - return new Dimension(DIALOG_WIDTH - 40, DIALOG_HEIGHT - 40); - } - }; - cardWrapper.setOpaque(false); - cardWrapper.setPreferredSize(new Dimension(DIALOG_WIDTH - 40, DIALOG_HEIGHT - 40)); - - GridBagConstraints gbc = new GridBagConstraints(); - gbc.gridx = 0; - gbc.gridy = 0; - root.add(cardWrapper, gbc); - - // 分层面板用于动画 - layeredPane = new JLayeredPane(); - layeredPane.setBounds(0, 0, DIALOG_WIDTH - 40, DIALOG_HEIGHT - 40); - cardWrapper.add(layeredPane); - - // 卡片背景(圆角) - JPanel backgroundCard = new JPanel(); - backgroundCard.setBackground(new Color(0x2A2E31)); - backgroundCard.setBorder(new RoundBorder(16, new Color(0x3A3F42))); - backgroundCard.setBounds(0, 0, layeredPane.getWidth(), layeredPane.getHeight()); - backgroundCard.setLayout(null); - layeredPane.add(backgroundCard, Integer.valueOf(0)); - - // 创建三个面板(宽度等于容器宽度),初始将登录面板放置在0位置 - JPanel loginPanel = buildLoginPanel(); - JPanel registerPanel = buildRegisterPanel(); - JPanel forgotPanel = buildForgotPanel(); - - int w = layeredPane.getWidth(); - int h = layeredPane.getHeight(); - - loginPanel.setBounds(0, 0, w, h); - registerPanel.setBounds(w, 0, w, h); // 右侧预置 - forgotPanel.setBounds(w * 2, 0, w, h); - - layeredPane.add(loginPanel, Integer.valueOf(1)); - layeredPane.add(registerPanel, Integer.valueOf(1)); - layeredPane.add(forgotPanel, Integer.valueOf(1)); - - dialog.pack(); - // ensure layered sizes match after pack - SwingUtilities.invokeLater(() -> { - layeredPane.setBounds(0, 0, cardWrapper.getWidth(), cardWrapper.getHeight()); - backgroundCard.setBounds(0, 0, layeredPane.getWidth(), layeredPane.getHeight()); - int nw = layeredPane.getWidth(), nh = layeredPane.getHeight(); - loginPanel.setBounds(0, 0, nw, nh); - registerPanel.setBounds(nw, 0, nw, nh); - forgotPanel.setBounds(nw * 2, 0, nw, nh); - }); - } - - // 切换动画:direction = 1 表示向左滑动进入下一页(当前 -> 右侧),-1 表示向右滑动进入上一页 - private void slideTo(int targetIndex) { - Component[] comps = layeredPane.getComponents(); - // 每个面板宽度 - int w = layeredPane.getWidth(); - // 当前最左边的x位置(找到最左的那个主要面板) - // 我们采用简单方式:目标面板应该位于 x = targetIndex * w (0,1,2) - int targetX = -targetIndex * w; // 我们会将所有面板整体平移,使目标显示在 x=0 - - // 获取当前 offset (使用第一个面板的 x 来代表整体偏移) - int startOffset = 0; - // find current offset by checking bounds of first panel (assume index 0 is login) - if (comps.length > 0) { - startOffset = comps[0].getX(); - } - - int start = startOffset; - int end = targetX; - - int duration = 300; // ms - int fps = 60; - int delay = 1000 / fps; - int steps = Math.max(1, duration / delay); - final int[] step = {0}; - - Timer timer = new Timer(delay, null); - timer.addActionListener((ActionEvent e) -> { - step[0]++; - double t = (double) step[0] / steps; - // ease in-out cubic - double tt = t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; - int cur = (int) Math.round(start + (end - start) * tt); - - // 平移所有在 layeredPane 中(除背景)组件 - for (Component c : layeredPane.getComponents()) { - if (c instanceof JPanel && c.isVisible()) { - // 计算原始索引根据名字 - String name = c.getName(); - // 我们之前把panel放在 x = idx * w ; 现在把它设置为 idx*w + cur - int idx = 0; - if ("login".equals(name)) idx = 0; - else if ("register".equals(name)) idx = 1; - else if ("forgot".equals(name)) idx = 2; - c.setLocation(idx * w + cur, 0); - } - } - - layeredPane.repaint(); - - if (step[0] >= steps) { - timer.stop(); - } - }); - timer.setInitialDelay(0); - timer.start(); - } - - private JPanel buildLoginPanel() { - JPanel p = new JPanel(null); - p.setOpaque(false); - p.setName("login"); - int w = DIALOG_WIDTH - 40; - int h = DIALOG_HEIGHT - 40; - - // 标题区 - JLabel appTitle = new JLabel("AXIS"); - appTitle.setFont(new Font("微软雅黑", Font.BOLD, 28)); - appTitle.setForeground(Color.WHITE); - appTitle.setBounds(28, 20, w - 56, 36); - - JLabel subtitle = new JLabel("安全认证 — 请登录以继续"); - subtitle.setFont(new Font("微软雅黑", Font.PLAIN, 12)); - subtitle.setForeground(new Color(0xA7AEB5)); - subtitle.setBounds(28, 56, w - 56, 18); - - p.add(appTitle); - p.add(subtitle); - - // 卡片内控件起始 y - int startY = 96; - int fieldW = Math.min(360, w - 56); - int fieldX = (w - fieldW) / 2; - - // Email - JLabel emailLabel = new JLabel("账号"); - emailLabel.setForeground(new Color(0x99A0A7)); - emailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13)); - emailLabel.setBounds(fieldX, startY, fieldW, 18); - - loginEmailField = new JTextField(); - styleInput(loginEmailField); - loginEmailField.setBounds(fieldX, startY + 22, fieldW, 44); - loginEmailField.putClientProperty("JTextField.placeholderText", "邮箱或手机号"); - - // Password - JLabel pwdLabel = new JLabel("密码"); - pwdLabel.setForeground(new Color(0x99A0A7)); - pwdLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13)); - pwdLabel.setBounds(fieldX, startY + 22 + 44 + 12, fieldW, 18); - - loginPasswordField = new JPasswordField(); - styleInput(loginPasswordField); - loginPasswordField.setBounds(fieldX, startY + 22 + 44 + 12 + 20, fieldW - 48, 44); - loginPasswordField.putClientProperty("JTextField.placeholderText", "请输入密码"); - - // eye toggle - JToggleButton eyeBtn = new JToggleButton("显示"); - eyeBtn.setFont(new Font("微软雅黑", Font.PLAIN, 12)); - eyeBtn.setFocusable(false); - eyeBtn.setBorderPainted(false); - eyeBtn.setContentAreaFilled(true); - eyeBtn.setBackground(new Color(0x39424A)); - eyeBtn.setForeground(Color.WHITE); - eyeBtn.setBounds(fieldX + fieldW - 44, startY + 22 + 44 + 12 + 20, 44, 44); - eyeBtn.addActionListener(e -> { - if (eyeBtn.isSelected()) loginPasswordField.setEchoChar((char) 0); - else loginPasswordField.setEchoChar('•'); - }); - - // 登录按钮(占满宽度) - JButton loginBtn = new JButton("立即登录"); - stylePrimaryButton(loginBtn); - loginBtn.setBounds(fieldX, startY + 22 + 44 + 12 + 20 + 44 + 22, fieldW, 48); - loginBtn.addActionListener(e -> doLogin()); - - // 链接区域(注册 / 忘记密码) — 点击切换到对应面板 - JButton toRegister = createTextLink("注册账号"); - toRegister.setBounds(fieldX, startY + 22 + 44 + 12 + 20 + 44 + 22 + 60, 120, 20); - toRegister.addActionListener(e -> slideTo(1)); - - JButton toForgot = createTextLink("忘记密码"); - toForgot.setBounds(fieldX + fieldW - 120, startY + 22 + 44 + 12 + 20 + 44 + 22 + 60, 120, 20); - toForgot.addActionListener(e -> slideTo(2)); - - // footer - JLabel footer = new JLabel("使用你的 AXIS 账户进行登录。"); - footer.setForeground(new Color(0x8F969C)); - footer.setFont(new Font("微软雅黑", Font.PLAIN, 11)); - footer.setBounds(fieldX, h - 44, fieldW, 18); - - p.add(emailLabel); - p.add(loginEmailField); - p.add(pwdLabel); - p.add(loginPasswordField); - p.add(eyeBtn); - p.add(loginBtn); - p.add(toRegister); - p.add(toForgot); - p.add(footer); - - return p; - } - - private JPanel buildRegisterPanel() { - JPanel p = new JPanel(null); - p.setOpaque(false); - p.setName("register"); - int w = DIALOG_WIDTH - 40; - int h = DIALOG_HEIGHT - 40; - - JLabel title = new JLabel("创建账号"); - title.setFont(new Font("微软雅黑", Font.BOLD, 24)); - title.setForeground(Color.WHITE); - title.setBounds(28, 20, w - 56, 36); - - JLabel desc = new JLabel("快速创建你的 AXIS 帐号"); - desc.setFont(new Font("微软雅黑", Font.PLAIN, 12)); - desc.setForeground(new Color(0xA7AEB5)); - desc.setBounds(28, 56, w - 56, 18); - - p.add(title); - p.add(desc); - - int startY = 96; - int fieldW = Math.min(360, w - 56); - int fieldX = (w - fieldW) / 2; - - // 用户名 - JLabel nameLabel = new JLabel("用户名"); - nameLabel.setForeground(new Color(0x99A0A7)); - nameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13)); - nameLabel.setBounds(fieldX, startY, fieldW, 18); - - JTextField nameField = new JTextField(); - styleInput(nameField); - nameField.setBounds(fieldX, startY + 22, fieldW, 44); - - // 邮箱 - JLabel emailLabel = new JLabel("邮箱"); - emailLabel.setForeground(new Color(0x99A0A7)); - emailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13)); - emailLabel.setBounds(fieldX, startY + 22 + 44 + 12, fieldW, 18); - - JTextField emailField = new JTextField(); - styleInput(emailField); - emailField.setBounds(fieldX, startY + 22 + 44 + 12 + 20, fieldW, 44); - - // 密码 - JLabel pwdLabel = new JLabel("密码"); - pwdLabel.setForeground(new Color(0x99A0A7)); - pwdLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13)); - pwdLabel.setBounds(fieldX, startY + 22 + 44 + 12 + 20 + 44 + 12, fieldW, 18); - - JPasswordField pwdField = new JPasswordField(); - styleInput(pwdField); - pwdField.setBounds(fieldX, startY + 22 + 44 + 12 + 20 + 44 + 12 + 20, fieldW, 44); - - // 确认密码 - JLabel confirmLabel = new JLabel("确认密码"); - confirmLabel.setForeground(new Color(0x99A0A7)); - confirmLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13)); - confirmLabel.setBounds(fieldX, startY + 22 + 44 + 12 + 20 + 44 + 12 + 20 + 44 + 12, fieldW, 18); - - JPasswordField confirmField = new JPasswordField(); - styleInput(confirmField); - confirmField.setBounds(fieldX, startY + 22 + 44 + 12 + 20 + 44 + 12 + 20 + 44 + 12 + 20, fieldW, 44); - - // 注册按钮 - JButton regBtn = new JButton("创建账号"); - stylePrimaryButton(regBtn); - regBtn.setBounds(fieldX, startY + 22 + 44 + 12 + 20 + 44 + 12 + 20 + 44 + 12 + 20 + 44 + 18, fieldW, 48); - - regBtn.addActionListener(e -> { - String name = nameField.getText().trim(); - String email = emailField.getText().trim(); - String pwd = new String(pwdField.getPassword()); - String confirm = new String(confirmField.getPassword()); - - if (name.isEmpty() || email.isEmpty() || pwd.isEmpty() || confirm.isEmpty()) { - JOptionPane.showMessageDialog(dialog, "请完整填写注册信息", "注册错误", JOptionPane.ERROR_MESSAGE); - return; - } - if (!pwd.equals(confirm)) { - JOptionPane.showMessageDialog(dialog, "两次输入的密码不一致", "注册错误", JOptionPane.ERROR_MESSAGE); - return; - } - boolean success = VerificationService.registerUser(name, email, pwd); - if (success) { - JOptionPane.showMessageDialog(dialog, "注册成功,请登录", "注册成功", JOptionPane.INFORMATION_MESSAGE); - slideTo(0); // 回到登录页面 - } else { - JOptionPane.showMessageDialog(dialog, "注册失败,请检查信息", "注册错误", JOptionPane.ERROR_MESSAGE); - } - }); - - // 返回登录链接 - JButton back = createTextLink("返回登录"); - back.setBounds(fieldX, regBtn.getY() + regBtn.getHeight() + 12, 120, 20); - back.addActionListener(e -> slideTo(0)); - - p.add(nameLabel); - p.add(nameField); - p.add(emailLabel); - p.add(emailField); - p.add(pwdLabel); - p.add(pwdField); - p.add(confirmLabel); - p.add(confirmField); - p.add(regBtn); - p.add(back); - - return p; - } - - private JPanel buildForgotPanel() { - JPanel p = new JPanel(null); - p.setOpaque(false); - p.setName("forgot"); - int w = DIALOG_WIDTH - 40; - int h = DIALOG_HEIGHT - 40; - - JLabel title = new JLabel("找回密码"); - title.setFont(new Font("微软雅黑", Font.BOLD, 24)); - title.setForeground(Color.WHITE); - title.setBounds(28, 20, w - 56, 36); - - JLabel desc = new JLabel("通过注册邮箱重置密码"); - desc.setFont(new Font("微软雅黑", Font.PLAIN, 12)); - desc.setForeground(new Color(0xA7AEB5)); - desc.setBounds(28, 56, w - 56, 18); - - p.add(title); - p.add(desc); - - int startY = 110; - int fieldW = Math.min(360, w - 56); - int fieldX = (w - fieldW) / 2; - - JLabel emailLabel = new JLabel("注册邮箱"); - emailLabel.setForeground(new Color(0x99A0A7)); - emailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13)); - emailLabel.setBounds(fieldX, startY, fieldW, 18); - - JTextField emailField = new JTextField(); - styleInput(emailField); - emailField.setBounds(fieldX, startY + 22, fieldW, 44); - - JButton sendBtn = new JButton("发送重置邮件"); - stylePrimaryButton(sendBtn); - sendBtn.setBounds(fieldX, startY + 22 + 44 + 22, fieldW, 48); - - sendBtn.addActionListener(e -> { - String email = emailField.getText().trim(); - if (email.isEmpty()) { - JOptionPane.showMessageDialog(dialog, "请输入注册邮箱", "错误", JOptionPane.ERROR_MESSAGE); - return; - } - if (VerificationService.sendPasswordReset(email)) { - JOptionPane.showMessageDialog(dialog, "重置邮件已发送,请查收", "成功", JOptionPane.INFORMATION_MESSAGE); - slideTo(0); - } else { - JOptionPane.showMessageDialog(dialog, "发送失败或邮箱未注册", "失败", JOptionPane.ERROR_MESSAGE); - } - }); - - JButton back = createTextLink("返回登录"); - back.setBounds(fieldX, sendBtn.getY() + sendBtn.getHeight() + 12, 120, 20); - back.addActionListener(e -> slideTo(0)); - - p.add(emailLabel); - p.add(emailField); - p.add(sendBtn); - p.add(back); - - return p; - } - - private void doLogin() { - String email = loginEmailField.getText().trim(); - String password = new String(loginPasswordField.getPassword()).trim(); - - if (email.isEmpty() || password.isEmpty()) { - JOptionPane.showMessageDialog(dialog, "请输入完整的登录信息", "验证错误", JOptionPane.ERROR_MESSAGE); - return; - } - - OnlineVerification onlineVerification = OnlineVerification.validateLogin(email, password); - if (onlineVerification == null) { - String err = OnlineVerification.errorMessage != null && !OnlineVerification.errorMessage.trim().isEmpty() - ? OnlineVerification.errorMessage - : "验证失败,请重试"; - JOptionPane.showMessageDialog(dialog, err, "验证错误", JOptionPane.ERROR_MESSAGE); - return; - } - - loginResult.set(VerificationService.determineUserType(onlineVerification)); - dialog.dispose(); - } - - - // 通用输入框样式 - private void styleInput(JComponent comp) { - comp.setFont(new Font("微软雅黑", Font.PLAIN, 14)); - comp.setBackground(new Color(0x2F3336)); - comp.setForeground(new Color(0xE8ECEF)); - comp.setBorder(BorderFactory.createCompoundBorder( - new RoundBorder(8, new Color(0x3A3F42)), - BorderFactory.createEmptyBorder(10, 12, 10, 12) - )); - if (comp instanceof JTextComponent) ((JTextComponent) comp).setCaretColor(new Color(0x9AA0A6)); - } - - // 主要操作按钮样式(主色) - private void stylePrimaryButton(AbstractButton b) { - b.setFont(new Font("微软雅黑", Font.BOLD, 14)); - b.setForeground(Color.WHITE); - b.setBackground(new Color(0x2B79D0)); - b.setBorderPainted(false); - b.setFocusPainted(false); - b.setOpaque(true); - b.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - } - - private JButton createTextLink(String text) { - JButton btn = new JButton(text); - btn.setFont(new Font("微软雅黑", Font.PLAIN, 12)); - btn.setForeground(new Color(0x79A6FF)); - btn.setBorderPainted(false); - btn.setContentAreaFilled(false); - btn.setFocusPainted(false); - btn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - return btn; - } - - private void setupLookAndFeel() { - try { - UIManager.setLookAndFeel(new FlatDarculaLaf()); - UIManager.put("Component.arc", 12); - UIManager.put("TextComponent.arc", 12); - UIManager.put("Button.arc", 10); - - UIManager.put("Panel.background", new Color(0x202225)); - UIManager.put("TextComponent.background", new Color(0x2F3336)); - UIManager.put("TextComponent.foreground", new Color(0xE8ECEF)); - } catch (UnsupportedLookAndFeelException ex) { - ex.printStackTrace(); - } - } - - // 圆角边框 - private static class RoundBorder extends LineBorder { - private final int radius; - - public RoundBorder(int radius, Color color) { - super(color, 1); - this.radius = radius; - } - - @Override - public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { - Graphics2D g2 = (Graphics2D) g.create(); - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2.setColor(lineColor); - g2.drawRoundRect(x, y, width - 1, height - 1, radius, radius); - g2.dispose(); - } - } -} diff --git a/src/main/java/config/CasdoorConfig.java b/src/main/java/config/CasdoorConfig.java new file mode 100644 index 0000000..feb5e97 --- /dev/null +++ b/src/main/java/config/CasdoorConfig.java @@ -0,0 +1,17 @@ +package config; + +public class CasdoorConfig { + // Casdoor Config Contructor + public static final String CASDOOR_API_URL = "https://casdoor.lingqi.vip"; + public static final String CASDOOR_CLIENT_ID = "efb6af7c9517f19340da"; + public static final String CASDOOR_CLIENT_SECRET = "7396f7e4bcda83756641179108b89356bb4d6d3b"; + public static final String CASDOOR_CERTIFICATE_FILE = ""; + public static final String CASDOOR_ORGANIZATION_NAME = "灵启"; + public static final String CASDOOR_APPLICATION_NAME = "lingqi_box"; + + // Casdoor AuthService getSigninUrl + public static final String CASDOOR_LOGIN_REDIRECT_URI = "http://localhost:10487/casdoor/callback"; + public static final String CASDOOR_SIGNUP_REDIRECT_URI = null; // 如果为 null 则自动跳转到登录页面,如果不需要跳转可以设置为"" + public static final int CASDOOR_WEB_SERVER_PORT = 10487; // HUST + +} diff --git a/src/main/resources/cert/casdoor_cert.pem b/src/main/resources/cert/casdoor_cert.pem new file mode 100644 index 0000000..5e35395 --- /dev/null +++ b/src/main/resources/cert/casdoor_cert.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE3TCCAsWgAwIBAgIDAeJAMA0GCSqGSIb3DQEBCwUAMCgxDjAMBgNVBAoTBWFk +bWluMRYwFAYDVQQDEw1jZXJ0LWJ1aWx0LWluMB4XDTI1MDgxNTEzNDgwMVoXDTQ1 +MDgxNTEzNDgwMVowKDEOMAwGA1UEChMFYWRtaW4xFjAUBgNVBAMTDWNlcnQtYnVp +bHQtaW4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCm56dxzpBRDyG7 +CNt3v51+ikP3dgAtgky5yUSeiQJW8eHaSJayNfIT42GCH3Nk6ZI3xR3nAZ1kaFyg +/ZiVw9QHzju3859osXoK+NMiv75G3Z0xEe2wg2gRJh0FvTDw6TKSv179C5BrxK5y +6O8caP5NAA5Kqn2OiQkIXRBxYU7i0fHrABeJpO396KtZ9oPoe9/ZWGRDTsilbyRB +8gz7JaVq9ECsgPDErmtvUhXLtNPKSfXN2UVVSpE+EET0DJv7dVlSowoaF4tNtA5e +QVTyAR559aPBnXZKD/9arTRCtU1YB2Uv3NuFELeJzchAmf/l+IPbBUfvGdaLE/kg +TB0T9hfiyRpIvjfdEpQTeaOSDVbnO165Z1AWhpxVHEHfaEDD/48aSSfAKty29CaV +wmmBA1aw90AtkyJR3BfrHExAMK4+s89P07ihe+YVUCgrrZk8J7xAQ+g3P4+FF4MI +NrhS/OgHss2e8pxoiv6rvlMXyZqFlocKuaNKCvTy0vvspj1wn6XdX8jZZnpZrKLy +Qbc1LN/HNmMe4RGjIBdlBdBHI0RIH7afLhghor+fZ/5lAa1Fzwv6TbD+QIx1wEq9 +zY6lRamN4iS/AxkIEovcKJq+W0Vcn+0bovcjjmojN2MnkHWH+XCBeyEixh+fuzSi +Zjt3ydhPzU2Vb1vPsVqFQjHHR+FYbwIDAQABoxAwDjAMBgNVHRMBAf8EAjAAMA0G +CSqGSIb3DQEBCwUAA4ICAQBTFfNo5BMqwwlSimxfT2uj1xfXZ/ek0suaY9xZIVVo +XlrbIuE1j4KS1MjOCs1IbkYZW5+MO4c4OcjHh/XusQ2t2PKP7Q0atdFjYeiw987a +oE9r5am2IPzfh3DI3jC7rfz6Jrq64oK23JAXisa21xoB4CflzlpgOFX3h6UfcNhB +bTSQjdyeTZYwCeSmO53iAW9oNlffnzaYtQXqiq7pCGcC7sGZBZJnM0nNvQRYqzcU +8E8elfEwdWqc9ZJ/ah8byobZQHvraRcYDEZ3oqfjMsoycbFfJztaZV1pfJOZNRzG +lypDUCjZeK1z91K6UFws6qOGc6j06pLIXxVmpuw62yCnG3KM55O3GcUc9q3oAyTb +Iq2g3CjIiaFu8AowVyFswvOIK2pvRBBA+54eP079SLqqznHV+AE91nvVLOMn7xVu +vfV2XbxzAk3to39WilEn3KpGavpQnyQnD/nVjpEzzInAjPO6vCkWXs1ZS2sqNciz +T+r9Q3cZUMWusgNNoGfWnjchLOaOPe1zGINwAsSDXjv1YD18pfp5hEPlwSTGrulI +7fN+cGQ5Spv5VhFcOyodgmIvMnM2EcgeH4CPkQ5aiE3qAGzRzfQiPeght/Wkdd4J ++SMykEE2VmLmVD8VQf2j/EVbGQ32TTllYfQEgS/S5pfUPr5RAEljCHlCUiNcHF9c ++g== +-----END CERTIFICATE----- diff --git a/state/toolbox.properties b/state/toolbox.properties new file mode 100644 index 0000000..e69de29