提交 b7e93988 编写于 作者: 楼国栋

Merge branch 'feature/ws_im_attachment' into 'develop'

im消息添加文件上传下载功能

See merge request o2oa/o2oa!721
package com.x.base.core.entity;
public enum StorageType {
file, processPlatform, mind, meeting, calendar, okr, cms, bbs, report, strategyDeploy, teamwork, structure;
file, processPlatform, mind, meeting, calendar, okr, cms, bbs, report, strategyDeploy, teamwork, structure, im;
public static final int length = JpaObject.length_32B;
}
\ No newline at end of file
......@@ -8,7 +8,7 @@ import com.x.base.core.project.annotation.ModuleType;
"com.x.message.core.entity.Instant", "com.x.message.core.entity.Message",
"com.x.message.core.entity.Mass", "com.x.message.core.entity.Org",
"com.x.message.core.entity.IMMsg", "com.x.message.core.entity.IMConversation",
"com.x.message.core.entity.IMConversationExt" }, storeJars = { "x_message_core_entity", "x_meeting_core_entity",
"com.x.message.core.entity.IMConversationExt", "com.x.message.core.entity.IMMsgFile" }, storeJars = { "x_message_core_entity", "x_meeting_core_entity",
"x_processplatform_core_entity", "x_organization_core_express" })
public class x_message_assemble_communicate extends Deployable {
}
package com.x.message.assemble.communicate.jaxrs.im;
import com.x.base.core.container.EntityManagerContainer;
import com.x.base.core.container.factory.EntityManagerContainerFactory;
import com.x.base.core.project.cache.ApplicationCache;
import com.x.base.core.project.config.StorageMapping;
import com.x.base.core.project.http.ActionResult;
import com.x.base.core.project.http.EffectivePerson;
import com.x.base.core.project.jaxrs.WoFile;
import com.x.base.core.project.logger.Logger;
import com.x.base.core.project.logger.LoggerFactory;
import com.x.message.assemble.communicate.ThisApplication;
import com.x.message.core.entity.IMMsgFile;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import java.io.ByteArrayOutputStream;
/**
* Created by fancyLou on 2020-06-15.
* Copyright © 2020 O2. All rights reserved.
*/
public class ActionFileDownload extends BaseAction {
private static Logger logger = LoggerFactory.getLogger( ActionFileDownload.class );
private Ehcache cache = ApplicationCache.instance().getCache(ActionFileDownload.class);
ActionResult<Wo> execute(EffectivePerson effectivePerson, String id) throws Exception {
try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
ActionResult<Wo> result = new ActionResult<>();
Wo wo = null;
/** 确定是否要用application/octet-stream输出 */
IMMsgFile file = emc.find(id, IMMsgFile.class);
if (null == file) {
throw new ExceptionFileNotExist(id);
}
String cacheKey = ApplicationCache.concreteCacheKey(this.getClass(), id);
Element element = cache.get(cacheKey);
if ((null != element) && (null != element.getObjectValue())) {
wo = (Wo) element.getObjectValue();
} else {
StorageMapping mapping = ThisApplication.context().storageMappings().get(IMMsgFile.class, file.getStorage());
if (null == mapping) {
throw new ExceptionStorageNotExist(file.getStorage());
}
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
file.readContent(mapping, os);
byte[] bs = os.toByteArray();
wo = new Wo(bs, this.contentType(false, file.getName()),
this.contentDisposition(false, file.getName()));
/**
* 对10M以下的文件进行缓存
*/
if (bs.length < (1024 * 1024 * 10)) {
cache.put(new Element(cacheKey, wo));
}
}catch (Exception e){
if(e.getMessage().indexOf("existed") > -1){
logger.warn("原始附件{}-{}不存在,删除记录!", file.getId(), file.getName());
emc.beginTransaction(IMMsgFile.class);
emc.delete(IMMsgFile.class, file.getId());
emc.commit();
}
throw e;
}
}
result.setData(wo);
return result;
}
}
public static class Wo extends WoFile {
public Wo(byte[] bytes, String contentType, String contentDisposition) {
super(bytes, contentType, contentDisposition);
}
}
}
package com.x.message.assemble.communicate.jaxrs.im;
import com.x.base.core.container.EntityManagerContainer;
import com.x.base.core.container.factory.EntityManagerContainerFactory;
import com.x.base.core.project.cache.ApplicationCache;
import com.x.base.core.project.config.StorageMapping;
import com.x.base.core.project.http.ActionResult;
import com.x.base.core.project.http.EffectivePerson;
import com.x.base.core.project.jaxrs.WoFile;
import com.x.base.core.project.logger.Logger;
import com.x.base.core.project.logger.LoggerFactory;
import com.x.message.assemble.communicate.ThisApplication;
import com.x.message.core.entity.IMMsgFile;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.imgscalr.Scalr;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
/**
* Created by fancyLou on 2020-06-15.
* Copyright © 2020 O2. All rights reserved.
*/
public class ActionImageDownloadWidthHeight extends BaseAction {
private static Logger logger = LoggerFactory.getLogger( ActionImageDownloadWidthHeight.class );
private Ehcache cache = ApplicationCache.instance().getCache(ActionImageDownloadWidthHeight.class);
ActionResult<Wo> execute(EffectivePerson effectivePerson, String id, Integer width, Integer height) throws Exception {
try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
ActionResult<Wo> result = new ActionResult<>();
Wo wo = null;
/** 确定是否要用application/octet-stream输出 */
IMMsgFile file = emc.find(id, IMMsgFile.class);
if (null == file) {
throw new ExceptionFileNotExist(id);
}
if (!ArrayUtils.contains(IMAGE_EXTENSIONS, file.getExtension())) {
throw new Exception("file is not image file.");
}
if (width < 0 || width > 5000) {
throw new Exception("invalid width:" + width + ".");
}
if (height < 0 || height > 5000) {
throw new Exception("invalid height:" + height + ".");
}
String cacheKey = ApplicationCache.concreteCacheKey(this.getClass(), id+width+height);
Element element = cache.get(cacheKey);
if ((null != element) && (null != element.getObjectValue())) {
wo = (Wo) element.getObjectValue();
result.setData(wo);
} else {
StorageMapping mapping = ThisApplication.context().storageMappings().get(IMMsgFile.class, file.getStorage());
if (null == mapping) {
throw new ExceptionStorageNotExist(file.getStorage());
}
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
file.readContent(mapping, os);
try (ByteArrayInputStream input = new ByteArrayInputStream(os.toByteArray())) {
BufferedImage src = ImageIO.read(input);
int scalrWidth = (width == 0) ? src.getWidth() : width;
int scalrHeight = (height == 0) ? src.getHeight() : height;
Scalr.Mode mode = Scalr.Mode.FIT_TO_WIDTH;
if(src.getWidth()>src.getHeight()){
mode = Scalr.Mode.FIT_TO_HEIGHT;
}
BufferedImage scalrImage = Scalr.resize(src,Scalr.Method.SPEED, mode, NumberUtils.min(scalrWidth, src.getWidth()),
NumberUtils.min(scalrHeight, src.getHeight()));
try (org.apache.commons.io.output.ByteArrayOutputStream baos = new org.apache.commons.io.output.ByteArrayOutputStream()) {
ImageIO.write(scalrImage, "png", baos);
byte[] bs = baos.toByteArray();
wo = new Wo(bs, this.contentType(false, file.getName()),
this.contentDisposition(false, file.getName()));
cache.put(new Element(cacheKey, wo));
result.setData(wo);
}
}
}catch (Exception e){
if(e.getMessage().indexOf("existed") > -1){
logger.warn("原始附件{}-{}不存在,删除记录!", file.getId(), file.getName());
emc.beginTransaction(IMMsgFile.class);
emc.delete(IMMsgFile.class, file.getId());
emc.commit();
}
throw e;
}
}
return result;
}
}
public static class Wo extends WoFile {
public Wo(byte[] bytes, String contentType, String contentDisposition) {
super(bytes, contentType, contentDisposition);
}
}
}
package com.x.message.assemble.communicate.jaxrs.im;
import com.x.base.core.container.EntityManagerContainer;
import com.x.base.core.container.factory.EntityManagerContainerFactory;
import com.x.base.core.entity.annotation.CheckPersistType;
import com.x.base.core.project.config.StorageMapping;
import com.x.base.core.project.http.ActionResult;
import com.x.base.core.project.http.EffectivePerson;
import com.x.base.core.project.jaxrs.WoId;
import com.x.base.core.project.tools.DefaultCharset;
import com.x.message.assemble.communicate.ThisApplication;
import com.x.message.core.entity.IMMsgFile;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import java.util.Date;
/**
* Created by fancyLou on 2020-06-15.
* Copyright © 2020 O2. All rights reserved.
*/
public class ActionUploadFile extends BaseAction {
public ActionResult<Wo> execute(EffectivePerson effectivePerson, String conversationId, String type, byte[] bytes,
FormDataContentDisposition disposition) throws Exception {
try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
ActionResult<Wo> result = new ActionResult<>();
StorageMapping mapping = ThisApplication.context().storageMappings().random(IMMsgFile.class);
if (null == mapping) {
throw new ExceptionAllocateStorageMaaping();
}
String fileName = "";
/** 文件名编码转换 */
if (StringUtils.isEmpty(fileName)) {
fileName = new String(disposition.getFileName().getBytes(DefaultCharset.charset_iso_8859_1),
DefaultCharset.charset);
}
fileName = FilenameUtils.getName(fileName);
/** 禁止不带扩展名的文件上传 */
if (StringUtils.isEmpty(FilenameUtils.getExtension(fileName))) {
throw new ExceptionEmptyExtension(fileName);
}
if(bytes==null){
throw new ExceptionAttachmentNone(fileName);
}
IMMsgFile file = new IMMsgFile();
file.setName(fileName);
file.setStorage(mapping.getName());
file.setPerson(effectivePerson.getDistinguishedName());
Date now = new Date();
file.setCreateTime(now);
file.setLastUpdateTime(now);
file.setExtension(StringUtils.lowerCase(FilenameUtils.getExtension(fileName)));
file.setConversationId(conversationId);
file.setType(type);
emc.check(file, CheckPersistType.all);
file.saveContent(mapping, bytes, fileName);
emc.beginTransaction(IMMsgFile.class);
emc.persist(file);
emc.commit();
Wo wo = new Wo();
wo.setId(file.getId());
result.setData(wo);
return result;
}
}
public static class Wo extends WoId {
}
}
package com.x.message.assemble.communicate.jaxrs.im;
import com.x.base.core.project.exception.PromptException;
class ExceptionAllocateStorageMaaping extends PromptException {
private static final long serialVersionUID = 4132300948670472899L;
ExceptionAllocateStorageMaaping() {
super("无法分派存储器.");
}
}
package com.x.message.assemble.communicate.jaxrs.im;
import com.x.base.core.project.exception.PromptException;
class ExceptionAttachmentNone extends PromptException {
private static final long serialVersionUID = 4132300948670472899L;
ExceptionAttachmentNone(String name) {
super("未上传附件: {}.", name);
}
}
package com.x.message.assemble.communicate.jaxrs.im;
import com.x.base.core.project.exception.PromptException;
class ExceptionEmptyExtension extends PromptException {
private static final long serialVersionUID = 4132300948670472899L;
ExceptionEmptyExtension(String name) {
super("不能上传文件扩展名为空的文件: {}.", name);
}
ExceptionEmptyExtension(String message, String name) {
super(message, name);
}
}
package com.x.message.assemble.communicate.jaxrs.im;
import com.x.base.core.project.exception.PromptException;
class ExceptionFileNotExist extends PromptException {
private static final long serialVersionUID = 7750207007061165350L;
ExceptionFileNotExist(String id) {
super("指定的文件: {} 不存在.", id);
}
ExceptionFileNotExist(String id, String fileId) {
super("指定的文件: {} 的附件{}不存在.", id, fileId);
}
}
package com.x.message.assemble.communicate.jaxrs.im;
import com.x.base.core.project.exception.PromptException;
class ExceptionStorageNotExist extends PromptException {
private static final long serialVersionUID = 4132300948670472899L;
ExceptionStorageNotExist(String name) {
super("无法找到存储器: {}.", name);
}
}
......@@ -12,6 +12,8 @@ import com.x.base.core.project.jaxrs.StandardJaxrsAction;
import com.x.base.core.project.jaxrs.WoId;
import com.x.base.core.project.logger.Logger;
import com.x.base.core.project.logger.LoggerFactory;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
......@@ -189,4 +191,63 @@ public class ImAction extends StandardJaxrsAction {
}
@JaxrsMethodDescribe(value = "上传文件.", action = ActionUploadFile.class)
@POST
@Path("msg/upload/{conversationId}/type/{type}")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
public void uploadFile(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
@JaxrsParameterDescribe("会话id") @PathParam("conversationId") String conversationId,
@JaxrsParameterDescribe("文件类型") @PathParam("type") String type,
@JaxrsParameterDescribe("附件标识") @FormDataParam(FILE_FIELD) final byte[] bytes,
@JaxrsParameterDescribe("上传文件") @FormDataParam(FILE_FIELD) final FormDataContentDisposition disposition){
ActionResult<ActionUploadFile.Wo> result = new ActionResult<>();
EffectivePerson effectivePerson = this.effectivePerson(request);
try {
result = new ActionUploadFile().execute(effectivePerson, conversationId, type, bytes, disposition);
} catch (Exception e) {
logger.error(e, effectivePerson, request, null);
result.error(e);
}
asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
}
@JaxrsMethodDescribe(value = "获取文件内容,输出头信息", action = ActionFileDownload.class)
@GET
@Path("msg/download/{id}")
@Consumes(MediaType.APPLICATION_JSON)
public void download(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
@JaxrsParameterDescribe("附件标识") @PathParam("id") String id) {
ActionResult<ActionFileDownload.Wo> result = new ActionResult<>();
EffectivePerson effectivePerson = this.effectivePerson(request);
try {
result = new ActionFileDownload().execute(effectivePerson, id);
} catch (Exception e) {
logger.error(e, effectivePerson, request, null);
result.error(e);
}
asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
}
@JaxrsMethodDescribe(value = "下载图片设定宽高后的(png格式).width(0-5000)像素,0代表不限制,height(0-5000)像素,0代表不限制.", action = ActionImageDownloadWidthHeight.class)
@GET
@Path("msg/download/{id}/image/width/{width}/height/{height}")
@Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
@Consumes(MediaType.APPLICATION_JSON)
public void downloadImageWidthHeight(@Suspended final AsyncResponse asyncResponse,
@Context HttpServletRequest request, @JaxrsParameterDescribe("附件标识") @PathParam("id") String id,
@JaxrsParameterDescribe("宽度") @PathParam("width") Integer width,
@JaxrsParameterDescribe("高度") @PathParam("height") Integer height) {
ActionResult<ActionImageDownloadWidthHeight.Wo> result = new ActionResult<>();
EffectivePerson effectivePerson = this.effectivePerson(request);
try {
result = new ActionImageDownloadWidthHeight().execute(effectivePerson, id, width, height);
} catch (Exception e) {
logger.error(e, effectivePerson, request, null);
result.error(e);
}
asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
}
}
package com.x.message.core.entity;
import com.x.base.core.entity.JpaObject;
import com.x.base.core.entity.Storage;
import com.x.base.core.entity.StorageObject;
import com.x.base.core.entity.StorageType;
import com.x.base.core.entity.annotation.CheckPersist;
import com.x.base.core.entity.annotation.ContainerEntity;
import com.x.base.core.project.annotation.FieldDescribe;
import com.x.base.core.project.tools.DateTools;
import org.apache.commons.lang3.StringUtils;
import org.apache.openjpa.persistence.jdbc.Index;
import javax.persistence.*;
import java.util.Date;
/**
* Created by fancyLou on 2020-06-15.
* Copyright © 2020 O2. All rights reserved.
*/
@ContainerEntity(dumpSize = 1000, type = ContainerEntity.Type.content, reference = ContainerEntity.Reference.strong)
@Entity
@Table(name = PersistenceProperties.IMMsgFile.table, uniqueConstraints = {
@UniqueConstraint(name = PersistenceProperties.IMMsgFile.table + JpaObject.IndexNameMiddle
+ JpaObject.DefaultUniqueConstraintSuffix, columnNames = { JpaObject.IDCOLUMN,
JpaObject.CREATETIMECOLUMN, JpaObject.UPDATETIMECOLUMN, JpaObject.SEQUENCECOLUMN }) })
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Storage(type = StorageType.im)
public class IMMsgFile extends StorageObject {
private static final long serialVersionUID = 492931147504877023L;
private static final String TABLE = PersistenceProperties.IMMsgFile.table;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@FieldDescribe("数据库主键,自动生成.")
@Id
@Column(length = length_id, name = ColumnNamePrefix + id_FIELDNAME)
private String id = createId();
/* 以上为 JpaObject 默认字段 */
public void onPersist() throws Exception {
}
public static final String person_FIELDNAME = "person";
@FieldDescribe("上传用户.")
@Column(length = length_255B, name = ColumnNamePrefix + person_FIELDNAME)
@Index(name = TABLE + IndexNameMiddle + person_FIELDNAME)
@CheckPersist(allowEmpty = true)
private String person;
public static final String name_FIELDNAME = "name";
@FieldDescribe("文件名称.")
@Column(length = length_255B, name = ColumnNamePrefix + name_FIELDNAME)
@Index(name = TABLE + IndexNameMiddle + name_FIELDNAME)
@CheckPersist(allowEmpty = false, fileNameString = true)
private String name;
public static final String storage_FIELDNAME = "storage";
@FieldDescribe("存储器的名称,也就是多个存放节点的名字.")
@Column(length = JpaObject.length_64B, name = ColumnNamePrefix + storage_FIELDNAME)
@CheckPersist(allowEmpty = false, simplyString = true)
@Index(name = TABLE + IndexNameMiddle + storage_FIELDNAME)
private String storage;
public static final String extension_FIELDNAME = "extension";
@FieldDescribe("扩展名,必须要有扩展名的文件才允许上传.")
@Column(length = JpaObject.length_64B, name = ColumnNamePrefix + extension_FIELDNAME)
@CheckPersist(allowEmpty = false, fileNameString = true)
private String extension;
public static final String lastUpdateTime_FIELDNAME = "lastUpdateTime";
@FieldDescribe("最后更新时间")
@Column(name = ColumnNamePrefix + lastUpdateTime_FIELDNAME)
@Index(name = TABLE + IndexNameMiddle + lastUpdateTime_FIELDNAME)
@CheckPersist(allowEmpty = false)
private Date lastUpdateTime;
public static final String deepPath_FIELDNAME = "deepPath";
@FieldDescribe("是否使用更深的路径.")
@CheckPersist(allowEmpty = true)
@Column(name = ColumnNamePrefix + deepPath_FIELDNAME)
@Index(name = TABLE + IndexNameMiddle + deepPath_FIELDNAME)
private Boolean deepPath;
public static final String length_FIELDNAME = "length";
@FieldDescribe("文件大小.")
@Column(name = ColumnNamePrefix + length_FIELDNAME)
@CheckPersist(allowEmpty = true)
private Long length;
@Override
public String path() throws Exception {
if (null == this.conversationId) {
throw new Exception("conversationId can not be null.");
}
if (StringUtils.isEmpty(type)) {
throw new Exception("type can not be empty.");
}
String str = this.conversationId;
str += PATHSEPARATOR;
str += DateTools.format(this.getCreateTime(), DateTools.formatCompact_yyyyMMdd);
str += PATHSEPARATOR;
str += this.type;
str += PATHSEPARATOR;
str += this.id;
str += StringUtils.isEmpty(this.extension) ? "" : ("." + this.extension);
return str;
}
public static final String conversationId_FIELDNAME = "conversationId";
@FieldDescribe("会话id, 关联的会话.")
@Column(length = length_64B, name = ColumnNamePrefix + conversationId_FIELDNAME)
@CheckPersist(allowEmpty = false)
private String conversationId;
public static final String type_FIELDNAME = "type";
@FieldDescribe("文件分类,对应消息类型.")
@Column(length = JpaObject.length_64B, name = ColumnNamePrefix + type_FIELDNAME)
@CheckPersist(allowEmpty = false)
private String type;
public String getPerson() {
return person;
}
public void setPerson(String person) {
this.person = person;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getStorage() {
return storage;
}
@Override
public void setStorage(String storage) {
this.storage = storage;
}
@Override
public String getExtension() {
return extension;
}
@Override
public void setExtension(String extension) {
this.extension = extension;
}
@Override
public Date getLastUpdateTime() {
return lastUpdateTime;
}
@Override
public void setLastUpdateTime(Date lastUpdateTime) {
this.lastUpdateTime = lastUpdateTime;
}
@Override
public Boolean getDeepPath() {
return deepPath;
}
@Override
public void setDeepPath(Boolean deepPath) {
this.deepPath = deepPath;
}
public String getConversationId() {
return conversationId;
}
public void setConversationId(String conversationId) {
this.conversationId = conversationId;
}
@Override
public Long getLength() {
return length;
}
@Override
public void setLength(Long length) {
this.length = length;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
......@@ -32,4 +32,7 @@ public final class PersistenceProperties extends AbstractPersistenceProperties {
public static class IMMsg {
public static final String table = "MSG_IM_MESSAGE";
}
public static class IMMsgFile {
public static final String table = "MSG_IM_MESSAGE_FILE";
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册