C# 自定义配置文件序列化生成+文件格式错误自动回档

文章目录

  • 前言
  • 选择Xml
  • 简单的Xml使用
    • 测试用例
    • 简单的写
    • 简单的读
    • 简单的生成配置
      • 修改配置类
      • 测试用例
      • 运行结果对比
  • 代码逻辑封装
    • 逻辑示意
    • 封装好的代码
    • 测试生成
    • 配置文件格式错误测试
      • 使用默认值覆盖来解决问题
  • 配置文件人为修改错误如何解决
    • 解决方案
    • 代码
    • 测试用例
    • 运行结果
  • 代码封装总结
  • 总结

前言

一般我们代码生成了之后,就不会动了。而可动的参数一般写在配置文件里面。配置语言的格式一般有一下几种

优点缺点
xml扩展性强,歧义性小对于人来说过于冗长
Json可读性强无法添加注释
yaml可读取强缩进地狱,手动修改时极其容易出现问题

选择Xml

首先Xml的文件不是我们自己生成的,而是机器自己主动生成的。因为我们一般的使用逻辑是

程序生成默认配置文件
人为修改文件
程序读取修改后的结果

对于我们配置人员来说,修改的部分是比较少的,而且由于其极强的拓展性,可以添加许多的注释。所以我打算使用Xml来生成对应的配置文档。而Json由于其修改时容易出错和扩展性的问题,我暂时就不用了。

简单的Xml使用

微软其实已经帮我们封装好了Xml的操控类。这里直接用序列化对象就行了

微软 XML 序列化示例。

测试用例

  public class MyConfigService
  {

      public string Name { get; set; }

      public string Description { get; set; }

      public int Id { get; set; }

      public bool IsEnabled { get; set; }

      public enum MyKeys { Apple,Banana,Pear}

      public MyKeys SettingKey { get; set; }

      public MyConfigService() { }
  }

简单的写

            MyConfigService myConfigService = new MyConfigService() {
                Name = "坤坤",
                Description = "偶像练习生",
                Id = 114514,
                IsEnabled = false,
                SettingKey = MyConfigService.MyKeys.Pear
            };

            var xmlHelper = new XmlSerializer(typeof(MyConfigService));
            StreamWriter xmlWriter = new StreamWriter("MyConfig.xml");
            xmlHelper.Serialize(xmlWriter, myConfigService);
            xmlWriter.Close();

在这里插入图片描述

简单的读

     static void Main(string[] args)
     {
         MyConfigService myConfigService = new MyConfigService()
         {
             Name = "坤坤",
             Description = "偶像练习生",
             Id = 114514,
             IsEnabled = false,
             SettingKey = MyConfigService.MyKeys.Pear
         };

         var xmlHelper = new XmlSerializer(typeof(MyConfigService));
         //StreamWriter xmlWriter = new StreamWriter("MyConfig.xml");
         //xmlHelper.Serialize(xmlWriter, myConfigService);
         //xmlWriter.Close();

         StreamReader xmlReader = new StreamReader("MyConfig.xml");
         var res = xmlHelper.Deserialize(xmlReader);
         Console.WriteLine(JsonConvert.SerializeObject(res));

         Console.WriteLine("运行完成!");
         Console.ReadKey();
     }

在这里插入图片描述

在这里插入图片描述

简单的生成配置

C# XML序列化/反序列化参考

修改配置类

    /// <summary>
    /// 重命名根节点
    /// </summary>
    [XmlRoot("RootTest")]
    public class MyConfigService
    {
        /// <summary>
        /// 重命名,从Name变成extra
        /// </summary>
        [XmlElement("extra")]
        public string Name { get; set; }

        public string Description { get; set; }

        public int Id { get; set; }
        public bool IsEnabled { get; set; }

        public enum MyKeys { Apple,Banana,Pear}

        public MyKeys SettingKey { get; set; }

        /// <summary>
        /// 以Default属性的形式加载到根节点上面
        /// </summary>
        [XmlAttribute()]
        public string Default = "描述";

        public MyConfigService() { }
    }

