虽然标题写的是大三上,但实际上这个项目是从暑假的八月二十几号就开始做了。
记得那天中午吃饭的时候,Java老师突然给我发了个消息,问我有没有时间出差,他想让我单独负责一个项目
我看到这条消息当场就傻眼了,忙推辞说我没有能力单独负责一个项目。
但Java老师特别看好我,他说我的学习能力很强,做项目做的比很多研究生都好,还说如果我一个人忙不过来,他会安排一个研究生给我打下手。。。。
说真的,我这辈子都没有这么受宠若惊过
不过还好,后来可能是因为老师发现这个项目比他想象得复杂,所以他还是找了之前那个带我的研究生学长来带我,两个人做这个项目。
(这属实让我松了一口气,毕竟要是真让我带研究生,我可不好意思给研究生分配任务。。。。)
经过一个多月的努力,现在这个项目基本是完成了。
虽然从难度上来说,学长负责的数据解析部分(即从串口服务器读取数据并解析)比我负责的部分难。但从代码量来看,我完成了整个项目的百分之70~80
(这个项目为商业项目,所以本博客不会大量地贴出项目中的代码,只用来记录做项目的历程和遇到的困难)
登陆界面
点击“修改登陆页面信息”按钮,弹出如下弹窗,可以修改登陆页面的公司名、协议、图片等信息
使用普通用户账号登录显示如下界面,可实时监控各个厂区的状态
在左侧进行勾选或者点击图上的绿色小圆圈可查看具体信息
再进行勾选还可以查看更详细的内容,这里不再演示
点击左上角链接可进行搜索
选择筛选条件后,可以查询信息,并进行导出(PDF或Exel)
选择帮助,点击“软件操作指南”
自动弹出帮助文档供用户查看
使用管理员账号登录,可进行后台管理
其他模块不再展示,这里展示一下图片管理模块
编辑窗口如下所示
这里管理的就是用户界面显示的那张图片
点击添加点位则会在图片左上角生成一个点位,通过鼠标拖动可以将点位移到指定位置
已有的点位也可以通过鼠标拖动改变位置
如果不想保存修改,则点击重载图片按钮,反之则点击保存按钮
点击“改变点位大小”按钮可以改变点位大小,单位为像素
导入功能与编辑功能差不多,这里不再演示
点击改变点位样式按钮,可以改变点位的样式(比如正常的点位用绿色圆圈,异常点位用红色圆圈)
其实做桌面软件的话最好是用C#,但由于我和学长之前都没学过C#,而且甲方说这个项目以后可能会推出网页版,用Java的话以后改成网页版比较方便,所以我们是选择了Java来做。
确定好语言后,由于我们俩之前也没接触过JavaFX,所以我们找了如下资料来学习JavaFX
JavaFX下载
JavaFX 程序退出时结束子线程
JavaFX 非Parent的Node只能真实地加在最后一个Parent中
javaGUI的替代者JavaFX
JavaFX入门(二):JavaFX和FXML
javaFX开发环境配置
Java: JavaFX桌面GUI开发
JavaFX中文资料
JavaFX教程
JavaFX 15官网
JavaFX教程
用maven创建javafx项目 解决“错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序”
jdk8版本以上的javafx安装操作,通过下载javafx安装包,内附jdk8的安装包
在开始做项目之前,我先写了一个简单的Demo来搞明白JavaFX究竟如何使用。
由于我们俩对做Web项目比较熟悉,所以我们准备使用JavaFX中的Webview内嵌HTML页面来开发
首先需要搞明白的就是如何进行前后端交互
javaFX与js交互
JAVAFX应用程序嵌入本地的html文件(webview)
参考了这两篇博客后我明白了具体流程
要想实现内嵌HTML开发,需要写如下三个文件
Main
public class TestMain extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(final Stage primaryStage) throws IOException {
Pane root = FXMLLoader.load(getClass().getResource("main.fxml"));
primaryStage.setScene(new Scene(root, 500, 400));
primaryStage.show();
}
}
fxml
<AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.BaseController">
<children>
<WebView fx:id="webView" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
children>
AnchorPane>
Controller
public class BaseController implements Initializable {
public static final Window stage = null;
private JSObject win;
@FXML
private WebView webView;
private WebEngine webengine;
@Override
public void initialize(URL location, ResourceBundle resources) {
// TODO Auto-generated method stub
webengine= webView.getEngine();
webengine.getLoadWorker().stateProperty().addListener(
(ObservableValue<? extends Worker.State> ov, State oldState, Worker.State newState) -> {
if (newState == State.SUCCEEDED) {
win = (JSObject) webengine.executeScript("window");
win.setMember("app", new JavaApp(webengine));
}
});
System.out.println("初始化");
String Url = this.getClass().getResource("test.html").toExternalForm();
webengine.load(Url);
}
}
具体过程就是Main函数中利用如下代码加载fxml文件
Pane root = FXMLLoader.load(getClass().getResource("main.fxml"));
然后fxml文件中会指定一个controller文件
fx:controller="application.BaseController
最后在controller文件中把html文件加载到webview中即可
String Url = this.getClass().getResource("test.html").toExternalForm();
webengine.load(Url);
而前后端的交互就是通过以下代码在HTML文件中增加Java对象来实现
win = (JSObject) webengine.executeScript("window");
win.setMember("app", new JavaApp(webengine));
前端如果想要调用后端的函数,只需在JavaScript函数中使用这个Java对象的函数即可。
格式为
app.函数()
参考了如下博客
JavaFX多个界面中的数据传递
我们可以在JavaApp(上面那个用来与前端交互的类)中加一个函数
public void turnto(String url) {
System.out.println(url);
String Url = Main.class.getResource(url).toExternalForm();
webengine.load(Url);
}
需要跳转时,只需在javascript中使用 app.turnto(url)即可,比如
app.turnto("/WEB-INF/views/admin/adminMenu.html");
在搭建项目架构时,我想尽可能地利用上一个项目的经验,所以使用了SM框架(SpringMVC肯定是用不了的)
SM整合(spring,mybatis)
接下来我像写SSM项目时一样,建了entity(实体类)、dao(与数据库进行交互)、service(业务逻辑)包。
接下来就是controller(与前端交互)层了,前面三个包和之前完全没区别,但controller层显然不能和之前一样。
不过了解了JavaFX如何与前端交互后,这一层也不难写,只需多写几个JavaApp类即可。
为了与上面的BaseController区分,我将controller层的包命名为controllerUtils
最后项目的架构如下所示
src里是Java文件,resource里是html、js 、css文件
src架构如下。
entity、dao、service、controllerUtils上面说过了
dto、tools、 utils 就是一些工具类
timer类用来进行实时刷新(后面会详细说)
config、config.mybatis、config.spring就是配置文件
config.mybatis.mapper就是写SQL的mapper映射文件(XML文件)
main和controller包里就是实现内嵌HTML的那三个文件,
只有BaseController和上面不太一样
首先就是spring配置文件需要手动导入
context = new ClassPathXmlApplicationContext("classpath:config/spring/applicationContext.xml");//context为org.springframework.context.ApplicationContext类
然后就是把JavaAPP换成了controllerUtils包里的那些类
win.setMember("app", new LoginUtil(context, win, webView, webengine, user));
win.setMember("normal", new NormalUserUtil(context, win, webView, webengine, user));
win.setMember("tiaoshi", new TiaoshiUtil(context, win, webView, webengine, user));
win.setMember("admin", new AdminUtil(context, win, webView, webengine, user));
之前是用Session记录当前用户,现在只能是使用静态变量了(登录后给静态变量currentUser赋值,退出后将该静态变量置为null)
之前遇到的另一个小问题:有时更改了代码后没有效果,看了下面的博客后解决了该问题
eclipse不能自动编译生成class文件的解决办法
做web项目是使用html里的标签就行,但这个项目使用html标签的话无法与后端交互
这个问题困扰了我很久,甚至让我一度想放弃使用javafx,最后我想到了可以用javafx自带的文件选择器
点击按钮时触发Java的函数
<button type="button" onclick="admin.chooseFile()" name="file">选择文件button>
然后通过以下函数
选择文件:
public void chooseFile() {
FileChooser fileChooser = new FileChooser();
fileChooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("All", "*.*"),
new FileChooser.ExtensionFilter("JPG", "*.jpg"),
new FileChooser.ExtensionFilter("PNG", "*.png")
);
file = fileChooser.showOpenDialog(stage);
System.out.println(file);
}
选择文件夹:
public void chooseFile() {
DirectoryChooser directoryChooser = new DirectoryChooser();
file = directoryChooser.showDialog(stage);
//System.out.println(file);
}
file即一个静态变量
得到选择的文件(静态变量file)后即可进行文件的上传了
最近学习了NIO,为了进行实践,我把文件上传函数中的文件复制部分改成了如下的样子(虽然效率提升不大,但聊胜于无)
(缓存、直接缓存、通道)
FileChannel inchannel=FileChannel.open(Paths.get(mfile.toURI()), StandardOpenOption.READ);
FileChannel outchannel=FileChannel.open(Paths.get(path), StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
inchannel.transferTo(0, inchannel.size(), outchannel);
另一个问题就是最开始的时候,上传文件后需要手动刷新项目才能查看文件,查看了博客后解决了该问题
解决springboot上传文件至当前项目目录下,上传成功后,再次刷新项目才显示上传结果
之前那个项目做过导出Exel,这次是导出PDF,其实也没多大区别:Java iText导出pdf功能实现
导出文件的时候遇到了一个让我挺无语的BUG,就是它会提示“指定的设备名无效”,看了下面的博客后才知道,给文件取名时有些名字是不能取的
win7下创建名为aux.c的文件提示“指定的设备名无效”
比如com3这个名字就不能取
做项目时有一个需求是用户点击按钮,系统直接打开某个文件(不是点击下载),看了如下博客解决了该问题
使用java打开本地文件的方法
实时检测系统,顾名思义就是可以实时地监测,一旦出了问题可以立即监测到
那如何实现呢?
一开始我和学长采用的方法是使用Javascript中的setInterval函数
在html页面实时显示系统时间
JS setInterval()/setTimeout()——实现动态时间,倒计时
setTimeout()、setInterval()不起作用?答案在这里
使用方法很简单:
setInterval(函数名,时间间隔) //时间间隔的单位为毫秒
但后来我们发现,用多了以后界面会非常卡
毕竟每次刷新都得从前端调用controllerUtils中的函数,然后controllerUtils调用service层,service层调用Dao层,Dao层查询数据库,最后再把数据一层层地返回回去,(尤其是涉及到数据解析的部分,还得从串口服务器读取数据,然后再解析,然后把解析出来的数据写入数据库)这么长的流程(而且还涉及到IO流操作)自然会很卡。
于是我和学长对此展开了讨论
学长说可以在后端写一个定时器,实时更新数据库(即把数据解析部分的实时刷新放到后端),然后前端再实时地读取数据库中的内容。
我在此基础上提出了另一个方案:把每一个需要实时刷新的数据存到controllerUtils中对应的一个静态变量中,后端用定时器实时更新这些静态变量,然后前端每次读取这些静态变量即可。
学长同意了这个方案,于是接下来的问题就是如何写定时器了
【简单定时器】JAVA 简单定时器的三种方法
我们选用了其中的第三种方法。
即
ScheduledExecutorService service=Executors.newSingleThreadScheduledExecutor();
service.scheduleAtFixedRate(runnable, 0, 1, TimeUnit.SECONDS);
第一行就是创建并管理一个只有一个线程的线程池
ScheduledExecutorService service=Executors.newSingleThreadScheduledExecutor();
第二行就是定时
service.scheduleAtFixedRate(runnable, 0, 1, TimeUnit.SECONDS);
第一个参数为运行的任务,第二个为延迟的时间,第三个为间隔的时间,第四个为时间单位
每次迭代完一个版本后,我们就需要将项目打包成一个可运行软件发给甲方。
一开始是最笨的方法:让甲方安装JDK和Mysql
后来我们通过查看下面的博客学会了如何打包,如何在没有JDK的电脑上运行。
然后我们把Mysql换成了sqlite(一种轻量级数据库,可以理解为项目中内置一个数据库),解决了这个问题
区别:
第二种写法的前提是函数的参数前加上了@Param(“name”),
如
public List<Baojing> queryAll(@Param("name")String name);
附:
输入参数:parameterType
1.类型为 简单类型(8个基本类型+String)
#{}、$ {}的区别
a.
#{任意值}
$ {value} ,其中的标识符只能是value
b.# {}自动给String类型加上'' (自动类型转换)
$ {} 原样输出,但是适合于 动态排序(动态字段)
select stuno,stuname,stuage from student where stuname = #{value}
select stuno,stuname,stuage from student where stuname = '$ {value}
动态排序:
select stuno,stuname,stuage from student order by $ {value} asc
c.# {}可以防止SQL注入
$ {}不防止
$ {}、#{}相同之处:
a.都可以 获取对象的值 (嵌套类型对象)
i.获取对象值:
模糊查询,方式一:
select stuno,stuname,stuage from student where stuage= #{stuAge} or stuname like #{stuName}
Student student = new Student();
student.setStuAge(24);
student.setStuName("%w%");
List< Student> students = studentMapper.queryStudentBystuageOrstuName(student) ;//接口的方法->SQL
模糊查询,方式二:
student.setStuName("w");
select stuno,stuname,stuage from student where stuage= #{stuAge} or stuname like '%${stuName}%'
ii.嵌套类型对象
2.对象类型
#{属性名}
${属性名}
Java Swing实用小工具开发
javafx项目打包
使用exe4j打包javafx项目
jdk11使用jlink定制精简jre
jdk11订制jre + JavaFX11打包exe可执行程序
eclipse导出可执行的jar文件
jar导出与制作成exe在没jdk电脑下运行(图文教程+工具)
exe4j 将jar包封装为exe
甲方那边是Win7系统,为了测试他们的机器能否运行,我在虚拟机安了一个Win7系统
VMware 15 虚拟机安装 win 7 系统
打包后文件目录如下:
db为数据库文件(.db格式),jre8即运行环境,加上这个后没有jdk的电脑也可以运行。resource即项目涉及到的图片
其实还可以在此基础上再打包成可安装程序,不过我们目前还只是测试阶段,就没有进行再次打包
jsp连接sqlite、Sqlite相对路径绝对路径问题
Spring配置文件打包到jar中无法加载问题之解决方案
使用exe4j把jar转换成exe文件时,报错java.lang.NoClassDefFoundError:
参考博客:
Java将二进制流转Base64字符串并在页面显示(附Base64转二进制流)
解决Eclipse中无法直接使用sun.misc.BASE64Encoder及sun.misc.BASE64Decoder的问题
如何用JAVA将二进制文件转换成BASE64格式保存到MySQL的Blob字段里并读出下载
HTML base64格式的二进制流在 img 标签内显示
显示图片有很多种方法
①直接把img标签中的src写成相对路径即可显示
②先在后端利用反射加载classpath下的图片
this.getClass().getClassLoader().getResource(图片的相对路径)//返回URL
或者用
this.getClass().getResourceAsStream(图片相对路径)//返回InputStream
也可以用System.getProperty("user.dir")
获取项目的真实路径,然后在此基础上获取resource文件夹中的图片(考虑到之后还得上传图片,我采取的是这种方式)
然后把图片流转成base64格式的二进制流
public String getImageByPath(String path) throws Exception {
File file=new File(path);
System.out.println("file:"+file.toString());
FileInputStream fin = new FileInputStream(file);
byte[] buffer = new byte[(int)file.length()];
fin.read(buffer);
String s=new BASE64Encoder().encode(buffer); //将文件内容编码成base64格式后以字符串的方式保存到Blob字段中
fin.close();
return s;
}
最后把该二进制流放到src里即可显示
document.getElementById("image").src="data:image/jpeg|png|gif;base64,"+app.getImageByPath(path);
③在数据库中直接存取图片二进制流,
然后用上面的方式显示。
但这样的话数据库查询效率会降低
打包后不能把图片上传到exe文件中,有以下两种解决方法(我想到的)
①直接把图片上传到数据库中(但数据库查询效率降低)
②打包后把用到的图片全放到与exe文件平行的resource文件夹里
可以这么做的原因是System.getProperty("user.dir")
这段代码比较神奇。
打包之前它获取的路径是项目的真实路径,打包之后它获取的是exe文件所在文件夹的路径
所以System.getProperty("user.dir")+"/resource/"
这段代码在在打包前可以正确获取到项目的resource文件夹中的图片,打包后也可以正确获取到与exe文件平行的resource文件夹中的图片
缺点就是每次打包都需要把项目中的resource文件夹中的图片复制到上面的resource文件夹中。(不影响正常使用)
剩下的就是一些前端的问题,比如之前演示的拖动点位的实现
另外就是不知道为什么,写前端时JQuery有时好使有时不好使,EasyUI和LayUI中的控件也是有的好使有的不好使
我个人猜测这是因为JavaFX8使用的内嵌浏览器是非常落后的,对一些较新的前端技术不怎么支持
怎么让背景图片铺满整个页面
ajax 报错 400
用户在图片上点选并标记位置,js实现
FileReader.readAsDataURL()函数的使用
java流下载,前端ajax blob接收
window.URL.createObjectURL 的使用
解决EasyUi的combobox绑定change事件
layui+树结构表格+动态单元格加背景色
使用layer,layui,不能显示弹出效果
JavaScript数组方法
如何让两个div并排同行显示
table 每行 改变颜色
使用css实现一个圆形头像框效果
JS中String转int
实现div可拖放
关于js函数传参的问题
a标签href属性传递参数,onclick属性传递参数
form中的button问题
getElementsByClassName()的详细用法
Javascript改变css样式的四种方法
js点击οnclick=“函数”参数传入URL问题
a标签携带参数跳转并在跳转页面接收参数
Html代码中,< img src="">如何写图片路径
js刷新当前页面的5种方式
JS之String类型
js子级窗口相互调用父级的方法
向iframe传递数值简单方法(父页面向子页面传递数值)
iframe的跳转:直接改变iframe标签的src,如果需要传参,利用标签的参数传参
JSObject 的相关用法.
js获取单选按钮选项
关于z-index的详细解释
background-image 关于把一张图片完全显示在大div中,小div中
div标签常用属性
js获取图片宽高的方法