為專案編寫Readme.MD檔案
瞭解一個專案,恐怕首先都是通過其Readme檔案瞭解資訊。如果你以為Readme檔案都是隨便寫寫的那你就錯了。github,oschina git gitcafe的程式碼託管平臺上的專案的Readme.MD檔案都是有其特有的語法的。稱之為Markdown語法。基本規則如下:
Markdown 語法速查表
1 標題與文字格式
標題
# 這是 H1 <一級標題>
## 這是 H2 <二級標題>
###### 這是 H6 <六級標題>
文字格式
**這是文字粗體格式**
*這是文字斜體格式*
~~在文字上新增刪除線~~
2 列表
無序列表
* 專案1
* 專案2
* 專案3
有序列表
1. 專案1
2. 專案2
3. 專案3
* 專案1
* 專案2
3 其它
圖片
![圖片名稱](http://gitcafe.com/image.png)
連結
[連結名稱](http://gitcafe.com)
引用
> 第一行引用文字
> 第二行引用文字
水平線
***
程式碼
`<hello world>`
程式碼塊高亮
```ruby
def add(a, b)
return a + b
end
```
表格
表頭 | 表頭
------------- | -------------
單元格內容 | 單元格內容
單元格內容l | 單元格內容
如果直接記語法,那似乎困難了些。這裡OneCoder推薦兩個Markdown的編輯器。
OneCoder這裡使用的是後者為自己的shurnim-storage專案寫Readme。至於這個專案是什麼,見Readme文件,OneCoder也會在另外的博文做一些補充說明。成品Readme如下:
# shurnim-storage
![Shurnim icon](http://onecoder.qiniudn.com/8wuliao/DLPii2Jx/rEBO.jpg)
## 目錄
* [背景介紹](#背景介紹)
* [專案介紹](#專案介紹)
* [使用說明](#使用說明)
* [獲取程式碼](#獲取程式碼)
* [開發外掛](#開發外掛)
* [使用ShurnimStorage介面](#使用ShurnimStorage介面)
* [介面介紹](#介面介紹)
* [使用樣例](#使用樣例)
* [其他](#其他)<a name="背景介紹"></a>
## 背景介紹*Shurnim*,是我和我老婆曾經養過的一隻倉鼠的名字。<br/>
*shurnim-storage*,是一個外掛式雲端儲存/網盤同步管理工具。是在參加又拍雲開發大賽的過程中設計並開發。<a name="專案介紹"></a>
## 專案介紹*shurnim-storage* 的設計初衷是給大家提供一個可方便擴充套件的雲端儲存/網盤同步工具。分後端介面和前端UI介面兩部分。<br>
由於目前各種雲端儲存和網盤系統層出不窮,單一工具往往支援支援某幾個特定儲存之間的同步,如**又拍雲**到**七牛雲端儲存**的同步工具,此時如若想同步到其他存則可能需要新的工具,給使用者帶來不便。*shurnim-storage* 正是為了解決此問題而設計的。
在*shurnim-storage*中,使用者使用的固定的統一的後端介面。而所有云儲存/網盤API的支援則是以外掛的形式部署到系統中的。如此,如果使用者想要一個從**又拍雲**到**Dropbox**的同步工具,則只需要在原有基礎上,增加**Dropbox**的外掛,即可實現互通,方便快捷。<br/>
同時,後端統一介面的設計也考慮到介面開發的需求,可直接通過後端提供的介面開發具有上述擴充套件功能的雲端儲存UI工具。<br>
目前,後端整體框架的核心部分已經基本開發完成。只需逐步補充後端介面和外掛開發介面的定義即可。但由於個人時間和能力所限,UI部分沒有開發,有興趣的同學可以一試。
<a name="使用說明"></a>
## 使用說明<a name="獲取程式碼"></a>
### 獲取程式碼* gitcafe專案主頁: <https://gitcafe.com/onecoder/shurnim-storage-for-UPYUN>
* OSChina專案主頁: <http://git.oschina.net/onecoder/shurnim-storage><br>
OSChina上的會持續更新。另外你也可以通過OSChina的Maven庫獲取依賴,或者自己編譯jar包。
* maven
1. 加入OSC倉庫
<repositories>
<repository>
<id>nexus</id>
<name>local private nexus</name>
<url>http://maven.oschina.net/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>2. 加入依賴
<dependency>
<groupId>com.coderli</groupId>
<artifactId>shurnim-storage</artifactId>
<version>0.1-alpha</version>
</dependency>
* Gradle 編譯Jar在專案目錄執行
gradle jar
<a name="開發外掛"></a>
### 開發外掛在*shurnim-storage*中,外掛就像一塊一塊的積木,不但支撐著框架的功能,也是框架可擴充套件性的基石。開發一個外掛,僅需兩步:
1. 實現PluginAPI介面
```
package com.coderli.shurnim.storage.plugin;import java.io.File;
import java.util.List;import com.coderli.shurnim.storage.plugin.model.Resource;
/**
* 各種雲端儲存外掛需要實現的通用介面
*
* @author OneCoder
* @date 2014年4月22日 下午9:43:41
* @website http://www.coderli.com
*/
public interface PluginAPI {/**
* 初始化介面
*
* @author OneCoder
* @date 2014年5月19日 下午10:47:40
*/
void init();/**
* 獲取子資源列表
*
* @param parentPath
* @return
* @author OneCoder
* @date 2014年4月24日 下午11:29:14
*/
List<Resource> getChildResources(String parentPath);/**
* 下載特定的資源
*
* @param parentPath
* 目錄路徑
* @param name
* 資源名稱
* @param storePath
* 下載資源儲存路徑
* @return
* @author OneCoder
* @date 2014年4月24日 下午11:30:19
*/
Resource downloadResource(String parentPath, String name, String storePath);/**
* 建立資料夾
*
* @param path
* 資料夾路徑
* @param auto
* 是否自動建立父目錄
* @return
* @author OneCoder
* @date 2014年5月15日 下午10:10:04
*/
boolean mkdir(String path, boolean auto);/**
* 上傳資源
*
* @param parentPath
* 父目錄路徑
* @param name
* 資源名稱
* @param uploadFile
* 待上傳的本地檔案
* @return
* @author OneCoder
* @date 2014年5月15日 下午10:40:13
*/
boolean uploadResource(String parentPath, String name, File uploadFile);
}
```目前外掛的介面列表僅為同步資源設計,如果想要支援更多操作(如刪除,查詢等),可擴充套件該介面定義。<br/><br/>
介面中,所有的引數和返回值均為*shurnim-storage*框架中定義的通用模型。因此,您在開發外掛過程中需要將特定SDK中的模型轉換成介面中提供的模型。<br/><br/>
外掛實現類只要與*shurnim-storage*工程在同一個classpath即可使用。您既可以直接在原始碼工程中開發外掛,就如工程裡提供的*upyun*和*qiniu*外掛一樣,也可以作為獨立工程開發,打成jar,放置在同一個classpath下。<br/><br/>
*upyun*外掛樣例(功能不完整):```
package com.coderli.shurnim.storage.upyun.plugin;import java.io.File;
import java.util.List;import com.coderli.shurnim.storage.plugin.AbstractPluginAPI;
import com.coderli.shurnim.storage.plugin.model.Resource;
import com.coderli.shurnim.storage.plugin.model.Resource.Type;
import com.coderli.shurnim.storage.upyun.api.UpYun;public class UpYunPlugin extends AbstractPluginAPI {
private UpYun upyun;
private String username;
private String password;
private String bucketName;public UpYun getUpyun() {
return upyun;
}public void setUpyun(UpYun upyun) {
this.upyun = upyun;
}public String getUsername() {
return username;
}public void setUsername(String username) {
this.username = username;
}public String getPassword() {
return password;
}public void setPassword(String password) {
this.password = password;
}public String getBucketName() {
return bucketName;
}public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}/*
* (non-Javadoc)
*
* @see
* com.coderli.shurnim.storage.plugin.PluginAPI#getChildResources(java.lang
* .String)
*/
@Override
public List<Resource> getChildResources(String parentPath) {
return null;
}/*
* (non-Javadoc)
*
* @see
* com.coderli.shurnim.storage.plugin.PluginAPI#downloadResource(java.lang
* .String, java.lang.String, java.lang.String)
*/
@Override
public Resource downloadResource(String parentPath, String name,
String storePath) {
File storeFile = new File(storePath);
// if (!storeFile.exists()) {
// try {
// storeFile.createNewFile();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
String filePath = getFullPath(parentPath, name);
upyun.readDir("/api");
if (upyun.readFile(filePath, storeFile)) {
Resource result = new Resource();
result.setName(name);
result.setPath(parentPath);
result.setType(Type.FILE);
result.setLocalFile(storeFile);
return result;
}
return null;
}String getFullPath(String parentPath, String name) {
if (!parentPath.endsWith(File.separator)) {
parentPath = parentPath + File.separator;
}
return parentPath + name;
}/*
* (non-Javadoc)
*
* @see com.coderli.shurnim.storage.plugin.PluginAPI#mkdir(java.lang.String,
* boolean)
*/
@Override
public boolean mkdir(String path, boolean auto) {
// TODO Auto-generated method stub
return false;
}/*
* (non-Javadoc)
*
* @see
* com.coderli.shurnim.storage.plugin.PluginAPI#uploadResource(java.lang
* .String, java.lang.String, java.io.File)
*/
@Override
public boolean uploadResource(String parentPath, String name,
File uploadFile) {
// TODO Auto-generated method stub
return false;
}/*
* (non-Javadoc)
*
* @see com.coderli.shurnim.storage.plugin.AbstractPluginAPI#init()
*/
@Override
public void init() {
upyun = new UpYun(bucketName, username, password);
}}
```
2. 編寫外掛配置檔案```
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<id>qiniu</id>
<name>七牛雲端儲存</name>
<api>
<className>com.coderli.shurnim.storage.qiniu.QiniuPlugin</className>
<params>
<param name="access_key" displayName="ACCESS_KEY">EjREKHI_GFXbQzyrKdVhhXrIRyj3fRC1s9UmZPZO
</param>
<param name="secret_key" displayName="SECRET_KEY">88NofFWUvkfJ6T6rGRxlDSZOQxWkIxY2IsFIXJLX
</param>
<param name="bucketName" displayName="空間名">onecoder
</param>
</params>
</api>
</plugin>
```
* **id** 為該外掛在*shurnim-storage*框架下的唯一標識,不可重複,必填。
* **name** 為顯示值,為UI開發提供可供顯示的有語義的值。
* **className** 為外掛介面實現類的完整路徑。必填
* **params/param** 為外掛需要使用者配置的引數列表。其中
* *name* 代表引數名,需要與介面實現類中的引數名嚴格一致,且必須有相應的set方法的格式要求嚴格,即set+首字母大寫的引數名。例如:setAccess_key(String arg); 目前只支援*String*型別的引數。
* *displayName* 為引數顯示名,同樣是為了UI開發的考慮,方便使用者開發出可根據引數列表動態顯示的UI介面。
* 引數的值可以直接配置在配置檔案中,也可以在執行期動態賦值。直接配置值,對於直接使用後端介面來說較為方便。對於UI開發來說,執行期動態賦值更為合理。<br/></br>在使用原始碼工程時,外掛配置檔案統一放置在工程的*plugins*目錄下。你也可以統一放置在任何位置。此時,在構造後端介面例項時,需要告知介面該位置。
<a name="使用ShurnimStorage介面"></a>
### 使用*ShurnimStorage*介面<a name="介面介紹"></a>
#### 介面介紹**ShurnimStorage**介面是*shurinm-storage*框架全域性的也是唯一的介面,目前定義如
```
package com.coderli.shurnim.storage;import java.util.List;
import java.util.Map;import com.coderli.shurnim.storage.plugin.model.Plugin;
import com.coderli.shurnim.storage.plugin.model.Resource;/**
* 後臺模組的全域性介面<br>
* 通過該介面使用後臺的全部功能。<br>
* 使用方式:<br>
* <li>
* 1.先通過{@link #getSupportedPlugins()}方法獲取所有支援的平臺/外掛列表。 <li>
* 2.將列表中返回的ID傳入對應的介面引數中,進行對應的平臺的相關操作。<br>
* 需要注意的是,不同平臺的外掛需要給不同的引數賦值,該值可以直接配置在配置檔案中。<br>
* 也可以在執行期動態賦值。(會覆蓋配置檔案中的值。)<br>
*
* 引數列表的設計,方便UI開發人員動態的根據引數列表生成可填寫的控制元件。並給引數賦值。增強了可擴充套件性。
*
* @author OneCoder
* @date 2014年4月22日 下午9:21:58
* @website http://www.coderli.com
*/
public interface ShurnimStorage {/**
* 獲取當前支援的外掛列表<br>
* 沒有支援的外掛的時候可能返回null
*
* @return
* @author OneCoder
* @date 2014年5月7日 下午8:53:25
*/
List<Plugin> getSupportedPlugins();/**
* 給指定的外掛的對應引數賦值<br>
* 此處賦值會覆蓋配置檔案中的預設值
*
* @param pluginId
* 外掛ID
* @param paramsKV
* 引數鍵值對
* @author OneCoder
* @date 2014年5月9日 上午12:41:53
*/
void setParamValues(String pluginId, Map<String, String> paramsKV);/**
* 獲取外掛對應目錄下的資源列表
*
* @param pluginId
* 外掛ID
* @param path
* 指定路徑
* @return
* @author OneCoder
* @date 2014年5月11日 上午8:52:00
*/
List<Resource> getResources(String pluginId, String path);/**
* 同步資源
*
* @param fromPluginId
* 待同步的外掛Id
* @param toPluginIds
* 目標外掛Id
* @param resource
* 待同步的資源
* @return 同步結果
* @author OneCoder
* @date 2014年5月11日 上午11:41:24
*/
boolean sycnResource(String fromPluginId, String toPluginId,
Resource resource) throws Exception;
}
```當前介面實際僅包含了獲取資源列表*getResources*和同步資源*sycnResource*功能,*getSupportedPlugins*和*setParamValues*實際為輔助介面,在UI開發時較為有用。<br/><br/>
同樣,您也可以擴充套件開發該介面增加更多的您喜歡的特性。例如,同時刪除給定儲存上的檔案。當然,這需要外掛介面的配合支援。<br/><br/>這裡,*sycnResource*設計成外掛間一對一的形式,是考慮到獲取同步是否成功的結果的需求。如果您想開發一次同步到多個儲存的功能,建議您重新開發您自己的介面實現類,因為預設實現會多次下次資源(每次同步後刪除),造成網路資源的浪費。
介面的預設實現類是: **DefaultShurnimStorageImpl**
<a name="使用樣例"></a>
#### 使用樣例
```
package com.coderli.shurnim.test.shurnimstorage;import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;import com.coderli.shurnim.storage.DefaultShurnimStorageImpl;
import com.coderli.shurnim.storage.ShurnimStorage;
import com.coderli.shurnim.storage.plugin.model.Resource;
import com.coderli.shurnim.storage.plugin.model.Resource.Type;/**
* 全域性介面測試類<br>
* 時間有限,目前僅作整體介面測試。細粒度的單元測試,隨開發補充。
*
* @author OneCoder
* @date 2014年5月19日 下午10:50:27
* @website http://www.coderli.com
*/
public class ShurnimStorageTest {private static ShurnimStorage shurnim;
@BeforeClass
public static void init() {
shurnim = new DefaultShurnimStorageImpl(
"/Users/apple/git/shurnim-storage-for-UPYUN/plugins");
}@Test
public void testSycnResource() {
Resource syncResource = new Resource();
syncResource.setPath("/api");
syncResource.setName("api.html");
syncResource.setType(Type.FILE);
try {
Assert.assertTrue(shurnim.sycnResource("upyun", "qiniu",
syncResource));
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
<a name="其他"></a>
## 其他時間倉促,功能簡陋,望您包涵。OneCoder(Blog:[http://www.coderli.com](http://www.coderli.com))特別希望看到該專案對您哪怕一點點的幫助。任意的意見和建議,歡迎隨意與我溝通,聯絡方式:
* Email: <[email protected]>
* QQ:57959968
* Blog:[OneCoder](http://www.coderli.com)專案的Bug和改進點,可在OSChina上以issue的方式直接提交給我。
效果預覽: