feat(gui,login): 完成登录逻辑
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -40,3 +40,10 @@ bin/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
|
||||
### logs ###
|
||||
*.log
|
||||
logs/
|
||||
|
||||
### JCEF Dlls ###
|
||||
library/jcef/
|
||||
@@ -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 {
|
||||
|
||||
1
gradle.properties
Normal file
1
gradle.properties
Normal file
@@ -0,0 +1 @@
|
||||
org.gradle.java.home=E:\\Softwares\\Java\\jdk-20.0.2
|
||||
@@ -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<WindowsJDialog> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态管理器
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<UserApiResult> sendVerificationCode(String email);
|
||||
|
||||
/**
|
||||
* 验证验证码
|
||||
* @param email 用户邮箱
|
||||
* @param verificationCode 验证码
|
||||
* @return 验证结果
|
||||
*/
|
||||
CompletableFuture<UserApiResult> verifyCode(String email, String verificationCode);
|
||||
|
||||
/**
|
||||
* 检验登录token有效性,用于检验登录状态
|
||||
* @param token 登录token
|
||||
* @return 检验结果
|
||||
*/
|
||||
CompletableFuture<UserApiResult> checkTokenValidity(String token);
|
||||
|
||||
/**
|
||||
* 申请新的token,可以定期申请新的token保证安全性
|
||||
* @param oldToken 旧的登录token
|
||||
* @return 申请结果
|
||||
*/
|
||||
CompletableFuture<UserApiResult> applyNewToken(String oldToken);
|
||||
|
||||
/**
|
||||
* 登录接口
|
||||
* @param username 用户名
|
||||
* @param password 密码(加密)
|
||||
* @return { token: String, expiresIn: Number } 登录结果(含token和有效期,临近到期需要手动申请新的token,每次登陆服务器将更新token有效期)
|
||||
*/
|
||||
CompletableFuture<UserApiResult> login(String username, String password);
|
||||
|
||||
/**
|
||||
* 登出接口
|
||||
* @param username 用户名
|
||||
* @return 登出结果
|
||||
*/
|
||||
CompletableFuture<UserApiResult> logout(String username);
|
||||
|
||||
/**
|
||||
* 注册接口
|
||||
* @param username 用户名
|
||||
* @param password 密码(加密)
|
||||
* @param email 邮箱
|
||||
* @return 注册结果
|
||||
*/
|
||||
CompletableFuture<UserApiResult> register(String username, String password, String email);
|
||||
|
||||
/**
|
||||
* 申请重置密码接口
|
||||
* @param email 用户邮箱
|
||||
* @return { resetToken: String, expiresIn: Number } 重置结果(包含有效期内的重置token和有效期,24小时内有效)
|
||||
*/
|
||||
CompletableFuture<UserApiResult> postResetPasswordRequest(String email);
|
||||
|
||||
/**
|
||||
* 重置密码接口
|
||||
* @param email 用户邮箱
|
||||
* @param resetToken 重置token(有效期24小时内)
|
||||
* @param newPassword 新密码(加密)
|
||||
* @return 重置结果
|
||||
*/
|
||||
CompletableFuture<UserApiResult> resetPassword(String email, String resetToken, String newPassword);
|
||||
|
||||
/**
|
||||
* 修改密码接口
|
||||
* @param username 用户名
|
||||
* @param oldPassword 旧密码(加密)
|
||||
* @param newPassword 新密码(加密)
|
||||
* @return 修改结果
|
||||
*/
|
||||
CompletableFuture<UserApiResult> changePassword(String username, String oldPassword, String newPassword);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.axis.innovators.box.verification.api;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class UserApiImpl implements UserApi {
|
||||
@Override
|
||||
public CompletableFuture<UserApiResult> sendVerificationCode(String email) {
|
||||
// Implementation here
|
||||
return CompletableFuture.completedFuture(new UserApiResult(true, "Code sent", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<UserApiResult> verifyCode(String email, String verificationCode) {
|
||||
// Implementation here
|
||||
return CompletableFuture.completedFuture(new UserApiResult(true, "Code verified", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<UserApiResult> checkTokenValidity(String token) {
|
||||
// Implementation here
|
||||
return CompletableFuture.completedFuture(new UserApiResult(true, "Token is valid", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<UserApiResult> applyNewToken(String oldToken) {
|
||||
// Implementation here
|
||||
return CompletableFuture.completedFuture(new UserApiResult(true, "New token applied", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<UserApiResult> login(String username, String password) {
|
||||
// Implementation here
|
||||
return CompletableFuture.completedFuture(new UserApiResult(true, "Login successful", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<UserApiResult> logout(String username) {
|
||||
// Implementation here
|
||||
return CompletableFuture.completedFuture(new UserApiResult(true, "Logout successful", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<UserApiResult> register(String username, String password, String email) {
|
||||
// Implementation here
|
||||
return CompletableFuture.completedFuture(new UserApiResult(true, "Registration successful", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<UserApiResult> postResetPasswordRequest(String email) {
|
||||
// Implementation here
|
||||
return CompletableFuture.completedFuture(new UserApiResult(true, "Reset password request sent", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<UserApiResult> resetPassword(String email, String resetToken, String newPassword) {
|
||||
// Implementation here
|
||||
return CompletableFuture.completedFuture(new UserApiResult(true, "Password reset successful", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<UserApiResult> changePassword(String username, String oldPassword, String newPassword) {
|
||||
// Implementation here
|
||||
return CompletableFuture.completedFuture(new UserApiResult(true, "Password changed successfully", null));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<String> code = new AtomicReference<>("");
|
||||
AtomicReference<String> 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<LoginResult> show() {
|
||||
resetResult();
|
||||
initUI(); // 每次显示窗口都先调用一次
|
||||
CompletableFuture<LoginResult> 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<LoginResult> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<UserTags> 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<UserTags> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/main/java/config/CasdoorConfig.java
Normal file
17
src/main/java/config/CasdoorConfig.java
Normal file
@@ -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
|
||||
|
||||
}
|
||||
29
src/main/resources/cert/casdoor_cert.pem
Normal file
29
src/main/resources/cert/casdoor_cert.pem
Normal file
@@ -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-----
|
||||
0
state/toolbox.properties
Normal file
0
state/toolbox.properties
Normal file
Reference in New Issue
Block a user