三沣开发知识 购物 网址 游戏 小说 歌词 地图 快照 开发 股票 美女 新闻 笑话 | 汉字 软件 日历 阅读 下载 图书馆 编程 租车 短信 China
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
vbs/VBScript DOS/BAT hta htc python perl 游戏相关 VBA 远程脚本 ColdFusion ruby专题
autoit seraphzone PowerShell linux shell Lua Golang Erlang 其它教程 CSS/HTML/Xhtml
html5 CSS XML/XSLT Dreamweaver教程 经验交流 开发者乐园 Android开发资料
站长资讯 .NET新手 ASP.NET C# WinForm Silverlight WCF CLR WPF XNA VisualStudio ASP.NET-MVC .NET控件开发 EntityFramework WinRT-Metro Java C++ PHP Delphi Python Ruby C语言 Erlang Go Swift Scala R语言 Verilog 其它语言 架构设计 面向对象 设计模式 领域驱动 Html-Css JavaScript jQuery HTML5 SharePoint GIS技术 SAP OracleERP DynamicsCRM K2 BPM 信息安全 企业信息 Android开发 iOS开发 WindowsPhone WindowsMobile 其他手机 敏捷开发 项目管理 软件工程 SQLServer Oracle MySQL NoSQL 其它数据库 Windows7 WindowsServer Linux
  IT知识库 -> C# -> 用T4 Template生成代码 -> 正文阅读
 

