简介

Unit of work:维护受业务事务影响的对象列表,并协调变化的写入和并发问题的解决。即管理对象的CRUD操作,以及相应的事务与并发问题等。Unit of Work是用来解决领域模型存储和变更工作,而这些数据层业务并不属于领域模型本身具有的

我们知道在ABP中应用服务层,仓储。默认是启用工作单元模式的

若我们关闭了全局的工作单元,则必须以特性的方式在 class,method interface上加上[Unit of work]

然后ABP会通过Castle 的动态代理(Dynamic Proxy)拦截,Unit of work Attribute,进行动态注入,实现了 UnitOfworkManager对方法的管理(通过事务)

其流程如下

Abp初始化=>注册uow相关组件=>监听ioc注册事件=>是否有unitofwork特性.=>有的话注册拦截器=>在拦截器中通过using包裹原有方法代码,并执行=>最后看是否调用了Complete方法=>是的话Savechanges

启动流程

  1. private AbpBootstrapper([NotNull] Type startupModule, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
  2. {
  3. Check.NotNull(startupModule, nameof(startupModule));
  4. var options = new AbpBootstrapperOptions();
  5. optionsAction?.Invoke(options);
  6. if (!typeof(AbpModule).GetTypeInfo().IsAssignableFrom(startupModule))
  7. {
  8. throw new ArgumentException($"{nameof(startupModule)} should be derived from {nameof(AbpModule)}.");
  9. }
  10. StartupModule = startupModule;
  11. IocManager = options.IocManager;
  12. PlugInSources = options.PlugInSources;
  13. _logger = NullLogger.Instance;
  14. if (!options.DisableAllInterceptors)
  15. {
  16. AddInterceptorRegistrars();
  17. }
  18. }

在AddAbp创建abpBootsstrapper时,会对执行这个ctor函数,可以看到最后一行有个AddInterceptorRegistrars

这里就是注册所有的拦截器

  1. private void AddInterceptorRegistrars()
  2. {
  3. ValidationInterceptorRegistrar.Initialize(IocManager);
  4. AuditingInterceptorRegistrar.Initialize(IocManager);
  5. EntityHistoryInterceptorRegistrar.Initialize(IocManager);
  6. UnitOfWorkRegistrar.Initialize(IocManager);
  7. AuthorizationInterceptorRegistrar.Initialize(IocManager);
  8. }

其中UnitOfWorkRegistrar.Initialize(IocManager);就是注册工作单元拦截器

  1. internal static class UnitOfWorkRegistrar
  2. {
  3. /// <summary>
  4. /// Initializes the registerer.
  5. /// </summary>
  6. /// <param name="iocManager">IOC manager</param>
  7. public static void Initialize(IIocManager iocManager)
  8. {
  9. // 添加组件注册事件
  10. iocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) =>
  11. {
  12. var implementationType = handler.ComponentModel.Implementation.GetTypeInfo();
  13. // 根据unitofwork特性注册
  14. HandleTypesWithUnitOfWorkAttribute(implementationType, handler);
  15. // 按照约定注册
  16. HandleConventionalUnitOfWorkTypes(iocManager, implementationType, handler);
  17. };
  18. }
  19. private static void HandleTypesWithUnitOfWorkAttribute(TypeInfo implementationType, IHandler handler)
  20. {
  21. // IsUnitOfWorkType:如果给定类型实现有unitofwork返回true
  22. // AnyMethodHasUnitOfWork:给定类型的成员有unitofwork返回tue
  23. // 这里只做了一件事 如果当前类型有Unitofwork特性。则会注册拦截器
  24. if (IsUnitOfWorkType(implementationType) || AnyMethodHasUnitOfWork(implementationType))
  25. {
  26. handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor)));
  27. }
  28. }
  29. private static void HandleConventionalUnitOfWorkTypes(IIocManager iocManager, TypeInfo implementationType, IHandler handler)
  30. {
  31. // IUnitOfWorkDefaultOptions:用于设置/获取工作单元的默认选项 (范围\超时\隔离等级等)
  32. // 这里是判断ioc容器中有没有注册 IUnitOfWorkDefaultOptions 防止后面获取不到出异常
  33. if (!iocManager.IsRegistered<IUnitOfWorkDefaultOptions>())
  34. {
  35. return;
  36. }
  37. // 从ioc容器中获取 IUnitOfWorkDefaultOptions
  38. var uowOptions = iocManager.Resolve<IUnitOfWorkDefaultOptions>();
  39. // 当前类型是否是 约定的类型,是的话注册拦截器
  40. // IRepository,IApplicationService实现了这两个的会注册拦截器
  41. if (uowOptions.IsConventionalUowClass(implementationType.AsType()))
  42. {
  43. handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor)));
  44. }
  45. }
  46. private static bool IsUnitOfWorkType(TypeInfo implementationType)
  47. {
  48. return UnitOfWorkHelper.HasUnitOfWorkAttribute(implementationType);
  49. }
  50. private static bool AnyMethodHasUnitOfWork(TypeInfo implementationType)
  51. {
  52. return implementationType
  53. .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
  54. .Any(UnitOfWorkHelper.HasUnitOfWorkAttribute);
  55. }
  56. }

