返回顶部
首页 > 资讯 > 精选 >怎么理解.NET可逆框架设计
  • 615
分享到

怎么理解.NET可逆框架设计

2023-06-17 10:06:52 615人浏览 八月长安
摘要

本篇文章为大家展示了怎么理解.net可逆框架设计,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。1. 什么是可逆的程序框架什么叫可逆的?程序的执行是可以被无限制回滚的。什么叫可逆的框架?实现了对可逆功

本篇文章为大家展示了怎么理解.net可逆框架设计,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。

1. 什么是可逆的程序框架

什么叫可逆的?程序的执行是可以被无限制回滚的。

什么叫可逆的框架?实现了对可逆功能的封装,并能通过简单的接口调用进行使用。框架可能有大有小,我想这么称呼它是为了表达它的整体性和重要性。

那么到底可逆的需求在哪里?其实在我们开发程序的时候经常会使用事务来进行业务的控制。比如删除订单,然后删除订单明细等等,对于这样的要求很多,我们只能将逻辑控制在一个事务范围内,不能在没有事务性的逻辑代码中编写这种要求的业务功能。等出现未知错误的时候在进行事务的回滚。

你也许会问,使用原来的事务处理不是也能进行回滚吗?当然不是这么简单的,我们使用事务回滚时只能将资源回滚到最初未进行事务处理前的状态。(这里不仅仅指的是数据库事务,而是全局的事务处理) 我们用图做个比较。

传统的事务处理图:

怎么理解.NET可逆框架设计

可逆的事务处理图:

怎么理解.NET可逆框架设计

从这两幅图中我们可以很明显的看出,传统的事务处理在事务处理的过程当中无法控制中间数据,也就是说无法对事务处理进行分段,然后在进行统一的提交或回滚。

在可逆框架的事务处理里我们就可以控制事务的执行阶段,在必要的时候我们只需提交或者回滚某一阶段的数据。

1.1环境事务

在可逆框架的事务处理图中,我们看到事务的开始,然后就进行下一步、下一步这样的操作。在每进行一个下一步操作的时候,就是进入到了一个子事务里处理,在.NET中是可以进行事务的嵌套,其实也就是依赖事务Dependent Transaction实现。通过使用环境事务可以让事务性感知代码能自动的识别出您将要使用事务进行操作。所以在每进行下一步操作的时候,只有将当前环境事务切换为您将依赖的子事务才行。如果只是单纯的使用依赖事务对象实例在使用,那么将无法进行诸多其他的事务处理。

2可逆框架的实现原理

由于我们只能控制自定义事务资源管理器的内部实现,所以我们在构建自己的数据处理时问题变的简单多了。

实现可逆框架的核心技术就是使用依赖事务进行事务的克隆操作。将一个大的事务处理逻辑上切割成多了小的事务操作,然后在进行统一的提交或回滚。

在实现上其实就是将Committable Transaction对象进行包装,实现简单的调用接口。这里参照了环境代码的概念,将对象的生命周期控制在代码片段中。

2.1自定义资源管理器的实现

我们需要扩展IEnlistmentNotification接口的实现,加入对“上一步”、“下一步”的数据操作。

