这两天顾着写项目的登录注册以及大体思路,花费了很多时间,虽然登录注册很早之前就写完了,但是有很多bug要改,然后还对登录注册新增加了一个验证码获取和验证码过期的功能,以及将登录传输到服务器上进行,与通过服务器来发送邮件的功能。
这里先说关于验证码过期,我是先设置了一个成员long变量用来获取时间,方便在这个类中调用
private long expirationTimeMillis;//获取点击获取验证码按钮的时间,实现60s内不能重复点击(在另一个方法中则是判断验证码过期
然后我设置了一个获取方法,在获取方法中会对邮箱格式进行正则判断,判断完格式后就对密码进行判断,判断是否一致或者是否存在空密码,直到这些验证都通过了,获取按钮才会起作用,设置了一个按钮事件是获取当前时间。
GetCaptcha.setOnMouseClicked(event -> expirationTimeMillis = System.currentTimeMillis());
这样可以做到一点击获取按钮就开始计时。
获取按钮上连接了一个方法,具体的方法如下:
@FXML
void CheckRegisterEmail() throws InterruptedException {
GetCaptcha.setOnMouseClicked(event -> expirationTimeMillis = System.currentTimeMillis());
// 获取验证码的时间
boolean isExpired = isCodeExpired(expirationTimeMillis);
//存储验证码
String Psd = PsdText.getText();//获得密码
String Psd2 = visiblePsdText.getText();
String checkPsd = CheckPsdText.getText();//获得第二份密码
String regPsd = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d!@#$%^&*.]{8,16}$";
Pattern patternPsd = Pattern.compile(regPsd);
Matcher matcherPsd = patternPsd.matcher(Psd);
if(!isExpired)
{
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("警告!");
alert.setHeaderText(null);
alert.setContentText("60s内不能重复获取验证码!");
alert.showAndWait();
}else{
if(Check())
{
if(Psd.length() == 0 || checkPsd.length() == 0)
{
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("警告!");
alert.setHeaderText(null);
alert.setContentText("密码不能为空!");
alert.showAndWait();
}else{
if(matcherPsd.find())
{
if(Psd.equals(checkPsd) ||Psd2.equals(checkPsd))
{
SendEmail();
}else{
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("警告!");
alert.setHeaderText(null);
alert.setContentText("两次输入的密码不一致!");
alert.showAndWait();
}
}else{
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("警告!");
alert.setHeaderText(null);
alert.setContentText("密码的长度应该在8-16位之间,且应同时包含英文、数字与特殊符号!");
alert.showAndWait();
}
}
}else{
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("警告!");
alert.setHeaderText(null);
alert.setContentText("请输入正确的邮箱格式!");
alert.showAndWait();
}
}
}
在所有判断都通过之后,就会发送一封邮件到对应的邮箱(因为要测试程序的原因,发送邮件给我写在了客户端上,目前已经迁到了服务端,但是还没写好所以这里不展示)。
在获取验证码后输入验证码完成注册操作,当然这里对注册按钮也设置了方法用来判断验证码过期问题,以及60s重复获取验证码的问题。这边是有一个方法,在点击获取按钮的时候会进入到这个方法再重新获取一次时间,然后用新获取的时间与最开始点击按钮所获取的时间进行比较,返回一个bool值,这样就可以知道是否在一分钟内重复获取了验证码,有一点要注意的是时间是以毫秒计时的所以要*1000才是一秒。
boolean isCodeExpired(long expirationTimeMillis) {//判断是否有重复获取验证码的行为
long currentTimeMillis = System.currentTimeMillis();
expirationTimeMillis = expirationTimeMillis + 1000*60;
return currentTimeMillis > expirationTimeMillis;
}
这个方法则是用来判断是否在60s内重复获取,当然也有个一样的方法判断5分钟内验证码过期的问题,在五分钟后根据bool值选择是否清空当前验证码来做到判断验证码过期问题。
boolean isCaptcha(long expirationTimeMillis) {//判断验证码是否过期
long currentTimeMillis = System.currentTimeMillis();
expirationTimeMillis = expirationTimeMillis + 1000 * 60 * 5;
return currentTimeMillis > expirationTimeMillis;
}
当然验证码后续会由服务器发出,应该在服务器中设置验证码过期(本地设置也行我觉得,但是服务器得同步删除验证码,具体看后续实现),而验证码过期的时间起点应该是从单击获取按钮开始的,也就是说获取按钮获得的时间可以给两个方法使用。
@FXML
void TryLogin() throws Exception{
String Cap = Captcha.getText();
Alert alert = new Alert(Alert.AlertType.INFORMATION);
if(!isCaptcha(expirationTimeMillis))
{
if(Captcha.getText().length() != 0){
if(Cap.equals(CheckCaptcha)){
alert.setTitle(null);
alert.setHeaderText(null);
alert.setContentText("注册成功!接下来为您跳转到登录页面!");
alert.showAndWait();
new StartLogin().returnLoginPage();//想一想如何将账号传进IDText中
}else{
alert.setTitle("错误!");
alert.setHeaderText(null);
alert.setContentText("验证码错误,请在60s后重新获取验证码!");
expirationTimeMillis = System.currentTimeMillis();
alert.showAndWait();
}
}else{
alert.setTitle("错误!");
alert.setHeaderText(null);
alert.setContentText("请先或取验证码!");
alert.showAndWait();
}
}else{
CheckCaptcha = "";
alert.setTitle("警告!");
alert.setHeaderText(null);
alert.setContentText("验证码过期,请重新获取验证码!");
alert.showAndWait();
}
}
在验证码获取后验证成功则直接跳转到登录页面,不过因为登录页面是后续加载的,暂时没办法实现将登录的ID自动输入进账号框中。
登录功能的大致实现是通过对象流,将User对象传入服务器,再从服务器读入数据库获取账号密码,判断并返回成功或者失败,也是通过boolean方法,但是由于暂时没有通过JDBC写入数据库,所以只是规定了一个账号和密码,暂时只有这一个账号能使用。
下面是客户端连接的代码
public class ClientServer {
private User ums = new User();
private Socket socket;
//这边接受一个ID和密码去服务器中与数据库存储的ID密码进行验证
public boolean checkUser(String UserId,String psd){
boolean flag = false;//判断验证是否成功
ums.setUserID(UserId);
ums.setUserPsd(psd);//设置对象
System.out.println("设置对象成功\n" + "对象ID "+ UserId + "\n" + "对象密码 " + psd);
try {
socket = new Socket(InetAddress.getLocalHost(), 4396);//socket传输
System.out.println("端口正在连接。。。端口连接成功");
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(ums);//对象写入流
System.out.println("对象写入成功");
//读取服务端回送的对象
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
System.out.println("读入流对象创建成功");
Message message = (Message) ois.readObject();//读取返回的Message对象判断是否成功
System.out.println("读取到服务器返回的对象");
if (message.getMsgType().equals(MessageType.MESSAGE_LOGIN_SUCCESS)) {
System.out.println("登陆成功");
flag = true;
} else {
//登录失败
System.out.println("登录失败");
socket.close();
}
}catch(Exception e){
e.printStackTrace();
}
return flag;
}
}
User对象:
这里多了个无参构造器
public class User implements Serializable {
public User(){}
private static final long serialVersionUID = 1L;
private String UserID;//用户ID
private String UserPsd;//用户密码
public User(String userID, String userPsd) {
UserID = userID;
UserPsd = userPsd;
}
public String getUserID() {
return UserID;
}
public void setUserID(String userID) {
this.UserID = userID;
}
public String getUserPsd() {
return UserPsd;
}
public void setUserPsd(String userPsd) {
this.UserPsd = userPsd;
}
}
服务端接受并返回Message对象
public void CheckLogin(){
try {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
System.out.println("读入流对象创建成功");
User user;
user = (User) ois.readObject();
System.out.println("对象读入成功");
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
Message message = new Message();
//接下来从数据库读取数据并验证
if(user.getUserID().equals("1572121126") && user.getUserPsd().equals("Tan.040506"))
{
System.out.println("账号密码验证成功");
message.setMsgType(MessageType.MESSAGE_LOGIN_SUCCESS);
oos.writeObject(message);
}else{
System.out.println("验证失败");
message.setMsgType(MessageType.MESSAGE_LOGIN_FAILED);
oos.writeObject(message);
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
服务端的User对象应该与客户端相同,不同的是客户端的对象我设置了一个无参的构造器方便实例化对象。
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String UserID;//用户ID
private String UserPsd;//用户密码
public User(String userID, String userPsd) {
UserID = userID;
UserPsd = userPsd;
}
public String getUserID() {
return UserID;
}
public void setUserID(String userID) {
UserID = userID;
}
public String getUserPsd() {
return UserPsd;
}
public void setUserPsd(String userPsd) {
UserPsd = userPsd;
}
}
记住要序列化要对象流的类,不然传输会出错,出现了System的地方都是我当时没有正确序列化而导致出错,使用输出语句查看哪里出错而设置的。这样可以完成一个简单的登录功能,注册功能和这个差不多,但是现在难住我的应该是如何通过一个端口接受不同的对象从而做到在服务端实现不同的方法(暂时想到了泛型但是没想到怎么去实现)。
能写的差不多就这些,剩下的有思路但是依然是苦于一个端口如何实现多种不同的功能,等加上线程池再来补充这个项目。