实现原理

工作单元拦截器

AbpBootstraper创建之后会执行工作单元拦截器的注册. 下面看看注册的拦截器长什么样子的

  1. internal class UnitOfWorkInterceptor : IInterceptor
  2. {
  3. private readonly IUnitOfWorkManager _unitOfWorkManager;
  4. private readonly IUnitOfWorkDefaultOptions _unitOfWorkOptions;
  5. public UnitOfWorkInterceptor(IUnitOfWorkManager unitOfWorkManager, IUnitOfWorkDefaultOptions unitOfWorkOptions)
  6. {
  7. _unitOfWorkManager = unitOfWorkManager;
  8. _unitOfWorkOptions = unitOfWorkOptions;
  9. }
  10. /// <summary>
  11. /// 拦截方法
  12. /// </summary>
  13. public void Intercept(IInvocation invocation)
  14. {
  15. MethodInfo method;
  16. try
  17. {
  18. method = invocation.MethodInvocationTarget;
  19. }
  20. catch
  21. {
  22. method = invocation.GetConcreteMethod();
  23. }
  24. // 工作单元特性
  25. var unitOfWorkAttr = _unitOfWorkOptions.GetUnitOfWorkAttributeOrNull(method);
  26. // 如果没有工作单元这个特性,直接执行原方法里的代码
  27. if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled)
  28. {
  29. invocation.Proceed();
  30. return;
  31. }
  32. // 这里分异步和同步执行的,
  33. PerformUow(invocation, unitOfWorkAttr.CreateOptions());
  34. }
  35. private void PerformUow(IInvocation invocation, UnitOfWorkOptions options)
  36. {
  37. // 异步
  38. if (invocation.Method.IsAsync())
  39. {
  40. PerformAsyncUow(invocation, options);
  41. }
  42. // 同步
  43. else
  44. {
  45. PerformSyncUow(invocation, options);
  46. }
  47. }
  48. private void PerformSyncUow(IInvocation invocation, UnitOfWorkOptions options)
  49. { // 开启一个工作单元
  50. using (var uow = _unitOfWorkManager.Begin(options))
  51. {
  52. // 执行原方法内部代码
  53. // 如果没有出错,就会执行Complete方法
  54. invocation.Proceed();
  55. uow.Complete();
  56. }
  57. }
  58. private void PerformAsyncUow(IInvocation invocation, UnitOfWorkOptions options)
  59. { // 开启一个工作单元
  60. var uow = _unitOfWorkManager.Begin(options);
  61. // 执行原方法内部代码,如果出错了则,释放当前工作单元
  62. try
  63. {
  64. invocation.Proceed();
  65. }
  66. catch
  67. {
  68. uow.Dispose();
  69. throw;
  70. }
  71. // 如果异步方法没有返回值.
  72. if (invocation.Method.ReturnType == typeof(Task))
  73. {
  74. invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(
  75. (Task) invocation.ReturnValue,// Task actualReturnValue,
  76. async () => await uow.CompleteAsync(),// Func<Task> postAction
  77. exception => uow.Dispose()// Action<Exception> finalAction
  78. );
  79. }
  80. // 如果异步方法返回值是Task<TResult>
  81. else
  82. {
  83. invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult(
  84. invocation.Method.ReturnType.GenericTypeArguments[0],// Type taskReturnType
  85. invocation.ReturnValue,// object actualReturnValue
  86. async () => await uow.CompleteAsync(),// Func<Task> action
  87. exception => uow.Dispose()//Action<Exception> finalAction
  88. );
  89. }
  90. }
  91. }
  92. }