请看代码:

   using System;   using System.Collections.Generic;   using System.Text;   using System.Transactions;     namespace ReversibleLib  {      /// <summary>      /// 可逆范围内的资源管理器。      /// 可以使用该类对易失性资源进行事务范围内的管理。在事务操作范围内进行可逆操作。      /// </summary>      /// <typeparam name="T">需要管理的资源类型</typeparam>      /// <typeparam name="Xcopy">资源在使用、恢复过程中的数据复制对象。</typeparam>      public class ReResourceManager<T, Xcopy> : IEnlistmentNotification, IReversibleGetResourceData<T>          where T : class, new()          where Xcopy : class     {          /// <summary>          /// 私有字段。资源的持久引用。          /// </summary>          T _commitfrontvalue;          /// <summary>          /// 私有字段。事务性操作数据对象。          /// </summary>          T _rollbackfrontvalue = new T();          /// <summary>          /// 保存数据复制对象。          /// </summary>          Xcopy _copy;          /// <summary>          /// 泛型约束需要,内部使用。          /// </summary>          public ReResourceManager() { }          /// <summary>          /// 资源管理器内部名称。便于追踪          /// </summary>          public string Name { get; set; }          /// <summary>          /// 重载默认构造函数,使用资源类型和数据复制对象初始化资源管理器。          /// </summary>          public ReResourceManager(T t, Xcopy icopy)          {              (icopy as IResourceCopy<T>).Copy(_rollbackfrontvalue, t);              _commitfrontvalue = t;              _copy = icopy;          }           #region IEnlistmentNotification 成员          public void Prepare(PreparingEnlistment preparingEnlistment)          {              preparingEnlistment.Prepared();          }          public void Commit(Enlistment enlistment)         {              enlistment.Done();          }          public void InDoubt(Enlistment enlistment)          {              enlistment.Done();          }          public void Rollback(Enlistment enlistment)          {              (_copy as IResourceCopy<T>).Copy(_commitfrontvalue, _rollbackfrontvalue);//回滚事务              enlistment.Done();          }          #endregion           #region IReversibleGetResourceData<T> 成员          T IReversibleGetResourceData<T>.GetPreviousData()          {              T result = new T();              (_copy as IResourceCopy<T>).Copy(result, _rollbackfrontvalue);              return result;          }          T IReversibleGetResourceData<T>.GetNextData()          {              T result = new T();              (_copy as IResourceCopy<T>).Copy(result, _commitfrontvalue);              return result;          }          #endregion      }  }

2.2可逆框架的入口实现

我们需要简单的调用就能方便的使用可逆功能,不能以一种新的方式使用。所以这里借鉴了Transaction Scope的设计思想。

请看代码:

View Code      using System;   using System.Collections.Generic;   using System.Text;   using System.Transactions;      namespace ReversibleLib   {       /// <summary>       /// 使代码成为可逆框架的事务性代码       /// </summary>       public class ReversibleManagerScope : IDisposable       {           /// <summary>           /// 初始化ReversibleManagerScope新的实例           /// </summary>           public ReversibleManagerScope()           {               ReversibleManager._reversibleManager = new ReversibleManager();           }           /// <summary>           /// 使用ReversibleManager对象构造ReversibleManagerScope使用范围对象           /// </summary>           /// <param name="manager">ReversibleManager实例</param>           public ReversibleManagerScope(ReversibleManager manager)           {               ReversibleManager._reversibleManager = manager;           }           /// <summary>           /// 使用自定义资源管理器构造ReversibleManagerScope包装的环境ReversibleManager.Current中的对象实例。           /// </summary>           /// <param name="source">IEnlistmentNotification资源管理器</param>           public ReversibleManagerScope(IEnlistmentNotification source)           {               ReversibleManager._reversibleManager = new ReversibleManager(source);           }           /// <summary>           /// 全局上下文ReversibleManager对象销毁           /// </summary>           public void Dispose()           {               ReversibleManager._reversibleManager = null;           }           /// <summary>           /// 完成整个操作的提交。该操作将提交事务栈中的所有依赖事务           /// </summary>           public void Completed()           {               ReversibleManager.Current.Commit();           }       }       /// <summary>       /// 可逆模块的入口。       /// ReversibleManager对事务对象的封装,实现阶段性的事务提交和回滚。       /// </summary>       public class ReversibleManager       {           #region 上下文静态ReversibleManager实例           /// <summary>           /// 持有对可逆框架的对象引用           /// </summary>           internal static ReversibleManager _reversibleManager;           /// <summary>           /// 获取当前上下文中可逆框架           /// </summary>           public static ReversibleManager Current           {               get { return _reversibleManager; }           }           #endregion              #region 构造对象           /// <summary>           /// 默认构造函数           /// </summary>           public ReversibleManager() { }           /// <summary>           /// 表示可提交的事务(主事务)           /// </summary>           private CommittableTransaction _commiTransaction;           /// <summary>           /// 支持两阶段提交协议的资源管理器(主资源管理器)           /// </summary>           private IEnlistmentNotification _resourceManager;           /// <summary>           /// 重载构造函数,使用自定义资源管理器构造可逆模块的开始。           /// </summary>           /// <param name="resource">IEnlistmentNotification接口对象</param>           public ReversibleManager(IEnlistmentNotification resource)           {               _resourceManager = resource;               InitLoad(IsolationLevel.Serializable);           }           /// <summary>           /// 重载构造函数,使用自定义资源管理器、内部事务范围的事务隔离级别构造可逆模型的开始。           /// </summary>           /// <param name="resource">IEnlistmentNotification接口对象</param>           /// <param name="isolationlevel">IsolationLevel枚举成员</param>           public ReversibleManager(IEnlistmentNotification resource, IsolationLevel isolationlevel)           {               _resourceManager = resource;               InitLoad(isolationlevel);           }           /// <summary>           /// 事务初始化阶段的参数对象           /// </summary>           TransactionOptions _options;           /// <summary>           /// 重载构造函数,使用自定义资源管理器、内部事务范围的事务隔离级别、事务超时时间范围构造可逆模块的开始。           /// </summary>           /// <param name="resource">IEnlistmentNotification接口对象</param>           /// <param name="isolationlevel">IsolationLevel枚举成员</param>           /// <param name="span">TimeSpan时间范围</param>           public ReversibleManager(IEnlistmentNotification resource, IsolationLevel isolationlevel, TimeSpan span)           {               _options = new TransactionOptions();               _options.Timeout = span;               InitLoad(isolationlevel);           }           /// <summary>           /// 构造CommittableTransaction对象实例。           /// </summary>           /// <param name="level">事务隔离级别</param>           private void InitLoad(IsolationLevel level)           {               if (_options == null)                   _options = new TransactionOptions();               _options.IsolationLevel = level;               _commiTransaction = new CommittableTransaction(_options);               _commiTransaction.EnlistVolatile(_resourceManager, EnlistmentOptions.None);               //作为事务栈的头开始整个可逆结构。               _tranStack.Push(_commiTransaction);//压入事务栈               _resourceStack.Push(_resourceManager);//压入资源栈               //设置环境事务,让所有支持事务性感知框架的代码都能执行。               Transaction.Current = _commiTransaction;           }           #endregion              /// <summary>           /// 事务栈,依次存放事务。           /// </summary>           private System.Collections.Generic.Stack<Transaction> _tranStack = new Stack<Transaction>();           /// <summary>           /// 资源栈,依次存放事务使用的资源。           /// </summary>           private System.Collections.Generic.Stack<IEnlistmentNotification> _resourceStack = new Stack<IEnlistmentNotification>();           /// <summary>           /// 阶段性事件委托           /// </summary>           /// <param name="tran">Transaction环境事务</param>           public delegate void PhaseHanlder(System.Transactions.Transaction tran);           /// <summary>           /// 下一步事件           /// </summary>           public event PhaseHanlder NextEvent;           /// <summary>           /// 上一步事件           /// </summary>           public event PhaseHanlder PreviousEvent;           /// <summary>           /// 开始下一步操作           /// </summary>           /// <typeparam name="S">IEnlistmentNotification接口实现</typeparam>           /// <param name="level">IsolationLevel事务的隔离级别(对全局事务处理设置)</param>           /// <param name="source">下一步操作的自定义数据管理器</param>           public void Next<S>(IsolationLevel level, S source)               where S : class,IEnlistmentNotification, new()           {               Transaction tran = _tranStack.Peek();//获取事务栈的顶端事务               if (tran == null)                   tran = Transaction.Current;//主事务               DependentTransaction depentran = tran.DependentClone(DependentCloneOption.BlockCommitUntilComplete);               //将本次事务处理的资源管理器压入资源栈中               depentran.EnlistVolatile(source, EnlistmentOptions.None);               _tranStack.Push(depentran);               _resourceStack.Push(source);               //切换环境事务场景               Transaction.Current = depentran;               if (NextEvent != null)                   if (NextEvent.GetInvocationList().Length > 0)                       NextEvent(Transaction.Current);           }           /// <summary>           /// 返回上一步操作           /// </summary>           /// <typeparam name="T">需要接受的数据对象类型</typeparam>           /// <param name="refadd">需要接受的数据对象引用</param>           public void Previous<T>(out T refadd) where T : class,new()           {               Transaction tran = _tranStack.Pop();               if (tran == null)//顶层事务                   Transaction.Current.Rollback();               // tran.Rollback();//回滚本事务,将触发所有克隆事务的回滚。               if (PreviousEvent != null)                   if (PreviousEvent.GetInvocationList().Length > 0)                   {                       //设置上一步数据对象                       refadd = (_resourceStack.Pop() as IReversibleGetResourceData<T>).GetPreviousData();                       PreviousEvent(Transaction.Current);                       return;                   }               refadd = new T();//事务处理异常           }           /// <summary>           /// 提交事物堆栈中的所有事物           /// </summary>           public void Commit()           {               if (Transaction.Current is DependentTransaction)                   (Transaction.Current as DependentTransaction).Complete();               for (int i = 0; i < _tranStack.Count - 1; i++)               {                   //依赖事务                   (_tranStack.Pop() as DependentTransaction).Complete();               }               //提交事务,主事务。必须进行克隆主体的提交才能完成所有阶段的操作。               (_tranStack.Pop() as CommittableTransaction).Commit();           }           /// <summary>           /// 回滚事物堆栈中的所有事物           /// </summary>           public void RollBack()           {               if (Transaction.Current is DependentTransaction)                   (Transaction.Current as DependentTransaction).Rollback();               for (int i = 0; i < _tranStack.Count - 1; i++)               {                   //依赖事务                   (_tranStack.Pop() as DependentTransaction).Rollback();               }               //提交事务,主事务。必须进行克隆主体的提交才能完成所有阶段的操作。               (_tranStack.Pop() as CommittableTransaction).Rollback();           }       }   }

3.示例

这里我使用了一个简单的String Builder作为资源管理器需要管理的对象。

请看代码:

View Code      using System;   using System.Collections.Generic;   using System.Text;   using System.Data;   using System.Transactions;   using ReversibleLib;      namespace ConsoleApplication1   {       class Program       {           static void Main(string[] args)           {               //构造数据               StringBuilder strbuilder = new StringBuilder();               strbuilder.Append("0");//初始数据为0                  //资源管理器               ReResourceManager<StringBuilder, StringBuilderCopy> strResource =                   new ReResourceManager<StringBuilder, StringBuilderCopy>(strbuilder, new StringBuilderCopy());               strResource.Name = "0资源管理器";               //开始进入可逆框架处理环境               using (ReversibleManagerScope reversible = new ReversibleManagerScope(strResource))               {                   try                  {                       ReversibleManager.Current.PreviousEvent += new ReversibleManager.PhaseHanlder(Current_PreviousEvent);                       ReversibleManager.Current.NextEvent += new ReversibleManager.PhaseHanlder(Current_NextEvent);                       strbuilder.Append("1");//***修改数据为01                          //获取下一步操作的数据                       StringBuilder strbuilder2 = (strResource as IReversibleGetResourceData<StringBuilder>).GetNextData();                       //构造下一步操作的自定义资源管理器                       ReResourceManager<StringBuilder, StringBuilderCopy> strResource2 =                           new ReResourceManager<StringBuilder, StringBuilderCopy>(strbuilder2, new StringBuilderCopy());                       strResource2.Name = "2资源管理器";                       ReversibleManager.Current.Next<ReResourceManager<StringBuilder, StringBuilderCopy>>(                           System.Transactions.IsolationLevel.Serializable, strResource2);                       strbuilder2.Append("2");//第二步修改数据为012                          //返回上一步,也就是回滚对数据进行“2”设置的前一个状态                       StringBuilder strbuilder3;                       ReversibleManager.Current.Previous<StringBuilder>(out strbuilder3);//获取上一步使用的数据,这里应该是01                          reversible.Completed();//提交所有操作                       Console.WriteLine(strbuilder3);                   }                   catch (Exception err)                   { Console.WriteLine(err.Message); ReversibleManager.Current.RollBack(); }               }               Console.ReadLine();           }              static void Current_NextEvent(Transaction tran)           {               Console.WriteLine("下一步:" + tran.TransactionInfORMation.LocalIdentifier);               Console.WriteLine("下一步:" + tran.TransactionInformation.DistributedIdentifier);           }           static void Current_PreviousEvent(Transaction tran)           {               Console.WriteLine("上一步:" + tran.TransactionInformation.LocalIdentifier);               Console.WriteLine("上一步:" + tran.TransactionInformation.DistributedIdentifier);           }       }   }

这里我使用0作为资源的初始数据,然后进入到***个环节,我将它附加了1,然后进入到第二个环节,我将它附加了2,这里应该是012了,但是下面我突然又返回到了上一步,所以***的数据应该是01。如果我们需要使用复杂的数据对象,如常用的Data Table类型,我们一般都是用它来展现一组数据,然后对这组数据进行一系列的操作。

主要是想介绍一下事务的另一种使用方式,对可逆框架的设计方向算是一个抛砖引玉吧。

上述内容就是怎么理解.NET可逆框架设计,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注编程网精选频道。

--结束END--

本文标题: 怎么理解.NET可逆框架设计

本文链接: https://lsjlt.com/news/288248.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 怎么理解.NET可逆框架设计
    本篇文章为大家展示了怎么理解.NET可逆框架设计,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。1. 什么是可逆的程序框架什么叫可逆的?程序的执行是可以被无限制回滚的。什么叫可逆的框架?实现了对可逆功...
    99+
    2023-06-17
  • 如何理解.NET可逆框架设计
    本篇文章为大家展示了如何理解.NET可逆框架设计,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。前段时间一直在学习和研究.NET事务处理,慢慢的我发现可以使用事务处理来实现一种可逆的系统框架。这种框架...
    99+
    2023-06-17
  • C#和.NET框架正则表达式怎么理解
    本篇内容主要讲解“C#和.NET框架正则表达式怎么理解”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C#和.NET框架正则表达式怎么理解”吧!C#和.NET框架正则表达式正则表达式的一种最常用的...
    99+
    2023-06-17
  • 怎么理解.NET三层架构应用中的十层框架
    这篇文章给大家介绍怎么理解.NET三层架构应用中的十层框架,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。凡是接触.net开发的,***要理解的就是三层架构的重要思想。关于三层架构的概述 我就不多说了,相信大家都早已明白...
    99+
    2023-06-17
  • .NET Framework 4.0框架怎么用
    这篇文章主要为大家展示了“.NET Framework 4.0框架怎么用”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“.NET Framework 4.0框架怎么用”这篇文章吧。在.NET Fr...
    99+
    2023-06-17
  • JavaScript框架设计模式详解
    目录mvcmvpmvvmvue的来源spa mpacreateElementclass总结mvc Model(模型) - 模型代表一个存取数据的对象或 JAVA POJO。它也可...
    99+
    2024-04-02
  • MyBatis的框架架构设计是怎么样的
    小编给大家分享一下MyBatis的框架架构设计是怎么样的,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!MyBatis的框架架构设...
    99+
    2024-04-02
  • 怎么解析iPhone中的MVC框架设计模式
    怎么解析iPhone中的MVC框架设计模式,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。整理自斯坦福大学iphone开发公开课,并加入了一些自己的理解。一、概念 ...
    99+
    2023-06-17
  • 如何理解.NET MVVM设计模式
    如何理解.NET MVVM设计模式,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。MVVM 模式能够帮你把你程序的业务与展现逻辑从用户界面干净地分离开。保持程序逻辑与界面分离能够...
    99+
    2023-06-17
  • 怎么理解SpringCloud框架
    这篇文章主要介绍“ 怎么理解SpringCloud框架”,在日常操作中,相信很多人在 怎么理解SpringCloud框架问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答” 怎么理...
    99+
    2024-04-02
  • .NET日志框架Nlog怎么使用
    这篇文章主要介绍“.NET日志框架Nlog怎么使用”,在日常操作中,相信很多人在.NET日志框架Nlog怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”.NET日志框架Nlog怎么使用”的疑惑有所帮助!...
    99+
    2023-06-29
  • .net日志框架Serilog怎么使用
    本篇内容介绍了“.net日志框架Serilog怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!首先安装Nuget包:Install-P...
    99+
    2023-07-02
  • .Net怎么使用日志框架NLog
    这篇文章主要讲解了“.Net怎么使用日志框架NLog”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“.Net怎么使用日志框架NLog”吧!在Nuget中安装NLogNLog可以直接使用Nuge...
    99+
    2023-07-02
  • Python Django框架设计模式详解
    目录MVC设计模式MTV设计模式总结MVC设计模式 MVC (Model-View-Controller) 是软件工程中常用的软件架构模式,它是一种分离业务逻辑与显示界面的设计方法。...
    99+
    2024-04-02
  • 解密CSS框架设计的奥秘
    探索CSS框架设计的秘诀 在现代网页设计中,CSS框架扮演着关键的角色。它们提供了一套预定义的样式和布局,使网页设计更加简单和一致。然而,并非所有的CSS框架都能满足每个项目的需求。设计高效、灵活和可扩展的CSS框架是一个挑战,...
    99+
    2024-01-16
    CSS设计 CSS框架设计 探索秘诀
  • Java框架设计之反射怎么应用
    这篇文章主要讲解了“Java框架设计之反射怎么应用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java框架设计之反射怎么应用”吧!框架:半成品软件。可以在框架的基础上进行软件开发,简化编码...
    99+
    2023-07-02
  • 怎么做web高可用的架构设计
    本篇内容介绍了“怎么做web高可用的架构设计”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!定义目标既然我们的目标是做到高可用,那么我们就有必...
    99+
    2023-06-05
  • CocosCreator通用框架设计之资源管理
    目录cocos creator 资源管理存在的问题资源依赖资源使用ResLoader使用ResLoader如果你想使用Cocos Creator制作一些规模稍大的游戏,那么资源管理是...
    99+
    2024-04-02
  • .Net性能测试框架Crank怎么使用
    这篇文章主要介绍“.Net性能测试框架Crank怎么使用”,在日常操作中,相信很多人在.Net性能测试框架Crank怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”.Net性能测试框架Crank怎么使用...
    99+
    2023-06-21
  • .net任务调度框架FluentScheduler怎么使用
    这篇文章主要介绍“.net任务调度框架FluentScheduler怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“.net任务调度框架FluentScheduler怎么使用”文章能帮助大家解...
    99+
    2023-07-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作