测试用例

  static void Main(string[] args)
  {
      MyConfigService myConfigService = new MyConfigService()
      {
          Name = "坤坤",
          Description = "偶像练习生",
          Id = 114514,
          IsEnabled = false,
          SettingKey = MyConfigService.MyKeys.Pear
      };

      var xmlHelper = new XmlSerializer(typeof(MyConfigService));
      StreamWriter xmlWriter = new StreamWriter("MyConfig.xml");
      //去掉烦人的命名空间
      XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
      ns.Add("", "");
      xmlHelper.Serialize(xmlWriter, myConfigService,ns);
      xmlWriter.Close();

      //StreamReader xmlReader = new StreamReader("MyConfig.xml");
      //var res = xmlHelper.Deserialize(xmlReader);
      //Console.WriteLine(JsonConvert.SerializeObject(res));

      Console.WriteLine("运行完成!");
      Console.ReadKey();
  }

运行结果对比

在这里插入图片描述

代码逻辑封装

逻辑示意

xml序列化Helper
默认生成
默认读取
读取解析出错覆盖
文件路径是否存在确认

封装好的代码

   public class MyXmlConfigHelper<T>
   {
       public T Setting { get; set; }

       public string FileName { get; set; } = "MyConfig.xml";

       public string DirectoryPath
       {
           get
           {
               var regex = new Regex(@"\\(\w+)\.(\w+)$");
               return regex.Split(FullPath)[0];
           }
       }
       public string DebugPath { get => Directory.GetCurrentDirectory(); }

       public string FullPath { get => DebugPath + "\\" + FileName; }

       public bool IsFileExist { get => File.Exists(FullPath); }

       public bool IsDirectoryExist { get => Directory.Exists(DirectoryPath); }

       public Action<string> ShowMsg { get; set; } = (msg)=>Console.WriteLine(msg);

       public MyXmlConfigHelper()
       {

       }
       public MyXmlConfigHelper(string filename)
       {
           FileName = filename;
           if (!IsDirectoryExist)
           {
               DirectoryInfo directoryInfo = new DirectoryInfo(DirectoryPath);
               directoryInfo.Create();
           }
       }

       public MyXmlConfigHelper(T setting ,string filename):this(filename)
       {
           Setting = setting;
       }

       /// <summary>
       /// 创建文件
       /// </summary>
       public void Init()
       {
           if(IsFileExist)
           {
               try
               {
                   Read();
               }
               catch (Exception ex)
               {
                   ShowMsg(ex.ToString());
                   throw new Exception("文件读取失败!请确认是否配置文件格式是否正确");
               }
           }
           else
           {
               Write();
           }
       }

       /// <summary>
       /// 覆盖文件
       /// </summary>
       public void ReInit()
       {
           ShowMsg("正在覆盖配置文件:" + FullPath);
           Write();
       }
       /// <summary>
       /// 写入配置类
       /// </summary>
       private void Write()
       {
           ShowMsg("正在生成配置文件:" + FullPath);
           var xmlHelper = new XmlSerializer(typeof(T));
           using (StreamWriter xmlWriter = new StreamWriter(FullPath))
           {
               //去掉烦人的命名空间
               XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
               ns.Add("", "");
               xmlHelper.Serialize(xmlWriter, Setting, ns);
               xmlWriter.Close();
           }
       }

       /// <summary>
       /// 读取配置类
       /// </summary>
       private void Read()
       {
           ShowMsg("正在读取配置文件:"+FullPath);
           var xmlHelper = new XmlSerializer(typeof(T));
           using (StreamReader xmlReader = new StreamReader(FullPath))
           {
               
               Setting = (T)xmlHelper.Deserialize(xmlReader);
               xmlReader.Close();
           }
           
       }
   }

测试生成

    static void Main(string[] args)
    {
        var config = new MyConfigService() {
            Name = "小坤",
            Description="爱坤",
            Default = "鲲鲲",
            SettingKey = MyConfigService.MyKeys.Banana,
            Id = 80086,
            IsEnabled = true,
        };

        var xmlHelper = new MyXmlConfigHelper<MyConfigService>(config, "Config\\MyConfig.xml");
 
        xmlHelper.Init();

        Console.WriteLine("运行完成!");
        Console.ReadKey();
    }