下面先看看同步的uow方法:同步的没什么好说的,就是开启一个工作单元,或者可以说开启了一个事务.

执行内部的方法,执行没有错误的情况下,会调用Complete()方法. Complete方法等下在讲.

下面看看实际情况工作单元下类之间的方法是怎么调用的.

  1. public class InvoiceService
  2. {
  3. private readonly InvoiceCoreService _invoiceCoreService;
  4. public InvoiceService(InvoiceCoreService invoiceCoreService)
  5. {
  6. _invoiceCoreService=InvoiceCoreService;
  7. }
  8. public bool Invoice(InvoiceDto input)
  9. {
  10. return _invoiceCoreService.InvoiceElectronic(input);
  11. }
  12. }
  13. public class InvoiceCoreService:ITransientDependency
  14. {
  15. public readonly ThridPartyService _thridPartyService;
  16. public InvoiceCoreService(ThridPartyService thridPartyService)
  17. {
  18. _thridPartyService=thridPartyService;
  19. }
  20. [UnitOfWork]
  21. public bool InvoiceElectronic(InvoiceDto input)
  22. {
  23. var res= _thridPartyService.Run(input);
  24. Console.WriteLine("调用ThirdParty完成");
  25. return res;
  26. }
  27. }
  28. public class ThridPartyService:ITransientDependency
  29. {
  30. [UnitOfWork]
  31. public bool Run(InvoiceDto input)
  32. {
  33. Console.WriteLine("调百旺开电子票");
  34. return true;
  35. }
  36. }

这是我工作中的例子,首先要做的就是模拟开电子票

InvoiceService会调用InvoiceCoreService,InvoiceCoreService会调用ThirdPartyService.

那么执行的过程是怎么样的呢?

  1. public bool Invoice(InvoiceDto Input)
  2. {
  3. using(var uow=_unitOfWrokManager.Begin(options))
  4. {
  5. bool res=false;
  6. using(var uow2=_unitOfWrokManager.Begin(options))
  7. {
  8. res= ThridPartyService.Run();
  9. uow2.Complete();
  10. return res;
  11. }
  12. // invoiceCoreService
  13. Console.WriteLine("调用ThirdParty完成");
  14. Uow.Complete();
  15. }
  16. }

两个工作单元是用using嵌套的 ,只要代码执行失败了,就会导致Complete不会调用.

而 Complete() 方法没有执行,则会导致 uow 对象被释放的时候

uow.Dispose() 内部检测到 Complete() 没有被调用,Abp 框架也会自己抛出异常

下面看下异步方法

首先是没有返回值的情况也就是返回值是Task

  1. public static async Task AwaitTaskWithPostActionAndFinally(
  2. Task actualReturnValue,
  3. Func<Task> postAction,
  4. Action<Exception> finalAction)
  5. {
  6. Exception exception = null;
  7. try
  8. {
  9. // 执行原方法
  10. await actualReturnValue;
  11. await postAction();// async () => await uow.CompleteAsync() 提交更改
  12. }
  13. catch (Exception ex)
  14. {
  15. exception = ex;
  16. throw;
  17. }
  18. finally
  19. {
  20. // exception => uow.Dispose() 最后都会执行uow的释放方法
  21. finalAction(exception);
  22. }
  23. }

