我前面讲过这个项目是分模块去开发的,开发完我就知道不是很符合业务逻辑。所以我还是按照业务去分解
近日,我醒悟。我觉得以后的开发生涯应该多注重一些“无用功”,比方说流程图。那么我们先画一个流程图分析一下登录的逻辑
普通用户与管理员登录逻辑其实是一致的,只是说登录完后的逻辑不一样。对于游客登录,我这边是提供一个按钮,点击后一键登录。另外在我v1版本开发中管理员登录是没有实现将登录的用户存在在Session中。因为初期我只设计一个管理员,显然是存在缺陷的。
我们使用Scene Builder开发页面,我这里是将后期游客登录也设计在内
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import java.lang.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="LeaveMessageSystem.controller.LogupFrameController">
<children>
<VBox prefHeight="393.0" prefWidth="600.0">
<children>
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
<children>
<Label fx:id="systemLabel" text="留言管理系统">
<font>
<Font name="System Bold" size="40.0" />
</font>
</Label>
</children>
<children>
<HBox alignment="BASELINE_RIGHT">
<children>
<Button fx:id="touristButton" mnemonicParsing="false" onAction="#touristButtonEvent" style="-fx-background-color: #00ffff;" text="游客登录" />
</children>
</HBox>
</children>
<VBox.margin>
<Insets left="40.0" />
</VBox.margin>
</HBox>
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
<children>
<Label fx:id="userNameLabel" text="用户名:" />
<TextField fx:id="userNameTextField" promptText="请输入您的账号" />
</children>
</HBox>
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
<children>
<Label fx:id="passwordLabel" text="密 码:" />
<PasswordField fx:id="passwordTextField" promptText="请输入您的密码" />
</children>
</HBox>
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" spacing="80.0">
<children>
<Button fx:id="logupButton" mnemonicParsing="false" onAction="#logupButtonEvent" text=" 登 录" />
<Button fx:id="resetButton" mnemonicParsing="false" onAction="#resetButtonEvent" text=" 重 置" />
</children>
</HBox>
</children>
</VBox>
</children>
</AnchorPane>
效果如下:
设计beans实体层
为了区分Users,我单独设计LoginUser,其实完全可以直接使用Users
package LeaveMessageSystem.beans;
public class LoginUser {
public int status;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
private String username;
private String password;
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
private int roled;
public int getRoled() {
return roled;
}
public void setRoled(int roled) {
this.roled = roled;
}
}
在controller下新建一个java文件用于处理登录逻辑
对于管理员我们跳转到管理员后台界面,对于普通游客在跳转之前,我们需要保存Session。当然我们知道此Session非彼Session,只是为了后面普通游客的实际需求而设计。而游客我们只需要一键跳转就行。 另外在项目中我用了一些封装的轮子,如下SimpleTools
package LeaveMessageSystem.controller;
import LeaveMessageSystem.MainApp;
import LeaveMessageSystem.beans.LoginUser;
import LeaveMessageSystem.beans.Session;
import LeaveMessageSystem.dao.UserDao;
import LeaveMessageSystem.tools.SimpleTools;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.stage.Stage;
import java.sql.SQLException;
public class LogupFrameController {
public Button touristButton;
private SimpleTools simpleTools = new SimpleTools();
@FXML
private Stage stage;
public Stage getStage() {
return stage;
}
public void setStage(Stage stage) {
this.stage = stage;
}
@FXML
private TextField userNameTextField;
@FXML
private PasswordField passwordTextField;
@FXML
private Button logupButton;
@FXML
private Label userNameLabel;
@FXML
private Label systemLabel;
@FXML
private Button resetButton;
@FXML
private Label passwordLabel;
// “登录”按钮的事件监听器方法
public void logupButtonEvent(ActionEvent event) throws Exception{
if (userNameTextField.getText().equals("") || passwordTextField.getText().equals("")) {
SimpleTools.informationDialog(Alert.AlertType.WARNING, "提示", "警告", "请按照文本框内容提示正确填写内容!");
} else {
// 实例化UserDao对象
UserDao userDao = new UserDao();
// 登录用户
LoginUser loginUser = null;
try {
loginUser = userDao.login(userNameTextField.getText(), passwordTextField.getText());
} catch (SQLException throwables) {
throwables.printStackTrace();
}
if(loginUser!=null) {
// 对是否登录成功进行判断
if (loginUser.getUsername() != null && loginUser.getPassword() != null) {
// roled用户状态
int roled=loginUser.getRoled();
// // 设置通信对象,建立登录成功通信
// Session.setUser(loginUser);
// 在弹出的提示框种获取用户反馈
boolean b = SimpleTools.informationDialog(Alert.AlertType.INFORMATION, "提示", "信息", "恭喜登录成功,欢迎使用本系统!");
// 如果用户确定登录,则跳转到主界面
if (b) {
//获取当前ip
// InetAddress ip4 = Inet4Address.getLocalHost();
// String[] strs=String.valueOf(ip4).split("/");
// String ip=strs[1];
// System.err.println(ip);
// 跳转到主界面后,关闭登录界面
stage.close();
if(roled==1) {
// 打开主窗口
new MainApp().initMainFrame();
}
else {
//设置session通信对象
Session.setLoginUser(loginUser);
//普通用户主界面
new MainApp().initMainOrdinaryUserFrame();
}
}
} else {
SimpleTools.informationDialog(Alert.AlertType.ERROR, "错误", "错误", "用户名或密码错误!");
}
}else {
SimpleTools.informationDialog(Alert.AlertType.ERROR, "错误", "错误", "用户名或密码错误!");
}
}
}
// “重置”按钮的事件监听器方法
public void resetButtonEvent(ActionEvent event) {
userNameTextField.setText("");
passwordTextField.setText("");
}
//游客登录
public void touristButtonEvent(ActionEvent actionEvent) {
// 跳转到主界面后,关闭登录界面
stage.close();
// 打开主窗口
new MainApp().initMainTouristFrame();
}
}
SimpleTools代码,其为一个工具类
package LeaveMessageSystem.tools;
import LeaveMessageSystem.beans.Message;
import LeaveMessageSystem.beans.UserTypeNewBeanData;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import java.util.List;
import java.util.Optional;
public class SimpleTools {
/**
* 操作结果:JavaFX设置按钮、标签等组件的图标
*
* @param labeleds 需要设置图标的按钮
* @param imagePaths 图标的路径
*/
public void setLabeledImage(Labeled[] labeleds, String[] imagePaths) {
for (int i = 0; i < labeleds.length; i++) {
labeleds[i].setGraphic(new ImageView(new Image("file:" + imagePaths[i])));
}
}
/**
* 操作结果:清空文本框组件的内容
*
* @param inputControls 文本框或文本域组等
*/
public void clearTextField(TextInputControl... inputControls) {
for (int i = 0; i < inputControls.length; i++) {
inputControls[i].setText("");
}
}
/**
* 操作结果:取消所有单选按钮选择
*
* @param toggleButtons 单选按钮组
*/
public void clearSelectedRadioButton(ToggleButton... toggleButtons) {
for (int i = 0; i < toggleButtons.length; i++) {
toggleButtons[i].setSelected(false);
}
}
/**
* 操作结果:取消所有下拉列表框选择
*
* @param comboBoxes 下拉列表框组
*/
public void clearSelectedComboBox(ComboBox... comboBoxes) {
for (int i = 0; i < comboBoxes.length; i++) {
comboBoxes[i].getSelectionModel().select(-1);// 设置选择的索引为-1,就不会选择任何选择选项了。
}
}
/**
* 操作结果:JavaFX设置菜单项组件的图标
*
* @param menuItems 菜单项
* @param imagePaths 图标的路径
*/
public void setMenuItemImage(MenuItem[] menuItems, String[] imagePaths) {
for (int i = 0; i < menuItems.length; i++) {
menuItems[i].setGraphic(new ImageView(new Image("file:" + imagePaths[i])));
}
}
/**
* 操作结果:JavaFX判断是否为空
*
* @param str 文本
* @return boolean 如果不为空返回true,否则返回false
*/
public boolean isEmpty(String str) {
if (str == null || "".equals(str.trim())) {
return true;
} else {
return false;
}
}
/**
* 操作结果:自定义一个JavaFX的对话框
*
* @param alterType 对话框类型
* @param title 对话框标题
* @param header 对话框头信息
* @param message 对话框显示内容
* @return boolean 如果点击了”确定“那么就返回true,否则返回false
*/
public static boolean informationDialog(Alert.AlertType alterType, String title, String header, String message) {
// 按钮部分可以使用预设的也可以像这样自己 new 一个
Alert alert = new Alert(alterType, message, new ButtonType("取消", ButtonBar.ButtonData.CANCEL_CLOSE), new ButtonType("确定", ButtonBar.ButtonData.YES));
// 设置对话框的标题
alert.setTitle(title);
alert.setHeaderText(header);
// showAndWait() 将在对话框消失以前不会执行之后的代码
Optional<ButtonType> buttonType = alert.showAndWait();
// 根据点击结果返回
if (buttonType.get().getButtonData().equals(ButtonBar.ButtonData.YES)) {
return true;// 如果点击了“确定”就返回true
} else {
return false;
}
}
/**
* 操作结果:JavaFX判断是否登录成功
*
* @param userNameTextField 用户名文本框
* @param passwordTextField 密码文本框
* @param userName 正确用户名
* @param password 正确密码
* @return boolean 如果登录成功返回true,否则返回false
*/
public boolean isLogIn(TextInputControl userNameTextField, TextInputControl passwordTextField, String userName, String password) {
SimpleTools simpleTools = new SimpleTools();
if (simpleTools.isEmpty(userNameTextField.getText())) {
simpleTools.informationDialog(Alert.AlertType.WARNING, "提示", "警告", "用户名不能为空!");
return false;
}
if (simpleTools.isEmpty(passwordTextField.getText())) {
simpleTools.informationDialog(Alert.AlertType.WARNING, "提示", "警告", "密码不能为空!");
return false;
}
if (!userNameTextField.getText().equals(userName)) {
simpleTools.informationDialog(Alert.AlertType.WARNING, "提示", "警告", "用户名不正确!");
return false;
}
if (!passwordTextField.getText().equals(password)) {
simpleTools.informationDialog(Alert.AlertType.WARNING, "提示", "警告", "密码不正确!");
return false;
}
if (!userNameTextField.getText().equals(userName) && !passwordTextField.getText().equals(password)) {
simpleTools.informationDialog(Alert.AlertType.ERROR, "提示", "错误", "用户名和密码均不正确!");
return false;
}
if (userNameTextField.getText().equals(userName) && passwordTextField.getText().equals(password)) {
boolean isOK = simpleTools.informationDialog(Alert.AlertType.INFORMATION, "提示", "信息", "登录成功!");
return isOK;
}
return false;
}
/**
* 操作结果:向下拉列表框中添加列表项
*
* @param comboBox 下拉列表框
* @param items 列表项
*/
public void addComboBoxItems(ComboBox comboBox, Object[] items) {
comboBox.getItems().clear();// 清除下列列表框中的所有选项
ObservableList options = FXCollections.observableArrayList(items);
comboBox.setItems(options);// 添加下拉列表项
}
/**
* 将查询的用户状态结果,放到表中
* @param userTypeManageView
* @param data
* @param idTableColumn
* @param userTypeNameColumn
* @param userTypeStatus
*/
public void setUserTypeTableViewData(TableView<UserTypeNewBeanData> userTypeManageView,ObservableList data, TableColumn<UserTypeNewBeanData , String> idTableColumn, TableColumn<UserTypeNewBeanData, String> userTypeNameColumn, TableColumn<UserTypeNewBeanData, String> userTypeStatus) {
//设置id列数据
idTableColumn.setCellValueFactory(cellData -> cellData.getValue().idProperty());
//设置name列数据
userTypeNameColumn.setCellValueFactory(cellData -> cellData.getValue().usernameProperty());
//设置status列数据
userTypeStatus.setCellValueFactory(cellData -> cellData.getValue().statusProperty());
userTypeManageView.setItems(data);
}
/**
* 把数据放到表中
* @param messageTypeManageView
* @param data
* @param messageIdColumn
* @param messageNameColumn
* @param messageTitleColumn
* @param messageContentColumn
* @param messageTimeColumn
*/
public void setMessageTableViewData(TableView<Message> messageTypeManageView, ObservableList<Message> data, TableColumn<Message, String> messageIdColumn, TableColumn<Message, String> messageNameColumn, TableColumn<Message, String> messageTitleColumn, TableColumn<Message, String> messageContentColumn, TableColumn<Message, String> messageTimeColumn) {
//id列数据
messageIdColumn.setCellValueFactory(cellData -> cellData.getValue().midProperty());
//name列数据
messageNameColumn.setCellValueFactory(cellData -> cellData.getValue().mnameProperty());
//title列数据
messageTitleColumn.setCellValueFactory(cellData -> cellData.getValue().mtitleProperty());
//content列数据
messageContentColumn.setCellValueFactory(cellData -> cellData.getValue().mcontentProperty());
//time列数据
messageTimeColumn.setCellValueFactory(cellData -> cellData.getValue().mtimeProperty());
messageTypeManageView.setItems(data);
}
/**
* 将数据库拿到data数据封装转换成javafx中 ObservableList<BookTypeBeanTableData>类型的数据
*/
public ObservableList<UserTypeNewBeanData> getUserTypeTableViewData(List<UserTypeNewBeanData> tableDataList) {
// 创建ObservableList<BookTypeBeanTableData>对象
ObservableList<UserTypeNewBeanData> data = FXCollections.observableArrayList();
// 循环遍历集合中的数据
for (int i = 0; i < tableDataList.size(); i++) {
UserTypeNewBeanData r = (UserTypeNewBeanData) tableDataList.get(i);
// 将数据封装到BookTypeBeanTableData中
UserTypeNewBeanData td = new UserTypeNewBeanData(r.getId(), r.getUsername(), r.getStatus());
// 将BookTypeBeanTableData对象添加到data中
data.add(td);
}
// 返回数据
return data;
}
/**
* 将数据库拿到data数据封装转换成javafx中 ObservableList<Message>类型的数据
* @param messageDataList
* @return
*/
public ObservableList<Message> getMessageTableViewData(List<Message> messageDataList) {
// 创建ObservableList<BookTypeBeanTableData>对象
ObservableList<Message> data = FXCollections.observableArrayList();
// 循环遍历集合中的数据
for (int i = 0; i < messageDataList.size(); i++) {
Message r = (Message) messageDataList.get(i);
// 将BookTypeBeanTableData对象添加到data中
data.add(r);
}
// 返回数据
return data;
}
/**
* 这是自己定义一个提示框
* @param alterType
* @param title
* @param header
* @param message
* @return
*/
public boolean informationDalog(Alert.AlertType alterType, String title, String header, String message) {
// 按钮部分可以使用预设的也可以像这样自己 new 一个
Alert alert = new Alert(alterType, message, new ButtonType("取消", ButtonBar.ButtonData.CANCEL_CLOSE), new ButtonType("确定", ButtonBar.ButtonData.YES));
// 设置对话框的标题
alert.setTitle(title);
alert.setHeaderText(header);
// showAndWait() 将在对话框消失以前不会执行之后的代码
Optional<ButtonType> buttonType = alert.showAndWait();
// 根据点击结果返回
if (buttonType.get().getButtonData().equals(ButtonBar.ButtonData.YES)) {
return true;// 如果点击了“确定”就返回true
} else {
return false;
}
}
}
在持久层UserDao添加登录方法
在v2版本中,我大量使用了面向接口的思想。很遗憾的是v1版本为了赶进度,并没有这么去处理。
/**
* 实现登录
* @param username
* @param password
* @return
* @throws SQLException
*/
public LoginUser login(String username, String password) throws SQLException{
MyDataSource dataSource=new MyDataSource();
QueryRunner runner=new QueryRunner(dataSource);
String sql="select * from users where username =? and password =?";
// return runner.query(sql,new BeanHandler<User>(User.class),username,password);
return runner.query(sql,new BeanHandler<LoginUser>(LoginUser.class),username,password);
}
连接数据库用的是dbUtils以及连接池技术,dbUtils这个半自动化工具是不提供类型转换,这里为我后面埋了一个坑。
两个工具类 MyDataSource和JDBCUtil_V3
JDBCUtil_V3代码
package LeaveMessageSystem.tools;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
//提供获取连接和资源释放的方法
public class JDBCUtil_V3 {
private static String driver;
private static String url;
private static String username;
private static String password;
/**
* 静态代码块加载配置文件信息
*/
static {
try {
// 1.通过当前类获取类加载器
ClassLoader classLoader = JDBCUtil_V3 .class.getClassLoader();
// 2.通过类加载器的方法获得一个输入流
InputStream is = classLoader.getResourceAsStream("db.properties");
// 3.创建一个properties对象
Properties props = new Properties();
// 4.加载输入流
props.load(is);
// 5.获取相关参数的值
driver = props.getProperty("driver");
url = props.getProperty("url");
username = props.getProperty("username");
password = props.getProperty("password");
System.out.println(driver+"---"+url+"--"+username+"---"+password);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取连接方法
*
* @return
*/
public static Connection getConnection() {
Connection conn = null;
try {
Class.forName(driver);
conn = DriverManager.getConnection(url, username, password);
System.out.println("连接成功");
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
/**
* 释放资源方法
*
* @param conn
* @param pstmt
* @param rs
*/
public static void release(Connection conn, PreparedStatement pstmt, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
MyDataSource代码
package LeaveMessageSystem.tools;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.logging.Logger;
public class MyDataSource implements DataSource{
// 1.创建一个容器用于存放Connection对象
private static LinkedList<Connection> pool=new LinkedList<Connection>();
// 2.创建5个连接放到容器中
static {
for (int i=0;i<5;i++){
Connection conn= JDBCUtil_V3.getConnection();
pool.add(conn);
}
}
/**
*获取重新连接的方法
*/
@Override
public Connection getConnection() throws SQLException {
Connection conn=null;
// 3.使用前的预处理
if (pool.size() == 0) {
for (int i = 0; i < 5; i++) {
conn = JDBCUtil_V3.getConnection();
pool.add(conn);
// System.out.println("执行了");
}
}
// 4.从池子里面获取一个连接对象
conn = pool.remove();
return conn;
}
/**
*归还对象到连接池
*/
public void backPoll(Connection conn){
pool.add(conn);
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
另外需要db.properties文件去配置连接数据库
代码已经放入GitHub,需要的同学自取。别忘了给一个star!!!