今天大家分享的是一个专注于NetCore平台图像处理的开源项目,老实说为这篇文章取名字想了5分钟,可能是词穷亦或是想更好的表达出这款开源项目的作用;这个项目在图像处理方面有很多功能,如:缩放,裁剪,绘画,组合图片等;今天主要讲的是用她怎么来绘图和生成验证码的实际例子。

  • 简单介绍ImageSharp
  • 试试画两条线(实线和虚线)
  • 生成个缩略图
  • 在图片上画字
  • 制作一个验证码图片
  • 结合RazorPage模板,展示验证码图片

简单介绍ImageSharp

ImageSharp是对NetCore平台扩展的一个图像处理方案,在写下本文为止它最新的nuget下载量为4,034次,作者团队最近一个月刚更新的包;没错这里说最新是因为她前身和之前的版本都很受欢迎下载量也超高;她的git项目地址:https://github.com/SixLabors/ImageSharp。如果您的项目和我一样是2.0版本(2.0以前的略过),那么直接可以通过vs的nuget控制台下载对应的包,注意绘图的话需要分别下载如下两个包:

Install-Package SixLabors.ImageSharp -Version 1.0.-beta0001

Install-Package SixLabors.ImageSharp.Drawing -Version 1.0.-beta0001

ImageSharp用法有朋友之前写过,不过都主要针对于之前的版本,本章主要用到的都是最新的,有部分写法可能不相同。

试试画两条线(实线和虚线)

这里将用她来画两条直线并保存成图片,主要起到一个介绍作用,先来看实线如下代码:

var path = @"D:\F\学习\vs2017\netcore\Study.AspNetCore\WebApp02-1\wwwroot\images";
//默认实线
using (Image<Rgba32> image = new Image<Rgba32>(, )) //画布大小
{
image.Mutate(x => x.
BackgroundColor(Rgba32.WhiteSmoke). //画布背景
DrawLines(
Rgba32.HotPink, //字体颜色
, //字体大小
new SixLabors.Primitives.PointF[]{
new Vector2(, ),
new Vector2(, ),
new Vector2(, )
} //两点一线坐标
)
); image.Save($"{path}/1.png"); //保存
}

总要步骤我都备注上文字了,这里主要通过两点一线来绘制图形,Vector2对象值得注意就是C#二维坐标(x,y)对象,其实除了Vector2还有Vector3(三维坐标)等,这对于做u3d的朋友来说不会陌生,老实说这个也是我在接触u3d时候才知道有这个类的。下面来看效果图:

由两个两点一线构造的一个角,下面来看下虚线绘制:

//虚线
using (Image<Rgba32> image = new Image<Rgba32>(, )) //画布大小
{
image.Mutate(x => x.
BackgroundColor(Rgba32.WhiteSmoke). //画布背景
DrawLines(
Pens.Dash(Rgba32.HotPink, ), //字体大小
new SixLabors.Primitives.PointF[]{
new Vector2(, ),
new Vector2(, ),
new Vector2(, )
} //两点一线坐标
)
); image.Save($"{path}/2.png"); //保存
}

步骤都差不多,只是调用了DrawLines的扩展方法而已,其他线条例子就不多说了各位自行实验。

生成个缩略图和在图片上画字

对于图片类型的网站来说缩略图是常见的,这里用ImageSharp生成缩略图很简单,本实例用8.png做样本来生成缩略图8-1.png,直接看例子如下是netstandard 1.3+的例子:

 //缩略图
using (Image<Rgba32> image = Image.Load($"{path}/8.png"))
{
image.Mutate(x => x
.Resize(image.Width / , image.Height / )
);
image.Save($"{path}/8-1.png");
}

为了更好的对比缩略图和原图的区别这里对接拿两图的属性做对比如:

能很好的看出缩略图文件大小和像素都减半了,实际缩略的时候不一定减半,这全由参数控制Resize(width,height);

画字:在图片上画我们想要的字,其实类似于水印的一种需求,下面是在图片上画字的代码:

//画字
var install_Family = new FontCollection().Install(
System.IO.Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/bak", "STKAITI.TTF")
//@"C:\Windows\Fonts\STKAITI.TTF" //字体文件
);
var font = new Font(install_Family, ); //字体
using (Image<Rgba32> image = Image.Load($"{path}/8.png"))
{
image.Mutate(x => x
.DrawText(
"你们好,我是神牛", //文字内容
font,
Rgba32.HotPink,
new Vector2(, ),
TextGraphicsOptions.Default)
);
image.Save($"{path}/8-2.png");
}

