-
springboot调用hdfs1 HDFS简介HDFS(Hadoop Distribute File System)是一个适合运行在通用硬件之上,具备高度容错特性,支持高吞吐量数据访问的分布式文件系统,非常适合大规模数据集应用2 样例背景HDFS的业务操作对象是文件,代码样例中所涉及的文件操作主要包括创建文件夹写文件追加文件内容读文件删除文件/文件夹HDFS还有其他的业务处理,例如设置文件权限等,其他操作可以在掌握本代码样例之后,再扩展学习。3 Windows环境样例调用步骤环境准备https://bbs.huaweicloud.com/forum/thread-88552-1-1.html比对时间,与集群时间误差不能超过5分钟检查 C:\Windows\System32\drivers\etc\hosts文件中是否包含所有集群节点的域名IP映射信息在IDEA打开样例代码中的hdfs-springboot目录,默认会自动下载依赖,如未下载,选中该目录下的pom.xml文件,右键点击“Add As Maven Project”后等待项目自动将依赖下载完毕从Manager界面下载用户认证凭据后,解压缩获取秘钥文件user.keytab和krb5.conf从客户端 /opt/client/HDFS/hadoop/etc/hadoop 目录中获取core-site.xml和hdfs-site.xml把上面获取的user.keytab\krb5.conf\core-stie.xml\hdfs-site.xml四个文件放到统一目录下配置application.properties中的用户名和配置文件存放目录(第7步文件的目录)打开测试类 HDFApplication.java, 文件右键执行Run 运行代码调用接口 POST cid:link_0创建hdfs目录4 Linux环境调试步骤完成Windows环境样例调用步骤在windows环境中执行打包检查linux环境时间与集群误差不超过5分钟检查linux环境的JDK版本为1.8检查linux环境的/etc/hosts文件中包含所有集群节点的域名IP映射信息创建样例执行路径,例如/opt/hdfstest上传windows环境打包后生成的target目录下的 hdfd-springboot-1.0-SNAPSHOT.jar 包到/opt/hdfstest目录上传windows环境中调试通过后的配置文件到/opt/hdfstest/conf目录配置application.properties中的用户名和配置文件存放目录(/opt/hdfstest/conf/)执行如下命令启动服务java –jar hdfs-springboot-1.0-SNAPSHOT.jar
-
使用CSS描绘页面样式时,颜色是其中不可或缺的,无论是对文本、背景还是边框、阴影,我们都写过无数代码用来增添颜色。而为了让网页的色彩表现更出色,我们很有必要完整梳理下CSS中的色彩。要讲清楚CSS中的颜色,离不开颜色模型的概念,这是我们需要先了解的。颜色模型颜色模型计算机中用来描述颜色的数学模型,最常见的就是RGB模型,CSS中就支持这种模型,除此外,CSS中还支持另外一种:HSL模型。关于这两种模型的详细知识,之前有提供文章进行讲解,可见博文 前端需要了解的颜色模型。这篇博文中也有提到前端支持的RGB模型的颜色值的表示方法,颜色值可以是 rgb表示法 和 hex十六进制表示法,如下所示: codeduidaima.com'rgb(255, 0, 0)''#ff0000'而 HSL模型 在CSS中的颜色值同样有效: codeduidaima.comhsl(0, 100%, 50%)以上颜色值,在CSS中都表示红色,而除了这几种颜色值以外,CSS中还有其中几种表示法。CSS颜色值的类型CSS中描述颜色值,大概有以下五种表示方式:1.颜色名称2. rgb[a]3. hex十六进制值4. hsl[a]5. 其他关键字前面三种是我们开发中最常用的,下面一一对它们进行介绍。颜色名称颜色名称是指在CSS标准中定义的一系列的颜色名称值,每一个名称都表示一个具体的颜色,如 black 表示黑色,white 表示白色等等。截止到CSS3的标准,总共提供了147个颜色名称值,都可以被用于CSS中的颜色属性中,下图是部分颜色值:在CSS中使用颜色名称值,也非常简单方便: codeduidaima.comdiv {color: red;background-color: blue;}上面代码,就是定义了div的文本颜色为red红色,背景色为blue蓝色。需要注意的是:1. CSS并不区分大小写,所以,这些颜色名称使用大小写都可以,一般习惯用小写。2. 如果使用CSS标准未定义的颜色名称,则样式的颜色属性不起作用。rgb[a]CSS支持RGB颜色模型,可以直接使用 rgb() 类函数的方式来表示CSS中的颜色值,如果带上透明度,就是 rgba()。基本的语法:rgba(r, g, b, a)。参数中:1. r, g, b分别表示红、绿、蓝三色,取值为 正整数 或者 百分数。为正整数时范围是 0 - 255,百分数时范围是 0% - 100%。这三个值必须写法一致,要么都是正整数,要么都是百分数,如果混用则颜色值错误,属性无效。2. a表示透明度,取值范围在 0 - 1 之间。如下所示,通过 rgba 描述不同的颜色状态: codeduidaima.com// 红色rgb(255, 0, 0)rgb(100%, 0%, 0%)// 带50%透明度的红色rgba(255, 0, 0, 0.5)rgba(255, 0, 0, 50%)rgba(100%, 0%, 0%, 50%)// 混用,无效值rgba(100%, 0, 0)rgba(255, 0%, 0%)以上代码,最后混用的颜色值,在前端页面中无效,需要进制类似写法。rgba的新写法最新的CSS颜色标准中,提供了新的rgba语法:rgb[a](r g b / a)。与以往不同的是:1.可以直接使用空格(替换逗号)来分隔R\G\B,透明通道A则使用 / 隔开;2.当需要设置透明度时,可以直接使用 rgb,不用带上 a,同样有效;如50%透明度的红色: codeduidaima.comrgba(255 0 0 / 0.5)rgb(255 0 0 / 0.5)rgb(255, 0, 0, 0.5)像上面这样表示颜色,也能正确显示颜色。CSS元素的透明度透明度体现在内容是否可见上,在网页内容的展示中,CSS提供了 opacity 属性可以直接描述元素的透明度值,取值在 0 - 1 范围之间,0代表完全透明,1表示完全不透明: codeduidaima.comdiv {opacity: 0;}以上代码,就定义了div的 opacity 属性,则div的所有内部内容都将完全透明,不显示出来,包括边框(border)、轮廓(outline)、阴影(box-shadow)等等。CSS3提供的 filter 属性中,也可以设置透明度,效果几乎一样: codeduidaima.comdiv {filter: opacity(0);}hex十六进制值hex颜色值表示法,实际上也是依赖的RGB模型,针对 R\G\B 红绿蓝三色通道,将每个通道的颜色采用十六进制来标注,值范围在 00-ff。所以一个颜色都是对应的3个十六进制数字(一般是6个字符),再加上 # 前缀,就能用来描述颜色值了,比如红色:#ff0000。当同一通道的两个十六进制字符相同时,则可以采用缩写,红色 #ff0000 可以缩写为 #f00。hex颜色值也能够加上透明度,这里就是 A(alpha) 通道,同样是两位的十六进制数字,如红色带上透明度:#ff0000ff。hex颜色值与rgb颜色之间也可以通过js进行转换,具体的代码见博文:前端需要了解的颜色模型。主要是基于进制之间的转换,JS中的数字进制和进制转换的知识可见博文:Javacript中的进制与进制转换。hsl[a]CSS除了支持 RGB 模型以外,还支持另外一种:HSL颜色模型。它在CSS中描述颜色值,也是通过函数的方式来处理的。比如,同样的红色: codeduidaima.comhsl(0, 100%, 50%)带透明度的话,就是就是 hsla: codeduidaima.comhsla(0, 100%, 50%, 1)hsla 类函数的的四个参数,依次为:色相H(hue)、饱和度S(saturation)、亮度L(lightness)和透明度A(alpha)。由于在前端开发中,hsla 很少有人使用,所以很多人并不太了解,关于它的详细知识以及hsl与rgb之间的转换,可见 前端需要了解的颜色模型。另外也有基于hsl模型实现的颜色选择器介绍,见博文 前端实现一个颜色选择器。颜色值关键字除了以上几种具体的颜色值以外,CSS中还提供了几个可以使用在颜色属性中的值:transparent、currentColor 和 inherit。其中 inherit 是所有CSS属性都有效的值,这里不做赘述,我们重点看下前面两个可用于颜色属性的值。transparenttransparent 表示一个完全透明的颜色,等同于一个透明度为0的颜色值,在视觉上根本不可见。它已被定义为一个颜色值,能被用于任何一个可使用颜色值的CSS属性中: codeduidaima.comspan.text {color: transparent;}以上代码,设置文本颜色值后,则该文本内容完全透明,视觉不可见。currentColorcurrentColor 表示当前颜色,这个当前颜色来自于当前元素或者它父级元素(往上追溯)的文本颜色值,即 color 值,如果都没有设置color值,则取默认的黑色值。取自身的文本颜色值: codeduidaima.comdiv {color: #f00;border: 1px solid currentColor;}以上代码,定义了div的文本颜色值为红色,边框颜色为 currentColor,则读取红色值。除了边框(border)以外,在背景色(background-color)、轮廓(outline)、阴影(shadow)等属性中使用,都会读取当前文本值。 codeduidaima.com<div class="container"><div class="content">content</div></div>.container {padding: 10px;width: 200px;color: red;}.content {border: 1px solid currentColor;outline: 2px solid #333;box-shadow: 3px 3px 3px currentColor;}以上代码,在父元素定义color值为红色,子元素中的 border 和 box-shadow 属性使用了 currentColor 值,则将会使用红色,而 outline 则定义了 灰色值,效果如下:CSS中的颜色属性上文主要介绍了颜色模型和CSS中支持的颜色值,本节内容将简单介绍下CSS中的颜色属性。而CSS中能够赋予颜色值的属性大概十个左右,我们在开发中最熟悉最常用的应该就是以下三个:color文本颜色属性,该属性给元素的文本内容设置颜色。一般网页中该属性的默认值是黑色。是 currentColor 颜色值的来源。background背景属性,可以为元素设置背景颜色。默认是透明transparent。border边框属性,可以为元素设置边框的颜色。如果边框属性没有设置颜色值,则默认使用元素的color值。 codeduidaima.com<div class="content">content</div>.content {padding: 10px;width: 200px;color: red;border: 1px solid;}以上,border属性没有设置颜色值,color属性设置为红色,则边框自动显示为红色,如下图所示:其他可用颜色属性text-decoration该属性用于给文本内容添加下划线、上划线、删除线等修饰,可以对它设置颜色属性值。使用方式:text-decoration: underline wavy red;。三个值分别表示线条类型(text-decoration-line)、线条形状(text-decoration-style)和线条颜色(text-decoration-color)。box-shadow给元素添加阴影属性,可为阴影设置相应的颜色值。使用方式:box-shadow: 4px 4px #ff0;。text-shadow用于增加文本内容的阴影,也能够设置相应的颜色值,主要针对页面上文字。使用方式:text-shadow: 2px 2px rgb(255, 0, 0);。filter: drop-shadow在设置图像滤镜上,也可以使用阴影,并设置颜色值。使用方式:filter: drop-shadow(8px 8px 10px red);。outline该属性将在元素的边缘绘制外围轮廓,位于边框(border)的外边缘,也是能够设置颜色属性。使用方式:outline: 2px solid red;。三个值分别表示宽度(outline-width)、形状(outline-style)和颜色(outline-color)。使用方法和border较类似,但不同的是,outline无法单独给一个边设置样式。column-rule该属性需要结合 column-count 一起使用。column-count:用于将div元素的内容分成几个列来显示,属性值为数字,代表列数。column-rule:用于指定所分的列之间的线条样式、宽度和颜色。column-count: 3;column-rule: 10px solid red;以上代码,即可将一个div的内容分隔为3列,并且每列中间都有红色的10px宽度的实线。三个值与上面outline一样,也分别表示宽度(column-rule-width)、形状(column-rule-style)和颜色(column-rule-color)。转载自cid:link_0Group/Topic/CSS/10427
-
首先,这个问题就很有意思,相信大部分人第一反应不就是null吗?比如: codeduidaima.comif(str != null){}可是,很多时候我们判断前端送过来的值,有可能是空字符串,所以更严格的写法是: codeduidaima.comif(str != null && !str.equals("")){}眼疾手快的同学肯定发现了问题,如果str是null怎么办?其实就算是null也没关系,因为前面已经判断了不为null,&&是短路与,如果前面的条件不是true,后面的压根不会执行。于是,这段代码成了很多公司的首选。有人问,为什么要判断是不是空字符串,我不判断行不行。有些情况行,但是很多情况就是不可以。比如你看这里的代码: codeduidaima.comif(userNo != null){User user = userMapper.selectOne(userNo);log.info(user.userName);}如果传过来的就是一个空字符串,那么是会进入这个逻辑的,查出来的user是null,在user.userName的时候就会报空指针。个别头铁的同学又会问,那我抛出异常不就行了。 codeduidaima.com//堆代码 duidaima.comif(userNo != null && userNo.equals("")){User user = userMapper.selectOne(userNo);if(user == null){throw new Exception("用户不存在!");}log.info(user.userName);}乍一看是没问题,但是你细品,问题就大了。比如我的业务场景是,用户存在就增加积分,用户不存在就创建这个用户。看代码: codeduidaima.comif(userNo != null){User user = userMapper.selectOne(userNo);if(user == null){throw new Exception("用户不存在!");}addPoints(user,100);}else{addUser();}代码的本意是,有userNo我就给他加积分,没有userNo就去新增这个用户。现在你传了一个空字符串过来,意思是空,却走进了加积分的逻辑。就算报错,可真的是客户想要的吗?客户是希望,我没有用户账号,你给我新增一个。所以这就与业务相悖了。好了,我其实会读心术,你此刻也许在想,那我随便传一个找不到的userNo给你,你不还是走不到addUser的逻辑吗?亲,你传个空字符串和null的本意肯定是,这个东西我没有。但是你传一个错误的数据,那性质就不一样了,意思是我有这个东西,但是我填错了。那么这样的逻辑自然就没有问题啦。所以,我们要牢记,如果业务上这个东西的概念是一个空,就一定要既判断null,还要判断空字符串,双管齐下才能确保万无一失。当然,实际上这么常见的功能,我们大可以使用org.apache.commons.lang3.StringUtils的isNotBlank方法: codeduidaima.comisNotBlankpublic static boolean isNotBlank(CharSequence cs) {return !isBlank(cs);}isBlankpublic static boolean isBlank(CharSequence cs) {int strLen = length(cs);if (strLen == 0) {return true;} else {for(int i = 0; i < strLen; ++i) {if (!Character.isWhitespace(cs.charAt(i))) {return false;}}return true;}}lengthpublic static int length(CharSequence cs) {return cs == null ? 0 : cs.length();}它不仅帮你判断了null和空字符串,还给你判断了空格,简直不要太爽!一个小小的判空,却很可能让你吃了大亏,如果一个判空没有做好,那么里面的逻辑就完全裸露了,相信你一定吃过 NullPointerException 的苦头!转载自cid:link_0Group/Topic/JAVA/10624
-
前言 作为C++11的新特性的Lambada表达式本身书写倒不是什么难事,网上找一找也能轻易写出正确地表达式,但是在那些场景下使用以及应该如何使用反而会让很多新手困惑。 Lambda表达式 1 表达式定义 构造一个闭包:一个能够捕获作用域中变量的未命名函数对象。 在操作上,闭包是将函数与环境一起存储的记录。 2 书写规范 主要分成六个部分,其中1,2,3,5,6是需要重点掌握的。 capture 子句(在 C++ 规范中也称为 lambda 引入器。) 参数列表自选。(也称为 lambda 声明符) 可变规范自选。 异常规范自选。 尾随返回类型自选。 λ体。 3 书写表达式 书写[] 用于捕获: 3.1 捕获的基本规则 [] - 不捕捉任何变量 [&] - 捕获外部作用域中所有变量,并作为引用在函数体内使用 (按引用捕 获) [=] - 捕获外部作用域中所有变量(依赖于编译器,只能捕获不被优化的变量),并作为副本在函数体内使用 (按值捕获) 注:拷贝的副本在匿名函数体内部为const类型. [=, &foo] - 按值捕获外部作用域中所有变量,并按照引用捕获外部变量 foo [bar] - 按值捕获 bar 变量,同时不捕获其他变量 [&bar] - 按引用捕获 bar 变量,同时不捕获其他变量 [this] - 捕获当前类中的 this 指针 让 lambda 表达式拥有和当前类成员函数同样的访问权限 如果已经使用了 & 或者 =, 默认添加此选项 3.2 使用的场景 用到函数指针的地方,应该考虑一下这个地方需不需要写Lambada表达式。 类中定义了只使用一次的方法,可以考虑是否需要用到Lambada表达式。 我只需要使用一次 类中进行调用。 好处: 简化代码: 维护代码完整性 捕获列表 简化代码解耦。 具体的例子: 01、代码维护上 Lambada类似于函数指针的东西,比如sort函数 自定义排序. auto compare = [](int a, int b) { return a > b; }; 02、代码解耦 如果某天多了一个条件,那么普通的函数参数列表要跟着改变,但是可以使用用匿名函数进行解耦。 例子: int lambadaParams() { bool cond1 = false; bool cond2 = false; bool cond3 = true; bool cond4 = true; auto fun = [=](int t) { if (cond1) { cout << "cond1 is true" << endl; cout << t << endl; } if (cond1 && cond2) { cout << "cond1 && cond2" << endl; cout << t << endl; } if (cond3 || cond4) { cout << "cond3 || cond4" << endl; cout << t << endl; } }; if (true) { fun(1); } if (true) { fun(2); } if (true) { fun(3); } } 3.3 细节——需要对捕获的值进行修改 由于捕获的值在函数内部是const类型,直接修改会报错。 注:拷贝的副本在匿名函数体内部为const类型. 但是可以添加可变规范 int num1 = 2; int num2 = 3; auto fun3 = [=](int a, int b) mutable { int p = num1 + num2; int q = (num1--) + (num2--); }; fun3(1, 2); 3.4 细节——接收返回值时需要注意 使用 auto 类型说明符声明的变量不能出现在其自身的初始值设定项中。 正确修改: std::function<int(int)> fun2 = [&fun2](int a) { if (a == 1) return 1; if (a == 2) return 2; else return fun2(a - 1) + fun2(a - 2); }; auto x2 = fun2(5); 运行: 3.5 细节——不写返回值会报错的情况 虽然一般Lambada可以不写返回值类型。 当编译器无法进行推断出来类型的时候就会报错。 只需要指明类型就科一了 auto fun1 = []() -> std::initializer_list<int> { return {1, 2, 3, 4, 5}; }; auto x = fun1(); 运行: 还有一种情况需要注意的是当返回值多个类型的时候,根据某个条件进行返回不同的类型这个时候也需要指明返回值类型。 3.6 细节——其他 引用捕获有性能代价么? 需要,这种效率比较细微,体现在 =引用捕获只针对不被编译器优化的部分: 只会捕获使用的变量而不是全部变量,编译器优化问题。不过我用的gcc版本问题对a1进行了初始化,因此fun4依然可以捕获到 例如: int a1, b1, c1; auto fun4 = [=](int num1, int num2) mutable { num1++; num2++; num1 = a1 + num1; }; fun4(0, 1); 完整的实验代码: #include <iostream> #include <algorithm> #include <vector> #include <functional> using namespace std; int main() { vector<int> vec{3, 5, 1, 8, 2, 9, 0}; auto compare = [](int a, int b) { return a > b; }; sort(vec.begin(), vec.end(), compare); auto fun1 = []() -> std::initializer_list<int> { return {1, 2, 3, 4, 5}; }; auto x = fun1(); std::function<int(int)> fun2 = [&fun2](int a) { if (a == 1) return 1; if (a == 2) return 2; else return fun2(a - 1) + fun2(a - 2); }; auto x2 = fun2(5); int num1 = 2; int num2 = 3; auto fun3 = [=](int a, int b) mutable { int p = num1 + num2; int q = (num1--) + (num2--); }; fun3(1, 2); int a1, b1, c1; auto fun4 = [=](int num1, int num2) mutable { num1++; num2++; num1 = a1 + num1; }; fun4(0, 1); return 0; } 总结 Lambda表达式是C++11重要的特性,使得代码变得更加简洁,易于维护,但是同时也可能导致影响性能,因此使用的时候要注意应用场景。 [capture list] (params list) 关键字-> return type {function body} captrue list:捕获外部变量列表(选填) params list:形参列表(可省略) 关键字:mutable,exception,attribute(可省略) return type:返回类型(可省略) function body:函数体 其中: mutable说明lambda表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获的对象的non-const方法。 exception说明lambda表达式是否抛出异常以及何种异常。 attribute用来声明属性。 特点: 一次性使用 作用: 总体上:代码完整性 局部:闭包是的传参数更容易 最后,最后 如果觉得有用,麻烦三连👍⭐️❤️支持一下呀,希望这篇文章可以帮到你,你的点赞是我持续更新的动力 ———————————————— 版权声明:本文为CSDN博主「忆_恒心」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_37457202/article/details/129672648
-
一、不用加号的加法 (1)原题再现 面试题 17.01. 不用加号的加法 设计一个函数把两个数字相加。不得使用 + 或者其他算术运算符。 示例: 输入: a = 1, b = 1 输出: 2 (2)问题分析 这道题要求不能用“+”等算数运算符,所以我们可以想到使用位运算符。 符号 描述 运算规则 & 与 两个位都为1时,结果才为1。 | 或 两个位都为0时,结果才为0。 ^ 异或 两个位相同为0,相异为1。 ~ 取反 0变1,1变0。 << 左移 各二进位全部左移若干位,高位丢弃,低位补0 >> 右移 各二进位全部右移若干位,对无符号数,高位补0,有符号数,右移补1。 本题可以使用&和^运算符,使用&得到两数相加的进位情况,使用^得到两数相加的结果(没有加进位) (3)完整代码 class Solution { public int add(int a, int b) { // write code here int ans=a^b;//无进位的情况下 int carry=a&b;//需要进位的地方 while(carry!=0) { carry=carry<<1; int tmp=ans; ans=ans^carry; carry=tmp&carry; } return ans; } } 二、走方格的方案数 (1)原题再现 走方格的方案数_牛客题霸_牛客网 描述 请计算n*m的棋盘格子(n为横向的格子数,m为竖向的格子数)从棋盘左上角出发沿着边缘线从左上角走到右下角,总共有多少种走法,要求不能走回头路,即:只能往右和往下走,不能往左和往上走。 注:沿棋盘格之间的边缘线行走 输入描述: 输入两个正整数n和m,用空格隔开。(1≤n,m≤8) 输出描述: 输出一行结果 示例: 输入: 2 2 输出: 6 (2)问题分析 这道题很明显是一道动态规划的题。需要额外注意的是n、m表示的是格子数,而我们走的是边,所以n和m都要+1,即2*2的4个格子有3*3的边框。 我们用二维数据记录每一步的状态。 状态: 子状态:从(0,0)到达(1,0),(1,1),(2,1),...(n,m)的路径数 F(i,j): 从(0,0)到达F(i,j)的路径数 到达黄色这一状态时可能是从上面走下来,也可能是从左边走过来的。这一点的路径数,就是左边一点的路径数+上边一点的路径数。 状态递推: F(i,j) = F(i-1,j) + F(i,j-1) 初始化: 因为只能向左或者向下,所以当只有一行时,只能向左,走法也只有一种;同理,当只有一列时,只能向下,走法也只有一种。 特殊情况:第0行和第0列 F(0,i) = 1 F(i,0) = 1 返回结果:右下角 F(n,m) (3)完整代码 import java.util.*; public class Main { public static void main(String[] args) { Scanner sc=new Scanner(System.in); int n=sc.nextInt(); int m=sc.nextInt(); int [][]maxNum=new int[n+1][m+1]; for(int i=0;i<=m;i++) { maxNum[0][i]=1; } for(int i=0;i<=n;i++) { maxNum[i][0]=1; } for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { maxNum[i][j]=maxNum[i-1][j]+maxNum[i][j-1]; } } System.out.println(maxNum[n][m]); } } ———————————————— 版权声明:本文为CSDN博主「小熊爱吃软糖吖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_63372226/article/details/129543104
-
前言 今天突然突发奇想,就想要用java来调用chatget的接口,实现自己的聊天机器人,但是网上找文章,属实是少的可怜(可能是不让发吧)。找到了一些文章,但是基本都是通过调用别人的库来完成的,导入其他的jar还有不低的学习成本,于是就自己使用HttpClient5写了一个,在这里讲解一下思路。 导包 对于http调用,我使用的是比较流行的httpclient5,然后直接创建了一个springboot项目,方便以后对外提供接口。 <parent> <artifactId>spring-boot-starter-parent</artifactId> <groupId>org.springframework.boot</groupId> <version>2.5.3</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5 --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency> </dependencies> 基本说明 在编写代码之前,这个先给出HttpClient的Api文档 api文档 我们在编写代码之前需要了解官方提供的接口如何进行访问以及返回的结果是什么 请求参数 官方文档地址为 文档,请求参数必须填写的内容如下 { "model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "Hello!"}] } 一个是model,一个是messages。model根据自己的情况来选择,聊天的话就是gpt-3.5-turbo,下面的messages里面包含n个对象,每个对象有role和content,role表示角色,content表示内容。 下面为官方文档中的解释 简单理解就是我们要问问题,role就是user。如果要实现连续对话,那么就将返回的返回内容设置到messages中,role设置为返回的role。 响应参数 下面直接给出响应的内容 { 'id': 'chatcmpl-6p9XYPYSTTRi0xEviKjjilqrWU2Ve', 'object': 'chat.completion', 'created': 1677649420, 'model': 'gpt-3.5-turbo', 'usage': {'prompt_tokens': 56, 'completion_tokens': 31, 'total_tokens': 87}, 'choices': [ { 'message': { 'role': 'assistant', 'content': 'The 2020 World Series was played in Arlington, Texas at the Globe Life Field, which was the new home stadium for the Texas Rangers.'}, 'finish_reason': 'stop', 'index': 0 } ] } 我们问问题的答案就在choices.message下的content中,而role就代表了chatGpt扮演的角色。看到这我们就应该知道该干嘛了吧肯定是创建对应的VO类啊。 创建请求和响应的VO类 下面5个类就对应了我们发送和接收的各种信息 ChatGptMessage类 @Data @NoArgsConstructor @AllArgsConstructor public class ChatGptMessage { String role; String content; } ChatGptRequestParameter 类 @Data @NoArgsConstructor @AllArgsConstructor public class ChatGptRequestParameter { String model = "gpt-3.5-turbo"; List<ChatGptMessage> messages = new ArrayList<>(); public void addMessages(ChatGptMessage message) { this.messages.add(message); } } ChatGptResponseParameter 类 @Data @NoArgsConstructor @AllArgsConstructor public class ChatGptResponseParameter { String id; String object; String created; String model; Usage usage; List<Choices> choices; } Choices 类 @Data @NoArgsConstructor @AllArgsConstructor public class Choices { ChatGptMessage message; String finish_reason; Integer index; } Usage 类 @Data @NoArgsConstructor @AllArgsConstructor public class Usage { String prompt_tokens; String completion_tokens; String total_tokens; } 代码编写 不说废话,首先创建一个CustomChatGpt类 public class CustomChatGpt { } 然后定义一些成员属性 /** * 自己chatGpt的ApiKey */ private String apiKey; /** * 使用的模型 */ private String model = "gpt-3.5-turbo-0301"; /** * 对应的请求接口 */ private String url = "https://api.openai.com/v1/chat/completions"; /** * 默认编码 */ private Charset charset = StandardCharsets.UTF_8; /** * 创建一个ChatGptRequestParameter,用于携带请求参数 */ private ChatGptRequestParameter chatGptRequestParameter = new ChatGptRequestParameter(); 提供一个ApiKey的构造器,创建该对象必须要传入ApiKey public CustomChatGpt(String apiKey) { this.apiKey = apiKey; } 定义一个响应超时时间 /** * 响应超时时间,毫秒 */ private int responseTimeout = 10000; public void setResponseTimeout(int responseTimeout) { this.responseTimeout = responseTimeout; } 编写一个getAnswer方法,要求传入一个CloseableHttpClient和一个问题 public String getAnswer(CloseableHttpClient client, String question) { } 继续实现方法,下面会完成一些参数的创建和设置 // 创建一个HttpPost HttpPost httpPost = new HttpPost(url); // 创建一个ObjectMapper,用于解析和创建json ObjectMapper objectMapper = new ObjectMapper(); // 设置请求参数 chatGptRequestParameter.addMessages(new ChatGptMessage("user", question)); HttpEntity httpEntity = null; try { // 对象转换为json字符串 httpEntity = new StringEntity(objectMapper.writeValueAsString(chatGptRequestParameter), charset); } catch (JsonProcessingException e) { System.out.println(question + "->json转换异常"); return null; } httpPost.setEntity(httpEntity); 下面会完成一些配置的设置 // 设置请求头 httpPost.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); // 设置登录凭证 httpPost.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey); // 用于设置超时时间 RequestConfig config = RequestConfig .custom() .setResponseTimeout(responseTimeout, TimeUnit.MILLISECONDS) .build(); httpPost.setConfig(config); 下面代码会提交请求,解析响应,最后返回对应问题的答案 try { // 提交请求 return client.execute(httpPost, response -> { // 得到返回的内容 String resStr = EntityUtils.toString(response.getEntity(), charset); // 转换为对象 ChatGptResponseParameter responseParameter = objectMapper.readValue(resStr, ChatGptResponseParameter.class); String ans = ""; // 遍历所有的Choices(一般都只有一个) for (Choices choice : responseParameter.getChoices()) { ChatGptMessage message = choice.getMessage(); chatGptRequestParameter.addMessages(new ChatGptMessage(message.getRole(), message.getContent())); String s = message.getContent().replaceAll("\n+", "\n"); ans += s; } // 返回信息 return ans; }); } catch (IOException e) { e.printStackTrace(); } // 发生异常,移除刚刚添加的ChatGptMessage chatGptRequestParameter.getMessages().remove(chatGptRequestParameter.getMessages().size()-1); return "您当前的网络无法访问"; 下面给出这个类的完整代码 import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.ttpfx.vo.ChatGptMessage; import com.ttpfx.vo.ChatGptRequestParameter; import com.ttpfx.vo.ChatGptResponseParameter; import com.ttpfx.vo.Choices; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; /** * @author ttpfx * @date 2023/3/23 */ public class CustomChatGpt { /** * 自己chatGpt的ApiKey */ private String apiKey; /** * 使用的模型 */ private String model = "gpt-3.5-turbo-0301"; /** * 对应的请求接口 */ private String url = "https://api.openai.com/v1/chat/completions"; /** * 默认编码 */ private Charset charset = StandardCharsets.UTF_8; /** * 创建一个ChatGptRequestParameter,用于携带请求参数 */ private ChatGptRequestParameter chatGptRequestParameter = new ChatGptRequestParameter(); /** * 相应超时时间,毫秒 */ private int responseTimeout = 1000; public void setResponseTimeout(int responseTimeout) { this.responseTimeout = responseTimeout; } public CustomChatGpt(String apiKey) { this.apiKey = apiKey; } public String getAnswer(CloseableHttpClient client, String question) { // 创建一个HttpPost HttpPost httpPost = new HttpPost(url); // 创建一个ObjectMapper,用于解析和创建json ObjectMapper objectMapper = new ObjectMapper(); // 设置请求参数 chatGptRequestParameter.addMessages(new ChatGptMessage("user", question)); HttpEntity httpEntity = null; try { // 对象转换为json字符串 httpEntity = new StringEntity(objectMapper.writeValueAsString(chatGptRequestParameter), charset); } catch (JsonProcessingException e) { System.out.println(question + "->json转换异常"); return null; } httpPost.setEntity(httpEntity); // 设置请求头 httpPost.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); // 设置登录凭证 httpPost.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey); // 用于设置超时时间 RequestConfig config = RequestConfig .custom() .setResponseTimeout(responseTimeout, TimeUnit.MILLISECONDS) .build(); httpPost.setConfig(config); try { // 提交请求 return client.execute(httpPost, response -> { // 得到返回的内容 String resStr = EntityUtils.toString(response.getEntity(), charset); // 转换为对象 ChatGptResponseParameter responseParameter = objectMapper.readValue(resStr, ChatGptResponseParameter.class); String ans = ""; // 遍历所有的Choices(一般都只有一个) for (Choices choice : responseParameter.getChoices()) { ChatGptMessage message = choice.getMessage(); chatGptRequestParameter.addMessages(new ChatGptMessage(message.getRole(), message.getContent())); String s = message.getContent().replaceAll("\n+", "\n"); ans += s; } // 返回信息 return ans; }); } catch (IOException e) { e.printStackTrace(); } // 发生异常,移除刚刚添加的ChatGptMessage chatGptRequestParameter.getMessages().remove(chatGptRequestParameter.getMessages().size()-1); return "您当前的网络无法访问"; } } 使用 下面就是测试代码,我们只需要传入一个CloseableHttpClient 和 question 即可 public class Test { public static void main(String[] args) throws IOException { CloseableHttpClient httpClient = HttpClients.createDefault(); String apiKey = "自己的ApiKey"; CustomChatGpt customChatGpt = new CustomChatGpt(apiKey); // 根据自己的网络设置吧 customChatGpt.setResponseTimeout(20000); while (true) { System.out.print("\n请输入问题(q退出):"); String question = new Scanner(System.in).nextLine(); if ("q".equals(question)) break; long start = System.currentTimeMillis(); String answer = customChatGpt.getAnswer(httpClient, question); long end = System.currentTimeMillis(); System.out.println("该回答花费时间为:" + (end - start) / 1000.0 + "秒"); System.out.println(answer); } httpClient.close(); } } 下面为运行图片 最后说明 对于ApiKey,只能说难者不会,会者不难,这个没办法教。 如果代码无法运行,或者运行速度及其缓慢,请使用代理,在HttpClient里面可以很轻松的使用代理 String proxyIp = "127.0.0.1"; int proxyPort = 7890; HttpHost httpHost = new HttpHost(proxyIp, proxyPort); 1 2 3 上面就是一个示例,对于代理,这里也就无法继续进行说明了。 如果我们完成了上面的功能,是不是就能够对外提供接口,然后写一个自己的网页端的ChatGpt或者弄一个聊天机器人呢?当然没问题啊 2023/3/27 23:27更新 很多人找我要这个小程序的demo,其实源代码上面都已经给出了。既然有人问,那么就给出这个demo的github地址 git地址 使用指令 git clone https://github.com/c-ttpfx/chatGPT.git 可以轻松的下载到本地 2023/3/31 19:54更新 上面这个代码大家应该发现了问题,就是返回很慢,原因就是我们请求不是异步的,是等待所有答案都给出了再返回,所以速度很慢,并且容易出现超时。 解决方法就是使用异步请求,大家请参照 使用chatgpt实现微信聊天小程序(秒回复),github开源(附带链接) ,基本可以实现秒回复 ———————————————— 版权声明:本文为CSDN博主「秃头披风侠.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_51545690/article/details/129739621
-
机缘 搞技术的不要看这一段,纯属内心独白 前段时间我的世界官方启动器由于不知名原因不能联机了,好久没玩我的世界了内心极度煎熬。 终于不久前发现另一款启动器pcl启动器,这款启动器需要java环境。这不巧了吗,我刚好有。我的java环境是1.8,但是我的世界已经更新到1.19了,java1.8的环境只能玩我的世界1.16以下的版本,我又emo了。 于是我去找度娘了,神奇的度娘告诉我一台电脑可以安装两个java,于是有了接下来的技术收获。 技术 首先安装好两个jdk 我原来jdk是1.8,一起更新了一下 安装jdk17的jre 刚安装好的jdk17是没有jre的(似乎比较高版本的都没有jre), 需要自己手动安装 进入jdk17的目录下输入cmd进入dos窗口,输入以下内容回车即可 回车后没有回弹信息,直接查看文件夹就能看到jre bin\jlink.exe --module-path jmods --add-modules java.desktop --output jre 1 配置环境变量 1. 新建两个jdk的变量 2. 配置JAVA_HOME 通过配置JAVA_HOME的变量值切换jdk版本 3. 新建CLASSPATH变量 将以下字符串作为变量值(必须完整复制) .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\bin; 1 4. 编辑path变量 没有%JAVA_HOME%\bin的路径就添加,已经有了就直接点击上移,将%JAVA_HOME%的路径上移到最上面。(亲测,没有上移可能无法切换jdk版本) 以上全部配置好后就可以自由切换jdk版本了 只需要编辑JAVA_HOME的变量值即可切换jdk版本 至此完成了一台电脑安装两个版本的jdk并实现自由切换。 ———————————————— 版权声明:本文为CSDN博主「尤雪爱学习0v0」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/Una_lover/article/details/127448264
-
一、Timer Timer是JAVA自带的定时任务类,实现如下: public class MyTimerTask { public static void main(String[] args) { // 定义一个任务 TimerTask timerTask = new TimerTask() { @Override public void run() { System.out.println("打印当前时间:" + new Date()); } }; // 计时器 Timer timer = new Timer(); // 开始执行任务 (延迟1000毫秒执行,每3000毫秒执行一次) timer.schedule(timerTask, 1000, 3000); } } Timer 优缺点分析 优点是使用简单,缺点是当添加并执行多个任务时,前面任务的执行用时和异常将影响到后面任务,这边深海建议谨慎使用。 二、ScheduledExecutorService ScheduledExecutorService 也是Java自带的类, 它可以实现Timer具备的所有功能,并解决了 Timer类存在的问题。 实现如下: public class MyScheduledExecutorService { public static void main(String[] args) { // 创建任务队列 10 为线程数量 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10); // 执行任务 scheduledExecutorService.scheduleAtFixedRate(() -> { System.out.println("打印当前时间:" + new Date()); }, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次 } } ScheduledExecutorService 优缺点分析 优点是,该类是JDK1.5自带的类,使用简单,缺点是该方案仅适用于单机环境。 三、Spring Task Spring系列框架中Spring Framework自带的定时任务, 使用上面两种方式,很难实现某些特定需求,比如每周一执行某任务,但SpringTask可轻松实现。 以SpringBoot为例来实现: 1、开启定时任务 在SpringBoot的启动类上声明 @EnableScheduling: @SpringBootApplication @EnableScheduling //开启定时任务 public class DemoApplication { // -- -- } 2、添加定时任务 只需使用@Scheduled注解标注即可, 如果有多个定时任务,可以创建多个@Scheduled标注的方法,示例如下: import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component // 把此类托管给 Spring,不能省略 public class TaskUtils { // 添加定时任务 @Scheduled(cron = "30 40 23 0 0 5") // cron表达式:每周一 23:40:30 执行 public void doTask(){ System.out.println("我是定时任务~"); } } Spring Boot 启动后会自动加载并执行定时任务,无需手动操作。 Cron 表达式 Spring Task 的实现需要使用 cron 表达式来声明执行的频率和规则,cron 表达式是由 6 位或者 7 位组成的(最后一位可以省略),每位之间以空格分隔,每位从左到右代表的含义如下: 其中 * 和 ? 号都表示匹配所有的时间。 cron 表达式在线生成地址:https://cron.qqe2.com/ 知识扩展:分布式定时任务 上面的方法都是关于单机定时任务的实现,如果是分布式环境可以使用 Redis 来实现定时任务。 使用 Redis 实现延迟任务的方法大体可分为两类:通过 ZSet 的方式和键空间通知的方式。 1、ZSet 实现方式 通过 ZSet 实现定时任务的思路是,将定时任务存放到 ZSet 集合中,并且将过期时间存储到 ZSet 的 Score 字段中,然后通过一个无线循环来判断当前时间内是否有需要执行的定时任务,如果有则进行执行,具体实现代码如下: import redis.clients.jedis.Jedis; import utils.JedisUtils; import java.time.Instant; import java.util.Set; public class DelayQueueExample { private static final String _KEY = "DelayQueueExample"; public static void main(String[] args) throws InterruptedException { Jedis jedis = JedisUtils.getJedis(); // 30s 后执行 long delayTime = Instant.now().plusSeconds(30).getEpochSecond(); jedis.zadd(_KEY, delayTime, "order_1"); // 继续添加测试数据 jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_2"); jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_3"); jedis.zadd(_KEY, Instant.now().plusSeconds(7).getEpochSecond(), "order_4"); jedis.zadd(_KEY, Instant.now().plusSeconds(10).getEpochSecond(), "order_5"); // 开启定时任务队列 doDelayQueue(jedis); } /** * 定时任务队列消费 * @param jedis Redis 客户端 */ public static void doDelayQueue(Jedis jedis) throws InterruptedException { while (true) { // 当前时间 Instant nowInstant = Instant.now(); long lastSecond = nowInstant.plusSeconds(-1).getEpochSecond(); // 上一秒时间 long nowSecond = nowInstant.getEpochSecond(); // 查询当前时间的所有任务 Set data = jedis.zrangeByScore(_KEY, lastSecond, nowSecond); for (String item : data) { // 消费任务 System.out.println("消费:" + item); } // 删除已经执行的任务 jedis.zremrangeByScore(_KEY, lastSecond, nowSecond); Thread.sleep(1000); // 每秒查询一次 } } } 2、键空间通知 我们可以通过 Redis 的键空间通知来实现定时任务,它的实现思路是给所有的定时任务设置一个过期时间,等到了过期之后,我们通过订阅过期消息就能感知到定时任务需要被执行了,此时我们执行定时任务即可。 默认情况下 Redis 是不开启键空间通知的,需要我们通过 config set notify-keyspace-events Ex 的命令手动开启,开启之后定时任务的代码如下: import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPubSub; import utils.JedisUtils; public class TaskExample { public static final String _TOPIC = "__keyevent@0__:expired"; // 订阅频道名称 public static void main(String[] args) { Jedis jedis = JedisUtils.getJedis(); // 执行定时任务 doTask(jedis); } /** * 订阅过期消息,执行定时任务 * @param jedis Redis 客户端 */ public static void doTask(Jedis jedis) { // 订阅过期消息 jedis.psubscribe(new JedisPubSub() { @Override public void onPMessage(String pattern, String channel, String message) { // 接收到消息,执行定时任务 System.out.println("收到消息:" + message); } }, _TOPIC); } } ———————————————— 版权声明:本文为CSDN博主「深海呐」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_39731011/article/details/123332641
-
使用Spring MVC提供的表单标签可以让JSP视图方便展示地Model中的数据,特别适合修改现有记录数据的情形,通常进行修改操作时,原有数据要先展示出来,再供用户重新选择或修改。使用表单标签能实现数据绑定,让表单中的各个表单域中的name属性绑定到对象模型中来。JSP页面中要想使用SpringMVC自带标签库,需要导入标签库,在JSP文件开头声明如下: <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> 1 Spring的form标签能够自动绑定来自Model中的一个属性值到当前form对应的实体对象,默认是command属性(可以通过ModelAttribute重写默认属性),这样就可以在form表单体里面方便的使用该对象的属性。Form标签下面又包含input,password,select/option/options,checkbox/checkboxs,radiobutton,radiobuttons等等子标签。这些标签的基本介绍如下图所示。 form 标签 form 标签主要包括下表所示的多个属性 @RequestMapping("detail/{id}") public ModelAndView detail (@PathVariable int id){ User myuser=userService.getUserById(id); ModelAndView mv=new ModelAndView(); mv.addObject("user",myuser); System.out.println(userService..getUserById(id).getUsername()); mv.setViewName("detail"); return mv; } 该代码表示接收到 id 后,调用业务层,获取到该 id 号的 User 类的用户对象 myuser,然后将 myuser 对象封装添加到 ModelAndView 中来,键为“user”,值为myuser 对象,这样这个模型旧风装好了属性名称为 user 的键值对(值为 myuser)。然后进入 JSP 页面,form 表单利用 commandName 与 modelAttribute 指定该模型的“user”属性名,就可绑定该模型属性的值(即 User 类的myuser 对象),并把该模型属性的值 myuser 对象中的各个属性与 form 表单的各个子标签一一绑定。Form 表单中应用 commandName 绑定模型属性名称示例代码如下: <form:form action="update" method="post" commandName="user"> //省略纸标签 </form:form> 这样就把后台控制器方法中创建的对象传递过来在前台展示了。 input 标签 input标签,用于展示控制器方法中封装的基本类型的Model数据。语法如下: <form:input path="domain的属性名"> 1 其中path用于指定要绑定的属性,比如上面案例中,表单中指定了要绑定的对象为User类的myuser,但要在表单中具体显示该对象的用户名username还需要这样做: <form:form action="update" method="post" commandName="user"> <form:input path="username"/> </form:form> 这样在表单绑定myuser对象的基础上,进一步把该对象的username属性绑定到文本输入框中来。 password 标签 password标签,用于填写密码,有掩码。语法如下: <form:password path="domain的属性名"/> 1 其path属性含义同上。上例中,若还要展示用户的密码,则代码如下所示: <form:form action="update" method="post" commandName="user"> <form:input path="username"/></br> <form:password path="password"/></br> </form:form> checkbox 标签 该标签的属性基本与input相同,但另有一个label属性用于指定显示的得选框的值。 <form:checkbox path="domain的属性名" label="复选框的值"> 如果该属性名存在,则默认已经选上。前台关键代码如下: myInteresting=new ArrayList<String>(); myInteresting.add("美食"); myInteresting.add("音乐"); 前台关键代码如下,则显示效果如图7.1所示。 <form:checkbox path="myInteresting" value="舞蹈" label="舞蹈"/> <form:checkbox path="myInteresting" value="旅游" label="旅游"/> <form:checkbox path="myInteresting" value="唱歌" label="唱歌"/> <form:checkbox path="myInteresting" value="音乐" label="音乐"/> <form:checkbox path="myInteresting" value="运动" label="运动"/> <form:checkbox path="myInteresting" value="美食" label="美食"/> 显示效果如图所示: checkboxes 标签 checkboxes的基本语法如下: <form:checkboxes items="${Model中Collections的属性}" path="domain的属性名"/> 1 checkboxes有几个重要属性如下表所示。 假定后台控制器方法中设置了一个集合allIntersting,用于描述兴趣爱好,其中包含了所有的兴趣爱好:旅游,音乐,唱歌,跳舞,运动,美食。但某一个人的兴趣可能只有其中若干项,所以再定义一个集合myIntesting,只含部分兴趣爱好。前台form表单需要把所有兴趣爱好以多选项的形式列出来,对某一个人有的兴趣爱好则打上勾。后台代码如下: @RequestMapping("/detail/{id}") public ModelAndView detail(@PathVariable int id){ List<String> allInteresting=new ArrayList<String>(); allInteresting.add("舞蹈"); allInteresting.add("旅游"); allInteresting.add("唱歌"); allInteresting.add("音乐"); allInteresting.add("运动"); allInteresting.add("美食"); UserService userService=new UserService(); User user=userService.getUserById(id); List<String> myInteresting=new ArrayList<String>(); myInteresting.add("旅游"); myInteresting.add("音乐"); user.setMyInteresting(myInteresting); ModelAndView mv=new ModelAndView(); mv.addObject("allInteresting", allInteresting); mv.addObject("user", user); mv.setViewName("detail"); return mv; } 前台关键代码如下: <form:checkboxes path="myInteresting" items="${allInteresting}"/> 1 最终结果如图所示: 对于Map类型的Collection来说,key值为标签的value值,value值为标签的label值,但是需要注意HashMap是乱序的 radiobutton 与 radiobuttons 标签 Radiobutton的语法如下: <form:radiobutton path="domain属性名" label=”显示的值” value=”值”/> 1 通常有多个,然后若后台传来的domain属性名的值跟其中一个value的值相同,则为选中。 Radiobuttons的语法如下: <form:radiobuttons path="domain属性值" items="${传入的Collection}"/> 1 其规则同checkboxes。如果后台传来的的domain属性名的值跟items集合中的其中一个value的值相同,则为选中。后台代码: @RequestMapping("/detail/{id}") public ModelAndView detail(@PathVariable int id){ List<String> cities=new ArrayList<String>(); cities.add("北京"); cities.add("上海"); cities.add("广州"); cities.add("深圳"); String city="深圳"; UserService userService=new UserService(); User user=userService.getUserById(id); user.setCity(city); user.setGender(true); ModelAndView mv=new ModelAndView(); mv.addObject("cities", cities); mv.addObject("user", user); mv.setViewName("detail"); return mv; } 前台代码如下: <tr> <td>性别:</td> <td> <form:radiobutton path="gender" value="true" label="男"/> <form:radiobutton path="gender" value="false" label="女"/> </td> </tr> <tr> <td>所在城市1</td> <td> <form:radiobuttons path="city" items="${cities}"/><br/> </td> </tr> 最终结果如图所示: select 与 option/options 标签 语法格式如下: <form:select path="domain的属性名"> <form:option value=”值”>显示的值</option> <form:options items="${Collections变量}"/> </form:select> 1 2 3 4 其中items的使用同前。 前台代码同前,后台代码如下: <tr> <td>所在城市2</td> <td> <form:select path="city"> <option>请选择城市</option> <form:option value="北京">北京</form:option> <form:option value="上海">上海</form:option> <form:option value="广州">广州</form:option> <form:option value="深圳">深圳</form:option> </form:select> </td> </tr> <tr> <td>所在城市3</td> <td> <form:select path="city"> <option>请选择城市</option> <form:options items="${cities}"/> </form:select> </td> </tr> ———————————————— 版权声明:本文为CSDN博主「Java Fans」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/hh867308122/article/details/129508455
-
最近项目里面需要做一个动态表头的导出。先放上来最后的表头效果。大致就是这样的一个复杂表头。所以就想到了用easyexcel来实现这个功能。那首先就是进官网,去查看相关文档。写Excel | Easy Excel (alibaba.com)得到下面的如图所示。但是,技术比较。。。在加上不完全符合我们项目先有得东西。所以进行了适配化改造。/** * 动态插入 * * @param sheetStartNum * @param sheetName sheet名称 * @param headList 动态标题,格式:List<List<String>> * @param list 数据 */public void createSheet(Integer sheetStartNum, String sheetName, List headList, List list) { WriteSheet sheet; if (Objects.isNull(excelMergeStrategy)) { sheet = EasyExcel.writerSheet(sheetStartNum, sheetName). registerWriteHandler(defaultStyle()). registerWriteHandler(new CustomerColumnWidthStyleStrategy()).build(); } else { sheet = EasyExcel.writerSheet(sheetStartNum, sheetName). registerWriteHandler(defaultStyle()). registerWriteHandler(excelMergeStrategy). registerWriteHandler(new CustomerColumnWidthStyleStrategy()).build(); } WriteTable table = new WriteTable(); table.setTableNo(0); table.setHead(headList); excelWriter.write(list, sheet, table);}具体的可以看方法注释 。/** * 创建合并策略 * * @param mergeRowIndex 合并开始行 (从1开始,忽略标题) * @param mergeColumnIndex 合并列(列下标,从0开始) * @param eachRow 合并间隔,指定几条数据之间合并 */public void mergeStrategy(int mergeRowIndex, int[] mergeColumnIndex, int eachRow) { excelMergeStrategy = new ExcelMergeStrategy(mergeRowIndex, mergeColumnIndex, eachRow);}/** * 设置头部样式 * * @return */private CellWriteHandler defaultStyle() { WriteCellStyle headWriteCellStyle = new WriteCellStyle(); //头部样式 headWriteCellStyle.setFillForegroundColor(IndexedColors.LIME.getIndex()); WriteFont headWriteFont = new WriteFont(); headWriteFont.setFontHeightInPoints((short) 12); headWriteFont.setFontName("Arial"); headWriteFont.setBold(Boolean.FALSE); headWriteFont.setColor(IndexedColors.WHITE.getIndex()); headWriteCellStyle.setWriteFont(headWriteFont); headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); headWriteCellStyle.setWrapped(Boolean.FALSE); headWriteCellStyle.setBorderRight(BorderStyle.NONE); headWriteCellStyle.setBorderLeft(BorderStyle.NONE); //内容样式 WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); return new CellStyleWriterHandler(headWriteCellStyle, contentWriteCellStyle);}然后就是组装数据 ,写了一个数据转换的方法,通过不同区划之间的关联关系,把对应区划的数据组装进去,然后导出 。本身这里就做完了 ,然后又来了新需求,按照文本内容设置单元格背景色 。本来某度某歌搜索下就搞定的,但是因为我这做了改造所以还不能直接使用。新建了一个 CellStyleWriterHandler类 继承 HorizontalCellStyleStrategy并且重写了setContentCellStyle方法 。String stringCellValue = cell.getStringCellValue(); if (StringUtils.isNotBlank(stringCellValue))) { Workbook workbook = cell.getSheet().getWorkbook(); CellStyle style = createStyle(workbook); style.setVerticalAlignment(VerticalAlignment.CENTER); style.setFillForegroundColor(IndexedColors.YELLOW.getIndex()); style.setFillPattern(FillPatternType.SOLID_FOREGROUND); cell.setCellStyle(style); }else { super.setContentCellStyle(cell, head, relativeRowIndex); }}实现之后觉得代码不多,但是当时做的时候还是花费了一番心思。做个记录提醒自己 。
-
这次给大家带来本版块3月份最新的技术干货合集。希望能有所帮助。 1.详解批处理文件语法【转】 https://bbs.huaweicloud.com/forum/thread-0298115570017548004-1-1.html 2.bat文件与Vbs文件之间的常用操作(获取用户输入,执行VBS文件)【转】 https://bbs.huaweicloud.com/forum/thread-0290115569860657004-1-1.html 3.Windows批处理实现邮件远程控制电脑的操作方法(第三方批处理)【转】 https://bbs.huaweicloud.com/forum/thread-0286115569775112001-1-1.html 4.VBScript编写Windows防止锁屏脚本程序【转】 https://bbs.huaweicloud.com/forum/thread-0235115569401321003-1-1.html 5.VBS 批量Ping的项目实现【转】 https://bbs.huaweicloud.com/forum/thread-0235115569329286002-1-1.html 6.在电脑上实现微信多开的技巧教程【转】 https://bbs.huaweicloud.com/forum/thread-0270115566231935002-1-1.html 7.Jetson NX 配置 pytorch的问题及解决方法【转】 https://bbs.huaweicloud.com/forum/thread-0290115566131044003-1-1.html 8.Swin Transformer图像处理深度learn模型【转】 https://bbs.huaweicloud.com/forum/thread-0231115565981113003-1-1.html 9.python删除文件夹中具有相同后缀类型文件的实战演练【转】 https://bbs.huaweicloud.com/forum/thread-0277115565379165001-1-1.html 10.使用Python对文件进行批量改名的方法【转】 https://bbs.huaweicloud.com/forum/thread-0231115565189-6-45002-1-1.html 11.Python调用系统命令的四种方法详解(os.system、os.popen、commands、subprocess)【转】 https://bbs.huaweicloud.com/forum/thread-0290115565107947002-1-1.html 12.Vision Transformer图像分类模型导论【转】 https://bbs.huaweicloud.com/forum/thread-0298115565007550002-1-1.html 13.Transformer导论之Bert预训练语言解析【转】 https://bbs.huaweicloud.com/forum/thread-0257115564902030001-1-1.html 14.【Java文件导出乱码】java导出文件中文名乱码或下划线或百分号解决方法(已解决)附源码【转】 https://bbs.huaweicloud.com/forum/thread-0292115543653797020-1-1.html 15.Docker 下MySQL数据库的备份和恢复的操作方法【转】 https://bbs.huaweicloud.com/forum/thread-0245115525449821010-1-1.html
-
今天将写好的springboot项目通过本地方式打包发现了这个错误
-
】其实导出这个功能在业务需求中是很多的,今天我就先不重点介绍导出实现过程,主要给大家讲解一下导出文件时候,文件名是中文的情况下会出现乱码、下划线等现象该如何去解决,如何理性分析。涉及知识点:java文件导出,中文名乱码,URLEncoder,java下载乱码,java导出中文名百分号字符串目录问题复现中文名乱码中文名下划线百分号串(ë%S7%.xlsx)中文名正常的模式1、下载异常现象原因1.1 下划线原因1.2 乱码原因1.3 %百分号串原因2、解决乱码方式2.1 源码实现2.2 源码分析Content-disposition应用URLEncoder.encode应用utf-8及ISO-8859-1应用(乱码的祸源)3、彩蛋上皇榜问题复现中文名乱码1、下载异常现象原因1.1 下划线原因没有做任何处理,直接将中文名塞到header里面,错误的代码如下:response.setHeader("Content-disposition","attachment; filename=黄大大的街舞梦.xlsx"); response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("UTF-8");1.2 乱码原因因为错乱使用转义,有些人经常会说用utf-8的ISO-8859-1方式,其实也没有错,但是这个之前应该要做一个encode操作错误代码如下:fileName = new String("黄大大的街舞梦.xlsx".getBytes("utf-8"),"ISO-8859-1"); response.setHeader("Content-disposition","attachment; filename="+ fileName); response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("UTF-8");1.3 %百分号串原因因为后端使用了encode进行中文转码,但是前端下载接收的时候没有用decode进行解码的操作。这个主要是前端的问题。2、解决乱码方式2.1 源码实现其实在上面的1.2就很接近正确的模式了,只是针对中文名缺少一个encode的方式,如下所示正确实现代码如下:fileName = URLEncoder.encode("黄大大的街舞梦.xlsx", "UTF-8"); response.setHeader("Content-disposition","attachment; filename="+ fileName); response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("UTF-8");所以说问题的核心在于没有对中文进行一个URLEncoder.encode的处理,具体效果如一开始效果图。完整实现导出的文章将在下一篇详解。2.2 源码分析可能到这里确实解决了大家的问题,但是呢我们对上面写的东西又了解多少呢?首先是Content-disposition是啥?其次URLEncoder.encode又是啥,还有utf-8及ISO-8859-1又是啥,我们为啥要从这些方向设置入手?下面请听我一一介绍。Content-disposition应用这个是MIME协议的一种扩展,主要目的就是显示待下载的文件,当浏览器接收到请求头后,就会去激活一个下载框,他们的名字需要自己塞值到header里面,从而我们下载的文件名要放这个里面。没有设置它就根本不能触发浏览器下载的那个动作。URLEncoder.encode应用URLEncoder.encode后其中fileName如下所示是带有%字母加数字的组合编码形式,其实这个你就可以这么去理解,后端采用这种编码格式,机器能识别,也能塞到头里面有助于传输,但是切记前端收到的时候用decodeURI进行解密哟,不然下载下来的是é»%之类的乱串。
-
用到了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.appl("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
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签