zoukankan      html  css  js  c++  java
  • MVC中使用T4模板

    参考博文

    http://www.cnblogs.com/heyuquan/archive/2012/07/26/2610959.html

    图片释义

    1、简单示例,对基本的模块标记

    2、根据上图生成的类

    网上大佬写的JuCheap项目中的T4模板

    https://gitee.com/jucheap/projects

    T4代码

    <#@ template  language="C#"    debug="true" hostspecific="True"#> 
    <#@ include file="$(SolutionDir)JuCheap.ServiceMultipleOutputHelper.ttinclude" #>
    <#@ import namespace="System"#>
    
    <#
        string solutionsPath = Host.ResolveAssemblyReference("$(SolutionDir)");
        var files = System.IO.Directory.GetFiles(solutionsPath + @"JuCheap.Entity", "*.cs");
    
        var manager = Manager.Create(Host, GenerationEnvironment);
    
        //1.开始输出接口契约文件
        foreach (var filePath in files)
        {
            var file = new FileInfo(filePath);
            var name = file.Name.Replace("Entity.cs",string.Empty);
            var lowerName =name.ToLower();
            //定义输出文件
            manager.StartNewFile("I"+name+"Service.Partial.cs", string.Empty);
    #>
    
    /*******************************************************************************
    * Copyright (C)  JuCheap.Com
    * 
    * Author: dj.wong
    * Create Date: <#=DateTime.Now#>
    * Description: Automated building by service@JuCheap.com 
    * 
    * Revision History:
    * Date         Author               Description
    * 2017-08-02   dj.wong              optimization
    *********************************************************************************/
    
    using System;
    using System.Collections.Generic;
    using System.Linq.Expressions;
    using JuCheap.Service.Dto;
    
    namespace JuCheap.Service.Abstracts
    { 
        /// <summary>
        /// <#=name#>业务契约
        /// </summary>
        public partial interface I<#=name#>Service
        {
            /// <summary>
            /// 添加<#=lowerName#>
            /// </summary>
            /// <param name="<#=lowerName#>"><#=lowerName#>实体</param>
            /// <returns></returns>
            bool Add(<#=name#>Dto <#=lowerName#>);
    
            /// <summary>
            /// 批量添加<#=lowerName#>
            /// </summary>
            /// <param name="models"><#=lowerName#>集合</param>
            /// <returns></returns>
            bool Add(List<<#=name#>Dto> models);
    
            /// <summary>
            /// 编辑<#=lowerName#>
            /// </summary>
            /// <param name="<#=lowerName#>">实体</param>
            /// <returns></returns>
            bool Update(<#=name#>Dto <#=lowerName#>);
    
            /// <summary>
            /// 批量更新<#=lowerName#>
            /// </summary>
            /// <param name="<#=lowerName#>s"><#=lowerName#>实体集合</param>
            /// <returns></returns>
            bool Update(IEnumerable<<#=name#>Dto> <#=lowerName#>s);
    
            /// <summary>
            /// 删除<#=lowerName#>
            /// </summary>
            /// <param name="id">Id</param>
            /// <returns></returns>
            bool Delete(int id);
    
            /// <summary>
            /// 批量删除<#=lowerName#>
            /// </summary>
            /// <param name="exp">条件表达式</param>
            /// <returns></returns>
            bool Delete(Expression<Func<<#=name#>Dto, bool>> exp);
    
            /// <summary>
            ///  获取单条符合条件的 <#=lowerName#> 数据
            /// </summary>
            /// <param name="exp">条件表达式</param>
            /// <returns></returns>
            <#=name#>Dto GetOne(Expression<Func<<#=name#>Dto, bool>> exp);
    
            /// <summary>
            /// 查询符合调价的 <#=lowerName#>
            /// </summary>
            /// <param name="exp">过滤条件</param>
            /// <param name="orderExp">排序条件</param>
            /// <param name="isDesc">是否是降序排列</param>
            /// <returns></returns>
            List<<#=name#>Dto> Query<OrderKeyType>(Expression<Func<<#=name#>Dto, bool>> exp, Expression<Func<<#=name#>Dto, OrderKeyType>> orderExp, bool isDesc = true);
    
            /// <summary>
            /// 分页获取<#=lowerName#>
            /// </summary>
            /// <param name="queryBase">QueryBase</param>
            /// <param name="exp">过滤条件</param>
            /// <param name="orderExp">排序条件</param>
            /// <param name="isDesc">是否是降序排列</param>
            /// <returns></returns>
            ResultDto<<#=name#>Dto> GetWithPages<OrderKeyType>(QueryBase queryBase, Expression<Func<<#=name#>Dto, bool>> exp, Expression<Func<<#=name#>Dto, OrderKeyType>> orderExp, bool isDesc = true);
    
            /// <summary>
            /// 分页获取<#=lowerName#>
            /// </summary>
            /// <param name="queryBase">QueryBase</param>
            /// <param name="exp">过滤条件</param>
            /// <param name="orderBy">排序条件</param>
            /// <param name="orderDir">是否是降序排列</param>
            /// <returns></returns>
            ResultDto<<#=name#>Dto> GetWithPages(QueryBase queryBase, Expression<Func<<#=name#>Dto, bool>> exp, string orderBy, string orderDir = "desc");
        } 
    }
    <# 
        // 结束输出文件
        manager.EndBlock();
    } 
        //2.开始生成契约实现文件
        foreach (var filePath in files)
        {
            var file = new FileInfo(filePath);
            var name = file.Name.Replace("Entity.cs",string.Empty);
            var lowerName = name.ToLower();
            //定义输出文件
            manager.StartNewFile(name+"Service.Partial.cs", string.Empty);
    #>
    
    /*******************************************************************************
    * Copyright (C)  JuCheap.Com
    * 
    * Author: dj.wong
    * Create Date: <#=DateTime.Now#>
    * Description: Automated building by service@JuCheap.com 
    * 
    * Revision History:
    * Date         Author               Description
    *
    *********************************************************************************/
    
    using System;
    using System.Collections.Generic;
    using System.Data.Entity.Migrations;
    using System.Linq;
    using System.Linq.Expressions;
    using EntityFramework.Extensions;
    using AutoMapper;
    using JuCheap.Core;
    using JuCheap.Core.Extentions;
    using JuCheap.Entity;
    using JuCheap.Service.Dto;
    using Mehdime.Entity;
    
    namespace JuCheap.Service.Abstracts
    { 
        /// <summary>
        /// <#=name#>业务契约
        /// </summary>
        public partial class <#=name#>Service : ServiceBase<<#=name#>Entity>, IDependency, I<#=name#>Service
        {
            #region 构造函数注册上下文
            public IDbContextScopeFactory _dbScopeFactory {get;set;}
    
            //private readonly IDbContextScopeFactory _dbScopeFactory;
    
            //public <#=name#>Service(IDbContextScopeFactory dbScopeFactory)
            //{
            //    _dbScopeFactory = dbScopeFactory;
            //}
    
            #endregion
    
            #region I<#=name#>Service 接口实现
    
            /// <summary>
            /// 添加<#=lowerName#>
            /// </summary>
            /// <param name="dto"><#=lowerName#>实体</param>
            /// <returns></returns>
            public bool Add(<#=name#>Dto dto)
            {
                using (var scope = _dbScopeFactory.Create())
                {
                    var db = GetDb(scope);
                    var dbSet = GetDbSet(db);
                    var entity = Mapper.Map<<#=name#>Dto, <#=name#>Entity>(dto);
                    dbSet.Add(entity);
                    var count = db.SaveChanges();
                    return count > 0;
                }
            }
    
            /// <summary>
            /// 批量添加<#=lowerName#>
            /// </summary>
            /// <param name="dtos"><#=lowerName#>集合</param>
            /// <returns></returns>
            public bool Add(List<<#=name#>Dto> dtos)
            {
                using (var scope = _dbScopeFactory.Create())
                {
                    var db = GetDb(scope);
                    var dbSet = GetDbSet(db);
                    var entities = Mapper.Map<List<<#=name#>Dto>, List<<#=name#>Entity>>(dtos);
                    dbSet.AddRange(entities);
                    return db.SaveChanges() > 0;
                }
            }
    
            /// <summary>
            /// 编辑<#=lowerName#>
            /// </summary>
            /// <param name="dto">实体</param>
            /// <returns></returns>
            public bool Update(<#=name#>Dto dto)
            {
                using (var scope = _dbScopeFactory.Create())
                {
                    var db = GetDb(scope);
                    var dbSet = GetDbSet(db);
                    var entity = Mapper.Map<<#=name#>Dto, <#=name#>Entity>(dto);
                    dbSet.AddOrUpdate(entity);
                    return db.SaveChanges() > 0;
                }
            }
    
            /// <summary>
            /// 批量更新<#=lowerName#>
            /// </summary>
            /// <param name="dtos"><#=lowerName#>实体集合</param>
            /// <returns></returns>
            public bool Update(IEnumerable<<#=name#>Dto> dtos)
            {
                using (var scope = _dbScopeFactory.Create())
                {
                    var db = GetDb(scope);
                    var dbSet = GetDbSet(db);
                    var entities = Mapper.Map<IEnumerable<<#=name#>Dto>, IEnumerable<<#=name#>Entity>>(dtos);
                    dbSet.AddOrUpdate(entities.ToArray());
                    return db.SaveChanges() > 0;
                }
            }
    
            /// <summary>
            /// 删除<#=lowerName#>
            /// </summary>
            /// <param name="id">Id</param>
            /// <returns></returns>
            public bool Delete(int id)
            {
                using (var scope = _dbScopeFactory.Create())
                {
                    var db = GetDb(scope);
                    var dbSet = GetDbSet(db);
    
                    var model = dbSet.FirstOrDefault(item => item.Id == id);
                    dbSet.Remove(model);
                    return db.SaveChanges() > 0;
                }
            }
    
            /// <summary>
            /// 批量删除<#=lowerName#>
            /// </summary>
            /// <param name="exp">条件表达式</param>
            /// <returns></returns>
            public bool Delete(Expression<Func<<#=name#>Dto, bool>> exp)
            {
                using (var scope = _dbScopeFactory.Create())
                {
                    var db = GetDb(scope);
                    var dbSet = GetDbSet(db);
                    var where = exp.Cast<<#=name#>Dto, <#=name#>Entity, bool>();
                    
                    var models = dbSet.Where(where);
                    dbSet.RemoveRange(models);
                    return db.SaveChanges() > 0;
                }
            }
    
            /// <summary>
            ///  获取单条符合条件的 <#=lowerName#> 数据
            /// </summary>
            /// <param name="exp">条件表达式</param>
            /// <returns></returns>
            public <#=name#>Dto GetOne(Expression<Func<<#=name#>Dto, bool>> exp)
            {
                using (var scope = _dbScopeFactory.CreateReadOnly())
                {
                    var db = GetDb(scope);
                    var dbSet = GetDbSet(db);
                    var where = exp.Cast<<#=name#>Dto, <#=name#>Entity, bool>();
                    var entity = dbSet.AsNoTracking().FirstOrDefault(where);
    
                    return Mapper.Map<<#=name#>Entity, <#=name#>Dto>(entity);
                }
            }
    
            /// <summary>
            /// 查询符合调价的 <#=lowerName#>
            /// </summary>
            /// <param name="exp">过滤条件</param>
            /// <param name="orderExp">排序条件</param>
            /// <param name="isDesc">是否是降序排列</param>
            /// <returns></returns>
            public List<<#=name#>Dto> Query<OrderKeyType>(Expression<Func<<#=name#>Dto, bool>> exp, Expression<Func<<#=name#>Dto, OrderKeyType>> orderExp, bool isDesc = true)
            {
                using (var scope = _dbScopeFactory.CreateReadOnly())
                {
                    var db = GetDb(scope);
                    var dbSet = GetDbSet(db);
                    var where = exp.Cast<<#=name#>Dto, <#=name#>Entity, bool>();
                    var order = orderExp.Cast<<#=name#>Dto, <#=name#>Entity, OrderKeyType>();
                    var query = GetQuery(dbSet, where, order, isDesc);
                    var list = query.ToList();
                    return Mapper.Map<List<<#=name#>Entity>, List<<#=name#>Dto>>(list);
                }
            }
    
            /// <summary>
            /// 分页获取<#=name#>
            /// </summary>
            /// <param name="queryBase">QueryBase</param>
            /// <param name="exp">过滤条件</param>
            /// <param name="orderExp">排序条件</param>
            /// <param name="isDesc">是否是降序排列</param>
            /// <returns></returns>
            public ResultDto<<#=name#>Dto> GetWithPages<OrderKeyType>(QueryBase queryBase, Expression<Func<<#=name#>Dto, bool>> exp, Expression<Func<<#=name#>Dto, OrderKeyType>> orderExp, bool isDesc = true)
            {
                using (var scope = _dbScopeFactory.CreateReadOnly())
                {
                    var db = GetDb(scope);
                    var dbSet = GetDbSet(db);
                    var where = exp.Cast<<#=name#>Dto, <#=name#>Entity, bool>();
                    var order = orderExp.Cast<<#=name#>Dto, <#=name#>Entity, OrderKeyType>();
                    var query = GetQuery(dbSet, where, order, isDesc);
    
                    var query_count = query.FutureCount();
                    var query_list = query.Skip(queryBase.Start).Take(queryBase.Length).Future();
                    var list = query_list.ToList();
    
                    var dto = new ResultDto<<#=name#>Dto>
                    {
                        recordsTotal = query_count.Value,
                        data = Mapper.Map<List<<#=name#>Entity>, List<<#=name#>Dto>>(list)
                    };
                    return dto;
                }
            }
    
            /// <summary>
            /// 分页获取<#=name#>
            /// </summary>
            /// <param name="queryBase">QueryBase</param>
            /// <param name="exp">过滤条件</param>
            /// <param name="orderBy">排序条件</param>
            /// <param name="orderDir">排序类型:desc(默认)/asc</param>
            /// <returns></returns>
            public ResultDto<<#=name#>Dto> GetWithPages(QueryBase queryBase, Expression<Func<<#=name#>Dto, bool>> exp, string orderBy, string orderDir = "desc")
            {
                using (var scope = _dbScopeFactory.CreateReadOnly())
                {
                    var db = GetDb(scope);
                    var dbSet = GetDbSet(db);
                    var where = exp.Cast<<#=name#>Dto, <#=name#>Entity, bool>();
                    //var order = orderExp.Cast<<#=name#>Dto, <#=name#>Entity, OrderKeyType>();
                    var query = GetQuery(dbSet, where, orderBy, orderDir);
    
                    var query_count = query.FutureCount();
                    var query_list = query.Skip(queryBase.Start).Take(queryBase.Length).Future();
                    var list = query_list.ToList();
    
                    var dto = new ResultDto<<#=name#>Dto>
                    {
                        recordsTotal = query_count.Value,
                        data = Mapper.Map<List<<#=name#>Entity>, List<<#=name#>Dto>>(list)
                    };
                    return dto;
                }
            }
    
            #endregion
        } 
    }
    <# 
        // 结束输出文件
        manager.EndBlock();
    }
    #> 
    
    
    <#
        manager.StartNewFile("AutoMapperConfiguration.Partial.cs", string.Empty);
    #>
    
    
    /*******************************************************************************
    * Copyright (C)  JuCheap.Com
    * 
    * Author: dj.wong
    * Create Date: 2015/8/7 11:12:12
    * Description: Automated building by service@JuCheap.com 
    * 
    * Revision History:
    * Date         Author               Description
    *
    *********************************************************************************/
    
    using AutoMapper;
    using JuCheap.Entity;
    using JuCheap.Service.Dto;
    
    namespace JuCheap.Service
    {
        /// <summary>
        /// AutoMapper 配置
        /// </summary>
        public partial class AutoMapperConfiguration
        {
            /// <summary>
            /// 配置AutoMapper
            /// </summary>
            public static void Config()
            {
    <#
        //1.开始输出接口契约文件
        foreach (var filePath in files)
        {
            var file = new FileInfo(filePath);
            var name = file.Name.Replace("Entity.cs",string.Empty);
            var lowerName =name.ToLower();
            //定义输出文件
    #>
                Mapper.CreateMap<<#=name#>Entity, <#=name#>Dto>();
                Mapper.CreateMap<<#=name#>Dto, <#=name#>Entity>();
    <# 
        }
    #>
            }
        }
    }
    <#
        // 结束输出文件
        manager.EndBlock();
        // 执行编译
        manager.Process(true);  
    #> 

    引用的ttInclude代码

    <#@ assembly name="System.Core"
    #><#@ assembly name="System.Data.Linq"
    #><#@ assembly name="EnvDTE"
    #><#@ assembly name="System.Xml"
    #><#@ assembly name="System.Xml.Linq"
    #><#@ import namespace="System.Collections.Generic"
    #><#@ import namespace="System.IO"
    #><#@ import namespace="System.Text"
    #><#@ import namespace="Microsoft.VisualStudio.TextTemplating"
    #><#+
    // https://raw.github.com/damieng/DamienGKit
    // http://damieng.com/blog/2009/11/06/multiple-outputs-from-t4-made-easy-revisited
    // Manager class records the various blocks so it can split them up
    class Manager {
        private class Block {
            public String Name;
            public string FilePath;
            public int Start, Length;
            public bool IncludeInDefault;
        }
    
        private Block currentBlock;
        private readonly List<Block> files = new List<Block>();
        private readonly Block footer = new Block();
        private readonly Block header = new Block();
        private readonly ITextTemplatingEngineHost host;
        private readonly StringBuilder template;
        protected readonly List<String> generatedFileNames = new List<String>();
    
        public static Manager Create(ITextTemplatingEngineHost host, StringBuilder template) {
            return (host is IServiceProvider) ? new VSManager(host, template) : new Manager(host, template);
        }
    
        public void StartNewFile(String name, string filePath) {
            if (name == null)
                throw new ArgumentNullException("name");
            CurrentBlock = new Block { Name = name, FilePath = filePath };
        }
    
        public void StartFooter(bool includeInDefault = true) {
            CurrentBlock = footer;
            footer.IncludeInDefault = includeInDefault;
        }
    
        public void StartHeader(bool includeInDefault = true) {
            CurrentBlock = header;
            header.IncludeInDefault = includeInDefault;
        }
    
        public void EndBlock() {
            if (CurrentBlock == null)
                return;
            CurrentBlock.Length = template.Length - CurrentBlock.Start;
            if (CurrentBlock != header && CurrentBlock != footer)
                files.Add(CurrentBlock);
            currentBlock = null;
        }
    
        public virtual void Process(bool split, bool sync = true) {
            if (split) {
                EndBlock();
                String headerText = template.ToString(header.Start, header.Length);
                String footerText = template.ToString(footer.Start, footer.Length);
                String outputPath = Path.GetDirectoryName(host.TemplateFile);
                files.Reverse();
                if (!footer.IncludeInDefault)
                    template.Remove(footer.Start, footer.Length);
                foreach(Block block in files) {
                    String myPath = block.FilePath;
                    if(!string.IsNullOrWhiteSpace(myPath))
                        outputPath = myPath;
                    String fileName = Path.Combine(outputPath, block.Name);
                    String content = headerText + template.ToString(block.Start, block.Length) + footerText;
                    generatedFileNames.Add(fileName);
                    CreateFile(fileName, content);
                    template.Remove(block.Start, block.Length);
                }
                if (!header.IncludeInDefault)
                    template.Remove(header.Start, header.Length);
            }
        }
    
        protected virtual void CreateFile(String fileName, String content) {
            if (IsFileContentDifferent(fileName, content))
                File.WriteAllText(fileName, content);
        }
    
        public virtual String GetCustomToolNamespace(String fileName) {
            return null;
        }
    
        public virtual String DefaultProjectNamespace {
            get { return null; }
        }
    
        protected bool IsFileContentDifferent(String fileName, String newContent) {
            return !(File.Exists(fileName) && File.ReadAllText(fileName) == newContent);
        }
    
        private Manager(ITextTemplatingEngineHost host, StringBuilder template) {
            this.host = host;
            this.template = template;
        }
    
        private Block CurrentBlock {
            get { return currentBlock; }
            set {
                if (CurrentBlock != null)
                    EndBlock();
                if (value != null)
                    value.Start = template.Length;
                currentBlock = value;
            }
        }
    
        private class VSManager: Manager {
            private readonly EnvDTE.ProjectItem templateProjectItem;
            private readonly EnvDTE.DTE dte;
            private readonly Action<String> checkOutAction;
            private readonly Action<List<String>> projectSyncAction;
    
            public override String DefaultProjectNamespace {
                get {
                    return templateProjectItem.ContainingProject.Properties.Item("DefaultNamespace").Value.ToString();
                }
            }
    
            public override String GetCustomToolNamespace(string fileName) {
                return dte.Solution.FindProjectItem(fileName).Properties.Item("CustomToolNamespace").Value.ToString();
            }
    
            public override void Process(bool split, bool sync) {
                if (templateProjectItem.ProjectItems == null)
                    return;
                base.Process(split, sync);
                if (sync)
                    projectSyncAction.EndInvoke(projectSyncAction.BeginInvoke(generatedFileNames, null, null));
            }
    
            protected override void CreateFile(String fileName, String content) {
                if (IsFileContentDifferent(fileName, content)) {
                    CheckoutFileIfRequired(fileName);
                    File.WriteAllText(fileName, content);
                }
            }
    
            internal VSManager(ITextTemplatingEngineHost host, StringBuilder template)
                : base(host, template) {
                var hostServiceProvider = (IServiceProvider)host;
                if (hostServiceProvider == null)
                    throw new ArgumentNullException("Could not obtain IServiceProvider");
                dte = (EnvDTE.DTE) hostServiceProvider.GetService(typeof(EnvDTE.DTE));
                if (dte == null)
                    throw new ArgumentNullException("Could not obtain DTE from host");
                templateProjectItem = dte.Solution.FindProjectItem(host.TemplateFile);
                checkOutAction = fileName => dte.SourceControl.CheckOutItem(fileName);
                projectSyncAction = keepFileNames => ProjectSync(templateProjectItem, keepFileNames);
            }
    
            private static void ProjectSync(EnvDTE.ProjectItem templateProjectItem, List<String> keepFileNames) {
                var keepFileNameSet = new HashSet<String>(keepFileNames);
                var projectFiles = new Dictionary<String, EnvDTE.ProjectItem>();
                var originalFilePrefix = Path.GetFileNameWithoutExtension(templateProjectItem.FileNames[0]) + ".";
                foreach (EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems)
                    projectFiles.Add(projectItem.FileNames[0], projectItem);
    
                // Remove unused items from the project
                foreach (var pair in projectFiles)
                    if (!keepFileNames.Contains(pair.Key) && !(Path.GetFileNameWithoutExtension(pair.Key) + ".").StartsWith(originalFilePrefix))
                        pair.Value.Delete();
    
                // Add missing files to the project
                foreach(String fileName in keepFileNameSet)
                    if (!projectFiles.ContainsKey(fileName))
                        templateProjectItem.ProjectItems.AddFromFile(fileName);
            }
    
            private void CheckoutFileIfRequired(String fileName) {
                var sc = dte.SourceControl;
                if (sc != null && sc.IsItemUnderSCC(fileName) && !sc.IsItemCheckedOut(fileName))
                    checkOutAction.EndInvoke(checkOutAction.BeginInvoke(fileName, null, null));
            }
        }
    } #>

    用到的语法:

    1、string solutionsPath = Host.ResolveAssemblyReference("$(SolutionDir)");

    T4模板中获取当前解决方案的文件路径。

    2、var files = System.IO.Directory.GetFiles(solutionsPath + @"JuCheap.Entity", "*.cs");

    获取JuCheap.Entity项目中所有cs扩展名的文件信息。

     3、var manager = Manager.Create(Host, GenerationEnvironment);

    在引用的ttInclude中,创建一个模板管理者的对象,用于操作和生成数据。

    4、 public partial interface I<#=name#>Service

    在<#  string name="ClassName" #> 中声明的变量,

    可以在文本块中以<#=name#>的形式调用。(name为声明的变量名称)

    5、 manager.EndBlock();

    在引用的ttInclude中,结束对新服务类的内容赋值,

    将文件对象添加到文件集合中。

    6、manager.Process(true); 

    在引用的ttInclude中,结束数据生成,编译运行,生成相应的文件。

    总结

    因为有CodeSmith的使用经验,所以在看到T4模板使用方法时,

    没有想象的那么困难。

    纸上得来终觉浅,绝知此事要躬行。

    目前只是看网上大佬的教程,还没有在项目中实际使用过,

    还有许多未知的问题和操作。

  • 相关阅读:
    mysql -- 字符串子串
    eclipse 快捷键
    eclipse git --- add to index
    eclipse -- git 同步
    jquery
    烂泥:KVM、kickstart与NFS集成
    烂泥:KVM与kickstart集成
    烂泥:kickstart无人值守安装CentOS6.5
    烂泥:【解决】Ubuntu下使用SSH连接centos系统很慢
    烂泥:CentOS安装及配置TFTP服务器
  • 原文地址:https://www.cnblogs.com/masonblog/p/9233178.html
Copyright © 2011-2022 走看看