这里用ImageSharp在图片上画字的时候需要注意:字体,因为windows系统自带了字体问题这里以STKAITI.TTF字体文件为例,它存储于 C:\Windows\Fonts\STKAITI.TTF 目录,当然您可以直接把它拷贝到我们项目中如下我这里的例子一样做法(这里只测试了windows下可用,尚未测试linux下直接使用该字体文件是否可行);

制作一个验证码图片

下面我们将用她来画一个验证码类型的图片,通常验证码都有一些点和线来干扰,上面已经有画线例子了,这里展示怎么画点:

//画点(规则的点,其他的各位自行写算法)
var dianWith = ; //点宽度
var xx = ; //图片宽度
var yy = ; //图片高度 var xx_space = ; //点与点之间x坐标间隔
var yy_space = ; //y坐标间隔 var listPath = new List<IPath>();
for (int i = ; i < xx / xx_space; i++)
{
for (int j = ; j < yy / yy_space; j++)
{
var position = new Vector2(i * xx_space, j * yy_space);
var linerLine = new LinearLineSegment(position, position);
var shapesPath = new SixLabors.Shapes.Path(linerLine);
listPath.Add(shapesPath);
}
} using (Image<Rgba32> image = new Image<Rgba32>(xx, yy)) //画布大小
{
image.Mutate(x => x.
BackgroundColor(Rgba32.WhiteSmoke). //画布背景
Draw(
Pens.Dot(Rgba32.HotPink, dianWith), //大小
new SixLabors.Shapes.PathCollection(listPath) //坐标集合
)
);
image.Save($"{path}/9.png"); //保存
}

这里直接利用IImageProcessingContext<TPixel>扩展方法Draw来绘制有规则的点,如图所示:

比较单调,或许您们能做的更好看些;下面来做验证码图片,主要由:画点+画字=验证码图片,这里我封装了一个方法直接生成验证码图片:

/// <summary>
/// 画点+画字=验证码图片
/// </summary>
/// <param name="content">验证码</param>
/// <param name="outImgPath">输出图片路径</param>
/// <param name="fontFilePath">字体文件</param>
/// <param name="x">图片宽度</param>
/// <param name="y">图片高度</param>
public void GetValidCode(
string content = "我是神牛",
string outImgPath = "D:/F/学习/vs2017/netcore/Study.AspNetCore/WebApp02-1/wwwroot/images/10.png",
string fontFilePath = @"D:\F\学习\vs2017\netcore\Study.AspNetCore\WebApp02-1\wwwroot\bak\STKAITI.TTF",
int xx = , int yy = )
{
var dianWith = ; //点宽度
var xx_space = ; //点与点之间x坐标间隔
var yy_space = ; //y坐标间隔
var wenZiLen = content.Length; //文字长度
var maxX = xx / wenZiLen; //每个文字最大x宽度
var prevWenZiX = ; //前面一个文字的x坐标
var size = ;//字体大小 //字体
var install_Family = new FontCollection().Install(
fontFilePath
//@"C:\Windows\Fonts\STKAITI.TTF" //windows系统下字体文件
);
var font = new Font(install_Family, size); //字体 //点坐标
var listPath = new List<IPath>();
for (int i = ; i < xx / xx_space; i++)
{
for (int j = ; j < yy / yy_space; j++)
{
var position = new Vector2(i * xx_space, j * yy_space);
var linerLine = new LinearLineSegment(position, position);
var shapesPath = new SixLabors.Shapes.Path(linerLine);
listPath.Add(shapesPath);
}
} //画图
using (Image<Rgba32> image = new Image<Rgba32>(xx, yy)) //画布大小
{
image.Mutate(x =>
{
//画点
var imgProc = x.BackgroundColor(Rgba32.WhiteSmoke). //画布背景
Draw(
Pens.Dot(Rgba32.HotPink, dianWith), //大小
new SixLabors.Shapes.PathCollection(listPath) //坐标集合
); //逐个画字
for (int i = ; i < wenZiLen; i++)
{
//当前的要输出的字
var nowWenZi = content.Substring(i, ); //文字坐标
var wenXY = new Vector2();
var maxXX = prevWenZiX + (maxX - size);
wenXY.X = new Random().Next(prevWenZiX, maxXX);
wenXY.Y = new Random().Next(, yy - size); prevWenZiX = Convert.ToInt32(Math.Floor(wenXY.X)) + size; //画字
imgProc.DrawText(
nowWenZi, //文字内容
font,
i % > ? Rgba32.HotPink : Rgba32.Red,
wenXY,
TextGraphicsOptions.Default);
}
});
//保存到图片
image.Save(outImgPath);
}
}

