-
使用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
-
【问题来源】 光大【问题简要】 如何获取随路数据【问题类别】 智能外呼、IVR、座席【AICC解决方案版本】 AICC版本信息:AICC 8.15.0 UAP版本信息:UAP9600 V100R005C00SPC112 CTI版本信息:ICDV300R008C23SPC014【问题现象描述】 登录客户服务云,智能自动外呼->外呼管理->外呼任务管理->新建自动外呼->任务类型选为"预测外呼",只能选择只能选择"技能队列"类型。是否可以任务类型选为"预测外呼"时,不进入技能队列,而是进入IVR流程文件,需要如何设置?
-
【问题来源】【必填】【可选问题来源:某地12345新建AICC项目】【问题简要】:8008分机、故障现象:发生时间2023-04-20 9:37、2023-04-17 10:08,2个时间通话过程中openeye断线(通话还保持着),无法正常挂断电话。 帮忙看下openeye日志 判断下原因 谢谢【问题类别】【可选问题分类:OPENEYE问题】【AICC解决方案版本】【必填】【AICC可选择版本:AICC 8.15.0】【UAP可选择版本:UAP9600 V100R005C00SPC026】【CTI可选择版本:ICD V300R008C23】【期望解决时间】越快越好
-
【问题来源】 光大【问题简要】 如何获取随路数据【问题类别】 智能外呼、座席【AICC解决方案版本】 AICC版本信息:AICC 8.15.0 UAP版本信息:UAP9600 V100R005C00SPC112 CTI版本信息:ICDV300R008C23SPC014【问题现象描述】 预测外呼系统,调用CC-iSales的新增外呼活动接口创建外呼活动(deviceType:2技能队列)、新增外呼号码接口添加外呼数据(设置参数callData随路数据)。OBS通过存储过程获取样本,进行外呼,客户接通后,电话转接人工客服系统时,人工客服系统如何获取callData(随路数据)。
-
您好:【AICC版本信息】 AICC版本信息:AICC 8.15.0 UAP版本信息:UAP9600 V100R005C00SPC112 CTI版本信息:ICDV300R008C23SPC014【问题场景及现象】客户通过iSales接口,调用新增外呼活动(deviceType值为2:技能队列)、新增外呼号码接口(callData随路数据),将外呼数据存储数据库。OBS通过存储过程获取样本,进行外呼工作,客户接通后,电话通过配置的技能队列,转接至人工座席,人工座席系统应如何获取callData随路数据(能否提供获取callData的demo)。
-
您好:【AICC版本信息】 AICC版本信息:AICC 8.15.0 UAP版本信息:UAP9600 V100R005C00SPC112 CTI版本信息:ICDV300R008C23SPC014【问题场景及现象】客户通过iSales接口,调用新增外呼活动(deviceType值为3:IVR)、新增外呼号码接口,将外呼数据存储数据库。OBS通过存储过程获取样本,进行外呼工作,外呼数据呼完后,外呼活动是否直接结束? 如果外呼活动结束,是否可以再次调用"新增外呼号码"接口,向该外呼活动中添加外呼数据,OBS再次进行外呼工作。
-
您好: 【AICC版本信息】AICC版本信息:AICC 8.15.0UAP版本信息:UAP9600 V100R005C00SPC112CTI版本信息:ICDV300R008C23SPC014【问题场景及现象】客户通过iSales接口,调用新增外呼活动(deviceType值为3:IVR)、新增外呼号码接口,将外呼数据存储数据库。最终外呼时,OBS存储过程获取样本,并通过icdcomm调用IVR,这个过程是否需要进行开发工作。
-
前言 作为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); }}实现之后觉得代码不多,但是当时做的时候还是花费了一番心思。做个记录提醒自己 。
上滑加载中
推荐直播
-
华为云码道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华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签