在这里插入图片描述
我还做了判断,如果不存在,则生成默认,如果存在,则读取的判断

配置文件格式错误测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用默认值覆盖来解决问题

    static void Main(string[] args)
    {
        var config = new MyConfigService() {
            Name = "小坤",
            Description="爱坤",
            Default = "鲲鲲",
            SettingKey = MyConfigService.MyKeys.Banana,
            Id = 80086,
            IsEnabled = true,
        };

        var xmlHelper = new MyXmlConfigHelper<MyConfigService>(config, "Config\\MyConfig.xml");
        try
        {
            xmlHelper.Init();

        }
        catch (Exception ex)
        {
            //如果出错,则使用默认值覆盖
            Console.WriteLine(ex.ToString());
            xmlHelper.ReInit();

        }

        Console.WriteLine("运行完成!");
        Console.ReadKey();
    }

在这里插入图片描述

配置文件人为修改错误如何解决

解决方案有以下几种

  • 不解决,使用默认值,一直到人为修改回去
  • 手动解决,但是现场人员不一定了解配置信息
  • 重新生成覆盖,但是这样会丢失以前配置的数据
  • 从缓存数据库中读取上传成功运行的代码,回复到最初的状态

理论上来说,第4个是最好的,因为我们现场人员就算修改出现问题了,也能回滚到程序之前的配置。但是C# 默认是没有缓存这个东西的。缓存是需要存在一个地方。我个人认为最好的存储中介就是Sqlite数据库。Sqlite本身体积小,性能强,不需要安装。对于1G以下,100万条以下的数据最好的存储中介。

挖个坑,后面研究一下基于Sqlite的缓存数据库

解决方案

生成两个配置文件,一个是主配置文件,一个是备份配置文件。

程序运行
读取主要+备份
主要+备份都完整
主要覆盖备份
主要破损,备份完整
备份还原主要
主要完整,备份破损
主要+备份都破损
主要备份默认值覆盖

代码

    public class MyXmlConfigAutoHelper<T>
    {
        public T Setting { get; set; }
        public string FileName { get; set; } = "MyConfig.xml";

        public string BackupName
        {
            get
            {
                var regex = new Regex(@"(\w+)\.(\w+)$");
                var filename = regex.Match(FileName).Value;
                var backName = (new Regex(@"\.(\w+)$")).Split(filename)[0];
                var newBackName = backName + "_back";

                return (new Regex(backName)).Replace(FileName, newBackName);
            }
        }

        /// <summary>
        /// 备份
        /// </summary>
        private MyXmlConfigHelper<T> backupXml { get; set; }

        private MyXmlConfigHelper<T> settingXml { get; set; }

        public MyXmlConfigAutoHelper()
        {

        }

        public MyXmlConfigAutoHelper(string fileName)
        {
            FileName = fileName;
        }
        public MyXmlConfigAutoHelper(string fileName, T setting)
        {
            Setting = setting;
            FileName = fileName;
        }

        /// <summary>
        /// 实例化
        /// </summary>
        public void AutoInit()
        {
            settingXml = new MyXmlConfigHelper<T>(Setting, FileName);
            backupXml = new MyXmlConfigHelper<T>(Setting, BackupName);
            //如果备份也损坏了,就GG了
            var settingFlag = true;
            var backupFlag = true;
            try
            {
                settingXml.Init();
            }
            catch (Exception ex)
            {
                Console.WriteLine("主文件读取失败");
                Console.WriteLine(ex.Message);
                settingFlag = false;
            }

            try
            {
                backupXml.Init();
            }
            catch (Exception ex)
            {
                Console.WriteLine("备份文件读取失败");
                Console.WriteLine(ex.Message);
                backupFlag = false;
            }
            Console.WriteLine($"文件完整性:setting[{settingFlag},backup[{backupFlag}]]");

            if (!backupFlag && !settingFlag)
            {
                Console.WriteLine("主要和备份文件完全破损,默认值覆盖");
                backupXml.ReInit();
                settingXml.ReInit();
            }
            else if (!backupFlag)
            {
                Console.WriteLine("备份文件完全破损,主要文件覆盖");
                backupXml.Setting = settingXml.Setting;
                backupXml.ReInit();
            }
            else if (!settingFlag)
            {
                Console.WriteLine("主要文件完全破损,备份文件覆盖");
                settingXml.Setting = backupXml.Setting;
                settingXml.ReInit();
            }
            else
            {
                Console.WriteLine("主要和备份文件正常,主要文件覆盖备份文件");
                backupXml.Setting = settingXml.Setting;
                backupXml.ReInit();
            }
            Setting = settingXml.Setting;
        }

        public void ReInit()
        {
            settingXml = new MyXmlConfigHelper<T>(Setting, FileName);
            backupXml = new MyXmlConfigHelper<T>(Setting, BackupName);
            settingXml.ReInit();
            backupXml.ReInit();
        }
    }

