• [技术干货] SpringMVC 域对象共享数据-转载
     在Spring MVC中,可以使用域对象来共享数据。域对象是一个Map类型的对象,可以在请求处理方法之间共享数据。 数据共享可以实现数据在不同组件、请求或模块之间的传递和共享,以方便数据的处理和展示,提高应用程序的性能和灵活性。 SpringMVC主要提供了一下几种方式来达到数据共享: 1、使用ServletAPI向request域对象共享数据 在处理请求的方法中将数据存储到了HttpServletRequest对象的request域中,使用setAttribute方法。"myData"是数据的键,"data"是数据的值。  示例代码: // 获取要共享的数据 // 将数据存储在request域对象中 // 转发请求至其他Servlet或JSP页面  protected void doGet(HttpServletRequest request, HttpServletResponse response) {         String data = "Hello, World!";          request.setAttribute("myData", c);          RequestDispatcher dispatcher =     request.getRequestDispatcher("/path/to/your/servlet-or-jsp");     dispatcher.forward(request, response); } 2、使用ModelAndView向request域对象共享数据 ModelAndView对象是一个包含数据模型和视图信息的容器,在控制器方法中可以将需要共享的数据存储到ModelAndView对象中,然后将该对象返回给Spring MVC框架。  创建一个ModelAndView对象,并使用addObject方法将数据存储到该对象的模型中。"key"是数据的键,“value"是数据的值。然后,使用setViewName方法设置视图信息,指定要渲染的视图名为"example-view”。  然后,Spring MVC框架会将ModelAndView对象中的数据传递给视图,并在视图渲染时将数据存储到request域对象中。在视图中可以使用相应的表达式语言(如JSTL或Thymeleaf的EL表达式)来获取并展示存储在request域中的数据。  @RequestMapping("/testModelAndView") public ModelAndView testModelAndView(){     /**      * ModelAndView有Model和View的功能      * Model主要用于向请求域共享数据      * View主要用于设置视图,实现页面跳转      */     ModelAndView mav = new ModelAndView();     //向请求域共享数据     mav.addObject("testScope", "hello,ModelAndView");     //设置视图,实现页面跳转     mav.setViewName("success");     return mav; } 建议尽量在实际开发中使用更现代和推荐的方式,例如使用@ModelAttribute注解或在方法参数中使用Model对象,以更加简洁和便捷地向request域对象共享数据。  3、使用Model向request域对象共享数据 Model是一个接口,它可以在控制器方法的参数中声明,并使用它的方法将数据添加到其中,然后这些数据将自动传递到request域中。  @RequestMapping("/testModel") public String testModel(Model model){     model.addAttribute("testScope", "hello,Model");     return "success"; } 4、使用map向request域对象共享数据 在控制器方法的参数中声明一个Map参数,Spring MVC会自动将一个Map对象注入到该参数中。您可以向这个Map对象添加键值对,然后它们将自动传递到request域中。  @RequestMapping("/testMap") public String testMap(Map<String, Object> map){     map.put("testScope", "hello,Map");     return "success"; } 当控制器方法返回视图名称时,Spring MVC框架会自动将Map对象中的数据传递到request域中,并在渲染视图时可以使用这些数据。例如,如果您使用JSP作为视图技术,您可以在JSP文件中使用EL表达式${key}来获取存储在request域中的数据  5、使用ModelMap向request域对象共享数据 ModelMap是一个具体实现了Model接口的类,它提供了更多的便捷方法来添加和访问数据。  @RequestMapping("/testModelMap") public String testModelMap(ModelMap modelMap){     modelMap.addAttribute("testScope", "hello,ModelMap");     return "success"; } 当控制器方法返回视图名称时,Spring MVC框架会自动将ModelMap对象中的数据传递到request域中,并在渲染视图时可以使用这些数据。例如,如果您使用JSP作为视图技术,您可以在JSP文件中使用EL表达式${key}来获取存储在request域中的数据。  6、Model、ModelMap、Map的关系 Model、ModelMap、Map类型的参数其实本质上都是 BindingAwareModelMap 类型的  public interface Model{} public class ModelMap extends LinkedHashMap<String, Object> {} public class ExtendedModelMap extends ModelMap implements Model {} public class BindingAwareModelMap extends ExtendedModelMap {} 7、向session域共享数据 在控制器类上使用@SessionAttributes注解并指定了"myData"作为需要存储在Session域中的模型属性。在example方法中,我们使用model.addAttribute方法将数据添加到"myData"模型属性中。  @RequestMapping("/testSession") public String testSession(HttpSession session){     session.setAttribute("myData", "hello,session");     return "success"; } 8、向application域共享数据 控制器方法的参数中声明了一个ServletContext对象,并将其命名为"servletContext"。然后,我们使用setAttribute方法将数据添加到ServletContext对象中,"key"是数据的键,"value"是数据的值。  数据将被存储在Application域中,可以在整个应用程序中访问和共享。  @RequestMapping("/testApplication") public String testApplication(HttpSession session){     ServletContext application = session.getServletContext();     application.setAttribute("testApplicationScope", "hello,application");     return "success"; } 9、总结 本文主要讲解了以下几种域对象可以使用: request域对象:通过使用HttpServletRequest对象的setAttribute方法向request域中存储数据,使用getAttribute方法从request域中获取数据。 session域对象:通过使用HttpSession对象的setAttribute方法向session域中存储数据,使用getAttribute方法从session域中获取数据。 application域对象:通过使用ServletContext对象的setAttribute方法向application域中存储数据,使用getAttribute方法从application域中获取数据。 这些域对象都可以在控制器中使用,可以在不同的请求处理方法之间共享数据。例如,一个请求处理方法可以将数据存储到request域中,然后另一个请求处理方法可以从request域中获取这些数据。 ———————————————— 版权声明:本文为CSDN博主「我有一颗五叶草」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_60915009/article/details/132835848 
  • [技术干货] JAVA中的泛型-转载
    如下是定义了一个类,这个类中可以存放一组 int 类型的数据。 class Number{     int[] arr;       public Number(int a) {         this.arr = new int[10];         this.arr[0] = a;     }       public void setArr(int n, int data) {         this.arr[n] = data;     }       public int getArr(int n) {         return arr[n];     } } 可是虽然这样写没有问题可是它的应用范围实在是太小了,它只能存储 int 类型的数据。如果我们现在想让这个类中可以存放任何类型的数据应该怎么做呢? 在 JAVA 中利用泛型就可以实现这点。 泛型 泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化。  泛型的语法: class 泛型类名称<类型形参列表> { }  class ClassName<T1, T2, ..., Tn> { } 类名后的 <T> 代表占位符,表示当前类是一个泛型类。 类型形参一般使用一个大写字母表示,常用的名称有: E 表示 Element K 表示 Key V 表示 Value N 表示 Number T 表示 Type S, U, V 等等 - 第二、第三、第四个类型 泛型类   我们将开始的代码进行改进:  class Number<T>{     Object[] arr;//这里可以暂时忽略Object,下文会说       public Number(T a) {         this.arr = new Object[10];         this.arr[0] = a;     }       public void setArr(int n, T data) {         this.arr[n] = data;     }       public T getArr(int n) {         return (T)arr[n];     } }  在 JAVA 中由于数组比较特殊,不能new泛型类型的数组,数组在new的时候必须要指定其类型所以如果定义成这样就会报错  。 为了不让它报错就需要进行强制类型转换因为 Object类 是所有类的父类所以也可以这样定义: 虽然这样写编译器没有报错可是还是报了警告,也就是说我们这样写会存在安全隐患。 这里推荐将数组定义成这样: 调用的时候需要注意: 如果是简单类型(如:int , char……)必须要传对应的包装类。 基本数据类型和对应的包装类  基本数据类型    包装类 byte    Byte short    Short int    Integer long    Long float    Float double    Double char    Character boolean    Boolean 这个表看起来多其实除了 Integer 和 Character, 其余基本类型的包装类都是首字母大写。 如图,此时我们的类泛用性就非常高了,我们可以使用 main 方法测试一下:     public static void main(String[] args) {         Number<Integer> a = new Number<>(3);         Number<Double> b = new Number<>(3.14);         Number<String> c = new Number<>("hello");         System.out.println(a.getArr(0));         System.out.println(b.getArr(0));         System.out.println(c.getArr(0));              } 泛型方法:  泛型除了可以应用于类之外也可以应用于方法。  语法: 方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }      public static <T> void swap(T[] array, int i, int j) {         T t = array[i];         array[i] = array[j];         array[j] = t;     }  泛型方法也和类一样不能使用基本类型,需要使用对应的包装类。 我们可以测试一下:      public static void main(String[] args) {         Integer[] b = {1,2,3,4};         swap(b, 0, 1);         System.out.println(Arrays.toString(b));     } 小结: 泛型类<类型实参> 变量名; // 定义一个泛型类引用 new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象,此时可以省略类型实参  Number<Integer> a = new Number<>(3); Number<Integer> list = new Number<Integer>(3); 泛型是将数据类型参数化,进行传递 使用 <T> 表示当前类是一个泛型类。 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换  ———————————————— 版权声明:本文为CSDN博主「休息一下…」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/2302_76339343/article/details/132867383 
  • [技术干货] MySQL常见面试题(2023年最新)-转载
     前言 java最新面试题(java基础、集合、多线程、jvm、锁、算法、CAS、Redis、数据库、mybatis、spring、springMVC、springBoot、微服务)  1.char和varchar的区别 ①char设置多少长度就是多少长度,varchar可以改变长度,所以char的空间利用率不如varchar的空间利用率高。 ②因为长度固定,所以存取速度要比varchar快。 ③char适用于固定长度的字符串,比如身份证号、手机号等,varchar适用于不固定的字符串。  2.数据库的三大范式 第一范式(1NF): 保证字段不可再分,保证原子性。 第二范式(2NF): 满足1NF前提下,表的每一列都必须和主键有关系。消除部分依赖关系。 第三范式(3NF): 满足2NF前提下,表的每一列比必须和主键有直接关系,不能是间接关系。消除传递依赖  3.你了解sql的执行顺序吗? ⑧select ⑨distinct(去重) ⑥聚合函数  ①from 表1  ③[inner join | left join | right join](连接) 表2  ②on(连接条件) 表1.字段 = 表2.字段  ④where 查询条件  ⑤group by(分组) 字段  ⑦having 分组过滤条件  ⑩order by(排序) 字段  ⑪limit(分页) 0,10  4.索引是什么 是一种高效获取数据的数据结构,相当于目录,更快的找到数据,是一个文件,占用物理空间。  5.索引的优点和缺点 优点: ①提高检索的速度。 ②索引列对数据排序,降低排序成本。 ③mysql 8之后引入了,隐藏索引,当一个索引被隐藏就不会被优化器所使用,就可以看出来索引对数据库的影响,有利于调优。 缺点: ①索引也是一个文件,所以会占用空间。 ②降低更新的速度,因为不光要更新数据,还要更新索引。  6.索引的类型 ①普通索引: 基本索引类型,允许定义索引的字段为空值和重复值。 ②唯一索引: 索引的值必须唯一,允许定义索引的字段为空值。 ③主键索引: 索引的值必须唯一,不可以为空。 ④复合索引: 多个字段加索引,遵守最左匹配规则。 ⑤全局索引: 只有在 MyISAM 引擎上才能使用。  7.索引怎么设计(优化) ①选择唯一性索引:值是唯一的,查询的更快。 ②经常作为查询条件的字段加索引。 ③为经常需要排序、分组和联合操作的字段建立索引:order by、group by、union(联合)、distinct(去重)等。 ④限制索引个数:索引数量多,需要的磁盘空间就越多,更新表时,对索引的重构和更新就很费劲。 ⑤表数据少的不建议使用索引(百万级以内):数据过少,有可能查询的速度,比遍历索引的速度都快。 ⑥删除不常用和不再使用的索引。 ⑦用类型小的类型做索引:比如:int和BIGINT能用int就使用int。因为类型小,查询速度快和索引占用的空间更少。 ⑧使用前缀索引,要是字符串越长,那么索引占的空间越大,并且比较起来就时间就越长。  8.怎么避免索引失效(也属于sql优化的一种) ①某列使用范围查询(>、<、like、between and)时, 右边的所有列索引也会失效。 ②不要对索引字段进行运算。 ③在where子句中不要使用 OR、!=、<>和对值null的判断。 ④避免使用’%'开头的like的模糊查询。 ⑤字符串不加单引号,造成索引失效。  9.索引的数据类型 Hash: 查询时调用Hash函数获得地址,回表查询实际数据。(InnoDB和MylSAM不支持,Memory支持)。 B+树: 每次从根节点出发去查询,然后得到地址,回表查询实际数据。  10.索引为什么使用树结构 因为可以加快查询效率,而且可以保持有序。  11.二叉查找树、B树、B+树 二叉查找树(二叉排序树、二叉搜索树): 一个节点最多两个子节点(左小右大),查询次数和比较次数都是最小的,但是索引是存在磁盘的,当数据量过大的时候,不能直接把整个索引文件加载到内存,需要分多次IO,最坏的情况IO的次数就是树的高度,为了减少IO,需要把树从竖向变成横向。 B树( B- ): 是一种多路查询树,每个节点包含K个子节点,节点都存储索引值和数据,K是B树的阶(树高被称为树的阶)。虽然比较的次数比较多,但是是在内存的比较,可以忽略不计,但是B树IO的次数要比二叉查找树要少,因为B树的高度可以更低。 B+树: B树的升级版,只有叶子节点储存的是索引值指向的数据库的数据。  12.为什么使用B+树不用B树 ①B树只适合随机检索,而B+树同时支持随机检索和顺序检索(因为叶子节点相当于链表,保存索引值都是有序的)。 顺序检索: 按照序列顺序遍历比较找到给定值。 随机检索: 不断从序列中随机抽取数据进行比较,最终找到结果。  ②减少了磁盘IO,提高空间利用率: 因为B+树非叶子节点不会存放数据,只有索引值,所以非叶子节点可以保存更多的索引值,这样B+树就可以更矮,减少IO次数。  ③B+树适合范围查找: 这才是关键,因为数据库大部分都是范围查找,B+树的叶子节点是有序链表,直接遍历就行,而B树的范围查找可能两个节点距离很远,只能通过中序遍历去查找,所以使用B+树更合适。 中序遍历: (根在中,从左往右,一棵树的左子树永远在根前面,根永远在右子树前面)  13.最左匹配原则 最左优先,以最左边为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between and、like)就会停止匹配。 例如:Z表建立联合索引 (a,b,c) //这样索引abc列都起效,因为符合最左匹配原则,where子句几个搜索条件顺序调换不影响查询结果,因为Mysql中有查询优化器,会自动优化查询顺序 select  *  from Z where a = 1 and b = 2 and c = 3   //因为a列是起点,没有a列匹配不上,所以索引失效 select * from table_name where  b = 2 and c = 3   //因为连续不到b,所以只有a列索引生效 select * from table_name where  a = 1 and c = 3  14.Mysql怎么查看是否使用到索引或怎么查看sql执行计划 使用explain  例如:explain select * from 表名 where 条件 结果:会查出key,key就是你使用的索引。还有type这个字段,可以看到索引是全表扫描还是索引扫描等等。 type字段内容性能对比:ALL < index < range ~ index_merge < ref < eq_ref < const < system  15.一条sql查询非常慢,我们怎么去排查和优化? 排查: (1) 开启慢查询。 (2) 查看慢查询日志(定位低效率sql,命令:show processlist)。 (3) 使用explain查看sql的执行计划(看看索引是否失效或者性能低)  优化: sql优化 + 索引 + 数据库结构优化 + 优化器优化  16.MylSAM和InnoDB、Memory的区别 MylSAM: mysql5.5之前的存储引擎,是表锁(悲观锁)级别的,不支持事务和外键。 InnoDB: mysql5.5之后的存储引擎,是行锁(乐观锁)级别的,支持事务和外键。 Memory: 内存数据库引擎,因为在内存操作,所以读写很快,但是Mysql服务重启,会丢失数据,不支持事务和外键。  17.什么是事务 事务和隔离级别详解及实际应用  事务是对数据库中一系列操作进行统一的回滚或者提交的操作,主要用来保证数据的完整性和一致性。  18.事务的四大特性(ACID) 原子性(Atomicity): 要么全部成功要么全部失败。 一致性(Consistency): 事务执行前和事务执行后,原本和数据库一致的数据仍然一致。 隔离性(Isolation): 事务与事务之间互不干扰。 持久性(Durability): 事务一旦被提交了,那么对数据库中的数据的改变就是永久的。  19.脏读、不可重复读、幻读 脏读: 也叫"读未提交",顾名思义,就是某一事务A读取到了事务B未提交的数据。 不可重复读: 在一个事务内,多次读取同一个数据,却返回了不同的结果。实际上,这是因为在该事务间隔读取数据的期间,有其他事务对这段数据进行了修改,并且已经提交,就会发生不可重复读事故。 幻读: 在同一个事务中,第一次读取到结果集和第二次读取到的结果集不同。像幻觉一样所以叫幻读。 从上面可以看出脏读和不可重复读是基于数据值的错误,幻读是基于条数增加或者减少的错误  20.事务的隔离级别? ① read uncommited(读取未提交内容): 在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。读取未提交的数据,也被称之为脏读(Dirty Read) ② read committed(读取提交内容): 这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。一个事务只能看见已经提交事务所做的改变。可解决脏读 ③ repeatable read(可重读): 这是MySQL的默认事务隔离级别,同一事务的多个实例在并发读取数据时,会看到同样的数据。不过理论上,这会导致另一个棘手的问题:幻读(Phantom Read)。可解决脏读、不可重复读 ④ serializable(可串行化) : 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。可解决脏读、不可重复读、幻读。  21.怎么优化数据库 ①SQL优化 ②加缓存 ③分表 ④读写分离  22.SQL优化 ①不要用select *,要使用具体字段。 ②使用数值代替字符串,比如:0=唱,1=跳,2=rap。 ③避免返回大量数据,采用分页最好。 ④使用索引,提升查询速度,不宜建太多索引,不能建在重复数据比较多的字段上。 ⑤批量插入比单条插入要快,因为事务只需要开启一次,数据量太小体现不了。 ⑥避免子查询,优化为多表连接查询。 ⑦尽量使用union all替代union,因为union会自动去重。  23.常用的聚合函数 ①sum(列名) 求和      ②max(列名) 最大值      ③min(列名) 最小值      ④avg(列名) 平均值      ⑤first(列名) 第一条记录 ⑥last(列名) 最后一条记录 ⑦count(列名) 统计记录数不包含null值 count(*)包含null值。  24.几种关联查询 内连接(inner join): 查询两个表匹配数据。 左连接(left join): 查询左表全部行以及右表匹配的行。 右连接(right join): 查询右表全部行以及左表匹配的行。  25.in和exists的区别 in(): 适合子表(子查询)比主表数据小的情况。 exists(): 适合子表(子查询)比主表数据大的情况。  26.drop、truncate、delete的区别 速度: drop > truncate > delete。 回滚: delete支持,truncate和drop不支持。 删除内容: delete表结构还在,删除部分或全部数据,不释放空间。truncate表结构还在,删除全部数据,释放空间。drop表结构和数据不在,包括索引和权限,释放空间。 ———————————————— 版权声明:本文为CSDN博主「爱穿背带裤的馫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/twotwo22222/article/details/129115194 
  • [技术干货] 【JAVA】Object类与抽象类-转载
     Object类 Object类是java中默认提供的一个类。java里面除了Object类,其他全部的类都是会默认继承Object类的。所以所有的类的对象都可以用Object类的引用来接收。 举个栗子: class Person{    } class Student{     } public class Test {     public static void function(Object obj) {     System.out.println(obj); }     public static void main(String[] args) {         function(new Person());         function(new Student());     }      } 在java开发的过程中,Objec类是参数的最高统一类型。但是Object类也存在有定义好的一些方法。  这些都是Object类定义好的方法: 这几个方法对于java学习来说是必须掌握,不可缺少的。 获取对象信息toString()方法 如果要打印对象中的内容,直接重写Object类的toSting方法即可: // Object类中的toString()方法实现:     public String toString() {         return getClass().getName() + "@" + Integer.toHexString(hashCode());     } 其中的道理就是:object类是所有类的父类,重写了tostring方法后,在使用print方法时就会调用我们重写的toString方法以达到效果。 对象比较equals方法 在java中,==进行比较时: 要是两个比较对象是基本类型时,比较的是变量值是否相等 要是两个比较对象是引用类型变量时,比较的是引用地址是否相等 如果要比较对象中的内容,就必须重写Object类的equals方法,equals方法默认是按地址来比较的 // Object类中的equals方法     public boolean equals(Object obj) {         return (this == obj); // 使用引用中的地址直接来进行比较     } class Person{     private String name ;     private int age ;     public Person(String name, int age) {         this.age = age ;         this.name = name ;     } } public class Test {     public static void main(String[] args) {         Person p1 = new Person("qeo", 20) ;         Person p2 = new Person("qeo", 20) ;         int a = 10;         int b = 10;         System.out.println(a == b); // 输出true         System.out.println(p1 == p2); // 输出false         System.out.println(p1.equals(p2)); // 输出false     } } 上面比较的是地址,想要比较他们的内容就要重写equals方法:  class Person{ ...     @Override     public boolean equals(Object obj) {         if (obj == null) {             return false ;         }          if(this == obj) {             return true ;         } // 不是Person类对象         if (!(obj instanceof Person)) {             return false ;         } Person person = (Person) obj ; // 向下转型,比较属性值         return this.name.equals(person.name) && this.age==person.age ;     } } 所以要比较对象的内容是不是相同的时候,就一定要重写equals方法   hashcode方法 通过观看toString方法的源码我们发现了里面有一个hashcode方法:      public String toString() {         return getClass().getName() + "@" + Integer.toHexString(hashCode());     } 它的作用就是帮助我们算一个具体的对象位置,它的结果是一个内存地址然后会调用toHexString方法,将这个地址以16进制来打印输出。  hashcode方法源码:   public native int hashCode(); 它是一个native方法,底层是用c/c++代码写的,在java中我们是看不到的。  在我们的认知中,两个名字相同,年龄相同的对象存储在同一个位置,但是不重写hashcode方法的话,结果是不同的:  class Person {     public String name;     public int age;     public Person(String name, int age) {         this.name = name;         this.age = age;     } } public class TestDemo4 {     public static void main(String[] args) {         Person per1 = new Person("go", 20) ;         Person per2 = new Person("go", 20) ;         System.out.println(per1.hashCode());         System.out.println(per2.hashCode());     } } //执行结果         460141958         1163157884 这时我们再重写hashcode方法:  class Person {     public String name;     public int age;     public Person(String name, int age) {         this.name = name;         this.age = age;     } @Override     public int hashCode() {         return Objects.hash(name, age);     } } public class TestDemo4 {     public static void main(String[] args) {         Person per1 = new Person("go", 20) ;         Person per2 = new Person("go", 20) ;         System.out.println(per1.hashCode());         System.out.println(per2.hashCode());     } } //执行结果         460141958         460141958 这时发现他们的哈希值就是一样的了。  结论:  hashcode方法用来确定对象在内存中储存的位置相不相同。  hashcode在散列表中才有用,在散列表中hashcode的作用是获取对象的散列码,来确定对象在散列表中的位置。  内部类 当一个事物的内部,还有一部分需要一个完整的结构进行描述,而这个内部的完整的结构只要为外部的事物提供服务,那这个内部结构使用内部类。  在Java中,可以将一个类定义在另一个类或者方法的内部,前者称为内部类,后者称为外部类。  public class OutClass {     class InnerClass{     } } // OutClass是外部类 // InnerClass是内部类 注意:  定义在class类名{}的外部的,即便在一个文件夹内,都不是内部类  public class A{ } class B{ } // A 和 B是两个独立的类,彼此之前没有关系 内部类和外部类共用同一个Java源文件,但是经过编译后,内部类会形成单独的字节码文件  内部类的分类 根据内部类在类中位置不同,可以分为以下几种:  成员内部类:未被static修饰的实例内部类和被static修饰的静态内部类  局部内部类(不谈修饰符)  匿名内部类  public class OutClass {     // 成员位置定义:未被static修饰 --->实例内部类     public class InnerClass1{     } // 成员位置定义:被static修饰 ---> 静态内部类     static class InnerClass2{     }     public void method(){ // 方法中也可以定义内部类 ---> 局部内部类:几乎不用         class InnerClass5{         }     } } 实例内部类 未被static修饰的内部类:  public class OutClass {     private int a;     static int b;     int c;       public void methodA() {         a = 10;         System.out.println(a);     }       public static void methodB() {         System.out.println(b);     } //实例内部类:未被static修饰       class InnerClass {         int c;           public void methodInner() { // 在实例内部类中可以直接访问外部类中:任意访问限定符修饰的成员             a = 100;             b = 200;             methodA();             methodB(); // 如果外部类和实例内部类中具有相同名称成员时,优先访问的是内部类自己的             c = 300;             System.out.println(c); // 如果要访问外部类同名成员时候,必须:外部类名称.this.同名成员名字             OutClass.this.c = 400;             System.out.println(OutClass.this.c);         }     }       public static void main(String[] args) { // 外部类:对象创建 以及 成员访问         OutClass outClass = new OutClass();         System.out.println(outClass.a);         System.out.println(OutClass.b);         System.out.println(outClass.c);         outClass.methodA();         outClass.methodB();         System.out.println("=============实例内部类的访问============="); // 要访问实例内部类中成员,必须要创建实例内部类的对象 // 而普通内部类定义与外部类成员定义位置相同,因此创建实例内部类对象时必须借助外部类 // 创建实例内部类对象         OutClass.InnerClass innerClass1 = new OutClass().new InnerClass(); // 上述语法比较怪异,也可以先将外部类对象先创建出来,然后再创建实例内部类对象         OutClass.InnerClass innerClass2 = outClass.new InnerClass();         innerClass2.methodInner();     } } 注意:  1. 外部类中的任何成员都可以在实力内部类中直接访问    2. 实例内部类所处的位置与外部类成员位置相同,因此也受public、private等访问限定符的约束  3. 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名称.this.同名成员 来访问  4. 实例内部类对象必须在先有外部类对象前提下才能创建  5. 实例内部类的非静态方法中包含了一个指向外部类对象的引用   6. 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。   静态内部类 被static修饰的内部类就称为静态内部类  public class OutClass {     private int a;     static int b;     public void methodA(){         a = 10;         System.out.println(a);     }     public static void methodB(){         System.out.println(b);     } // 静态内部类:被static修饰的成员内部类     static class InnerClass{         public void methodInner(){ // 在内部类中只能访问外部类的静态成员 // a = 100; // 编译失败,因为a不是类成员变量             b =200; // methodA(); // 编译失败,因为methodA()不是类成员方法             methodB();         }     }     public static void main(String[] args) { // 静态内部类对象创建 & 成员访问         OutClass.InnerClass innerClass = new OutClass.InnerClass();         innerClass.methodInner();     } } 注意:  在静态内部类中只能访问外部类中的静态成员   创建静态内部类对象时,不需要先创建外部类对象   局部内部类  定义在外部类的方法体或者{}中,这种内部类只能在其定义的位置使用,一般使用的非常少:  public class OutClass {     int a = 10;     public void method(){         int b = 10; // 局部内部类:定义在方法体内部 // 不能被public、static等访问限定符修饰         class InnerClass{             public void methodInnerClass(){                 System.out.println(a);                 System.out.println(b);             }         } // 只能在该方法体内部使用,其他位置都不能用         InnerClass innerClass = new InnerClass();         innerClass.methodInnerClass();     }     public static void main(String[] args) { // OutClass.InnerClass innerClass = null; 编译失败     } } 注意:  1. 局部内部类只能在所定义的方法体内部使用  2. 不能被public、static等修饰符修饰  3. 编译器也有自己独立的字节码文件,命名格式:外部类名字$数字内部类名字.class  4. 几乎不会使用   匿名内部类  这个内部类是没有名字的:  interface IA {     void test(); }     public class Test {     public static void main(String[] args) {         IA a = new IA() {             @Override             public void test() {                 System.out.println("重写的方法");             }         };         a.test();     }  ———————————————— 版权声明:本文为CSDN博主「paper jie」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/paperjie/article/details/132684042 
  • [技术干货] 使用HttpURLConnection发送POST请求并携带请求参数-转载
     1、先创建URL对象,指定请求的URL地址。 URL url = new URL("http://example.com/api"); 2、调用URL对象的openConnection()方法创建HttpURLConnection对象。 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 3、设置请求方法为POST。 connection.setRequestMethod("POST"); 4、设置请求头,包括Content-Type、Content-Length等。其中Content-Type表示请求体的格式,Content-Length表示请求体的长度。 connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setRequestProperty("Content-Length", String.valueOf(param.getBytes().length)); 5、设置连接超时和读取超时时间。 connection.setConnectTimeout(5000); connection.setReadTimeout(5000); 6、允许向服务器写入写出数据。 connection.setDoOutput(true);   connection.setDoInput(true); 7、获取输出流,向服务器写入数据。 OutputStream outputStream = connection.getOutputStream(); outputStream.write(param.getBytes()); outputStream.flush(); outputStream.close();  这里的param是请求参数,需要将其转换为字节数组后写入输出流。  8、获取响应码,判断请求是否成功。 int statusCode = connection.getResponseCode(); 9、读取响应数据。 InputStream inputStream = statusCode == 200 ? connection.getInputStream() : connection.getErrorStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) {     response.append(line); } reader.close(); inputStream.close();  这里的response是响应数据,需要将其读取为字符串后使用。 完整的示例代码如下所示:  String param = "name=张三&age=18"; URL url = new URL("http://example.com/api"); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setRequestProperty("Content-Length", String.valueOf(param.getBytes().length)); connection.setConnectTimeout(5000); connection.setReadTimeout(5000); connection.setDoOutput(true); OutputStream outputStream = connection.getOutputStream(); outputStream.write(param.getBytes()); outputStream.flush(); outputStream.close(); int statusCode = connection.getResponseCode();   InputStream inputStream = statusCode == 200 ? connection.getInputStream() : connection.getErrorStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) {    response.append(line); } reader.close(); inputStream.close(); connection.disconnect(); System.out.println(response.toString());  需要注意的是,以上示例代码中的请求参数是以字符串形式传递的,如果需要传递复杂的请求参数,可以考虑使用JSON等格式。同时,如果请求的URL需要携带查询参数,可以在URL中添加查询参数。  下面使用HttpURLConnection 发送POST 请求 参数类型是json  下面是使用HttpURLConnection微信小程序发送订阅消息的一个例子  POST请求  json组装成了一个JSONObject  json类似是这样的  {   "touser": "OPENID",   "template_id": "TEMPLATE_ID",   "page": "index",   "data": {       "name01": {           "value": "某某"       },       "amount01": {           "value": "¥100"       },       "thing01": {           "value": "广州至北京"       } ,       "date01": {           "value": "2018-01-01"       }   } }    try {               URL url = new URL(" https://api.weixin.qq.com/cgi-bin/message/subscribe/send?" +                     "access_token=" +                     "自己的小程序token");               HttpURLConnection connection = (HttpURLConnection) url.openConnection();             connection.setRequestMethod("POST");             connection.setRequestProperty("Content-Type", "application/json");               connection.setDoOutput(true);             connection.setDoInput(true);   //构造发送给用户的订阅消息内容             Map messageContent = new HashMap<String, Object>();             messageContent.put("character_string1", new HashMap<String, Object>() {{                 put("value", "a123456789");             }});             messageContent.put("amount2", new HashMap<String, Object>() {{                 put("value", "1元");             }});             messageContent.put("thing3", new HashMap<String, Object>() {{                 put("value", "西安大学长安学区");             }});             messageContent.put("time4", new HashMap<String, Object>() {{                 put("value", "2021年10月20日");             }});             messageContent.put("thing5", new HashMap<String, Object>() {{                 put("value", "这是备注");             }});             JSONObject messageContentJson = new JSONObject(messageContent);               //构造订阅消息             Map subscribeMessage = new HashMap<String, Object>();             subscribeMessage.put("touser", " 。。。");//填写你的接收者openid             subscribeMessage.put("template_id", " 填写你的模板ID");//填写你的模板ID             subscribeMessage.put("data", messageContentJson);             JSONObject subscribeMessageJson = new JSONObject(subscribeMessage); /*             String s = subscribeMessageJson.toJSONString();             System.out.println("JSONString:" + s); */             String s1 = subscribeMessageJson.toString();             System.out.println("String:" + s1);             byte[] bytes = s1.getBytes();               DataOutputStream wr = new DataOutputStream(connection.getOutputStream());             wr.write(bytes);             wr.close();               int statusCode = connection.getResponseCode();               InputStream inputStream = statusCode == 200 ? connection.getInputStream() : connection.getErrorStream();             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));             StringBuilder response = new StringBuilder();             String line;             while ((line = reader.readLine()) != null) {                 response.append(line);             }             reader.close();             inputStream.close();             connection.disconnect();             System.out.println(response.toString());               connection.disconnect();           } catch (Exception e) {             e.printStackTrace();         } ———————————————— 版权声明:本文为CSDN博主「ImisLi」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/ImisLi/article/details/129946651 
  • [技术干货] 三路排序算法(Java 实例代码)-转载
     三路排序算法 一、概念及其介绍 三路快速排序是双路快速排序的进一步改进版本,三路排序算法把排序的数据分为三部分,分别为小于 v,等于 v,大于 v,v 为标定值,这样三部分的数据中,等于 v 的数据在下次递归中不再需要排序,小于 v 和大于 v 的数据也不会出现某一个特别多的情况),通过此方式三路快速排序算法的性能更优。 二、适用说明 时间和空间复杂度同随机化快速排序。  三路快速排序算法是使用三路划分策略对数组进行划分,对处理大量重复元素的数组非常有效提高快速排序的过程。它添加处理等于划分元素值的逻辑,将所有等于划分元素的值集中在一起。  三、过程图示  图片看转载链接哈~我们分三种情况进行讨论 partiton 过程,i 表示遍历的当前索引位置: (1)当前处理的元素 e=V,元素 e 直接纳入蓝色区间,同时i向后移一位。 (2)当前处理元素 e<v,e 和等于 V 区间的第一个位置数值进行交换,同时索引 lt 和 i 都向后移动一位(3)当前处理元素 e>v,e 和 gt-1 索引位置的数值进行交换,同时 gt 索引向前移动一位。 最后当 i=gt 时,结束遍历,同时需要把 v 和索引 lt 指向的数值进行交换,这样这个排序过程就完成了,然后对 <V 和 >V 的数组部分用同样的方法再进行递归排序。  四、Java 实例代码 源码包下载: Download https://www.runoob.com/wp-content/uploads/2020/09/runoob-algorithm-QuickSort3Ways.zip  QuickSort3Ways.java 文件代码: package runoob;  /**  * 三路快速排序  */ public class QuickSort3Ways {     //核心代码---开始     // 递归使用快速排序,对arr[l...r]的范围进行排序     private static void sort(Comparable[] arr, int l, int r){         if (l >= r) {             return;         }         // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot         swap( arr, l, (int)(Math.random()*(r-l+1)) + l );         Comparable v = arr[l];         int lt = l;     // arr[l+1...lt] < v         int gt = r + 1; // arr[gt...r] > v         int i = l+1;    // arr[lt+1...i) == v         while( i < gt ){             if( arr[i].compareTo(v) < 0 ){                 swap( arr, i, lt+1);                 i ++;                 lt ++;             }             else if( arr[i].compareTo(v) > 0 ){                 swap( arr, i, gt-1);                 gt --;             }             else{ // arr[i] == v                 i ++;             }         }         swap( arr, l, lt );         sort(arr, l, lt-1);         sort(arr, gt, r);     }     //核心代码---结束      public static void sort(Comparable[] arr){          int n = arr.length;         sort(arr, 0, n-1);     }      private static void swap(Object[] arr, int i, int j) {         Object t = arr[i];         arr[i] = arr[j];         arr[j] = t;     }      // 测试 QuickSort3Ways     public static void main(String[] args) {          // 三路快速排序算法也是一个O(nlogn)复杂度的算法         // 可以在1秒之内轻松处理100万数量级的数据         int N = 1000000;         Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);         sort(arr);         SortTestHelper.printArray(arr);     } } ———————————————— 版权声明:本文为CSDN博主「彼岸的菜鸟」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/2301_78835635/article/details/132144215 
  • [技术干货] 【Java 基础篇】Java 迭代器详解-转载
     导言 在 Java 中,迭代器是一种常用的设计模式,用于遍历集合中的元素。它提供了一种统一的方式来访问集合中的元素,而不必暴露集合的内部实现细节。本文将介绍 Java 迭代器的概念、使用方法和常见技巧,并提供一些示例代码。 一、迭代器的概念 迭代器是一种对象,它允许按顺序访问集合中的元素,而不需要知道集合的底层结构。通过使用迭代器,我们可以遍历集合并访问其中的元素,而无需关心集合的具体实现方式。 Java 提供了 Iterator 接口作为迭代器的基础接口。该接口定义了一组用于访问集合元素的方法,包括 hasNext、next 和 remove 等。  二、使用迭代器 要使用迭代器遍历集合,我们需要进行以下步骤:  获取集合的迭代器对象:通过调用集合的 iterator 方法获取迭代器对象。例如,对于 ArrayList 集合,可以使用 iterator() 方法获取迭代器对象。 List<String> list = new ArrayList<>(); // 添加元素到集合中  Iterator<String> iterator = list.iterator(); 遍历集合元素:通过使用迭代器的 hasNext 和 next 方法来遍历集合中的元素。hasNext 方法用于检查是否还有下一个元素,next 方法用于获取下一个元素的值。 while (iterator.hasNext()) {     String element = iterator.next();     // 处理元素 } 可选操作:迭代器还提供了 remove 方法,用于从集合中删除当前迭代的元素。需要注意的是,该方法只能在调用 next 方法后才能调用,且每次只能调用一次。 iterator.remove(); 三、迭代器的优势 使用迭代器遍历集合具有以下优势:  抽象集合的实现:通过使用迭代器,我们可以在不了解集合内部实现的情况下遍历和访问集合元素。这样,集合的实现细节对外部代码是透明的。  安全性:迭代器提供了一种安全的方式来遍历集合。它通过维护迭代器的状态来保证在遍历过程中不会出现并发修改的问题。  通用性:迭代器是一种通用的设计模式,在 Java 中被广泛应用于各种集合类型。无论是数组、列表、集合还是映射,我们都可以使用迭代器来遍历和访问元素。  四、迭代器的常见技巧 除了基本的使用方法外,还有一些常见的技巧可以帮助我们更好地使用迭代器。  1. 使用增强的 for 循环 Java 提供了增强的 for 循环(foreach 循环),可以简化迭代器的使用。它可以直接遍历集合中的元素,而不需要显式地使用迭代器。  for (String element : list) {     // 处理元素 } 2. 遍历过程中的修改 在使用迭代器遍历集合时,如果需要在遍历过程中修改集合,应使用迭代器的 remove 方法,而不是直接操作集合。直接操作集合可能会导致并发修改异常。  Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) {     String element = iterator.next();     if (shouldRemove(element)) {         iterator.remove(); // 使用迭代器删除元素     } } 3. 避免重复创建迭代器 在迭代器的使用过程中,应避免在每次迭代时都创建新的迭代器对象。如果需要多次遍历集合,可以在第一次遍历时创建迭代器,并在后续的遍历中重复使用该迭代器。  Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) {     String element = iterator.next();     // 处理元素 }  // 再次使用同一个迭代器进行遍历 while (iterator.hasNext()) {     String element = iterator.next();     // 处理元素 } 4. 使用迭代器的限制功能 迭代器提供了一些限制功能,如只读迭代器和单向迭代器。如果在遍历过程中不需要修改集合或只需要向前遍历,可以使用只读或单向迭代器,以提高性能和安全性。  List<String> list = new ArrayList<>(); // 添加元素到集合中  Iterator<String> readOnlyIterator = list.iterator(); Iterator<String> forwardIterator = list.listIterator(); 五、示例代码 下面是一个使用迭代器遍历集合并打印元素的示例代码:  List<String> list = new ArrayList<>(); list.add("Apple"); list.add("Banana"); list.add("Orange");  Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) {     String element = iterator.next();     System.out.println(element); } 在上面的示例中,我们创建了一个 ArrayList 集合,并使用迭代器遍历集合中的元素,然后打印每个元素的值。  总结 迭代器是一种在 Java 中常用的设计模式,用于遍历集合中的元素。通过使用迭代器,我们可以统一访问集合元素,而不需要了解集合的具体实现。本文介绍了迭代器的概念、使用方法和常见技巧,并提供了示例代码。  希望本文对你理解和使用 Java 迭代器提供了帮助。如果你有任何问题或建议,请随时留言。 ———————————————— 版权声明:本文为CSDN博主「繁依Fanyi」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_21484461/article/details/131424468 
  • [技术干货] 【JAVA】多态的概念与实际利用-转载
     前言 在面向对象(OOP)的程序设计语言中,多态与封装、继承合称为OOP的三大特性。在今天,我们就来学习一下JAVA中的多态是什么样子的。、 多态 指一个对象在不同情况下可以表现出不同的行为。Java多态性分为两种:编译时多态性(静态多态性)和运行时多态性(动态多态性)。 编译时多态性:也称为静态多态性,是指在编译期间就能确定方法的调用方式、参数类型及返回值类型等,主要通过方法重载实现。 运行时多态性:也称为动态多态性,是指在运行期间才能确定方法的调用方式,主要通过方法重写实现。Java中实现运行时多态性的关键是继承和方法重写。 具体来说,当一个类的子类重新定义了一个或多个已在父类中定义的方法时,那么子类的对象将可以调用重新定义的方法,而不是调用父类中的方法。这种现象称为方法重写。通过父类的引用变量来引用一个子类的对象时,父类引用变量不能直接调用子类中重新定义的方法,而是要调用子类中的方法,这种多态性称为动态多态性。 多态实现的重要条件 Java多态实现的重要条件包括: 继承:必须有继承关系,子类必须继承父类。 覆盖:子类必须重写父类的方法。 向上转型:可以声明一个父类引用类型的变量,将它指向一个子类对象。通过这种方式调用方法,就可以实现多态。 动态绑定:在运行时而不是编译时进行方法调用。这样就能够根据实际调用的对象类型来决定调用哪个方法,实现多态。 虚函数 在Java中,所有方法都是虚函数,因为它们都是在运行时动态绑定的。当子类继承父类时,它可以覆盖父类的方法,但具体调用哪个方法取决于对象的实际类型而不是变量的声明类型。因此,可以在运行时动态处理对象的多态性。 使用关键字“override”来覆盖父类的方法。子类中的方法必须与父类中被覆盖的方法具有相同的名称、参数列表和返回类型。这样,当调用子类中的方法时,根据对象的类型来确定应该调用哪个方法。 实例 假设我们有一个Animal类和一个Cat类,Cat类是Animal类的子类。 Animal类中定义了一个虚方法makeSound(),它会输出动物发出的声音: public class Animal {     public void makeSound() {         System.out.println("The animal makes a sound");     } } Cat类继承了Animal类并覆盖了makeSound()方法: public class Cat extends Animal {     public void makeSound() {         System.out.println("The cat meows");     } } 现在,我们可以实例化一个Animal对象和一个Cat对象,并调用它们makeSound()方法: Animal animal = new Animal(); Cat cat = new Cat(); animal.makeSound(); // 输出 "The animal makes a sound" cat.makeSound(); // 输出 "The cat meows" 注意,由于Cat类继承了Animal类,因此可以使用Cat对象来代替Animal对象,因为它们都是Animal类的实例。这就是面向对象编程中的多态性。 多态的实现方式 方式一:重写: 这个内容已经在上一篇文章中我有详细的讲过Java 重写(Override)与重载(Overload) 方式二:接口 生活中的接口最具代表性的就是插座,例如一个三接头的插头都能接在三孔插座中,因为这个是每个国家都有各自规定的接口规则,有可能到国外就不行,那是因为国外自己定义的接口类型。 java中的接口类似于生活中的接口,就是一些方法特征的集合,但没有方法的实现。具体可以看 java接口 这一章节的内容。 方式三:抽象类和抽象方法 ———————————————— 版权声明:本文为CSDN博主「许思王」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_73602725/article/details/133038845 
  • [技术干货] Java:不支持发行版本5-转载
     错误 Java:不支持发行版本5 详细错误 同学在github上找到一个微服务项目(基于maven进行构建),进行二开,导入项目运行控制台报错 Java:不支持发行版本5,笔者修改项目结构(F i l e FileFile→ \rightarrow→P r o j e c t S t r u c t u r e Project StructureProjectStructure)以及设置(F i l e FileFile→ \rightarrow→S e t t i n g s SettingsSettings)后,依旧报错 解决方案 对于pom.xml依赖文件加入如下配置 <properties>     <maven.compiler.source>1.8</maven.compiler.source>     <maven.compiler.target>1.8</maven.compiler.target> </properties> 报错原因 报错提示 “Java:不支持发行版本5” 表明项目使用了 Java 5 的发行版本,但当前环境不支持该版本。 解决原因 通过将项目的编译器源码和目标版本设置为 1.8,确保项目在 Java 8 环境下进行编译和运行。这样可以与当前环境的 Java 版本保持一致,避免报错。 笔者在此对pom.xml依赖文件加入如下配置进行详细解释 <properties>     <maven.compiler.source>1.8</maven.compiler.source>     <maven.compiler.target>1.8</maven.compiler.target> </properties> <properties> </properties> 标签用于定义项目的属性。在上述解决方案中,我们向 标签添加了两个属性: <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.source>:指定项目源代码的 Java 版本。在此处,我们将其设置为 1.8,表示使用 Java 8 的语法和特性编写源代码。 <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.target>:指定编译生成的字节码的 Java 版本。同样地,我们将其设置为 1.8,以确保编译后的代码与 Java 8 兼容。 通过将这两个属性设置为 1.8,我们告诉 Maven 使用 Java 8 的编译器进行编译,并生成与 Java 8 兼容的字节码文件。这样可以解决报错问题,确保项目在 Java 8 环境下正确编译和运行。 参考文献 解决方案参考错误“ Java:不支持发行版本5”的正确解决方案 解决原因参考chatgpt 原创不易 转载请标明出处 如果对你有所帮助 别忘啦点赞支持哈 ———————————————— 版权声明:本文为CSDN博主「飞滕人生TYF」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/T_Y_F_/article/details/131317847 
  • [技术干货] html简洁漂亮的个人简历,个人主页,个人简介网页版(源码) -转载
     1.设计来源 主旨: 简洁,干净,明亮,舒适的风格; 语言: 可以html+js+css,也可以html+css; 版块: 人信息,基本资料,专业技能,教育经历(支持更多扩展); 适用: 个人简介,个人简历,个人主页,个人博客,个人空间等方向; 1.1 主界面 主界面动态的朦胧天空背景图,可以配置自己的图标,个人名言,名字,职称,邮箱,手机号,工作地址等信息,也可以扩展新的。 1.2 基本资料 基本资料动态的朦胧天空背景图,可以配置个人职业技能,大图标,姓名,年龄,性别,手机号,邮箱,qq号,居住地,户籍,学历,学校,专业等信息,也可以扩展新的。 1.3 专业技能 专业技能动态的科技线路背景图,可以配置个人专业技能,按列表排序出来,个人的掌握的工具,及熟练度等信息,也可以扩展新的。 1.4 教育经历 教育经历动态的科技线路背景图,可以配置个人教育的学校,时间,几学到的东西等信息,也可以扩展新的。 1.5 工作经验 工作经验动态的科技线路背景图,可以配置个人工作过的公司,工作的时间,负责的项目,开发工具等信息,也可以扩展新的。 2.效果和源码 2.1 动态效果 下面咋们一起来看看这个个人简历的动态效果,改变图片,可以配置多种风格,灵活运用,效果酷炫。  html简洁漂亮的个人简历,个人主页,个人简介网页版  2.2 源代码 这里是主界面的代码,其他图片、js、css等代码,见下面的 源码下载 ,里面有所有代码资源和相关说明。  <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <meta name="viewport" content="width=device-width, initial-scale=1">     <title>徐纯宇 - 个人简历,个人主页,个人介绍,个人简介</title>     <link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">     <link rel="stylesheet" type="text/css" href="css/main.css"> </head> <body oncontextmenu="return false;" onselectstart="return false;" unselectable="on" ondragstart="return false;"> <aside>     <a class="cur_a"><span>徐纯宇</span></a>     <a><span>基本资料</span></a>     <a><span>专业技能</span></a>     <a><span>教育经历</span></a>     <a><span>工作经验</span></a> </aside>     <section class="page_one" id="page1">     <div class="cen_con">         <div class="portrait">             <img onmousemove="this.src='images/user_c.jpg'" onmouseout="this.src='images/user.jpg'"/ src="images/user.jpg">         </div>         <div class="cen_text">             <h2>业精于勤;荒于嬉;行成于思;毁于随。</h2>             <hr>             <h3>徐纯宇</h3>             <h3>全栈工程师</h3>             <h3> 1376174032@qq.com |  13300000000</h3>             <h3>在职北京 ▣ 海淀</h3>         </div>     </div>     <div class="down_arrow">         <a class="scroll"><span></span></a>      </div> </section> <section class="page_two" id="page2">     <div class="con_wrap">         <div class="tit_wrap">             <h1 style="font-weight: bold;">基本资料</h1>             <div class="scissors" style="border-top:1px dashed orange;">             </div>             <h2> &nbsp;&nbsp;&nbsp;&nbsp;毕业于北京理工大学的计算机应用专业。从业十年有余,熟悉C#和Java前后端开发,熟悉oracle和mysql数据库,熟悉Html和js及css前端开发,熟悉uniapp跨平台开发小程序和app。</h2>         </div>                 <div class="myinfo">             <table>                 <tbody>                     <tr>                         <td rowspan="6">                             <img src="images/user.jpg">                         </td>                         <td>姓名 | 徐纯宇</td>                         <td>手机 | 13300000000</td>                     </tr>                     <tr>                         <td>性别 | 男</td>                         <td>邮箱 | 1376174032@qq.com</td>                     </tr>                     <tr>                         <td>出生 | 1992.02.20</td>                         <td>QQ号 | 1376174032</td>                     </tr>                     <tr>                         <td>居住 | 北京市海淀区</td>                         <td>户籍 | 北京市海淀区</td>                     </tr>                     <tr>                         <td>学历 | 本科</td>                         <td>学校 | 北京理工大学</td>                     </tr>                     <tr>                         <td>专业 | 计算机应用</td>                         <td></td>                     </tr>                 </tbody>             </table>         </div>     </div>     <div class="down_arrow">         <a class="scroll"><span></span></a>      </div> </section> <section class="page_three" id="page3">     <div class="con_wrap">         <div class="tit_wrap">             <h1 style="font-weight: bold;">专业技能</h1>             <div class="scissors" style="border-top:1px dashed orange;">             </div>             <h2>长年累月后,精进又向前。唯有勤学练,擅长最优先。</h2>             </div>         <div class="skill_con">             <div class="canvas_wrap">                 <div class="canvas_con">                     <div class="text_con">                         <p class="percent">98%</p>                         <p class="chart_title">C#</p>                     </div>                     <canvas id="html5" width=160 height=160></canvas>                 </div>                 <div class="canvas_con">                     <div class="text_con">                         <p class="percent">93%</p>                         <p class="chart_title">JAVA</p>                     </div>                     <canvas id="css3" width=160 height=160></canvas>                 </div>                 <div class="canvas_con">                     <div class="text_con">                         <p class="percent">96%</p>                         <p class="chart_title">HTML</p>                     </div>                     <canvas id="js" width=160 height=160></canvas>                 </div>                 <div class="canvas_con">                     <div class="text_con">                         <p class="percent">99%</p>                         <p class="chart_title">ORACLE</p>                     </div>                     <canvas id="jq" width=160 height=160></canvas>                 </div>             </div>             <div class="text_wrap">                 <p>1.  熟练使用java,基于ssh能快速搭建系统框架。</p>                 <p>2.  熟练使用java,基于spring boot能快速搭建系统框架。</p>                 <p>3.  熟练使用HTML/CSS技术,精通js/jquery编程,能够熟练使用angularjs,vue,reactjs等前端框架。</p>                 <p>4.  熟悉oracle,mysql,sqlserver等各平台安装使用,熟练使用SQL语句增删改查,触发器,存储过程,索引,序列。</p>                 <p>5.  熟练的使用grunt,gulp等前端工具。</p>                 <p>6.  熟悉c#语言研发,基于asp.new mvc能快速搭建系统框架,新技术asp.net core做后台数据服务能快速搭建,跨平台使用。</p>                 <p>7.  熟悉c#语言研发,基于winform的c/s应用程序能快速搭建框架。</p>             </div>             </div>     </div>     <div class="down_arrow">         <a class="scroll"><span></span></a>      </div> </section> <section class="page_four" id="page4">     <div class="con_wrap">         <div class="tit_wrap">             <h1 style="font-weight: bold;">教育经历</h1>             <div class="scissors" style="border-top:1px dashed orange;">             </div>             <h2>学则智,不学则愚;学则治,不学则乱。自古圣贤,成大业,未有不由学而成者。</h2>         </div>           <div class="work_con">               <div class="programe">                 <div class="work_time">4年<br>北京理工大学</div>                 <div class="work_text">                       <div class="triangle-left"></div>                       <div class="exCon">                         <h4>学习时间:2008/06 -- 2012/06</h4>                         <p>学习技能:</p>                         <p>大学四年时间完成了所有的学业,并参与了很多项目研发工作,配合老师和同学攻关了很多难题。</p>                         <p> 1.掌握了办公五剑客,Word,PPT,excel,ps,viso。</p>                                <p> 2.掌握了C#和java的前后端开发,数据库基本应用。</p>                      </div>                 </div>              </div>             <div class="programe">                 <div class="work_time">1年<br>Java培训班</div>                 <div class="work_text">                       <div class="triangle-left"></div>                       <div class="exCon">                         <h4>学习时间:2011/05 -- 2012/04</h4>                         <p>学习技能:</p>                         <p>参加了一年的JAVA培训班,跟着老师一起做了多个项目。积累了很多经验,有了很多经历。</p>                         <p>1.掌握了springboot框架下的api开发,并开发了自己的一套数据接口。</p>                             <p>2.掌握了ssm框架下的前后端开发,并开发了自己的博客。</p>                           </div>                 </div>             </div>         </div>     </div>     <div class="down_arrow">         <a class="scroll"><span></span></a>      </div> </section> <section class="page_five" id="page5">     <div class="con_wrap">         <div class="tit_wrap">             <h1 style="font-weight: bold;">工作经验</h1>             <div class="scissors" style="border-top:1px dashed orange;">             </div>             <h2>生命,需要我们去努力。年轻时,我们要努力锻炼自己的能力,掌握知识、掌握技能、掌握必要的社会经验。</h2>         </div>           <div class="work_con">               <div class="programe">                 <div class="work_time">22个月<br>汽车之家官网</div>                 <div class="work_text">                       <div class="triangle-left"></div>                       <div class="exCon">                         <h4>开发时间:2019 /12--2021 /10</h4>                         <h5>开发工具:VS2015,VSCode,ORACLE</h5>                         <p>项目描述:</p>                         <p>参与与客户交流需求,参与项目整体开发,主要负责首页面各方面数据综合显示,系统采用预加载,通过wcf对硬件控制,实时更新,一些页面设计,数据库的设计,数据测试。</p>                         <p> 1.数据信息展示块,2.软硬件交互块,3.数据解析块。</p>                            </div>                 </div>              </div>             <div class="programe">                 <div class="work_time">8个月<br>联想后台研发</div>                 <div class="work_text">                       <div class="triangle-left"></div>                       <div class="exCon">                         <h4>开发时间:2021 /10--2022 /06</h4>                         <h5>开发工具:IDEA,VSCode,MYSQL</h5>                         <p>项目描述:</p>                         <p>参与与客户交流需求,参与项目整体开发,主要负责首页面各方面数据综合显示,系统采用预加载,实时更新,一些页面设计,数据库的设计,数据测试。 </p>                         <p>1.设备基础信息 2.部门信息 3.用户信息 4.数据报表 5.调试工具,6.数据监测,7.系统配置等7个大功能块 系统采用角色分配,操作分配等权限,实现各个角色的权限功能。精确划分各个部门的职责。</p>                           </div>                 </div>             </div>         </div>     </div> </section> </body> <script type="text/javascript" src="js/main.js"></script> </html> 
  • [技术干货] 整合Security 到SSM框架中 使用Spring Security下的BCryptPasswordEncoder进行密码加密
     一.Spring Security安全框架实现密码加密方法简述 1.首先:Spring Security提供了强大的加密工具PasswordEncoder,PasswordEncoder接口的代码如下:  package org.springframework.security.crypto.password;  public interface PasswordEncoder {     String encode(CharSequence var1);//是是对密码加密的方法      boolean matches(CharSequence var1, String var2);//是用来验证密码和加密后密码是否一致的如果一致则返回true } 2.其次:Spring Security提供了BCryptPasswordEncoder类,该类实现了Spring的PasswordEncoder接口,使用BCrypt强哈希方法来对密码进行加密,通过BCrypt强哈希方法每一次加密的结果都不一样:可以看看示例加密后的密码,需要说明的是这两种加密后的密码,其明文密码都是:123 3.加密的代码,可以看到每次加密产生的都是随机字符串:  public String encode(CharSequence rawPassword) {         String salt;         if (this.strength > 0) {             if (this.random != null) {                 salt = BCrypt.gensalt(this.strength, this.random);             } else {                 salt = BCrypt.gensalt(this.strength);             }         } else {             salt = BCrypt.gensalt();         }          return BCrypt.hashpw(rawPassword.toString(), salt);     } 二.加密的具体实现步骤 1.在配置文件中配置加密所需要的工具类  <!--配置加密工具类--> <bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/> 2.在service的实现层中注入BCryptPasswordEncoder  @Autowired  private BCryptPasswordEncoder passwordEncoder;//通过注解拿到加密对象 3.然后在在service的实现层中的需要加密的操作方法中添加比如保存操作  //保存一个用户,使用加密算法把加密后的密码保存入数据库     @Override     public void save(SysUser sysUser) {         //二.使用安全框架加密方式         //1.Security安全框架加密操作:加密密码并存入sysUser对象中         String encode = passwordEncoder.encode(sysUser.getPassword());//拿到加密后的密码         sysUser.setPassword(encode);//将加密后的密码设置到sysUser对象中         userDao.save(sysUser);     } 4.以上配置结束你需要运行一下,添加一个用户(这个用户的密码是加密的),否则用之前没有加密的密码登录是会失败的 ;然后去除"{noop}"  //2.Security安全框架加密操作:去除+"{noop}"+————————————非常重要             User user = new User(sysUser.getUsername(), sysUser.getPassword(), authorities);             return user; 5.在配置文件中spring_security.xml 里面引用第一步配置的id  <!-- Security安全框架加密操作:引入加密操作--> <security:password-encoder ref="passwordEncoder"/> ———————————————— 原文链接:https://blog.csdn.net/weixin_43330884/article/details/104632267 
  • [技术干货] Spring Security:密码编码器PasswordEncoder介绍与Debug分析
    博主在之前已经介绍了Spring Security的用户UserDetails与用户服务UserDetailsService,本篇博客介绍Spring Security的密码编码器PasswordEncoder,它们是相互联系的,博主会带大家一步步深入理解Spring Security的实现原理,也会带来Spring Security的实战分享。  Spring Security:用户UserDetails源码与Debug分析 Spring Security:用户服务UserDetailsService源码分析 为什么是介绍而不是源码分析?博主虽然在研一上过密码学的课,但毕竟没有细致研究过密码学领域,因此不敢管中窥豹。再者,这些加密算法的实现原理、是否能抵御攻击、明文与密文(明文经过加密得到)的匹配方法以及时间成本等因素都不是学习Spring Security框架的核心内容,因此本篇博客只会简单介绍Spring Security的密码编码器PasswordEncoder及其实现类,以及密码编码器在Spring Security中的使用时机。  PasswordEncoder PasswordEncoder接口有很多实现类,也有被标记了@Deprecated注解的实现类,一般是该类表示的密码编码器(加密算法)不安全,比如可以在能接受的时间内被破解,比如彩虹表攻击。  这里不去分析每个密码编码器的实现原理,因为密码编码器的种类太多了,而且没有必要,密码编码器的主要作用无非就是对密码进行编码(加密),以及原始密码(客户端登录验证时输入的密码)与编码密码(正确原始密码通过密码编码器编码的结果)的正确匹配,因此密码编码器必定需要实现PasswordEncoder接口的两个方法,而其他方法的实现是服务于这两个方法。  package org.springframework.security.crypto.password;  // 首选实现是BCryptPasswordEncoder public interface PasswordEncoder {      /**      * 对原始密码进行编码      */     String encode(CharSequence rawPassword);      /**      * 验证从存储(比如数据库或者内存等)中获取的编码密码是否与需要验证的密码匹配       * 如果密码匹配,则返回 true,否则返回 false      * 存储的编码密码永远不会被解码      * 因此会将需要验证的密码进行编码,然后与编码密码进行匹配      */     boolean matches(CharSequence rawPassword, String encodedPassword);      /**      * 如果为了更好的安全性需要再次对编码的密码进行编码,则返回 true,否则返回 false      * 默认实现始终返回 false      */     default boolean upgradeEncoding(String encodedPassword) {         return false;     } } 很显然密码编码器的主要作用是为了编码与匹配,而有些加密算法需要经过多次迭代加密,因此也需要实现upgradeEncoding方法,比如BCryptPasswordEncoder类的实现(strength属性越大,需要做更多的工作来加密密码,默认值为10):      @Override     public boolean upgradeEncoding(String encodedPassword) {         if (encodedPassword == null || encodedPassword.length() == 0) {             logger.warn("Empty encoded password");             return false;         }          Matcher matcher = BCRYPT_PATTERN.matcher(encodedPassword);         if (!matcher.matches()) {             throw new IllegalArgumentException("Encoded password does not look like BCrypt: " + encodedPassword);         }         else {             int strength = Integer.parseInt(matcher.group(2));             return strength < this.strength;         }     } PasswordEncoderFactories PasswordEncoderFactories类源码:  package org.springframework.security.crypto.factory;  import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.DelegatingPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;  import java.util.HashMap; import java.util.Map;  /**  * 用于创建PasswordEncoder实例  */ public class PasswordEncoderFactories {      /**      * 使用默认映射创建一个DelegatingPasswordEncoder      * 可能会添加其他映射,并且将更新编码以符合最佳实践      * 但是,由于DelegatingPasswordEncoder的性质,更新不应影响用户      */     @SuppressWarnings("deprecation")     public static PasswordEncoder createDelegatingPasswordEncoder() {         // 默认bcrypt         String encodingId = "bcrypt";         Map<String, PasswordEncoder> encoders = new HashMap<>();         encoders.put(encodingId, new BCryptPasswordEncoder());         encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());         encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());         encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));         encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());         encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());         encoders.put("scrypt", new SCryptPasswordEncoder());         encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));         encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));         encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());         encoders.put("argon2", new Argon2PasswordEncoder());          return new DelegatingPasswordEncoder(encodingId, encoders);     }      private PasswordEncoderFactories() {} } PasswordEncoderFactories类可以看作密码编码器工厂,它将已有的密码编码器存储在HashMap中,通过静态方法createDelegatingPasswordEncoder即可获取,该方法的返回值是一个DelegatingPasswordEncoder实例。  DelegatingPasswordEncoder DelegatingPasswordEncoder类源码:  public class DelegatingPasswordEncoder implements PasswordEncoder {     private static final String PREFIX = "{";     private static final String SUFFIX = "}";     private final String idForEncode;     private final PasswordEncoder passwordEncoderForEncode;     private final Map<String, PasswordEncoder> idToPasswordEncoder;     private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder();      /**      * 创建一个新实例      * idForEncode:用于查找应使用哪个PasswordEncoder进行encode      * idToPasswordEncoder:id到PasswordEncoder的映射,用于确定应使用哪个PasswordEncoder进行matches      */     public DelegatingPasswordEncoder(String idForEncode,         Map<String, PasswordEncoder> idToPasswordEncoder) {         if (idForEncode == null) {             throw new IllegalArgumentException("idForEncode cannot be null");         }         if (!idToPasswordEncoder.containsKey(idForEncode)) {             throw new IllegalArgumentException("idForEncode " + idForEncode + "is not found in idToPasswordEncoder " + idToPasswordEncoder);         }         for (String id : idToPasswordEncoder.keySet()) {             if (id == null) {                 continue;             }             // 如果id有'{'或者'}'字符则会出现问题,比如:             // 基于prefixEncodedPassword获取id,是根据'{'和'}'字符对第一次出现的位置来截取             // 以及去除{id}得到encodedPassword,是根据'}'字符第一次出现的位置来截取             if (id.contains(PREFIX)) {                 throw new IllegalArgumentException("id " + id + " cannot contain " + PREFIX);             }             if (id.contains(SUFFIX)) {                 throw new IllegalArgumentException("id " + id + " cannot contain " + SUFFIX);             }         }         // 初始化         this.idForEncode = idForEncode;         this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode);         this.idToPasswordEncoder = new HashMap<>(idToPasswordEncoder);     }      /**      * 设置defaultPasswordEncoderForMatches,默认为UnmappedIdPasswordEncoder实例      */     public void setDefaultPasswordEncoderForMatches(         PasswordEncoder defaultPasswordEncoderForMatches) {         if (defaultPasswordEncoderForMatches == null) {             throw new IllegalArgumentException("defaultPasswordEncoderForMatches cannot be null");         }         this.defaultPasswordEncoderForMatches = defaultPasswordEncoderForMatches;     }      // 编码,{id}前缀拼接委托的PasswordEncoder的编码结果     @Override     public String encode(CharSequence rawPassword) {         return PREFIX + this.idForEncode + SUFFIX + this.passwordEncoderForEncode.encode(rawPassword);     }      // 匹配     @Override     public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {         if (rawPassword == null && prefixEncodedPassword == null) {             return true;         }         // 根据prefixEncodedPassword提取id         String id = extractId(prefixEncodedPassword);         // 根据id获取PasswordEncoder          PasswordEncoder delegate = this.idToPasswordEncoder.get(id);         // 是否有对应的PasswordEncoder          if (delegate == null) {             // 没有对应的PasswordEncoder             // 则使用defaultPasswordEncoderForMatches进行匹配             return this.defaultPasswordEncoderForMatches                 .matches(rawPassword, prefixEncodedPassword);         }         // 有对应的PasswordEncoder         // 提取encodedPassword,即去掉{id}前缀         String encodedPassword = extractEncodedPassword(prefixEncodedPassword);         // 返回匹配结果         return delegate.matches(rawPassword, encodedPassword);     }      // 提取id     private String extractId(String prefixEncodedPassword) {         if (prefixEncodedPassword == null) {             return null;         }         // 第一个'{'字符的位置         int start = prefixEncodedPassword.indexOf(PREFIX);         if (start != 0) {             return null;         }         // 从start开始的第一个'}'字符的位置         int end = prefixEncodedPassword.indexOf(SUFFIX, start);         if (end < 0) {             return null;         }         // 截取得到id         return prefixEncodedPassword.substring(start + 1, end);     }      @Override     public boolean upgradeEncoding(String prefixEncodedPassword) {         // 提取id         String id = extractId(prefixEncodedPassword);         // id与idForEncode属性不匹配,则返回true         if (!this.idForEncode.equalsIgnoreCase(id)) {             return true;         }         else {             // 提取encodedPassword              String encodedPassword = extractEncodedPassword(prefixEncodedPassword);             // 根据id获取PasswordEncoder             // 返回该PasswordEncoder的upgradeEncoding方法基于encodedPassword的返回值             return this.idToPasswordEncoder.get(id).upgradeEncoding(encodedPassword);         }     }          // 提取encodedPassword     private String extractEncodedPassword(String prefixEncodedPassword) {         // 第一个'}'字符的位置         int start = prefixEncodedPassword.indexOf(SUFFIX);         // 截取得到encodedPassword         return prefixEncodedPassword.substring(start + 1);     }      /**      * 引发异常的默认PasswordEncoder      */     private class UnmappedIdPasswordEncoder implements PasswordEncoder {          @Override         public String encode(CharSequence rawPassword) {             throw new UnsupportedOperationException("encode is not supported");         }          @Override         public boolean matches(CharSequence rawPassword,             String prefixEncodedPassword) {             String id = extractId(prefixEncodedPassword);             throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id \"" + id + "\"");         }     } } DelegatingPasswordEncoder是基于前缀标识符并委托给另一个PasswordEncoder的密码编码器,可以使用PasswordEncoderFactories类创建一个DelegatingPasswordEncoder实例,也可以创建自定义的DelegatingPasswordEncoder实例。  密码存储格式为 {id}encodedPassword(prefixEncodedPassword),id是用于查找应该使用哪个PasswordEncoder的标识符,encodedPassword是使用PasswordEncoder对原始密码进行编码的结果,id必须在密码的开头,以{开头,}结尾。 如果找不到id,则id将为空。 例如,以下可能是使用不同id(PasswordEncoder)编码的密码列表:  {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG {noop}password {pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc {scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= {sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 第一个密码的id为bcrypt,encodePassword为$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG,匹配时,将委托给BCryptPasswordEncoder。第二个密码的id为noop,encodePassword为password。匹配时,将委托给NoOpPasswordEncoder。以此类推。  传递给构造函数的idForEncode确定将使用哪个PasswordEncoder来编码原始密码。匹配是基于id和构造函数中提供的idToPasswordEncoder来完成的。matches方法可能使用带有未映射id(包括空id)的密码,调用matches方法将抛出IllegalArgumentException异常, 可以使用setDefaultPasswordEncoderForMatches方法自定义此行为,即设置defaultPasswordEncoderForMatches属性,当根据id获取不到PasswordEncoder时使用。 Debug分析 依赖:  <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">     <modelVersion>4.0.0</modelVersion>      <groupId>com.kaven</groupId>     <artifactId>security</artifactId>     <version>1.0-SNAPSHOT</version>      <parent>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-parent</artifactId>         <version>2.3.1.RELEASE</version>     </parent>      <properties>         <maven.compiler.source>8</maven.compiler.source>         <maven.compiler.target>8</maven.compiler.target>     </properties>      <dependencies>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-security</artifactId>         </dependency>         <dependency>             <groupId>org.projectlombok</groupId>             <artifactId>lombok</artifactId>         </dependency>     </dependencies> </project>  接口:  @RestController public class MessageController {      @GetMapping("/message")     public String getMessage() {         return "hello kaven, this is security";     } } 启动类:  @SpringBootApplication public class Application {     public static void main(String[] args) {         SpringApplication.run(Application.class);     } }  Debug启动应用,访问接口,会被重定向到默认登录页。  使用Spring Security自动创建的用户(用户名为user,密码在启动日志中)进行登录验证。 用户验证时Spring Security会使用DelegatingPasswordEncoder类的matches方法进行密码匹配,提取的id为noop(prefixEncodedPassword有{noop}前缀,应用启动时,如果没有用户与用户源的相关配置,Spring Security会创建一个默认用户,即一个UserDetails实例,该实例的密码就是prefixEncodedPassword),因此委托给NoOpPasswordEncoder进行密码匹配。 NoOpPasswordEncoder类的matches方法只是简单的字符串匹配,上图的rawPassword和encodedPassword很显然是匹配的。      public boolean matches(CharSequence rawPassword, String encodedPassword) {         return rawPassword.toString().equals(encodedPassword);     } 验证成功。 配置PasswordEncoder 增加配置:  package com.kaven.security.config;  import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder;  @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {           // 重写验证处理的配置     @Override     protected void configure(AuthenticationManagerBuilder auth) throws Exception {         auth.userDetailsService(new UserDetailsServiceImpl()).passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());     }      // 自定义的用户服务     public static class UserDetailsServiceImpl implements UserDetailsService {                  // 使用PasswordEncoderFactories工厂创建DelegatingPasswordEncoder实例作为该用户服务的密码编码器         private static final PasswordEncoder PASSWORD_ENCODER = PasswordEncoderFactories.createDelegatingPasswordEncoder();          @Override         public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {             // TODO 查找数据库             // 使用密码编码器对原始密码进行编码             String encodedPassword = PASSWORD_ENCODER.encode("itkaven");             // 默认存在该用户名的用户,并且原始密码都为itkaven,角色都为USER和ADMIN             return User.withUsername(username).password(encodedPassword).roles("USER", "ADMIN").build();         }     } } Debug启动应用,访问接口,然后进行验证登录,由于自定义的用户服务默认任意用户名的用户都存在,并且原始密码都为itkaven,角色都为USER和ADMIN,因此登录时用户名可任意,但密码必须为itkaven才能通过验证。  客户端进行验证登录时,Spring Security通过用户服务加载匹配用户名的UserDetails实例,而博主自定义的用户服务直接默认该UserDetails实例存在,并且设置默认的密码(编码后的密码,使用UserDetailsServiceImpl类中的PASSWORD_ENCODER进行编码)与角色(权限)。密码匹配使用在重写验证处理的配置时指定的密码编码器来完成(passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()))。  id为bcrypt,使用BCryptPasswordEncoder进行密码匹配,因为通过自定义的用户服务加载的UserDetails实例的密码就是DelegatingPasswordEncoder的{bcrypt}前缀与BCryptPasswordEncoder对原始密码(itkaven)的编码的拼接,最后会返回true。  所以,密码编码器在用户验证时用于密码的匹配,以及创建UserDetails实例时对密码进行编码(可选,如果是基于用户服务加载的UserDetails实例创建的新实例,新实例一般不更改该UserDetails实例的密码,因此,通过用户服务加载的UserDetails实例的密码应该是编码后的密码),因此密码的编码与匹配过程需要使用相同的密码编码器,不然一样的原始密码也有可能匹配不成功。  Spring Security的密码编码器PasswordEncoder的介绍与Debug分析就到这里,如果博主有说错的地方或者大家有不同的见解,欢迎大家评论补充。 ———————————————— 原文链接:https://blog.csdn.net/qq_37960603/article/details/122318269 
  • [技术干货] BCryptPasswordEncoder的使用
    在 Spring Security 中有一个加密的类 BCryptPasswordEncoder ,它的使用非常的简单而且也比较有趣。让我们来看看它的使用。 BCryptPasswordEncoder 的使用         首先创建一个 SpringBoot 的项目,在创建项目的时候添加 Spring Security 的依赖。然后我们添加一个测试类,写如下的代码: final private String password = "123456"; @Test public void TestCrypt() {     BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();       String encode1 = bCryptPasswordEncoder.encode(password);     System.out.println("encode1:" + encode1);          String encode2 = bCryptPasswordEncoder.encode(password);     System.out.println("encode2:" + encode2); }         上面的代码中,首先实例化了一个 BCryptPasswordEncoder 类,然后使用该类的 encode 方法对同一个明文字符串进行了加密,并输出。运行上面的代码,查看输出。  encode1:$2a$10$SqbQb0pD3KYrH7ZVTWdRZOhPAelQqa..lUnysXoWag6RvMkyC5SE6 encode2:$2a$10$0sjBLlwrrch2EjgYls197e9dGRCMbQ7KUIt/ODPTSU0W.mEPaGkfG         从上面的输出可以看出,同一个明文加密两次,却输出了不同的结果。是不是很神奇?但是这样有一个问题,如果使用 BCryptPasswordEncoder 去加密登录密码的话,还能进行验证么?当然是可以验证的。验证的话,使用的是 BCryptPasswordEncoder 的 matches 方法,代码如下。  final private String password = "123456";   @Test public void TestCrypt() {     BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();       String encode1 = bCryptPasswordEncoder.encode(password);     System.out.println("encode1:" + encode1);       boolean matches1 = bCryptPasswordEncoder.matches(password, encode1);     System.out.println("matches1:" + matches1);       String encode2 = bCryptPasswordEncoder.encode(password);     System.out.println("encode2:" + encode2);       boolean matches2 = bCryptPasswordEncoder.matches(password, encode2);     System.out.println("matches2:" + matches2); }         使用 matches 方法可以对加密前和加密后是否匹配进行验证。输出如下:  encode1:$2a$10$qxU.rFLeTmZg47FyqJlZwu.QNX9RpEvqBUJiwUvUE0p4ENR.EndfS matches1:true encode2:$2a$10$NyGEOsQ1Hxv2gvYRmaEENueORlVDtSqoB/fHN76KkvQDeg7fbTy22 matches2:true        可以看到两次加密后的字符串虽然不同,但是通过 matches 方法都可以匹配出它们是 “123456” 这个明文加密的结果。同样很神奇,这是为什么呢?  encode 和 matches 方法的原理        我们通过源码来看看它们的原理吧。         首先我们将依赖的源码下载到本地,方便我们进行调试。开始我使用 IDEA 进行调试时是没有源码的,后来下载了源码,发现没有源码时调试的是 Class 文件,IDEA 提供了 Class 文件与源码等价的反编译代码。而源码逻辑性更好一些。         首先在 encode 代码处下断点,然后我们单步步入,去查看 encode 的实现,代码如下:  @Override public String encode(CharSequence rawPassword) {   if (rawPassword == null) {     throw new IllegalArgumentException("rawPassword cannot be null");   }   String salt = getSalt();   return BCrypt.hashpw(rawPassword.toString(), salt); }        直接运行到 return 语句处,查看一下 salt 的值,该值如下:  $2a$10$H73jYFFLWWf.VV/mwonqru         然后继续单步步入到 BCrypt.hashpw 方法内,该方法代码如下:  public static String hashpw(String password, String salt) {   byte passwordb[];     passwordb = password.getBytes(StandardCharsets.UTF_8);     return hashpw(passwordb, salt); }         该方法的重点同样是 hashpw 方法,没有做什么处理,继续进入 hashpw 方法中。代码如下:  public static String hashpw(byte passwordb[], String salt) {   BCrypt B;   String real_salt;   byte saltb[], hashed[];   char minor = (char) 0;   int rounds, off;   StringBuilder rs = new StringBuilder();     if (salt == null) {     throw new IllegalArgumentException("salt cannot be null");   }     int saltLength = salt.length();     if (saltLength < 28) {     throw new IllegalArgumentException("Invalid salt");   }     if (salt.charAt(0) != '$' || salt.charAt(1) != '2') {     throw new IllegalArgumentException("Invalid salt version");   }   if (salt.charAt(2) == '$') {     off = 3;   }   else {     minor = salt.charAt(2);     if ((minor != 'a' && minor != 'x' && minor != 'y' && minor != 'b') || salt.charAt(3) != '$') {       throw new IllegalArgumentException("Invalid salt revision");     }     off = 4;   }     // Extract number of rounds   if (salt.charAt(off + 2) > '$') {     throw new IllegalArgumentException("Missing salt rounds");   }     if (off == 4 && saltLength < 29) {     throw new IllegalArgumentException("Invalid salt");   }   rounds = Integer.parseInt(salt.substring(off, off + 2));     real_salt = salt.substring(off + 3, off + 25);   saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);     if (minor >= 'a') {     passwordb = Arrays.copyOf(passwordb, passwordb.length + 1);   }     B = new BCrypt();   hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 0x10000 : 0);     rs.append("$2");   if (minor >= 'a') {     rs.append(minor);   }   rs.append("$");   if (rounds < 10) {     rs.append("0");   }   rs.append(rounds);   rs.append("$");   encode_base64(saltb, saltb.length, rs);   encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1, rs);   return rs.toString(); }         代码很长,但是真正关键的代码在上面第 43 、44 和 51 行的位置处,43 行处获取真正的 salt ,44 行是使用 base64 进行解码,然后 51 行用 密码、salt 进行处理。在来看看返回值是 rs,在第 63 行和 64 行,对 salt 进行 base64 编码后放入了 rs 中,然后对 hashed 进行 base64 编码后也放入了 rs 中,最后 rs.toString() 返回。          虽然上面代码很长,其实真正关键的就只有上面我提到的几句,其余的部分不用看。我们接着看 matches 的源码,同样单步进入,代码如下:  @Override public boolean matches(CharSequence rawPassword, String encodedPassword) {   if (rawPassword == null) {     throw new IllegalArgumentException("rawPassword cannot be null");   }   if (encodedPassword == null || encodedPassword.length() == 0) {     this.logger.warn("Empty encoded password");     return false;   }   if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) {     this.logger.warn("Encoded password does not look like BCrypt");     return false;   }   return BCrypt.checkpw(rawPassword.toString(), encodedPassword); }          单步到第 14 行的 return 处,这里调用 BCrypt.checkpw 的方法,rawPassword.toString() 是我们的密码,即 ”123456“, 后面的 encodePassword 是我们加密后的密码,单步步入进去,代码如下:  public static boolean checkpw(String plaintext, String hashed) {   return equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed)); }         这里只有一行代码,但是代码中同样调用了前面的 hashpw 这个方法,传入的参数是 plaintext 和 hashed,plaintext 是我们的密码,即 “123456”, hashed 是加密后的密码。到这里基本就明白了。hashed 在进入 hashpw 函数后,会通过前面说到第 43 行代码取出真正的 salt,然后对通过 salt 和 我们的密码进行加密,这样流程就串联起来了。  总结        当时看到使用 BCryptPasswordEncoder 时,同样的密码可以生成不同的 密文 而且还可以通过 matches 方法进行匹配验证,觉得很神奇。后来经过调试发现,密文中本身包含了很多信息,包括 salt 和 使用 salt 加密后的 hash。因为每次的 salt 不同,因此每次的 hash 也不同。这样就可以使得相同的 明文 生成不同的 密文,而密文中包含 salt 和 hash,因此验证过程和生成过程也是相同的。 ———————————————— 原文链接:https://blog.csdn.net/easysec/article/details/121719949 
  • [技术干货] BCryptPasswordEncoder
    BCryptPasswordEncoder 是一种使用 BCrypt 加密算法来加密密码的方法。它是在 Spring Security 中用来加密用户密码的一个类,其目的是为了防止密码被明文存储在数据库中。BCrypt 是一种强哈希算法,它能很好地防止被暴力破解。采用SHA-256 +随机盐+密钥对密码进行加密 首先导入依赖 <dependency>             <groupId>org.springframework.security</groupId>             <artifactId>spring-security-core</artifactId>             <version>5.7.6</version>         </dependency> 编写配置类  package com.qingyun.kunba.config;   import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import java.security.SecureRandom;   @Data @Configuration @ConfigurationProperties(prefix = "encoder.crypt") public class PasswordConfig {     /**      * 加密强度      */     private int strength;     /**      * 干扰因子      */     private String secret;       @Bean     public BCryptPasswordEncoder passwordEncoder() {         //System.out.println("secret = " + secret);         //对干扰因子加密         SecureRandom secureRandom = new SecureRandom(secret.getBytes());         //对密码加密         return new BCryptPasswordEncoder(strength, secureRandom);     } }   配置yml  encoder:   crypt:     secret: ${random.uuid} # 随机的密钥,使用uuid     strength: 6 # 加密强度4~31,决定盐加密时的运算强度,超过10以后加密耗时会显著增加 接着就可以测试了  @Autowired private BCryptPasswordEncoder encoder;     @Test     void savePassword() {         // encode():对明文字符串进行加密         //注册用户时,使用SHA-256+随机盐+密钥把用户输入的密码进行hash处理,得到密码的hash值,然后将其存入数据库中。         String pw = encoder.encode("abcd");         System.out.println(pw);           // matches():对加密前和加密后是否匹配进行验证         //用户登录时,密码匹配阶段并没有进行密码解密(因为密码经过Hash处理,是不可逆的),         // 而是使用相同的算法把用户输入的密码进行hash处理,得到密码的hash值,然后将其与从数据库中查询到的密码hash值进行比较。         // 如果两者相同,说明用户输入的密码正确。         System.out.println(encoder.matches("asdsad", pw));         System.out.println(encoder.matches("abcd", pw));         } 测试结果如下 ———————————————— 原文链接:https://blog.csdn.net/qq_52458633/article/details/130990910 
  • [技术干货] weui 弹出 $.toast(“我是文本“,“text“); 总是带有图标
    在前段代码中引入 weui.css ,weuix.css 和js jquery-weui.min.js就可以使用weui的一些样式了按照官方的文档中                 $.toast("我是文本","text");弹出的样式应该如下: 但是我在实际中使用弹出的结果却是这个样子:   为什么 原因肯定是weui的样式和其他的样式冲突了 我们看看 $.toast 这一串代码: t.toast=function(t,a,r)    { "function"==typeof a&&(r=a);      var o,s="weui-icon-success-no-circle",       c=i.duration;      "cancel"==a?(o="weui-toast_cancel",s="weui-icon-cancel"):"forbidden"==a?(o="weui-toast--forbidden",s="weui-icon-warn"):"text"==a?o="weui-toast--text":"number"==typeof a&&(c=a), e('<i class="'+s+' weui-icon_toast"></i><p class="weui-toast_content">'+(t||"已经完成")+"</p>",o),setTimeout(function(){n(r)},c)} 大概能看出来 cancel ,forbidden,text 这些都是一些选项,当选择而不同的时候,出现不同的样式 text 应该是没有图标,为什么有图标了 我在页面上设置100,来查看这个样式 可以看到的是图标来源于这一部分 我们选中 i 这个标签,看右侧使用的样式:    果然是其他的样式干扰的 如果我们引入的其他的css作用不大,我们可以找到直接删除了 或者将 important 删除了 最终结果:  因为这次的样式调试用了很长的时间,特此记录 ———————————————— 原文链接:https://blog.csdn.net/datouniao1/article/details/111311058 
总条数:692 到第
上滑加载中