[C#]用T4 Template生成代码

用T4 Template生成代码 1   T4语法
T4的语法与ASP.NET的方式比较类似。主要包括指令、文本块、控制块。
1.1    指令
指令主要包括template, output, assembly, import, include等类型,用以告诉T4引擎如何编译和运行一个模板。这些指令相当于T4引擎的配置参数。
示例:

<#@ template debug="true" hostspecific="true" language="C#"  #>

告诉T4引擎控制块用c#编写;

<#@ output extension=".cs" #>

告诉T4引擎生成文件的后缀名是.cs;

<#@ assembly name="System.Core"#>

告诉T4引擎编译运行时引用System.Core程序集;

<#@ assembly name="$(SolutionDir)\Project.CodeGenerator\bin\Debug\MySql.Data.Dll"  #>

告诉T4引擎引用一个特定的位置上的程序集;

<#@ import namespace="System.Data.SqlClient"#>

告诉T4引擎编译运行时引用某个名称空间

<#@ include file="../Code/DBSchema.ttinclude"#>

告诉T4引擎编译运行时引用某个文件,类似于JS的引用
1.2    文本块
文本块, T4引擎会将文本块的内容直接复制到输出文件中。
1.3    控制块
控制块,主要用于控制文本的输出。在控制块可以写任意的C#代码。
1.4    示例Hello world

<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ output extension=".txt" #>

Hello, <#Write("World");#>

2   工作原理
转载自:http://www.olegsych.com/2007/12/text-template-transformation-toolkit/

1>     Step1:编译模板,根据指令编译模板中的文本块和控制块,并生成一个继承于TextTransformation的类。
2>     Step2: T4引擎动态创建类的实例,并调用TransformText方法。
3  在T4中读取表结构
我们用T4时,主要是基于数据库或配置文件来生成各类的代码。所以如何有效地获取数据库的结构信息,是比较重要的。 之前看很多人直接把获取数据库的信息放在每一个模板中,在更换其它数据库时,又要重写模板。一个模板同时支持多个项目时,不同的项目数据库很有可能是不同的。
主要设计思想如下,简单地应用了简单工厂模式。通过DBSchemaFactory类根据不同数据库类型,获取数据库访问类的接口IDBSchema。最后返回相同的表结构信息。

DBSchema.ttinclude类:
根据不同的数据库,获取表结构信息。已包括SQLSERVER和MYSQL。


<#@ assembly name="System.Core"#>
<#@ assembly name="System.Data" #>
<#@ assembly name="System.xml" #>
<#@ assembly name="$(SolutionDir)\bin\Debug\MySql.Data.Dll"  #>
<#@ import namespace="System"#>
<#@ import namespace="System.Collections.Generic"#>
<#@ import namespace="System.Data"#>
<#@ import namespace="System.Data.SqlClient"#>
<#@ import namespace="MySql.Data.MySqlClient"#>
<#+ 
#region Code
    public class DBSchemaFactory
    {
        static readonly string DatabaseType = "SqlServer";
        public static IDBSchema GetDBSchema()
        {
            IDBSchema dbSchema;
            switch (DatabaseType) 
            {
                case "SqlServer":
                    {
                        dbSchema =new SqlServerSchema();
                        break;
                    }
                case "MySql":
                    {
                        dbSchema = new MySqlSchema();
                        break;
                    }
                default: 
                    {
                        throw new ArgumentException("The input argument of DatabaseType is invalid!");
                    }
            }
            return dbSchema;
        }
    }

    public interface IDBSchema : IDisposable
    {
        List<string> GetTablesList();

        Table GetTableMetadata(string tableName);
    }

    public class SqlServerSchema : IDBSchema
    {
        public string ConnectionString = "Data Source=.;Initial Catalog=ProjectData;Persist Security Info=True;User ID=sa;Password=123456;";

        public SqlConnection conn;

        public SqlServerSchema()
        {
            conn = new SqlConnection(ConnectionString);
            conn.Open();
        }

        public List<string> GetTablesList()
        {
            DataTable dt = conn.GetSchema("Tables");
            List<string> list = new List<string>();
            foreach (DataRow row in dt.Rows)
            {
                list.Add(row["TABLE_NAME"].ToString());
            }
            return list;
        }

        public Table GetTableMetadata(string tableName)
        {
            string selectCmdText = string.Format("SELECT * FROM {0}", tableName); ;
            SqlCommand command = new SqlCommand(selectCmdText, conn);
            SqlDataAdapter ad = new SqlDataAdapter(command);
            System.Data.DataSet ds = new DataSet();
            ad.FillSchema(ds, SchemaType.Mapped, tableName);

            Table table = new Table(ds.Tables[0]);
            return table;
        }

        public void Dispose()
        {
            if (conn != null)
                conn.Close();
        }
    }

    public class MySqlSchema : IDBSchema
    {
        public string ConnectionString = "Server=localhost;Port=3306;Database=ProjectData;Uid=root;Pwd=;";

        public MySqlConnection conn;

        public MySqlSchema()
        {
            conn = new MySqlConnection(ConnectionString);
            conn.Open();
        }

        public List<string> GetTablesList()
        {
            DataTable dt = conn.GetSchema("Tables");
            List<string> list = new List<string>();
            foreach (DataRow row in dt.Rows)
            {
                list.Add(row["TABLE_NAME"].ToString());
            }
            return list;
        }

        public Table GetTableMetadata(string tableName)
        {
            string selectCmdText = string.Format("SELECT * FROM {0}", tableName); ;
            MySqlCommand command = new MySqlCommand(selectCmdText, conn);
            MySqlDataAdapter ad = new MySqlDataAdapter(command);
            System.Data.DataSet ds = new DataSet();
            ad.FillSchema(ds, SchemaType.Mapped, tableName);

            Table table = new Table(ds.Tables[0]);
            return table;
        }

        public void Dispose()
        {
            if (conn != null)
                conn.Close();
        }
    }

    public class Table
    {
        public Table(DataTable t)
        {
            this.PKs = this.GetPKList(t);
            this.Columns = this.GetColumnList(t);
            this.ColumnTypeNames = this.SetColumnNames();
        }

        public List<Column> PKs;

        public List<Column> Columns;

        public string ColumnTypeNames;
        public List<Column> GetPKList(DataTable dt)
        {
            List<Column> list = new List<Column>();
            Column c = null;
            if (dt.PrimaryKey.Length > 0)
            {
                list = new List<Column>();
                foreach (DataColumn dc in dt.PrimaryKey)
                {
                    c = new Column(dc);
                    list.Add(c);
                }
            }
            return list;
        }

        private List<Column> GetColumnList(DataTable dt)
        {
            List<Column> list = new List<Column>();
            Column c = null;
            foreach (DataColumn dc in dt.Columns)
            {
                c = new Column(dc);
                list.Add(c);
            }
            return list;
        }

        private string SetColumnNames()
        {
            List<string> list = new List<string>();
            foreach (Column c in this.Columns)
            {
                list.Add(string.Format("{0} {1}", c.TypeName, c.LowerColumnName));
            }
            return string.Join(",", list.ToArray());
        }
    }

    public class Column
    {
        DataColumn columnBase;

        public Column(DataColumn columnBase)
        {
            this.columnBase = columnBase;
        }

        public string ColumnName { get { return this.columnBase.ColumnName; } }

        public string MaxLength { get { return this.columnBase.MaxLength.ToString(); } }

        public string TypeName { 
            get 
            {
                string result = string.Empty;
                if (this.columnBase.DataType.Name == "Guid")//for mysql,因为对于MYSQL如果是CHAR(36),类型自动为Guid
                    result = "String";
                else
                    result = this.columnBase.DataType.Name;
                return result; 
            } 
        }

        public bool AllowDBNull { get { return this.columnBase.AllowDBNull; } }

        public string UpColumnName
        {
            get
            {
                return string.Format("{0}{1}", this.ColumnName[0].ToString().ToUpper(), this.ColumnName.Substring(1));
            }
        }

        public string LowerColumnName
        {
            get
            {
                return string.Format("{0}{1}", this.ColumnName[0].ToString().ToLower(), this.ColumnName.Substring(1));
            }
        }
    }

    public class GeneratorHelper
    {
        public static readonly string StringType = "String";
        public static readonly string DateTimeType = "DateTime";
        public static string GetQuesMarkByType(string typeName)
        {
            string result = typeName;
            if (typeName == DateTimeType)
            {
                result += "?";
            }
            return result;
        }
    }

    #endregion
#>

View Code
数据库结构的测试模板02 DBSchema.tt
输出数据库的所有表的结构信息


<#@ template debug="true" hostspecific="true" language="C#"  #>
<#@ output extension=".txt" #>
<#@ assembly name="System.Core"#>

<#@ import namespace="System"#>
<#@ import namespace="System.Collections.Generic"#>


<#@ include file="../Code/DBSchema.ttinclude"#>
<#
    var dbSchema=DBSchemaFactory.GetDBSchema();
    List<string> tableList=dbSchema.GetTablesList();
    foreach(string tableName in tableList)
    {
#>
<#= tableName #>
<#
        Table table=dbSchema.GetTableMetadata(tableName);
        foreach(Column c in table.PKs)
        {
#>
<#= c.ColumnName#>
<#        }
#>
ColumnName,TypeName,MaxLength,UpColumnName,LowerColumnName
<#
        foreach(Column c in table.Columns)
        {
#>
<#=c.ColumnName#>,<#=c.TypeName#>,<#=c.MaxLength#>,<#=c.UpColumnName#>,<#=c.LowerColumnName#>
<#
        }
#>
<#    
    }
    dbSchema.Dispose();
#>

View Code
注:
1> 在DBSchema.ttinclude,所有的类都被包含在<#+ #>中,<#+ #>是一个类功能的控制块,其中可以定义任意的C#代码。这些类多是帮助类,所以又定义在一个可复用的模板中”.ttinclude”.
2> 在02 DBSchema.tt中有一行” <#@ include file="../Code/DBSchema.ttinclude"#>“,是指引用某个位置的文件,在这里指是引用一个可复用的模板。
4用T4生成实体
用T4生成一个代码的一个常用应用是生成实体类,下面是一个示例代码(此模板引用了DBSchema.ttinclude):


<#@ template debug="true" hostspecific="true" language="C#"  #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core"#>
<#@ import namespace="System"#>
<#@ import namespace="System.Collections.Generic"#>

<#@ include file="../Code/DBSchema.ttinclude"#>
<#
    var dbSchema=DBSchemaFactory.GetDBSchema();
    List<string> tableList=dbSchema.GetTablesList();
    foreach(string tableName in tableList)
    {
        Table table=dbSchema.GetTableMetadata(tableName);
#>
using System;
using System.Collections.Generic;
using System.Text;

namespace Project.Model
{
    [Serializable]
    public class <#=tableName#>
    {
        #region Constructor
        public <#=tableName#>() { }

        public <#=tableName#>(<#=table.ColumnTypeNames#>)
        {
<#
        foreach(Column c in table.Columns)
        {
#>
            this.<#=c.LowerColumnName#> = <#=c.LowerColumnName#>;
<#
        }
#>
        }
        #endregion

        #region Attributes
<#
        foreach(Column c in table.Columns)
        {
#>
        private <#=GeneratorHelper.GetQuesMarkByType(c.TypeName)#> <#=c.LowerColumnName#>;

        public <#=GeneratorHelper.GetQuesMarkByType(c.TypeName)#> <#=c.UpColumnName#>
        {
            get { return <#=c.LowerColumnName#>; }
            set { <#=c.LowerColumnName#> = value; }
        }
<#
        }
#>
        #endregion

        #region Validator
        public List<string> ErrorList = new List<string>();
        private bool Validator()
        {    
            bool validatorResult = true;
<#
        foreach(Column c in table.Columns)
        {
            if(!c.AllowDBNull)
            {
                if(c.TypeName==GeneratorHelper.StringType)
                {
#>
            if (string.IsNullOrEmpty(this.<#=c.UpColumnName#>))
            {
                validatorResult = false;
                this.ErrorList.Add("The <#=c.UpColumnName#> should not be empty!");
            }
<#
                }
                if(c.TypeName==GeneratorHelper.DateTimeType)
                {
#>
            if (this.<#=c.UpColumnName#>==null)
            {
                validatorResult = false;
                this.ErrorList.Add("The <#=c.UpColumnName#> should not be empty!");
            }
<#
                }
            }
            if(c.TypeName==GeneratorHelper.StringType)
            {
#>
            if (this.<#=c.UpColumnName#> != null && <#=c.MaxLength#> < this.<#=c.UpColumnName#>.Length)
            {
                validatorResult = false;
                this.ErrorList.Add("The length of <#=c.UpColumnName#> should not be greater then <#=c.MaxLength#>!");
            }
<#
            }
        }
#>
            return validatorResult;
        }    
        #endregion
    }
}
<#
    }
    dbSchema.Dispose();
#>

View Code
注:
1>     在这个模板中,<#= #>为表达式控制块
2>     除了表达式控制块,其它的代码块的开始<#和结束符#>最好是放在行首,这样一来容易分辨,二来最终输出的文本也是你想要的,不然文本块会乱掉。
5生成多个实体并分隔成多个文件
对于同时生成多个文件的模板可以直接下面的一个帮助类,这个帮助类可以帮助我们将一个文件分隔成多个文件(网上找的)。
分隔文件的帮助类:


<#@ assembly name="System.Core"#>

<#@ assembly name="EnvDTE"#>

<#@ import namespace="System.Collections.Generic"#>

<#@ import namespace="System.IO"#>

<#@ import namespace="System.Text"#>

<#@ import namespace="Microsoft.VisualStudio.TextTemplating"#>

<#+

// T4 Template Block manager for handling multiple file outputs more easily.
// Copyright (c) Microsoft Corporation.  All rights reserved.
// This source code is made available under the terms of the Microsoft Public License (MS-PL)

// Manager class records the various blocks so it can split them up
class Manager
{
    public struct Block {
        public String Name;
        public int Start, Length;
    }

    public List<Block> blocks = new List<Block>();
    public Block currentBlock;
    public Block footerBlock = new Block();
    public Block headerBlock = new Block();
    public ITextTemplatingEngineHost host;
    public ManagementStrategy strategy;
    public StringBuilder template;
    public String OutputPath { get; set; }

    public Manager(ITextTemplatingEngineHost host, StringBuilder template, bool commonHeader) {
        this.host = host;
        this.template = template;
        OutputPath = String.Empty;
        strategy = ManagementStrategy.Create(host);
    }

    public void StartBlock(String name) {
        currentBlock = new Block { Name = name, Start = template.Length };
    }

    public void StartFooter() {
        footerBlock.Start = template.Length;
    }

    public void EndFooter() {
        footerBlock.Length = template.Length - footerBlock.Start;
    }

    public void StartHeader() {
        headerBlock.Start = template.Length;
    }

    public void EndHeader() {
        headerBlock.Length = template.Length - headerBlock.Start;
    }    

    public void EndBlock() {
        currentBlock.Length = template.Length - currentBlock.Start;
        blocks.Add(currentBlock);
    }

    public void Process(bool split) {
        String header = template.ToString(headerBlock.Start, headerBlock.Length);
        String footer = template.ToString(footerBlock.Start, footerBlock.Length);
        blocks.Reverse();
        foreach(Block block in blocks) {
            String fileName = Path.Combine(OutputPath, block.Name);
            if (split) {
                String content = header + template.ToString(block.Start, block.Length) + footer;
                strategy.CreateFile(fileName, content);
                template.Remove(block.Start, block.Length);
            } else {
                strategy.DeleteFile(fileName);
            }
        }
    }
}

class ManagementStrategy
{
    internal static ManagementStrategy Create(ITextTemplatingEngineHost host) {
        return (host is IServiceProvider) ? new VSManagementStrategy(host) : new ManagementStrategy(host);
    }

    internal ManagementStrategy(ITextTemplatingEngineHost host) { }

    internal virtual void CreateFile(String fileName, String content) {
        File.WriteAllText(fileName, content);
    }

    internal virtual void DeleteFile(String fileName) {
        if (File.Exists(fileName))
            File.Delete(fileName);
    }
}

class VSManagementStrategy : ManagementStrategy
{
    private EnvDTE.ProjectItem templateProjectItem;

    internal VSManagementStrategy(ITextTemplatingEngineHost host) : base(host) {
        IServiceProvider hostServiceProvider = (IServiceProvider)host;
        if (hostServiceProvider == null)
            throw new ArgumentNullException("Could not obtain hostServiceProvider");

        EnvDTE.DTE 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);
    }

    internal override void CreateFile(String fileName, String content) {
        base.CreateFile(fileName, content);
        ((EventHandler)delegate { templateProjectItem.ProjectItems.AddFromFile(fileName); }).BeginInvoke(null, null, null, null);
    }

    internal override void DeleteFile(String fileName) {
        ((EventHandler)delegate { FindAndDeleteFile(fileName); }).BeginInvoke(null, null, null, null);
    }

    private void FindAndDeleteFile(String fileName) {
        foreach(EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems) {
            if (projectItem.get_FileNames(0) == fileName) {
                projectItem.Delete();
                return;
            }
        }
    }
}#>

View Code
示例模板:
生成某个数据库下面所有的表的实体,并放在不同的文件里。


<#@ template debug="true" hostspecific="true" language="C#"  #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core"#>
<#@ import namespace="System"#>
<#@ import namespace="System.Collections.Generic"#>
<#@ include file="../Code/DBSchema.ttinclude"#>
<#@ include file="../Code/MultiDocument.ttinclude"#>
<# var manager = new Manager(Host, GenerationEnvironment, true) { OutputPath = Path.GetDirectoryName(Host.TemplateFile)}; #>
<#
    var dbSchema=DBSchemaFactory.GetDBSchema();
    List<string> tableList=dbSchema.GetTablesList();
    foreach(string tableName in tableList)
    {
        manager.StartBlock(tableName+".cs");
        Table table=dbSchema.GetTableMetadata(tableName);
#>
using System;
using System.Collections.Generic;
using System.Text;

namespace Project.Model
{
    [Serializable]
    public class <#=tableName#>
    {
        #region Constructor
        public <#=tableName#>() { }

        public <#=tableName#>(<#=table.ColumnTypeNames#>)
        {
<#
        foreach(Column c in table.Columns)
        {
#>
            this.<#=c.LowerColumnName#> = <#=c.LowerColumnName#>;
<#
        }
#>
        }
        #endregion

        #region Attributes
<#
        foreach(Column c in table.Columns)
        {
#>
        private <#=GeneratorHelper.GetQuesMarkByType(c.TypeName)#> <#=c.LowerColumnName#>;

        public <#=GeneratorHelper.GetQuesMarkByType(c.TypeName)#> <#=c.UpColumnName#>
        {
            get { return <#=c.LowerColumnName#>; }
            set { <#=c.LowerColumnName#> = value; }
        }
<#
        }
#>
        #endregion

        #region Validator
        public List<string> ErrorList = new List<string>();
        private bool Validator()
        {    
            bool validatorResult = true;
<#
        foreach(Column c in table.Columns)
        {
            if(!c.AllowDBNull)
            {
                if(c.TypeName==GeneratorHelper.StringType)
                {
#>
            if (string.IsNullOrEmpty(this.<#=c.UpColumnName#>))
            {
                validatorResult = false;
                this.ErrorList.Add("The <#=c.UpColumnName#> should not be empty!");
            }
<#
                }
                if(c.TypeName==GeneratorHelper.DateTimeType)
                {
#>
            if (this.<#=c.UpColumnName#>==null)
            {
                validatorResult = false;
                this.ErrorList.Add("The <#=c.UpColumnName#> should not be empty!");
            }
<#
                }
            }
            if(c.TypeName==GeneratorHelper.StringType)
            {
#>
            if (this.<#=c.UpColumnName#> != null && <#=c.MaxLength#> < this.<#=c.UpColumnName#>.Length)
            {
                validatorResult = false;
                this.ErrorList.Add("The length of <#=c.UpColumnName#> should not be greater then <#=c.MaxLength#>!");
            }
<#
            }
        }
#>
            return validatorResult;
        }    
        #endregion
    }
}
<#
        manager.EndBlock();
    }
    dbSchema.Dispose();

    manager.Process(true);
#>

View Code
 6 其它
T4的编辑工具下载地址http://t4-editor.tangible-engineering.com/Download_T4Editor_Plus_ModelingTools.html
VS默认的编辑工具无高亮,无提示,错误不易定位。 没这个工具,真心不想写任何T4代码。
所有示例代码: CodeGenerator.zip
  C# 最新文章
字符串阵列分别输出元素的索引,原值和长度
格式化你的字符串
C#宣告一个变量
C#中级
拆分一个字符串并把每个字符单独输出
通过手机号定位归属地
使类的扩展更简单——扩展方法
C#全屏随机位置显示图片的小程序
今天才知道 #还可以这样用
C#多线程学习(一) 多线程的相关概念
上一篇文章      下一篇文章      查看所有文章
加:2015-03-29 22:43:36  更:2017-05-14 17:20:21 
 
技术频道: 站长资讯 .NET新手区 ASP.NET C# WinForm Silverlight WCF CLR WPF XNA Visual Studio ASP.NET MVC .NET控件开发 Entity Framework WinRT/Metro Java C++ PHP Delphi Python Ruby C语言 Erlang Go Swift Scala R语言 Verilog 其它语言 架构设计 面向对象 设计模式 领域驱动设计 Html/Css JavaScript jQuery HTML5 SharePoint GIS技术 SAP Oracle ERP Dynamics CRM K2 BPM 信息安全 企业信息化其他 Android开发 iOS开发 Windows Phone Windows Mobile 其他手机开发 敏捷开发 项目与团队管理 软件工程其他 SQL Server Oracle MySQL NoSQL 其它数据库 Windows 7 Windows Server Linux
脚本语言: vbs/VBScript DOS/BAT hta htc python perl 游戏相关 VBA 远程脚本 ColdFusion ruby专题 autoit seraphzone PowerShell linux shell Lua Golang Erlang 其它教程
网站开发: CSS/HTML/Xhtml html5 CSS XML/XSLT Dreamweaver教程 经验交流 开发者乐园 Android开发资料
360图书馆 软件开发资料 文字转语音 购物精选 软件下载 美食菜谱 新闻资讯 电影视频 小游戏 Chinese Culture 股票 租车
生肖星座 三丰软件 视频 开发 短信 中国文化 网文精选 搜图网 美图 阅读网 多播 租车 短信 看图 日历 万年历 2018年7日历
2018-7-17 11:48:07
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT知识库