测试用例

        static void Main(string[] args)
        {
            var config = new MyConfigService() {
                Name = "小坤",
                Description="爱坤",
                Default = "鲲鲲",
                SettingKey = MyConfigService.MyKeys.Banana,
                Id = 80086,
                IsEnabled = true,
            };

            var xmlAutoHelper = new MyXmlConfigAutoHelper<MyConfigService>("resource\\Myconfig.xml", config);
            xmlAutoHelper.AutoInit();
            //Console.WriteLine(xmlAutoHelper.BackupName);

            Console.WriteLine("运行完成!");
            Console.ReadKey();
        }

运行结果

由于测试步骤过于复杂,情况比较多,这里就不放截图了。简单来说就是尽可能的使用已有的数据进行还原,如果两个文件都损坏直接使用默认值替换

代码封装总结

    public class MyXmlConfigAutoHelper<T>
  {
      public T Setting { get; set; }
      public string FileName { get; set; } = "MyConfig.xml";

      public string BackupName
      {
          get
          {
              var regex = new Regex(@"(\w+)\.(\w+)$");
              var filename = regex.Match(FileName).Value;
              var backName = (new Regex(@"\.(\w+)$")).Split(filename)[0];
              var newBackName = backName + "_back";

              return (new Regex(backName)).Replace(FileName, newBackName);
          }
      }

      /// <summary>
      /// 备份
      /// </summary>
      private MyXmlConfigHelper<T> backupXml { get; set; }

      private MyXmlConfigHelper<T> settingXml { get; set; }

      public MyXmlConfigAutoHelper()
      {

      }

      public MyXmlConfigAutoHelper(string fileName)
      {
          FileName = fileName;
      }
      public MyXmlConfigAutoHelper(string fileName, T setting)
      {
          Setting = setting;
          FileName = fileName;
      }

      /// <summary>
      /// 实例化
      /// </summary>
      public void AutoInit()
      {
          settingXml = new MyXmlConfigHelper<T>(Setting, FileName);
          backupXml = new MyXmlConfigHelper<T>(Setting, BackupName);
          //如果备份也损坏了,就GG了
          var settingFlag = true;
          var backupFlag = true;
          try
          {
              settingXml.Init();
          }
          catch (Exception ex)
          {
              Console.WriteLine("主文件读取失败");
              Console.WriteLine(ex.Message);
              settingFlag = false;
          }

          try
          {
              backupXml.Init();
          }
          catch (Exception ex)
          {
              Console.WriteLine("备份文件读取失败");
              Console.WriteLine(ex.Message);
              backupFlag = false;
          }
          Console.WriteLine($"文件完整性:setting[{settingFlag},backup[{backupFlag}]]");

          if (!backupFlag && !settingFlag)
          {
              Console.WriteLine("主要和备份文件完全破损,默认值覆盖");
              backupXml.ReInit();
              settingXml.ReInit();
          }
          else if (!backupFlag)
          {
              Console.WriteLine("备份文件完全破损,主要文件覆盖");
              backupXml.Setting = settingXml.Setting;
              backupXml.ReInit();
          }
          else if (!settingFlag)
          {
              Console.WriteLine("主要文件完全破损,备份文件覆盖");
              settingXml.Setting = backupXml.Setting;
              settingXml.ReInit();
          }
          else
          {
              Console.WriteLine("主要和备份文件正常,主要文件覆盖备份文件");
              backupXml.Setting = settingXml.Setting;
              backupXml.ReInit();
          }
          Setting = settingXml.Setting;
      }

      public void ReInit()
      {
          settingXml = new MyXmlConfigHelper<T>(Setting, FileName);
          backupXml = new MyXmlConfigHelper<T>(Setting, BackupName);
          settingXml.ReInit();
          backupXml.ReInit();
      }
  }

  public class MyXmlConfigHelper<T>
  {
      public T Setting { get; set; }

      public string FileName { get; set; } = "MyConfig.xml";

      public string DirectoryPath
      {
          get
          {
              var regex = new Regex(@"\\(\w+)\.(\w+)$");
              return regex.Split(FullPath)[0];
          }
      }
      public string DebugPath { get => Directory.GetCurrentDirectory(); }

      public string FullPath { get => DebugPath + "\\" + FileName; }

      public bool IsFileExist { get => File.Exists(FullPath); }

      public bool IsDirectoryExist { get => Directory.Exists(DirectoryPath); }

      public Action<string> ShowMsg { get; set; } = (msg) => Console.WriteLine(msg);

      public MyXmlConfigHelper()
      {

      }
      public MyXmlConfigHelper(string filename)
      {
          FileName = filename;
          if (!IsDirectoryExist)
          {
              DirectoryInfo directoryInfo = new DirectoryInfo(DirectoryPath);
              directoryInfo.Create();
          }
      }

      public MyXmlConfigHelper(T setting, string filename) : this(filename)
      {
          Setting = setting;
      }

      /// <summary>
      /// 创建文件
      /// </summary>
      public void Init()
      {
          if (IsFileExist)
          {
              try
              {
                  Read();
              }
              catch (Exception ex)
              {
                  ShowMsg(ex.ToString());
                  throw new Exception("文件读取失败!请确认是否配置文件格式是否正确");
              }
          }
          else
          {
              Write();
          }
      }

      /// <summary>
      /// 覆盖文件
      /// </summary>
      public void ReInit()
      {
          ShowMsg("正在覆盖配置文件:" + FullPath);
          Write();
      }
      /// <summary>
      /// 写入配置类
      /// </summary>
      public void Write()
      {
          ShowMsg("正在生成配置文件:" + FullPath);
          var xmlHelper = new XmlSerializer(typeof(T));
          using (StreamWriter xmlWriter = new StreamWriter(FullPath))
          {
              //去掉烦人的命名空间
              XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
              ns.Add("", "");
              xmlHelper.Serialize(xmlWriter, Setting, ns);
              xmlWriter.Close();
          }
      }

      /// <summary>
      /// 读取配置类
      /// </summary>
      public void Read()
      {
          ShowMsg("正在读取配置文件:" + FullPath);
          var xmlHelper = new XmlSerializer(typeof(T));
          using (StreamReader xmlReader = new StreamReader(FullPath))
          {

              Setting = (T)xmlHelper.Deserialize(xmlReader);
              xmlReader.Close();
          }
      }
  }