下面看看有返回值的情况

  1. public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue,
  2. Func<Task> action, Action<Exception> finalAction)
  3. { // 反射获取到 AwaitTaskWithPostActionAndFinallyAndGetResult 并调用
  4. return typeof (InternalAsyncHelper)
  5. .GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static)
  6. .MakeGenericMethod(taskReturnType)
  7. .Invoke(null, new object[] { actualReturnValue, action, finalAction });
  8. }
  1. public static async Task<T> AwaitTaskWithPostActionAndFinallyAndGetResult<T>(Task<T> actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
  2. {
  3. Exception exception = null;
  4. try
  5. {
  6. // 执行原方法内部代码 并获取返回值
  7. var result = await actualReturnValue;
  8. // 执行CompleteAsync方法
  9. await postAction();
  10. return result;
  11. }
  12. catch (Exception ex)
  13. {
  14. exception = ex;
  15. throw;
  16. }
  17. finally
  18. {
  19. // Dispose方法
  20. finalAction(exception);
  21. }
  22. }

工作单元拦截器就到此为止了

IUnitOfWorkManager

拦截器中有_unitOfWorkManager.Begin,之前只是说了 是开启了一个工作单元,那么这个是什么呢,我们来看看吧.

  1. internal class UnitOfWorkManager : IUnitOfWorkManager, ITransientDependency
  2. {
  3. private readonly IIocResolver _iocResolver;
  4. private readonly ICurrentUnitOfWorkProvider _currentUnitOfWorkProvider;
  5. private readonly IUnitOfWorkDefaultOptions _defaultOptions;
  6. public IActiveUnitOfWork Current
  7. {
  8. get { return _currentUnitOfWorkProvider.Current; }
  9. }
  10. public UnitOfWorkManager(
  11. IIocResolver iocResolver,
  12. ICurrentUnitOfWorkProvider currentUnitOfWorkProvider,
  13. IUnitOfWorkDefaultOptions defaultOptions)
  14. {
  15. _iocResolver = iocResolver;
  16. _currentUnitOfWorkProvider = currentUnitOfWorkProvider;
  17. _defaultOptions = defaultOptions;
  18. }
  19. public IUnitOfWorkCompleteHandle Begin()
  20. {
  21. return Begin(new UnitOfWorkOptions());
  22. }
  23. public IUnitOfWorkCompleteHandle Begin(TransactionScopeOption scope)
  24. {
  25. return Begin(new UnitOfWorkOptions { Scope = scope });
  26. }
  27. public IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options)
  28. {
  29. // 没有设置参数 填充默认参数
  30. options.FillDefaultsForNonProvidedOptions(_defaultOptions);
  31. // 获取当前工作单元
  32. var outerUow = _currentUnitOfWorkProvider.Current;
  33. // 当前已有工作单元,直接创建一个在它内部的工作单元
  34. if (options.Scope == TransactionScopeOption.Required && outerUow != null)
  35. {
  36. return new InnerUnitOfWorkCompleteHandle();
  37. }
  38. // 如果没有外部的工作单元,从ioc容器中直接获取一个
  39. var uow = _iocResolver.Resolve<IUnitOfWork>();
  40. // 绑定外部工作单元的一些事件.
  41. // 调用Complete方法后 设置当前工作单元为null
  42. uow.Completed += (sender, args) =>
  43. {
  44. _currentUnitOfWorkProvider.Current = null;
  45. };
  46. // 失败的时候,设置当前工作单元为null
  47. uow.Failed += (sender, args) =>
  48. {
  49. _currentUnitOfWorkProvider.Current = null;
  50. };
  51. // 从ioc容器释放
  52. uow.Disposed += (sender, args) =>
  53. {
  54. _iocResolver.Release(uow);
  55. };
  56. // 设置过滤器
  57. if (outerUow != null)
  58. {
  59. options.FillOuterUowFiltersForNonProvidedOptions(outerUow.Filters.ToList());
  60. }
  61. // 调用UnitOfWorkBase的Begin方法
  62. uow.Begin(options);
  63. // 设置租户id
  64. if (outerUow != null)
  65. {
  66. uow.SetTenantId(outerUow.GetTenantId(), false);
  67. }
  68. // 绑定外部工作单元为刚才创建的工作单元
  69. _currentUnitOfWorkProvider.Current = uow;
  70. return uow;
  71. }
  72. }
  73. }