通过简单的调用 GetValidCode("我是神牛");return Page(); 能得到如图验证码图片的效果:

文字看起来好像在点的前面,不过没关系只需要把画点和画字的先后顺序修改下就行了,这里不贴图了;

结合RazorPage模板,展示验证码图片

上面一节是生成了验证码图片,当然实际场景中我们是不需要生成验证码物理图片的,只需要返回一个流或base64等方式输出到web界面上就行了,我们可以来看看 Image<TPixel> 保存时候的扩展方法:

        //
// 摘要:
// Saves the image to the given stream using the currently loaded image format.
//
// 参数:
// source:
// The source image
//
// filePath:
// The file path to save the image to.
//
// 类型参数:
// TPixel:
// The Pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void Save<TPixel>(this Image<TPixel> source, string filePath) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream using the currently loaded image format.
//
// 参数:
// source:
// The source image
//
// filePath:
// The file path to save the image to.
//
// encoder:
// The encoder to save the image with.
//
// 类型参数:
// TPixel:
// The Pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the encoder is null.
public static void Save<TPixel>(this Image<TPixel> source, string filePath, IImageEncoder encoder) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream using the currently loaded image format.
//
// 参数:
// source:
// The source image
//
// stream:
// The stream to save the image to.
//
// format:
// The format to save the image to.
//
// 类型参数:
// TPixel:
// The Pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void Save<TPixel>(this Image<TPixel> source, Stream stream, IImageFormat format) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the bmp format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsBmp<TPixel>(this Image<TPixel> source, Stream stream) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the bmp format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// encoder:
// The encoder to save the image with.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsBmp<TPixel>(this Image<TPixel> source, Stream stream, BmpEncoder encoder) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the gif format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// encoder:
// The options for the encoder.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsGif<TPixel>(this Image<TPixel> source, Stream stream, GifEncoder encoder) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the gif format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsGif<TPixel>(this Image<TPixel> source, Stream stream) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the jpeg format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// encoder:
// The options for the encoder.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsJpeg<TPixel>(this Image<TPixel> source, Stream stream, JpegEncoder encoder) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the jpeg format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsJpeg<TPixel>(this Image<TPixel> source, Stream stream) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the png format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsPng<TPixel>(this Image<TPixel> source, Stream stream) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the png format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// encoder:
// The options for the encoder.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsPng<TPixel>(this Image<TPixel> source, Stream stream, PngEncoder encoder) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the raw image to the given bytes.
//
// 参数:
// source:
// The source image
//
// buffer:
// The buffer to save the raw pixel data to.
//
// 类型参数:
// TPixel:
// The Pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, byte[] buffer) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the raw image to the given bytes.
//
// 参数:
// source:
// The source image
//
// 类型参数:
// TPixel:
// The Pixel format.
//
// 返回结果:
// A copy of the pixel data as bytes from this frame.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static byte[] SavePixelData<TPixel>(this ImageFrame<TPixel> source) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the raw image to the given bytes.
//
// 参数:
// source:
// The source image
//
// buffer:
// The buffer to save the raw pixel data to.
//
// 类型参数:
// TPixel:
// The Pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SavePixelData<TPixel>(this Image<TPixel> source, byte[] buffer) where TPixel : struct, IPixel<TPixel>;

好吧有点多,我们只需要明白她能转base64,stream,保存为图片等就行了;这里我们将用到 SaveAsPng(Stream) 方法,然后获取他的byte[],如下代码:

