天天看點

某電商平台開發記要——客服系統

假如網站需要提供客服功能,如果隻是簡單的聊天咨詢可以考慮營銷QQ、百度商橋等(目前大部分網站采用此方式,包括一些知名行業電商);如果需要更精細化的管理,比如客服人員安排、各項資料統計彙總,那麼需要對接專業的第三方客服平台,比如網易七魚,當然價格不菲;然而若是如京東本身就是一個平台,需要為每個商家提供各自的客服管理,首先目前第三方提供商并無此類産品(網易七魚據說已經開發出來了,但是官網上沒找到),其次即使有,價格也肯定不便宜,而且資料在别人那裡總歸不好。是以電商平台的客服系統,一般都是自己開發。當然了,借助優秀的開源項目,自主開發[一套簡單能用的]也變得輕松很多。

我采用了openfire+spark+layim,前兩者基于java平台,layim是國人開發的一個webim前端元件。

先來看大緻效果(左邊是浏覽器layim-客戶提咨詢,右邊是spark聊天視窗-客服解答)

某電商平台開發記要——客服系統
圖示:
某電商平台開發記要——客服系統

本文涉及到的知識點(雜亂,後續會不定期添加内容):

Java基礎

Intellij Idea:Java IDE

Mybatis:半ORM

XMPP協定

smack:XMPP協定的Java封裝

openfire

fastpath:openfire插件,我們需要依賴它實作客服功能

wechat

spark

一秒鐘入門Java

Java SE(J2SE):Standard Edition,可認為是基礎庫,用于開發和部署桌面、伺服器以及嵌入裝置(J2ME)和實時環境中的Java應用程式。

Java EE(J2EE):基于SE的進階庫,提供 Web 服務、元件模型、管理和通信 API,可以用來實作企業級的面向服務體系結構。

可以知道J2EE比J2SE多了Web相關的元件和API,但是本人在使用SpringMVC架構開發Web應用程式時,去官網Java SE頁面下載下傳的JDK,也能正常開發。後來檢視官網的Java EE的下載下傳頁面,發現提供的SDK中主要包含一個叫GlassFish的開源元件和一些示例及文檔,而Java EE剛開始是以一種規範提出,GlassFish可以看作是實作了這些規範的JEE容器,而我們開發Web站點時部署到伺服器(比如Tomcat),實作了JEE規範其中的Servlet容器部分,是以以JDK開發Web并不會出現問題。

JNDI 是什麼 :簡單的說就是為了解耦,非直接引用,而是通過名稱或位址查找然後加載的方式,常用依賴注入的方式實作。

目前流行的IDE有Eclipse和IntelliJ IDEA,前者免費且由于曆史關系占有率一直很高,後者也有社群版,據說使用性上目前完勝前者。

final關鍵詞:類似于.NET的readonly

匿名内部類:

定義一個類A(可以為abstract),為友善說明,在A中定義一個[抽象]方法dosth。在調用方法裡可以直接new A,并且同時給dosth賦方法體。

public abstract A{    
    public void dosth() {
    }
}

public abstract B{    
    public void call() {
        final A a = new A() {
            public void dosth() {
                //這裡寫方法體
            }
        };
    }
}      

看着是執行個體化了A的一個對象,其實是執行個體化了A類的匿名子類。

Access restriction:eclipse對某些java包(or 類?)有access rules,比如 sun.awt.shell.ShellFolder。因為這些JAR預設包含了一系列的代碼通路規則(Access Rules),如果代碼中引用了這些通路規則所禁止引用類,那麼就會提示這個錯誤資訊。解決方法:既然存在通路規則,那麼修改通路規則即可。打開項目的Build Path Configuration頁面,打開引用的[報錯]JAR包,選中Access rules條目,選擇右側的編輯按鈕,添加一個通路規則即可。

Java NIO

Apache Mina

CopyOnWrite:CopyOnWrite容器即寫時複制的容器。通俗的了解是當我們往一個容器添加元素的時候,不直接往目前容器添加,而是先将目前容器進行Copy,複制出一個新的容器,然後新的容器裡添加元素,添加完元素之後,再将原容器的引用指向新的容器。這樣做的好處是我們可以對CopyOnWrite容器進行并發的讀,而不需要加鎖。從JDK1.5開始Java并發包裡提供了兩個使用CopyOnWrite機制實作的并發容器,它們是CopyOnWriteArrayList和CopyOnWriteArraySet。