总结

我这里最后加了个back备份文件,我们平时就修改主要文件的配置即可。如果主要文件损坏,那就备份文件补上。但是这个是主要文件损坏的情况,如果主要文件没损坏,是参数设置错了呢?那我们可以自动生成按照时间戳的备份文件,一次存多个。

  • 主要文件
  • 备份文件-2024-1-6 17:37:20
  • 备份文件-2024-1-6 17:37:30

为了安全考虑的方式是没有上限的。这里就不展开说明了,这里已经写好一个基本的设置文件自动保存,和设置文件自动备份回档的功能,如果想要更高的安全基本可以自己在我的代码上面继续封装。

还有备份文件可以当做缓存文件一样来使用,但是这个是明文存储的。可以自己手动加密一下,反正加密和解密的方法也有很多。可以自己琢磨一下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/297673.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Swift爬虫使用代理IP采集唯品会商品详情

目录 一、准备工作 二、代理IP的选择与使用 三、使用Swift编写唯品会商品爬虫 四、数据解析与处理 五、注意事项与优化建议 六、总结 一、准备工作 在开始编写爬虫之前&#xff0c;需要准备一些工具和库&#xff0c;以确保数据抓取的顺利进行。以下是所需的工具和库&…

第14课 利用openCV快速数豆豆

