-
面向对象三大特性:继承,封装,多态封装3中修饰符:public,private,protected,给位于同一个或不同包中的对象赋予了不同的访问权限封装的一些好处通过隐藏对象的属性来保护对象内部的状态提高代码的可用性,可维护性提高模块化继承给对象提供从基类获取字段和方法的能力,基础提高代码的重用性,可以在不修改类的情况下添加新的特性多态多态就是同一函数在不同类中有不同的实现;面向对象的多态性,即“一个接口,多个方法”。多态性体现在基类中定义的属性和方法被子类继承后,可以具有不同的属性或表现方式。多态性允许一个接口被多个同类使用,弥补了单继承的不足。final,finally,finalize的区别finalfinal可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个 常量不能被重新赋值。finallyfinally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码放入finally代码块中,表示不管是 否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。finalizefinalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调 用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断。int和integer的区别intint是基本数据类型integerinteger是其包装类,是一个类为了在各种类型中转换,通过各种方法调用int a = 0; String result = Integer.toString(a); //将int转换为String重载与重写的区别override重写overload重载抽象类和接口的区别接口时公开的,不能有私有的方法和变量,抽象类可以有私有的方法或私有的变量抽象类和接口的区别反射的用途以及实现反射机制所提供的功能在运行时创造一个类的对象;判断一个类所具有的成员变量和方法调用一个对象的方法生成动态代理Java反射的主要功能:确定一个对象的类取出类的modifiers,数据成员,方法,构造类,超类在运行时刻调用动态对象的方法.创建数组,数组大小和类型反射机制的理解和应用自定义注解的场景及实现登陆、权限拦截、日志处理,以及各种Java 框架,如Spring,Hibernate,JUnit 提到注解就不能不说反射,Java自定义注解是通过运行时靠反射获取注解。实际开发中,例如我们要获取某个方法的调用日志,可以通过AOP(动态代理机制)给方法添加切面,通过反射来获取方法包含的注解,如果包含日志 注解,就进行日志记录。http请求的get和post方式的区别原理区别一般我们在浏览器输入一个网址访问网站都是GET请求;再FORM表单中,可以通过设置Method指定提交方式为GET或者POST提交方式,默认为GET提交方式。HTTP定义了与服务器交互的不同方法,其中最基本的四种:GET,POST,PUT,DELETE,HEAD,其中GET和HEAD被称为安全方法,因为使用GET和HEAD的HTTP请求不会产生什么动作。不会产生动作意味着GET和HEAD的HTTP请求不会在服务器上产生任何结果。但是安全方法并不是什么动作都不产生,这里的安全方法仅仅指不会修改信息。根据HTTP规范,POST可能会修改服务器上的资源的请求。比如CSDN的博客,用户提交一篇文章或者一个读者提交评论是通过POST请求来实现的,因为再提交文章或者评论提交后资源(即某个页面)不同了,或者说资源被修改了,这些便是“不安全方法”。请求的区别GET方法会把名值对追加在请求的URL后面。因为URL对字符数目有限制,进而限制了用在 客户端请求的参数值的数目。并且请求中的参数值是可见的,因此,敏感信息不能用这种方式传递。POST方法通过把请求参数值放在请求体中来克服GET方法的限制,因此,可以发送的参数的数目是没有限制的。最后,通过POST请求传递的敏感信息对外部客户端是不可见的。参考:get和post请求方式的区别seesion与cookie的区别cookie数据存放在客户的浏览器上,session数据放在服务器上.cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗考虑到安全应当使用session。设置cookie时间可以使cookie过期。但是使用session-destory(),我们将会销毁会话。session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用cookie。单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。(Session对象没有对存储的数据量的限制,其中可以保存更为复杂的数据类型)JDBC流程加载JDBC驱动程序:在连接数据库之前,首先要加载想要连接的数据库的驱动到JVM(Java虚拟机), 这通过java.lang.Class类的静态方法forName(String className)实现。例如:try{ //加载MySql的驱动类 Class.forName("com.mysql.jdbc.Driver") ; }catch(ClassNotFoundException e){ System.out.println("找不到驱动程序类 ,加载驱动失败!"); e.printStackTrace() ; 成功加载后,会将Driver类的实例注册到DriverManager类中。提供JDBC连接的URL连接URL定义了连接数据库时的协议、子协议、数据源标识。书写形式:协议:子协议:数据源标识协议:在JDBC中总是以jdbc开始 子协议:是桥连接的驱动程序或是数据库管理系统名称。数据源标识:标记找到数据库来源的地址与连接端口。例如:jdbc:mysql://localhost:3306/test? useUnicode=true&characterEncoding=gbk;useUnicode=true;(MySql的连接URL)表示使用Unicode字符集。如果characterEncoding设置为 gb2312或GBK,本参数必须设置为true 。characterEncoding=gbk:字符编码方式。创建数据库的连接java.sql.DriverManager Connection代表一个数据库的连接。使用DriverManager的getConnectin(String url , String username , String password )方法传入指定的欲连接的数据库的路径、数据库的用户名和 密码来获得。例如: //连接MySql数据库,用户名和密码都是rootString url = "jdbc:mysql://localhost:3306/test" ; String username = "root" ; String password = "root" ; try{ Connection con = DriverManager.getConnection(url , username , password ) ; }catch(SQLException se){ System.out.println("数据库连接失败!"); se.printStackTrace() ; • 要执行SQL语句,必须获得java.sql.Statement实例,Statement实例分为以下3 种类型:1、执行静态SQL语句。通常通过Statement实例实现。2、执行动态SQL语句。通常通过PreparedStatement实例实现。3、执行数据库存储过程。通常通过CallableStatement实例实现。具体的实现方式:Statement stmt = con.createStatement() ; PreparedStatement pstmt = con.prepareStatement(sql) ; CallableStatement cstmt = con.prepareCall("{CALL demoSp(? , ?)}") ;执行SQL语句Statement接口提供了三种执行SQL语句的方法:executeQuery 、executeUpdate 和execute1、ResultSet executeQuery(String sqlString):执行查询数据库的SQL语句 ,返回一个结果集(ResultSet)对象。2、int executeUpdate(String sqlString):用于执行INSERT、UPDATE或 DELETE语句以及SQL DDL语句,如:CREATE TABLE和DROP TABLE等3、execute(sqlString):用于执行返回多个结果集、多个更新计数或二者组合的 语句。 具体实现的代码:ResultSet rs = stmt.executeQuery(“SELECT * FROM …”) ; int rows =stmt.executeUpdate(“INSERT INTO …”) ; boolean flag = stmt.execute(String sql) ;处理结果两种情况:1、执行更新返回的是本次操作影响到的记录数。2、执行查询返回的结果是一个ResultSet对象。• ResultSet包含符合SQL语句中条件的所有行,并且它通过一套get方法提供了对这些 行中数据的访问。• 使用结果集(ResultSet)对象的访问方法获取数据:while(rs.next()){String name = rs.getString(“name”) ;String pass = rs.getString(1) ; // 此方法比较高效}(列是从左到右编号的,并且从列1开始)关闭JDBC对象操作完成以后要把所有使用的JDBC对象全都关闭,以释放JDBC资源,关闭顺序和声 明顺序相反:1、关闭记录集2、关闭声明3、关闭连接对象 if(rs!=null){ // 关闭记录集 try{ rs.close(); }catch(SQLException e){e.printStackTrace(); } }try{ stmt.close(); } }catch(SQLException e){e.printStackTrace(); } if(conn!=null){ // 关闭连接对象 try{ conn.close(); }catch(SQLException e){ e.printStackTrace();20} }MVC思想M:Model 模型V:View 视图C:Controller控制器模型就是封装业务逻辑和数据的一个一个的模块,控制器就是调用这些模块的(java中通常是 用Servlet来实现,框架的话很多是用Struts2来实现这一层),视图就主要是你看到的,比如JSP 等.当用户发出请求的时候,控制器根据请求来选择要处理的业务逻辑和要选择的数据,再返回去 把结果输出到视图层,这里可能是进行重定向或转发等.equals 与 == 的区别值类型(int,char,long,boolean等)都是用==判断相等性。对象引用的话,判断引用所指的对象 是否是同一个。equals是Object的成员函数,有些类会覆盖(override)这个方法,用于判 断对象的等价性。例如String类,两个引用所指向的String都是"abc",但可能出现他们实际对应的对象并不是同一个(和jvm实现方式有关),因此用 ==判断他们可能不相等,但用equals判断一定是相等的。线程创建线程的方法及实现Java中创建线程主要有三种方式:一、继承Thread类创建线程类(1) 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。(2) 创建Thread子类的实例,即创建了线程对象。(3) 调用线程对象的start()方法来启动该线程。package com.thread; public class FirstThreadTest extends Thread{ int i = 0; //重写run方法,run方法的方法体就是现场执行体public void run() { for(;i<100;i++){ System.out.println(getName()+" "+i); } } public static void main(String[] args) { for(int i = 0;i< 100;i++) { System.out.println(Thread.currentThread().getName()+" "+i): if(i==20) { new FirstThreadTest().start(); new FirstThreadTest().start(); } } }二、通过Runnable接口创建线程类(1) 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。(2) 创建 Runnable实现类的实例的target来创建Thread对象,该Thread对象才是真正的线程对象。(3)调用线程对象的start()方法来启动该线程package com.thread; public class RunnableThreadTest implements Runnable { private int i; public void run() { for(i = 0;i <100;i++) { System.out.println(Thread.currentThread().getName()+" "+i); } } public static void main(String[] args) { for(int i = 0;i < 100;i++) { System.out.println(Thread.currentThread().getName()+" "+i); if(i==20) { RunnableThreadTest rtt = new RunnableThreadTest(); new Thread(rtt,"新线程1").start(); new Thread(rtt,"新线程2").start(); } } } } 三、通过Callable和Future创建线程(1) 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。(2) 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。(3) 使用FutureTask对象作为Thread对象的target创建并启动新线程。(4) 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值package com.thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); 13 for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " 的循 环变量i的值" + i); if (i == 20) { new Thread(ft, "有返回值的线程").start(); } } try { System.out.println("子线程的返回值:" + ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } @Override public Integer call() throws Exception 34 { int i = 0; for (; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } return i; } }采用实现Runnable、Callable接口的方式创见多线程时,优势是:线程类只是实现了 接口或 接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同 一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面 向对象的思想。劣势是:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。使用继承Thread类的方式创建多线程时优势是:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this 即可获得当前线程。劣势是:线程类已经继承了Thread类,所以不能再继承其他父类。sleep() 、join()、yield()的区别一、sleep()在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度 程序精度和准确性的影响。 让其他线程有机会继续执行,但它并不释放对象锁。也就是如 果有Synchronized同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常比如有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另一 个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完成后,低优先级 的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的 线程有执行的机会。二、join()Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前, B不能工作。保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有 存活,则当前线程不需要停止。三、yield()yield()方法和sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方 法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态 后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也 和sleep()方法不同。再给大家推荐一本书:Java并发编程想要电子版也可以私信我线程池的几种方式newFixedThreadPool(int nThreads)创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数 量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程newCachedThreadPool()创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当 需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制newSingleThreadExecutor()这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会 创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行newScheduledThreadPool(int corePoolSize)创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。private static final Executor exec=Executors.newFixedThreadPool(50); Runnable runnable=new Runnable(){ public void run(){ ... } } exec.execute(runnable); Callable<Object> callable=new Callable<Object>() { public Object call() throws Exception { return null; } } Future future=executorService.submit(callable); future.get(); // 等待计算完成后,获取结果 future.isDone(); // 如果任务已完成,则返回 true future.isCancelled(); // 如果在任务正常完成前将其取消,则返回 true future.cancel(true); // 试图取消对此任务的执行,true中断运行的任务,false 允许正在运行的任务运行完成线程的生命周期新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态(1)生命周期的五种状态新建(new Thread)当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。例如:Thread t1=new Thread();就绪(runnable)线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队 等候得到CPU资源。例如:t1.start();运行(running)线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有 优先级更高的线程进入,线程将一直运行到结束。死亡(dead)当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态 等待执行。自然终止:正常运行run()方法后终止异常终止:调用**stop()**方法让一个线程终止运行堵塞(blocked)由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。正在等待:调用wait()方法。(调用motify()方法回到就绪状态)被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)锁机制线程安全线程安全是指要控制多个线程对某个资源的有序访问或修改,而在这些线程之间没有产生冲 突。在Java里,线程安全一般体现在两个方面:1、多个thread对同一个java实例的访问(read和modify)不会相互干扰,它主要体现在关 键字synchronized。如ArrayList和Vector,HashMap和Hashtable(后者每个方法前都有synchronized关键字)。如果你在interator一个List对象时,其它线程remove一个element, 问题就出现了。2、每个线程都有自己的字段,而不会在多个线程之间共享。它主要体现在java.lang.ThreadLocal类,而没有Java关键字支持,如像static、transient那样。volatle实现原理深入分析 Volatile 的实现原理悲观锁,乐观锁是一种思想。可以用在很多方面。比如数据库方面。悲观锁就是for update(锁定查询的行)乐观锁就是 version字段(比较跟上一次的版本号,如果一样则更新,如果失败则要重复读比较写的操作。)JDK方面:悲观锁就是sync乐观锁就是原子类(内部使用CAS实现)本质来说,就是悲观锁认为总会有人抢我的。乐观锁就认为,基本没人抢。乐观锁是一种思想,即认为读多写少,遇到并发写的可能性比较低,所以采取在写时先读出 当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重 复读比较写的操作。CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。CAS顶多算是乐观锁写那一步操作的一种实现方式罢了,不用CAS自己加锁也是可以的。乐观锁的业务场景及实现方式每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁, 但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不 进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁, 期间该数据可以被其他线程进行读写操作。乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可 能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量 的查询操作,降低了系统的吞吐量。最后都看到这了,给孩子一个三连支持一下吧,Java对初学者很友好;Java资源丰富,因为它可以解决不同的问题;Java有一个庞大而友好的社区;Java无处不在,因此更容易找到第一份工作;Java开发人员缺口大,薪水很高。只要你具备能力,薪资待遇你敢要公司就敢给。链接:https://bbs.huaweicloud.com/blogs/336625
-
使用Java SDK上传一个对象,如 ```java ObsClient obsClient = xxx; String bucket = "xxx", key = "yyy"; InputStream inputStream = new FileInputStream("xxx.jpg"); PutObjectResult result = obsClient.putObject(bucketName, key, inputStream); ``` 其中result中可以获得该对象的url,通过浏览器访问url,并不会直接显示,而是下载 看到有一个帖子,想像该地址一样,可以在浏览器直接展示图片 [https://hot-image-1.obs.cn-east-3.myhuaweicloud.com/huawei/full/0fa336004bff33b6443b79f3feebf751734992f6.jpg](https://hot-image-1.obs.cn-east-3.myhuaweicloud.com/huawei/full/0fa336004bff33b6443b79f3feebf751734992f6.jpg) 对比后发现我这里的响应多了一个`Content-Disposition: attachement`,这个就是下载,我想把这个响应头属性去掉,但是不知道如何弄  也尝试在上传对象的时候或上传后重新设置元属性,比如以下方式 ```java ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentType("image/jpeg"); // 设置ContentType为jpeg metadata.setContentDisposition("inline"); // 设置ContentDispositon不为attachement,防止下载 PutObjectResult result = obsClient.putObject(bucketName, key, inputStream); ``` 后台看确实设置成功了  但是通过链接访问还是下载,请问如何让该图片直接在浏览器中直接展示呢 ---- ---- 下午补充:貌似现在不支持在线预览了?[华为云对象存储服务OBS禁止通过OBS的默认域名(桶访问域名或静态网站访问域名)在线预览桶内对象](https://support.huaweicloud.com/obs_faq/obs_03_0087.html#obs_03_0087__section178821448171714)。但是为什么上边那个可以预览呢,是因为他是规定颁布之前上传的吗
-
# 基于KML_SPBLAS的JAVA语言适配 ### 项目介绍 本项目的主要任务是为鲲鹏处理器的高性能稀疏线性代数库函数提供Java平台适配,包括向量与矩阵,矩阵与矩阵等共32个函数。基本原理是利用Java语言的JNI调用C语言的高性能库函数接口。 下面详细介绍我们团队在此次项目中的经验总结。 ### JNI简述 **JNI**是Java Native Interface的缩写,为Java程序提供与本地程序交互的能力。使用JNI技术,能够使得Java程序充分利用本地代码的优势,如高性能,不必重复造轮子等,在生产中,有着诸多实用价值。  ### JNI工作流程 JNI的典型使用场景是:Java程序调用C,C++代码编译而成的动态库文件。动态库文件在Windows下是.dll文件,在Linux下为.so文件。其主要工作流程如图所示:  1. 编写.java源代码,其中用native关键字标明需要本地实现的函数。 2. 使用命令 `javac -h 输出位置> 源代码路径>` 编译出.h头文件。 3. 编写.cpp源代码实现.h文件中声明的native函数。 4. 编译.h头文件和.cpp源文件生成.dll动态库,并移动到.java源代码中指定的位置。 5. 使用命令 `javac 源代码路径>` 将.java源代码编译成.class字节码。 6. 使用命令 `java 类名>` 执行类名.class文件,调用.dll库文件得到输出。 ### Hello, world! 这里实现一个简单的Windows版本的demo,带读者来熟悉上述流程。 1. 编写Hello.java源代码 ```java public class Hello { // native 关键字声明native函数 native void sayHelloToC(); // 加载 java2c.dll库文件 static { System.loadLibrary("java2c");} public static void main(String []args){ Hello hello = new Hello(); hello.sayHelloToC(); } } ``` 2. 在Hello.java所在目录,打开cmd命令行,编译出Hello.h头文件。(也一并编译出了Hello.class文件) ```sh javac -encoding utf-8 -h . Hello.java ``` utf-8防止中文注释乱码,. 表示输出到当前路径下。 3. 为了简化较长的.dll编译命令,此处我们借助 Dev-C++ 开发工具来完成C++代码的编写和编译。 1. 打开Dev-C++,新建dll项目,将自动生成dll.h和dllmain.cpp两个文件。 2. 将dll.h的内容替换为Hello.h。 ```c++ /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class Hello */ #ifndef _Included_Hello #define _Included_Hello #ifdef __cplusplus extern "C" { #endif /* * Class: Hello * Method: sayHelloToC * Signature: ()V */ JNIEXPORT void JNICALL Java_Hello_sayHelloToC (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif ``` 3. 清空dllmain.cpp中的内容,修改为如下 ```c++ #include"dll.h" #include // 实现dll.h中声明的函数 JNIEXPORT void JNICALL Java_Hello_sayHelloToC (JNIEnv * env, jobject obj){ printf("Hello, C! I am Java."); } ``` 4. 编译生成.dll库文件。 dll.h的第一行,引入了jni.h头文件。在java安装目录下的include文件夹,找到**jni.h**文件和**jni_md.h**。将它们复制到dev-c++项目的 工具->编译选项->目录->C++包含文件 中的任一目录下。  点击编译,生成hello_c++.dll文件(hello_c++为项目名),将其重命名为java2c.dll(与1中的java源代码保持一致),移动到Hello.java所在的文件夹。 5. 编译Hello.java生成Hello.class字节码,其实在第2步已经顺带完成。 ```sh javac -encoding utf-8 Hello.java ``` 6. 在Hello.class目录下,打开cmd命令行,执行字节码程序。 ```sh java Hello ```  以上实现一个简单的demo,完成Java程序调用C++编写的动态库的目标。通过JNI技术还可以实现Java程序和C/C++程序间的数据传递,下面将一一陈述。 ### 基本数据 | Java类型 | JNI类型 | 描述 | | :------- | :------- | :--------------- | | boolean | jboolean | unsigned 8 bits | | byte | jbyte | signed 8 bits | | char | jchar | unsigned 16 bits | | short | jshort | signed 16 bits | | int | jint | signed 32 bits | | long | jlong | signed 64 bits | | float | jfloat | 32 bits | | double | jdouble | 64 bits | | void | void | void | JNI的基本数据类型与Java类型的对照表如上,下面演示传递基本类型的数据。 Hello.java中添加: ```java native double average(int x, int y); ``` Hello.h中新增: ```c++ /* * Class: Hello * Method: average * Signature: (II)D */ JNIEXPORT jdouble JNICALL Java_Hello_average (JNIEnv *, jobject, jint, jint); ``` java编译程序自动添加了三行注释,Class表示类名,Method表示方法名,Signature是java的函数签名,告诉我们函数的参数类型和返回类型。参数是两个Interger,返回是Double。 函数体代码中,函数返回值为jdouble与java中的double对映,函数名为程序自动生成的**Java _类名 _方法名**形式,JNIEnv参数为jni环境,jobject为native函数所在的java对象,这两个是jni自带的。后面两个jint参数对应原本java方法的int参数。 在dllmain.cpp中实现average的逻辑: ```c++ JNIEXPORT jdouble JNICALL Java_Hello_average (JNIEnv * env, jobject obj, jint x, jint y){ return jdouble(x + y) / 2; } ``` 执行输出: ```java System.out.println(hello.average(1, 2)); // 1.5 ``` ### 字符串 JNI的字符串处理函数丰富全面,此处演示传递字符串的简单例子,并以此引出JNI处理复杂数据类型时的一般流程。更多API文件见参考链接的官方文档。 Hello.java中添加: ```java native String sendMessage(String msg); ``` Hello.h中生成: ```c++ /* * Class: Hello * Method: sendMessage * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_Hello_sendMessage (JNIEnv *, jobject, jstring); ``` dllmain.cpp中实现: ```c++ JNIEXPORT jstring JNICALL Java_Hello_sendMessage (JNIEnv * env, jobject obj, jstring msg){ // 1.convert jstring to cstring const char* cMsg = env->GetStringUTFChars(msg, NULL); if(NULL == cMsg) return NULL; // 2.use cstring printf("Java: %s\n", cMsg); // 3.release resources env->ReleaseStringUTFChars(msg, cMsg); // 4.return const char* cMsg2 = "Good afternoon! Miss.Java."; return env->NewStringUTF(cMsg2); } ``` 注意:JNI中需要手动显式地释放资源,否则会造成内存泄漏。 执行输出: ```java String msg = "Good morning! Mr.C++."; System.out.println("C++: " + hello.sendMessage(msg)); // Java: Good morning! Mr.C++. // C++: Good afternoon! Miss.Java. ``` ### 对象 JNI同样可以传递对象,也就是说,JNI让C/C++代码能够访问Java类中的成员变量和方法。 从这里开始,我们使用Maven来管理我们的代码。 新建maven项目,其目录结构如下:  Hello.java的代码如下 ```java package org.example; public class Hello { static { System.loadLibrary("java2c"); } private int num = 2021; private void printNum(){ System.out.println("In Java, num is " + num); } // 演示访问成员变量和方法 native void jniMethod(); public static void main(String []args){ Hello hello = new Hello(); hello.jniMethod(); } } ``` 此处引入了java包,且Hello中导入了Color类,所以如果直接在Hello.java同级文件夹下调用**javac**命令,会报找不到符号的错误,应在所有源文件的根目录,即maven项目的java文件夹下,调用**javac**命令。这样编译器能将包名和路径对应起来。 进入src\main\java路径,运行: ```sh javac -encoding utf-8 -h . ./org/example/Hello.java ``` 如果源文件还引入了其他位置的包,如test文件夹下的源文件引入了main文件夹下的包,可使用`javac -cp 依赖路径>`告知java编译器依赖包的位置。更多javac选项,可使用`javac -help`自行查看。 Hello.h中生成: ```c++ /* * Class: org_example_Hello * Method: jniMethod * Signature: ()V */ JNIEXPORT void JNICALL Java_org_example_Hello_jniMethod (JNIEnv *, jobject); ``` dllmain.h中实现定义的函数: ```c++ JNIEXPORT void JNICALL Java_org_example_Hello_jniMethod (JNIEnv* env, jobject obj){ jclass cls = env->GetObjectClass(obj); // fieldID is certain for a class jfieldID numField = env->GetFieldID(cls, "num", "I"); // get number jint num = env->GetIntField(obj, numField); printf("In C++, num is %d\n", num); // change number env->SetIntField(obj, numField, 2035); // call method jmethodID printNumField = env->GetMethodID(cls, "printNum", "()V"); env->CallVoidMethod(obj, printNumField); return; } ``` 执行编译,在maven项目中编译执行的路径不再是源文件所在的路径。为简单起见,我们将生成的dll文件放到系统环境变量中,以便java虚拟机能够找到。执行输出得到: ```c++ public static void main(String []args){ Hello hello = new Hello(); hello.jniMethod(); } // In Java, num is 2035 // In C++, num is 2021 ``` 通过JNI,获取对象的成员变量或调用对象的主要过程如下: 1. `GetObjectClass`通过jobject获得jclass,或者`FindClass`通过类名直接获得jclass 2. 通过jclass获得jfieldID获得jmethodID。 3. 使用jfieldID获取成员变量的值或使用jmethodID调用对象的方法。 dllmain.cpp中的"I"、"()V"是java的签名字符串,可以在终端通过`javap`命令获取。 ### 数组 jni也可以传递基本类型的数组和对象数组,此处通过向量加法来演示此功能。 Hello.java中新增: ```java native void vectorAdd(int[] a, int[] b, int[] c); public static void main(String []args){ Hello hello = new Hello(); int []a = {1, 2, 3}; int []b = {4, 5, 6}; int []c = new int[3]; hello.vectorAdd(a, b, c); for(int i:c){ System.out.print(i + " "); } ``` `javac -h`重新编译后,Hello.h中生成: ```c++ /* * Class: org_example_Hello * Method: vectorAdd * Signature: ([I[I[I)V */ JNIEXPORT void JNICALL Java_org_example_Hello_vectorAdd (JNIEnv *, jobject, jintArray, jintArray, jintArray); ``` 在dllmain.h中实现上述函数: ```c++ JNIEXPORT void JNICALL Java_org_example_Hello_vectorAdd (JNIEnv *env, jobject obj, jintArray a, jintArray b, jintArray c){ // 1.convert JNI jintArray to C jint[] jint *cA = env->GetIntArrayElements(a, NULL); jint *cB = env->GetIntArrayElements(b, NULL); jint *cC = env->GetIntArrayElements(c, NULL); jint len = env->GetArrayLength(a); // 2.use array for(jint i=0; i+ cB[i]; } // 3.release resources env->ReleaseIntArrayElements(a, cA, JNI_ABORT); env->ReleaseIntArrayElements(b, cB, JNI_ABORT); env->ReleaseIntArrayElements(c, cC, 0); return; } ``` 函数依旧分为:转化数据、使用数据、释放资源三步。 Release函数第三个参数为mode,有三个备选值如下: | mode | 行为 | | :--------- | :-------------------------------------------------------- | | 0 | copy back the content and free the elems buffer | | JNI_COMMIT | copy back the content but do not free the elems buffer | | JNI_ABORT | free the buffer without copying back the possible changes | 编译后执行得输出如下: ``` 5 7 9 ``` 对象数组与基本类型数组的方法相似,但是没有**Get``ArrayElements**对数组元素进行批量转化,究其原因,Java对象不能直接转化为C/C++的对象。 使用如下两个函数,可以操作基本数据类型数组的直接指针,极大的加快程序运行的效率。但同时,我们要保证在调用ReleasePrimitiveArrayCritical函数之前,不能进行任何可能导致线程阻塞的操作。 void * **GetPrimitiveArrayCritical**(JNIEnv *env, jarray array, jboolean *isCopy); void **ReleasePrimitiveArrayCritical**(JNIEnv *env, jarray array, void *carray, jint mode); ### 工程经验 #### c语言宏定义 观察`javac -h`生成的函数声明,我们会发现都是如下结构: ``` Java_包名_方法名 JNIEXPORT void JNICALL Java_org_example_Hello_jniMethod(...) JNIEXPORT void JNICALL Java_org_example_Hello_vectorAdd(...) ``` 当程序越来越复杂,我们调整Java的包结构,每一个地方都要修改。可以使用C++的宏替换来减少这种冗余如下: ```c++ #define FULL_FUNC_NAME(SHORT_NAME) Java_org_example_Hello_##SHORT_NAME JNIEXPORT void JNICALL FULL_FUNC_NAME(jniMethod)(...) JNIEXPORT void JNICALL FULL_FUNC_NAME(vectorAdd)(...) ``` #### 将dll动态库打入jar包 首先我们要明白Java加载动态库的方式: 1. **System.load** System.load加载绝对路径下的库文件,如: ```java System.load("D:\\workplace\\java2c.dll"); ``` 2. **System.loadLibrary** System.loadLibrary加载相对路径下的库文件,参数为库文件名,不包含库文件的扩展名,如: ```java System.loadLibrary("java2c"); ``` 这里java2c.dll必须在**java.library.path**这一jvm变量指向的路径中。 可以通过如下方法来获得该变量的值: ```java System.getProperty("java.library.path"); ``` 默认情况下,在Windows平台下,该值包含如下位置: 1)和jre相关的一些目录 2)程序当前目录 3)Windows目录 4)系统目录(system32) 5)系统环境变量path指定目录 上述两个函数都是以路径作为参数的,但是jar包中的文件没有路径,只能以文件流的形式获取。为了能将动态库文件打入jar包,并且能够顺利调用,我们的策略是将jar包中的动态库写入系统临时目录下,再调用System.load载入,参考代码如下: ```java static { // copy .so from jar to syetem tmp dir String libName = "java2c.dll"; String nativeTempDir = System.getProperty("java.io.tmpdir"); File extractedLibFile = new File(nativeTempDir + File.separator + libName); InputStream in = null; BufferedInputStream reader = null; FileOutputStream writer = null; if(!extractedLibFile.exists()){ try{ in = Level2.class.getClassLoader().getResourceAsStream(libName); reader = new BufferedInputStream(in); writer = new FileOutputStream(extractedLibFile); byte[] buffer = new byte[1024]; while(reader.read(buffer) > 0){ writer.write(buffer); } }catch(IOException e){ e.printStackTrace(); }finally { if (in != null){ try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if (writer != null){ try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } } } System.load(extractedLibFile.toString()); } ``` ### 参考链接 [JNIdemo图文](https://blog.csdn.net/WeiHao0240/article/details/99568579) [JNI完全指南文档](https://www.zybuluo.com/cxm-2016/note/563686) [JNI 官方文档](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html)
-
很荣幸参加《鲲鹏Java性能分析工具IntelliJ插件实现》项目开发工作的,我们项目主要是做Inellij插件开发实现性能分析工具功能。由于插件开发的特殊性,常常需要和一些命令行打交道,下面记录一次通过Java代码打开Windows证书安装窗口安装证书cer文件。思路如下:第一步:下载证书,保存到本地 通过cer文件下载接口将文件保存到本地,记录文件的保存位置第二步:通过代码在IDEA的Terminal命令窗口输入命令打开证书安装窗口 // 将项目对象,ToolWindow的id传入,获取控件对象 ToolWindow toolWindow = ToolWindowManager.getInstance(e.getProject()).getToolWindow("Terminal"); if (toolWindow != null) { // 无论当前状态为关闭/打开,进行强制打开ToolWindow toolWindow.show(new Runnable() { @Override public void run() { Process p = Runtime.getRuntime().exec("rundll32.exe cryptext.dll,CryptExtAddCER "+ path); } }); } }第三步:提醒用户重启IDEA,使证书生效 Messages.showMessageDialog("安装证书", "安装证书后请重启IDEA,证书才能生效", Messages.getInformationIcon());
-
IDEA是目前Java开发人员最受欢迎的工具开发之一,很重要的原因是其提供了很多插件,这让开发人员降低了开发难度,能更有效更高质量的开发代码,我也很荣幸成为IDEA插件开发的一员。 我所在的是鲲鹏Java性能分析工具IntelliJ这个开源项目,刚开始参与进来的时候感觉这个项目很难,因为对Java Swing这块的技术了解的很少。长时间下来感觉挺有意思的,以下我是我对Swing这块的总结: Swing图形组件是java中一套图形界面类,主要分为两类:容器类和元素类。容器类表示可以放置其他的元素组件或者容器组件,JFrame 窗体组件,JPanel 面板容器,JMeauBar 菜单栏;元素类表示不可以放其他的组件,比如:JButton 按钮组件,JLable 文本标签组件,JTextField 文本输入框组件,JPasswordField 密码输入框组件,JCheckBox 复选框组件等。主要就是在合理布局下各种组件之间的搭配,其中还有很多重要的事件,如:ActionEvents,ChangeEvents,ItemEvents等,来响应用户的鼠标点击等行为。 每当感觉自己已经掌握了Swing这门技术的时候,但是在开发过程中处处有惊喜,总会碰到这样或那样的难题,因此还是得通过不断的学习来提高自己,这样才能更好、更有效更轻松的为这个团队服务。加油!
-
链接:https://bbs.huaweicloud.com/blogs/3359231 偏向锁的意义无多线程竞争时,减少不必要的轻量级锁执行路径。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一条线程去多次获得锁,为了让线程获得锁的性能代价更低而引入了偏向锁。偏向锁主要用来优化同一线程多次申请同一个锁的竞争,即当对象被当做同步锁并有一个线程抢到了锁时,则在Mark Word设置该线程的线程ID、是否偏向锁设置1、锁标志位设置01等信息,此时的Mark Word 存储的就是偏向锁状态信息。在:创建一个线程并在线程中执行循环监听的场景下或单线程操作一个线程安全集合时同一线程每次都需获取和释放锁,每次操作都会发生用户态与内核态的切换。获取偏向锁的场景:在自己的线程栈生成一条Lock Record,然后Object Reference指向对象头,此时Lock Record与对象头就建立了联系:① : 先判断Mard Word的Thread ID是否有值没有,则表示当前资源没有被其他线程占用,把当前线程ID等信息记录到Mark Word(这需CAS,可能多条线程修改Mark Word,需要保证原子性)有,则表示当前资源被线程占用,需要判断该线程是不是自己该线程ID是自己的,则表示可重入,直接获取(此时在自己的线程栈中继续生成一条新的Lock Record)该线程ID不是自己的,说明出现其他线程竞争,当前持有偏向锁的线程就需要撤销了,即当其他线程尝试获取偏向锁才释放锁轻量级锁的获取及释放依赖多次的CAS操作,而偏向锁只依赖一次CAS置换ThreadID。一旦出现多个线程竞争时必须撤销偏向锁,所以:撤销偏向锁消耗的性能必须 < 之前节省下来的CAS原子操作的性能消耗不然得不偿失!JDK6默认开启偏向锁,可通过-XX:-UseBiasedLocking禁用偏向锁。2 偏向锁的获取偏向锁的入口,synchronizer.cpp 文件的ObjectSynchronizer::fast_enter由BiasedLocking::revoke_and_rebias实现2.1 markOop mark = obj->mark()获取对象的markOop数据mark,即对象头的Mark Word2.2 判断mark是否为可偏向状态mark的偏向锁的锁标志位为 012.3 判断mark中JavaThread的状态若指向当前线程,则执行同步代码块若为空,则走4若指向其它线程,则走52.4 执行CAS原子指令设置mark中JavaThread为当前线程ID。若CAS成功,则执行同步代码块,否则走5。2.5 执行CAS失败说明当前存在多个线程竞争锁,当达到全局安全点(safepoint),获得偏向锁的线程就会被挂起,撤销偏向锁,并升级为轻量级锁。升级完成后被阻塞在安全点的线程继续执行同步代码块。BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) { assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint"); // We can revoke the biases of anonymously-biased objects // efficiently enough that we should not cause these revocations to // update the heuristics because doing so may cause unwanted bulk // revocations (which are expensive) to occur. // step1 markOop mark = obj->mark(); if (mark->is_biased_anonymously() && !attempt_rebias) { // We are probably trying to revoke the bias of this object due to // an identity hash code computation. Try to revoke the bias // without a safepoint. This is possible if we can successfully // compare-and-exchange an unbiased header into the mark word of // the object, meaning that no other thread has raced to acquire // the bias of the object. markOop biased_value = mark; markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark); if (res_mark == biased_value) { return BIAS_REVOKED; } } else if (mark->has_bias_pattern()) { Klass* k = obj->klass(); markOop prototype_header = k->prototype_header(); if (!prototype_header->has_bias_pattern()) { // This object has a stale bias from before the bulk revocation // for this data type occurred. It's pointless to update the // heuristics at this point so simply update the header with a // CAS. If we fail this race, the object's bias has been revoked // by another thread so we simply return and let the caller deal // with it. markOop biased_value = mark; markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark); assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked"); return BIAS_REVOKED; } else if (prototype_header->bias_epoch() != mark->bias_epoch()) { // The epoch of this biasing has expired indicating that the // object is effectively unbiased. Depending on whether we need // to rebias or revoke the bias of this object we can do it // efficiently enough with a CAS that we shouldn't update the // heuristics. This is normally done in the assembly code but we // can reach this point due to various points in the runtime // needing to revoke biases. if (attempt_rebias) { assert(THREAD->is_Java_thread(), ""); markOop biased_value = mark; markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark); if (res_mark == biased_value) { return BIAS_REVOKED_AND_REBIASED; } } else { markOop biased_value = mark; markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark); if (res_mark == biased_value) { return BIAS_REVOKED; } } } } HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias); if (heuristics == HR_NOT_BIASED) { return NOT_BIASED; } else if (heuristics == HR_SINGLE_REVOKE) { Klass *k = obj->klass(); markOop prototype_header = k->prototype_header(); if (mark->biased_locker() == THREAD && prototype_header->bias_epoch() == mark->bias_epoch()) { // A thread is trying to revoke the bias of an object biased // toward it, again likely due to an identity hash code // computation. We can again avoid a safepoint in this case // since we are only going to walk our own stack. There are no // races with revocations occurring in other threads because we // reach no safepoints in the revocation path. // Also check the epoch because even if threads match, another thread // can come in with a CAS to steal the bias of an object that has a // stale epoch. ResourceMark rm; if (TraceBiasedLocking) { tty->print_cr("Revoking bias by walking my own stack:"); } EventBiasedLockSelfRevocation event; BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD, NULL); ((JavaThread*) THREAD)->set_cached_monitor_info(NULL); assert(cond == BIAS_REVOKED, "why not?"); if (event.should_commit()) { event.set_lockClass(k); event.commit(); } return cond; } else { EventBiasedLockRevocation event; VM_RevokeBias revoke(&obj, (JavaThread*) THREAD); VMThread::execute(&revoke); if (event.should_commit() && (revoke.status_code() != NOT_BIASED)) { event.set_lockClass(k); // Subtract 1 to match the id of events committed inside the safepoint event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); event.set_previousOwner(revoke.biased_locker()); event.commit(); } return revoke.status_code(); } } assert((heuristics == HR_BULK_REVOKE) || (heuristics == HR_BULK_REBIAS), "?"); EventBiasedLockClassRevocation event; VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD, (heuristics == HR_BULK_REBIAS), attempt_rebias); VMThread::execute(&bulk_revoke); if (event.should_commit()) { event.set_revokedClass(obj->klass()); event.set_disableBiasing((heuristics != HR_BULK_REBIAS)); // Subtract 1 to match the id of events committed inside the safepoint event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); event.commit(); } return bulk_revoke.status_code(); }3 偏向锁的撤销只有当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销由BiasedLocking::revoke_at_safepoint实现:void BiasedLocking::revoke_at_safepoint(Handle h_obj) { assert(SafepointSynchronize::is_at_safepoint(), "must only be called at safepoint"); oop obj = h_obj(); HeuristicsResult heuristics = update_heuristics(obj, false); if (heuristics == HR_SINGLE_REVOKE) { revoke_bias(obj, false, false, NULL, NULL); } else if ((heuristics == HR_BULK_REBIAS) || (heuristics == HR_BULK_REVOKE)) { bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL); } clean_up_cached_monitor_info(); }偏向锁的撤销动作必须等待全局安全点(safepoint,GC时会让所有线程阻塞的停顿点)暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态撤销偏向锁,恢复到无锁(标志位 01)或轻量级锁(标志位 00)状态偏向锁在Java 1.6后默认启用,但在应用程序启动几s后才激活,可关闭延迟:-XX:BiasedLockingStartupDelay=0若确定应用程序中所有锁通常情况下处于竞争状态,可关闭偏向锁:XX:-UseBiasedLocking=false(默认打开)偏向锁的释放遍历线程栈的所有Lock Record,把ObjectReference切断,即ObjectReference = null.把ObjectReference置null,但锁对象的对象头的Mark Word还是没改变,依然偏向之前的线程,那还是没释放锁的嘛,的确是,线程退出临界区时候,并没有释放偏向锁,这么做是为 : 当再次需要获取锁时,只需要简单判断是否是重入,即可快速获取锁,而不用每次都CAS,这也是偏向锁在只有一个线程访问锁的情景下高效的核心。总结当出现锁资源访问的时候,都会在当前线程栈生成一条Lock Record,并且ObjectReference将指向锁对象的对象头 的Mark Word,该设置可能出现多线程,需CAS操作多线程情况下竞争同一个锁资源,偏向锁的撤销会影响效率偏向锁的重入计数依靠线程栈里Lock Record个数偏向锁撤销失败,最终会升级为轻量级锁偏向锁退出时并没有修改Mark Word,也就是没有释放锁偏向锁相对轻量级锁来说,当同一线程去再次获取锁的时候,不用进行CAS操作,提高了性能.(轻量级锁在同一线程情况下每次去获取锁,在无锁的状态下,每次都要进行一次CAS操作)偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁偏向锁的撤销是很复杂,成为理解代码的障碍,也阻碍了对同步系统重构,而且现如今基本都是多核系统,偏向锁的劣势越来越明显,所以在Java 15废弃了偏向锁】
-
原文链接:https://bbs.huaweicloud.com/blogs/335767大风吹去了往日的雾霾,阳光透过窗户照进来,透过窗户可以看到远处的山脉与蓝天相接,这可比我那永远见不到阳光的出租屋好多了。渐渐走进的脚步声打断了我的思绪,一位小姐姐坐在了面前,甜甜的香水味立刻钻进了我的鼻孔。小姐姐微笑地说:”您好,我是今天的面试官,那么开始吧?“我收起直勾勾的眼睛,说:“好的。”小姐姐说:“在Java的异常处理中有两大组成要素:抛出异常和捕获异常。那么抛出异常可以分为哪两种呢?”我立刻回答到:抛出异常可以分为显式和隐式。显式抛异常是在代码中使用throw关键字手动将异常实例抛出。隐式抛异常是 Java 虚拟机在执行过程中,遇到无法继续执行的异常状态,自动将异常实例抛出,比如我们经常遇到的空指针异常(NullPointerException)。小姐姐说:“很好,那么捕获异常中经常用到哪些关键字呢?”我立刻回答到:我们一般用到try、catch、finally等关键字。try被用来标记需要进行异常监控的代码;catch被用来捕获在try 监控的代码中触发的某种指定类型的异常,还可以定义针对该异常类型进行如何处理;finally被用来声明一段无论发生什么异常都必定运行的代码,它避免跳过某些关键的清理代码,比如:关闭已打开的IO资源。小姐姐说:“很好,如果三个关键字一起使用,代码执行的顺序是什么样子的?”我立刻回答到:在正常执行的情况下,先执行try中的代码再执行finally中的代码。如果try中的代码触发异常,并且异常没有被捕获,finally中代码会被直接执行,并且在执行之后重新抛出该异常;如果异常被catch捕获,先执行catch中的代码再执行finally中的代码。如果catch中的代码也触发了异常,那么finally中代码同样会被执行,并会抛出catch代码触发的那个异常。如果finally中的代码也触发了异常,那么会中断当前finally 代码的执行,并抛出异常。小姐姐说:“很好,在Java虚拟机中,是通过什么方式实现异常处理?”这个问题有点难度,我稍微思考了一下回答到:主要是通过异常表。在编译生成的字节码中,每个方法都附带一个异常表。异常表中可能有多条记录,每一条记录都包括from指针、to 指针、target指针和所捕获的异常类型。这些指针的值是字节码索引(bytecode index),用于定位字节码。其中,from指针和to指针表示异常处理监控的范围,比如: try所覆盖的范围。target指针指向异常处理代码的起始位置,比如:catch代码的起始位置。如果有异常触发时,Java虚拟机会从上至下遍历异常表中的所有记录。当触发异常的字节码的索引值在某个异常表记录的监控范围内,Java虚拟机会判断所抛出的异常和该记录想要捕获的异常是否匹配。如果匹配,控制流转将会移至该记录 target 指针指向的字节码。小姐姐说:“有点抽象,可以举个例子吗?”“当然可以。”我一边说,一边在纸上写了起来:public class OneMore { public static void main(String[] args) { String str = "万猫学社"; try { str = "try"; } catch (Exception e) { str = "catch"; } } }这段代码的 main 方法中,我定义了一段try 和catch 代码。编译过后的字节码中,这个方法的异常表拥有一个记录:public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=3, args_size=1 0: ldc #2 // String 万猫学社 2: astore_1 3: ldc #3 // String try 5: astore_1 6: goto 13 9: astore_2 10: ldc #5 // String catch 12: astore_1 13: return Exception table: from to target type 3 6 9 Class java/lang/Exception其from指针和to指针分别为 3 和 6,代表它的监控范围从索引为 3 的字节码开始,到索引为 6 的字节码结束(不包括 6)。该记录的 target 指针是 9,代表这个异常处理从索引为 9 的字节码开始。该记录的最后一列,代表该异常处理所捕获的异常类型是Exception。当触发异常的字节码的索引值在 3 和 6 之间时,Java虚拟机会判断所抛出的异常是否时Exception。如果是,控制流转将会移至索引为 9 的字节码开始执行。小姐姐满意地说:“很好,我去叫HR和你聊聊,如果顺利的话,今天就可以发offer。”参考文献:《Java编程思想》《Java核心技术》《深入理解Java虚拟机:JVM高级特性与实践》
-
数组是相同数据类型的元素的有序集合。为什么说是有序的呢?访问数组元素是通过数组下标进行的,下标从0开始,依次递增直到 length - 1(length为数组长度),所以可以理解为有序的。首先,来谈谈如何初始化数组?两种初始化方法:动态初始化、静态初始化。(这里以一维数组为例)//动态初始化int[] arr1 = new int[9];//声明了一个数组长度为9的一维数组//除了上面的初始化方式,还可以拆分为两步int[] arr2;arr2 = new int [9];//这两种都属于动态初始化,只是方式存在差异//静态初始化,声明数组长度为5的一维数组int[] arr3 = new int[]{1,2,3,4,5};//静态初始化的标准格式//也可以分为两步初始化//int[] arr3;//arr3 = new int[]{1,2,3,4,5};int[] arr4 = {1,2,3,4},5;//静态初始化的省略格式,不能分为两步既然声明了一个数组,那么数组是储存在哪里的呢?当使用"new"关键字创建数组对象后,会在堆中为数组分配相应的内存空间。总的来说,就是当你使用"new"关键字创建一个对象时,就会在堆中为该对象分配相应的内存空间。数组初始化,那么如何访问数组元素进行赋值和获取元素值呢?int[] arr = new int[5];//数组元素的值默认为0//数组元素赋值for(int i = 0; i < 5; i++){arr = 1;//这里就将数组下标为i的元素赋值为1}//既然对数组赋值了,那么如何获取数组元素的值System.out.println(arr);//这样是否会输出数组元素的值呢//答案是不会,这样输出的是数组的内存地址哈希值[I@#########]//方括号代表是数组,大写字母I代表为整型,后面的#则是十六进制数//这里介绍一种循环:for each循环for(int x:arr){System.out.println(x);//这样就能输出数组元素的值了,当然你也可以使用普通的for循环来进行访问//for(int i = 0; i < 5; i++){//System.out.println(arr);//}}以上就是Java数组中关于一维数组的简单介绍。
-
KML_VML的JAVA语言适配项目实践分享 很高兴能够为大家提供我们在项目实践过程中所获得的经验,我们团队所做的项目是基于KML_VML的JAVA语言适配项目,我们项目的主要任务就是通过JAVA适配C语言接口,实现跨语言调用的问题。接下来对我们项目的开发过程进行一些分享。项目介绍:此项目是基于鲲鹏平台的openJDK,通过JNI编程框架,实现JAVA程序对C接口动态库的调用,提供KML_VML的JAVA语言适配,完善鲲鹏高性能向量数学库的生态。主要的任务便是做出JAVA与C语言的适配器,同时达标项目的要求。项目要求:实现KML_VML32个接口的JAVA适配,通过JAVA接口单元测试。性能损耗相比C接口调用不超过5%。符合华为编码规范。工作过程:如下图所示,我们的主要工作过程,首先在项目起始阶段,开展项目启动会,会后进行详细的需求分析,并根据内容做出详尽的设计方案,然后,依照需求分析,设计方案,项目的要求等等从而做出详尽的工作计划。工作计划:按照迭代的方式进行开发,主要分为了三个阶段性任务,包括技术方案设计,开发撰写报告(阶段性交付),验收和总结。计划是根据项目的规划,以及工作量进行详细的设计,尽量按照时间稳步推进。如图所示,展示了部分计划的安排方式。同时,为了项目稳步推进,就需要与老师,与华为方定期进行会议讨论,汇报进度,以下是会议的安排方式,以及图示的会议纪要1、每周实验室内部开展组会,面对面形式进行汇报,包括进度的汇报,项目开发内容,遇到的问题讨论等。2、每两周与华为方进行线上会议的汇报,稳定推进项目,其中内容,包括了项目进度,开发内容,共同协商遇到的问题,并记录会议纪要,撰写周报,对该阶段的项目开发进行总结。3、除此之外,遇到问题,需要多多通过聊天等方式进行联系,尽快确定问题解决方式。遇到的问题及解决方式因为部分函数本身特性,跨语言调用传参占用时间显得过于大,造成效率低解决:我们先制订比较合适的测试方案,再与华为方进行协商讨论1、由于JAVA的语言特性,需要制定更加详细,更加符合的测试方案2、根据实际情况选定合适的数量级参数进行测试3、选取不同数量级的参数,统计变异系数,从而选定最稳定的测试情况同时根据不同的参数与函数指定可展现编译系数的图表,以找寻稳定的测试情况,如下图所示,该图表示程序运行时的变异参数,其中横轴为参数指标,纵轴为变异系数(变异系数=标准差/均值,该指标可反映数据的稳定性)通过分析上述的原因,进行迭代开发,优化性能测试,从而得出:1、所有性能指标均达标2、提供多种测试方式,包括全量测试,单例测试3、动态生成测试用例,实时展示测试后的效率,并以表格形式进行汇总4、多种测试案例,汇总到表格中,提供更详细的性能表格 同时在项目开发过程中,必不可少的会遇到一些细节方面的问题1、服务器环境问题2、功能指标问题3、性能指标问题4、兼容性问题5、项目及文档交付问题解决方式:1、多开会交流,比较大的问题在网络会议中讨论解决方式,小问题,可以直接在群聊中交流,更快得到双方的讨论结果2、根据既定的目标,不断迭代开发,保证新版本比之前的版本,功能性能,以及代码质量都得到提升3、可以多采用软件工程的开发方式,如敏捷开发,迭代开发等等项目的总结与感受: 1、在项目前期进行完善的需求分析,采用软件工程的方式对项目进行管理,制定合适的项目计划2、撰写设计方案,综合考量项目的需求,目标,制定合理的设计方案3、对项目开发的文档,代码进行统一管理,使用一定的开发模式4、多开会沟通,定期的会议是尤为重要的,及时反映开发的问题才能做到事半功倍5、华为方的设计与规划也都十分的合适,同时与华为方的沟通也非常顺畅,能够及时沟通解决遇到的问题,项目管控好,隔周开会可以逐步稳定推进项目进行,同时这也让我们团队学习到了许多项目管理知识,期待以后能够与华为有更多的合作。 北京邮电大学-智能应用软件开发团队-郭成旺 指导教师:杨正球,修佳鹏
-
@GetMapping("/downloadFile") public String downloadFile(@RequestParam Integer fId){ if (fileStoreService.getFileStoreByUserId(loginUser.getUserId()).getPermission() == 2){ logger.error("用户没有下载文件的权限!下载失败..."); return "redirect:/error401Page"; } //获取文件信息 MyFile myFile = myFileService.getFileByFileId(fId); String remotePath = myFile.getMyFilePath(); String fileName = myFile.getMyFileName()+myFile.getPostfix(); try { //去FTP上拉取 OutputStream os = new BufferedOutputStream(response.getOutputStream()); response.setCharacterEncoding("utf-8"); // 设置返回类型 response.setContentType("multipart/form-data"); // 文件名转码一下,不然会出现中文乱码 response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileName, "UTF-8")); boolean flag = FtpUtil.downloadFile("/" + remotePath, fileName, os); if (flag) { myFileService.updateFile( MyFile.builder().myFileId(myFile.getMyFileId()).downloadTime(myFile.getDownloadTime() + 1).build()); os.flush(); os.close(); logger.info("文件下载成功!" + myFile); } } catch (Exception e) { e.printStackTrace(); } return "success"; } /** * @Description 删除文件 * @Author xw * @Date 23:14 2020/2/10 * @Param [fId, folder] * @return java.lang.String **/ @GetMapping("/deleteFile") public String deleteFile(@RequestParam Integer fId,Integer folder){ //获得文件信息 MyFile myFile = myFileService.getFileByFileId(fId); String remotePath = myFile.getMyFilePath(); String fileName = myFile.getMyFileName()+myFile.getPostfix(); //从FTP文件服务器上删除文件 boolean b = FtpUtil.deleteFile("/"+remotePath, fileName); if (b){ //删除成功,返回空间 fileStoreService.subSize(myFile.getFileStoreId(),Integer.valueOf(myFile.getSize())); //删除文件表对应的数据 myFileService.deleteByFileId(fId); } logger.info("删除文件成功!"+myFile); return "redirect:/files?fId="+folder; } /** * @Description 删除文件夹并清空文件 * @Author xw * @Date 15:22 2020/2/12 * @Param [fId] * @return java.lang.String **/ @GetMapping("/deleteFolder") public String deleteFolder(@RequestParam Integer fId){ FileFolder folder = fileFolderService.getFileFolderByFileFolderId(fId); //强制删除 deleteFolderF(folder); return folder.getParentFolderId() == 0?"redirect:/files":"redirect:/files?fId="+folder.getParentFolderId(); } /** * @Description 迭代删除文件夹里面的所有文件和子文件夹 * @Author xw * @Date 9:17 2020/2/29 * @Param [folder] * @return void **/ public void deleteFolderF(FileFolder folder){ //获得当前文件夹下的所有子文件夹 List<FileFolder> folders = fileFolderService.getFileFolderByParentFolderId(folder.getFileFolderId()); //删除当前文件夹的所有的文件 List<MyFile> files = myFileService.getFilesByParentFolderId(folder.getFileFolderId()); if (files.size()!=0){ for (int i = 0; i < files.size(); i++) { Integer fileId = files.get(i).getMyFileId(); boolean b = FtpUtil.deleteFile("/"+files.get(i).getMyFilePath(), files.get(i).getMyFileName() + files.get(i).getPostfix()); if (b){ myFileService.deleteByFileId(fileId); fileStoreService.subSize(folder.getFileStoreId(),Integer.valueOf(files.get(i).getSize())); } } } if (folders.size()!=0){ for (int i = 0; i < folders.size(); i++) { deleteFolderF(folders.get(i)); } } fileFolderService.deleteFileFolderById(folder.getFileFolderId()); } /** * @Description 添加文件夹 * @Author xw * @Date 23:16 2020/2/10 * @Param [folder, map] * @return java.lang.String **/ @PostMapping("/addFolder") public String addFolder(FileFolder folder,Map<String, Object> map) { //设置文件夹信息 folder.setFileStoreId(loginUser.getFileStoreId()); folder.setTime(new Date()); //获得当前目录下的所有文件夹,检查当前文件夹是否已经存在 List<FileFolder> fileFolders = null; if (folder.getParentFolderId() == 0){ //向用户根目录添加文件夹 fileFolders = fileFolderService.getRootFoldersByFileStoreId(loginUser.getFileStoreId()); }else{ //向用户的其他目录添加文件夹 fileFolders = fileFolderService.getFileFolderByParentFolderId(folder.getParentFolderId()); } for (int i = 0; i < fileFolders.size(); i++) { FileFolder fileFolder = fileFolders.get(i); if (fileFolder.getFileFolderName().equals(folder.getFileFolderName())){ logger.info("添加文件夹失败!文件夹已存在..."); return "redirect:/files?error=1&fId="+folder.getParentFolderId(); } } //向数据库写入数据 Integer integer = fileFolderService.addFileFolder(folder); logger.info("添加文件夹成功!"+folder); return "redirect:/files?fId="+folder.getParentFolderId(); } /** * @Description 重命名文件夹 * @Author xw * @Date 23:18 2020/2/10 * @Param [folder, map] * @return java.lang.String **/ @PostMapping("/updateFolder") public String updateFolder(FileFolder folder,Map<String, Object> map) { //获得文件夹的数据库信息 FileFolder fileFolder = fileFolderService.getFileFolderByFileFolderId(folder.getFileFolderId()); fileFolder.setFileFolderName(folder.getFileFolderName()); //获得当前目录下的所有文件夹,用于检查文件夹是否已经存在 List<FileFolder> fileFolders = fileFolderService.getFileFolderByParentFolderId(fileFolder.getParentFolderId()); for (int i = 0; i < fileFolders.size(); i++) { FileFolder folder1 = fileFolders.get(i); if (folder1.getFileFolderName().equals(folder.getFileFolderName()) && folder1.getFileFolderId() != folder.getFileFolderId()){ logger.info("重命名文件夹失败!文件夹已存在..."); return "redirect:/files?error=2&fId="+fileFolder.getParentFolderId(); } } //向数据库写入数据 Integer integer = fileFolderService.updateFileFolderById(fileFolder); logger.info("重命名文件夹成功!"+folder); return "redirect:/files?fId="+fileFolder.getParentFolderId(); } /** * @Description 重命名文件 * @Author xw * @Date 12:47 2020/2/12 * @Param [file, map] * @return java.lang.String **/ @PostMapping("/updateFileName") public String updateFileName(MyFile file,Map<String, Object> map) { MyFile myFile = myFileService.getFileByFileId(file.getMyFileId()); if (myFile != null){ String oldName = myFile.getMyFileName(); String newName = file.getMyFileName(); if (!oldName.equals(newName)){ boolean b = FtpUtil.reNameFile(myFile.getMyFilePath() + "/" + oldName+myFile.getPostfix(), myFile.getMyFilePath() + "/" + newName+myFile.getPostfix()); if (b){ Integer integer = myFileService.updateFile( MyFile.builder().myFileId(myFile.getMyFileId()).myFileName(newName).build()); if (integer == 1){ logger.info("修改文件名成功!原文件名:"+oldName+" 新文件名:"+newName); }else{ logger.error("修改文件名失败!原文件名:"+oldName+" 新文件名:"+newName); } } } } return "redirect:/files?fId="+myFile.getParentFolderId(); } /** * @Description 获得二维码 * @Author xw * @Date 15:20 2020/2/12 * @Param [id, url] * @return java.util.Map<java.lang.String,java.lang.Object> **/ @GetMapping("getQrCode") @ResponseBody public Map<String,Object> getQrCode(@RequestParam Integer id,@RequestParam String url){ Map<String,Object> map = new HashMap<>(); map.put("imgPath","https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2654852821,3851565636&fm=26&gp=0.jpg"); if (id != null){ MyFile file = myFileService.getFileByFileId(id); if (file != null){ try { String path = request.getSession().getServletContext().getRealPath("/user_img/"); url = url+"/file/share?t="+ UUID.randomUUID().toString().substring(0,10) +"&f="+file.getMyFileId()+"&p="+file.getUploadTime().getTime()+""+file.getSize()+"&flag=1"; File targetFile = new File(path, ""); if (!targetFile.exists()) { targetFile.mkdirs(); } File f = new File(path, id + ".jpg"); if (!f.exists()){ //文件不存在,开始生成二维码并保存文件 OutputStream os = new FileOutputStream(f); QRCodeUtil.encode(url, "/static/img/logo.png", os, true); os.close(); } map.put("imgPath","user_img/"+id+".jpg"); map.put("url",url); } catch (Exception e) { e.printStackTrace(); } } } return map; } /** * @Description 分享文件 * @Author xw * @Date 14:23 2020/2/12 * @Param [fId] * @return void **/ @GetMapping("/file/share") public String shareFile(Integer f,String p,String t,Integer flag){ String fileNameTemp = ""; String remotePath = ""; String fileName = ""; Integer times = 0; if (flag == null || f == null || p == null || t == null){ logger.info("下载分享文件失败,参数错误"); return "redirect:/error400Page"; } if(flag == 1){ //获取文件信息 MyFile myFile = myFileService.getFileByFileId(f); if (myFile == null){ return "redirect:/error404Page"; } String pwd = myFile.getUploadTime().getTime()+""+myFile.getSize(); if (!pwd.equals(p)){ return "redirect:/error400Page"; } remotePath = myFile.getMyFilePath(); fileName = myFile.getMyFileName()+myFile.getPostfix(); }else if(flag == 2){ TempFile tempFile = tempFileService.queryById(f); if (tempFile == null){ return "redirect:/error404Page"; } Long test = tempFile.getUploadTime().getTime(); String pwd = tempFile.getSize(); if (!pwd.equals(p)){ return "redirect:/error400Page"; } remotePath = tempFile.getFilePath(); fileName = tempFile.getFileName(); }else { return "redirect:/error400Page"; } fileNameTemp = fileName; try { //解决下载文件时 中文文件名乱码问题 boolean isMSIE = isMSBrowser(request); if (isMSIE) { //IE浏览器的乱码问题解决 fileNameTemp = URLEncoder.encode(fileNameTemp, "UTF-8"); } else { //万能乱码问题解决 fileNameTemp = new String(fileNameTemp.getBytes("UTF-8"), "ISO-8859-1"); } //去FTP上拉取 OutputStream os = new BufferedOutputStream(response.getOutputStream()); response.setCharacterEncoding("utf-8"); // 设置返回类型 response.setContentType("multipart/form-data"); // 文件名转码一下,不然会出现中文乱码 response.setHeader("Content-Disposition", "attachment;fileName=" + fileNameTemp); if (FtpUtil.downloadFile("/" + remotePath, fileName, os)) { myFileService.updateFile( MyFile.builder().myFileId(f).downloadTime(times + 1).build()); os.flush(); os.close(); logger.info("文件下载成功!"); } } catch (Exception e) { e.printStackTrace(); } return "success"; } /** * @Description 根据文件的后缀名获得对应的类型 * @Author xw * @Date 23:20 2020/2/10 * @Param [type] * @return int 1:文本类型 2:图像类型 3:视频类型 4:音乐类型 5:其他类型 **/ public int getType(String type){ if (".chm".equals(type)||".txt".equals(type)||".xmind".equals(type)||".xlsx".equals(type)||".md".equals(type) ||".doc".equals(type)||".docx".equals(type)||".pptx".equals(type) ||".wps".equals(type)||".word".equals(type)||".html".equals(type)||".pdf".equals(type)){ return 1; }else if (".bmp".equals(type)||".gif".equals(type)||".jpg".equals(type)||".ico".equals(type)||".vsd".equals(type) ||".pic".equals(type)||".png".equals(type)||".jepg".equals(type)||".jpeg".equals(type)||".webp".equals(type) ||".svg".equals(type)){ return 2; } else if (".avi".equals(type)||".mov".equals(type)||".qt".equals(type) ||".asf".equals(type)||".rm".equals(type)||".navi".equals(type)||".wav".equals(type) ||".mp4".equals(type)||".mkv".equals(type)||".webm".equals(type)){ return 3; } else if (".mp3".equals(type)||".wma".equals(type)){ return 4; } else { return 5; } } /** * @Description 正则验证文件名是否合法 [汉字,字符,数字,下划线,英文句号,横线] * @Author xw * @Date 23:22 2020/2/10 * @Param [target] * @return boolean **/ public boolean checkTarget(String target) { final String format = "[^\\u4E00-\\u9FA5\\uF900-\\uFA2D\\w-_.]"; Pattern pattern = Pattern.compile(format); Matcher matcher = pattern.matcher(target); return !matcher.find(); } /** * @Description 判断当前浏览器是否为ie * @Author xw * @Date 22:39 2020/3/5 * @Param [request] * @return boolean **/ public static boolean isMSBrowser(HttpServletRequest request) { String[] IEBrowserSignals = {"MSIE", "Trident", "Edge"}; String userAgent = request.getHeader("User-Agent"); for (String signal : IEBrowserSignals) { if (userAgent.contains(signal)){ return true; } } return false; }原文链接:https://blog.csdn.net/m0_66863468/article/details/122853352
-
public String uploadTempFile(@RequestParam("file") MultipartFile file,String url) { session.setAttribute("imgPath","https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2654852821,3851565636&fm=26&gp=0.jpg"); String name = file.getOriginalFilename().replaceAll(" ",""); if (!checkTarget(name)){ logger.error("临时文件上传失败!文件名不符合规范..."); session.setAttribute("msg", "上传失败!文件名不符合规范"); return "redirect:/temp-file"; } SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); String dateStr = format.format(new Date()); String path = "temp/"+dateStr +"/"+UUID.randomUUID(); try { if (FtpUtil.uploadFile("/"+path, name, file.getInputStream())){ //上传成功 logger.info("临时文件上传成功!"+name); String size = String.valueOf(file.getSize()); TempFile tempFile = TempFile.builder().fileName(name).filePath(path).size(size).uploadTime(new Date()).build(); if (tempFileService.insert(tempFile)) { try { String id = UUID.randomUUID().toString(); String p = request.getSession().getServletContext().getRealPath("/user_img/"); Long t = tempFile.getUploadTime().getTime(); url = url+"/file/share?t="+ UUID.randomUUID().toString().substring(0,10) +"&f="+tempFile.getFileId()+"&p="+size+"&flag=2"; File targetFile = new File(p, ""); if (!targetFile.exists()) { targetFile.mkdirs(); } File f = new File(p, id + ".jpg"); if (!f.exists()){ //文件不存在,开始生成二维码并保存文件 OutputStream os = new FileOutputStream(f); QRCodeUtil.encode(url, "/static/img/logo.png", os, true); os.close(); } //异步删除临时文件 tempFileService.deleteById(tempFile.getFileId()); session.setAttribute("imgPath","user_img/"+id+".jpg"); session.setAttribute("url",url); session.setAttribute("msg","上传成功,扫码/访问链接 即可下载!"); return "redirect:/temp-file"; } catch (Exception e) { e.printStackTrace(); } }else { logger.info("临时文件数据库写入失败!"+name); session.setAttribute("url","error"); session.setAttribute("msg", "服务器出错了,临时文件上传失败!"); } }else{ //上传失败 logger.info("临时文件上传失败!"+name); session.setAttribute("url","error"); session.setAttribute("msg", "服务器出错了,上传失败!"); } } catch (IOException e) { e.printStackTrace(); } return "redirect:/temp-file"; } /** * @Description 网盘的文件上传 * @Author xw * @Date 23:10 2020/2/10 * @Param [files] * @return java.util.Map<java.lang.String,java.lang.Object> **/ @PostMapping("/uploadFile") @ResponseBody public Map<String, Object> uploadFile(@RequestParam("file") MultipartFile files) { Map<String, Object> map = new HashMap<>(); if (fileStoreService.getFileStoreByUserId(loginUser.getUserId()).getPermission() != 0){ logger.error("用户没有上传文件的权限!上传失败..."); map.put("code", 499); return map; } FileStore store = fileStoreService.getFileStoreByUserId(loginUser.getUserId()); Integer folderId = Integer.valueOf(request.getHeader("id")); String name = files.getOriginalFilename().replaceAll(" ",""); //获取当前目录下的所有文件,用来判断是否已经存在 List<MyFile> myFiles = null; if (folderId == 0){ //当前目录为根目录 myFiles = myFileService.getRootFilesByFileStoreId(loginUser.getFileStoreId()); }else { //当前目录为其他目录 myFiles = myFileService.getFilesByParentFolderId(folderId); } for (int i = 0; i < myFiles.size(); i++) { if ((myFiles.get(i).getMyFileName()+myFiles.get(i).getPostfix()).equals(name)){ logger.error("当前文件已存在!上传失败..."); map.put("code", 501); return map; } } SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); String dateStr = format.format(new Date()); String path = loginUser.getUserId()+"/"+dateStr +"/"+folderId; if (!checkTarget(name)){ logger.error("上传失败!文件名不符合规范..."); map.put("code", 502); return map; } Integer sizeInt = Math.toIntExact(files.getSize() / 1024); //是否仓库放不下该文件 if(store.getCurrentSize()+sizeInt > store.getMaxSize()){ logger.error("上传失败!仓库已满。"); map.put("code", 503); return map; } //处理文件大小 String size = String.valueOf(files.getSize()/1024.0); int indexDot = size.lastIndexOf("."); size = size.substring(0,indexDot); int index = name.lastIndexOf("."); String tempName = name; String postfix = ""; int type = 4; if (index!=-1){ tempName = name.substring(index); name = name.substring(0,index); //获得文件类型 type = getType(tempName.toLowerCase()); postfix = tempName.toLowerCase(); } try { //提交到FTP服务器 boolean b = FtpUtil.uploadFile("/"+path, name + postfix, files.getInputStream()); if (b){ //上传成功 logger.info("文件上传成功!"+files.getOriginalFilename()); //向数据库文件表写入数据 myFileService.addFileByFileStoreId( MyFile.builder() .myFileName(name).fileStoreId(loginUser.getFileStoreId()).myFilePath(path) .downloadTime(0).uploadTime(new Date()).parentFolderId(folderId). size(Integer.valueOf(size)).type(type).postfix(postfix).build()); //更新仓库表的当前大小 fileStoreService.addSize(store.getFileStoreId(),Integer.valueOf(size)); try { Thread.sleep(5000); map.put("code", 200); } catch (InterruptedException e) { e.printStackTrace(); } }else{ logger.error("文件上传失败!"+files.getOriginalFilename()); map.put("code", 504); } } catch (IOException e) { e.printStackTrace(); } return map; } /** * @Description 网盘的文件下载 * @Author xw * @Date 23:13 2020/2/10 * @Param [fId] * @return void **/原文链接:https://blog.csdn.net/m0_66863468/article/details/122853352
-
一、项目简述功能:用户的邮箱注册、验证码验证以及用户登录。 不需要注册账号,也可以上传满足条件的临时文件,但是 只4小时内有效。 文件的管理,上传、下载、重命名、删除、查看统计数 据、分类管理等。 文件夹的管理,创建、删除、重命名。 文件的分享,支持通过链博口二维码的分享方式等等。二、项目运行环境配置: Jdk1.8 + Tomcat8.5 + mysql + Eclispe (IntelliJ IDEA,Eclispe,MyEclispe,Sts 都支持)项目技术: JSP +Springboot+ SpringMVC + MyBatis + ThymeLeaf + FTP+ JavaScript + JQuery + Ajax + maven等等管理员控制器:/** * @ClassName: AdminController * @Description: 管理员控制器 **/@Controllerpublic class AdminController extends BaseController { private Logger logger = LogUtils.getInstance(AdminController.class); /** * @Description 前往用户管理页面 * @Author xw * @Date 15:11 2020/3/10 * @Param [map] * @return java.lang.String **/ @GetMapping("/manages-users") public String manageUsers(Map<String,Object> map,Integer cur){ if (loginUser.getRole() == 1){ //用于无访问权限 logger.error("当前登录用户:"+loginUser.getUserName()+"无管理员权限!"); return "redirect:/error401Page"; } //获取全部的用户 Integer usersCount = userService.getUsersCount(); //获取当前查询的页数,如果为空,默认为0 cur = (cur == null || cur<0)?0:cur; //获得统计信息 FileStoreStatistics statistics = myFileService.getCountStatistics(loginUser.getFileStoreId()); //分页获得20个用户信息 Page<Object> page = PageHelper.startPage(cur, 20); List<UserToShow> users = userService.getUsers(); map.put("statistics", statistics); map.put("users", users); map.put("page", page); map.put("usersCount", usersCount); logger.info("用户管理域的内容:"+map); return "admin/manage-users"; } /** * @Description 修改用户的权限和最大容量 * @Author xw * @Date 18:20 2020/3/10 * @Param [uId, pre, size] * @return java.lang.String **/ @GetMapping("/updateStoreInfo") @ResponseBody public String updateStoreInfo(Integer uId,Integer pre,Integer size){ Integer integer = fileStoreService.updatePermission(uId, pre, size*1024); if (integer == 1) { //更新成功,返回200状态码 logger.info("修改用户"+userService.queryById(uId).getUserName()+":的权限和仓库大小成功!"); return "200"; }else { //更新失败,返回500状态码 logger.error("修改用户"+userService.queryById(uId).getUserName()+":的权限和仓库大小失败!"); return "500"; } } /** * @Description 删除用户 * @Author xw * @Date 18:44 2020/3/10 * @Param [uId, cur] * @return java.lang.String **/ @GetMapping("/deleteUser") public String deleteUser(Integer uId,Integer cur){ cur = (cur == null || cur < 0)?1:cur; User user = userService.queryById(uId); FileStore fileStore = fileStoreService.getFileStoreByUserId(uId); List<FileFolder> folders = fileFolderService.getRootFoldersByFileStoreId(fileStore.getFileStoreId()); //迭代删除文件夹 for (FileFolder f:folders) { deleteFolderF(f); } List<MyFile> files = myFileService.getRootFilesByFileStoreId(fileStore.getFileStoreId()); //删除该用户仓库根目录下的所有文件 for (MyFile f:files) { String remotePath = f.getMyFilePath(); String fileName = f.getMyFileName()+f.getPostfix(); //从FTP文件服务器上删除文件 boolean b = FtpUtil.deleteFile("/"+remotePath, fileName); if (b){ //删除成功,返回空间 fileStoreService.subSize(f.getFileStoreId(),Integer.valueOf(f.getSize())); //删除文件表对应的数据 myFileService.deleteByFileId(f.getMyFileId()); } logger.info("删除文件成功!"+f); } if (FtpUtil.deleteFolder("/" + uId)){ logger.info("清空FTP上该用户的文件成功"); }else { logger.error("清空FTP上该用户的文件失败"); } userService.deleteById(uId); fileStoreService.deleteById(fileStore.getFileStoreId()); return "redirect:/manages-users?cur="+cur; } /** * @Description 迭代删除文件夹里面的所有文件和子文件夹 * @Author xw * @Date 9:17 2020/2/29 * @Param [folder] * @return void **/ public void deleteFolderF(FileFolder folder){ //获得当前文件夹下的所有子文件夹 List<FileFolder> folders = fileFolderService.getFileFolderByParentFolderId(folder.getFileFolderId()); //删除当前文件夹的所有的文件 List<MyFile> files = myFileService.getFilesByParentFolderId(folder.getFileFolderId()); if (files.size()!=0){ for (int i = 0; i < files.size(); i++) { Integer fileId = files.get(i).getMyFileId(); boolean b = FtpUtil.deleteFile("/"+files.get(i).getMyFilePath(), files.get(i).getMyFileName() + files.get(i).getPostfix()); if (b){ myFileService.deleteByFileId(fileId); fileStoreService.subSize(folder.getFileStoreId(),Integer.valueOf(files.get(i).getSize())); } } } if (folders.size()!=0){ for (int i = 0; i < folders.size(); i++) { deleteFolderF(folders.get(i)); } } fileFolderService.deleteFileFolderById(folder.getFileFolderId()); }}登录控制器:/** * @Description 登录控制器 * @return **/@Controllerpublic class LoginController extends BaseController { private Logger logger = LogUtils.getInstance(LoginController.class); /** * @Description 免登陆用户入口,用于本地开发测试,上线运营为了安全请删除此方法 * @Author xw * @Date 15:17 2020/2/26 * @Param [] * @return java.lang.String **/ @GetMapping("/admin") public String adminLogin(){ User user = userService.getUserByOpenId("123456"); logger.info("使用免登陆方式登录成功!"+user); session.setAttribute("loginUser", user); return "redirect:/index"; } /** * 用于注册流程,用户名,密码,邮箱等校验工作由前端来完成 * * @param map 用于存储提示信息 * @author GGBOY * @date 2020/1/28 */ @PostMapping("/register") public String register(User user, String code, Map<String, Object> map) { String uCode = (String) session.getAttribute(user.getEmail() + "_code"); if (!code.equals(uCode)) { map.put("errorMsg", "验证码错误"); return "index"; } // 用户名去空格 user.setUserName(user.getUserName().trim()); user.setImagePath("https://p.qpic.cn/qqconnect/0/app_101851241_1582451550/100?max-age=2592000&t=0"); user.setRegisterTime(new Date()); user.setRole(1); if (userService.insert(user)) { FileStore store = FileStore.builder().userId(user.getUserId()).currentSize(0).build(); fileStoreService.addFileStore(store); user.setFileStoreId(store.getFileStoreId()); userService.update(user); logger.info("注册用户成功!当前注册用户" + user); logger.info("注册仓库成功!当前注册仓库" + store); } else { map.put("errorMsg", "服务器发生错误,注册失败"); return "index"; } session.removeAttribute(user.getEmail() + "_code"); session.setAttribute("loginUser", user); return "redirect:/index"; } /** * 用户登录 * @param map 存储提示信息 * @return java.lang.String * @author 莫提 * @date 2020/1/28 */ @PostMapping("/login") public String login(User user, Map<String, Object> map) { User userByEmail = userService.getUserByEmail(user.getEmail()); if (userByEmail != null && userByEmail.getPassword().equals(user.getPassword())) { session.setAttribute("loginUser", userByEmail); logger.info("登录成功!"+userByEmail); return "redirect:/index"; }else{ User user1 = userService.getUserByEmail(user.getEmail()); String errorMsg = user1 == null ? "该邮箱尚未注册" : "密码错误"; logger.info("登录失败!请确认邮箱和密码是否正确!"); //登录失败,将失败信息返回前端渲染 map.put("errorMsg", errorMsg); return "index"; } } /** * @return void * @Description 向注册邮箱发送验证码, 并验证邮箱是否已使用 * @Author xw * @Date 19:32 2020/1/29 * @Param [userName, email, password] **/ @ResponseBody @RequestMapping("/sendCode") public String sendCode(String userName, String email, String password) { User userByEmail = userService.getUserByEmail(email); if (userByEmail != null) { logger.error("发送验证码失败!邮箱已被注册!"); return "exitEmail"; } logger.info("开始发送邮件.../n" + "获取的到邮件发送对象为:" + mailSender); mailUtils = new MailUtils(mailSender); String code = "123456"; session.setAttribute(email + "_code", code); return "success"; } /** * @Description 请求QQ登录 * @Author xw * @Date 18:27 2020/2/25 * @Param [] * @return void **/ @GetMapping("/loginByQQ") public void login() { response.setContentType("text/html;charset=utf-8"); try { response.sendRedirect(new Oauth().getAuthorizeURL(request)); logger.info("请求QQ登录,开始跳转..."); } catch (QQConnectException | IOException e) { e.printStackTrace(); } } /** * @Description QQ登录回调地址 * @Author xw * @Date 18:27 2020/2/25 * @Param [] * @return java.lang.String **/ @GetMapping("/connection") public String connection() { try { AccessToken accessTokenObj = (new Oauth()).getAccessTokenByRequest(request); String accessToken = null, openID = null; long tokenExpireIn = 0L; if ("".equals(accessTokenObj.getAccessToken())) { logger.error("登录失败:没有获取到响应参数"); return "accessTokenObj=>" + accessTokenObj + "; accessToken" + accessTokenObj.getAccessToken(); } else { accessToken = accessTokenObj.getAccessToken(); tokenExpireIn = accessTokenObj.getExpireIn(); logger.error("accessToken" + accessToken); request.getSession().setAttribute("demo_access_token", accessToken); request.getSession().setAttribute("demo_token_expirein", String.valueOf(tokenExpireIn)); // 利用获取到的accessToken 去获取当前用的openid -------- start OpenID openIDObj = new OpenID(accessToken); openID = openIDObj.getUserOpenID(); UserInfo qzoneUserInfo = new UserInfo(accessToken, openID); UserInfoBean userInfoBean = qzoneUserInfo.getUserInfo(); if (userInfoBean.getRet() == 0) { logger.info("用户的OPEN_ID: " + openID); logger.info("用户的昵称: " + removeNonBmpUnicode(userInfoBean.getNickname())); logger.info("用户的头像URI: " + userInfoBean.getAvatar().getAvatarURL100()); //设置用户信息 User user = userService.getUserByOpenId(openID); if (user == null){ user = User.builder() .openId(openID).userName(removeNonBmpUnicode(userInfoBean.getNickname())) .imagePath(userInfoBean.getAvatar().getAvatarURL100()). registerTime(new Date()).build(); if (userService.insert(user)){ logger.info("注册用户成功!当前注册用户" + user); FileStore store = FileStore.builder().userId(user.getUserId()).build(); if (fileStoreService.addFileStore(store) == 1){ user.setFileStoreId(store.getFileStoreId()); userService.update(user); logger.info("注册仓库成功!当前注册仓库" + store); } } else { logger.error("注册用户失败!"); } }else { user.setUserName(removeNonBmpUnicode(userInfoBean.getNickname())); user.setImagePath(userInfoBean.getAvatar().getAvatarURL100()); userService.update(user); } logger.info("QQ用户登录成功!"+user); session.setAttribute("loginUser", user); return "redirect:/index"; } else { logger.error("很抱歉,我们没能正确获取到您的信息,原因是: " + userInfoBean.getMsg()); } } } catch (QQConnectException e) { } finally { logger.error("登录成功!"); } return "登录失败!请查看日志信息..."; } /** * @Description 处理掉QQ网名中的特殊表情 * @Author xw * @Date 18:26 2020/2/25 * @Param [str] * @return java.lang.String 返回处理之后的网名 **/ public String removeNonBmpUnicode(String str) { if (str == null) { return null; } str = str.replaceAll("[^\\u0000-\\uFFFF]", ""); if ("".equals(str)) { str = "($ _ $)"; } return str; } /** * @Description 退出登录,清空session * @Author xw * @Date 18:26 2020/2/25 * @Param [] * @return java.lang.String **/ @GetMapping("/logout") public String logout() { logger.info("用户退出登录!"); session.invalidate(); return "redirect:/"; } }文件仓库控制器:/** * @ClassName: FileStoreController * @Description: 文件仓库控制器 **/@Controllerpublic class FileStoreController extends BaseController { private Logger logger = LogUtils.getInstance(FileStoreController.class); /** * @Description 上传临时文件 * @Author xw * @Date 23:14 2020/3/9 * @Param [files] * @return void **/ @PostMapping("/uploadTempFile") 原文链接:https://blog.csdn.net/m0_66863468/article/details/122853352
-
一、前言介绍: 随着社会的快速发展,计算机的影响是全面且深入的。人们生活水平的不断提高,日常生活中用户对手办周边商城方面的要求也在不断提高,而漫威系列手办周边商城得到广大用户的青睐,使得漫威系列手办周边商城的开发成为必需而且紧迫的事情。漫威系列手办周边商城主要是借助计算机,通过对漫威系列手办周边商城所需的信息管理,增加用户的选择,同时也方便对广大用户信息的及时查询、修改以及对用户信息的及时了解。手办周边商城对用户带来了更多的便利,该系统通过和数据库管理系统软件协作来满足用户的需求。计算机技术在现代管理中的应用,使计算机成为人们应用现代技术的重要工具。能够有效的解决获取信息便捷化、全面化的问题,提高效率。 二、功能设计: 本漫威系列手办周边商城系统主要功能设计为实现管理员;个人中心、系统公告管理、用户管理、商品系列管理、商品信息管理、订单评价管理、论坛管理、系统管理、订单管理,用户;个人中心、订单评价管理、我的收藏管理、订单管理,前台首页;首页、商品信息、论坛信息、我的、跳转到后台、购物车客服等信息管理功能。系统操作流程如下:三、功能截图: 普通用户登录注册: 首页主要功能介绍:商品信息、论坛信息、我的、跳转到后台、购物车客服等信息管理功能 商品详情:可加入购物车、修改数量、立即购买和收藏商品商品详情可以配置图文介绍等 商品评论交流模块 添加了论坛帖子系列功能:普通用户可以发布、可以对帖子进行交流讨论等 帖子详情数据查看、可以在底部评论 在我的个人中心可以查看我的发布、我的订单、我的地址修改以及我的收藏模块和个人信息模块 购物车模块:要选择收货地址信息等 我的订单模块: 我的收藏模块:普通用户后台中心: 订单评价: 收藏管理: 订单信息管理:四、 后端主要功能:修改密码: 用户管理: 商品系列管理: 商品信息管理: 商品详情修改: 商品评价管理: 论坛管理:客服管理: 首页轮播图管理: 订单信息管理:五、数据库设计:表address (地址)编号名称数据类型长度小数位允许空值主键1idbigint200NY2addtimetimestamp190NN3useridbigint200NN4addressvarchar2000NN5namevarchar2000NN6phonevarchar2000NN7isdefaultvarchar2000NN表cart (购物车表)编号名称数据类型长度小数位允许空值主键1idbigint200NY2addtimetimestamp190NN3tablenamevarchar2000YN4useridbigint200NN5goodidbigint200NN6goodnamevarchar2000YN7picturevarchar2000YN8buynumberint100NN9pricefloat130YN10discountpricefloat130YN表chat (客服聊天表)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3useridbigint200NN用户id4adminidbigint200YN管理员id5asklongtext21474836470YN提问6replylongtext21474836470YN回复7isreplyint100YN是否回复表config (配置文件)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2namevarchar1000NN配置参数名称3valuevarchar1000YN配置参数值表dingdanpingjia (订单评价)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3dingdanbianhaovarchar2000NN订单编号4shangpinbianhaovarchar2000YN商品编号5shangpinmingchengvarchar2000NN商品名称6shangpinxilievarchar2000YN商品系列7pingfenvarchar2000YN评分8pingjianeironglongtext21474836470YN评价内容9tianjiatupianvarchar2000YN添加图片10pingjiariqidate100YN评价日期11yonghumingvarchar2000YN用户名12lianxidianhuavarchar2000YN联系电话13sfshvarchar2000YN是否审核14shhflongtext21474836470YN审核回复表discussshangpinxinxi (商品信息评论表)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3refidbigint200NN关联表id4useridbigint200NN用户id5contentlongtext21474836470NN评论内容6replylongtext21474836470YN回复内容表discussxitonggonggao (系统公告评论表)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3refidbigint200NN关联表id4useridbigint200NN用户id5contentlongtext21474836470NN评论内容6replylongtext21474836470YN回复内容表forum (论坛表)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3titlevarchar2000YN帖子标题4contentlongtext21474836470NN帖子内容5parentidbigint200YN父节点id6useridbigint200NN用户id7usernamevarchar2000YN用户名8isdonevarchar2000YN状态表orders (订单)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3orderidvarchar2000NN订单编号4tablenamevarchar2000YN商品表名5useridbigint200NN用户id6goodidbigint200NN商品id7goodnamevarchar2000YN商品名称8picturevarchar2000YN商品图片9buynumberint100NN购买数量10pricefloat130NN价格/积分11discountpricefloat130YN折扣价格12totalfloat130NN总价格/总积分13discounttotalfloat130YN折扣总价格14typeint100YN支付类型15statusvarchar2000YN状态16addressvarchar2000YN地址表shangpinxilie (商品系列)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3shangpinxilievarchar2000NN商品系列表shangpinxinxi (商品信息)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3shangpinbianhaovarchar2000YN商品编号4shangpinmingchengvarchar2000NN商品名称5shangpinxilievarchar2000YN商品系列6tupianvarchar2000YN图片7guigevarchar2000YN规格8shangpinxiangqinglongtext21474836470YN商品详情9clicktimedatetime190YN最近点击时间10clicknumint100YN点击次数11pricefloat130NN价格表storeup (收藏表)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3useridbigint200NN用户id4refidbigint200YN收藏id5tablenamevarchar2000YN表名6namevarchar2000NN收藏名称7picturevarchar2000NN收藏图片表token (token表)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2useridbigint200NN用户id3usernamevarchar1000NN用户名4tablenamevarchar1000YN表名5rolevarchar1000YN角色6tokenvarchar2000NN密码7addtimetimestamp190NN新增时间8expiratedtimetimestamp190NN过期时间表users (用户表)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2usernamevarchar1000NN用户名3passwordvarchar1000NN密码4rolevarchar1000YN角色5addtimetimestamp190NN新增时间表xitonggonggao (系统公告)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3gonggaobiaotivarchar2000NN公告标题4gonggaoleixingvarchar2000NN公告类型5tupianvarchar2000YN图片6neironglongtext21474836470YN内容7faburiqidate100YN发布日期表yonghu (用户)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3yonghumingvarchar2000NN用户名4mimavarchar2000NN密码5xingmingvarchar2000NN姓名6xingbievarchar2000YN性别7touxiangvarchar2000YN头像8lianxidianhuavarchar2000YN联系电话9moneyfloat130YN余额六、关键代码: /** * 登录相关 */@RequestMapping("users")@RestControllerpublic class UserController{ @Autowired private UserService userService; @Autowired private TokenService tokenService; /** * 登录 */ @IgnoreAuth// @PostMapping(value = "/login") @RequestMapping(value = "/login", method ={RequestMethod.GET,RequestMethod.POST} ) public R login(String username, String password, String captcha, HttpServletRequest request) { UserEntity user = userService.selectOne(new EntityWrapper<UserEntity>().eq("username", username)); if(user==null || !user.getPassword().equals(password)) { return R.error("账号或密码不正确"); } String token = tokenService.generateToken(user.getId(),username, "users", user.getRole()); return R.ok().put("token", token); } /** * 注册 */ @IgnoreAuth @PostMapping(value = "/register") public R register(@RequestBody UserEntity user){// ValidatorUtils.validateEntity(user); if(userService.selectOne(new EntityWrapper<UserEntity>().eq("username", user.getUsername())) !=null) { return R.error("用户已存在"); } userService.insert(user); return R.ok(); } /** * 退出 */ @GetMapping(value = "logout") public R logout(HttpServletRequest request) { request.getSession().invalidate(); return R.ok("退出成功"); } /** * 密码重置 */ @IgnoreAuth @RequestMapping(value = "/resetPass") public R resetPass(String username, HttpServletRequest request){ UserEntity user = userService.selectOne(new EntityWrapper<UserEntity>().eq("username", username)); if(user==null) { return R.error("账号不存在"); } user.setPassword("123456"); userService.update(user,null); return R.ok("密码已重置为:123456"); } /** * 列表 */ @RequestMapping("/page") public R page(@RequestParam Map<String, Object> params,UserEntity user){ EntityWrapper<UserEntity> ew = new EntityWrapper<UserEntity>(); PageUtils page = userService.queryPage(params, MPUtil.sort(MPUtil.between(MPUtil.allLike(ew, user), params), params)); return R.ok().put("data", page); } /** * 列表 */ @RequestMapping("/list") public R list( UserEntity user){ EntityWrapper<UserEntity> ew = new EntityWrapper<UserEntity>(); ew.allEq(MPUtil.allEQMapPre( user, "user")); return R.ok().put("data", userService.selectListView(ew)); } /** * 信息 */ @RequestMapping("/info/{id}") public R info(@PathVariable("id") String id){ UserEntity user = userService.selectById(id); return R.ok().put("data", user); } /** * 获取用户的session用户信息 */ @RequestMapping("/session") public R getCurrUser(HttpServletRequest request){ Long id = (Long)request.getSession().getAttribute("userId"); UserEntity user = userService.selectById(id); return R.ok().put("data", user); } /** * 保存 */ @PostMapping("/save") public R save(@RequestBody UserEntity user){// ValidatorUtils.validateEntity(user); if(userService.selectOne(new EntityWrapper<UserEntity>().eq("username", user.getUsername())) !=null) { return R.error("用户已存在"); } userService.insert(user); return R.ok(); } /** * 修改 */ @RequestMapping("/update") public R update(@RequestBody UserEntity user){// ValidatorUtils.validateEntity(user); userService.updateById(user);//全部更新 return R.ok(); } /** * 删除 */ @RequestMapping("/delete") public R delete(@RequestBody Long[] ids){ userService.deleteBatchIds(Arrays.asList(ids)); return R.ok(); }}<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置数据源 --> <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc_url}"/> <property name="username" value="${jdbc_username}"/> <property name="password" value="${jdbc_password}"/> <!-- 初始化连接大小 --> <property name="initialSize" value="0"/> <!-- 连接池最大使用连接数量 --> <property name="maxActive" value="20"/> <!-- 连接池最大空闲 --> <property name="maxIdle" value="20"/> <!-- 连接池最小空闲 --> <property name="minIdle" value="0"/> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="60000"/> <property name="validationQuery" value="${validationQuery}"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <property name="testWhileIdle" value="true"/> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="25200000"/> <!-- 打开removeAbandoned功能 --> <property name="removeAbandoned" value="true"/> <!-- 1800秒,也就是30分钟 --> <property name="removeAbandonedTimeout" value="1800"/> <!-- 关闭abanded连接时输出错误日志 --> <property name="logAbandoned" value="true"/> <!-- 监控数据库 --> <property name="filters" value="mergeStat"/> </bean> <!-- Spring整合Mybatis,更多查看文档:http://mp.baomidou.com --> <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- 自动扫描Mapping.xml文件 --> <property name="mapperLocations" value="classpath:mapper/*.xml"/> <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/> <property name="typeAliasesPackage" value="com..model.*"/> <property name="typeEnumsPackage" value="com.model.enums"/> <property name="plugins"> <array> <!-- 分页插件配置 --> <bean id="paginationInterceptor" class="com.baomidou.mybatisplus.plugins.PaginationInterceptor"> </bean> </array> </property> <!-- 全局配置注入 --> <property name="globalConfig" ref="globalConfig" /> </bean> <bean id="globalConfig" class="com.baomidou.mybatisplus.entity.GlobalConfiguration"> <!-- AUTO->`0`("数据库ID自增") INPUT->`1`(用户输入ID") ID_WORKER->`2`("全局唯一ID") UUID->`3`("全局唯一ID") --> <property name="idType" value="2" /> <!-- MYSQL->`mysql` ORACLE->`oracle` DB2->`db2` H2->`h2` HSQL->`hsql` SQLITE->`sqlite` POSTGRE->`postgresql` SQLSERVER2005->`sqlserver2005` SQLSERVER->`sqlserver` --> <!-- Oracle需要添加该项 --> <!-- <property name="dbType" value="oracle" /> --> <!-- 全局表为下划线命名设置 true --> <!-- <property name="dbColumnUnderline" value="true" /> --> <property name="metaObjectHandler"> <bean class="com.config.MyMetaObjectHandler" /> </property> </bean> <!-- MyBatis 动态扫描 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.dao"/> </bean> <!-- 配置事务管理 --> <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 事务管理 属性 --> <tx:advice id="transactionAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED"/> <tx:method name="append*" propagation="REQUIRED"/> <tx:method name="save*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="modify*" propagation="REQUIRED"/> <tx:method name="edit*" propagation="REQUIRED"/> <tx:method name="insert*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="remove*" propagation="REQUIRED"/> <tx:method name="repair" propagation="REQUIRED"/> <tx:method name="get*" propagation="REQUIRED" read-only="true"/> <tx:method name="find*" propagation="REQUIRED" read-only="true"/> <tx:method name="load*" propagation="REQUIRED" read-only="true"/> <tx:method name="search*" propagation="REQUIRED" read-only="true"/> <tx:method name="datagrid*" propagation="REQUIRED" read-only="true"/> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!-- 配置切面 --> <aop:config> <aop:pointcut id="transactionPointcut" expression="execution(* com.service..*.*(..))"/> <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice"/> </aop:config> </beans>七、论文报告: 八、源码获取:大家点赞、收藏、关注、评论啦 、查看微信公众号获取联系方式打卡 文章 更新 200/ 365天 精彩专栏推荐订阅:在下方专栏Java项目精品实战案例《100套》web前端期末大作业网页实战《100套》原文链接:https://blog.csdn.net/weixin_39709134/article/details/122967896
-
作为一名开发者,免不了要和数据库打交道,于是我们就需要一款顺手的数据库管理工具。很长一段时间里,Navicat 都是我的首选,但最近更换了一台新电脑,之前的绿色安装包找不到了。于是就琢磨着,找一款免费的,功能和 Navicat 有一拼的数据库管理工具来替代。好朋友 macrozheng 给我推荐了 DBeaver,试用完后体验真心不错,于是就来给大家安利一波。一、关于 DBeaverDBeaver 是一个跨平台的数据库管理工具,支持 Windows、Linux 和 macOS。它有两个版本,企业版和社区版,对于个人开发者来说,社区版的功能已经足够强大。DBeaver 是由 Java 编写的,默认使用 JDK 11 进行编译。社区版基于 Apache-2.0 License 在 GitHub 上开源,目前已获得 24k+ 的星标。https://github.com/dbeaver/dbeaverDBeaver 支持几乎所有主流的数据库,包括关系型数据库和非关系数据库。二、安装 DBeaver可以通过 DBeaver 官方下载安装包,也可以通过 GitHub 下载 release 版本。官方下载地址:https://dbeaver.io/download/根据自己电脑的操作系统下载对应的安装包,完整安装后,第一步要做的是配置 Maven 镜像,否则在后续下载数据库驱动的时候会非常的慢。因为 DBeaver 是基于 Maven 构建的,数据库驱动也就是链接数据库的 JDBC 驱动是通过 Maven 仓库下载的。选择「首选项」→「Maven」,添加阿里云镜像地址:http://maven.aliyun.com/nexus/content/groups/public和配置 Maven 镜像一样,如下图所示。配置完成后,记得把阿里云镜像仓库置顶。三、管理数据源像使用 Navicat 一样,我们需要先建立连接,这里就以 MySQL 为例。点击「连接」小图标,选择数据库。点击下一步,这时候需要填写数据库连接信息。点击「测试链接」,如果使用默认的 Maven 仓库时,下载驱动会非常慢,如下图所示,还容易失败「踩过的坑就不要再踩了」。如果你前面按照我说的配置了阿里云的 Maven 镜像,程序就不一样了,点了「测试链接」,瞬间会弹出「连接已成功」的提示框。链接成功后,就可以看到数据库中的表啊、视图啊、索引啊等等。四、管理表数据库连接成功后,最重要的还是操作表。01、查看表选择一张表,双击后就可以看到表的属性了,可以查看表的列、约束(主键)、外键、索引等等信息。点击「DDL(Data Definition Language,数据定义语言)」可以看到详细的建表语句。点击「数据」可以查看表的数据,底部有「新增」、「修改」、「删除」等行操作按钮。可以在顶部的过滤框中填写筛选条件,然后直接查询结果。如果不想显示某一列的话,可以直接点击「自定义结果集」图表,将某个字段的状态设置为不可见即可。02、新增表在左侧选择「表」,然后右键选择「新建表」即可建表id。之后在右侧列的区域右键,选择「新建列」即可添加字段。比如说我们新建一个主键 ID,如下图所示。在 DBeaver 中,[v] 表示真,[] 表示否。紧接着在「约束」里选择 ID 将其设置为主键。最后点击保存,会弹出一个建表语句的预览框,点击「执行」即可完成表的创建。五、执行 SQL右键数据库表,选择右键菜单中的「SQL 编辑器」可以打开 SQL 编辑面板。然后编辑 SQL 语句,点击运行的小图标就可以查询数据了。这个过程会有语法提示,非常 nice。DBeaver 有一个很亮眼的操作就是,可以直接选中一条结果集,然后右键生成 SQL。比如说 insert 语句,这样再插入一条重复性内容的时候就非常方便了。六、外观配置可以在首选项里对外观进行设置,比如说把主题修改为暗黑色。然后界面就变成了暗黑系。还可以设置字体大小等。从整体的风格来看,DBeaver 和 Eclipse 有些类似,事实上也的确如此,DBeaver 是基于 Eclipse 平台构建的。七、总结总体来说,DBeaver是一款非常优秀的开源数据库管理工具了,功能很全面,日常的开发基本上是够用了。对比收费的 Navicat 和 DataGrip,可以说非常良心了。大家如果遇到收费版不能使用的时候,可以来体验一下社区版 DBeaver。本篇已收录至 GitHub 上星标 1.5k+ star 的开源专栏《Java 程序员进阶之路》,据说每一个优秀的 Java 程序员都喜欢她,风趣幽默、通俗易懂。内容包括 Java 基础、Java 并发编程、Java 虚拟机、Java 企业级开发、Java 面试等核心知识点。学 Java,就认准 Java 程序员进阶之路。https://github.com/itwanger/toBeBetterJavaerstar 了这个仓库就等于你拥有了成为了一名优秀 Java 工程师的潜力。也可以戳下面的链接跳转到《Java 程序员进阶之路》的官网网址,开始愉快的学习之旅吧。https://tobebetterjavaer.com/没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。————————————————版权声明:本文为CSDN博主「沉默王二」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qing_gee/article/details/122975576
-
弃用 Java 8,最低要求 Java 17; Java EE 迁移到了 Jakarta EE; 看到这个心理慌的要命........
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签