談一談CloudBlog的系統架構

---------------------------------------------------------------------------------------------
[版權申明:本文系做者原創,轉載請註明出處] 
文章出處: http://blog.csdn.net/sdksdk0/article/details/53634332
做者:朱培      ID:sdksdk0     

--------------------------------------------------------------------------------------------javascript

最近十幾天在作一個博客系統,由於域名服務器都閒置已久,因而乎決定合理利用起來,作個網站。系統總體架構採用分佈式的系統,也是當今不少企業都在用的,基於restful風格的一套系統。從父工程開始blog-parent.這是一個pom工程,主要用來放置pom.xml文件的,這個包含了整個項目全部依賴的jar包。而後是blog-common,這個存放項目中使用到的一些工具類,也是一個pom工程。而後是blog-manager工程,這個主要是後臺,包括用戶操做以及管理員操做,這個項目還有一個積分商城的功能,因此商城的後臺我也是放到這個manager工程裏面的。這個manager是一個pom工程,而後下面的mapper和pojo以及service和web都是一個maven  module.而後除了blog-manager-web是一個war以外,剩下的三個都是jar工程。html

而後前臺是blog-portal,還有就是rest、search、sso、order。rest實際上是給積分商城用的。search使用的是一個solr集羣。由於服務器性能緣由,因此我搭建的是三臺tomcat的solr集羣,依託zookeeper來進行管理。java

sso就是單點登陸系統,主要給整個系統提供登陸服務的。order系統主要是給積分商城提供訂單服務的。mysql




下面來講詳細內容:nginx

1、系統後臺:web

後臺是一個easyui的界面,很是簡約,寫文章的富文本編輯器我採用的是kindeditor。這個編輯器性能仍是不錯的,其實使用百度的ueditor富文本編輯器效果也不錯,只是由於個人springmvc攔截配置的config一直不成功,因而我就換了其餘的編輯器。數據提交採用的post提交。redis

if(title==null || title==''){
			alert("請輸入標題!");
		}else if(typeId==null || typeId==''){
			alert("請選擇博客類別!");
		}else if(blogTypeId==null || blogTypeId==''){
			alert("請選擇博客類別!");
		}else if(content==null || content==''){
			alert("請輸入內容!");
		}else{
			 $.post("/mg/user/blog/save",{'username':username,'title':title,'typeid':typeId,'blogtypeid':blogTypeId,'content':content,'summary':summary,'contentNoTag':contentNoTag,'keyword':keyWord},function(result){
				if(result.success){
					alert("博客發佈成功!");
					resetValue();
				}else{
					alert("博客發佈失敗!");
				}
			},"json"); 
		}


編輯器有一個sync的方法;來將textarea進行同步。


var content=itemAddEditor.html();
itemAddEditor.sync();


由於這裏是加入了lucene全文檢索功能,因此在添加或者修改文章的時候,都須要進行索引字段處理,由於富文本編輯器存到數據庫中的內容都是帶html標籤格式的,可是我檢索確定是不須要這些標籤的,因此使用下面的方法,來把這個html標籤去掉,放到contentNoTag字段,用於檢索。而content就是帶html標籤的須要存放在數據庫的內容。spring

var dd=content.replace(/<\/?.+?>/g,"");
		
var contentNoTag=dd.replace(/(^\s*)|(\s*$)/g,"");//dds爲獲得後的內容





而後就是一個列表的查詢。由於這是一個多用戶的系統,因此每一個用戶查看的都是本身的博客信息,因此在查詢的時候須要加上用戶名。sql


對於添加博客的時候須要的一個lucene操做。數據庫


/**//**
	 * 獲取IndexWriter實例
	 * @return
	 * @throws Exception
	 *//*
*/	private IndexWriter getWriter()throws Exception{
		dir=FSDirectory.open(Paths.get("/home/tf/work/data/lucene1/"));
		//dir=FSDirectory.open(Paths.get("c:\\lucene"));
		SmartChineseAnalyzer analyzer=new SmartChineseAnalyzer();
		IndexWriterConfig iwc=new IndexWriterConfig(analyzer);
		IndexWriter writer = null;
		try {
			writer = new IndexWriter(dir, iwc);
		} catch (Exception e) {
			writer.rollback();
			e.printStackTrace();
		}
		return writer;
	}
	
/*	*//**
	 * 添加博客索引
	 * @param blog
	 *//*
*/	public void addIndex(UBlog blog)throws Exception{
		IndexWriter writer=getWriter();
		Document doc=new Document();
		doc.add(new StringField("id",String.valueOf(blog.getBlogid()),Field.Store.YES));
		doc.add(new StringField("username",String.valueOf(blog.getUsername()),Field.Store.YES));
		doc.add(new TextField("title",blog.getTitle(),Field.Store.YES));
		doc.add(new StringField("releaseDate",DateUtil.formatDate(new Date(), "yyyy-MM-dd"),Field.Store.YES));
		doc.add(new TextField("content",blog.getContentNoTag(),Field.Store.YES));
		writer.addDocument(doc);
		writer.close();
	}