除了检测运动&#xff0c;openCV还能做许多有趣且实用的事情。其实openCV和FFmpeg一样都是宝藏开源项目&#xff0c;貌似简单的几行代码功能实现背后其实是复杂的算法在支撑。有志于深入学习的同学可以在入门后进一步研究算法的实现&#xff0c;一定会受益匪浅。 这节课&#…

opencv003图像裁剪(应用NumPy矩阵的切片)

这一部分相对于马上要学习的二值化是要更更更简单一些的&#xff0c;只需三行&#xff0c;便能在opencv上裁剪图像啦&#xff08;顺便云吸猫&#xff0c;太可爱了&#xff01;&#xff09; 出处见水印&#xff01; 1、复习图像的显示 前几天期末考试&#xff0c;太久没有看…

docker安装nodejs,并更改为淘宝源

拉取官方 Node.js 镜像 docker pull node:latest创建 Dockerfile&#xff0c;并更改 NPM 下载源为淘宝源&#xff0c;设置为全局持久化 # 使用最新版本的Node.js作为基础镜像 FROM node:latest# 设置工作目录为/app WORKDIR /app # 更改 NPM 下载源为淘宝源&#xff0c;并设置…

限制选中指定个数CheckBox控件(1/2)

限制选中指定个数CheckBox控件&#xff08;1/2&#xff09; 实例需求&#xff1a;工作表中有8个CheckBox控件&#xff08;下文中简称为控件&#xff09;&#xff0c;现在需要实现限制用户最多只能勾选4个控件。 Dim OnDic As Object Sub CheckboxeEvent()Dim oCB As CheckBox…

test mutation-01-变异测试 PITest PIT 是一种先进的变异测试系统,为 Java 和 JVM 提供黄金标准的测试覆盖率。

拓展阅读 test 系统学习-04-test converate 测试覆盖率 jacoco 原理介绍 test 系统学习-05-test jacoco 测试覆盖率与 idea 插件 test 系统学习-06-test jacoco SonarQube Docker learn-29-docker 安装 sonarQube with mysql Ubuntu Sonar PITest 实际应用的变异测试 …

Linux的基本指令(5)

目录 bc指令 uname指令 压缩解压相关的指令 zip指令 unzip指令 tar打包压缩指令 tar解压解包指令 ​编辑​编辑sz&rz 热键 关机命令 安装&#xff1a;yum install -y 指令 bc指令 bc命令可以很方便的进行浮点运算 Linux中的计算器 uname指令 语法&#xff1a;un…

QtApplets-SystemInfo

QtApplets-SystemInfo ​ 今天是2024年1月3日09:18:44&#xff0c;这也是2024年的第一篇博客&#xff0c;今天我们主要两件事&#xff0c;第一件&#xff0c;获取系统CPU使用率&#xff0c;第二件&#xff0c;获取系统内存使用情况。 ​ 这里因为写博客的这个本本的环境配置不…

高性能NVMe Host Controller IP

NVMe Host Controller IP 介绍 NVMe Host Controller IP可以连接高速存储PCIe SSD&#xff0c;无需CPU和外部存储器&#xff0c;自动加速处理所有的NVMe协议命令&#xff0c;具备独立的数据写入AXI4-Stream/FIFO接口和数据读取AXI4-Stream/FIFO接口&#xff0c;非常适合于超高…

Python爬虫中的协程

协程 基本概念 协程&#xff1a;当程序执行的某一个任务遇到了IO操作时&#xff08;处于阻塞状态&#xff09;&#xff0c;不让CPU切换走&#xff08;就是不让CPU去执行其他程序&#xff09;&#xff0c;而是选择性的切换到其他任务上&#xff0c;让CPU执行新的任务&#xff…

