在BDF2-UPLOADER模块当中,为我们提供了一个将文件从浏览器上传到服务端的工具,该工具以标准的Dorado7组件形式存在,利用标准的HTML及JAVASCRIPT实现文件的选择与上传(未采用任何其它第三方控件,比如flash,activex等),兼容所有主流浏览器(IE、FF、Chrome等),默认提供了将文件保存到服务端某文件夹或服务端数据表中两种类型的文件存储机制。
要使用BDF2-UPLOADER模块,我们可以到nexus.bsdn.org上下载最新的BDF2-UPLOADER模块的jar,或者可以到我们提供的在线创建项目向导中选择BDF2-UPLOADER模块并下载即可;同样,如果您采用的是Maven来管理项目,那么只需要将BDF2-UPLOADER模块的依赖信息加到我们的pom.xml当中即可:
<dependency> <groupId>com.bstek.bdf2</groupId> <artifactId>bdf2-uploader</artifactId> <version>2.0.0</version> </dependency>
添加好BDF2-UPLOADER模块之后,就可以启动我们的项目。
我们知道BDF2-JASPERREPORTS模块提供了一个标准的Dorado7组件来让我们实现上传功能,所以启动工程后,要在我们view的工具栏当中看到这个上传组件,需要更新Dorado7规则(在线更新方式),更新完成之后,打开一个view,可以在工具栏当中看到如下图所示组件图标。
这个名为“RichUploader”的组件就是BDF2-UPLOADER模块提供的上传组件,将这个组件添加到view当前,运行这个view可以看到这个组件就是一个标准的HTML的Button,如下图所示:
因为RichUploader上传组件就是一个标准的HTML的Button,所以在Dorado7 View当中使用空上组件时,我们就可以当其是一个Dorado7的button,可以像操作Dorado7的button一样,将其放在一个容器当中,放在一个表单当中,或者放在一个FormElement的container当中,对于RichUploader我们需要注意下面这几个属性:
属性名 | 类型 | 默认值 | 描述 |
---|---|---|---|
allowFileTypes | String | 空 | 允许用户上传哪些类型的文件,默认为空,表示不限制类型。 |
allowMaxFileSize | int | 空 | 允许用户上传文件的最大尺寸,单位是byte,默认为空,表示不限制尺寸。 |
caption | String | 空 | 上传按钮的标题,如上图当中的“上传测试”。 |
processor | String | 空 | 文件上传到服务端时采用哪个文件上传处理器来处理上传的文件,默认为空,表示采用ID为“Database”的文件上传处理器,这个处理器的作用就是将文件上传到服务器下某个目录。 |
autoSubmit | boolean | true | 选择好文件后是否自动上传,默认为true,表示选择好文件组件会自动触发上传动作,如果设置为false,那么我们需要手工调用该组件的submit方法来执行上传动作。 |
除了上述四个属性外,RichUploader还提供了四个事件,如下表所示:
事件名 | 描述 |
---|---|
onSelect | 在选择好一个文件,但还没开始将文件上传到服务端时触发的事件,在这个事件当中,我们可以通过其中提供的arg参数的filename属性拿到选择好的文件的文件名称。 |
onSuccess | 文件上传成功之后触发的事件,从这个事件当中的arg参数中,我们可以获取到上传成功之后文件的文件名(arg.filename),以及文件存储后产生的id(arg.id),一般来说,对于我们的业务系统,我们需要保存这个id ,通过这个id就可以找到上传的文件,filename一般仅用于显示。 |
onFail | 文件上传失败之后触发的事件,从这个事件当中的arg参数中,我们可以获取到上传失败的错误消息(arg.errorMessage),比如上传文件类型不对,文件尺寸太大,或其它的错误消息等。 |
我们知道RichUploader组件有个名为processor的属性,它可以决定文件上传到服务端时采用哪个文件上传处理器来处理上传的文件,默认情况下,BDF2-UPLOADER模块当中提供了两个文件上传处理器,分别是LocalDirectory及Database。LocalDirectory就是将上传的文件存储到服务端某个目录下;而Database则是将上传文件存储到服务端数据库的特定表当中。如果在使用过程当中,我们有自己的文件存储方式(比如存储到DMS中),那么可以自定义自己的文件上传处理器,要自定义文件上传处理器,我们需要实现IFileProcessor接口,这个接口源码如下:
package com.bstek.bdf2.uploader.processor; import java.io.InputStream; import com.bstek.bdf2.uploader.model.UploadDefinition; /** * @author Jacky.gao * @since 2013-5-1 */ public interface IFileProcessor { /** * 保存上传的文件 * @param uploadDefinition 可以从这个对象中取到上传文件的ID、名称、大小等信息 * @param inputStream 上传文件的流对象 */ void saveFile(UploadDefinition uploadDefinition,InputStream inputStream); /** * 根据给出的文件上传对象,返回对应的文件流 * @param uploadDefinition 文件上传对象 * @return 要取回的文件流 */ InputStream loadFile(UploadDefinition uploadDefinition); /** * 根据文件上传对象,删除对应的文件 * @param uploadDefinition 文件上传对象 */ void deleteFile(UploadDefinition uploadDefinition); /** * @return 返回当前处理器的ID */ String key(); /** * @return 是否禁用当前处理器 */ boolean isDisabled(); }
接口比较简单,这里就不于解释了,处理器实现类编写完成之后,需要配置到Spring当中,这样我们就可以在RichUploader中使用这个处理器了,方法就是给RichUploader的processor属性值设置为我们自定义的处理器key()方法返回的值即可。
例子是最好的示范,我们来看看系统默认提供的Database这个自定义处理器的写法:
package com.bstek.bdf2.uploader.processor.impl; import java.io.InputStream; import com.bstek.bdf2.uploader.model.UploadDefinition; import com.bstek.bdf2.uploader.processor.IFileProcessor; import com.bstek.bdf2.uploader.service.ILobStoreService; /** * @author Jacky.gao * @since 2013-5-20 */ public class DatabaseFileProcessor implements IFileProcessor { private boolean disabled; private ILobStoreService lobStoreService; public void saveFile(UploadDefinition uploadDefinition,InputStream inputStream) { try { lobStoreService.storeBinaryStream(inputStream, inputStream.available(),uploadDefinition.getId()); } catch (Exception e) { throw new RuntimeException(e); } } public InputStream loadFile(UploadDefinition uploadDefinition) { try { return lobStoreService.getBinaryStream(uploadDefinition.getId()); } catch (Exception e) { throw new RuntimeException(e); } } public void deleteFile(UploadDefinition uploadDefinition) { try { lobStoreService.deleteBinaryStream(uploadDefinition.getId()); } catch (Exception e) { throw new RuntimeException(e); } } public String key() { return "Database"; } public boolean isDisabled() { return disabled; } ...... }
对于已经上传好的文件,我们可以通过下页两个URL对通过RichUploader上传的文件进行在线显示(比如图片、XML文件等)或直接下载。我们首先来看看下载文件的URL:
<contextPath>/dorado/bdf2/uploader/process.download?id=<通过RichUploader上传文件成功后拿到的文件的id>
如果需要在线显示一个通过RichUploader上传的文件,其访问的URL格式如下:
<contextPath>/dorado/bdf2/uploader/process.display?id=<通过RichUploader上传文件成功后拿到的文件的id>
对于BDF2-UPLOADER模块,我们还提供了下面这个属性允许用户覆盖:
属性名 | 类型 | 默认值 | 描述 |
---|---|---|---|
bdf2.upload.dataSourceName | String | 空 | BDF2-UPLOADER模块要采用的数据源,为空表示采用默认数据源。 |
bdf2.uploader.allowMaxFileSize | int | 0 | 允许上传文件的最大尺寸,默认为0表示不限制尺寸。该属性可以在RichUploader组件使用时重新定义,一旦定义会覆盖这个属性定义的值,否则就采用这里定义的值。 |
bdf2.uploader.allowFileTypes | String | 空 | 允许上传文件的类型,多个类型用逗号分隔,默认为空表示不限制类型。该属性可以在RichUploader组件使用时重新定义,一旦定义会覆盖这个属性定义的值,否则就采用这里定义的值。 |
bdf2.uploader.defaultProcessor | String | Database | 默认要采用的文件上传处理器,默认为Database,表示上传的文件存放于数据库特定表中。该属性可以在RichUploader组件使用时重新定义,一旦定义会覆盖这个属性定义的值,否则就采用这里定义的值。 |
bdf2.uploader.localDirectoryFileProcessorDirectory | String | uploadfiles | 如果采用LocalDirectory这个文件上传处理器,那么该属性就是定义文件上传到服务端后该存放于哪个目录下,默认为uploadfiles,表示将存放到应用所在目录的WEB-INF/uploadfiles目录下,如果我们需要指定其它目录,那么需要定义一个真实存在的目录,比如定义值为D:\myuploadfiles,那么就表示上传的文件放存放于D盘下的myuploadfiles目录中。 |
bdf2.uploader.localDirectoryFileProcessorStorageMethod | String | month | 文件存储在目录中时,子目录的定义方式,这里支持三个值:year、month、day,分别表示按年、按年\月及按年\月\日这三种格式来划分目录存放上传文件。默认为month,表示按年\月格式划分目录存放上传文件。 |
bdf2.uploader.localDirectoryFileProcessorFileNameStorageMethod | String | uuid | 上传后文件命名方式,这里支持三种格式:hybrid、uuid及realName。hybrid表示文件在命名是将uuid+文件名这种混合方式命名;uuid则将直接采用uuid命名文件; 而realName只是采用文件真实名称。 |
bdf2.uploader.disableDatabaseFileProcessor | boolean | false | 是否禁用Database方式文件处理器,一旦禁用,运行时将不能使用。 |
bdf2.uploader.disableLocalDirectoryFileProcessor | boolean | false | 是否禁用LocalDirectory方式文件处理器,一旦禁用,运行时将不能使用。 |
在BDF2-UPLOADER当中为了方便我们操作文件,还提供了两个service,分别是IFileService及ILobStoreService。IFileService用于实现对上传文件的各种,比如根据上文件的文件ID查找对应的上传文件对象、根据上传文件ID查询上传的文件流等,其源码如下:
package com.bstek.bdf2.uploader.service; import java.io.InputStream; import com.bstek.bdf2.uploader.model.UploadDefinition; /** * @author Jacky.gao * @since 2013-5-12 */ public interface IFileService { public static final String BEAN_ID="bdf2.uploader.fileService"; UploadDefinition getUploadDefinition(String id); InputStream getFile(UploadDefinition definition); InputStream getFile(String id); void deleteUploadDefinition(String id); }
对于这个service的实例获取,我们可以通过ContextHolder.getBean(IFileService.BEAN_ID)来实现,或者直接在Spring当中注入ID为bdf2.uploader.fileService的bean。
ILobStoreService则为我们提供了一个将文件存储到BDF2-UPLOADER中特定的数据库表的service,其源码如下:
package com.bstek.bdf2.uploader.service; import java.io.InputStream; import java.io.Reader; import java.sql.SQLException; /** * 大对象仓库服务接口,对外提供二进制数据,超长文本等的储存、更新、取出等操作。 * <p> * 这里的操作分为两组:<br/> * <ol> * <li>byte及binaryStream相关的为第一组,代表了对二进制数据,如图片、声音、视频等的操作,在数据库中通常以BLOB或IMAGE类型表示</li> * <li>string、asciiString及characterStream相关的为第二组,代表了对大文本对象,如文本文件、新闻公告等的操作, * 在数据库中通常以CLOB或TEXT类型表示</li> * </ol> * 需要注意的是,不管为哪种类型的大对象,仓库中均不允许其内容为空, 若业务数据允许引用一个空对象,请将业务表中的允许为空的记录的相关字段设置为NULL。<br/> * 对于不再需要的大对象,请及时使用delete*函数清理,以免造成空间的浪费。<br/> * 此接口中的所有函数均不能保证一定能完成指定的操作,请在使用时处理抛出的异常<br/> * </p> * @author jacky.gao@bstek.com * @since 2.0 */ public interface ILobStoreService { /** * 保存一个byte数组,并返回它在仓库中的主键。 * * @param content * 需要保存的内容,不能为空 * @return 保存后的内容在仓库中的主键,稍后可通过{@link #getBytes(String)}取得仓库中的内容 * @throws SQLException * 将byte数组保存到数据库时可能抛出此异常 */ String storeBytes(byte[] content) throws SQLException; /** * 保存一个byte数组,并返回它在仓库中的主键。 * @param content 需要保存的内容,不能为空 * @param id 需要保存的内容的ID * @throws SQLException * 将byte数组保存到数据库时可能抛出此异常 */ void storeBytes(byte[] content,String id) throws SQLException; /** * 根据内容在仓库中的主键,删除对应的记录。 * * @param id * 需要删除的内容在仓库中的主键 * @throws SQLException * 在从数据库中删除数据时可能抛出此异常; 此外,如果删除不成功,如与主键对应的记录不存在,也抛出此异常。 */ void deleteBytes(String id) throws SQLException; /** * 更新主键所指的内容。 * <p> * 保证此内容的名称<font color="red">不</font>变。 * </p> * * @param id * 需要被更新的内容的主键 * @param content * 新的值 * @throws SQLException * 在更新数据库的过程中可能抛出此异常;此外,如果更新不成功,如与主键对应的记录不存在,也抛出此异常。 */ void updateBytes(String id, byte[] content) throws SQLException; /** * 根据主键查询内容,若未找到,返回<code>null</code>。 * * @param id * 内容的主键,通常由业务记录中的某一字段保有 * @return 与主键相对应的内容 * @throws SQLException * 在查询数据库的过程中可能抛出此异常。 */ byte[] getBytes(String id) throws SQLException; /** * 保存一个二进制数据流中的数据,并返回其在仓库中的主键。 * * @param inputStream * 需要保存的内容,不能为空 * @param contentLength * 内容的长度。如若保存一个{@link java.io.File}对象,则通常此长度由 * {@link java.io.File#length}取得 * @return 保存后的内容在仓库中的主键,稍后可通过{@link #getBinaryStream(String)}取得仓库中的内容 * @throws SQLException */ String storeBinaryStream(InputStream inputStream, int contentLength) throws SQLException; /** * 保存一个二进制数据流中的数据,并返回其在仓库中的主键。 * * @param inputStream * 需要保存的内容,不能为空 * @param contentLength * 内容的长度。如若保存一个{@link java.io.File}对象,则通常此长度由 * {@link java.io.File#length}取得 * @param id * 需要保存的内容ID * @throws SQLException */ void storeBinaryStream(InputStream inputStream, int contentLength,String id) throws SQLException; /** * 根据内容在仓库中的主键,删除对应的记录。 * * @param id * 需要被删除的内容的主键 * @throws SQLException * 在删除数据库中的记录时可能抛出此异常;此外,如果删除不成功,如与主键对应的记录不存在,也抛出此异常。 */ void deleteBinaryStream(String id) throws SQLException; /** * 更新指定主键代表的内容。 * <p> * 保证此内容的名称<font color="red">不</font>变。 * </p> * * @param id * 需要被更新的内容的主键 * @param inputStream * 用于更新内容的二进制输入流 * @param contentLength * 二进制输入流的长度 * @throws SQLException * 在更新数据库中的记录时可能抛出此异常;此外,如果更新不成功,如与主键对应的记录不存在,也抛出此异常。; */ void updateBinaryStream(String id, InputStream inputStream, int contentLength) throws SQLException; /** * 根据主键取得内容的二进制输入流,若未找到,返回<code>null</code>。 * * @param id * 需要查询的内容的主键 * @return 代表内容的二进制输入流 * @throws SQLException * 在查询数据库的过程中可能抛出此异常 */ InputStream getBinaryStream(String id) throws SQLException; /** * 保存一个超长的字符串,并返回其在仓库中的主键。 * * @param content * 需要保存的内容。 * @return 保存后的内容在仓库中的主键,稍后可通过{@link #getString(String)}取得仓库中的内容 * @throws SQLException * 在将内容保存到数据库的过程中可能抛出此异常。 */ String storeString(String content) throws SQLException; /** * 保存一个超长的字符串,并返回其在仓库中的主键。 * * @param content 需要保存的内容。 * @param id 需要保存的内容ID。 * @throws SQLException * 在将内容保存到数据库的过程中可能抛出此异常。 */ void storeString(String content,String id) throws SQLException; /** * 根据内容在仓库中的主键删除对应的记录。 * * @param id * 需要删除的内容在仓库中的主键 * @throws SQLException * 在从数据库中删除记录时可能抛出此异常;此外,如果删除不成功,如与主键对应的记录不存在,也抛出此异常。; */ void deleteString(String id) throws SQLException; /** * 更新指定的主键代表的内容。 * * @param id * 需要更新的内容的主键 * @param content * 用于更新的值 * @throws SQLException * 在更新数据库中的记录时可能抛出此异常;此外,如果更新不成功,如与主键对应的记录不存在,也抛出此异常。 */ void updateString(String id, String content) throws SQLException; /** * 根据主键查询其在仓库中的内容,若未找到,返回<code>null</code>。 * * @param id * 需要查询的内容的主键 * @return 代表查询内容的字符串 * @throws SQLException * 在查询数据库的过程中可能抛出此异常 */ String getString(String id) throws SQLException; /** * 保存一个字节流,并返回其在仓库中的主键。 * * @param asciiSrteam * 需要保存到仓库中的字节流。 * @param contentLength * 字节流的长度。 * @return 保存后的内容在仓库中的主键,稍后可通过{@link #getAsciiStream(String)}取得仓库中的内容 * @throws SQLException * 在将内容保存到数据库的过程中可能抛出此异常。 */ String storeAsciiStream(InputStream asciiSrteam, int contentLength) throws SQLException; /** * 保存一个字节流,并返回其在仓库中的主键。 * * @param asciiSrteam * 需要保存到仓库中的字节流。 * @param contentLength * 字节流的长度。 * @param id 保存的ID * @throws SQLException * 在将内容保存到数据库的过程中可能抛出此异常。 */ void storeAsciiStream(InputStream asciiSrteam, int contentLength,String id) throws SQLException; /** * 根据内容在仓库中的主键删除对应的记录。 * * @param id * 需要删除的内容在仓库中的主键 * @throws SQLException * 在从数据库中删除记录时可能抛出此异常;此外,如果删除不成功,如与主键对应的记录不存在,也抛出此异常。; */ void deleteAsciiStream(String id) throws SQLException; /** * 更新指定的主键代表的内容。 * * @param id * 需要更新的内容的主键 * @param asciiStream * 用于更新的字节流 * @param contentLength * 字节流的长度 * @throws SQLException * 在更新数据库中的记录时可能抛出此异常;此外,如果更新不成功,如与主键对应的记录不存在,也抛出此异常。; */ void updateAsciiStream(String id, InputStream asciiStream, int contentLength) throws SQLException; /** * 根据主键查询其在仓库中的内容,若未找到,返回<code>null</code>。 * * @param id * 需要查询的内容的主键 * @return 代表内容的字节流 * @throws SQLException * 在查询数据库的过程中可能抛出此异常 */ InputStream getAsciiStream(String id) throws SQLException; /** * 保存一个字符流,并返回其在仓库中的主键。 * * @param reader * 需要保存的字符流 * @param contentLength * 字符流的长度 * @return 保存后的内容在仓库中的主键,稍后可通过{@link #getCharacterStream(String)}取得仓库中的内容 * @throws SQLException * 在将内容保存到数据库的过程中可能抛出此异常。 */ String storeCharacterStream(Reader reader, int contentLength) throws SQLException; /** * 保存一个字符流,并返回其在仓库中的主键。 * * @param reader * 需要保存的字符流 * @param contentLength * 字符流的长度 * @param reader * 需要保存的字符流ID * @throws SQLException * 在将内容保存到数据库的过程中可能抛出此异常。 */ void storeCharacterStream(Reader reader, int contentLength,String id) throws SQLException; /** * 根据内容在仓库中的主键删除对应的记录。 * * @param id * 需要删除的内容在仓库中的主键 * @throws SQLException * 在从数据库中删除记录时可能抛出此异常;此外,如果删除不成功,如与主键对应的记录不存在,也抛出此异常。; */ void deleteCharacterStream(String id) throws SQLException; /** * 更新指定的主键代表的内容。 * * @param id * 需要更新的内容的主键 * @param reader * 用于更新的字符流 * @param contentLength * 字符流的长度 * @throws SQLException * 在更新数据库中的记录时可能抛出此异常;此外,如果更新不成功,如与主键对应的记录不存在,也抛出此异常。; */ void updateCharacterStream(String id, Reader reader, int contentLength) throws SQLException; /** * 根据主键查询其在仓库中的内容,若未找到,返回<code>null</code>。 * * @param id * 需要查询的内容的主键 * @return 代表内容的字符流 * @throws SQLException * 在查询数据库的过程中可能抛出此异常 */ Reader getCharacterStream(String id) throws SQLException; /** * 服务ID,代表了其在spring中的beanName。 */ public static final String BEAN_ID = "bdf2.lobStoreService"; }
同样对于这个service的实例获取,我们可以通过ContextHolder.getBean(ILobStoreService.BEAN_ID)来实现,或者直接在Spring当中注入ID为bdf2.lobStoreService的bean。