可以看到返回值是IUnitOfWorkCompleteHandle

  1. public interface IUnitOfWorkCompleteHandle : IDisposable
  2. {
  3. void Complete();
  4. Task CompleteAsync();
  5. }

他有个默认实现InnerUnitOfWorkCompleteHandle

  1. internal class InnerUnitOfWorkCompleteHandle : IUnitOfWorkCompleteHandle
  2. {
  3. public const string DidNotCallCompleteMethodExceptionMessage = "Did not call Complete method of a unit of work.";
  4. private volatile bool _isCompleteCalled;
  5. private volatile bool _isDisposed;
  6. public void Complete()
  7. {
  8. _isCompleteCalled = true;
  9. }
  10. public Task CompleteAsync()
  11. {
  12. _isCompleteCalled = true;
  13. return Task.FromResult(0);
  14. }
  15. public void Dispose()
  16. {
  17. if (_isDisposed)
  18. {
  19. return;
  20. }
  21. _isDisposed = true;
  22. if (!_isCompleteCalled)
  23. {
  24. if (HasException())
  25. {
  26. return;
  27. }
  28. throw new AbpException(DidNotCallCompleteMethodExceptionMessage);
  29. }
  30. }
  31. private static bool HasException()
  32. {
  33. try
  34. {
  35. return Marshal.GetExceptionCode() != 0;
  36. }
  37. catch (Exception)
  38. {
  39. return false;
  40. }
  41. }
  42. }
  43. }

就是调用Complete的时候把_isCompleteCalled设置为true,在Dispose的时候判断,如果没有调用Complete那么会抛出异常

那么这里仅仅是做个标记的作用,savachanges 并没有在这里调用,那么数据库的保存是什么时候执行的呢?

其实之前在UnitOfManager内部的工作单元的类型就是InnerUnitOfWorkCompleteHandle,那么外部的工作单元是从Ioc容器中获取的.IUnitOfWork

它有几个默认实现,其中就有EfCoreUnitOfWork。里面就有savechanges方法. 它继承自UnitOfWorkBase。 UnitOfWorkBase继承自IUnitOfWork

  1. public interface IUnitOfWork : IActiveUnitOfWork, IUnitOfWorkCompleteHandle
  2. {
  3. /// <summary>
  4. /// 工作单元唯一Id,
  5. /// </summary>
  6. string Id { get; }
  7. /// <summary>
  8. /// 外部工作单元的引用对象
  9. /// </summary>
  10. IUnitOfWork Outer { get; set; }
  11. }
  1. public abstract class UnitOfWorkBase:IUnitOfWork
  2. {
  3. // 其他代码 略.
  4. // 由具体的子类实现
  5. protected abstract void CompleteUow();
  6. public void Complete()
  7. {
  8. // 确保Complete方法没有被调用过
  9. PreventMultipleComplete();
  10. try
  11. {
  12. CompleteUow();
  13. _succeed = true;
  14. OnCompleted();
  15. }
  16. catch (Exception ex)
  17. {
  18. _exception = ex;
  19. throw;
  20. }
  21. }
  22. }
  1. public class EfCoreUnitOfWork : UnitOfWorkBase, ITransientDependency
  2. {
  3. // 遍历所有有效的dbcontext,依次调用SaveChanges方法
  4. public override void SaveChanges()
  5. {
  6. foreach (var dbContext in GetAllActiveDbContexts())
  7. {
  8. SaveChangesInDbContext(dbContext);
  9. }
  10. }
  11. }