C++ 虚函数virtual的引入和应用

来回顾一下使用引用或指针调用方法的过程。请看下面的代码: BrassPlus ophelia; // 子类对象 Brass * bp; // 基类指针 bp &ophelia; // 让基类指针指向子类对象 bp->ViewAcct(); // ViewAcct() 如果基类和子类都有这个函…

tp8/6 插件PhpOffice\PhpSpreadsheet导入表格

一、安装 composer require phpoffice/phpspreadsheet 官网&#xff1a;phpoffice/phpspreadsheet - Packagist 二、代码 <?php namespace app\services\upload\model; use app\services\BaseServices; use \PhpOffice\PhpSpreadsheet\Spreadsheet; use \PhpOffice\Php…

计算机Java项目|基于SpringBoot+Vue的图书个性化推荐系统

项目编号&#xff1a;L-BS-GX-10 一&#xff0c;环境介绍 语言环境&#xff1a;Java: jdk1.8 数据库&#xff1a;Mysql: mysql5.7 应用服务器&#xff1a;Tomcat: tomcat8.5.31 开发工具&#xff1a;IDEA或eclipse 二&#xff0c;项目简介 图片管理系统是一个为学生和…

Mac打包Unix可执行文件为pkg

Mac打包Unix可执行文件为pkg 方式一&#xff1a;通过packages页面打包 1.下载packages app Distribution&#xff1a;自定义化更高&#xff0c;包括修改安装页面的内容提示 我这里主要演示Distribution模式的项目&#xff1a;通过unix可执行文件postinstall.sh脚本实现通过ma…

浅谈冒泡排序

手写一个冒泡排序的代码。 1.数组 let arr [10, 2, 50, 23, 30, 56, 3]; 2.排序的思路 里层的循环: for (var i 0; i < arr.length; i) {if (arr[i] < arr[i 1]) {var temp arr[i];arr[i] arr[i 1];arr[i 1] temp;} 用途&#xff1a; [2, 10, 23, 30, 50, 3, …

腾讯云取消免费10G CDN流量包:免费CDN时代结束

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 免费送了7-8年的腾讯云10G免费流量包&#xff0c;从2024年开始&#xff0c;停止赠送了!自此&#xff0c;国内绝大多数互联网大厂的CDN都开收费了! 大概从2016年开始&#xff0c;腾讯云为了抢夺CDN客户&#xff0…

基于JavaWeb+SSM+Vue四六级词汇微信小程序系统的设计和实现

基于JavaWebSSMVue四六级词汇微信小程序系统的设计和实现 源码获取入口KaiTi 报告Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 KaiTi 报告 &#xff08;1&#xff09;课题背景 伴随着社会的快速发展, 现代社…

[NAND Flash 5.2] SLC、MLC、TLC、QLC、PLC NAND_闪存颗粒类型

依公知及经验整理&#xff0c;原创保护&#xff0c;禁止转载。 专栏 《深入理解NAND Flash》 <<<< 返回总目录 <<<< 前言 闪存最小物理单位是 Cell, 一个Cell 是一个晶体管。 闪存是通过晶体管储存电子来表示信息的。在晶体管上加入了浮动栅贮存电子…

odoo16 销售模块易错的几个操作

odoo16 销售模块易错的几个操作 据168Report调研团队最新报告“全球定制服装市场报告2023-2029”显示&#xff0c;预计2029年全球定制服装市场规模将达到1082.4亿美元&#xff0c;未来几年年复合增长率CAGR为7.8%。一个普通定制的小皮袄竟月销二十多万件&#xff0c;比我们做定…

HTML的简单介绍

文章目录 1. HTML1.1 HTML 基础认识1.2 快速生成代码框架1.3 HTML 基础标签 1. HTML 1.1 HTML 基础认识 什么是HTML呢&#xff1f; HTML叫做超文本标记语言。超文本&#xff1a;例如图片&#xff0c;视频&#xff0c;文本&#xff0c;声音&#xff0c;表格&#xff0c;链接等…