play框架使用起来(13)
Posted zyhlal
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了play框架使用起来(13)相关的知识,希望对你有一定的参考价值。
高级指南
1、文件上传
1.1 架构考虑#
应用中通常有两种方式来保存二进制数据:将数据保存到服务器的文件系统中,或者直接保存到数据库中。当然这两种实现各有利弊,使用文件系统非常容易,而使用数据库则具有事务处理支持,但两者都有通病,那就是很难扩展。
这一节需要向读者着重强调的是,Play中提供的play.db.jpa.Blob类型,与java.sql.Blob类型有很大的区别。在Play中声明为play.db.jpa.Blob类型的属性默认将数据存储在数据库之外的文件当中,并没有在数据库中使用带有BLOB的java.sql.Blob类型。在服务器端,Play默认将上传的文件存储到应用的data/attachments/目录下。文件名称(UUID)和MIME类型拼接的字符串被作为SQL中VARCHAR类型存储在数据库属性中。
如果需要将数据存储到数据库表中,可以将@javax.persistence.Lob注解添加到模型属性前。带有@Lob注解的数据便会作为BLOB或者CLOB类型存储到数据表中。
1.2 上传文件并存储到服务器中#
在Play中实现文件的上传,存储和共享都非常简单,因为框架自动将html表单中提交的文件绑定到JPA模型中,而且Play提供的便捷的方法使得共享文件如同显示文本一样简单。
首先,在应用中定义User模型,该模型用于存储上传文件。将photo属性定义为play.db.jpa.Blob类型:
package models;
import play.db.jpa.Blob;
import play.db.jpa.Model;
import javax.persistence.Entity;
@Entity
public class User extends Model
public Blob photo;
模型定义完成后,在视图文件中添加上传文件的表单。为了绑定JPA模型,上传文件的name属性设置为user.photo:
#form @addUser(), enctype:'multipart/form-data'
<input type="file" name="user.photo">
<input type="submit" name="submit" value="Upload">
#/form
在控制器中添加相应的Action方法,使用新建的模型对象来保存上传的文件:
public static void addUser(User user)
user.save();
index();
因为文件上传是由框架自动处理的,所以代码看起来除了保存JPA实体以外,并没有做其他事情。实际上,框架内部帮我们完成了如下一系列的操作:首先,在Action方法执行之前,上传的文件会被保存到应用的tmp/uploads子目录。当实体保存之后,上传的文件使用UUID命名并被复制到data/attachments目录下。最后当Action方法执行完毕时,删除临时文件。
如果需要将文件保存到其他目录,可以在conf/application.conf文件中指定路径,可以是绝对路径,也可以是Play应用目录的相对路径:
attachments.path=photos
为了使上传的图片更加直观,我们为模板文件中增加<img>标签,用于显示上传的图片:
#list items:models.User.findAll(), as:'user'
<img src="@userPhoto(user.id)">
#/list
最后,在控制器中增加Action方法,用于加载模型并返回上传的图片:
public static void userPhoto(long id)以上就是Play中实现的简单的文件上传功能。读者会发现,我们并没有写很多的业务代码,也没有做过多的配置处理,只不过几句方法调用就完成了文件上传的功能。这就是Play提倡的设计哲学:简单并且高效。
final User user = User.findById(id);
notFoundIfNull(user);
response.setContentTypeIfNotSet(user.photo.type());
renderBinary(user.photo.get());
文件上传
1.3 更新上传#
如果读者希望更新上传的文件,也很容易,只需在请求参数中提供user.id,就可以使用save()方法来更新实体:
public static void updateUser(User user)
user.save();
index();
上传的更新文件会以新的UUID命名并保存,这意味着原始的文件会失去关联。如果服务器没有足够的空间,那么就必须实现清理策略。本节介绍以下几种可行的方案:
- 比较蛮力的方式是使用异步Job定期查询所有当前使用文件类型的JPA模型,扫描attachments文件夹,然后删除没有关联的文件。这种方法很难在大规模的应用中使用。
- 针对某些应用,在模型中维护所有文件之前版本的引用是非常有意义的。这样我们可以在用户界面显示这些旧版本的文件信息,就像wiki通常会保存每个页面之前的版本那样。清理工作可以是手动,也可以是新文件上传或者异步Job进行触发。清理的策略可以是保存一定数量的版本,或者删除指定日期之前的版本。使用@PreUpdate和@PreRemove JPA拦截器能够很好地处理以上问题。
- 比较极端的做法是直接使用BinaryField字段来实现具有事务的上传更新。
1.4 文件删除#
如果我们删除带有play.db.jpa.Blob属性的对象,attachments文件夹中的文件不会自动删除,我们需要通过引用java.io.File属性来手动进行删除,具体操作如下:
public static void deleteUser(long id)
final User user = User.findById(id);
user.photo.getFile().delete();
user.delete();
index();
我们也可以将文件删除封装到模型里,只要在User.java中覆盖_delete()方法,就可以在数据库实体成功删除后自动执行文件删除操作。
@Override
public void _delete()
super._delete();
photo.getFile().delete();
1.5 上传文件并保存文件名#
如果读者需要保存原始上传文件的名称,我们可以在服务器端将文件扩展名映射为MIME类型,这样就可以以原始文件名的方式保存文件了。
我们需要将表单控制绑定到具有java.io.File类型的Action方法参数中以便得到文件名,这意味着我们需要在控制器中创建一个新的Action方法,将单独的表单参数构建为模型对象,而不是像第一个例子那样直接绑定模型对象。
首先,在User模型中添加photoFileName属性:
@Entity
public class User extends Model
public String photoFileName;
public Blob photo;
新建Action方法addUserWithFileName(),实例化模型对象以及初始化Blob属性:
public static void addUserWithFileName(File photo) throws FileNotFoundException
final User user = new User();
user.photoFileName = photo.getName();
user.photo = new Blob();
user.photo.set(new FileInputStream(photo),
MimeTypes.getContentType(photo.getName());
user.save();
index();
接着修改视图层的模版,显示上传后的图片文件名称。由于使用新的控制器方法为addUserWithFileName(),文件上传控件的名称也需要做相应的改变(将user.photo改成photo):
#list items:models.User.findAll(), as:'user'
<img title="$user.photoFileName" src="@userPhoto(user.id)">
#/list
#form @addUserWithFileName(), enctype:'multipart/form-data'
<input type="file" name="photo">
<input type="submit" name="submit" value="Upload">
#/form
1.6下载文件#
当我们访问二进制数据(比如图片)时,可能会以正常的方式直接在浏览器中显示。比如之前通过URL访问的图片资源就是直接在浏览器中显示的。但是,我们可以设置HTTP头来通知浏览器将文件作为附件的形式下载到用户的计算机中,而不是直接显式地在浏览器中呈现。
首先,创建实现下载功能的Action方法。假设文件名已经被正确设置,实现下载功能的Action方法与userPhoto()方法唯一不同的地方是需要将文件名作为renderBinary()方法的参数传递,这样Play就会设置Content-Disposition响应头来提供文件名。
public static void downloadUserPhoto(long id)
final User user = User.findById(id);
notFoundIfNull(user);
response.setContentTypeIfNotSet(user.photo.play框架使用起来(14)-高级指南