提交 77553fa2 编写于 作者: O o2sword

流程正文签批接口

上级 e1d590ce
......@@ -13,7 +13,8 @@ import com.x.base.core.project.annotation.ModuleType;
"com.x.processplatform.core.entity.content.Task", "com.x.processplatform.core.entity.content.TaskCompleted",
"com.x.processplatform.core.entity.content.Work", "com.x.processplatform.core.entity.content.WorkCompleted",
"com.x.processplatform.core.entity.content.WorkLog", "com.x.processplatform.core.entity.content.Record",
"com.x.processplatform.core.entity.content.KeyLock",
"com.x.processplatform.core.entity.content.KeyLock", "com.x.processplatform.core.entity.content.DocSign",
"com.x.processplatform.core.entity.content.DocSignScrawl",
"com.x.processplatform.core.entity.content.DocumentVersion", "com.x.processplatform.core.entity.element.Agent",
"com.x.processplatform.core.entity.element.Application",
"com.x.processplatform.core.entity.element.ApplicationDict",
......@@ -30,7 +31,7 @@ import com.x.base.core.project.annotation.ModuleType;
"com.x.processplatform.core.entity.element.QueryView", "com.x.processplatform.core.entity.element.QueryStat",
"com.x.processplatform.core.entity.element.Mapping", "com.x.query.core.entity.Item",
"com.x.cms.core.entity.element.Script", "com.x.portal.core.entity.Script",
"com.x.general.core.entity.GeneralFile" }, storageTypes = { StorageType.processPlatform,
"com.x.general.core.entity.GeneralFile"}, storageTypes = { StorageType.processPlatform,
StorageType.general }, storeJars = { "x_organization_core_entity", "x_organization_core_express",
"x_processplatform_core_entity", "x_processplatform_core_express", "x_query_core_entity",
"x_cms_core_entity", "x_portal_core_entity", "x_general_core_entity" })
......
......@@ -15,6 +15,7 @@ import com.x.base.core.project.annotation.ModuleType;
"com.x.processplatform.core.entity.content.Task", "com.x.processplatform.core.entity.content.Work",
"com.x.processplatform.core.entity.content.Read", "com.x.processplatform.core.entity.content.DocumentVersion",
"com.x.processplatform.core.entity.content.SerialNumber", "com.x.processplatform.core.entity.element.End",
"com.x.processplatform.core.entity.content.DocSign", "com.x.processplatform.core.entity.content.DocSignScrawl",
"com.x.processplatform.core.entity.element.Application",
"com.x.processplatform.core.entity.element.ApplicationDict",
"com.x.processplatform.core.entity.element.ApplicationDictItem",
......
......@@ -27,6 +27,7 @@ import com.x.processplatform.assemble.surface.jaxrs.route.RouteAction;
import com.x.processplatform.assemble.surface.jaxrs.script.ScriptAction;
import com.x.processplatform.assemble.surface.jaxrs.serialnumber.SerialNumberAction;
import com.x.processplatform.assemble.surface.jaxrs.service.ServiceAction;
import com.x.processplatform.assemble.surface.jaxrs.sign.SignAction;
import com.x.processplatform.assemble.surface.jaxrs.snap.SnapAction;
import com.x.processplatform.assemble.surface.jaxrs.task.TaskAction;
import com.x.processplatform.assemble.surface.jaxrs.taskcompleted.TaskCompletedAction;
......@@ -67,6 +68,7 @@ public class ActionApplication extends AbstractActionApplication {
classes.add(DraftAction.class);
classes.add(SnapAction.class);
classes.add(AnonymousAction.class);
classes.add(SignAction.class);
return classes;
}
}
package com.x.processplatform.assemble.surface.jaxrs;
import com.x.base.core.project.jaxrs.CipherManagerUserJaxrsFilter;
import javax.servlet.annotation.WebFilter;
@WebFilter(urlPatterns = "/jaxrs/sign/*", asyncSupported = true)
public class SignJaxrsFilter extends CipherManagerUserJaxrsFilter {
}
package com.x.processplatform.assemble.surface.jaxrs.sign;
import com.x.base.core.container.EntityManagerContainer;
import com.x.base.core.container.factory.EntityManagerContainerFactory;
import com.x.base.core.project.annotation.ActionLogger;
import com.x.base.core.project.config.StorageMapping;
import com.x.base.core.project.exception.ExceptionAccessDenied;
import com.x.base.core.project.exception.ExceptionEntityNotExist;
import com.x.base.core.project.http.ActionResult;
import com.x.base.core.project.http.EffectivePerson;
import com.x.base.core.project.jaxrs.WrapBoolean;
import com.x.base.core.project.logger.Logger;
import com.x.base.core.project.logger.LoggerFactory;
import com.x.base.core.project.tools.ListTools;
import com.x.processplatform.assemble.surface.Business;
import com.x.processplatform.assemble.surface.ThisApplication;
import com.x.processplatform.core.entity.content.DocSign;
import com.x.processplatform.core.entity.content.DocSignScrawl;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
class ActionDelete extends BaseAction {
@ActionLogger
private static Logger logger = LoggerFactory.getLogger(ActionDelete.class);
ActionResult<Wo> execute(EffectivePerson effectivePerson, String id) throws Exception {
logger.debug(effectivePerson.getDistinguishedName());
try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
ActionResult<Wo> result = new ActionResult<>();
Business business = new Business(emc);
DocSign docSign = emc.find(id, DocSign.class);
if (null == docSign) {
throw new ExceptionEntityNotExist(id, DocSign.class);
}
if(!business.canManageApplication(effectivePerson, null) &&
!docSign.getPerson().equals(effectivePerson.getDistinguishedName())){
throw new ExceptionAccessDenied(effectivePerson, id);
}
List<DocSignScrawl> signScrawlList = emc.listEqual(DocSignScrawl.class, DocSignScrawl.signId_FIELDNAME, docSign.getId());
if(ListTools.isNotEmpty(signScrawlList)) {
emc.beginTransaction(DocSignScrawl.class);
for (DocSignScrawl signScrawl : signScrawlList) {
if(StringUtils.isNotBlank(signScrawl.getStorage())) {
StorageMapping mapping = ThisApplication.context().storageMappings().get(DocSignScrawl.class, signScrawl.getStorage());
signScrawl.deleteContent(mapping);
}
emc.remove(signScrawl);
}
}
emc.beginTransaction(DocSign.class);
emc.remove(docSign);
emc.commit();
Wo wo = new Wo();
wo.setValue(true);
result.setData(wo);
return result;
}
}
public static class Wo extends WrapBoolean {
}
}
package com.x.processplatform.assemble.surface.jaxrs.sign;
import com.x.base.core.container.EntityManagerContainer;
import com.x.base.core.container.factory.EntityManagerContainerFactory;
import com.x.base.core.project.annotation.ActionLogger;
import com.x.base.core.project.config.StorageMapping;
import com.x.base.core.project.exception.ExceptionAccessDenied;
import com.x.base.core.project.exception.ExceptionEntityNotExist;
import com.x.base.core.project.http.ActionResult;
import com.x.base.core.project.http.EffectivePerson;
import com.x.base.core.project.jaxrs.WrapBoolean;
import com.x.base.core.project.logger.Logger;
import com.x.base.core.project.logger.LoggerFactory;
import com.x.base.core.project.tools.ListTools;
import com.x.processplatform.assemble.surface.Business;
import com.x.processplatform.assemble.surface.ThisApplication;
import com.x.processplatform.core.entity.content.DocSign;
import com.x.processplatform.core.entity.content.DocSignScrawl;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
class ActionDeleteByTask extends BaseAction {
@ActionLogger
private static Logger logger = LoggerFactory.getLogger(ActionDeleteByTask.class);
ActionResult<Wo> execute(EffectivePerson effectivePerson, String taskId) throws Exception {
logger.debug(effectivePerson.getDistinguishedName());
try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
ActionResult<Wo> result = new ActionResult<>();
Wo wo = new Wo();
Business business = new Business(emc);
wo.setValue(true);
DocSign docSign = emc.firstEqual(DocSign.class, DocSign.taskId_FIELDNAME, taskId);
if (null != docSign) {
if(!business.canManageApplication(effectivePerson, null) &&
!docSign.getPerson().equals(effectivePerson.getDistinguishedName())){
throw new ExceptionAccessDenied(effectivePerson, taskId);
}
List<DocSignScrawl> signScrawlList = emc.listEqual(DocSignScrawl.class, DocSignScrawl.signId_FIELDNAME, docSign.getId());
if(ListTools.isNotEmpty(signScrawlList)) {
emc.beginTransaction(DocSignScrawl.class);
for (DocSignScrawl signScrawl : signScrawlList) {
if(StringUtils.isNotBlank(signScrawl.getStorage())) {
StorageMapping mapping = ThisApplication.context().storageMappings().get(DocSignScrawl.class, signScrawl.getStorage());
signScrawl.deleteContent(mapping);
}
emc.remove(signScrawl);
}
}
emc.beginTransaction(DocSign.class);
emc.remove(docSign);
emc.commit();
}
result.setData(wo);
return result;
}
}
public static class Wo extends WrapBoolean {
}
}
package com.x.processplatform.assemble.surface.jaxrs.sign;
import com.x.base.core.container.EntityManagerContainer;
import com.x.base.core.container.factory.EntityManagerContainerFactory;
import com.x.base.core.project.config.StorageMapping;
import com.x.base.core.project.exception.ExceptionAccessDenied;
import com.x.base.core.project.exception.ExceptionEntityNotExist;
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.processplatform.assemble.surface.Business;
import com.x.processplatform.assemble.surface.ThisApplication;
import com.x.processplatform.core.entity.content.DocSignScrawl;
import org.apache.commons.lang3.StringUtils;
class ActionDownload extends BaseAction {
ActionResult<Wo> execute(EffectivePerson effectivePerson, String scrawlId)
throws Exception {
ActionResult<Wo> result = new ActionResult<>();
DocSignScrawl signScrawl = null;
try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
Business business = new Business(emc);
signScrawl = emc.find(scrawlId, DocSignScrawl.class);
if (null == signScrawl) {
throw new ExceptionEntityNotExist(scrawlId, DocSignScrawl.class);
}
if(StringUtils.isBlank(signScrawl.getStorage())){
throw new IllegalStateException(scrawlId+"附件不存在!");
}
if(!business.readableWithJob(effectivePerson, signScrawl.getJob())){
throw new ExceptionAccessDenied(effectivePerson, scrawlId);
}
}
String fileName = signScrawl.getName();
StorageMapping mapping = ThisApplication.context().storageMappings().get(DocSignScrawl.class,
signScrawl.getStorage());
byte[] bytes = signScrawl.readContent(mapping);
Wo wo = new Wo(bytes, this.contentType(false, fileName), this.contentDisposition(false, fileName));
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.processplatform.assemble.surface.jaxrs.sign;
import com.x.base.core.container.EntityManagerContainer;
import com.x.base.core.container.factory.EntityManagerContainerFactory;
import com.x.base.core.entity.JpaObject;
import com.x.base.core.project.bean.WrapCopier;
import com.x.base.core.project.bean.WrapCopierFactory;
import com.x.base.core.project.exception.ExceptionAccessDenied;
import com.x.base.core.project.exception.ExceptionEntityNotExist;
import com.x.base.core.project.http.ActionResult;
import com.x.base.core.project.http.EffectivePerson;
import com.x.base.core.project.logger.Logger;
import com.x.base.core.project.logger.LoggerFactory;
import com.x.processplatform.assemble.surface.Business;
import com.x.processplatform.core.entity.content.DocSign;
class ActionGet extends BaseAction {
private static Logger logger = LoggerFactory.getLogger(ActionGet.class);
ActionResult<Wo> execute(EffectivePerson effectivePerson, String id) throws Exception {
logger.debug(effectivePerson.getDistinguishedName());
try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
ActionResult<Wo> result = new ActionResult<>();
Business business = new Business(emc);
DocSign docSign = emc.find(id, DocSign.class);
if (null == docSign) {
throw new ExceptionEntityNotExist(id, DocSign.class);
}
if(!business.readableWithJob(effectivePerson, docSign.getJob())){
throw new ExceptionAccessDenied(effectivePerson, id);
}
Wo wo = Wo.copier.copy(docSign);
result.setData(wo);
return result;
}
}
public static class Wo extends DocSign {
static WrapCopier<DocSign, Wo> copier = WrapCopierFactory.wo(DocSign.class, Wo.class,
null, JpaObject.FieldsInvisible);
}
}
package com.x.processplatform.assemble.surface.jaxrs.sign;
import com.x.base.core.container.EntityManagerContainer;
import com.x.base.core.container.factory.EntityManagerContainerFactory;
import com.x.base.core.entity.JpaObject;
import com.x.base.core.project.annotation.ActionLogger;
import com.x.base.core.project.bean.WrapCopier;
import com.x.base.core.project.bean.WrapCopierFactory;
import com.x.base.core.project.exception.ExceptionAccessDenied;
import com.x.base.core.project.exception.ExceptionEntityNotExist;
import com.x.base.core.project.http.ActionResult;
import com.x.base.core.project.http.EffectivePerson;
import com.x.base.core.project.logger.Logger;
import com.x.base.core.project.logger.LoggerFactory;
import com.x.processplatform.assemble.surface.Business;
import com.x.processplatform.core.entity.content.Attachment;
import com.x.processplatform.core.entity.content.DocSign;
class ActionGetByTask extends BaseAction {
@ActionLogger
private static Logger logger = LoggerFactory.getLogger(ActionGetByTask.class);
ActionResult<Wo> execute(EffectivePerson effectivePerson, String taskId) throws Exception {
logger.debug(effectivePerson.getDistinguishedName());
try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
ActionResult<Wo> result = new ActionResult<>();
Business business = new Business(emc);
DocSign docSign = emc.firstEqual(DocSign.class, DocSign.taskId_FIELDNAME, taskId);
if (null == docSign) {
throw new ExceptionEntityNotExist(taskId, DocSign.class);
}
if(!business.readableWithJob(effectivePerson, docSign.getJob())){
throw new ExceptionAccessDenied(effectivePerson, taskId);
}
Wo wo = Wo.copier.copy(docSign);
result.setData(wo);
return result;
}
}
public static class Wo extends DocSign {
static WrapCopier<DocSign, Wo> copier = WrapCopierFactory.wo(DocSign.class, Wo.class,
null, JpaObject.FieldsInvisible);
}
}
package com.x.processplatform.assemble.surface.jaxrs.sign;
import com.x.base.core.container.EntityManagerContainer;
import com.x.base.core.container.factory.EntityManagerContainerFactory;
import com.x.base.core.entity.JpaObject;
import com.x.base.core.project.annotation.FieldDescribe;
import com.x.base.core.project.bean.WrapCopier;
import com.x.base.core.project.bean.WrapCopierFactory;
import com.x.base.core.project.config.Config;
import com.x.base.core.project.exception.ExceptionAccessDenied;
import com.x.base.core.project.http.ActionResult;
import com.x.base.core.project.http.EffectivePerson;
import com.x.base.core.project.logger.Logger;
import com.x.base.core.project.logger.LoggerFactory;
import com.x.base.core.project.tools.SortTools;
import com.x.processplatform.assemble.surface.Business;
import com.x.processplatform.core.entity.content.DocSign;
import com.x.processplatform.core.entity.content.DocSignStatus;
import org.apache.commons.lang3.BooleanUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
class ActionListWithJob extends BaseAction {
private static Logger logger = LoggerFactory.getLogger(ActionListWithJob.class);
ActionResult<List<Wo>> execute(EffectivePerson effectivePerson, String job) throws Exception {
ActionResult<List<Wo>> result = new ActionResult<>();
CompletableFuture<List<Wo>> listFuture = listFuture(job);
CompletableFuture<Boolean> checkControlFuture = checkJobControlFuture(effectivePerson, job);
result.setData(listFuture.get(Config.processPlatform().getAsynchronousTimeout(), TimeUnit.SECONDS));
if (BooleanUtils
.isFalse(checkControlFuture.get(Config.processPlatform().getAsynchronousTimeout(), TimeUnit.SECONDS))) {
throw new ExceptionAccessDenied(effectivePerson, job);
}
return result;
}
private CompletableFuture<List<Wo>> listFuture(String flag) {
return CompletableFuture.supplyAsync(() -> {
List<Wo> wos = new ArrayList<>();
try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
final String job = flag;
List<DocSign> docSignList = emc.listEqualAndNotEqual(DocSign.class, DocSign.job_FIELDNAME, job,
DocSign.status_FIELDNAME, DocSignStatus.STATUS_1.getValue());
for (DocSign docSign : docSignList) {
Wo wo = Wo.copier.copy(docSign);
wo.setInputList(wo.getProperties().getInputList());
wo.setScrawlList(wo.getProperties().getScrawlList());
wo.setProperties(null);
wos.add(wo);
}
SortTools.asc(wos, DocSign.createTime_FIELDNAME);
} catch (Exception e) {
logger.error(e);
}
return wos;
});
}
private CompletableFuture<Boolean> checkJobControlFuture(EffectivePerson effectivePerson, String job) {
return CompletableFuture.supplyAsync(() -> {
Boolean value = false;
try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
Business business = new Business(emc);
value = business.readableWithJob(effectivePerson, job);
} catch (Exception e) {
logger.error(e);
}
return value;
});
}
public static class Wo extends DocSign {
static WrapCopier<DocSign, Wo> copier = WrapCopierFactory.wo(DocSign.class, Wo.class, null,
JpaObject.FieldsInvisible);
@FieldDescribe("输入框列表.")
private List<String> inputList = new ArrayList<>();
@FieldDescribe("涂鸦列表.")
private List<String> scrawlList = new ArrayList<>();
public List<String> getInputList() {
return inputList;
}
public void setInputList(List<String> inputList) {
this.inputList = inputList;
}
public List<String> getScrawlList() {
return scrawlList;
}
public void setScrawlList(List<String> scrawlList) {
this.scrawlList = scrawlList;
}
}
}
......@@ -5,8 +5,10 @@ import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
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.annotation.FieldDescribe;
import com.x.base.core.project.config.Config;
import com.x.base.core.project.config.StorageMapping;
import com.x.base.core.project.exception.ExceptionAccessDenied;
import com.x.base.core.project.exception.ExceptionEntityNotExist;
import com.x.base.core.project.exception.ExceptionFieldEmpty;
......@@ -16,16 +18,16 @@ import com.x.base.core.project.http.EffectivePerson;
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 com.x.base.core.project.tools.DateTools;
import com.x.base.core.project.tools.FileTools;
import com.x.base.core.project.tools.ListTools;
import com.x.processplatform.assemble.surface.Business;
import com.x.processplatform.assemble.surface.ThisApplication;
import com.x.processplatform.core.entity.content.DocSign;
import com.x.processplatform.core.entity.content.DocSignScrawl;
import com.x.processplatform.core.entity.content.DocSignStatus;
import com.x.processplatform.core.entity.content.Task;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.omg.CORBA.ExceptionDefPOA;
import java.io.File;
import java.util.ArrayList;
......@@ -35,182 +37,219 @@ import java.util.List;
class ActionSave extends BaseAction {
private static Logger logger = LoggerFactory.getLogger(ActionSave.class);
ActionResult<Wo> execute(EffectivePerson effectivePerson, String taskId, JsonElement jsonElement) throws Exception {
String job = null;
Wi wi = this.convertToWrapIn(jsonElement, Wi.class);
if(wi.getStatus() == null){
throw new ExceptionFieldEmpty(DocSign.status_FIELDNAME);
}
if(wi.getStatus().equals(DocSign.STATUS_3) && StringUtils.isBlank(wi.getHtmlContent())){
throw new ExceptionFieldEmpty("htmlContent");
}
Task task = null;
Wo wo = new Wo();
try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
task = emc.fetch(taskId, Task.class);
if(task == null){
throw new ExceptionEntityNotExist(taskId, Task.class);
}
if(!task.getPerson().equals(effectivePerson.getDistinguishedName())){
throw new ExceptionAccessDenied(effectivePerson);
}
}
this.saveDocSign(wi, task);
ActionResult<Wo> result = new ActionResult<>();
result.setData(wo);
return result;
}
private String saveDocSign(Wi wi, Task task) throws Exception{
try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
boolean flag = false;
emc.beginTransaction(DocSign.class);
DocSign docSign = emc.firstEqual(DocSign.class, DocSign.taskId_FIELDNAME, task.getId());
if(docSign == null) {
flag = true;
docSign = new DocSign(task);
docSign.setStatus(wi.getStatus());
}
docSign.getProperties().setInputList(wi.getInputList());
docSign.getProperties().setScrawlList(wi.getScrawlList());
if(wi.getStatus().equals(DocSign.STATUS_2)){
docSign.setCommitTime(new Date());
emc.beginTransaction(DocSignScrawl.class);
emc.deleteEqual(DocSignScrawl.class, DocSignScrawl.signId_FIELDNAME, docSign.getId());
if(ListTools.isNotEmpty(wi.getScrawlList())){
for (String data : wi.getScrawlList()){
JsonObject jsonObject = gson.fromJson(data, JsonObject.class);
DocSignScrawl signScrawl = new DocSignScrawl(docSign, data, null);
if(jsonObject.has("src")){
signScrawl.setType(DocSignScrawl.SCRAWL_TYPE_BASE64);
}
emc.persist(signScrawl);
}
}
}else if(wi.getStatus().equals(DocSign.STATUS_3)){
docSign.setCommitTime(new Date());
}
if(flag){
emc.persist(docSign);
}
emc.commit();
return docSign.getId();
}
}
private void html2Img(DocSign docSign, Wi wi, EntityManagerContainer emc) throws Exception{
String html = wi.getHtmlContent();
if (html.toLowerCase().indexOf("<html") == -1) {
html = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"></head><body>" + html + "</body></html>";
}
String name = StringUtils.split(docSign.getPerson(),"@")[0] + docSign.getTaskId() + ".png";
byte[] bytes = null;
try (Playwright playwright = Playwright.create()) {
List<BrowserType> browserTypes = Arrays.asList(
playwright.chromium(),
playwright.firefox(),
playwright.webkit()
);
for (BrowserType browserType : browserTypes) {
BrowserType.LaunchOptions options = new BrowserType.LaunchOptions();
options.setHeadless(true);
try (Browser browser = browserType.launch(options)) {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.setContent(html);
Page.ScreenshotOptions screenshotOptions = new Page.ScreenshotOptions();
screenshotOptions.setFullPage(true);
if(wi.getHtmlWidth()!=null && wi.getHtmlHeight()!=null){
screenshotOptions.setClip(0, 0, wi.getHtmlWidth(), wi.getHtmlHeight());
}
File tempDir = Config.dir_local_temp();
FileTools.forceMkdir(tempDir);
File file = new File(tempDir, name);
screenshotOptions.setPath(file.toPath());
page.screenshot(screenshotOptions);
bytes = FileUtils.readFileToByteArray(file);
break;
} catch (Exception e) {
logger.warn("Playwright user browser:{} error:{}",browserType.name(), e.getMessage());
}
}
}
if(bytes==null){
throw new IllegalStateException("签批转图片异常!");
}
}
public static class Wi extends GsonPropertyObject {
@FieldDescribe("状态:1(暂存)|2(签批正文不可以修改)|3(签批正文可以修改,正文保存为图片).")
private Integer status;
@FieldDescribe("包含签批的html正文后内容,状态为3时必传.")
private String htmlContent;
@FieldDescribe("html宽度,允许为空.")
private Double htmlWidth;
@FieldDescribe("html宽度长度,允许为空.")
private Double htmlHeight;
@FieldDescribe("输入框列表.")
private List<String> inputList = new ArrayList<>();
@FieldDescribe("涂鸦列表.")
private List<String> scrawlList = new ArrayList<>();
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getHtmlContent() {
return htmlContent;
}
public void setHtmlContent(String htmlContent) {
this.htmlContent = htmlContent;
}
public Double getHtmlWidth() {
return htmlWidth;
}
public void setHtmlWidth(Double htmlWidth) {
this.htmlWidth = htmlWidth;
}
public Double getHtmlHeight() {
return htmlHeight;
}
public void setHtmlHeight(Double htmlHeight) {
this.htmlHeight = htmlHeight;
}
public List<String> getInputList() {
return inputList;
}
public void setInputList(List<String> inputList) {
this.inputList = inputList;
}
public List<String> getScrawlList() {
return scrawlList;
}
public void setScrawlList(List<String> scrawlList) {
this.scrawlList = scrawlList;
}
}
public static class Wo extends WoId {
private static final long serialVersionUID = -2577413577740827608L;
}
private static Logger logger = LoggerFactory.getLogger(ActionSave.class);
private static final String SCRAWL_SRC = "src";
ActionResult<Wo> execute(EffectivePerson effectivePerson, String taskId, JsonElement jsonElement) throws Exception {
Wi wi = this.convertToWrapIn(jsonElement, Wi.class);
if (wi.getStatus() == null) {
throw new ExceptionFieldEmpty(DocSign.status_FIELDNAME);
}
if (wi.getStatus().equals(DocSignStatus.STATUS_3.getValue()) && StringUtils.isBlank(wi.getHtmlContent())) {
throw new ExceptionFieldEmpty("htmlContent");
}
Task task = null;
Wo wo = new Wo();
try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
task = emc.fetch(taskId, Task.class);
if (task == null) {
throw new ExceptionEntityNotExist(taskId, Task.class);
}
if (!task.getPerson().equals(effectivePerson.getDistinguishedName())) {
throw new ExceptionAccessDenied(effectivePerson);
}
}
wo.setId(this.saveDocSign(wi, task));
ActionResult<Wo> result = new ActionResult<>();
result.setData(wo);
return result;
}
private String saveDocSign(Wi wi, Task task) throws Exception {
try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
boolean flag = true;
emc.beginTransaction(DocSign.class);
DocSign docSign = emc.firstEqual(DocSign.class, DocSign.taskId_FIELDNAME, task.getId());
if (docSign == null) {
flag = false;
docSign = new DocSign(task);
}
docSign.setStatus(wi.getStatus());
docSign.getProperties().setInputList(wi.getInputList());
docSign.getProperties().setScrawlList(wi.getScrawlList());
emc.beginTransaction(DocSignScrawl.class);
if (flag) {
List<DocSignScrawl> signScrawlList = emc.listEqual(DocSignScrawl.class, DocSignScrawl.signId_FIELDNAME, docSign.getId());
for (DocSignScrawl signScrawl : signScrawlList) {
if (StringUtils.isNotBlank(signScrawl.getStorage())) {
StorageMapping mapping = ThisApplication.context().storageMappings().get(DocSignScrawl.class, signScrawl.getStorage());
signScrawl.deleteContent(mapping);
}
emc.remove(signScrawl);
}
}
if (wi.getStatus().equals(DocSignStatus.STATUS_2.getValue())) {
List<String> list = new ArrayList<>();
docSign.setCommitTime(new Date());
if (ListTools.isNotEmpty(wi.getScrawlList())) {
for (String data : wi.getScrawlList()) {
DocSignScrawl signScrawl = this.base642Img(docSign, data);
list.add(gson.toJson(signScrawl));
emc.persist(signScrawl, CheckPersistType.all);
}
}
docSign.getProperties().setScrawlList(list);
} else if (wi.getStatus().equals(DocSignStatus.STATUS_3.getValue())) {
docSign.setCommitTime(new Date());
DocSignScrawl signScrawl = this.html2Img(docSign, wi, emc);
emc.persist(signScrawl, CheckPersistType.all);
docSign.setSignScrawlId(signScrawl.getId());
}
if (!flag) {
emc.persist(docSign, CheckPersistType.all);
}
emc.commit();
return docSign.getId();
}
}
private DocSignScrawl base642Img(DocSign docSign, String data) throws Exception {
JsonObject jsonObject = gson.fromJson(data, JsonObject.class);
DocSignScrawl signScrawl = new DocSignScrawl(docSign, null);
if (jsonObject.has(SCRAWL_SRC)) {
String base64String = jsonObject.get(SCRAWL_SRC).getAsString();
if (StringUtils.isNotBlank(base64String)) {
byte[] bytes = Base64.decodeBase64(base64String);
String name = StringUtils.split(docSign.getPerson(), "@")[0] + signScrawl.getId() + ".png";
signScrawl.setType(DocSignScrawl.SCRAWL_TYPE_BASE64);
signScrawl.setName(name);
StorageMapping mapping = ThisApplication.context().storageMappings().random(DocSignScrawl.class);
signScrawl.saveContent(mapping, bytes, name);
}
}
if (jsonObject.has(DocSignScrawl.width_FIELDNAME)) {
signScrawl.setWidth(jsonObject.get(DocSignScrawl.width_FIELDNAME).getAsString());
}
if (jsonObject.has(DocSignScrawl.height_FIELDNAME)) {
signScrawl.setHeight(jsonObject.get(DocSignScrawl.height_FIELDNAME).getAsString());
}
return signScrawl;
}
private DocSignScrawl html2Img(DocSign docSign, Wi wi, EntityManagerContainer emc) throws Exception {
String html = wi.getHtmlContent();
if (html.toLowerCase().indexOf("<html") == -1) {
html = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"></head><body>" + html + "</body></html>";
}
String name = StringUtils.split(docSign.getPerson(), "@")[0] + docSign.getTaskId() + ".png";
byte[] bytes = null;
try (Playwright playwright = Playwright.create()) {
List<BrowserType> browserTypes = Arrays.asList(
playwright.chromium(),
playwright.firefox(),
playwright.webkit()
);
for (BrowserType browserType : browserTypes) {
BrowserType.LaunchOptions options = new BrowserType.LaunchOptions();
options.setHeadless(true);
try (Browser browser = browserType.launch(options)) {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.setContent(html);
Page.ScreenshotOptions screenshotOptions = new Page.ScreenshotOptions();
screenshotOptions.setFullPage(true);
if (wi.getHtmlWidth() != null && wi.getHtmlHeight() != null) {
screenshotOptions.setClip(0, 0, wi.getHtmlWidth(), wi.getHtmlHeight());
}
File tempDir = Config.dir_local_temp();
FileTools.forceMkdir(tempDir);
File file = new File(tempDir, name);
screenshotOptions.setPath(file.toPath());
page.screenshot(screenshotOptions);
bytes = FileUtils.readFileToByteArray(file);
break;
} catch (Exception e) {
logger.warn("Playwright user browser:{} error:{}", browserType.name(), e.getMessage());
}
}
}
if (bytes == null) {
throw new IllegalStateException("签批转图片异常!");
}
DocSignScrawl signScrawl = new DocSignScrawl(docSign, name);
signScrawl.setType(DocSignScrawl.SCRAWL_TYPE_IMAGE);
StorageMapping mapping = ThisApplication.context().storageMappings().random(DocSignScrawl.class);
signScrawl.saveContent(mapping, bytes, name);
return signScrawl;
}
public static class Wi extends GsonPropertyObject {
@FieldDescribe("状态:1(暂存)|2(签批正文不可以修改)|3(签批正文可以修改,正文保存为图片).")
private Integer status;
@FieldDescribe("包含签批的html正文后内容,状态为3时必传.")
private String htmlContent;
@FieldDescribe("html正文宽度,允许为空.")
private Double htmlWidth;
@FieldDescribe("html正文高度,允许为空.")
private Double htmlHeight;
@FieldDescribe("输入框列表.")
private List<String> inputList = new ArrayList<>();
@FieldDescribe("涂鸦列表,示例[\"{'src':'base64图片','width':'100','height':'100'}\"].")
private List<String> scrawlList = new ArrayList<>();
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getHtmlContent() {
return htmlContent;
}
public void setHtmlContent(String htmlContent) {
this.htmlContent = htmlContent;
}
public Double getHtmlWidth() {
return htmlWidth;
}
public void setHtmlWidth(Double htmlWidth) {
this.htmlWidth = htmlWidth;
}
public Double getHtmlHeight() {
return htmlHeight;
}
public void setHtmlHeight(Double htmlHeight) {
this.htmlHeight = htmlHeight;
}
public List<String> getInputList() {
return inputList;
}
public void setInputList(List<String> inputList) {
this.inputList = inputList;
}
public List<String> getScrawlList() {
return scrawlList;
}
public void setScrawlList(List<String> scrawlList) {
this.scrawlList = scrawlList;
}
}
public static class Wo extends WoId {
}
}
......@@ -11,9 +11,6 @@ import com.x.base.core.project.jaxrs.ResponseFactory;
import com.x.base.core.project.jaxrs.StandardJaxrsAction;
import com.x.base.core.project.logger.Logger;
import com.x.base.core.project.logger.LoggerFactory;
import com.x.processplatform.assemble.surface.jaxrs.snap.*;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
......@@ -32,7 +29,7 @@ public class SignAction extends StandardJaxrsAction {
private static Logger logger = LoggerFactory.getLogger(SignAction.class);
/*@JaxrsMethodDescribe(value = "获取快照对象.", action = ActionGet.class)
@JaxrsMethodDescribe(value = "获取签批信息.", action = ActionGet.class)
@GET
@Path("{id}")
@Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
......@@ -50,7 +47,43 @@ public class SignAction extends StandardJaxrsAction {
asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
}
@JaxrsMethodDescribe(value = "删除快照", action = ActionDelete.class)
@JaxrsMethodDescribe(value = "根据task获取签批信息.", action = ActionGetByTask.class)
@GET
@Path("task/{taskId}")
@Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
@Consumes(MediaType.APPLICATION_JSON)
public void getByTask(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
@JaxrsParameterDescribe("任务ID") @PathParam("taskId") String taskId) {
ActionResult<ActionGetByTask.Wo> result = new ActionResult<>();
EffectivePerson effectivePerson = this.effectivePerson(request);
try {
result = new ActionGetByTask().execute(effectivePerson, taskId);
} catch (Exception e) {
logger.error(e, effectivePerson, request, null);
result.error(e);
}
asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
}
@JaxrsMethodDescribe(value = "根据工作的job获取签批列表.", action = ActionListWithJob.class)
@GET
@Path("list/job/{job}")
@Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
@Consumes(MediaType.APPLICATION_JSON)
public void listWithJob(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
@JaxrsParameterDescribe("工作的job") @PathParam("job") String job) {
ActionResult<List<ActionListWithJob.Wo>> result = new ActionResult<>();
EffectivePerson effectivePerson = this.effectivePerson(request);
try {
result = new ActionListWithJob().execute(effectivePerson, job);
} catch (Exception e) {
logger.error(e, effectivePerson, request, null);
result.error(e);
}
asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
}
@JaxrsMethodDescribe(value = "删除签批", action = ActionDelete.class)
@DELETE
@Path("{id}")
@Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
......@@ -68,7 +101,7 @@ public class SignAction extends StandardJaxrsAction {
asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
}
@JaxrsMethodDescribe(value = "删除快照", action = ActionDelete.class)
@JaxrsMethodDescribe(value = "删除签批", action = ActionDelete.class)
@GET
@Path("{id}/mockdeletetoget")
@Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
......@@ -84,13 +117,49 @@ public class SignAction extends StandardJaxrsAction {
result.error(e);
}
asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
}*/
}
@JaxrsMethodDescribe(value = "根据task删除签批", action = ActionDeleteByTask.class)
@DELETE
@Path("task/{taskId}")
@Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
@Consumes(MediaType.APPLICATION_JSON)
public void deleteByTask(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
@JaxrsParameterDescribe("待办标识") @PathParam("taskId") String taskId) {
ActionResult<ActionDeleteByTask.Wo> result = new ActionResult<>();
EffectivePerson effectivePerson = this.effectivePerson(request);
try {
result = new ActionDeleteByTask().execute(effectivePerson, taskId);
} catch (Exception e) {
logger.error(e, effectivePerson, request, null);
result.error(e);
}
asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
}
@JaxrsMethodDescribe(value = "根据task删除签批mockdeletetoget", action = ActionDeleteByTask.class)
@GET
@Path("task/{taskId}/mockdeletetoget")
@Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
@Consumes(MediaType.APPLICATION_JSON)
public void deleteByTaskMockGet2Delete(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
@JaxrsParameterDescribe("待办标识") @PathParam("taskId") String taskId) {
ActionResult<ActionDeleteByTask.Wo> result = new ActionResult<>();
EffectivePerson effectivePerson = this.effectivePerson(request);
try {
result = new ActionDeleteByTask().execute(effectivePerson, taskId);
} catch (Exception e) {
logger.error(e, effectivePerson, request, null);
result.error(e);
}
asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
}
@JaxrsMethodDescribe(value = "保存签批.", action = ActionSave.class)
@POST
@Path("save/task/{taskId}")
@Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Consumes(MediaType.APPLICATION_JSON)
public void save(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
@JaxrsParameterDescribe("待办ID") @PathParam("taskId") String taskId, JsonElement jsonElement) {
ActionResult<ActionSave.Wo> result = new ActionResult<>();
......@@ -104,23 +173,21 @@ public class SignAction extends StandardJaxrsAction {
asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
}
/*@JaxrsMethodDescribe(value = "按条件对快照分页显示.", action = ActionManageListFilterPaging.class)
@POST
@Path("list/filter/{page}/size/{size}/manage")
@Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
@JaxrsMethodDescribe(value = "下载签批涂鸦附件", action = ActionDownload.class)
@GET
@Path("download/{scrawlId}")
@Consumes(MediaType.APPLICATION_JSON)
public void manageListFilterPaging(@Suspended final AsyncResponse asyncResponse,
@Context HttpServletRequest request, @JaxrsParameterDescribe("分页") @PathParam("page") Integer page,
@JaxrsParameterDescribe("数量") @PathParam("size") Integer size, JsonElement jsonElement) {
ActionResult<List<ActionManageListFilterPaging.Wo>> result = new ActionResult<>();
public void download(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
@JaxrsParameterDescribe("签批涂鸦附件标志") @PathParam("scrawlId") String scrawlId) {
ActionResult<ActionDownload.Wo> result = new ActionResult<>();
EffectivePerson effectivePerson = this.effectivePerson(request);
try {
result = new ActionManageListFilterPaging().execute(effectivePerson, page, size, jsonElement);
result = new ActionDownload().execute(effectivePerson, scrawlId);
} catch (Exception e) {
logger.error(e, effectivePerson, request, jsonElement);
logger.error(e, effectivePerson, request, null);
result.error(e);
}
asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result, jsonElement));
}*/
asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
}
}
......@@ -32,10 +32,6 @@ public class DocSign extends SliceJpaObject {
private static final String TABLE = PersistenceProperties.Content.DocSign.table;
public static final Integer STATUS_1 = 1;
public static final Integer STATUS_2 = 3;
public static final Integer STATUS_3 = 3;
@Override
public String getId() {
return id;
......@@ -89,6 +85,7 @@ public class DocSign extends SliceJpaObject {
this.activity = task.getActivity();
this.activityName = task.getActivityName();
this.person = task.getPerson();
this.taskId = task.getId();
}
public DocSignProperties getProperties() {
......@@ -155,11 +152,11 @@ public class DocSign extends SliceJpaObject {
@CheckPersist(allowEmpty = false)
private String person;
public static final String signPicAttId_FIELDNAME = "signPicAttId";
public static final String signScrawlId_FIELDNAME = "signScrawlId";
@FieldDescribe("正文签批转存为图片的ID.")
@Column(length = JpaObject.length_id, name = ColumnNamePrefix + signPicAttId_FIELDNAME)
@Column(length = JpaObject.length_id, name = ColumnNamePrefix + signScrawlId_FIELDNAME)
@CheckPersist(allowEmpty = true)
private String signPicAttId;
private String signScrawlId;
public static final String status_FIELDNAME = "status";
@FieldDescribe("状态:1(暂存)|2(签批正文不可以修改)|3(签批正文可以修改).")
......@@ -243,12 +240,12 @@ public class DocSign extends SliceJpaObject {
this.commitTime = commitTime;
}
public String getSignPicAttId() {
return signPicAttId;
public String getSignScrawlId() {
return signScrawlId;
}
public void setSignPicAttId(String signPicAttId) {
this.signPicAttId = signPicAttId;
public void setSignScrawlId(String signScrawlId) {
this.signScrawlId = signScrawlId;
}
public Integer getStatus() {
......
package com.x.processplatform.core.entity.content;
import com.x.base.core.entity.AbstractPersistenceProperties;
import com.x.base.core.entity.JpaObject;
import com.x.base.core.entity.SliceJpaObject;
import com.x.base.core.entity.StorageObject;
import com.x.base.core.entity.*;
import com.x.base.core.entity.annotation.CheckPersist;
import com.x.base.core.entity.annotation.ContainerEntity;
import com.x.base.core.project.annotation.FieldDescribe;
......@@ -17,6 +14,8 @@ import org.apache.openjpa.persistence.jdbc.Index;
import javax.persistence.*;
import java.util.Date;
import static com.x.base.core.entity.StorageType.processPlatform;
/**
* 签批涂鸦信息
* @author sword
......@@ -28,9 +27,10 @@ import java.util.Date;
+ JpaObject.DefaultUniqueConstraintSuffix, columnNames = { JpaObject.IDCOLUMN,
JpaObject.CREATETIMECOLUMN, JpaObject.UPDATETIMECOLUMN, JpaObject.SEQUENCECOLUMN }) })
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Storage(type = processPlatform)
public class DocSignScrawl extends StorageObject {
private static final long serialVersionUID = -8648872600208475747L;
private static final long serialVersionUID = 3335030367719974674L;
private static final String TABLE = PersistenceProperties.Content.DocSignScrawl.table;
......@@ -57,30 +57,31 @@ public class DocSignScrawl extends StorageObject {
@Override
public void onPersist() throws Exception {
if(StringUtils.isNotBlank(this.name)){
if(this.lastUpdateTime == null){
this.lastUpdateTime = new Date();
}
this.extension = StringUtils.lowerCase(FilenameUtils.getExtension(name));
}
}
public DocSignScrawl() {
// nothing
}
public DocSignScrawl(DocSign docSign, String data, String name) {
public DocSignScrawl(DocSign docSign, String name) {
this.signId = docSign.getId();
this.job = docSign.getJob();
this.activity = docSign.getActivity();
this.activityName = docSign.getActivityName();
this.person = docSign.getPerson();
this.data = data;
this.type = SCRAWL_TYPE_PLACEHOLDER;
if(StringUtils.isNotBlank(name)){
this.lastUpdateTime = new Date();
this.name = name;
this.extension = StringUtils.lowerCase(FilenameUtils.getExtension(name));
}
this.name = name;
}
@Override
public String path() {
String str = DateTools.format(this.getCreateTime(), DateTools.formatCompact_yyyyMMdd);
String str = DateTools.format(new Date(), DateTools.formatCompact_yyyyMMdd);
str += PATHSEPARATOR;
str += this.job;
str += PATHSEPARATOR;
......@@ -234,17 +235,21 @@ public class DocSignScrawl extends StorageObject {
public static final String type_FIELDNAME = "type";
@FieldDescribe("涂鸦类型:placeholder(占位符)|base64(图片base64存储在data字段中)|image(图片存储在附件中)")
@Column(name = ColumnNamePrefix + type_FIELDNAME)
@Column(length = length_64B, name = ColumnNamePrefix + type_FIELDNAME)
@CheckPersist(allowEmpty = true)
private String type;
public static final String data_FIELDNAME = "data";
@FieldDescribe("涂鸦信息.")
@Lob
@Basic(fetch = FetchType.EAGER)
@Column(length = JpaObject.length_20M, name = ColumnNamePrefix + data_FIELDNAME)
public static final String width_FIELDNAME = "width";
@FieldDescribe("宽")
@Column(length = length_64B, name = ColumnNamePrefix + width_FIELDNAME)
@CheckPersist(allowEmpty = true)
private String data;
private String width;
public static final String height_FIELDNAME = "height";
@FieldDescribe("高")
@Column(length = length_64B, name = ColumnNamePrefix + height_FIELDNAME)
@CheckPersist(allowEmpty = true)
private String height;
public String getPerson() {
return person;
......@@ -306,11 +311,19 @@ public class DocSignScrawl extends StorageObject {
this.type = type;
}
public String getData() {
return data;
public String getWidth() {
return width;
}
public void setWidth(String width) {
this.width = width;
}
public String getHeight() {
return height;
}
public void setData(String data) {
this.data = data;
public void setHeight(String height) {
this.height = height;
}
}
package com.x.processplatform.core.entity.content;
public enum DocSignStatus {
STATUS_1(1, "暂存"),
STATUS_2(2, "签批正文不可以修改"),
STATUS_3(3, "签批正文可以修改");
private Integer value;
private String name;
private DocSignStatus(Integer value, String name) {
this.value = value;
this.name = name;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name == null ? null : name.trim();
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册