而管理員就能夠查看全部用戶的文章,以及能夠進行凍結解凍操做。

後臺管理員這裏還有一個積分商城,主要是用戶發表博客以後又積分,積分能夠兌換K幣,而後K幣能夠兌換這個積分商城中的東西。這個商品添加以後,是不能直接在前臺進行查詢的,由於我對於這個商品時啓用了solr搜索服務的,在個人blog-search工程中作了一個定時任務,在天天凌晨兩點進行數據導入操做,系統導入完成以後,就能夠在前臺查看到添加的這些商品了。


	/**
	 * 導入商品數據庫到索引庫
	 */
	@Scheduled(cron = "0 0 2 * * ?")   //天天凌晨兩點執行
	@RequestMapping("/import")
	public void importAllItems() {
		System.out.println("開始執行");
		itemService.importAllItems();
		System.out.println("執行結束");
	}







關於整個後臺來講,界面很是簡約,須要的功能仍是基本上齊全的。由於是分佈式系統的,因此我上線的時候都是分開上線的,在上線後臺以後,這個規格參數和商品列表查詢一個不能及時刷新$("#itemList").datagrid("reload");我開始覺得是數據量太大致使刷新慢,後來發現並非。例如我操做了刪除,其實數據庫中的數據已經刪除了,可是這個datagrid卻沒有反應,查看狀態碼返回的是304.而後我想到了我後臺是啓動了CDN緩存加速的,因此我就又跑去看CDN的配置,而後就發現問題了,因而我把列表查詢的/mg/item目錄的刷新時間設置爲0,這樣就能夠及時刷新,再也不是一直在緩存。這樣這個問題就解決了。


經過這個事情我知道,在本地的localhost操做和上線真的是不一樣的。在上線過程還遇到了不少在本地操做沒有發生的事情,在本地都沒有什麼問題,一到雲服務器上部署,立刻問題就來了。哎!真是個磨人的小妖精。

2、前臺

前臺的話基本上也就這樣了,由於審美水平問題,只能作到這個樣子了,畢竟沒有美工,畢竟我是作系統架構和數據處理數據分析的,哎,看來仍是有不足之處啊。你們就將就着看吧,哈哈。 http://www.tianfang1314.cn/


前臺的這個博客文章我都加入到了redis緩存中,因此訪問速度理論上仍是提高了的。前臺頁面在CDN緩存設置的是一分鐘,因此後臺增刪改什麼的理論上是要過一分鐘以後,前臺纔會更新的。






關於前臺的lucene搜索就是:

/**//**
	 * 查詢博客信息
	 * @param q 查詢關鍵字
	 * @return
	 * @throws Exception
	 *//*
*/	public List<UBlog> searchBlog(String q)throws Exception{
		
	     dir=FSDirectory.open(Paths.get("/home/tf/work/data/lucene1/"));
		IndexReader reader = DirectoryReader.open(dir);
		IndexSearcher is=new IndexSearcher(reader);
		BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
		SmartChineseAnalyzer analyzer=new SmartChineseAnalyzer();
		QueryParser parser=new QueryParser("title",analyzer);
		Query query=parser.parse(q);
		QueryParser parser2=new QueryParser("content",analyzer);
		Query query2=parser2.parse(q);
		

		
		booleanQuery.add(query,BooleanClause.Occur.SHOULD);
		booleanQuery.add(query2,BooleanClause.Occur.SHOULD);
		
		
		TopDocs hits=is.search(booleanQuery.build(), 100);
		QueryScorer scorer=new QueryScorer(query);  
		Fragmenter fragmenter = new SimpleSpanFragmenter(scorer);  
		SimpleHTMLFormatter simpleHTMLFormatter=new SimpleHTMLFormatter("<b><font color='red'>","</font></b>");
		Highlighter highlighter=new Highlighter(simpleHTMLFormatter, scorer);
		highlighter.setTextFragmenter(fragmenter);  
		List<UBlog> blogList=new LinkedList<UBlog>();
		for(ScoreDoc scoreDoc:hits.scoreDocs){
			Document doc=is.doc(scoreDoc.doc);
			UBlog blog=new UBlog();
			blog.setBlogid(doc.get(("id")));
			
			blog.setUsername(doc.get("username"));
			
			blog.setReleaseDateStr(doc.get(("releaseDate")));
			
			String title=doc.get("title");
			String content=StringEscapeUtils.escapeHtml(doc.get("content"));

			
			if(title!=null){
				TokenStream tokenStream = analyzer.tokenStream("title", new StringReader(title));
				String hTitle=highlighter.getBestFragment(tokenStream, title);
				if(StringUtil.isEmpty(hTitle)){
					blog.setTitle(title);
				}else{
					blog.setTitle(hTitle);					
				}
			}
			if(content!=null){
				TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(content)); 
				String hContent=highlighter.getBestFragment(tokenStream, content);
				if(StringUtil.isEmpty(hContent)){
					if(content.length()<=200){
						blog.setContent(content);
					}else{
						blog.setContent(content.substring(0, 200));						
					}
				}else{
					blog.setContent(hContent);					
				}
			}
			blogList.add(blog);
		}
		return blogList;
	}