ABP工作单元的更多相关文章

  1. 基于DDD的.NET开发框架 - ABP工作单元(Unit of Work)

    返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...

  2. 手工搭建基于ABP的框架 - 工作单元以及事务管理

    一个业务功能往往不只由一次数据库请求(或者服务调用)实现.为了功能的完整性,我们希望如果该功能执行一半时出错,则撤销前面已执行的改动.在数据库层面上,事务管理实现了这种完整性需求.在ABP中,一个完整 ...

  3. ABP(现代ASP.NET样板开发框架)系列之12、ABP领域层——工作单元(Unit Of work)

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之12.ABP领域层——工作单元(Unit Of work) ABP是“ASP.NET Boilerplate Pr ...

  4. ABP框架 - 工作单元

    文档目录 本节内容: 简介 在ABP中管理连接和事务 约定的工作单元 UnitOfWork 特性 IUnitOfWorkManager 工作单元详情 禁用工作单元 非事务性工作单元 工作单元方法调用另 ...

  5. ABP理论学习之工作单元(Unit of Work)

    返回总目录 本篇目录 公共连接和事务管理方法 ABP中的连接和事务管理 仓储类 应用服务 工作单元 工作单元详解 关闭工作单元 非事务的工作单元 工作单元方法调用其它 工作单元作用域 自动保存 IRe ...

  6. 解析ABP框架中的事务处理和工作单元,ABP事务处理

    通用连接和事务管理方法连接和事务管理是使用数据库的应用程序最重要的概念之一.当你开启一个数据库连接,什么时候开始事务,如何释放连接...诸如此类的. 正如大家都知道的,.Net使用连接池(connec ...

  7. ABP的工作单元

    http://www.aspnetboilerplate.com/Pages/Documents/Unit-Of-Work 工作单元位于领域层.   ABP的数据库连接和事务处理: 1,仓储类 ASP ...

  8. ABP领域层——工作单元(Unit Of work)

    ABP领域层——工作单元(Unit Of work) 点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之12.ABP领域层——工作单元(Unit Of work) ...

  9. ABP官方文档翻译 3.6 工作单元

    工作单元 介绍 ABP中的连接和事务管理 传统的工作单元方法 控制工作单元 UnitOfWork特性 IUnitOfWorkManager 工作单元详情 禁用工作单元 无事务工作单元 一个工作单元方法 ...

随机推荐

  1. 由于ptrace.h文件导致的内核编译出错的解决方法

    arch/x86/kernel/ptrace.c:1472:17: error: conflicting types for 'syscall_trace_enter'  In file includ ...

  2. QT 利用QSplitter 分割区域, 并添加QScrollArea 滚动区域,滚动条

    1. QSplitter 分割区域, 可以分割区域中可以随意添加自己的布局 2. #include "dialog.h" #include <QApplication> ...

  3. QT 创建主窗口 MainWindow 实例

    1. 2. mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include & ...

  4. java异常中的finally(二)

    对于含有return语句的情况,这里我们可以简单地总结如下: try语句在返回前,将其他所有的操作执行完,保留好要返回的值,而后转入执行finally中的语句,而后分为以下三种情况: 情况一:如果fi ...

  5. 万字总结:学习MySQL优化原理,这一篇就够 了!【转】

    说起MySQL的查询优化,相信大家收藏了一堆奇技淫巧:不能使用SELECT *.不使用NULL字段.合理创建索引.为字段选择合适的数据类型..... 你是否真的理解这些优化技巧?是否理解其背后的工作原 ...

  6. asp.net上传文件大小限制

    <system.webServer> <security> <requestFiltering> <requestLimits maxAllowedConte ...

  7. mongodb数据文件结构——record是内嵌BSON的双向链表,多个record或索引组成extent

    数据文件结构 Extent 在每一个数据文件内,MongoDB把所存储的BSON文档的数据和B树索引组织到逻辑容器“Extent”里面.如下图所示(my-db.1和my-db.2 是数据库的两个数据文 ...

  8. cocos2d-html5 中的性能优化

    游戏开发中,难免会遇到性能瓶颈.图片一多,渲染批次就会直线上升,任何动画都会变得闪动. OpenGL ES优化的问题,主要考虑两个方面:内存存储和运行速度. 2D游戏中的最占内存的就是图片资源,一张图 ...

  9. Linux 链接网络

    目录 查看网卡 存在多个网卡 网卡配置静态IP 报错总结 诚邀访问我的个人博客:我在马路边 更好的阅读体验点击查看原文:Linux链接网络 原创博客,转载请注明出处 @ Linux在安装系统结束后总要 ...

  10. php 服务器的安全笔记

    php 服务器的安全笔记 操作系统安全 默认端口修改 MySQL 端口禁止外网访问 用户权限 父进程 子进程 目录权限 TODO Web Server 版本信息 服务器版本信息 PHP 版本 open ...