php file_get_contents怎么读取图片 - 问答 - 亿速云

mikel阅读(317)

来源: php file_get_contents怎么读取图片 – 问答 – 亿速云

要使用file_get_contents函数读取图片,需要提供图片的URL地址作为参数。示例代码如下:

$imageUrl = 'https://example.com/image.jpg';
$imageData = file_get_contents($imageUrl);

// 保存图片到本地文件
file_put_contents('path/to/save/image.jpg', $imageData);

在上述示例中,$imageUrl是图片的URL地址,$imageData是通过file_get_contents函数获取的图片内容。然后,可以使用file_put_contents函数将图片内容保存到本地文件中。请将path/to/save/image.jpg替换为您要保存图片的本地路径。

请注意,使用file_get_contents读取大型或者远程服务器上的图片可能会导致内存溢出,这种情况下可以考虑使用stream_context_create函数和fopen函数来处理。以下是一个使用stream_context_createfopen函数的示例:

$imageUrl = 'https://example.com/image.jpg';

$context = stream_context_create(['http' => ['user_agent' => 'Mozilla/5.0 (Windows NT 6.1; rv:35.0) Gecko/20100101 Firefox/35.0']]);
$imageFile = fopen('path/to/save/image.jpg', 'w');
stream_copy_to_stream(fopen($imageUrl, 'r', false, $context), $imageFile);
fclose($imageFile);

在上述示例中,stream_context_create函数用于创建一个包含用户代理信息的上下文,避免某些远程服务器拒绝访问的限制。然后,使用fopen函数打开本地文件和远程图片文件,并使用stream_copy_to_stream函数将远程图片内容复制到本地文件。最后,关闭文件句柄。同样,请将path/to/save/image.jpg替换为您要保存图片的本地路径。

购买使用亿速云服务器,可以极大降低初创企业、中小企业以及个人开发者等用户群体的整体IT使用成本,无需亲自搭建基础设施、简化了运维和管理的日常工作量,使用户能够更专注于自身的业务发展和创新。

php将远程图片保存到本地,php使用file_get_contents 将远程图片保存到本地_php保存图片到本地-CSDN博客

mikel阅读(309)

来源: php将远程图片保存到本地,php使用file_get_contents 将远程图片保存到本地_php保存图片到本地-CSDN博客

需求背景:将远程图片保存至本地

PHP可以使用 file_get_contents 函数和 file_put_contents 函数来将远程图片保存到本地。具体步骤如下:

使用 file_get_contents 函数获取远程图片的内容。

$url = ‘http://example.com/image.jpg’;
$imgContent = file_get_contents($url);

使用 file_put_contents 函数将获取到的图片内容保存到本地文件中。

$localPath = ‘/path/to/local/image.jpg’;
file_put_contents($localPath, $imgContent);

完整的代码如下所示:

$url = ‘http://example.com/image.jpg’;
$localPath = ‘/path/to/local/image.jpg’;
$imgContent = file_get_contents($url);
file_put_contents($localPath, $imgContent);

在上面的代码中,我们先定义了远程图片的 URL 和本地图片的路径,然后使用 file_get_contents 函数获取远程图片的内容,并将其保存到 $imgContent 变量中。最后,使用 file_put_contents 函数将 $imgContent 的内容保存到本地文件中。
需要注意的是,使用该方法保存远程图片时,需要确保 PHP 环境对远程文件的访问权限。如果远程图片需要进行身份验证或者需要通过代理服务器访问,可以使用 CURL 函数来获取图片内容。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/gjwgjw1111/article/details/129572956

使用phpqrcode生成二维码 - 简书

mikel阅读(328)

来源: 使用phpqrcode生成二维码 – 简书

在项目中使用的到了显示二维码,网上搜了一下php主要使用phpqrcode实现。

        $data = 'www.baidu.com'; 
        $level = 'L';// 纠错级别:LMQH
        $size = 6;// 点的大小:110,用于手机端4就可以了
        
        include EXTEND_PATH.'org/phpqrcode/phpqrcode.php';
        $QRcode = new \QRcode();
        ob_start();
        $QRcode->png($data,false,$level,$size);
        $imageString = base64_encode(ob_get_contents());
        ob_end_clean();
        //$path=ROOT_PATH.'public/static/images/qrcode/';
        //$QRcode->png($data,$path.$fileName,$level,$size);// 生成本地图片
        return $imageString;

上面的代码是生成一个base64的字符串,在页面如下使用就可以显示图片了。

<img src="data:image/png;base64,这里是base64编码内容" />

关于QRcode::png方法参数说明:

1.第一个参数$text,就是上面代码里的URL网址参数,
2.第二个参数$outfile默认为否,不生成文件,只将二维码图片返回,否则需要给出存放生成二维码图片的路径
3.第三个参数$level默认为L,这个参数可传递的值分别是L(QR_ECLEVEL_L,7%),M(QR_ECLEVEL_M,15%),Q(QR_ECLEVEL_Q,25%),H(QR_ECLEVEL_H,30%)。这个参数控制二维码容错率,不同的参数表示二维码可被覆盖的区域百分比。利用二维维码的容错率,我们可以将头像放置在生成的二维码图片任何区域。
4.第四个参数$size,控制生成图片的大小,默认为4
5.第五个参数$margin,控制生成二维码的空白区域大小
6.第六个参数$saveandprint,保存二维码图片并显示出来,$outfile必须传递图片路径。

第二个参数默认是false,方法返回的是二进制的图片流。生成在缓冲区的,在页面输出的时候会从缓冲区送到浏览器。所以在代码中是使用log输出是不会记录在日志中的,也不需要使用echo进行内容输出。所以直接使用base64_encode(QRcode::png)是没有用的。
这里使用到了ob_start()方法,打开输出缓冲区,所有的输出信息不在直接发送到浏览器,而是保存在输出缓冲区里面。这里就是把生成的图片流从缓冲区保存到内存对象上,使用base64_encode变成编码字符串,通过json返回给页面。
看一下直接使用QRcode::png返回图片流到浏览器


