前言
我们每个猿都有一个搭建自己独立博客的梦,我也不例外。以前想 现在想 以后也想。之所以一直迟迟没有着手,是因为难以跨出第一步。每次心里想着,等我以后技术好了再说,然后就没有然后了。以前用过wordpress,虽然插件很多,不过有时候想改改自己想要的效果很难,因为 我压根就不会php。也看过.net的一些开源博客,代码量多,看得头晕,没那个耐心。
再说,别人的始终是别人的。得鱼不如得渔。与其花时间去研究php还不如自己写个.net版的。有人说博客园已经很好了啊,我承认确实,而且还可以后台定制自己想要的样式和js。不过始终还是不如自己开发的来得随心所欲。最重要自己开发还可以当作练手 对一个网站的各环节 做一次练习,用以发现自己的不足,并加以提升。那我以后的博客写哪里呢?当然还是会继续发博客园,谁叫这里人气旺呢。
这次开发的博客主要功能或特点:
第一:可以兼容各终端,特别是手机端。
第二:到时会用到大量html5,炫啊。
第三:导入博客园的精华文章,并做分类。(不要封我)
第四:做个插件,任何网站上的技术文章都可以转发收藏 到本博客。
所以打算写个系类:《一步步搭建自己的博客》
一步步开发自己的博客 .NET版(1、页面布局、blog迁移、数据加载)
一步步开发自己的博客 .NET版(2、评论功能)
一步步开发自己的博客 .NET版(3、注册登录功能)
一步步开发自己的博客 .NET版(4、文章发布功能)
一步步开发自己的博客 .NET版(5、搜索功能)
一步步开发自己的博客 .NET版(6、手机端的兼容)
关于域名和空间
在以前我们学C#的想要搭一个免费的博客,要不只能用国外的免费空间要么在linux下用php。用起来都是各种坑,网速各种卡。然后,现在我们学C#的时代来了,这里要感谢阿里云(免费主机)。当然域名还是要自己买的。万网 和 新网 可以对比下 哪里便宜买哪里,都可以用。(注意:最好买 .com .net .cc .org 因为有些域名不能在阿里备案)。买好域名之后 然后就是备案了,备案也没什么复杂的,阿里自动备案。中间除了 找阿里 要一块免费的布 照个相 寄过去,就是等了,其他的什么也不用做。大概半个月的样子吧。建议 买域名的时候最好一次性买久一点,不然后期再续费要比第一次买贵。(如果您实在不想花这个钱,这个我最后给您支个招吧,你申请好免费的主机后,你把主机ip给我,我免费给你二级域名。谁叫我是活雷锋)
开发环境
域名和主机都搞定以后,就开始选择开发环境了。我选的是 vs2013 mvc4 ef6.0 mssql 。
博客迁移
然后就是博客迁移,之前也老想着搭建博客,可一直没有行动。这就是从0到1 的难。只要你走出了第一步 后面就 顺畅得多了。那么 我们搭博客 没有测试数据 总还是感觉没什么动力。所以,我就写了个程序,把我在博客园发表的文章扒过去。
那么我们需要哪些数据呢?现在大概想到的有:博客正文、tag标签、文章分类、创建时间、博客标题
好了,那我们就正式开始扒吧。(可以参考我以前的博客备份小工具3)
首先是从/mvc/blog/sidecolumn.aspx页面取得 文章分类。然后根据 每个类型 的链接 取得这个类型下的所有文章。然后在取正文的时候发现 文章所属tag标签和分类是异步的到页面的, http://www.cnblogs.com/mvc/blog/CategoriesTags.aspx?blogApp=用户名&postId=文章id。(也许博客园有api,我也没去看没去找。)
1.首先建一个实体数据模型
我这里采用的是 model first(之前搞错概念了,谢谢园友指正) 。这里要说明的是 tag标签和文章是多对多的关系,文章类型和文章也是多对多的关系。
2.然后根据模型生成数据库
个人觉得这里非常爽,自动帮我建好了 主外键 和索引,免除了我们自己手动去在数据库里面建。
3.从博客园扒数据
模型和数据库建好了,那么我们现在就开始迁移吧~下面是全部代码,其中有存数据库的部分可以自己改改。
/// <summary> /// 根据用户导入cnblog数据 /// </summary> /// <param name="userName"></param> /// <returns></returns> public string Import(string userName, string iszf = "false") { int blosNumber = 0; JavaScriptSerializer jss = new JavaScriptSerializer(); string url = "http://www.cnblogs.com/" + userName + @"/mvc/blog/sidecolumn.aspx"; HtmlAgilityPack.HtmlWeb htmlweb = new HtmlAgilityPack.HtmlWeb(); var docment = htmlweb.Load(url); string userid = GetCnblogUserId(userName); var liS = docment.DocumentNode.SelectNodes("//*[@id='sidebar_categories']/div[1]/ul/li"); foreach (var item in liS) { var tXPath = item.XPath; var href = item.SelectSingleNode(tXPath + "/a").Attributes["href"].Value; var blogtype = htmlweb.Load(href); //var entrylistItem = blogtype.DocumentNode.SelectNodes("//*[@id='mainContent']/div/div[2]/div[@class='entrylistItem']"); var entrylistItem = blogtype.DocumentNode.SelectNodes("//div[@class='entrylistItem']"); if (null == entrylistItem)//做兼容 entrylistItem = blogtype.DocumentNode.SelectNodes("//div[@class='post post-list-item']"); // if (null == entrylistItem) { continue; } foreach (var typeitem in entrylistItem) { var typeitemXPath = typeitem.XPath; var typeitemhrefObj = typeitem.SelectSingleNode(typeitemXPath + "/div/a"); if (null == typeitemhrefObj) //做兼容 typeitemhrefObj = typeitem.SelectSingleNode(typeitemXPath + "/h2/a"); var typeitemhref = typeitemhrefObj.Attributes["href"].Value; if (IsAreBlog(typeitemhref)) continue;//说明这篇文章已经备份过了的 var bloghtml = htmlweb.Load(typeitemhref); var blogcontextobj = bloghtml.DocumentNode.SelectSingleNode("//*[@id='cnblogs_post_body']");//.InnerHtml; if (blogcontextobj == null) continue;//有可能是加密文章 var blogcontext = blogcontextobj.InnerHtml; var blogtitle = bloghtml.DocumentNode.SelectSingleNode("//*[@id='cb_post_title_url']").InnerText; var blogurl = bloghtml.DocumentNode.SelectSingleNode("//*[@id='cb_post_title_url']").Attributes["href"].Value; var blogtypetagurl = "http://www.cnblogs.com/mvc/blog/CategoriesTags.aspx?blogApp=" + userName + "&blogId=" + userid + " =" + typeitemhref.Substring(typeitemhref.LastIndexOf('/') + 1, typeitemhref.LastIndexOf('.') - typeitemhref.LastIndexOf('/') - 1); var blogtag = Blogs.Common.Helper.MyHtmlHelper.GetRequest(blogtypetagurl); var jsonobj = jss.Deserialize<Dictionary<string, string>>(blogtag); if (null == jsonobj) continue;//如果没有 则返回 (这里只能去 数字.html 不能取那种自定义的url) var tagSplit = jsonobj["Tags"].Split(','); var blogtagid = new List<int>(); for (int i = 0; i < tagSplit.Length; i++) { if (tagSplit[i].Length >= 1 && tagSplit[i].LastIndexOf('<') >= 1) { var blogtagname = tagSplit[i].Substring(tagSplit[i].IndexOf('>') + 1, tagSplit[i].LastIndexOf('<') - tagSplit[i].IndexOf('>') - 1); blogtagid.Add(this.GetTagId(blogtagname, userName)); } } var categoriesSplit = jsonobj["Categories"].Split(','); var blogtypeid = new List<int>(); for (int i = 0; i < categoriesSplit.Length; i++) { if (categoriesSplit[i].Length >= 1 && categoriesSplit[i].LastIndexOf('<') >= 1) { var blogtypename = categoriesSplit[i].Substring(categoriesSplit[i].IndexOf('>') + 1, categoriesSplit[i].LastIndexOf('<') - categoriesSplit[i].IndexOf('>') - 1); blogtypeid.Add(this.GetTypeId(blogtypename, userName)); } } var blogtimeobj = bloghtml.DocumentNode.SelectSingleNode("//*[@id='post-date']"); var blogtime = ""; if (null != blogtimeobj) blogtime = blogtimeobj.InnerText; BlogsBLL blog = new BlogsBLL(); var myBlogTags = new BlogTagsBLL().GetList(t => blogtagid.Contains(t.Id), isAsNoTracking: false).ToList();//.ToList(); var myBlogTypes = new BLL.BlogTypesBLL().GetList(t => blogtypeid.Contains(t.Id), isAsNoTracking: false).ToList();//.ToList(); try { var modelMyBlogs = new ModelDB.Blogs() { BlogContent = blogcontext, BlogCreateTime = blogtime, BlogTitle = blogtitle, BlogUrl = blogurl, IsDel = false, BlogTags = myBlogTags, BlogTypes = myBlogTypes, UsersId = GetUserId(userName), BlogForUrl = blogurl, IsForwarding = iszf == "checked" }; blog.Add(modelMyBlogs); blog.save(); blosNumber++; } catch (Exception) { throw; } } } if (blosNumber > 0) return "成功导入" + blosNumber + "篇Blog"; return "ok"; } private int GetTagId(string tagname, string userName) { BlogTagsBLL blogtag = new BlogTagsBLL(); try { var blogtagmode = blogtag.GetList(t => t.TagName == tagname); if (blogtagmode.Count() >= 1) return blogtagmode.FirstOrDefault().Id; else { blogtag.Add(new ModelDB.BlogTags() { TagName = tagname, IsDel = false, UsersId = GetUserId(userName) }); blogtag.save(); return GetTagId(tagname, userName); } } catch (Exception) { return -1; } } private int GetTypeId(string typename, string userName) { BlogTypesBLL blogtype = new BlogTypesBLL(); var blogtagmode = blogtype.GetList(t => t.TypeName == typename); if (blogtagmode.Count() >= 1) return blogtagmode.FirstOrDefault().Id; else { blogtype.Add(new ModelDB.BlogTypes() { TypeName = typename, CreateTime = DateTime.Now, IsDel = false, UsersId = GetUserId(userName) }); blogtype.save(); return GetTypeId(typename, userName); } } /// <summary> /// 获取haojima用户id /// </summary> /// <param name="userName"></param> /// <returns></returns> private int GetUserId(string userName) { BlogUsersBLL user = new BlogUsersBLL(); var blogtagmode = user.GetList(t => t.UserName == userName); if (blogtagmode.Count() >= 1) return blogtagmode.FirstOrDefault().Id; else { user.Add(new ModelDB.BlogUsers() { UserName = userName, IsDel = false, UserPass = "admin", UserNickname = userName }); user.save(); return GetUserId(userName); } } /// <summary> /// 检查 这个 url地址 是否被添加过 /// </summary> /// <param name="url"></param> /// <returns></returns> private bool IsAreBlog(string url) { BLL.BlogsBLL blog = new BLL.BlogsBLL(); var blogs = blog.GetList(t => t.BlogUrl == url); return blogs.Count() >= 1; } /// <summary> /// 获取cnblog用户id /// </summary> /// <param name="url"></param> /// <returns></returns> private string GetCnblogUserId(string url) { HtmlAgilityPack.HtmlWeb htmlweb = new HtmlAgilityPack.HtmlWeb(); var docment = htmlweb.Load("http://www.cnblogs.com/" + url); var list = docment.DocumentNode.SelectNodes("//link[@rel='stylesheet']"); foreach (var item in list) { if (null != item.Attributes && item.Attributes.Contains("href")) { var href = item.Attributes["href"].Value; href = href.Substring(href.LastIndexOf("/") + 1, href.IndexOf(".") - href.LastIndexOf("/") - 1); int userid = -1; if (int.TryParse(href, out userid)) return userid.ToString(); } } return ""; }
View Code
页面布局
关于页面布局 ,怎样简单怎样来。我是分成了 头、尾、中间。中间二八分。这个不重要,现在暂时这么遭。以后再考虑 多终端的兼容。
数据加载
现在数据都已经迁移过来的,需要展示在我们自己搭建的博客,我想对于大家来书应该没什么难度。从学编程开始,老师就教我们 增删改查。只是美与丑的问题。
这里有一点要注意,因为正文内容保存到数据库的都是html代码,而我们要在首页展示文章列表只显示小部分内容,那怎么截取字符串呢?你不能保证刚好是在html标签结尾后截取啊。我这里用到了HtmlAgilityPack取InnerText 的属性。就像jqeruy中 .html() 和 .text()区别,如果截图断了html标签 显示 将会很混乱。
最后总结
这个博客我也才刚开始做,现有也仅仅只是实现了博客的展示功能,连分页都还没有去实现。所以本系列博客更新会比较慢。我也需要边做边学边更新,工作中还没用过MVC。
到最后等我做完了,我会放git上开源,到时候大家有兴趣的可以一起来完善和定制自己想要的效果。
说了这么多来张效果图吧。