export task complete

This commit is contained in:
Donghuang 2022-06-23 22:32:12 +08:00
parent 126575f0bf
commit 7640c1dab6
13 changed files with 163 additions and 50 deletions

View File

@ -1,18 +1,27 @@
package com.pudonghot.ambition.crm.controller; package com.pudonghot.ambition.crm.controller;
import lombok.val; import lombok.val;
import java.io.File;
import java.util.Map;
import javax.validation.Valid; import javax.validation.Valid;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.chyxion.tigon.mybatis.Search; import me.chyxion.tigon.mybatis.Search;
import org.springframework.http.HttpStatus;
import org.apache.commons.lang3.StringUtils;
import javax.validation.constraints.NotNull;
import com.pudonghot.ambition.crm.model.User; import com.pudonghot.ambition.crm.model.User;
import org.springframework.http.ResponseEntity;
import me.chyxion.tigon.web.controller2.ArgQuery; import me.chyxion.tigon.web.controller2.ArgQuery;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import com.pudonghot.ambition.crm.model.ExportTask; import com.pudonghot.ambition.crm.model.ExportTask;
import com.pudonghot.ambition.crm.auth.SessionAbility; import com.pudonghot.ambition.crm.auth.SessionAbility;
import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.authz.annotation.RequiresRoles;
import com.pudonghot.ambition.crm.util.FileDownloadUtils;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import me.chyxion.tigon.web.controller2.BaseQueryController; import me.chyxion.tigon.web.controller2.BaseQueryController;
import org.springframework.web.bind.annotation.RequestParam;
import com.pudonghot.ambition.crm.service.ExportTaskService;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import com.pudonghot.ambition.crm.ws.service.WebSocketService; import com.pudonghot.ambition.crm.ws.service.WebSocketService;
@ -43,6 +52,40 @@ public class ExportTaskController
return list(req, new Search()); return list(req, new Search());
} }
/**
* download file
* @param id id
* @return file
*/
@RequiresRoles(User.ROLE_ADMIN)
@RequestMapping("/download")
public ResponseEntity file(@NotNull @RequestParam("id") final Long id) {
val exportTask = service.find(id);
if (exportTask == null) {
return ResponseEntity.notFound().build();
}
val userId = getUserId();
if (!userId.equals(exportTask.getCreatedBy())) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
exportTask.setDownloaded(true);
exportTask.setUpdatedBy(userId);
((ExportTaskService) service).update(exportTask);
val location = exportTask.getLocation();
if (StringUtils.isNotBlank(location)) {
val file = new File(location);
if (file.exists() && file.isFile()) {
((ExportTaskService) service).notifyTaskUpdated(userId);
return FileDownloadUtils.toResponseEntity(file, file.getName());
}
}
return ResponseEntity.notFound().build();
}
/** /**
* {@inheritDoc} * {@inheritDoc}
@ -62,8 +105,7 @@ public class ExportTaskController
@PostMapping("/test-push") @PostMapping("/test-push")
@RequiresRoles(User.ROLE_ADMIN) @RequiresRoles(User.ROLE_ADMIN)
public void testPush(@RequestBody ExportTaskUpdatedWsResp resp) { public void testPush(@RequestBody Map<String, Object> resp) {
resp.setEmployeeKey(getUserId()); webSocketService.publish(getUserId(), resp);
webSocketService.publish(resp);
} }
} }

View File

@ -1,6 +1,7 @@
package com.pudonghot.ambition.crm.service; package com.pudonghot.ambition.crm.service;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import com.pudonghot.ambition.crm.model.ExportTask; import com.pudonghot.ambition.crm.model.ExportTask;
import me.chyxion.tigon.service2.BaseQueryService; import me.chyxion.tigon.service2.BaseQueryService;
@ -26,5 +27,12 @@ public interface ExportTaskService extends BaseQueryService<Long, ExportTask> {
* @param employeeKey employee key * @param employeeKey employee key
*/ */
void notifyTaskUpdated(@NotBlank String employeeKey); void notifyTaskUpdated(@NotBlank String employeeKey);
/**
* update task
*
* @param exportTask task
*/
void update(@NotNull ExportTask exportTask);
} }

View File

@ -24,6 +24,7 @@ import com.pudonghot.ambition.crm.ws.service.WebSocketService;
import me.chyxion.tigon.service.support2.BaseQueryServiceSupport; import me.chyxion.tigon.service.support2.BaseQueryServiceSupport;
import com.pudonghot.ambition.crm.enumeration.EnumExportTaskStatus; import com.pudonghot.ambition.crm.enumeration.EnumExportTaskStatus;
import com.pudonghot.ambition.crm.ws.model.ExportTaskUpdatedWsResp; import com.pudonghot.ambition.crm.ws.model.ExportTaskUpdatedWsResp;
import com.pudonghot.ambition.crm.ws.model.ExportTaskCompletedWsResp;
/** /**
* @author Donghuang * @author Donghuang
@ -73,15 +74,16 @@ public class ExportTaskServiceSupport
exportTask.setLocation(new File(distDir, exportFile.getName()).getPath()); exportTask.setLocation(new File(distDir, exportFile.getName()).getPath());
exportTask.setDateUpdated(new Date()); exportTask.setDateUpdated(new Date());
mapper.update(exportTask); mapper.update(exportTask);
} catch (final Throwable e) { notifyTaskComplete(employeeKey, true, null);
}
catch (final Throwable e) {
log.error("Export file error caused.", e); log.error("Export file error caused.", e);
exportTask.setStatus(EnumExportTaskStatus.FAILED); exportTask.setStatus(EnumExportTaskStatus.FAILED);
exportTask.setNote(ExceptionUtils.getStackTrace(e)); exportTask.setNote(ExceptionUtils.getStackTrace(e));
exportTask.setDateUpdated(new Date()); exportTask.setDateUpdated(new Date());
mapper.update(exportTask); mapper.update(exportTask);
notifyTaskComplete(employeeKey, false, e.getMessage());
} }
notifyTaskUpdated(employeeKey);
} }
finally { finally {
LOCK_MAP.remove(employeeKey); LOCK_MAP.remove(employeeKey);
@ -89,6 +91,15 @@ public class ExportTaskServiceSupport
}); });
} }
void notifyTaskComplete(final String employeeKey, final boolean success, final String message) {
val resp = new ExportTaskCompletedWsResp(employeeKey,
mapper.count(new Search(ExportTask.CREATED_BY, employeeKey)
.isFalse(ExportTask.DOWNLOADED)));
resp.setSuccess(success);
resp.setMessage(message);
webSocketService.publish(resp);
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -99,4 +110,12 @@ public class ExportTaskServiceSupport
mapper.count(new Search(ExportTask.CREATED_BY, employeeKey) mapper.count(new Search(ExportTask.CREATED_BY, employeeKey)
.isFalse(ExportTask.DOWNLOADED)))); .isFalse(ExportTask.DOWNLOADED))));
} }
/**
* {@inheritDoc}
*/
@Override
public void update(final ExportTask exportTask) {
mapper.update(exportTask);
}
} }

View File

@ -10,7 +10,7 @@ import org.springframework.http.MediaType;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.FileSystemResource;
/** /**
* @author Donghuang * @author Donghuang
@ -36,16 +36,11 @@ public class FileDownloadUtils {
headers.add("Pragma", "no-cache"); headers.add("Pragma", "no-cache");
headers.add("Expires", "0"); headers.add("Expires", "0");
try (val fin = new FileInputStream(file)) {
return ResponseEntity.ok() return ResponseEntity.ok()
.headers(headers) .headers(headers)
.contentLength(file.length()) .contentLength(file.length())
.contentType(MediaType.APPLICATION_OCTET_STREAM) .contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new InputStreamResource(fin)); .body(new FileSystemResource(file));
}
catch (final IOException e) {
log.error("Download file error caused.", e);
}
} }
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();

View File

@ -0,0 +1,30 @@
package com.pudonghot.ambition.crm.ws.model;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @author Donghuang
* @date Jun 17, 2022 15:01:49
*/
@Getter
@Setter
@ToString
public class ExportTaskCompletedWsResp extends BaseWsResp {
private static final long serialVersionUID = 1L;
private Integer countUndownloaded;
private Boolean success;
private String message;
public ExportTaskCompletedWsResp() {
setType("EXPORT_TASK_COMPLETED");
}
public ExportTaskCompletedWsResp(final String employeeKey, final Integer countUndownloaded) {
this();
this.countUndownloaded = countUndownloaded;
setEmployeeKey(employeeKey);
}
}

View File