/// <summary>
/// 画点+画字=验证码byte[]
/// </summary>
/// <param name="content">验证码</param>
/// <param name="outImgPath">输出图片路径</param>
/// <param name="fontFilePath">字体文件</param>
/// <param name="x">图片宽度</param>
/// <param name="y">图片高度</param>
public byte[] GetValidCodeByte(
string content = "我是神牛",
string fontFilePath = @"D:\F\学习\vs2017\netcore\Study.AspNetCore\WebApp02-1\wwwroot\bak\STKAITI.TTF",
int xx = , int yy = )
{
var bb = default(byte[]);
try
{
var dianWith = ; //点宽度
var xx_space = ; //点与点之间x坐标间隔
var yy_space = ; //y坐标间隔
var wenZiLen = content.Length; //文字长度
var maxX = xx / wenZiLen; //每个文字最大x宽度
var prevWenZiX = ; //前面一个文字的x坐标
var size = ;//字体大小 //字体
var install_Family = new FontCollection().Install(
fontFilePath
//@"C:\Windows\Fonts\STKAITI.TTF" //windows系统下字体文件
);
var font = new Font(install_Family, size); //字体 //点坐标
var listPath = new List<IPath>();
for (int i = ; i < xx / xx_space; i++)
{
for (int j = ; j < yy / yy_space; j++)
{
var position = new Vector2(i * xx_space, j * yy_space);
var linerLine = new LinearLineSegment(position, position);
var shapesPath = new SixLabors.Shapes.Path(linerLine);
listPath.Add(shapesPath);
}
} //画图
using (Image<Rgba32> image = new Image<Rgba32>(xx, yy)) //画布大小
{
image.Mutate(x =>
{
var imgProc = x; //逐个画字
for (int i = ; i < wenZiLen; i++)
{
//当前的要输出的字
var nowWenZi = content.Substring(i, ); //文字坐标
var wenXY = new Vector2();
var maxXX = prevWenZiX + (maxX - size);
wenXY.X = new Random().Next(prevWenZiX, maxXX);
wenXY.Y = new Random().Next(, yy - size); prevWenZiX = Convert.ToInt32(Math.Floor(wenXY.X)) + size; //画字
imgProc.DrawText(
nowWenZi, //文字内容
font,
i % > ? Rgba32.HotPink : Rgba32.Red,
wenXY,
TextGraphicsOptions.Default);
} //画点
imgProc.BackgroundColor(Rgba32.WhiteSmoke). //画布背景
Draw(
Pens.Dot(Rgba32.HotPink, dianWith), //大小
new SixLabors.Shapes.PathCollection(listPath) //坐标集合
);
});
using (MemoryStream stream = new MemoryStream())
{
image.SaveAsPng(stream);
bb = stream.GetBuffer();
}
}
}
catch (Exception ex)
{
}
return bb;
}

该方法返回了一个byte[]数组,然后通过HttpGet方式请求Razor接口,前端就能够获取到这个验证码图片byte[]了;

/// <summary>
/// Get获取验证码图片byte[]
/// </summary>
/// <returns></returns>
public FileResult OnGetValidCode()
{
var codebb = GetValidCodeByte(DateTime.Now.ToString("mmssfff"));
return File(codebb, "image/png");
}

我们通过get请求获取验证码: http://localhost:1120/login?handler=ValidCode ,然后得到如图效果:

本篇内容到此就结束了,如果对您有好的帮助,不妨点个“赞”;一起努力推动NetCore发展吧,谢谢。