效果仍是不錯的,你們能夠去試一下,文末提供訪問網址。說到這個文章啊,遇到最頭疼的問題就是編碼問題了,由於頁面展現是一種,而後我mysql數據庫的編碼,還有redis中存放的文章的編碼。添加緩存的時候又各類進行轉換。都快轉暈了,哈哈,固然最終仍是解決了,挺開心的。

blog.setContent(new String(blog.getContent().getBytes("iso-8859-1"),
				"utf-8"));
商品搜索這邊就是調用者blog-search的服務就能夠了。關於這個地方的細節我就再也不重複說了,今天只談架構。


3、Nginx

由於這個系統先後十二個工程,其中mg作爲後臺,portal作的前臺,以及前面說到的各類。因此我須要一個nginx來處理,主要就是配置端口與域名的映射。在nginx中進行配置便可。固然,也是能夠直接使用tomcat熱部署直接傳到服務器中的tomcat中。固然若是用的是其餘中間件服務器的話配置也是相似的。就是域名端口號說明的。(爲了我服務器的安全,下面配置的端口號我修改了與我真實上線不一樣的端口號了),你也能夠按本身的實際狀況配置。總之仍是很是實用和簡單的。


upstream manager.tianfang1314.cn{
		server  139.199.158.214:9100;
	}
	upstream rest.tianfang1314.cn{
		server  139.199.158.214:9101;
	}
	upstream search.tianfang1314.cn{
		server  139.199.158.214:9101;
	}
	upstream sso.tianfang1314.cn{
		server  139.199.158.214:9103;
	}

server {
        listen       80;
        server_name  manager.tianfang1314.cn;

        location / {
            proxy_pass   http://manager.tianfang1314.cn;
            index  index.html index.htm;
        }
	}
	server {
        listen       80;
        server_name  rest.tianfang1314.cn;

        location / {
            proxy_pass   http://rest.tianfang1314.cn;
            index  index.html index.htm;
        }
	}
	
	server {
        listen       80;
        server_name  search.tianfang1314.cn;

        location / {
            proxy_pass   http://search.tianfang1314.cn;
            index  index.html index.htm;
        }
	}
	
	server {
        listen       80;
        server_name  sso.tianfang1314.cn;

        location / {
            proxy_pass   http://sso.tianfang1314.cn;
            index  index.html index.htm;
        }
	}


4、開發中遇到的問題

我遇到最煩人的問題就是這個圖片上傳的問題,我原本是使用的FTP進行圖片上傳的。而後這個磨人的小妖精在本地上傳的時候沒有任何問題,我一部署到服務器,完了,徹底post不上去了,折騰的夠嗆,一直是什麼network什麼什麼的錯誤,簡直氣炸!我後來慢慢的排查問題,從文件大小限制,nginx配置,cdn緩存配置,服務器權限,還從朋友那裏在接了一臺tomcat來測試,發現依然是這個問題。折騰了一天。而後我就放棄治療。

Uncaught SecurityError: Failed to read the 'contentDocument' property from 'HTMLIFrameElement': Blocked a frame with origin "http://manager.tianfang1314.cn" from accessing a frame with origin "null".  The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.


固然,最後我換了一種方案,原本這個圖片是所有要存放到個人ftp圖片服務器中的。既然解決不了怎麼辦呢,這個圖片上傳的功能是必需要的啊,因而乎,我忽然發現了COS對象存儲服務。簡直髮現寶有木有。開始看文檔感受挺複雜的,後來本身折騰了一下,將這個cos的官方代碼於個人ftp工具類的代碼進行了整合,哎呦喂,竟然成功了,1個小時就搞定了。在這一篇博客中介紹如何使用騰訊雲的COS對象存儲服務。



網站的訪問網址就是: http://www.tianfang1314.cn/

或者http://blog.tianfang1314.cn/

相關文章
相關標籤/搜索