-
项目中引入的本地的jar包,在自己本地运行也是没有任何问题,但是在打包发布的时候就会报错找不到自己引入的文件 这是本地lib下的jar包 在打包运行后报错NoClassDefFoundError,找不到文件: 本地启动的时候自己本地项目知道根据你导入jar的路径去加载jar包,所以会没问题。但是打包之后,这个jar包是不会被打包进去的,导致报错。所以,我们想要正常运行,就需要把自己的jar包打包到项目中; 1.首先在pom中添加本地jar包的依赖 其中groupId artifactId 和version自己随便写。scope作用于定义为system,systemPath定义为jar包在项目中的路径(${basedir}就是项目的根目录): 2.光做这些还是不够的,这样只能保证我们在本地能够正常运行,打包还是不会被打包进去,所以说我们需要在引入的springboot的maven插件中告诉maven,将我们的刚刚引入的作用域为system的本地jar也打包进来 ———————————————— 原文链接:https://blog.csdn.net/Handsome__c/article/details/121776225
-
在很多地方看见add as library,但就是不知道这是干啥的。上网搜,发现并没有直接介绍这个的,经过一段时间的信息搜集,总结出以下几个知识点,希望给一些和我一样的小小白经验学习,希望每个小白都能成为自己心中向往的大佬。 一. add as library是什么? add as library是IntelliJ IDEA里面的一个功能,当然eclipse里面也有,多用于java开发和Android开发。 二. add as library有什么用? 多用于将将外部jar(lib / * .jar)添加到IntelliJ IDEA项目。 通俗点说,就是你做项目的时候需要一些外部jar包,比如我们用java连接数据库时用到了阿里巴巴公司的数据库连接池Druid,我们就需要导入Druid相关的jar包和配置文件,这些jar包和配置文件就属于外部jar包和配置文件。一般我们都是在项目路径下单独再建一个libs目录来存放这些东西。但直接复制粘贴到这个lib文件夹里面是不够的,我们还要把这些文件和jar包 add as library 来保证各种依赖关系。 三. add as library如何调出? 点击你要add as library的文件或jar包,右键选择add as library即可。 当然还有其他当如jar包的方法,还比较常用的是点File->Project Structure->Moudules->Dependenceies-> 右侧的 + 即可添加jar包 ———————————————— 原文链接:https://blog.csdn.net/doubleguy/article/details/104947149
-
一、什么是JAR包 JAR文件的全称是Java Archive File,即Java档案文件。JAR文件是一种压缩文件,与常见的ZIP压缩文件兼容,被称为JAR包。JAR文件与zip文件的主要区别是在JAR文件中默认包含了一个名为META-INF/MANIFEST.MF的清单文件,这个清单文件是在生成JAR文件时系统自动创建的 二、打JAR包步骤 1.先创建一个要打包成JAR包的类 2.File -> Project Structrue -> Artifacts -> + -> JAR -> from modules with dependencies… 3.在Main Class中选择要打包的类 project -> 选择打包的类 -> 然后OK project -> 点击OK后,会弹出错误提示框,关闭该提示框,并关闭类选择框 点击OK 4.Apply -> OK 5.Build -> Build Artifacts 6.点击Build后就会再之前选择的jar存放目录中生成jar包 三、jar包使用 方式一:java -jar 命令 CMD 中打开刚刚创建jar包的目录,运行命令 java -jar myProject.jar 显示运行结果 方式二:导入jar包 (1)File -> Project Structrue -> Modules ->Dependencies -> + -> jARs or directories… (2)在jar包的存放目录中找到jar包,然后点击OK (3)选择jar包 -> Apply -> OK (4)然后就可以在External Libraries中看到导入的jar包 (5)将之前的MyJar类删除掉,然后新建一个测试类Test,测试一下jar包是否可以,这里可以正常使用 ———————————————— 原文链接:https://blog.csdn.net/ZZQHELLO2018/article/details/124431906
-
最近接手了一个后台管理项目,项目使用了Element-ui组件, 使用了Select 下拉菜单展示并选择内容,使用了cascader做级联城市选择器 看了官方文档我们知道可以使用v-model绑定数据,设置初始值/默认值 <el-form-item label="班级" prop="classId"> <el-select v-model="form.classId" placeholder="请选择班级"> <el-option v-for="(value) of classList" :key="value.classId" :label="value.className" :value="value.classId"></el-option> </el-select> </el-form-item> <el-form-item label="机构地址" prop="district"> <el-cascader style="width: 216px;" v-model="form.district" :options="dataDistrict" @change="handleChange" > </el-cascader> </el-form-item> 问题来了,明明已经设置了v-model但是没有显示,百度发现问题在于数据类型,form.classId的值为 String ,而 el-select列表的 value 为 Number,el-cascader的value是Array,form.district却是JSON字符串,并不全等,导致无法找到对应的项,把数据类型进行转换后就能正常显示啦 ———————————————— 原文链接:https://blog.csdn.net/weixin_43144209/article/details/120891475
-
一、背景 在写项目的过程中,突然发现el-select这个组件,绑定的值没办法显示name,而是直接显示value。在编辑项目的弹框中,要显示项目绑定了哪个数据库(项目外连数据库表主键ID),结果发现直接把关联数据库的ID显示了出来,这与我的设想不一致,因此来找找是什么问题 二、解决 有问题的,有文档的当然第一时间去看文档,一看数据类型是string。 value-key 作为 value 唯一标识的键名,绑定值为对象类型时必填 数据类型string 再去检查项目传值,发现el-option绑定的list中key的类型是string,但v-model绑定的值是int,所以就导致了无法显示正确的名字,而显示主键ID 知道问题解决起来就很简单了,直接parseInt()转一下数据类型 <el-form-item label="归属数据库:" prop="databaseId"> <el-select v-model="projectForm.databaseId" placeholder="归属项目" style="width: 400px"> <el-option v-for="item in databaseList" :key="item.id" :value="parseInt(item.id)" :label="item.name" /> </el-select> </el-form-item> 但是可以的话,还是尽量数据格式一致,如果都是int的话,也是能可以正确显示的,那就无须做额外的处理了 ———————————————— 原文链接:https://blog.csdn.net/weixin_40295575/article/details/124250581
-
在前段代码中引入 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
-
微信分享网页不显示缩略图片的原因为规范自定义分享链接功能在网页上的使用,自2017年4月25日起,JSSDK“分享到朋友圈”及“发送给朋友”接口,自定义的分享链接,其域名或路径必须与当前页面对应的公众号JS安全域名一致,否则将调用失败。也就是说如果你想你的网站在被分享时显示缩略图,那么你要有一个公众号,并且在公众号内设置JS安全域名,也就是添加你网站的网址,这是必须的,如果这一步不能实现,那么你不用往下看了。一、最快的解决方法如果你不是专业的程序员,虽然微信官方给出了代码,以及我下面也会再次给出代码,你可能仍然无法实现。因为在微信官方代码与你的网站之间,还需要二次开发对接代码,把微信代码与你的网站连接起来,这个功能才能最终实现。同时因为每个网站的源程序不一样,所以这个对接代码也不能通用,需要根据你的网站程序单独开发。请在微信内打开链接分享测试。二、微信官方办法步骤一:绑定域名先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。步骤二:设置开发者密码和IP白名单登录微信公众平台,进入开发――基本配置――开发者密码(AppSecret)和IP白名单步骤三:引入JS文件在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.2.0.js备注:支持使用AMD/CMD 标准模块加载方法加载步骤四:通过config接口注入权限验证配置所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用)。wx.config({debug: true,// 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。appId: ‘‘,// 必填,公众号的唯一标识timestamp:,// 必填,生成签名的时间戳nonceStr: ‘‘,// 必填,生成签名的随机串signature: ‘‘,// 必填,签名,见附录1jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2});微信开发文档说明:https://mp.weixin.qq.com/advanced/wiki?t=t=resource/res_main&id=mp1421141115微信JS 接口签名校验工具https://mp.weixin.qq.com/debug/cgi-bin/sandBox?t=jsapisign原文链接:https://blog.csdn.net/weixin_31499719/article/details/117834995
-
用到了wrapper,ge、le、ne、eq等的用法,及多表查询自写sql整理资料记录一下,以备后续复习。 目录------------(可点击相应目录直接跳转) 一、条件构造器关系介绍 条件构造器关系介绍 : wapper介绍 : 二、项目实例 1、根据主键或者简单的查询条件进行查询 2、MyBatis-Plus还提供了Wrapper条件构造器,具体使用看如下代码: 三、具体使用操作 1、ge、gt、le、lt、isNull、isNotNull 2、eq、ne 3、between、notBetween 4、allEq 5、like、notLike、likeLeft、likeRight 6、in、notIn、inSql、notinSql、exists、notExists 7、or、and 8、嵌套or、嵌套and 9、orderBy、orderByDesc、orderByAsc 10、last 11、指定要查询的列 12、set、setSql 四、项目中实际应用代码实例 实例1--包含 eq相等的比较方法 实例2--包含 ge le ge等比较方法,及分页查询方法 实例3--多表查询,手写sql示例,五表联查 先了解一下内外连接: SQL内连接(INNER JOIN) SQL外连接(OUTER JOIN 一、条件构造器关系介绍 条件构造器关系介绍 : 上图绿色框为抽象类abstract 蓝色框为正常class类,可new对象 黄色箭头指向为父子类关系,箭头指向为父类 wapper介绍 : Wrapper : 条件构造抽象类,最顶端父类 AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件 QueryWrapper : Entity 对象封装操作类,不是用lambda语法 UpdateWrapper : Update 条件封装,用于Entity对象更新操作 AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。 LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper LambdaUpdateWrapper : Lambda 更新封装Wrapper 二、项目实例 1、根据主键或者简单的查询条件进行查询 /** * 通过单个ID主键进行查询 */ @Test public void selectById() { User user = userMapper.selectById(1094592041087729666L); System.out.println(user); } /** * 通过多个ID主键查询 */ @Test public void selectByList() { List<Long> longs = Arrays.asList(1094592041087729666L, 1094590409767661570L); List<User> users = userMapper.selectBatchIds(longs); users.forEach(System.out::println); } /** * 通过Map参数进行查询 */ @Test public void selectByMap() { Map<String, Object> params = new HashMap<>(); params.put("name", "张雨琪"); List<User> users = userMapper.selectByMap(params); users.forEach(System.out::println); } 2、MyBatis-Plus还提供了Wrapper条件构造器,具体使用看如下代码: /** * 名字包含雨并且年龄小于40 * <p> * WHERE name LIKE '%雨%' AND age < 40 */ @Test public void selectByWrapperOne() { QueryWrapper<User> wrapper = new QueryWrapper(); wrapper.like("name", "雨").lt("age", 40); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); } /** * 名字包含雨 * 年龄大于20小于40 * 邮箱不能为空 * <p> * WHERE name LIKE '%雨%' AND age BETWEEN 20 AND 40 AND email IS NOT NULL */ @Test public void selectByWrapperTwo() { QueryWrapper<User> wrapper = Wrappers.query(); wrapper.like("name", "雨").between("age", 20, 40).isNotNull("email"); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); } /** * 名字为王性 * 或者年龄大于等于25 * 按照年龄降序排序,年龄相同按照id升序排序 * <p> * WHERE name LIKE '王%' OR age >= 25 ORDER BY age DESC , id ASC */ @Test public void selectByWrapperThree() { QueryWrapper<User> wrapper = Wrappers.query(); wrapper.likeRight("name", "王").or() .ge("age", 25).orderByDesc("age").orderByAsc("id"); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); } /** * 查询创建时间为2019年2月14 * 并且上级领导姓王 * <p> * WHERE date_format(create_time,'%Y-%m-%d') = '2019-02-14' AND manager_id IN (select id from user where name like '王%') */ @Test public void selectByWrapperFour() { QueryWrapper<User> wrapper = Wrappers.query(); wrapper.apply("date_format(create_time,'%Y-%m-%d') = {0}", "2019-02-14") .inSql("manager_id", "select id from user where name like '王%'"); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); } /** * 查询王姓 * 并且年龄小于40或者邮箱不为空 * <p> * WHERE name LIKE '王%' AND ( age < 40 OR email IS NOT NULL ) */ @Test public void selectByWrapperFive() { QueryWrapper<User> wrapper = Wrappers.query(); wrapper.likeRight("name", "王").and(qw -> qw.lt("age", 40).or().isNotNull("email")); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); } /** * 查询王姓 * 并且年龄大于20 、年龄小于40、邮箱不能为空 * <p> * WHERE name LIKE ? OR ( age BETWEEN ? AND ? AND email IS NOT NULL ) */ @Test public void selectByWrapperSix() { QueryWrapper<User> wrapper = Wrappers.query(); wrapper.likeRight("name", "王").or( qw -> qw.between("age", 20, 40).isNotNull("email") ); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); } /** * (年龄小于40或者邮箱不为空) 并且名字姓王 * WHERE ( age < 40 OR email IS NOT NULL ) AND name LIKE '王%' */ @Test public void selectByWrapperSeven() { QueryWrapper<User> wrapper = Wrappers.query(); wrapper.nested(qw -> qw.lt("age", 40).or().isNotNull("email")) .likeRight("name", "王"); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); } /** * 查询年龄为30、31、32 * WHERE age IN (?,?,?) */ @Test public void selectByWrapperEight() { QueryWrapper<User> wrapper = Wrappers.query(); wrapper.in("age", Arrays.asList(30, 31, 32)); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); } /** * 查询一条数据 * limit 1 */ @Test public void selectByWrapperNine() { QueryWrapper<User> wrapper = Wrappers.query(); wrapper.in("age", Arrays.asList(30, 31, 32)).last("limit 1"); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); } 三、具体使用操作 注意:以下条件构造器的方法入参中的 column 均表示数据库字段 1、ge、gt、le、lt、isNull、isNotNull @Test public void testDelete() { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper .isNull("name") .ge("age", 12) .isNotNull("email"); int result = userMapper.delete(queryWrapper); System.out.println("delete return count = " + result); } SQL:UPDATE user SET deleted=1 WHERE deleted=0 AND name IS NULL AND age >= ? AND email IS NOT NULL 2、eq、ne 注意:seletOne返回的是一条实体记录,当出现多条时会报错 @Test public void testSelectOne() { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("name", "Tom"); User user = userMapper.selectOne(queryWrapper); System.out.println(user); } 3、between、notBetween 包含大小边界 @Test public void testSelectCount() { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.between("age", 20, 30); Integer count = userMapper.selectCount(queryWrapper); System.out.println(count); } SELECT COUNT(1) FROM user WHERE deleted=0 AND age BETWEEN ? AND ? 4、allEq @Test public void testSelectList() { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); Map<String, Object> map = new HashMap<>(); map.put("id", 2); map.put("name", "Jack"); map.put("age", 20);9 queryWrapper.allEq(map); List<User> users = userMapper.selectList(queryWrapper); users.forEach(System.out::println); } SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND name = ? AND id = ? AND age = ? 5、like、notLike、likeLeft、likeRight selectMaps返回Map集合列表 @Test public void testSelectMaps() { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper .notLike("name", "e") .likeRight("email", "t"); List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);//返回值是Map列表 maps.forEach(System.out::println); } SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND name NOT LIKE ? AND email LIKE ? 6、in、notIn、inSql、notinSql、exists、notExists in、notIn: notIn("age",{1,2,3})--->age not in (1,2,3) notIn("age", 1, 2, 3)--->age not in (1,2,3) inSql、notinSql:可以实现子查询 例: inSql("age", "1,2,3,4,5,6")--->age in (1,2,3,4,5,6) 例: inSql("id", "select id from table where id < 3")--->id in (select id from table where id < 3) @Test public void testSelectObjs() { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); //queryWrapper.in("id", 1, 2, 3); queryWrapper.inSql("id", "select id from user where id < 3"); List<Object> objects = userMapper.selectObjs(queryWrapper);//返回值是Object列表 objects.forEach(System.out::println); } SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND id IN (select id from user where id < 3) 7、or、and 注意:这里使用的是 UpdateWrapper 不调用or则默认为使用 and 连 @Test public void testUpdate1() { //修改值 User user = new User(); user.setAge(99); user.setName("Andy"); //修改条件 UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>(); userUpdateWrapper .like("name", "h") .or() .between("age", 20, 30); int result = userMapper.update(user, userUpdateWrapper); System.out.println(result); } UPDATE user SET name=?, age=?, update_time=? WHERE deleted=0 AND name LIKE ? OR age BETWEEN ? AND ? 8、嵌套or、嵌套and 这里使用了lambda表达式,or中的表达式最后翻译成sql时会被加上圆括号 @Test public void testUpdate2() { //修改值 User user = new User(); user.setAge(99); user.setName("Andy"); //修改条件 UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>(); userUpdateWrapper .like("name", "h") .or(i -> i.eq("name", "李白").ne("age", 20)); int result = userMapper.update(user, userUpdateWrapper); System.out.println(result); } UPDATE user SET name=?, age=?, update_time=? WHERE deleted=0 AND name LIKE ? OR ( name = ? AND age <> ? ) 9、orderBy、orderByDesc、orderByAsc @Test public void testSelectListOrderBy() { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.orderByDesc("id"); List<User> users = userMapper.selectList(queryWrapper); users.forEach(System.out::println); } SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 ORDER BY id DESC 10、last 直接拼接到 sql 的最后 注意:只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用 @Test public void testSelectListLast() { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.last("limit 1"); List<User> users = userMapper.selectList(queryWrapper); users.forEach(System.out::println); } SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 limit 1 11、指定要查询的列 @Test public void testSelectListColumn() { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.select("id", "name", "age"); List<User> users = userMapper.selectList(queryWrapper); users.forEach(System.out::println); } SELECT id,name,age FROM user WHERE deleted=0 12、set、setSql 最终的sql会合并 user.setAge(),以及 userUpdateWrapper.set() 和 setSql() 中 的字段 @Test public void testUpdateSet() { //修改值 User user = new User(); user.setAge(99); //修改条件 UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>(); userUpdateWrapper .like("name", "h") .set("name", "老李头")//除了可以查询还可以使用set设置修改的字段 .setSql(" email = '123@qq.com'");//可以有子查询 int result = userMapper.update(user, userUpdateWrapper); } UPDATE user SET age=?, update_time=?, name=?, email = '123@qq.com' WHERE deleted=0 AND name LIKE ? 四、项目中实际应用代码实例 (此部分更新于2022年7月20日) 实例1--包含 eq相等的比较方法 实例2--包含 ge le ge等比较方法,及分页查询方法 实例3--多表查询,手写sql示例,五表联查 先了解一下内外连接: 什么是连接表? 多表查询原理:将多个表通过笛卡尔积形成一个虚表,再根据查询条件筛选符合条件的数据。 在关系数据库中,数据分布在多个逻辑表中。 要获得完整有意义的数据集,需要使用连接来查询这些表 中的数据。 SQL Server支持多种 连接包括 INNER JOIN:内连接,关键字在表中存在至少一个匹配时返回行。 left join : 左连接,返回左表中所有的记录以及右表中连接字段相等的记录。 right join : 右连接,返回右表中所有的记录以及左表中连接字段相等的记录。 inner join : 内连接,又叫等值连接,只返回两个表中连接字段相等的行。 full join : 外连接,返回两个表中的行:left join + right join。 cross join : 结果是笛卡尔积,就是第一个表的行数乘以第二个表的行数。 GROUP BY:全外连接, 子句必须放在 WHERE 子句中的条件之后,必须放在 ORDER BY 子句之前 每种连接类型指定SQL Server如何使用一个表中的数据来选择另一个表中的行 SQL内连接(INNER JOIN) 返回两张表中符合连接条件的数据行 内连接是从结果表中删除与被连接表中未匹配行的所有行,所以内连接可能会丢失信息 SQL外连接(OUTER JOIN) 外连接(OUTER JOIN)分 为左连接、右连接和全连接 左连接:返回左表中的所以行,如果左表中行在右表中没有匹配行,则结果中右表中的列返回空值NULL 语法:SELECT * FROM 表1 LEFT OUTER JOIN 表2 ON 条件 eg:我们左连接Student表、Score表查询学生的成绩,SQL 语句如下: SELECT * FROM Student LEFT OUTER JOIN Score ON Student.id = Score.studentID 右 连 接:返回右表中的所以行,如果右表中行在左表中没有匹配行,则结果中左表中的列返回空值NULL 语法:SELECT * FROM 表1 RIGHT OUTER JOIN 表2 ON 条件 eg:我们右连接Student表、Score表查询学生的成绩,SQL 语句如下: SELECT * FROM Student RIGHT OUTER JOIN Score ON Student.id = Score.studentID 全连接:返回左表和右表中的所有行,当某行在另一表中没有匹配行,则另一表中的补NULL 这个是mybatis-plus插件中mapper的一个表查询写法,由多个表内连接或外连接组成的数据。 用到的表结构如下: 分别为问题库表、数据字典、数据字典类型、部门表、用户表 数据示例如下:obdis_problem表里去查type字段内容并返回对应的汉字语义 查询每个字段都进行一次表连接,拿到对应的值,得出一个表结果值,即A\B\C\D等等结果,最后拼起来返回前端展示即可。搞懂了这个,所有的多表查询就基本迎刃而解。 <!-- 问题库列表返回与查询--> <select id="searchMoreProblem" resultType="com.hollysys.obdis.vo.problem.SearchObdisProblem"> select p.id , p.level, A.dict_value as level_text, p.dept_id_mgr, B.org_name as deptIdMgr_text, p.dept_id_work, C.org_name as deptIdWork_text, p.station, D.dict_value as station_text, p.source, E.dict_value as source_text, p.categroy, F.dict_value as categroy_text, p.type, G.dict_value as type_text, p.dev_code, H.dict_value as devCode_text, p.check_time, p.check_user, I.username as checkUser_text, p.deadline, p.deadline_real, p.duty_user, J.username as dutyUser_text, p.correct_user, K.username as correctUser_text, p.state, p.problem, p.requirement, p.solution from obdis_problem p left join t_dict A on p.level = A.id left join t_dept B on p.dept_id_mgr = B.org_id left join t_dept C on p.dept_id_work = C.org_id left join t_dict D on p.station = D.id left join t_dict E on p.source = E.id left join t_dict F on p.categroy = F.id left join t_dict G on p.type = G.id left join t_dict H on p.dev_code = H.id left join t_user I on p.check_user = I.user_id left join t_user J on p.duty_user = J.user_id left join t_user K on p.correct_user = K.user_id where 1=1 <if test="searchObdisProblem.levelText != null and searchObdisProblem.levelText != ''"> and A.dict_value like concat('%', #{searchObdisProblem.levelText}, '%') </if> <if test="searchObdisProblem.deptIdMgrText != null and searchObdisProblem.deptIdMgrText != ''"> and B.org_name like concat('%', #{searchObdisProblem.deptIdMgrText}, '%') </if> <if test="searchObdisProblem.deptIdWorkText != null and searchObdisProblem.deptIdWorkText != '' "> and C.org_name like concat('%', #{searchObdisProblem.deptIdWorkText}, '%') </if> <if test="searchObdisProblem.stationText != null and searchObdisProblem.stationText != ''"> and D.dict_value = #{searchObdisProblem.stationText} </if> <if test="searchObdisProblem.sourceText != null and searchObdisProblem.sourceText != ''"> and E.dict_value = #{searchObdisProblem.sourceText} </if> <if test="searchObdisProblem.categroyText != null and searchObdisProblem.categroyText != ''"> and F.dict_value = #{searchObdisProblem.categroyText} </if> <if test="searchObdisProblem.typeText != null and searchObdisProblem.typeText != ''"> and G.dict_value = #{searchObdisProblem.typeText} </if> <if test="searchObdisProblem.devCodeText != null and searchObdisProblem.devCodeText != ''"> and H.dict_value = #{searchObdisProblem.devCodeText} </if> <if test="searchObdisProblem.checkTimeStart != null "> and p.check_time >= #{searchObdisProblem.checkTimeStart} </if> <if test="searchObdisProblem.checkTimeEnd != null "> and p.check_time <= #{searchObdisProblem.checkTimeEnd} order by p.check_time </if> <if test="searchObdisProblem.checkUserText != null and searchObdisProblem.checkUserText != ''"> and I.username = #{searchObdisProblem.checkUserText} </if> <if test="searchObdisProblem.deadline != null "> and p.deadline = #{searchObdisProblem.deadline} </if> <if test="searchObdisProblem.deadlineReal != null "> and p.deadline_real = #{searchObdisProblem.deadlineReal} </if> <if test="searchObdisProblem.dutyUserText != null and searchObdisProblem.dutyUserText != ''"> and J.username = #{searchObdisProblem.dutyUserText} </if> <if test="searchObdisProblem.correctUserText != null and searchObdisProblem.correctUserText != ''"> and K.username = #{searchObdisProblem.correctUserText} </if> <if test="searchObdisProblem.state != null and searchObdisProblem.state != ''"> and state = #{searchObdisProblem.state} </if> <if test="searchObdisProblem.problem != null and searchObdisProblem.problem != ''"> and p.problem like concat('%', #{searchObdisProblem.problem}, '%') </if> <if test="searchObdisProblem.requirement != null and searchObdisProblem.requirement != ''"> and p.requirement like concat('%', #{searchObdisProblem.requirement}, '%') </if> <if test="searchObdisProblem.solution != null and searchObdisProblem.solution != ''"> and p.solution like concat('%', #{searchObdisProblem.solution}, '%') </if> </select> -------------------------------------------------------------------------------------以下无正文------------------- 参考文档 1、(五)springboot + mybatis plus强大的条件构造器queryWrapper、updateWrapper_青蛙与大鹅的博客-CSDN博客_querywrapper.eq 2、https://blog.csdn.net/kepengs/article/details/112345870 3、Wrapper使用_weixin_39615889的博客-CSDN博客_wrapper使用 4、MyBatis-Plus Wrapper条件构造器查询大全_IT贱男的博客-CSDN博客_wrapper.like 5、https://blog.csdn.net/qq_48209375/article/details/114446611 6、https://blog.csdn.net/TBDBTUO/article/details/123550498 ———————————————— 原文链接:https://blog.csdn.net/qq_39715000/article/details/120090033
-
场景介绍 人有时会身兼数职,需要查找出其中担任某一职务的都有哪些人,如下面position字段,不同的职务用数字表示,多个职务以逗号隔开。 先要查找出担任1职务的人员,通过以下两种方式来查询。 方式一 采用模糊查询,匹配出1职务的记录,如下SQL: select * from user where position like '%1%' 查询结果如下,仔细观察你会发现position为10的也被查出来了,但这个不符合业务要求。 方式二 采用MySQL的原生函数find_in_set(str,array)来查询,SQL如下: select * from user where find_in_set(1,position) 查询结果如下,符合要求。 函数介绍 FIND_IN_SET(str,strlist),注意其中strlist只识别英文逗号。 https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_find-in-set ———————————————— 原文链接:https://blog.csdn.net/loongshawn/article/details/78611636
-
这篇文章主要介绍了使用Vant如何完成各种Toast提示框,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教1)使用前的需要安装Vant奥。 2)在main.js里面引入Toast import { Toast } from 'vant'; Vue.use(Toast); (3)在页面使用:(根据步骤代码可以运行奥 Toast.vue文件)(上面截图的,在下面代码都有栗子奥)。 <template> <!-- Toast提示 --> <div id="toast"> <van-button plain type="primary" @click="toToast">普通文字提示</van-button> <van-button plain type="primary" @click="toLoading">加载转圈提示</van-button> <van-button plain type="primary" @click="toSuccessTip">成功提示</van-button> <van-button plain type="primary" @click="toFailTip">失败提示</van-button> <van-button plain type="primary" @click="toCustomIcon">自定义图标提示</van-button> <van-button plain type="primary" @click="toCustomImage">自定义图片提示</van-button> </div> </template> <script> export default { data() { return { msg: '' } }, // 引入 Toast 组件后,会自动在 Vue 的 prototype 上挂载 $toast 方法,便于在组件内调用。 methods: { // 普通文字提示 toToast() { this.$toast({ message:'我是需要提示的文字', position:'top' }); }, // 加载转圈提示 toLoading() { this.$toast.loading({ mask: true, message: '加载中...' }); }, // 成功提示 toSuccessTip() { this.$toast.success({ message:'成功的提示文案', }) }, // 失败提示 toFailTip() { this.$toast.fail({ message:'失败的提示文案' }) }, // 自定义图标 toCustomIcon() { this.$toast({ icon: 'star-o', // 找到自己需要的图标 message: '我是提示文字' }) }, //自定义图片提示 toCustomImage() { this.$toast({ icon:'https://www.baidu.com/favicon.ico', message:'我是提示文字' }) } }, mounted() { } } </script> <style> </style> (4)Toast的相关API和Options 点击去查看 更新补充 position 里面的高度不局限与 top bottom等,也可设置数值,例如: this.$toast({ message:'我是需要提示的文字', position:'200px' // 弹框的位置可以自己设置 }); Vant Toast用法 题外话就不多讲了,这是围绕vue.js写的,爱上vue.js 1.首先引入 import { Toast } from 'vant' 写个小列子 绑定一个click事件 2.写事件 在methods写方法 showToast() { this.$toast({ message: "今日签到+3", }) }, 一个简单的toast提示成就好了 原文链接:https://www.jb51.net/article/249445.htm
-
一、静态web页面,一般指html:1、在静态Web程序中,客户端使用Web浏览器(IE、FireFox等)经过网络(Network)连接到服务器上,使用HTTP协议发起一个请求(Request),告诉服务器我现在需要得到哪个页面,所有的请求交给Web服务器,之后WEB服务器根据用户的需要,从文件系统(存放了所有静态页面的磁盘)取出内容。之后通过Web服务器返回给客户端,客户端接收到内容之后经过浏览器渲染解析,得到显示的效果。2、为了让静态web页面显示更加好看,使用javascript/VBScript/ajax(AJAX即“Asynchronous Javascript And XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术。)但是这些特效都是在客户端上借助于浏览器展现给用户的,所以在服务器上本身并没有任何的变化。3、静态web无法连接数据库;4、静态web资源开发技术:HTML;5、由于现在的web页面中,大量使用JS,导致浏览器打开页面,就会占用大量的内存,服务端的压力是减轻了,但压力转移到了客户端。二、动态web页面,一般指jsp:动态WEB中,程序依然使用客户端和服务端,客户端依然使用浏览器(IE、FireFox等),通过网络(Network)连接到服务器上,使用HTTP协议发起请求(Request),现在的所有请求都先经过一个WEB Server来处理。如果客户端请求的是静态资源(*.htm或者是*.htm),则将请求直接转交给WEB服务器,之后WEB服务器从文件系统中取出内容,发送回客户端浏览器进行解析执行。如果客户端请求的是动态资源(*.jsp、*.asp/*.aspx、*.php),则先将请求转交给WEB Container(WEB容器),在WEB Container中连接数据库,从数据库中取出数据等一系列操作后动态拼凑页面的展示内容,拼凑页面的展示内容后,把所有的展示内容交给WEB服务器,之后通过WEB服务器将内容发送回客户端浏览器进行解析执行。再进一步深入分析动态web的访问过程:浏览器访问web时,看似是直接访问的jsp页面,其实是,最先到达的地方是服务器,服务器创建好req和resp对象后再给jsp页面使用。在jsp中完成设置字符集和取得表单参数后再调用servlet,完成业务处理。然后返回到jsp,jsp就会生成相应的html页面。该页面会返回到服务器,再由服务器,通过response对象返回给客户端。为什么需要web服务器?(web server)1)不管什么web资源,想被远程计算机访问,都必须有一个与之对应的网络通信程序,当用户来访问时,这个网络通信程序读取web资源数据,并把数据发送给来访者。2)WEB服务器就是这样一个程序,它用于完成底层网络通迅,处理http协议。使用这些服务器,We应用的开发者只需要关注web资源怎么编写,而不需要关心资源如何发送到客户端手中,从而极大的减轻了开发者的开发工作量。常用动态web资源开发技术:JSP/Servlet、ASP、PHP等。三、关于两者区别的简单直接的描述1、静态页面就是设计者把页面上所有东西都设定好、做死了,然后放上去,不管是谁在任何时候看到的页面内容都是一样的,一成不变(除非手动修改页面内容)。静态html页面文件,可以直接用本地的浏览器打开。比如:file:///Users/Phil/Documents/DevOps/HBuilderProjects/testJSP/index.html。2、动态页面的内容一般都是依靠服务器端的程序来生成的,不同人、不同时候访问页面,显示的内容都可能不同。网页设计者在写好服务器端的页面程序后,不需要手工控制,页面内容会按照页面程序的安排自动更改变换。3、html是w3c规范的一种网页书写格式,是一种统一协议语言,静态网页。我们上网看的网页都是大部分都是基于html语言的。jsp是一种基于动态语言,jsp可以实现html的所有任务。4、HTML(Hypertext Markup Language)文本标记语言,它是静态页面,和JavaScript一样解释性语言,为什么说是解释性语言呢?因为,只要你有一个浏览器那么它就可以正常显示出来,而不需要指定的编译工具,只需在TXT文档中写上HTML标记就可以正常显示。JSP(Java Server Page)是Java服务端的页面,所以它是动态的,它是需要经过JDK编译后把内容发给客户端去显示,我们都知道,Java文件编译后会产生一个class文件,最终执行的就是这个class文件。5、JSP的前身是servlet。6、html和jsp的表头不一样,这个是JSP的头“ <%@ page language="java" import="java.util.*" pageEncoding="gbk"%>”在表头中有编码格式和倒入包等。也是很好区分的,在jsp中用<%%>就可以写Java代码了,而html没有<%%>。7、,不认识jsp或者asp什么什么的,但是有时候界面需要逻辑控制,所以我们就用相应的技术来实现,这样比较方便。而jsp在后台通过服务器解析为相应的html,然后在供浏览器识别显示。例如<% if(flag == a){<label>a<label>}else {<label>b<label>}%>服务器在读取到这段代码后,根据相应的业务逻辑,编译成相应的servlet,再由servlet输出到页面(输出的就是html)。各自的优缺点:一.ajax的优点: 1.开发过程中前端与后端脱离,交互通过JSON传输来实现 2.跨平台能力更强,依托于浏览器的支持 3.使后台数据接口能够得到复用二.ajax的缺点: 1.开发难度大,考虑浏览器的兼容性 2.页面请求过多 3.属于后加载,无法被爬虫爬到 4.接口代码需要新增很多 5.无法直接显示java实体类对象,需要转换为json格式三.jsp的优点: 1.可被爬虫爬到 2.减少请求次数 3.不用考虑浏览器的兼容性四.jsp的缺点: 1.增大了服务器的压力 2.前端与后端未脱离,拖慢开发进度 3.过于依赖java运行环境 4.复用较低。原文链接:https://www.pianshen.com/article/83231978420/
-
在 Spring Security 中有一个加密的类 BCryptPasswordEncoder ,它的使用非常的简单而且也比较有趣。让我们来看看它的使用。BCryptPasswordEncoder 的使用首先创建一个 SpringBoot 的项目,在创建项目的时候添加 Spring Security 的依赖。然后我们添加一个测试类,写如下的代码:final private String password = "123456";@Testpublic 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..lUnysXoWag6RvMkyC5SE6encode2:$2a$10$0sjBLlwrrch2EjgYls197e9dGRCMbQ7KUIt/ODPTSU0W.mEPaGkfG从上面的输出可以看出,同一个明文加密两次,却输出了不同的结果。是不是很神奇?但是这样有一个问题,如果使用 BCryptPasswordEncoder 去加密登录密码的话,还能进行验证么?当然是可以验证的。验证的话,使用的是 BCryptPasswordEncoder 的 matches 方法,代码如下。final private String password = "123456";@Testpublic 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.EndfSmatches1:trueencode2:$2a$10$NyGEOsQ1Hxv2gvYRmaEENueORlVDtSqoB/fHN76KkvQDeg7fbTy22matches2:true可以看到两次加密后的字符串虽然不同,但是通过 matches 方法都可以匹配出它们是 “123456” 这个明文加密的结果。同样很神奇,这是为什么呢?encode 和 matches 方法的原理我们通过源码来看看它们的原理吧。首先我们将依赖的源码下载到本地,方便我们进行调试。开始我使用 IDEA 进行调试时是没有源码的,后来下载了源码,发现没有源码时调试的是 Class 文件,IDEA 提供了 Class 文件与源码等价的反编译代码。而源码逻辑性更好一些。首先在 encode 代码处下断点,然后我们单步步入,去查看 encode 的实现,代码如下:@Overridepublic 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 roundsif (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 的源码,同样单步进入,代码如下:@Overridepublic 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介绍 Spring Security 中提供了 BCryptPasswordEncoder用于用户密码的加密和验证,这里讲解一下该 PasswordEncoder 的实现逻辑. 首先 BCryptPasswordEncoder 使用了 BCrypt 算法来对密码实现加密和验证。由于 BCrypt本身是一种 单向Hash算法,因此它和我们日常用的 MD5一样,通常情况下是无法逆向解密的。 在 BSD系统中 BCrypt 算法主要用来替代 md5 加密算法,它使用了一种可变版本的Blowfish流密码算法。通过多次加盐和随机数,大大提高了通过彩虹表撞库的形式破解密码的难度.因此这套加密算法被广泛用于许多系统的密码加密当中。目前来看该加密算法没有已知的漏洞。在新应用中BCrypt加密算法是推荐的四种密码散列算法之一. 流密码加密 相较于 分组加密方案和消息填充算法,流密码加密算法方案本身就可以加密任意长度的数据,无需密码分组和消息填充。 加密参数 在 BCryptPasswordEncoder 加密过程中,会提供主要参数来辅助加密: salt:可选的加盐字符串,如果未指定,则会自动生成一个加盐字符.取值范围为 [./0-9A-Za-z] rounds 一个可选的随机数,默认为 12 ,取值范围为 4 到 31 之间,左闭右闭. version 用于指定一个 BCrypt 算法版本,如果未指定,则默认为 2b,可选版本有: 2,BCrypt 的第一个版本,由于存在一个小的安全缺陷,目前已不再使用 2a在一些实现中存在罕见的安全缺陷,被 2b版本取代 2y使用 crypt_blowfish BCrypt算法实现格式化,除了名称外与 2b无明显区别 官方最新的 BCrypt 算法版本,目前为默认版本 hash算法是一种单向密码体制,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程。同时,哈希函数可以将任意长度的输入经过变化以后得到固定长度的输出。 二.BCryptPasswordEncoder中的方法: encode-加密:在BCryptPasswordEncoder中使用encode方法对密码进行加密,因为是通过hash算法进行加密,同样的密码输出的密文是不同的 matches-解密/匹配: 虽然通过encode方法加密的密码是不能解密的,但是在BCryptPasswordEncoder中提供了一个matches方法来匹配密码,它的原理是把需要配对的密码经过同一个hash函数计算,把计算得到的hash值到数据库中匹配,相同的hash值则说明是同一个密码。 ———————————————— 原文链接:https://blog.csdn.net/superxmh/article/details/118497296
-
单例模式简介单例模式是 Java 中最简单,也是最基础,最常用的设计模式之一。在运行期间,保证某个类只创建一个实例,保证一个类仅有一个实例,并提供一个访问它的全局访问点。下面就来讲讲Java中的N种实现单例模式的写法。饿汉式public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } }这是实现一个安全的单例模式的最简单粗暴的写法,这种实现方式我们称之为饿汉式。之所以称之为饿汉式,是因为肚子很饿了,想马上吃到东西,不想等待生产时间。这种写法,在类被加载的时候就把Singleton实例给创建出来了。饿汉式的缺点就是,可能在还不需要此实例的时候就已经把实例创建出来了,没起到lazy loading的效果。优点就是实现简单,而且安全可靠。懒汉式public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }相比饿汉式,懒汉式显得没那么“饿”,在真正需要的时候再去创建实例。在getInstance方法中,先判断实例是否为空再决定是否去创建实例,看起来似乎很完美,但是存在线程安全问题。在并发获取实例的时候,可能会存在构建了多个实例的情况。所以,需要对此代码进行下改进。public class SingletonSafe { private static volatile SingletonSafe singleton; private SingletonSafe() { } public static SingletonSafe getSingleton() { if (singleton == null) { synchronized (SingletonSafe.class) { if (singleton == null) { singleton = new SingletonSafe(); } } } return singleton; } }这里采用了双重校验的方式,对懒汉式单例模式做了线程安全处理。通过加锁,可以保证同时只有一个线程走到第二个判空代码中去,这样保证了只创建 一个实例。这里还用到了volatile关键字来修饰singleton,其最关键的作用是防止指令重排。什么是指令重排?private Singleton(){ 1 int x = 10; 2 int y =3 0; 3 Object o = new Object(); }上面的构造函数 Singleton(),我们编写的顺序是1、2、3,JVM 会对它进行指令重排序,所以执行顺序可能是3、1、2,也可能是2、3、1,不管是那种执行顺序,JVM 最后都会保证所以实例都完成实例化。如果构造函数中操作比较多时,为了提升效率,JVM 会在构造函数里面的属性未全部完成实例化时,就返回对象。双重检测锁出现空指针问题的原因就是出现在这里,当某个线程获取锁进行实例化时,其他线程就直接获取实例使用,由于JVM指令重排序的原因,其他线程获取的对象也许不是一个完整的对象,所以在使用实例的时候就会出现空指针异常问题。静态内部类public class Singleton { private static class SingletonHolder { private static Singleton instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return SingletonHolder.instance; } }通过静态内部类的方式实现单例模式是线程安全的,同时静态内部类不会在Singleton类加载时就加载,而是在调用getInstance()方法时才进行加载,达到了懒加载的效果。似乎静态内部类看起来已经是最完美的方法了,其实不是,可能还存在反射攻击或者反序列化攻击。且看如下代码:public static void main(String[] args) throws Exception { Singleton singleton = Singleton.getInstance(); Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton newSingleton = constructor.newInstance(); System.out.println(singleton == newSingleton); }除了反射攻击之外,还可能存在反序列化攻击的情况。如下:这个依赖提供了序列化和反序列化工具类:<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8.1</version> </dependency>Singleton类实现java.io.Serializable接口:public class Singleton implements Serializable { private static class SingletonHolder { private static Singleton instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return SingletonHolder.instance; } public static void main(String[] args) { Singleton instance = Singleton.getInstance(); byte[] serialize = SerializationUtils.serialize(instance); Singleton newInstance = SerializationUtils.deserialize(serialize); System.out.println(instance == newInstance); } }通过枚举实现单例模式在effective java(这本书真的很棒)中说道,最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。除此之外,写法还特别简单。public enum Singleton { INSTANCE; public void doSomething() { System.out.println("doSomething"); } }直接通过Singleton.INSTANCE.doSomething()的方式调用即可。方便、简洁又安全。public class Main { public static void main(String[] args) { Singleton.INSTANCE.doSomething(); } }转自:https://mp.weixin.qq.com/s/hi0vJgaHBLhu1KFK0xUrDw
-
前言代理模式是一种结构型设计模式,它允许通过创建一个代理对象来控制对另一个对象的访问。代理对象充当了客户端和实际对象之间的中介,客户端通过代理对象访问实际对象,从而隐藏了实际对象的具体实现细节。代理模式通常涉及三个角色:抽象主题(Subject):定义了代理对象和实际对象之间的共同接口,客户端通过该接口访问实际对象。实际主题(Real Subject):实现了抽象主题接口,是被代理的真实对象,即客户端最终要访问的对象。代理(Proxy):实现了抽象主题接口,并持有一个实际主题的引用。代理对象在执行具体操作之前或之后,可以添加额外的逻辑。代理模式的主要目的是在不改变实际对象的情况下,通过代理对象提供额外的功能或控制访问。它可以用于实现延迟加载、访问控制、远程访问、事务管理等。代理模式还可以实现AOP(面向切面编程)的核心机制,通过代理对象在方法执行前后添加横切逻辑。优点:代理对象和实际对象之间的解耦,客户端只需要通过代理对象进行访问,无需直接与实际对象交互。可以在代理对象中添加额外的逻辑,如访问控制、缓存、日志记录等。可以实现延迟加载,只有在需要时才创建实际对象。缺点:增加了系统的复杂性,引入了额外的代理类。可能会降低系统的性能,因为代理对象需要执行额外的操作。演示JDK动态代理实现方式:JDK动态代理是基于接口的代理,使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。通过Proxy.newProxyInstance方法创建代理对象,代理对象会实现指定接口,并将方法调用转发给InvocationHandler处理。限制:JDK动态代理要求目标对象必须实现接口。如果目标对象没有实现接口,就无法使用JDK动态代理。性能:JDK动态代理的性能相对较低,因为它需要使用反射来调用目标对象的方法。import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface UserService { void addUser(String username); } class UserServiceImpl implements UserService { public void addUser(String username) { System.out.println("Adding user: " + username); } } class UserServiceProxy implements InvocationHandler { private Object target; public Object bind(Object target) { this.target = target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before method call"); Object result = method.invoke(target, args); System.out.println("After method call"); return result; } } public class Main { public static void main(String[] args) { UserService userService = new UserServiceImpl(); UserServiceProxy userServiceProxy = new UserServiceProxy(); UserService proxy = (UserService) userServiceProxy.bind(userService); proxy.addUser("一安未来"); } }在上面的示例中,我们定义了一个UserService接口和一个实现类UserServiceImpl。然后,我们创建了一个UserServiceProxy类,实现了InvocationHandler接口,并在invoke方法中添加了额外的逻辑。最后用Proxy.newProxyInstance方法创建了代理对象,并将其绑定到实际对象上。CGLIB动态代理实现方式:CGLIB动态代理是基于类的代理,使用net.sf.cglib.proxy.Enhancer类和net.sf.cglib.proxy.MethodInterceptor接口实现。通过Enhancer创建代理对象,代理对象会继承目标对象,并重写方法,在方法调用时通过MethodInterceptor处理。限制:CGLIB动态代理可以代理没有实现接口的类,因为它是基于类的代理。性能:CGLIB动态代理的性能相对较高,因为它直接操作字节码,无需使用反射。import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; class UserService { public void addUser(String username) { System.out.println("Adding user: " + username); } } class UserServiceInterceptor implements MethodInterceptor { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Before method call"); Object result = proxy.invokeSuper(obj, args); System.out.println("After method call"); return result; } } public class Main { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserService.class); enhancer.setCallback(new UserServiceInterceptor()); UserService proxy = (UserService) enhancer.create(); proxy.addUser("一安未来"); } }在上面的示例中,我们定义了一个UserService类,没有实现任何接口。然后,我们创建了一个UserServiceInterceptor类,实现了MethodInterceptor接口,并在intercept方法中添加了额外的逻辑。最后使用Enhancer类创建了代理对象,并设置了回调对象为UserServiceInterceptor。总结实现方式:JDK动态代理是基于接口的代理,而CGLIB动态代理是基于类的代理。接口要求:JDK动态代理要求目标对象必须实现接口,而CGLIB动态代理可以代理没有实现接口的类。性能:CGLIB动态代理的性能通常比JDK动态代理更高,因为它直接操作字节码,无需反射。但在某些情况下,JDK动态代理可能更适合,特别是当目标对象实现了多个接口时。选择使用JDK动态代理还是CGLIB动态代理取决于具体的使用场景。如果目标对象实现了接口,且对性能要求不高,可以选择JDK动态代理。如果目标对象没有实现接口,或对性能要求较高,可以选择CGLIB动态代理。这里顺带提一下动态代理与反射:动态代理目的:动态代理是一种机制,用于在运行时创建代理对象,以在代理对象上执行额外的逻辑或操作。使用方式:使用动态代理时,我们需要定义一个接口或类,并创建一个代理对象,将其绑定到实际对象上。代理对象会拦截对实际对象的方法调用,并可以在调用前后执行额外的逻辑。实现方式:Java提供了两种动态代理的实现方式,即基于接口的动态代理(JDK动态代理)和基于类的动态代理(CGLIB动态代理)。反射目的:反射是一种机制,用于在运行时检查、获取和操作类的成员(字段、方法、构造函数等)。使用方式:使用反射时,我们可以通过Class对象获取类的信息,如字段、方法、构造函数等。然后,可以使用反射机制调用这些成员,甚至可以在运行时创建类的实例。实现方式:Java的反射机制主要通过Class、Field、Method等类来实现。1.通过类的全限定名字符串获取Class对象 Class<?> clazz = Class.forName("com.example.MyClass"); 2.通过类名的.class语法获取Class对象 Class<?> clazz = MyClass.class; 3.通过类加载器的loadClass方法获取Class对象 ClassLoader classLoader = MyClass.class.getClassLoader(); Class<?> clazz = classLoader.loadClass("com.example.MyClass"); 4.通过对象的getClass方法获取Class对象 MyClass obj = new MyClass(); Class<?> clazz = obj.getClass(); class MyClass { public void sayHello(String name) { System.out.println("Hello, " + name); } } public class ReflectionExample { public static void main(String[] args) { try { Class<?> clazz = Class.forName("MyClass"); Object obj = clazz.newInstance(); Method method = clazz.getMethod("sayHello", String.class); // Method method = clazz.getDeclaredMethod("sayHello", String.class); method.invoke(obj, "一安未来"); } catch (Exception e) { e.printStackTrace(); } } }在上面的示例中,我们使用Class.forName方法通过类名获取Class对象。然后,使用newInstance方法创建类的实例。接下来,获取sayHello方法的Method对象,并使用invoke方法调用该方法,传递参数"一安未来"。getDeclaredMethod和getMethod是Java反射中用于获取方法的两个方法。它们在获取方法时有以下几个区别:访问级别:getDeclaredMethod:可以获取类中声明的所有方法,包括私有方法、受保护方法和公共方法。不考虑方法的访问修饰符。getMethod:只能获取公共方法(public修饰符),无法获取私有方法和受保护方法。继承关系:getDeclaredMethod:可以获取类中声明的方法,包括私有方法、受保护方法和公共方法,不考虑继承关系。getMethod:除了能获取类中声明的公共方法,还可以获取从父类继承的公共方法。参数类型:getDeclaredMethod和getMethod都可以通过参数类型来定位方法。但是,对于方法重载的情况,getDeclaredMethod和getMethod的参数类型要求略有不同。getDeclaredMethod:参数类型必须与目标方法的参数类型完全匹配。getMethod:参数类型可以是目标方法参数类型的子类。动态代理和反射都是在运行时处理类和对象的机制。它们都允许我们在程序运行时动态地获取和操作类的信息。动态代理和反射都提供了一种动态性的机制,允许我们在运行时进行灵活的操作。它们在某些场景下可以结合使用,例如使用反射获取类的信息,然后使用动态代理来创建代理对象并执行额外的逻辑。总结来说,动态代理和反射都是Java中非常有用的机制,可以在运行时动态地操作类和对象。动态代理主要用于创建代理对象并执行额外的逻辑,而反射主要用于在运行时检查和操作类的成员。转自:https://mp.weixin.qq.com/s/JG8snawxJyVI08KobyXBQg
上滑加载中
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签