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) 
   
final User user = User.findById(id);
   notFoundIfNull
(user);
   response
.setContentTypeIfNotSet(user.photo.type());
   renderBinary
(user.photo.get());
      以上就是Play中实现的简单的文件上传功能。读者会发现,我们并没有写很多的业务代码,也没有做过多的配置处理,只不过几句方法调用就完成了文件上传的功能。这就是Play提倡的设计哲学:简单并且高效。
文件上传

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)-高级指南

play框架使用起来(14)-高级指南

play框架使用起来

play框架使用起来(16)

play框架使用起来

play框架用起来