ImageSharp一个专注于NetCore平台图像处理的开源项目的更多相关文章

  1. 我发起了一个 .Net Core 平台上的 开源项目 ShadowDomain 用于 热更新

    大家好,  我发起了一个 .Net Core 平台上的 开源项目 ShadowDomain  用于 热更新 . 简单的说, 原理就是 类似 Asp.net 那样 让 当前 WebApp 运行在一个 A ...

  2. 我发起了一个 用 物理服务器 和 .Net 平台 构建云平台 的 .Net 开源项目

    大家好 , 我发起了一个 用 物理服务器 和 .Net 平台 构建云平台 的 .Net 开源项目 . 对 , 用 物理服务器 和 .Net 平台 构建 云平台 . 通过 .Net 构建 分布式 计算集 ...

  3. 我发起了一个 .Net 平台上的 开源项目 知识图谱 Babana Map 和 文本文件搜索引擎 Babana Search

    起因 也是 前几天 有 网友 在 群 里发了   知识图谱   相关的文章, 还有 有 网友 问起   NLog -> LogStash -> Elastic Search  的 问题, ...

  4. TomatoLog 是一个基于 .NETCore 平台的产品。

    TomatoLog TomatoLog 是一个基于 .NETCore 平台的产品. The TomatoLog 是一个中间件,包含客户端.服务端,非常容易使用和部署. 客户端实现了ILoggerFac ...

  5. 融e学 一个专注于重构知识,培养复合型人才的平台【获取考试答案_破解】

    考试系统-融e学-一个专注于重构知识,培养复合型人才的平台.[获取答案] ganquanzhong 背景:今天去完成学校在融e学上开设的必修课和选修课考试,由于自己的时间有限(还有其他的事情要去做). ...

  6. 转:一个跨WINDOWS LINUX平台的线程类

     来源:http://blog.csdn.net/dengxu11/article/details/7232681 继Windows下实现一个CThread封装类之后,这里我再实现一个跨WINDOWS ...

  7. Composite C1是一个.Net平台上开源专业的CMS开源项目

    CompositeC1 4 发布 Composite C1是一个.Net平台上开源专业的CMS开源项目,很多的功能用户界面,面向任务的支持与各种工具协作.当编辑内容时在用户端体验很友好.编辑器与开发者 ...

  8. 我发起了一个 .Net 平台上的 产生式编程 开源项目 GP.Net

    大家好 , 我发起了一个 .Net 平台上的 产生式编程 开源项目 GP.Net . 我们可以先看看一个网友的 代码生成器 项目 : <.Net 代码生成器 for PostgreSql> ...

  9. 这是一个专注于电脑技术、软件应用、互联网、嵌入式,电子技术行业等的原创IT博客

    http://www.choovin.com/ 这是一个专注于电脑技术.软件应用.互联网.嵌入式,电子技术行业等的原创IT博客

随机推荐

  1. Python输入输出练习,运算练习,turtle初步练习

    Hello World! 简单交互(交互式,文件式)教材P19 radius=25 area=3.1415*radius*radius print(area) print('{:.2f}'.forma ...

  2. Socket通信中AF_INET 和 AF_UNIX域的区别

    转载:http://blog.csdn.net/sandware/article/details/40923491 1.  AF_INET域socket通信过程 典型的TCP/IP四层模型的通信过程. ...

  3. 团队作业4--第一次项目冲刺(Alpha版本) 4

    一.Daily Scrum Meeting照片 二.燃尽图 三.项目进展 完成对查重结果的写出与保存,将查重结果写出并导出保存为Excel形式 四.困难与问题 对查重结果的保存,当有多份文档进行比较的 ...

  4. 团队作业8——第二次项目冲刺(Beta阶段)--5.19 first day

    团队作业8--第二次项目冲刺(Beta阶段)--5.19 Day one: 会议照片 项目进展 由于今天是Beta版本项目冲刺的第一天,所以没有昨天已完成任务.以下是今日具体的任务安排. 队员 今日计 ...

  5. 201521123067 《Java程序设计》第8周学习总结

    201521123067 <Java程序设计>第8周学习总结 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 2. 书面作业 Q1.List中指定 ...

  6. 201521123119《Java程序设计》第8周学习总结

    1. 本周学习总结 2. 书面作业 Q1.List中指定元素的删除(题目4-1) Q1.1 实验总结 用split(" ")方法将list转化为字符串数组.要注意行中含有多个空格的 ...

  7. Java 第十周总结

    1. 本周学习总结 2. 书面作业 1.finally (题目4-2) 1.1 截图你的提交结果(出现学号) 1.2 4-2中finally中捕获异常需要注意什么? finally创建一个代码块.该代 ...

  8. HTML结构

    HTML:超文本标记语言. 可以放除了文本之外的内容,像图片.音频.视频等 由很多标签组成 html基本结构: <html> <head> 头标签存放网页信息,编码格式等 &l ...

  9. Hyperledger Fabric 1.0 从零开始(八)——Fabric多节点集群生产部署

    6.1.平台特定使用的二进制文件配置 该方案与Hyperledger Fabric 1.0 从零开始(五)--运行测试e2e类似,根据企业需要,可以控制各节点的域名,及联盟链的统一域名.可以指定单独节 ...

  10. JSP第四篇【EL表达式介绍、获取各类数据、11个内置对象、执行运算、回显数据、自定义函数、fn方法库】

    什么是EL表达式? 表达式语言(Expression Language,EL),EL表达式是用"${}"括起来的脚本,用来更方便的读取对象! EL表达式主要用来读取数据,进行内容的 ...