文章

为项目编写Readme.MD文件

了解一个项目,恐怕首先都是通过其Readme文件了解信息。如果你以为Readme文件都是随便写写的那你就错了。github,oschina git gitcafe的代码托管平台上的项目的Readme.MD文件都是有其特有的语法的。称之为Markdown语法。语法规则详见:Markdown 官方教程

如果直接记语法,那似乎困难了些。这里OneCoder推荐两个Markdown的编辑器。

在线编辑器stackedit网址:https://stackedit.io/

Mac下离线编辑器Mou下载地址:http://mouapp.com/

Mou

OneCoder这里使用的是后者为自己的shurnim-storage项目写Readme。至于这个项目是什么,见Readme文档,OneCoder也会在另外的博文做一些补充说明。成品Readme如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
# shurnim-storage

## 目录
- [shurnim-storage](#shurnim-storage)
 - [目录](#目录)
 - [背景介绍](#背景介绍)
 - [项目介绍](#项目介绍)
 - [使用说明](#使用说明)
  - [获取代码](#获取代码)
  - [开发插件](#开发插件)
  - [使用*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>
### 获取代码

* GitHub项目主页: <https://github.com/lihongzheshuai/shurnim-storage>
* 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接口

```java
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*插件样例(功能不完整):

```java    
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
<?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*框架全局的也是唯一的接口,目前定义如

```java
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>
#### 使用样例
```java        
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: <wushikezuo@gmail.com>
* QQ:57959968
* Blog:[OneCoder](http://www.coderli.com)

效果预览: preview

本文由作者按照 CC BY 4.0 进行授权