最后简单说一下ob:
ob是output buffering的简称,而不是output cache,ob用对了是能对速度有一定的帮助,但是盲目的加上ob函数,只会增加CPU额外的负担。说说ob的基本作用:
1.防止在浏览器有输出之后再使用setcookie,或者header,session_start函数造成的错误。(我本以为最开始说的代码是这样的作用,但后来朋友说不是的),其实这样的用法少用为好,养成良好的代码习惯。
2.捕捉对一些不可获取的函数的输出,比如phpinfo会输出一大堆的HTML,但是我们无法用一个变量例如$info=phpinfo();来捕捉,这时候ob就管用了。
3.对输出的内容进行处理,例如进行gzip压缩,例如进行简繁转换,例如进行一些字符串替换。
4.生成静态文件,其实就是捕捉整页的输出,然后存成文件,经常在生成HTML,或者整页缓存中使用。
对于刚才说的第三点中的GZIP压缩,可能是很多人想用,却没有真真用上的,加一个ob_gzhandler这个回调函数就可以了。

ob_start(ob_gzhandler);
内容
echo ob_get_contents() ;
ob_end_flush();

作者:可乐爱上咖啡
链接:https://www.jianshu.com/p/768e72938237
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

宝塔面板建站后如何本地调试 - 问答 - 亿速云

mikel阅读(302)

来源: 宝塔面板建站后如何本地调试 – 问答 – 亿速云

使用宝塔建站后需要在本地实现调试,要在本机hosts文件中进行配置

1.在本机上使用记事本打开hosts文件,hosts文件路径:C:\Windows\System32\Drivers\etc;

2.hosts文件打开后,在文件最后面添加宝塔建站的网站域名,如127.0.0.1 www.123.com,保存即可;

 

购买使用亿速云服务器,可以极大降低初创企业、中小企业以及个人开发者等用户群体的整体IT使用成本,无需亲自搭建基础设施、简化了运维和管理的日常工作量,使用户能够更专注于自身的业务发展和创新。

linux宝塔面板安装composer的方法[全网详解]_宝塔 composer-CSDN博客

mikel阅读(284)

来源: linux宝塔面板安装composer的方法[全网详解]_宝塔 composer-CSDN博客

概念:
Composer是一个PHP依赖管理器,可以方便地管理和安装PHP项目所依赖的库和软件包。它可以自动下载、安装和更新这些库,同时确保它们与项目的其他部分兼容。Composer可以通过一个名为composer.json的配置文件来配置,该文件列出了项目所依赖的库、版本等信息。Composer还可以从Packagist等仓库中查找和下载各种PHP库和软件包。由于Composer的出现,开发人员不再需要手动下载、安装和配置PHP库,从而大大简化了PHP项目的开发和维护过程。

有些项目安装会用到composer,如果直接安装会报错“Warning: putenv() has been disabled”。

 

所以宝塔面板安装composer我们需要删除禁用函数,删除之后,才可以正常安装。下面就说说,宝塔面板linux版本如何安装composer!

1、删除禁用函数

宝塔面板默认禁用一些安装 Composer 要用到的 3 个函数如下:

putenv() 、 pcntl_signal() 、 proc_open(),

“PHP管理”→“禁用函数”→“删除putenv”。

 

这样删除就可以了。其他诸如此类,这里不再赘述。

2、全局安装

1)安装之前更新服务器软件包

如果是centos系统,可以使用 SSH 执行下方命令:

yum update -y
如果是debian类的

apt update
删除禁用函数和更新软件包后,就可以愉快安装composer了。这里用全局安装,安装命令如下:

cd ~
php -r “copy(‘https://install.phpcomposer.com/installer’, ‘composer-setup.php’);” # 将安装脚本下载到当前目录
php composer-setup.php # 运行安装脚本
php -r “unlink(‘composer-setup.php’);” # 删除安装脚本
mv composer.phar /usr/local/bin/composer # 全局安装 composer(配置系统环境变量)

如果是国内服务器,可以会下载缓慢,可以换源解决。将 composer 源改成阿里云的镜像

composer config repo.packagist composer https://mirrors.aliyun.com/composer/

安装完成后,可以用命令:“composer –version”来查看你安装的版本号。

 

3、局部安装

这里简单提一下,上述下载Composer的过程正确执行完毕后(最后一步不要执行),可以将composer.phar文件复制到任意目录(比如项目根目录下),然后通过php composer.phar指令即可使用Composer了

不过还是推荐用全局安装~~~

4、更换镜像源

随着Composer的发展,已经很多大厂商都加入了Composer的镜像源,强烈推荐使用阿里云的,同步频率快,速度也很稳定,不过你可能对其他的也有兴趣:

阿里云Composer镜像
https://mirrors.aliyun.com/composer/
腾讯云Composer镜像
PHP Composer
华为云Composer镜像
https://repo.huaweicloud.com/repository/php/
安畅云Composer镜像
https://php.cnpkg.org
5、最后

宝塔安装composer会报错的处理方法,大概就是这样了 。如果是lnmp环境那么需要编辑PHP配置文件:

vi /usr/local/php/etc/php.ini进入编辑状态。

输入/,进入搜索模式,找到disable_functions

寻找disable_functions字符串,将后面的putenv删除。
————————————————
版权声明:本文为CSDN博主「奔跑的蜗牛.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/MrWangisgoodboy/article/details/129929934

.net 温故知新:【10】.NET ORM框架EFCore使用入门之CodeFirs、DBFirst - XSpringSun - 博客园

mikel阅读(290)

来源: .net 温故知新:【10】.NET ORM框架EFCore使用入门之CodeFirs、DBFirst – XSpringSun – 博客园

前言:本系列是我自己学习.net相关知识,以便跟上.net跨平台的步伐,目前工作原因基本在.net Framework4.7以下,所以才有了这一系列的学习总结,但是并不是从基本的C#语法和基础知识开始的,而是围绕.net core以后平台的重要设计和差异进行温故知新。目的在于通过要点的梳理最后串联起整个跨平台框架。之前的几篇算是把框架重要设计和框架重要知识点复习了,当然什么系统都可能使用到ORM框架。所以这里为了整个过程的完整连续性加入一个EFCore的示例,ORM不算详细写了,毕竟ORM框架可以根据需求选择很多,如果再详细那又是另外一个系列了,这里只做简单介绍。从这篇ORM完成之后就将进入ASP.NET core的学习总结!

EFCore

Entity Framework Core (EF Core) 是适用于 .NET 的新式对象数据库映射器。 它支持 LINQ 查询、更改跟踪、更新和架构迁移。

EF Core 通过数据库提供程序插件模型与 SQL Server/Azure SQL 数据库、SQLite、Azure Cosmos DB、MySQL、PostgreSQL 和更多数据库配合使用。

EFCore入门

在上面的EFCore介绍中我们又看到了提供程序描述,之前文章多次提到这个提供程序是.net框架中随处可见的,也就是通过这些不同的提供程序实现扩展和适配。本文我用两种数据库,sqlite和SQLServer并分别用code-first(代码优先)模式和db-frist(数据库优先)模式演示EFCore的使用。

1、Code First

新建一个.net 6.0 控制台应用程序,安装nuget包(EFCore的sqlite提供程序):

Install-Package Microsoft.EntityFrameworkCore.Sqlite

重要依赖Package Microsoft.EntityFrameworkCore包会自动安装。

编写SqliteContext类构成模型的上下文类,实体类:Student、Course。

namespace EFCoreDemo.Sqlite
{
    public class SqliteContext : DbContext
    {
        public DbSet<Student> Students { get; set; }
        public DbSet<Course> Courses { get; set; }

        /// <summary>
        /// sqlite 数据库文件路径
        /// </summary>
        public string DbPath { get; }

        public SqliteContext()
        {
            var folder = Environment.CurrentDirectory;
            DbPath = System.IO.Path.Join(folder, "CodeFirst.db");
        }

        protected override void OnConfiguring(DbContextOptionsBuilder options)
            => options.UseSqlite($"Data Source={DbPath}");
    }

    /// <summary>
    /// 学生
    /// </summary>
    public class Student
    {
        public int id { get; set; }
        public string name { get; set; }

        public List<Course> courses { get; set; }
    }

    /// <summary>
    /// 课程
    /// </summary>
    public class Course
    {
        public int id { get; set; }

        public string name { get; set; }
    }
}

接着我们安装包Microsoft.EntityFrameworkCore.Tools用来生成数据库

Install-Package Microsoft.EntityFrameworkCore.Tools

然后在“包管理器控制台(PMC)”中使用命令:

Add-Migration InitialCreate

提示创建成功:

PM> Add-Migration InitialCreate
Build started...
Build succeeded.
To undo this action, use Remove-Migration.
PM> Update-Database
Build started...
Build succeeded.
Applying migration '20221130040124_InitialCreate'.
Done.

搭建基架,为模型创建一组初始表。该命令完成后会生成一个Migration文件夹包含两个类,一个数据库创建类InitialCreate是我们定义的,可以按需求更改名称。一个模型快照类SqliteContextModelSnapshot上面的[DbContext(typeof(SqliteContext))]属性标识类所属的DbContext。使用该atrribute确定迁移应用于哪个上下文。

由于这是项目的第一次迁移,如果我们修改了模型后再Add-Migration生成的时候EFCore 会在添加列之前将更新的模型与旧模型的快照进行比较。基于该比较,EF Core 检测变化,并添加适当的迁移而不是再重新生成数据库。

image

最后运行命令Update-Database生成数据库和表,数据库在我们配置的程序路径下。

PM> Update-Database
Build started...
Build succeeded.
Applying migration '20221130040124_InitialCreate'.
Done.

image

生成的sqlite数据库如图,因为我们定义了Student、Course实体,所以会生成两个表以及表的字段,同时还会生成一个历史表,用于记录我们每次迁移的记录。另外在Student类里面我们定义了一个public List<Course> courses { get; set; } 属性,表示学生拥有哪些课程,这样相当于是一个外键关系,EFCore会为我们在Coures表里面创建一个Sudentid的外键来表达关联关系。同时我们查询学生的话理论上也能查出学生拥有的课程,接下来我们向数据库中插入数据并进行查询。

注意在命令生成的时候CurrentDirectory是项目目录,我们运行的时候要把生成的CodeFirst.db复制到bin/Debug

        static void Main(string[] args)
        {
            //实例化context
            SqliteContext context = new SqliteContext();
            //添加数据
            for (int i = 0; i < 5; i++)
            {

                List<Course> courses = new()
                {
                    new Course()
                    {
                        name = "语文"+i
                    },
                     new Course()
                    {
                        name = "数学"+i
                    },
                };

                context.Students.Add(new Student()
                {
                    name = "学生" + i,
                    courses = courses
                });
                context.SaveChanges();
            }
            //查询
            var students = context.Students.Include(t=>t.courses).ToList();
            students.ForEach(e =>
            {
                Console.Write(e.name);
                e.courses.ForEach(e => { Console.Write(e.name); });
                Console.WriteLine();
            });

            var student = context.Students.SingleOrDefault(t => t.name=="学生3");
            Console.WriteLine($"id:{student.id},name:{student.name}");
            Console.ReadKey();
        }

image

查询如果要包含外键关联的数据,需要用Include方法。不然上面的结果第一次可以用,第二次就查询不出来,因为第一次的数据添加后会直接在上下文Context里面。

2、 DB Frist

首先我们建一个SQLServer数据库,然后反向建两张一样的表。

image

在项目中安装 Microsoft.EntityFrameworkCore.Design 的 nuget 包。
Install-Package Microsoft.EntityFrameworkCore.Design
由于上面我安装Microsoft.EntityFrameworkCore.Tools nuget包的时候依赖项已经安装了所以就不需要再安装了。

然后安装SQLServer的提供程序 nuget 包。

Install-Package Microsoft.EntityFrameworkCore.SqlServer

image

安装好 nuget 包后在程序包管理器控制台里面使用命令:

Scaffold-DbContext 'Data Source=192.168.40.165;Initial Catalog=DBFirst;User Id=sa;Password=123456;Encrypt=False' -Context SqlServerContext    -OutputDir DBFirst Microsoft.EntityFrameworkCore.SqlServer
  • -Context:指定DbContext 类名称,如果不指定则是数据库名称+Context。
  • -OutputDir:指定生成的模型目录。

除此之外该命令还有其它参数包括:

  • -ContextDir:指定DbContext类生成目录。
  • -ContextNamespace:覆盖 DbContext 类的命名空间。
  • -Namespace: 覆盖所有输出类的命名空间。

生成的DBContext和模型:

image

使用SqlServerContext 查询数据:

            var students = context.Students.Include(t=>t.Courses).ToList();
            students.ForEach(e =>
            {
                Console.Write(e.Name);
                e.Courses.ToList().ForEach(e => { Console.Write("   "+e.Name); });
                Console.WriteLine();
            });

            var student = context.Students.SingleOrDefault(t => t.Name=="李四");
            Console.WriteLine($"id:{student.Id},name:{student.Name}");

image

EFCore的其它重要知识点

本篇作为入门使用,其它EFCode的重要知识点还是建议使用时候查看官方文档即可。
我认为EFCore的重要知识点包括但不限于:

  • EFCore中的约定大于配置,比如模型如果有Id字段默认就是主键。
  • 一对多关系配置和获取,上面示例中学生有哪些课程就是一对多,查询关联要用includ。
  • 多个外键字段的配置。
  • 一对多,多对多关系。
  • Iqueryable的作用和数据延迟加载,在我们查询数据的时候列表数据以Iqueryable类型返回,如果我们不是调用获取结果api,则不会立即查询数据库,而是等到使用tolist()、count()、max()等方法时才会查询数据返回结果。这样有利于我们在代码中复用Iqueryable,比如不同的if条件拼接后组成不同的查询语句。
  • EFcore 缓存。
  • AsNoTracking使用,EFCore默认会跟踪实体,也就是保存在内存中,用于更新删除等操作。如果只是查询数据而不用跟踪则使用AsNoTracking查询。
  • 并发控制。
    …..

.net 温故知新:【9】.NET日志记录 ILogger使用和原理 - XSpringSun - 博客园

mikel阅读(299)

来源: .net 温故知新:【9】.NET日志记录 ILogger使用和原理 – XSpringSun – 博客园

日志

日志作为我们程序记录的“黑匣子”不论什么系统都应该使用到的,比如我们经常使用的log4net就是第三方日志记录提供程序。.NET 支持使用各种内置和第三方日志记录提供程序的日志记录 API,这篇文章主要介绍的是内置提供程序和API的使用并看下他们是如何实现的。

日志优先级

如果你使用过log4net的话那么你对这个优先级应该不陌生,在日志记录过程中我们可以对记录的日志信息进行优先级划分,根据优先级我们可以配置只记录哪些优先级别的日志,同时日志信息也会标记这条信息的优先级。在我们查找问题的时候更好的筛选和定位。

.net 的日志优先级LogLevel 分为:

Trace = 0、Debug = 1、Information = 2、Warning = 3、Error = 4、Critical = 5 和 None = 6。

日志级别:

Trace<Debug<Information<Warning<Error<Critical<None

使用控制台输出日志

现在我们来感受下如何用内置提供程序记录日志,使用的是.NET 6 控制台程序进行示例。

  • 添加Install-Package Microsoft.Extensions.Logging 日志基础包
    Install-Package Microsoft.Extensions.Logging
  • 添加Microsoft.Extensions.Logging.Console 控制台输出日志提供程序包
    Install-Package Microsoft.Extensions.Logging.Console
  • DI注入
ServiceCollection services = new ServiceCollection();
//添加日志到容器
services.AddLogging(loggingBuilder =>
    {
        loggingBuilder.AddConsole();
    }
    //回调,或者是叫委托方法,调用AddConsole()添加控制台输入提供程序Provider。
    //AddConsole方法就是在Microsoft.Extensions.Logging.Console包中LoggingBuilder的扩展方法。
);

可以通过容器Provider直接获取日志对象然后调用写日志方法。

image

当然更常用的是在其它类中通过构造函数注入,使用 DI 中的 ILogger 对象(TCategoryName 类别字符串是任意的,但约定将使用类名称,在日志中能知道是哪个类输出的)。

    public class TestLog
    {
        private readonly ILogger _logger;
        public TestLog(ILogger<TestLog> logger)
        {
            _logger = logger;
        }

        public void Test()
        {
            _logger.LogDebug("测试");
        }
    }

上面的控制台打印我们注意到没有输出“调试日志”,“信息日志”,这是因为未设置默认日志级别,则默认的日志级别值为 Information。所以输出程序只输出>=Information的日志。

通过代码设置:
image

日志设计的原理

如下我大概画了一个逻辑图,对于如何实现日志进行了一个梳理,代码部分未.net源码截取。

image

通过如上的流程我们知道其实日志对象是由LoggerFactory类创建的,所以我们不使用注入的方式也可以直接获取日志对象并写日志。

            var loggerFactory = LoggerFactory.Create(builder =>
            {
                builder.AddConsole();
                builder.SetMinimumLevel(LogLevel.Debug);
            });

            ILogger loger = loggerFactory.CreateLogger<Program>();

image

提供程序

目前内置的日志记录提供程序:

  • Console:Console 提供程序将输出记录到控制台。
  • Debug:Debug 提供程序使用 System.Diagnostics.Debug 类写入日志输出。
  • EventSource:EventSource 提供程序写入名称为 Microsoft-Extensions-Logging 的跨平台事件源。
  • EventLog:EventLog 提供程序将日志输出发送到 Windows 事件日志。
    比如我们在测试里面添加一个EventLog将日志写入Windows 事件日志:
    安装提供程序包:Install-Package Microsoft.Extensions.Logging.EventLog
    image

内置程序未提供对日志记录到文件,所以我们可以使用一些三方包,当然也可以自己开发。

image
查看地址:三方包

在上一篇 .NET 配置 中我们也见到提供程序这个概念,或者是说这种设计结构,其实.net中很多地方都用到提供程序的思维,然可以灵活扩展。

.net 温故知新:【8】.NET 中的配置从xml转向json - XSpringSun - 博客园

mikel阅读(316)

来源: .net 温故知新:【8】.NET 中的配置从xml转向json – XSpringSun – 博客园

一、配置概述

在.net framework平台中我们常见的也是最熟悉的就是.config文件作为配置,控制台桌面程序是App.config,Web就是web.config,里面的配置格式为xml格式。
image

在xml里面有系统生成的配置项,也有我们自己添加的一些配置,最常用的就是appSettings节点,用来配置数据库连接和参数。
使用的话就引用包System.Configuration.ConfigurationManager 之后取里面的配置信息:System.Configuration.ConfigurationManager.AppSettings["ConnectionString"]

随着技术的发展这种配置方式显得冗余复杂,如果配置项太多层级关系参数表达凌乱,在.net core开始也将配置的格式默认成了json格式,包括现在很多的其它配置也是支持的,比如java中常用的yaml格式,为什么能支持这么多读取源和格式,其实质在于配置提供程序
目前.NET 中的配置是使用一个或多个配置提供程序执行的。 配置提供程序使用各种配置源从键值对读取配置数据,这些配置程序稍后我们会看到,读取的配置源可以是如下这些:

  • 设置文件,appsettings.json
  • 环境变量
  • Azure Key Vault
  • Azure 应用配置
  • 命令行参数
  • 已安装或已创建的自定义提供程序
  • 目录文件
  • 内存中的 .NET 对象
  • 第三方提供程序

二、配置初识

IConfiguration 接口是所有配置源的单个表示形式,给定一个或多个配置源,IConfiguration 类型提供配置数据的统一视图。
image

上图我们可能没有直观的感受,现在写一个例子来看看

(1). 新建控制台应用程序:
创建控制台使用的是.net 6.0 框架,vs 2022。
安装 Microsoft.Extensions.Configuration.Json NuGet 包,该包提供json配置文件读取。

Install-Package Microsoft.Extensions.Configuration.Json

image

(2). 添加appsettings.json 文件

{
  "person": {
    "name": "XSpringSun",
    "age": 18
  }
}

(3). 使用json提供程序读取json配置
new一个ConfigurationBuilder,添加json配置,AddJsonFile是在包中的IConfigurationBuilder扩展方法,其它配置提供程序也是用这种扩展方法实现。

        static void Main(string[] args)
        {

            IConfiguration configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .Build();

            Console.WriteLine(configuration["person:name"]);
            Console.WriteLine(configuration["person:age"]);

            Console.WriteLine("Hello, World!");
            Console.ReadLine();
        }

image

可以看到已经取到json配置文件中的值了,配置值可以包含分层数据。 分层对象使用配置键中的 : 分隔符表示。在下面的调试对象中我们可以看到实际configuration的Providers 提供程序数组有一个值,就是我们的JsonConfigurationProvider,并且JsonConfigurationProvider里面已经读取了json的数据存储在Data数组中。

对于如上几行代码干了什么呢:

  • 将 ConfigurationBuilder 实例化(new ConfigurationBuilder)。
  • 添加 “appsettings.json” 文件,由 JSON 配置提供程序识别(AddJsonFile(“appsettings.json”))。
  • 使用 configuration 实例获取所需的配置

三、选项模式

这样已经实现json进行配置读取,但是取值的方式似乎和以前没什么太大变法,所以.net提供了选项模式,选项模式就是使用类来提供对相关设置组的强类型访问。
我们创建一个Config类用来转换json:

namespace ConfigDemo
{
    public class Config
    {
        public Person? person { get; set; }
    }

    public class Person {
        public string? name { get; set; }
        public int age { get; set; }
    }
}

绑定配置

IConfiguration configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .Build();


            Config options = new Config();
            ConfigurationBinder.Bind(configuration, options);

            Person person = configuration.GetSection("person").Get<Person>();

            Console.WriteLine(options.person.name);
            Console.WriteLine(options.person.age);

            Console.WriteLine("-----------GetSection获取-------------");
            Console.WriteLine(person.name);
            Console.WriteLine(person.age);

image

用了两种方式获取配置,第一种使用ConfigurationBinder.Bind()将整个配置绑定到对象Config上,另外一种是使用IConfiguration的GetSection().Get<T>()并返回指定的类型。两种方式都可以使用,看实际需求和用途。

四、选项依赖注入

在控制台程序中我们引用DI注入包,然后演示下如何进行配置的注入。关于DI和IOC不清楚的看我上篇文章.net 温故知新:【7】IOC控制反转,DI依赖注入

  • 新建一个测试类TestOptionDI
    public class TestOptionDI
    {
        private readonly IOptionsSnapshot<Config> _options;
        public TestOptionDI(IOptionsSnapshot<Config> options)
        {
            _options = options;
        }

        public void Test()
        {
            Console.WriteLine("DI测试输出:");
            Console.WriteLine($"姓名:{_options.Value.person.name}");
            Console.WriteLine($"年龄:{_options.Value.person.age}");
        }
    }

在测试类中我们使用IOptionsSnapshot<T>接口作为依赖注入,还有其它不同定义的接口用来配置注入,关于选项接口:。

image

不同接口可以配合读取配置的不同方式起作用,IOptionsSnapshot接口可以在配置文件改变后不同作用域进行刷新配置。接着我们修改main方法,引入DI,并将AddJsonFile方法的参数reloadOnChange设置为true,optional参数是否验证文件存在,建议开发时都设置为true,这样如果文件有问题会进行报错。
注入配置这句services.AddOptions().Configure<Config>(e=>configuration.Bind(e))是关键,通过容器调用AddOptions方法注册,然后Configure方法里面是一个委托方法,该委托的作用就是将配置的信息绑定到Config类型的参数e上。注册到容器的泛型选项接口,这样在TestOptionDI类构造函数注入就能注入IOptionsSnapshot了,这里有点绕。

        static void Main(string[] args)
        {

            IConfiguration configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json",optional:true,reloadOnChange:true)
                .Build();
            
            //IServiceCollection 服务
            ServiceCollection services = new ServiceCollection();
            //注入配置
            services.AddOptions().Configure<Config>(e=>configuration.Bind(e));
            //注入TestOptionDI
            services.AddScoped<TestOptionDI>();

            using (var provider = services.BuildServiceProvider())
            {
                //获取服务
                var testOption = provider.GetRequiredService<TestOptionDI>();
                testOption.Test();
            }
            Console.ReadLine();
        }

image

为了测试IOptionsSnapshot接口在不同作用域会刷新配置,我们修改下main方法,用一个while循环在ReadLine时修改json文件值,不同的Scope里进行打印。

            using (var provider = services.BuildServiceProvider())
            {
                while (true)
                {
                    using (var scope = provider.CreateScope())
                    {
                        //获取服务
                        var testOption = scope.ServiceProvider.GetRequiredService<TestOptionDI>();
                        testOption.Test();
                    }
                    Console.ReadLine();
                }
            }

image

这个功能在web中使用很方便,因为框架的一次请求就是一个作用域,所以我们修改了配置,下次请求就能生效了,而不用重启服务。

五、其它配置

如最开始所说,不仅能配置json文件,由于各种提供程序,还可以配置其它的,但是根据配置的顺序会进行覆盖。我们只添加一个环境变量配置演示下:
首先添加提供程序包:Install-Package Microsoft.Extensions.Configuration.EnvironmentVariables
然后添加环境变量配置代码AddEnvironmentVariables()

IConfiguration configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json",optional:true,reloadOnChange:true)
                .AddEnvironmentVariables()
                .Build();

在VS中配置临时环境变量
image

这里有个扁平化配置,就是表示层级用冒号person:age
image

六、托管模式

对于web项目我们没有进行这么多操作它是怎么配置的呢,其实框架已经自动帮我们做了,其它非web项目也可以使用这种托管模式,在Microsoft.Extensions.Hosting 包中,只需要使用简单的代码就能配置好。

IHost host = Host.CreateDefaultBuilder(args).Build();
await host.RunAsync();

其加载配置的优先级:
image
通过分析我们对整个配置如何运行的机制有了一个大体的了解,如果想详细了解托管模式的还是建议看官方文档:.NET配置

.net 温故知新:【7】IOC控制反转,DI依赖注入 - XSpringSun - 博客园

mikel阅读(314)

来源: .net 温故知新:【7】IOC控制反转,DI依赖注入 – XSpringSun – 博客园

IOC控制反转

大部分应用程序都是这样编写的:编译时依赖关系顺着运行时执行的方向流动,从而生成一个直接依赖项关系图。 也就是说,如果类 A 调用类 B 的方法,类 B 调用 C 类的方法,则在编译时,类 A 将取决于类 B,而 B 类又取决于类 C

image

应用程序中的依赖关系方向应该是抽象的方向,而不是实现详细信息的方向。
而这就是控制反转的思想。
应用依赖关系反转原则后,A 可以调用 B 实现的抽象上的方法,让 A 可以在运行时调用 B,而 B 又在编译时依赖于 A 控制的接口(因此,典型的编译时依赖项发生反转)。 运行时,程序执行的流程保持不变,但接口引入意味着可以轻松插入这些接口的不同实现。

image

上下不同的实现方式在于之前的依赖关系是A->B->C,控制反转后A->B接口->C接口,然后具体的B,C实现又是B->B接口 的反转依赖。这样的好处就是A只依赖B接口而不是依赖实现,具体我们要实现什么只需要按照业务需求进行编写,并且可以随时替换实现而不会影响A的实现,这种思想就是控制反转。

如下是顺序依赖:

        public class A
        {
            //依赖具体类
            public B b;
            public C c;
            public A(B _b, C _c) {
                b = _b;
                c = _c;
            }
            public void Listen()
            {
                b.SayHi();
                c.SayBye();
            }
        }

        public class B
        {
            public void SayHi()
            {
                Console.WriteLine("hi...");
            }
        }
        public class C
        {
            public void SayBye()
            {
                Console.WriteLine("bye...");
            }
        }

如下是控制反转:

        public class A
        {
            //依赖接口
            public IB b;
            public IC c;
            public A(IB _b, IC _c)
            {
                b = _b;
                c = _c;
            }
            public void Listen()
            {
                b.SayHi();
                c.SayBye();
            }
        }

        public interface IB
        {
            public void SayHi();
        }
        public interface IC
        {
            public void SayBye();
        }

DI依赖注入

.NET 支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖项之间实现控制反转 (IoC) 的技术。
我们首先用代码来看什么是DI,在.net提供的扩展包Microsoft.Extensions.DependencyInjection中来完成DI,nuget安装。

image

然后我们实现接口B和接口C,实现我们可以说英语,也可以说汉语,我们在SayHi和SayBye中输出汉语。

        public class B : IB
        {
            public void SayHi()
            {
                Console.WriteLine("你好...");
            }
        }

        public class C : IC
        {
            public void SayBye()
            {
                Console.WriteLine("再见...");
            }
        }

然后在服务容器中注册依赖关系。 .NET 提供了一个内置的服务容器 IServiceProvider。 服务通常在应用启动时注册,并追加到 IServiceCollection。 添加所有服务后,可以使用 BuildServiceProvider 创建服务容器,然后在容器中直接“要”对象而不用去管它如何实例化,并且DI具备传染性,假如B引用了D接口ID,那么我们注册B并在获取B实例时,引用的D接口也会被实例化。

            //IServiceCollection 服务
            IServiceCollection services = new ServiceCollection();
            //服务注册
            services.AddTransient<A>();
            services.AddTransient<IB, B>();
            services.AddTransient<IC, C>();
            //创建服务容器
            var serviceProvider = services.BuildServiceProvider();
            //获取服务
            var a = serviceProvider.GetRequiredService<A>();
            //使用
            a.Listen();
            Console.ReadKey();

image

这就是通过DI依赖注入的方式来实现IOC的思想,或许你会好奇为什么我们不直接实例化A,然后在构造方法里面传进去就行了,也就不依赖DI实现了。但是程序结构更复杂些呢,比如上面提到的B又有D,D又有F呢,这样在构造的时候不是一直要new很多对象,而且同一个接口的不同实现还要去找实例化处的代码进行修改。例如SayHI我想说英文呢?那么我们就可以实现一个BB,然后在服务注册的地方注册BB就可以了。

        public class BB : IB
        {
            public void SayHi()
            {
                Console.WriteLine("hello...");
            }
        }

替换注册BB services.AddTransient<IB, BB>(),而不用去改任何逻辑。
image

服务生命周期

在注册服务的时候我使用的AddTransient方法,表示注册的服务是瞬态的,也就是每次请求都是重新创建实例。同时还提供其它注册服务的方法。
image
服务有三种声明周期:

  • 瞬态
  • 作用域
  • 单例
  1. 瞬态
    服务是每次从服务容器进行请求时创建的。 这种生存期适合轻量级、 无状态的服务。 用 AddTransient 注册服务。在处理请求的应用中,在请求结束时会释放暂时服务。
  2. 作用域
    指定了作用域的生存期指明了每个客户端请求(连接)创建一次服务。 向 AddScoped 注册范围内服务。在处理请求的应用中,在请求结束时会释放有作用域的服务。
    ASP.NET 在处理一个请求的时候是一个作用域,同样我们自己也可以定义作用域。使用serviceProvider.CreateScope()创建作用域,在作用域释放后对象将被释放。
    image
    我们使用AddScoped添加对象,然后在作用域中取两个A对象进行比较,可以看到是True
    如果我们用AddTransient注册A,即使在作用域内两个对象比较也是不一样的,结果为False
    image
  3. 单例
    单例大家应该好理解,就是设计模式中的单例,使用AddSingleton 注册,在首次请求它们时进行创建;或者在向容器直接提供实现实例时由开发人员进行创建。 很少用到此方法,因为可能是线程不安全的,如果服务中有状态。

其它

在Microsoft.Extensions.DependencyInjection中只能用构造函数注入,其它框架还提供属性注入,比如autofac。至于原因不得而知,当然也看个人喜好。查了些资料说是构造函数注入更科学,在对象创建的瞬间对象的构造方法将服务实例化,避免逻辑问题。

.net 温故知新:【6】Linq是什么 - XSpringSun - 博客园

mikel阅读(294)

来源: .net 温故知新:【6】Linq是什么 – XSpringSun – 博客园

1、什么是Linq

关于什么是Linq 我们先看看这段代码。

            List<int> list = new List<int> { 1, 1, 2, 2, 3, 3, 3, 5, 7, 8, 10, 12 };
            var linqList = list.Where(t => t < 10)              //列表中值小于10
                           .GroupBy(t => t)                     //分组
                           .Where(t => t.Count() > 1)           //分组后出现次数大于1
                           .OrderByDescending(t => t.Count())   //按照出现次数倒序
                           .Select(t => t.Key);                 //选择值
            Console.WriteLine(string.Join(' ',linqList));


这段代码使用Linq对List列表进行筛选、分组、排序等一系列操作展示了Linq的强大和便捷,那么我们为什么需要学习Linq?可以看到这样一堆逻辑只几行Linq很快就可以实现,如果要我们自己实现方法去处理这个List肯定是比较繁琐的。
Linq是什么?如下是官方文档对于Linq的描述:

语言集成查询 (LINQ) 是一系列直接将查询功能集成到 C# 语言的技术统称。 数据查询历来都表示为简单的字符串,没有编译时类型检查或 IntelliSense 支持。 此外,需要针对每种类型的数据源了解不同的查询语言:SQL 数据库、XML 文档、各种 Web 服务等。 借助 LINQ,查询成为了最高级的语言构造,就像类、方法和事件一样。
对于编写查询的开发者来说,LINQ 最明显的“语言集成”部分就是查询表达式。 查询表达式采用声明性查询语法编写而成。 使用查询语法,可以用最少的代码对数据源执行筛选、排序和分组操作。 可使用相同的基本查询表达式模式来查询和转换 SQL 数据库、ADO .NET 数据集、XML 文档和流以及 .NET 集合中的数据。

Linq的使用频率和范围可以说是很高很广的,基本每天应该都会用到,那么Linq到底是什么呢?怎么实现的?
要学习Linq首先需要先了解委托Lambda 表达式,因为Linq是由 委托->Lambda->Linq 的一个变换过程。

2、委托

委托简单来讲就是指向方法的指针,就像变量是用来指向具体实现。例如String对象,我们定义一个对象string str="变量"那么str就是指向具体实例化对象的地址,String就是类型。
按照这个思路,如果我们要定义一个指向方法的变量,委托就是为了实现该目的。委托使用 delegate 关键字来声明委托类型。
用类似于定义方法签名的语法来定义委托类型。 只需向定义添加 delegate 关键字即可,如下我们定义一个比较两个数字的委托类型。

//比较两个数字
public delegate int Comparison(int i, int n);

接着我们定义委托变量comparison并指向方法ComparisonMax方法,该方法比较两个int大小,返回大的一个。

委托是和类平级的应以,理应放类同级别,但是C#支持类嵌套定义,所以我们把和本类关联性强的委托可以嵌套定义,委托变量comparison指向方法后,调用comparison(1, 2)执行委托方法并打印。
当然委托可以有返回值也可以定义void无返回值,关于委托的其它方面这里不再赘述,这里主要是为了看清Linq所以浅显的梳理下。
每次使用委托的时候我们都要定义比较麻烦,所以框架已经为我们定义好了两个类型,ActionFunc一个无返回值,一个有返回值,并且采用泛型定义了多个委托以满足我们日常使用。

有了这两个系列的委托类型,上面的方式我们也可以不定义委托直接使用Func<int,int,int> comparison = ComparisonMax;来实现。

3、Lambda

在看Lamda之前我们再看下委托方法的另外一种编写方式,匿名方法

delegate 运算符创建一个可以转换为委托类型的匿名方法
如下我们直接在委托变量后面使用delegate 将参数方法体直接写,而不用声明其名称的方式。


Func<int,int,int> comparison = delegate(int i,int n) { return i > n ? i : n; };
         

运行打印下结果:

从 C# 3 开始,lambda 表达式提供了一种更简洁和富有表现力的方式来创建匿名函数。 使用 => 运算符构造 Lambda
在 lambda 表达式中,lambda 运算符 将左侧的输入参数与右侧的 lambda 主体分开。

使用 Lambda 表达式来创建匿名函数。 使用 lambda 声明运算符=>(读作 goes to) 从其主体中分离 lambda 参数列表。 Lambda 表达式可采用以下任意一种形式:

其中第一种后面写表达式,第二种是使用大括号{}的代码块作为主体,语句 lambda 与表达式 lambda 类似,只是语句括在大括号中。
其实 表达式lambda 就是 语句lambda 在只有一行的情况下可以省略大括号和return。表达式 lambda 的主体可以包含方法调用。 不过若在表达式树中,则不得在 Lambda 表达式中使用方法调用。表达式树是另外一个东西,我们现在使用的ORM框架就是将lambda转换为SQL,这个过程使用表达式树技术,比如EF查询中,如果我们写一个Console.WriteLine()表达式树是没办法转换的,想一下这个调用对于sql查询来说是没有意义的,表达式树以后再讨论吧。

因此上面的匿名函数可以通过lambda变换为:


Func<int,int,int> comparison = (int i,int n) =>{ return i > n ? i : n; };
         

Lambda表达式参数类型也可以省略,输入参数类型必须全部为显式或全部为隐式;否则,便会生成 CS0748 编译器错误。
所以表达式还可以变换为:


Func<int,int,int> comparison = (i,n) =>{ return i > n ? i : n; };
         

将 lambda 表达式的输入参数括在括号中。 如果没有参数则直接写():Action ac = () => {Console.WriteLine();}或者Action ac = () => Console.WriteLine()
如果 lambda 表达式只有一个输入参数,则括号是可选:Func<int,int> fun = i => {return i++;}或者Func<int,int> fun = i =>i++
关于更多的lambda知识可以参看文档:Lambda 表达式

4、实现一个Linq

有了委托和Lambda 的知识,我们可以自己写一个简易的Linq实现,写一个where吧。
我们需要扩展List类的方法,当然不用扩展方法也是可以实现,直接写方法然后调用,但是为了还原框架实现方式,我们模仿IEnumerable类(List 继承至IEnumerable)。

关于扩展方法:

扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种静态方法,但可以像扩展类型上的实例方法一样进行调用。
扩展方法被定义为静态方法,但它们是通过实例方法语法进行调用的。 它们的第一个参数指定方法操作的类型。 参数前面是 this 修饰符。 仅当你使用 using 指令将命名空间显式导入到源代码中之后,扩展方法才位于范围中。

  • 定义扩展方法
    public static class MyLinq
    {
        public static List<T> MyLinqWhere<T>(this List<T> list, Func<T, bool> predicate)
        {
            List<T> tempList = new List<T>();
            foreach (var item in list)
            {
                if (predicate(item))
                {
                    tempList.Add(item);
                }
            }
            return tempList;
        }
    }

List类是泛型,所以我们定义泛型MyLinqWhere 方法,第一个参数使用this关键字修饰,然后predicate为一个输入参数是T返回时bool的委托用来进行对List里面的每一个元素进行筛选,返回的bool结果判断是否符合要求。
我们将符合要求的元素放到一个新的List里面最后返回该List。

  • 使用Linq方式调用自定义的where方法
     List<int> list = new List<int> { 1, 1, 2, 2, 3, 3, 3, 5, 7, 8, 10, 12 };
      var listWhere = list.MyLinqWhere(x => x < 7);
      Console.WriteLine(string.Join(' ', listWhere));

这样就实现了一个简单的Linq,虽然实际的IEnumerable扩展方法里面还有其它操作,但是通过这个过程我们知道了Linq的实现。
在IEnumerable扩展方法返回参数仍然是IEnumerable,所以可以像开始我们写的那样进行链式调用

5 Linq的另外一种写法

在刚开始的例子中我们换另外一种写法:

var linqList2 = from t in list
                   where t < 10
                   group t by t into t
                   where t.Count() > 1
                   orderby t.Count() descending
                   select t.Key;

输出的结果和方法调用,使用Lambda出来的结果是一样的。

这种方式称为语言集成查询,查询表达式采用声明性查询语法编写而成。 使用查询语法,可以用最少的代码对数据源执行筛选、排序和分组操作。 可使用相同的基本查询表达式模式来查询和转换 SQL 数据库、ADO .NET 数据集、XML 文档和流以及 .NET 集合中的数据。
这种写法只是一种语法方式,或者说语法糖,在编译阶段生成的代码和Lambda表达式生成的代码是一致的,虽然这种方法看起来比较炫酷,但是目前大家还是比较习惯Lambda的书写方式和阅读,了解就行了,要详细学习可以参看官方文档。