@ -15,7 +15,6 @@ public class ExportTaskUpdatedWsResp extends BaseWsResp {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private Integer countUndownloaded; private Integer countUndownloaded;
private Integer countFailed;
public ExportTaskUpdatedWsResp() { public ExportTaskUpdatedWsResp() {
setType("EXPORT_TASK_UPDATED"); setType("EXPORT_TASK_UPDATED");

View File

@ -21,7 +21,7 @@ datasource:
username: root username: root
database: database:
backup-dir: /Users/chyxion/Workspaces/ambition-crm/database_backups backup-dir: d:/data/database_backups
restore-shell: /data/program/mysql-backup/bin/mysql_restore.sh restore-shell: /data/program/mysql-backup/bin/mysql_restore.sh
file: file:

View File

@ -1,5 +1,12 @@
import BaseListRoute from './../base-list'; import BaseListRoute from './../base-list';
import { set } from '@ember/object';
export default BaseListRoute.extend({ export default BaseListRoute.extend({
breadcrumbs: [{text: 'Export Tasks'}] breadcrumbs: [{text: 'Export Tasks'}],
actions: {
download(rec) {
const me = this;
set(rec, 'downloaded', true);
}
}
}); });

View File

@ -7,8 +7,20 @@ export default BaseService.extend({
const me = this; const me = this;
$.on('WEBSOCKET', function(e, data) { $.on('WEBSOCKET', function(e, data) {
console.log('Export task: On websocket message trigger.', e, data); console.log('Export task: On websocket message trigger.', e, data);
if (data.type == 'EXPORT_TASK_UPDATED') { if (!data) {
me.set('countUndownloaded', data ? data.countUndownloaded : null); console.log('Event data is null, ignore.', e);
return;
}
me.set('countUndownloaded', data.countUndownloaded);
if (data.type == 'EXPORT_TASK_COMPLETED') {
if (data.success) {
me.get('message').alert('Export task completed.');
}
else {
me.get('message').error('Export task error caused: ' + data.message);
}
} }
}); });
} }

View File

@ -11,6 +11,11 @@ export default Service.extend({
warn(msg) { warn(msg) {
toastr.warning(msg); toastr.warning(msg);
}, },
error(msg) {
toastr.options.closeButton = true;
toastr.error(msg);
toastr.options.closeButton = false;
},
alert2(msg) { alert2(msg) {
this._show_alert_(msg); this._show_alert_(msg);
}, },

View File

@ -118,7 +118,7 @@ input[type="number"] {
font-size: 10px; font-size: 10px;
border-radius: 50%; border-radius: 50%;
color: #fff; color: #fff;
background-color: rgba(255, 0, 0, 0.8); background-color: rgba(209, 91, 71, 0.9);
// rgba(0, 0, 0, 0.7); // rgba(0, 0, 0, 0.7);
display: flex; display: flex;

View File

@ -100,9 +100,11 @@
</li> </li>
<li> <li>
<LinkTo @route="export-task.list" @model=1> <LinkTo @route="export-task.list" @model=1>
<i class="menu-icon fa fa-tasks {{if this.exportTaskService.countUndownloaded 'green' 'blue'}}"></i> <i class="menu-icon fa fa-tasks blue"></i>
<span class="export-task-badge">21</span> {{#if this.exportTaskService.countUndownloaded}}
<span class="menu-text"> Export Task {{#if this.exportTaskService.countUndownloaded}}<span class="badge badge-danger">{{this.exportTaskService.countUndownloaded}}</span>{{/if}}</span> <span class="export-task-badge">{{this.exportTaskService.countUndownloaded}}</span>
{{/if}}
<span class="menu-text"> Export Task </span>
</LinkTo> </LinkTo>
</li> </li>
{{/if}} {{/if}}

View File

@ -7,7 +7,9 @@
<table class="table table-striped table-bordered table-hover dataTable" style="border: 1px solid #ddd;"> <table class="table table-striped table-bordered table-hover dataTable" style="border: 1px solid #ddd;">
<thead class="thin-border-bottom"> <thead class="thin-border-bottom">
<tr> <tr>
{{sortable-th name='dateCreated' text='Date Created' style='min-width: 105px;'}} <th>
Date Created
</th>
<th> <th>
<i class="ace-icon fa fa-exchange bigger-110"></i> <i class="ace-icon fa fa-exchange bigger-110"></i>
Status Status
@ -32,32 +34,24 @@
{{it.status}} {{it.status}}
</td> </td>
<td> <td>
{{status-cell model=it field='downloaded' enabledText='DOWNLOADED' disabledText='NOT DOWNLOAD'}} {{#if it.downloaded}}
<span class="green">
<i class="ace-icon fa fa-check-square bigger-120"></i>
DOWNLOADED
</span>
{{else}}
<span class="blue">
<i class="ace-icon fa fa-spinner bigger-120"></i>
NOT DOWNLOAD
</span>
{{/if}}
</td> </td>
<td> <td>
<div class="hidden-sm hidden-xs btn-group"> <div class="btn-group">
{{#link-to 'user.edit' it.id class='btn btn-xs btn-info' data-rel='tooltip' title='Download'}} <a {{on 'click' (route-action 'download' it)}} target="_blank" href="/export-task/download?id={{it.id}}" class="btn btn-xs btn-info" data-rel="tooltip" title="Download">
<i class="ace-icon fa fa-pencil bigger-120"></i> <i class="ace-icon fa fa-download bigger-120"></i>
Download Download
{{/link-to}} </a>
</div>
<div class="hidden-md hidden-lg">
<div class="inline pos-rel">
<button class="btn btn-minier btn-primary dropdown-toggle" data-toggle="dropdown" data-position="auto">
<i class="ace-icon fa fa-cog icon-only bigger-110"></i>
</button>
<ul class="dropdown-menu dropdown-only-icon dropdown-yellow dropdown-menu-right dropdown-caret dropdown-close">
<li>
{{#link-to 'user.edit' it.id class='tooltip-info' data-rel='tooltip' title='Download'}}
<span class="blue">
<i class="ace-icon fa fa-pencil-square-o bigger-120"></i>
Download
</span>
{{/link-to}}
</li>
</ul>
</div>
</div> </div>
</td> </td>
</tr> </tr>