Maven:項目管理工具。不像VS,eclipse是更純粹的編碼工具,在維護jar包和項目之間的依賴關系、項目的建構目标等方面的功能比較弱(比如拷貝了一個項目,我們需要手動去Configure Build Path),而Maven就是補足于此。Maven獨立于IDE,eclipse有一個插件叫M2E,裡面内置了Maven。Maven項目的配置資訊儲存在pom.xml檔案中。

我們在導入Maven項目時,有時會發現不止一個pom.xml,那是因為項目中有子項目(module,module有自己的pom.xml),隻要選擇最頂層的pom.xml檔案即可,會自動加載引用到的子項目。

JavaBean:一般可看作是POJO,可參看 Java Bean 是個什麼概念? (不過這個問題裡有個答主說Java沒有事件的概念,讓我大吃一驚,不過轉念一想,Java主要用于開發服務端應用,确實不怎麼涉及到[自定義]事件。其實Java中是有事件機制的,隻是不知變通,就一個半成品的觀察者模式,要預先以類的形式定義好各互動方,較為繁瑣(參看Java —— 事件處理機制),想想C#的委托,其實就一個函數指針的事)

MVC:當.Neter們在被Asp.Net的重量壓得踹不過氣來的時候,Java已經有MVC的概念了。很多模式,.Net界都是直接copy,.Neter們并沒有對其曆史的認知,是以接收不能,MVC就是如此。其實在Asp.Net時代已經有MVC的影子,就是一般處理程式.ashx。很早以前,使用者送出都是送出到具體的一個頁面,于是會經常導緻一個頁面并不是用于顯示,而是用于業務邏輯的處理,于是後來把業務邏輯單獨拎出來,這便是Controller,使用者請求的是Controller,不再是具體頁面,并且Controller裡不再使用類似HttpRequest或者HttpResponse擷取資料和傳回響應,而是使用對象的形式(M),這便是MVC模式。可參看 Java Web開發模式

Java中的注解相當于.NET中的Attribute。

Spring是一個IOC和AOP架構。我們可以通過在xml檔案中配置bean,然後在代碼中使用@Autowired或@Resource注入bean執行個體,不過配置的環節稍顯繁瑣,能不能省略呢?答案是肯定的,Sping2.5開始支援注解注入,具體可看 spring注解注入:<context:component-scan>詳解。需要注意的是,@Component及相關的幾個注解類,在應用到interface上的時候,可能并不如預期工作,因為interface并不能執行個體化,而這幾個注解類貌似又沒有@Inherited修飾,是以就算有實作類或運作時的動态實作類,也不會注冊到上下文中;且修飾的類要有公共構造函數。另外注入[被注入方]一般隻能在注入方本身是已注冊的bean裡,若在普通類裡想通過@Autowired或@Resource的方式注入bean,則稍微有點繞,可參看 Java普通類擷取Spring XML中Bean的方法總結

關于Servlet、Struts、Spring、SpringMVC的關系與差別可參看 Java開發web的幾種開發模式 和 SpringMVC與Struts2的對比

SpringMVC竟然URL和參數大小寫敏感,雖然有辦法配置,但這種預設沒有道理吧。。。

Servlet url-pattern /與/*差別:兩者的長度不同,根據最長路徑比對的優先級,/*比/更容易被選中,而/的真正含義是,預設比對。既所有的URL都無法被選中的時候,就一定會選中/,可見它的優先級是最低的,這就兩者的差別。

xml檔案也可以打包進jar包,但是通路jar包裡的xml檔案就不能按檔案目錄的方式來了,可參看 http://blog.csdn.net/jianxin1009/article/details/18814799 

application.getInitParameter:jsp中9個内置對象之一application,它的資料對整個web應用都有效,application有一個重要的用途就是通過getInitParameter()擷取web.xm中的配置參數,這樣可以提高代碼的移植性。

dwr:簡化ajax調用,使得調用遠端伺服器方法看上去像調用本地方法一樣。

在java項目中必不可少的是我們要指定一個jdk。在指定jdk的同時,還可以指定jdk的Language level,這個有點像我們工程最低支援版本。比如Language level 設定了5.0 隻是就不能出現使用6.0/7.0特性的代碼,因為這些特性在5.0的環境下是無法編譯的。或者可以了解ide會安裝Language level指定的jdk版本來對我們的代碼進行編譯,以及錯誤檢查。即同樣的jdk對應不同的Language Level會采用[可能]不同的編譯和優化方式。

Java中也有類似.Net的字元串池的概念,請看 String中intern的方法

Java插件技術: OSGi

貌似在同一package下,protected可見。(和.NET不同)

Java的泛型類型隻能是引用類型,而不能是基礎類型,但是Java針對每個基礎類型有對應的封裝類型,比如boolean對應Boolean,後者是引用類型,可以為null,當封裝類型不為null時,可以隐式轉換,但寫代碼時null的情況要自己處理,如

private boolean existUser(String username) {
    Boolean result = null; 
    return result != null && result.booleanValue();
}      

Ant:類似于.NET的MSBuild,其建構檔案預設為build.xml(可以在其中指定建構基于的Java平台版本),每個建構檔案都對應于一個項目,但是大型項目經常包含大量的子項目,每一個子項目都可以有自己的建構檔案。

一個.java檔案中可以定義多個類,但是public修飾的隻能至多有一個,且要與檔案名相同,編譯後,有幾個類就會産生幾個對應的.class檔案。jar包類似.Net的dll,它将多個.class檔案打包一塊。大多數 JAR 檔案包含一個 META-INF 目錄,它用于存儲包和擴充的配置資料,如安全性和版本資訊。Java 2 平台識别并解釋 META-INF 目錄中的下述檔案和目錄,以便配置應用程式、擴充和類裝載器。具體可看 MANIFEST.MF 檔案内容完全詳解。

System.getProperty()擷取系統/項目全局變量,比如Java運作時版本,當然我們也可以通過System.setProperty()設定自定義變量。

Java桌面用戶端程式設計:Java Swing 。桌面程式畢竟不是Java的主流領域,是以各IDE貌似也并未作太多努力,相較VS的所見即所得的控件拖拽開發模式,Java GUI程式設計就吃力很多了。

Java國際化:i18n,注意中文的資源檔案,貌似需要先UTF-8轉碼,大約就是像這樣

某電商平台開發記要——客服系統

。(可以使用JDK自帶的native2ascii.exe)

Intellij Idea

使用Intellij Idea建立spring mvc時(沒用maven),run都報 Error during artifact deployment. See server log for details 錯誤,後來把lib檔案夾拷到WEB-INFO檔案夾下就沒問題了,不知何故。

原因:tomcat預設是去web-info/lib/下找依賴的jar包。手動拷jar包畢竟不是一個好辦法,其實我們可以在下圖處進行Artifacts設定

某電商平台開發記要——客服系統

運作項目,項目目錄下會多出一個out檔案夾,生成所有的站點檔案,依賴包會自動拷貝到下面的WEB-INF/lib/下,如下圖:

某電商平台開發記要——客服系統

IDEA配置artifacts中Web Application:Exploded和Web Application:Archive的差別:前者以檔案夾形式(War Exploded)釋出項目,後者以war包形式(每次都會重新打包全部的)。Tomcat會自動解壓war包并啟動站點,缺點是會造成一段時間的站點不可用,而以檔案夾形式釋出的話,則支援熱部署(需進行額外的一些配置)。

當然我們也可以使用Maven進行依賴包的管理。在目前項目右鍵->Add Framework Support->Maven即可。注意需要在Project Structure-> Project Settings中移除之前非Maven引用的包依賴。此時運作項目,項目目錄下會多出一個target檔案夾,其下有生成的站點檔案。但是運作時發現WEB-INF下的檔案除了web.xml外,其它的檔案都不會覆寫,貌似用maven管理的web工程,需要将applicationContext.xml等資源檔案放在resource目錄下,然後以classpath的方式去通路。後來發現jsp頁面也無法自動更新到target目錄,再後來聽說maven有一套約定的目錄結構,貌似又可以通過pom.xml進行自定義配置,神煩!目前靠手動覆寫。參考 Maven使用點滴 配置即可(webappDirectory我沒設定,就設定了warSourceDirectory,能正常更新了)

Intellij Idea中有個Ant Build Window,預設顯示的是主項目下build.xml中的targets,and by default, IDEA only shows the default target and targets that have descriptions。對這個有疑問可參看 How to get Ant Build to list all targets in a hierarchy of build files.

可以在Run/Debug Configurations Window中設定自定義系統變量,如下圖(-D不能省):

某電商平台開發記要——客服系統

MyBatis

一個半ORM架構,SQL語句并不是像EF一樣由架構解析,而是要預先寫在xml中或者寫在Java注解(同.Net的Attribute)中,且不支援匿名類型(即select出來的資料要麼是基礎類型,要麼要有對應的Java Bean)。一般情況下,我們使用resultType映射查詢結果和對象即可(MyBatis 會在幕後自動建立一個 ResultMap),當隻想映射部分字段或者包含複雜類型屬性的時候,我們需要自定義ResultMap。

MyBatis不支援方法重載,因為它是通過方法名稱(不加參數)去查找執行方法,是以我們設定不同的方法名,或者使用動态sql。

JID表示一個位址,由三部分組成——node、domain和resource。例如:[email protected]/sleeping,xiaoming就是node ,xiaoming.home就是domain,sleeping就是resource。node domain 和resource任何一部分都不能超過1023 位元組 ,加上@和 /,一個JID 總共不能超過3071位元組。BareJid就是去掉resource,隻包含node@domain。

XMPP包含IQ, message and presence 三種packet。

smack

ConnectionConfiguration.Builder的setXmppDomain和setHost的差別?一個是域(伺服器叢集),一個是其中的一台伺服器,應該隻要設定其中一個就可以了。

使用XMPPTCPConnectionConfiguration建立連接配接時報空指針錯誤,調試發現有個base64encoder未指派,需要引用smack-java7包,該包會初始化base64encoder,如果是安卓開發,那麼就引用smack-android。

openfire 

使用idea導入openfire代碼,過程可參考将openfire源碼部署到IDEA中 或者 IntelliJ IDEA搭建openfire4.1.3開發環境 。使用openfire配置界面隻能配置一個資料庫,且我也不打算完全依賴它生成的資料庫。我需要openfire部分功能使用現有的資料庫(比如使用者表),而openfire的業務資料仍然使用生成的資料庫,是以涉及到多庫連接配接。這隻能去修改源碼了。

上面說到的配置界面設定的項最終存儲在ofproperty表中。在配置界面完成配置後,我們也可以在conf/openfire.xml中重新設定值,重新開機openfire,配置檔案中的值會更新到資料庫中。

以AuthFactory為例,其initProvider方法裡有 JiveGlobals.migrateProperty("provider.auth.className"); ,XMLProperties根據"provider.auth.className"讀取xml檔案中的值(getProperty方法)

//按逗号拆分為數組
String[] propName = parsePropertyName(name);
// Search for this property by traversing down the XML hierarchy.
Element element = document.getRootElement();
for (String aPropName : propName) {
    element = element.element(aPropName);
    if (element == null) {
        return null;
    }
}
value = element.getTextTrim();      

對應的配置節寫法如下(可以看到,propName對應各層級element,而非attribute形式)

<provider>
    <auth>
        <className>org.jivesoftware.openfire.auth.JDBCAuthProvider</className>
    </auth>
</provider>          

而後覆寫資料庫值

public void migrateProperty(String name) {
    if (getProperty(name) != null) {
        if (JiveGlobals.getProperty(name) == null) {
            JiveGlobals.setProperty(name, getProperty(name));
            deleteProperty(name);
        }
        else if (JiveGlobals.getProperty(name).equals(getProperty(name))) {
            deleteProperty(name);
        }
        else if (!JiveGlobals.getProperty(name).equals(getProperty(name))) {
            Log.warn("XML Property '"+name+"' differs from what is stored in the database.  Please make property changes in the database instead of the configuration file.");
        }
    }
}      

當然,若是我們有資料庫權限,直接進入資料庫修改也一樣。

openfire源碼采用JDBC方式操作資料庫,而且沒有做很好的封裝,重複代碼較多,如下圖所示

某電商平台開發記要——客服系統

相似代碼在與資料庫互動的地方随處可見。部分邏輯的抽取,莫過于lambda(回調函數)的方式。考慮到Java8已經支援lambda表達式,重構如下:

public <T> T excuteQuery(String queryText, Function<ResultSet, T> func) {
    T result = null;
    Connection con = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
        con = getConnection();
        pstmt = con.prepareStatement(queryText);
        rs = pstmt.executeQuery();
        if (rs.next()) {
            result = func.apply(rs);
        }
    } catch (SQLException e) {
        Log.error(e.getMessage(), e);
    } finally {
        DbConnectionManager.closeConnection(rs, pstmt, con);
    }
    return result;
}      

但是在寫調用代碼的時候提示:

某電商平台開發記要——客服系統

雖然我們在excuteQuery方法中已經catch了這個異常,但是編譯器并不買賬。而且就算我們在方法定義時已經throws了相關異常,也沒用,如下圖:

某電商平台開發記要——客服系統

解決方法有兩種:可以在lambda體内catch異常後不再throw;或者自定義一個Functional Interface,其中聲明一個定義了異常的方法,

@FunctionalInterface
public interface CheckedSQLExceptionFunction<T, R> {
    R apply(T t) throws SQLException;
}      

然後将Function<Result,T>的地方替換為CheckedSQLExceptionFunction<ResultSet, T>。這兩種都顯得别扭與不合理,導緻這一問題的是,Java Lambda規定如果Lambda中抛出了異常,那麼這個異常一定要在Functional Interface中的abstract方法上定義。這是一個讓人無法了解的規定。

遇到lambda的另一個坑:

某電商平台開發記要——客服系統

由于username有重新指派,是以編譯報錯,是不是很喜感?我不得不用一個臨時變量解決。。

官方提供了一種內建外部使用者體系的方法(Custom Database Integration Guide),然後并不支援加鹽密碼,于是我隻能自己撸碼解決。關鍵是實作兩個接口:AuthProvider 和 UserProvider,隻要實作部分方法即可,很簡單不贅述。

部署

部署到centos7。首先 rpm -qa | grep openjdk 檢視所有已安裝的jdk,如果版本不滿足則先 rpm -e --nodeps [java-1.7.0-openjdk[-headless]] 解除安裝掉。然後去官網上下載下傳合适版本的server jre/jre/sdk包(下面會進一步說明),然後解壓,設定環境變量,就算安裝完畢了(不過這種安裝方式通過rpm -qa可是找不到的哦)。具體可看 Centos7 JDK8安裝配置。

講道理,jdk是開發時候用的,部署的話我們隻要安裝jre就可以了。我剛開始下載下傳的是server jre包,在ant的時候報 package javafx.util does not exist 的錯(因為我在代碼裡用到了Pair<>二進制組,屬于javafx.util包),然而網上查了下,貌似javaFX是用于用戶端GUI方面的元件(不知道是否我這裡報錯的javafx同個概念)。我懶得探究,馬上去官網下了jre包(官網說Covers most end-users needs. Contains everything required to run Java applications on your system.),載下來之後發現果然有jfxrt.jar(包含javafx.util),歡欣鼓舞,但是ant之後報無法找到/lib/tools.jar——因為build.xml裡有用到這個jar——之前server jre是有的,也是日了狗了。馬上去下jdk,瘋狂操作之後終于編譯通過。

也可以在windows平台編譯打包,然後拷貝到linux系統。

官網上是說./openfire start啟動openfire,然而我隻找到openfire.bat和openfirectl,先試了./openfirectl start 報錯:Could not find Openfire installation under /opt,/usr/share,or /usr/local,檢視openfirectl的shell代碼,發現當OPENFIRE_HOME未設定時,會去這三個目錄下找openfire,于是為其設定真實根目錄,然而雖沒報錯,但還是沒有運作起來。試了下openfire.bat,報Permission denied,尼瑪,我可是用root登入的。先不管原因,我再去官網下了4.1.6(目前最新版)的tar包,發現bin目錄下果然有個openfire檔案,拷到伺服器上後報同樣的Permission denied的錯誤——網上說root并不預設就有所有檔案的最高權限,但是他可以随意給自己增權重限——好吧,設定了權限之後,執行./openfire start 沒報錯,但是依舊沒有運作起來。。。後來發現沒有輸出錯誤資訊,是因為shell裡寫了/dev/null 2>&1,去掉之後終于提示——Could not find or load main class com.install4j.runtime.launcher.UnixLauncher——shell代碼裡該類指向的目錄本地編譯不存在,最後在官網tar包裡發現有一個名為.install4j的隐藏檔案夾,拷貝後總算運作起來了。 

記得打開相應端口。

webchat

使用者一般都是通過浏覽器進行咨詢,有個webchat示例可以參考(openfire4.2 配置fastpath、webchat、spark實作客服系統),但那是基于很久以前的smack版本,轉過來也費了不少勁,特别是QueueUpdate包擴充已經不再内置支援,調試了半天在smack中找到幾個關鍵檔案

某電商平台開發記要——客服系統
某電商平台開發記要——客服系統

,這些都是内置資源檔案,項目運作時會讀取這些檔案,調用ProviderManager.addExtensionProvider将配置項緩存起來,如果不修改xml的話,那麼在外部調用該方法也是可以的。參照着寫了一個QueueUpdateProvider,順便了解了下XmlPullParser的用法。

關于自定義包和擴充,後來才發現官網上有介紹: Provider Architecture: Stanza Extensions and Custom IQ's,也是心累。

再後來,發現部分非内置的擴充的Provider已經在擴充類裡[作為内部類]定義好了,比如QueueUpdate.Provider。。。吐血。關于内部類可參看 java中的内部類總結

在CentOS安裝tomcat9.0.1。去官網下載下傳tar.gz包,解壓,然後去到bin目錄,在catalina.sh檔案添加内容export CLASSPATH=$JAVA_HOME/lib,然後./startup.sh即可,另外記得開放8080端口。當然我們可以更改端口以及綁定域名,參考 tomcat釋出應用并配置域名。關于項目打包成war包,參考 Intellij IDEA社群版打包Maven項目成war包,并部署到tomcat上。

fastpath

增加幾個http接口,如新增客服組,添加客服等,示例代碼如下:

public class MasonServlet extends HttpServlet {
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        AuthCheckFilter.addExclude("fastpath/mason/*"); // 公共接口不需身份校驗
    }

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String action = request.getRequestURI();
        action = action.substring(action.indexOf("mason/") + 6);
        OPResult result = null;
        if (action.toLowerCase().equals("createworkgroup")) {
            String wgName = request.getParameter("wgName");
            String description = request.getParameter("description");
            String agents = request.getParameter("agents");

            result = createWorkgroup(wgName, description, agents);
        }
        if (result == null) {
            result = new OPResult();
            result.setSuccess(false);
            result.setMessage("未找到對應方法");
        }
        response.setContentType("application/json; charset=utf-8");
        response.setCharacterEncoding("UTF-8");
        Genson genson = new Genson();
        String json = genson.serialize(result);
        response.getOutputStream().write(json.getBytes("UTF-8"));
    }

    // 新增工作組(會同時建立一個預設客服組,每個工作組可以包含多個客服組)
    private OPResult createWorkgroup(String wgName, String description, String agents) {
        OPResult result = new OPResult();
        Map errors = WorkgroupUtils.createWorkgroup(wgName, description, agents);
        if (errors.size() == 0) {
            Workgroup workgroup = WorkgroupManager.getInstance().getWorkgroup(wgName);
            result.setData(workgroup.getJID());
            result.setSuccess(true);
        } else
            result.setSuccess(false);
        return result;
    }
}      

完了我們就可以重新建構該插件了,在intellij中可以在視窗中設定(看了下build.xml,發現plugin任務可以建構單個插件,它接收plugin的參數表明建構的是哪個插件):

某電商平台開發記要——客服系統

由于代碼中用到了genson這個第三方jar包,雖然直接編譯沒問題(項目的其它地方有引用),但用ant建構的時候會報錯,提示找不到這個元件,原因官網說了:Any JAR files your plugin needs during compilation should be put into the lib directory,是以我們需要将該jar包複制一份到fastpath/lib目錄下。

此spark非彼spark,而是一個開源IM桌面用戶端。下載下傳下來2.8.3代碼,導入到IntelliJ,運作輸出了空指針異常,調試發現找不到資源檔案 "META-INF/plugins.xml",檢視編譯後的jar檔案,裡面已經包含了resources/META-INF/plugins.xml。再檢視Project Structure,發現沒有為主子產品Spark設定Resource Folders,添加了resources檔案夾後編譯運作正常,此時再看jar檔案,裡面并沒有resources目錄,META-INF直接在根目錄展現。

某電商平台開發記要——客服系統

也就是說,将某個目錄設定為資源檔案夾(Resource Folders),意即将該目錄下的子目錄一起打包進jar包(不包含該目錄本身),而getResource()方法擷取特定路徑的資源時,是直接去jar包根目錄下查找對應檔案。

似乎還要設定VM arguments:-Djava.library.path=build/lib/dist/windows64,具體值按照作業系統來。參看 openfire-spark 二次開發-(二)運作環境配置

相關資料:TCP長連接配接與短連接配接、心跳機制

轉載請注明本文出處:http://www.cnblogs.com/newton/p/7269373.html