以对象的方式来访问xml数据表(三)
2018-06-18 04:38:18来源:未知 阅读 ()
怎样以对象的方式来访问xml数据表?
在讲如何具体实现(二)中所说的专门用于访问xml文件的动态链接库之前,我们先来看看这个动态链接库具体要实现什么功能。
动态链接库IXmlDB.dll的功能:
1、对于不同的对象具有通用性。(简单地说就是在不修改内部代码的情况下,可以用不同的对象去映射不同的xml数据表)
由于数据保存在xml数据表里,所有数据都是以字符串的形式保存的,那么与之对应的对象里的属性就可以全部统一为string类型。
类与xml数据表映射的两个实例代码:
User类与其对应xml数据文件
class User { public string Id { get; set; } public string Name { get; set; } public string Password { get; set; } public string IsAdmin { get; set; } public string CreateTime { get; set; } }
<?xml version="1.0" encoding="utf-8"?> <Users> <User> <Id>1</Id> <Name>forcheng</Name> <Password>123456</Password> <IsAdmin>True</IsAdmin> <CreateTime>2016/01/14 16:08:00</CreateTime> </User> <User> <Id>2</Id> <Name>chuan</Name> <Password>123456</Password> <IsAdmin>False</IsAdmin> <CreateTime>2016/01/15 11:23:00</CreateTime> </User> </Users>
Task类与其对应xml数据文件
class Task { public string Id { get; set; } public string Name { get; set; } public string CreateTime { get; set; } public string Description { get; set; } public string StartTime { get; set; } public string EndTime { get; set; } }
<?xml version="1.0" encoding="utf-8"?> <Tasks> <Task> <Id>1</Id> <Name>完成wpf桌面应用程序设计</Name> <CreateTime>2016/01/14 16:08:00</CreateTime> <Description>高效快速</Description> <StartTime>2016/01/15 12:00:00</StartTime> <EndTime>2016/01/18 12:00:00</EndTime> </Task> <Task> <Id>2</Id> <Name>买牙膏</Name> <CreateTime>2016/01/15 16:08:00</CreateTime> <Description>不要忘记了</Description> <StartTime>2016/01/16 12:00:00</StartTime> <EndTime>2016/01/16 14:00:00</EndTime> </Task> </Tasks>
2、对象和xml数据(XElement)的相互转换。(首先,从xml数据文件里面加载数据,需要转化为对相应的TEntity对象(泛型),然后保存在List<TEntity>列表里面,以提供对数据的操作,其次,是将List<TEntity>里面的数据转化为对应的XElement数据,然后添加保存到xml数据文件中)
3、需要提供一个构造函数或公有方法用于连接指定的xml数据文件。(需要给定一些参数,比如说xml文件所在的路径,xml文件的访问节点的名称(如"User")和根节点的名称(如"Users"))
4、提供一些公有方法用于用户操作数据。(比如说:添加数据、删除数据、查询数据、更改数据——这里的数据是以一个一个的对象形式存在的)
5、一些私有方法。(主要用于实现对象与XElement数据的转换,以及其他一些对数据合法性的验证)
在了解了动态链接库具体要实现什么功能之后,简单的谈一谈实现某几个功能的难点。
第一个难点:对于不同的对象具有通用性。怎样使得这个动态链接库能够在不改变源代码的情况下去操作具有不同属性的类?本来最初我是打算使用动态编译类(需要使用程序集和反射的知识)来实现,但是后面发现使用动态编译类不能满足这个要求或即使实现了对性能的牺牲太大了。于是乎就舍弃了动态类这个想法。最终还是从Entity Framework的操作数据库接口DbSet那里获得的灵感,使用泛型类。泛型恰好满足了不同对象这一点要求。最终问题成功解决。
第二个难点:对象和xml数据(XElement)的相互转换。由于对象里面的属性名称是不确定的(至少对于动态链接库里面那个访问数据文件的对象来说)。这就造成了一个很大的问题:如何在一个已经封装好了的对象(也就是动态链接库里面的那个泛型类)里面的去操作一个未知属性的对象的属性?因为对象和XElement之间进行数据的传递,必须要对对象的属性进行操作。为了解决这个问题,我查了相应的资料,发现了反射能够成功解决这个问题。首先,实例化泛型类的时候,可以获取到TEntity对象的属性的名称(可以用一个数组临时保存),然后再通过泛型直接对TEntity对象的属性的值进行获取或赋值,这样就可以实现对象和xml数据的相互传递了。
最后,附上IXmlDB4.1.0.dll的源代码和一个简单的演示实例(以对象的方式来访问xml数据表)。
这是我经过反复修改和提高之后产生的(大家可以从它的版本号看出)。 感兴趣的可以深入研究一下源代码(其中有的地方用了取巧的方法^_^),若只是想用一下,则可以直接编译成.dll文件即可使用。
using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using System.IO; using System.Reflection; using System.Text.RegularExpressions; namespace IXmlDB { public class XmlDbSet<TEntity> where TEntity : class, new() { //构造函数 public XmlDbSet(string path = "XmlDb.xml", string nodeName = "Node", string rootName = "Root") { defaultProperty = "Id"; if (Connect(path, nodeName, rootName)) { //链接成功,载入数据 classList = new List<TEntity>(); foreach (var item in AllNodes) { classList.Add(XElementToClass(item)); } } else { throw new Exception("连接数据文件失败!"); } } #region 私有字段 private string xmlFilePath; private string[] xmlProperties; private string nodeName; private string defaultProperty; private XElement xmlRoot; private List<TEntity> classList; #endregion #region 私有属性 //获取新的Id private int NewId { get { if (classList.Count() > 0) { var lastNode = classList.Select(m => Convert.ToInt32(ReflectionGetProperty(m, defaultProperty))); return (lastNode.Max() + 1); } else { return 1; } } } //获取所有子节点 private IEnumerable<XElement> AllNodes { get { return xmlRoot.Elements(nodeName); } } #endregion #region 公有方法 //添加单个实例 public TEntity Add(TEntity entity) { if(ReflectionGetProperty(entity,defaultProperty) == "") { ReflectionSetProperty(entity, defaultProperty, NewId.ToString()); classList.Add(entity); } return entity; } //添加多个实例 public IEnumerable<TEntity> AddRange(IEnumerable<TEntity> entities) { int id = NewId; foreach (var entity in entities) { if (ReflectionGetProperty(entity, defaultProperty) == "") { ReflectionSetProperty(entity, defaultProperty, id.ToString()); classList.Add(entity); id++; } } return entities; } //确定序列中所有元素是否满足条件 public bool All(Func<TEntity, bool> predicate) { return classList.All(predicate); } //确定序列中是否包含任何元素 public bool Any() { return classList.Any(); } //确定序列中任何元素是否满足条件 public bool Any(Func<TEntity, bool> predicate) { return classList.Any(predicate); } //从序列中移除所有元素 public void Clear() { classList.Clear(); } //链接两个序列 public IEnumerable<TEntity> Concat(IEnumerable<TEntity> second) { return classList.Concat(second); } //获取序列中包含的元素数 public int Count() { return classList.Count; } //返回一个数字,表示在指定序列中满足条件的元素的数量 public int Count(Func<TEntity, bool> predicate) { return classList.Count(predicate); } //确定指定元素是否在序列中 public bool Contains(TEntity item) { return classList.Contains(item); } //返回序列中指定索引出的元素 public TEntity ElementAt(int index) { return classList.ElementAt(index); } //返回序列中指定索引出的元素,如果超出指定范围,则返回默认值(null) public TEntity ElementAtOrDefault(int index) { return classList.ElementAtOrDefault(index); } //确定序列中是否包含与指定谓词所定义的条件相匹配的元素 public bool Exists(Predicate<TEntity> match) { return classList.Exists(match); } //通过使用默认比较器对值进行比较生成两个序列的差集 public IEnumerable<TEntity> Equals(IEnumerable<TEntity> second) { return classList.Except(second); } //返回序列中满足指定条件的第一个元素;若序列中不包含元素,则返回默认值 public TEntity FirstOrDefault(Func<TEntity, bool> predicate) { return classList.FirstOrDefault(predicate); } //搜索与指定谓词相匹配的元素,并返回第一个匹配的元素 public TEntity Find(Predicate<TEntity> match) { return classList.Find(match); } //搜索与指定谓词相匹配的所有元素 public IEnumerable<TEntity> FindAll(Predicate<TEntity> match) { return classList.FindAll(match); } //搜索与指定谓词相匹配的元素,并返回第一个匹配的元素的从零开始的索引 public int FindIndex(Predicate<TEntity> match) { return classList.FindIndex(match); } //搜索与指定谓词相匹配的元素,并返回最后一个匹配的元素 public TEntity FindLast(Predicate<TEntity> match) { return classList.FindLast(match); } //搜索与指定谓词相匹配的元素,并返回最后一个一个匹配的元素的从零开始的索引 public int FindLastIndex(Predicate<TEntity> match) { return classList.FindLastIndex(match); } //返回序列中的第一个元素 public TEntity First() { return classList.First(); } //返回序列中满足指定条件的第一个元素 public TEntity First(Func<TEntity, bool> predicate) { return classList.First(predicate); } //返回序列中的第一个元素;若序列中不包含元素,则返回默认值 public TEntity FirstOrDefault() { return classList.FirstOrDefault(); } //对序列中的每一个元素执行指定操作 public void ForEach(Action<TEntity> action) { classList.ForEach(action); } //搜索指定对象,并返回序列中第一个匹配项的从零开始的索引 public int IndexOf(TEntity item) { return classList.IndexOf(item); } //将元素插入到序列中指定的索引处(索引从0开始) public void Insert(int index, TEntity item) { if (ReflectionGetProperty(item, defaultProperty) == "") { ReflectionSetProperty(item, defaultProperty, NewId.ToString()); } classList.Insert(index,item); } //将集合中的元素插入到序列中指定的索引处(索引从0开始) public void InsertRange(int index, IEnumerable<TEntity> collection) { int id = NewId; foreach (var item in collection) { if (ReflectionGetProperty(item, defaultProperty) == "") { ReflectionSetProperty(item, defaultProperty, id.ToString()); id++; } } classList.InsertRange(index, collection); } //返回序列中的最后一个元素 public TEntity Last() { return classList.Last(); } //返回序列中满足指定条件的最后一个元素 public TEntity Last(Func<TEntity, bool> predicate) { return classList.Last(predicate); } //搜索指定对象,并返回序列中第一个匹配项的从零开始的索引 public int LastIndexOf(TEntity item) { return classList.LastIndexOf(item); } //返回序列中的最后一个元素;若序列中不包含元素,则返回默认值 public TEntity LastOrDefault() { return classList.LastOrDefault(); } //调用泛型序列的每一个元素上的转换函数并返回最大结果值 public string Max(Func<TEntity,string> selector) { return classList.Max(selector); } //调用泛型序列的每一个元素上的转换函数并返回最小结果值 public string Min(Func<TEntity, string> selector) { return classList.Min(selector); } //根据键按升序对序列的元素排序 public void OrderBy(Func<TEntity, string> keySelector) { classList = classList.OrderBy(keySelector).ToList(); } //根据键按降序对序列的元素排序 public void OrderByDescending(Func<TEntity, string> keySelector) { classList = classList.OrderByDescending(keySelector).ToList(); } //将整个序列中元素顺序逆转 public void Reverse() { classList.Reverse(); } //从classLIst中移除特定对象的第一个匹配项 public TEntity Remove(TEntity entity) { classList.Remove(entity); return entity; } //从classLIst中移除一组对象的第一个匹配项 public IEnumerable<TEntity> RemoveRange(IEnumerable<TEntity> entities) { foreach(var entity in entities) { classList.Remove(entity); } return entities; } //删除指定谓词所定义的条件匹配的所有元素 public int RemoveAll(Predicate<TEntity> match) { return classList.RemoveAll(match); } //删除指定索引出的元素 public void RemoveAt(int index) { classList.RemoveAt(index); } //保存更改(返回值:表示重写以及删除的实例个数) public void SaveChanges() { xmlRoot.RemoveAll(); foreach (var item in classList) { xmlRoot.Add(ClassToXElement(item)); }; xmlRoot.Save(xmlFilePath); } //保存更改(返回值:表示重写的实例个数) public int SaveChanges(TEntity entity) { if(entity != null) { if(ClassModToXElement(entity)) { xmlRoot.Save(xmlFilePath); return 1; } else { return 0; } } else { return 0; } } //保存更改(返回值:表示重写实例个数) public int SaveChanges(IEnumerable<TEntity> entities) { if (entities != null) { int count = 0; foreach(var entity in entities) { if (ClassModToXElement(entity)) { count++; } } if(count > 0) { xmlRoot.Save(xmlFilePath); } return count; } else { return 0; } } //返回序列中唯一满足条件的元素;如果这类元素不存在,则返回默认值;如果存在多个元素满足条件,此方法将引发异常 public TEntity SingleOrDefault(Func<TEntity, bool> predicate) { return classList.SingleOrDefault(predicate); } //将序列中的每个元素投影到新表中 public IEnumerable<TResult> Select<TResult>(Func<TEntity, TResult> predicate) { return classList.Select(predicate); } //将序列中的每个元素投影到IEnumerable<out T> 并将结果序列合并为一个序列 public IEnumerable<TResult> SelectMany<TResult>(Func<TEntity, IEnumerable<TResult>> selector) { return classList.SelectMany(selector); } //跳过序列中指定数量的元素,然后返回剩余元素 public IEnumerable<TEntity> Skip(int index) { return classList.Skip(index); } //只要满足指定的条件,就跳过序列中的元素,然后返回剩余元素 public IEnumerable<TEntity> SkipWhile(Func<TEntity, bool> predicate) { return classList.SkipWhile(predicate); } //计算int值序列的和,这些值是通过对输入序列中的每一个元素调用转换函数得到的 public int Sum(Func<TEntity, int> selector) { return classList.Sum(selector); } public long Sum(Func<TEntity, long> selector) { return classList.Sum(selector); } public float Sum(Func<TEntity, float> selector) { return classList.Sum(selector); } public double Sum(Func<TEntity, double> selector) { return classList.Sum(selector); } public decimal Sum(Func<TEntity, decimal> selector) { return classList.Sum(selector); } //从序列的开头返回指定数量的连续元素 public IEnumerable<TEntity> Take(int index) { return classList.Take(index); } //只要满足指定条件,就会返回序列中的元素 public IEnumerable<TEntity> TakeWhile(Func<TEntity, bool> predicate) { return classList.TakeWhile(predicate); } //确定是否序列中每一个元素都与指定的谓词所定义的条件相匹配 public bool TrueForAll(Predicate<TEntity> match) { return classList.TrueForAll(match); } //通过使用默认的相等比较器生成两个序列的并集 public IEnumerable<TEntity> Union(IEnumerable<TEntity> second) { return classList.Union(second); } //基于谓此筛选值序列 public IEnumerable<TEntity> Where(Func<TEntity, bool> predicate) { return classList.Where(predicate); } #endregion #region 私有方法 //连接数据文件 private bool Connect(string path, string nodeName, string rootName) { try { //检查参数是否为null或为空字符串 if (string.IsNullOrWhiteSpace(path) || string.IsNullOrWhiteSpace(nodeName) || string.IsNullOrWhiteSpace(rootName)) { return false; } //匹配xml文件路径 if (path.IndexOf("\\") == -1) { path = Environment.CurrentDirectory + "\\" + path; } if (!Regex.IsMatch(path, @"^(?<fpath>([a-zA-Z]:\\)([\s\.\-\w]+\\)*)(?<fname>[\w]+.[\w]+)") || path.Length < 5 || path.Substring(path.Length - 4).ToLower() != ".xml") { return false; } //检查属性是否合法 TEntity objClass = new TEntity(); PropertyInfo[] infos = objClass.GetType().GetProperties(); if (infos.Length == 0 || infos.Count(m => m.Name == defaultProperty) == 0) { return false; } xmlProperties = new string[infos.Length]; int i = 0; foreach (var info in infos) { if (string.IsNullOrWhiteSpace(info.Name) || infos.Count(m => m.Name == info.Name) > 1) { return false; } else { xmlProperties[i] = info.Name; i++; } } this.nodeName = nodeName; xmlFilePath = path; //判断xml文件是否存在,若不存在则创建 if (path.LastIndexOf("\\") > 0) { path = path.Substring(0, path.LastIndexOf("\\")); } else { path = ""; } string quote = "\""; if (path != "" && !Directory.Exists(path)) { Directory.CreateDirectory(path); var xmlFile = new StreamWriter(xmlFilePath); xmlFile.WriteLine("<?xml version=" + quote + "1.0" + quote + " encoding=" + quote + "utf-8" + quote + "?>"); xmlFile.WriteLine("<" + rootName + ">"); xmlFile.WriteLine("</" + rootName + ">"); xmlFile.Close(); } else { if (!File.Exists(xmlFilePath)) { var xmlFile = new StreamWriter(xmlFilePath); xmlFile.WriteLine("<?xml version=" + quote + "1.0" + quote + " encoding=" + quote + "utf-8" + quote + "?>"); xmlFile.WriteLine("<" + rootName + ">"); xmlFile.WriteLine("</" + rootName + ">"); xmlFile.Close(); } } xmlRoot = XElement.Load(xmlFilePath);//载入数据文件 //自检数据文件 if (NodesPropertiesIsValid()) { return true; } else { throw new Exception("数据文件不完整或损坏!"); } } catch (Exception e) { throw e; } } //检查节点属性是否合法 private bool NodePropertiesIsValid(XElement targetNode) { try { if (targetNode.Name.ToString() != nodeName) { return false; } for (int i = 0; i < xmlProperties.Length; i++) { if (targetNode.Element(xmlProperties[i]) == null) { return false; } } return true; } catch { return false; } } //检查整个xml文件属性和Id是否合法(加载自检) private bool NodesPropertiesIsValid() { try { if(AllNodes.Count() == 0) { return true; } var strs = AllNodes.Select(m => m.Element(defaultProperty).Value).Distinct(); if (strs.Count() != AllNodes.Count() || AllNodes.Count(m => !NodePropertiesIsValid(m)) > 0) { return false; } else { return true; } } catch { return false; } } //将xml元素转化为对应对象(新实例) private TEntity XElementToClass(XElement targetNode) { if (targetNode == null) { return null; } else { TEntity objClass = new TEntity(); for (int i = 0; i < xmlProperties.Length; i++) { ReflectionSetProperty(objClass, xmlProperties[i], targetNode.Element(xmlProperties[i]).Value); } return objClass; } } //将对象转化为对应的xml元素新实例) private XElement ClassToXElement(TEntity objClass) { if (objClass == null) { return null; } else { XElement newNode = new XElement(nodeName); for (int i = 0; i < xmlProperties.Length; i++) { newNode.Add(new XElement(xmlProperties[i], ReflectionGetProperty(objClass, xmlProperties[i]))); } return newNode; } } //将对象的值传给对应的xml元素,或直接添加 //private void ClassSaveToXElement(TEntity objClass) //{ // string id = ReflectionGetProperty(objClass, defaultProperty); // var targetNode = AllNodes.SingleOrDefault(m => m.Element(defaultProperty).Value == id); // if (targetNode != null) // { // for (int i = 0; i < xmlProperties.Length; i++) // { // targetNode.Element(xmlProperties[i]).Value = ReflectionGetProperty(objClass, xmlProperties[i]); // } // } // else // { // xmlRoot.Add(ClassToXElement(objClass)); // } //} //将对象的值传给对应的xml元素 private bool ClassModToXElement(TEntity objClass) { string id = ReflectionGetProperty(objClass, defaultProperty); var targetNode = AllNodes.SingleOrDefault(m => m.Element(defaultProperty).Value == id); if (targetNode != null) { for (int i = 0; i < xmlProperties.Length; i++) { targetNode.Element(xmlProperties[i]).Value = ReflectionGetProperty(objClass, xmlProperties[i]); } return true; } else { return false; } } ////动态编译类 //private Assembly NewAssembly() //{ // //创建编译器实例。 // CSharpCodeProvider provider = new CSharpCodeProvider(); // //设置编译参数。 // CompilerParameters paras = new CompilerParameters(); // paras.GenerateExecutable = false; // paras.GenerateInMemory = true; // //创建动态代码。 // StringBuilder classSource = new StringBuilder(); // classSource.Append("public class DynamicClass \n"); // classSource.Append("{\n"); // //创建属性。 // for (int i = 0; i < xmlProperties.Length; i++) // { // classSource.Append(" public string " + xmlProperties[i] + " { get; set; } \n"); // } // classSource.Append("}"); // System.Diagnostics.Debug.WriteLine(classSource.ToString()); // //编译代码。 // CompilerResults result = provider.CompileAssemblyFromSource(paras, classSource.ToString()); // //获取编译后的程序集。 // Assembly assembly = result.CompiledAssembly; // return assembly; //} //反射设置动态类的实例对象的指定的属性值 private void ReflectionSetProperty(TEntity objClass, string propertyName, string value) { objClass.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance).SetValue(objClass, value ?? "", null); } //反射返回动态类的实例对象的指定的属性值 private string ReflectionGetProperty(TEntity objClass, string propertyName) { try { return objClass.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance).GetValue(objClass, null).ToString(); } catch { return ""; } } #endregion } }
实例演示代码——以对象的方式来访问xml数据表:
using System; using IXmlDB; namespace ConsoleApplication2 { //操作xml数据库的类 class DbSet { public XmlDbSet<User> Users { get; set; } = new XmlDbSet<User>("User.xml", "User", "Users");//连接User.xml数据文件,若不存在,会自动创建 public XmlDbSet<Task> Tasks { get; set; } = new XmlDbSet<Task>("Task.xml", "Task", "Tasks");//连接Task.xml数据文件 } //用户类 class User { public string Id { get; set; } //为了数据表的规范,Id属性是每个xml数据表的必须有的,并且它的值是动态链接库自动添加上去的(可以理解为具有自加性),用户不用给它赋值 public string Name { get; set; } public string Password { get; set; } public string IsAdmin { get; set; } public string CreateTime { get; set; } } //任务类 class Task { public string Id { get; set; } public string Name { get; set; } public string CreateTime { get; set; } public string Description { get; set; } public string StartTime { get; set; } public string EndTime { get; set; } } class Program { static void Main(string[] args) { DbSet entity = new DbSet();//创建访问数据库的实例 User u = new User(); u.Name = "forcheng"; u.Password = "123456"; u.IsAdmin = true.ToString(); u.CreateTime = DateTime.Now.ToString(); Task task = new Task(); task.Name = "买牙膏"; task.CreateTime = DateTime.Now.ToString(); task.Description = "不要忘记了"; task.StartTime = DateTime.Now.ToString(); task.EndTime = ""; //添加对象 entity.Users.Add(u); entity.Tasks.Add(task); //输出已添加对象的个数 Console.WriteLine(entity.Users.Count()); Console.WriteLine(entity.Tasks.Count()); var user = entity.Users.FirstOrDefault();//获取第一个 if(user != null) { Console.WriteLine(user.Name); Console.WriteLine(user.Password); Console.WriteLine(user.IsAdmin); Console.WriteLine(user.CreateTime); } entity.Users.RemoveAt(0);//移除第一个对象 var user1 = entity.Users.FirstOrDefault();//获取第一个 if (user1 != null) { Console.WriteLine(user1.Name); Console.WriteLine(user1.Password); Console.WriteLine(user1.IsAdmin); Console.WriteLine(user1.CreateTime); } entity.Users.SaveChanges();//把更改保存到xml文件中 entity.Tasks.SaveChanges();//把更改保存到xml文件中 Console.ReadKey(); } } }
欢迎大家借鉴学习,以对象的方式来访问xml数据表的专题到这里就结束了!!!
接下来,我可能会讲我比较喜爱的命令行与脚本专题系列,感兴趣的敬请期待哦。
<我的博客主页>:http://www.cnblogs.com/forcheng/
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- 通过与C++程序对比,彻底搞清楚JAVA的对象拷贝 2020-06-11
- Java笔记:集合 2020-06-10
- java修饰符的访问权限 2020-06-10
- Spring Boot 实现定时任务的 4 种方式 2020-06-10
- Java基础复习——类和对象 2020-06-09
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash