Add points task submission and review workflow
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
package com.ruoyi.web.controller.app;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.ruoyi.app.domain.AppPointTask;
|
||||
import com.ruoyi.app.domain.AppPointTaskAttachment;
|
||||
import com.ruoyi.app.domain.AppPointTaskSubmission;
|
||||
import com.ruoyi.app.domain.AppPointTaskUploadRequest;
|
||||
import com.ruoyi.app.service.IAppPointTaskService;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.annotation.RateLimiter;
|
||||
import com.ruoyi.common.annotation.RepeatSubmit;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.enums.LimitType;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
import com.ruoyi.web.qiniu.QiNiuConfig;
|
||||
import com.ruoyi.web.qiniu.QiNiuUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/** 积分任务、小程序提交和后台审核接口。 */
|
||||
@RestController
|
||||
@RequestMapping("/app/pointTask")
|
||||
public class AppPointTaskController extends BaseController
|
||||
{
|
||||
private static final long MAX_FILE_SIZE = 5L * 1024L * 1024L;
|
||||
private static final int UPLOAD_TOKEN_EXPIRES = 600;
|
||||
|
||||
@Autowired
|
||||
private IAppPointTaskService pointTaskService;
|
||||
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@GetMapping("/active")
|
||||
public AjaxResult activeList()
|
||||
{
|
||||
return success(pointTaskService.selectActiveTaskList(getUserId()));
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@GetMapping("/active/{id}")
|
||||
public AjaxResult activeDetail(@PathVariable Long id)
|
||||
{
|
||||
return success(pointTaskService.selectActiveTaskDetail(id, getUserId()));
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@RateLimiter(time = 60, count = 20, limitType = LimitType.IP)
|
||||
@PostMapping("/upload-token")
|
||||
public AjaxResult uploadToken(@RequestBody AppPointTaskUploadRequest request)
|
||||
{
|
||||
validateUploadRequest(request);
|
||||
Long userId = getUserId();
|
||||
String extension = extensionOf(request.getFileName(), request.getContentType());
|
||||
String key = "point-task/" + userId + "/"
|
||||
+ LocalDate.now(ZoneId.of("Asia/Shanghai")).toString().substring(0, 7).replace("-", "")
|
||||
+ "/" + IdUtils.fastSimpleUUID() + extension;
|
||||
|
||||
AppPointTaskAttachment attachment = new AppPointTaskAttachment();
|
||||
attachment.setUserId(userId);
|
||||
attachment.setObjectKey(key);
|
||||
attachment.setOriginalName(request.getFileName());
|
||||
attachment.setFileSize(request.getFileSize());
|
||||
attachment.setFileType(request.getContentType());
|
||||
pointTaskService.createTempAttachment(attachment);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("uploadToken", QiNiuUtil.createTaskImageUploadToken(key, UPLOAD_TOKEN_EXPIRES, MAX_FILE_SIZE));
|
||||
data.put("objectKey", key);
|
||||
data.put("uploadHost", QiNiuConfig.getInstance().getUploadHost());
|
||||
data.put("expiresIn", UPLOAD_TOKEN_EXPIRES);
|
||||
return success(data);
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@Log(title = "积分任务提交", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit(interval = 3000)
|
||||
@PostMapping("/{taskId}/submit")
|
||||
public AjaxResult submit(@PathVariable Long taskId, @RequestBody AppPointTaskSubmission request)
|
||||
{
|
||||
verifyUploadedFiles(request.getAttachmentKeys(), getUserId());
|
||||
return success(pointTaskService.submitTask(taskId, request, getUserId()));
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@Log(title = "积分任务重新提交", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit(interval = 3000)
|
||||
@PostMapping("/submission/{id}/resubmit")
|
||||
public AjaxResult resubmit(@PathVariable Long id, @RequestBody AppPointTaskSubmission request)
|
||||
{
|
||||
verifyUploadedFiles(request.getAttachmentKeys(), getUserId());
|
||||
return success(pointTaskService.resubmitTask(id, request, getUserId()));
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@GetMapping("/submission/my")
|
||||
public TableDataInfo myList()
|
||||
{
|
||||
startPage();
|
||||
return getDataTable(pointTaskService.selectMySubmissionList(getUserId()));
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@GetMapping("/submission/my/{id}")
|
||||
public AjaxResult myDetail(@PathVariable Long id)
|
||||
{
|
||||
AppPointTaskSubmission submission = pointTaskService.selectMySubmissionDetail(id, getUserId());
|
||||
populatePreviewUrls(submission);
|
||||
return success(submission);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('app:pointTask:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(AppPointTask task)
|
||||
{
|
||||
startPage();
|
||||
return getDataTable(pointTaskService.selectAppPointTaskList(task));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('app:pointTask:query')")
|
||||
@GetMapping("/{id}")
|
||||
public AjaxResult detail(@PathVariable Long id)
|
||||
{
|
||||
return success(pointTaskService.selectAppPointTaskById(id));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('app:pointTask:add')")
|
||||
@Log(title = "积分任务", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody AppPointTask task)
|
||||
{
|
||||
task.setCreateBy(getUsername());
|
||||
task.setUpdateBy(getUsername());
|
||||
return toAjax(pointTaskService.insertAppPointTask(task));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('app:pointTask:edit')")
|
||||
@Log(title = "积分任务", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody AppPointTask task)
|
||||
{
|
||||
task.setUpdateBy(getUsername());
|
||||
return toAjax(pointTaskService.updateAppPointTask(task));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('app:pointTask:remove')")
|
||||
@Log(title = "积分任务", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Long[] ids)
|
||||
{
|
||||
return toAjax(pointTaskService.deleteAppPointTaskByIds(ids));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('app:pointTaskReview:list')")
|
||||
@GetMapping("/submission/list")
|
||||
public TableDataInfo submissionList(AppPointTaskSubmission submission)
|
||||
{
|
||||
startPage();
|
||||
return getDataTable(pointTaskService.selectSubmissionList(submission));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('app:pointTaskReview:query')")
|
||||
@GetMapping("/submission/{id}")
|
||||
public AjaxResult submissionDetail(@PathVariable Long id)
|
||||
{
|
||||
AppPointTaskSubmission submission = pointTaskService.selectSubmissionDetail(id);
|
||||
if (submission == null)
|
||||
{
|
||||
return error("任务记录不存在");
|
||||
}
|
||||
populatePreviewUrls(submission);
|
||||
return success(submission);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('app:pointTaskReview:edit')")
|
||||
@Log(title = "积分任务审核通过", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/submission/{id}/approve")
|
||||
public AjaxResult approve(@PathVariable Long id)
|
||||
{
|
||||
return toAjax(pointTaskService.approveSubmission(id, getUsername()));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('app:pointTaskReview:edit')")
|
||||
@Log(title = "积分任务审核驳回", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/submission/{id}/reject")
|
||||
public AjaxResult reject(@PathVariable Long id, @RequestBody AppPointTaskSubmission request)
|
||||
{
|
||||
return toAjax(pointTaskService.rejectSubmission(id, request.getRejectReason(), getUsername()));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('app:pointTaskReview:revoke')")
|
||||
@Log(title = "积分任务撤销奖励", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/submission/{id}/revoke")
|
||||
public AjaxResult revoke(@PathVariable Long id, @RequestBody AppPointTaskSubmission request)
|
||||
{
|
||||
return toAjax(pointTaskService.revokeSubmission(id, request.getRejectReason(), getUsername()));
|
||||
}
|
||||
|
||||
private void validateUploadRequest(AppPointTaskUploadRequest request)
|
||||
{
|
||||
if (request == null || StringUtils.isEmpty(request.getFileName()) || request.getFileSize() == null
|
||||
|| request.getFileSize() <= 0 || request.getFileSize() > MAX_FILE_SIZE)
|
||||
{
|
||||
throw new ServiceException("截图大小不能超过5MB");
|
||||
}
|
||||
String contentType = StringUtils.trim(request.getContentType());
|
||||
if (!"image/jpeg".equals(contentType) && !"image/png".equals(contentType) && !"image/webp".equals(contentType))
|
||||
{
|
||||
throw new ServiceException("仅支持JPG、PNG和WebP图片");
|
||||
}
|
||||
}
|
||||
|
||||
private String extensionOf(String fileName, String contentType)
|
||||
{
|
||||
if ("image/png".equals(contentType)) return ".png";
|
||||
if ("image/webp".equals(contentType)) return ".webp";
|
||||
return ".jpg";
|
||||
}
|
||||
|
||||
private void verifyUploadedFiles(List<String> keys, Long userId)
|
||||
{
|
||||
if (keys == null || keys.isEmpty() || keys.size() > 3)
|
||||
{
|
||||
throw new ServiceException("请上传1至3张任务截图");
|
||||
}
|
||||
String prefix = "point-task/" + userId + "/";
|
||||
for (String key : keys)
|
||||
{
|
||||
if (StringUtils.isEmpty(key) || !key.startsWith(prefix) || !QiNiuUtil.exists(key))
|
||||
{
|
||||
throw new ServiceException("截图上传未完成,请重新上传");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void populatePreviewUrls(AppPointTaskSubmission submission)
|
||||
{
|
||||
if (submission == null || submission.getAttachments() == null) return;
|
||||
for (AppPointTaskAttachment attachment : submission.getAttachments())
|
||||
{
|
||||
try
|
||||
{
|
||||
attachment.setPreviewUrl(QiNiuUtil.fileUrl(attachment.getObjectKey()));
|
||||
}
|
||||
catch (UnsupportedEncodingException ex)
|
||||
{
|
||||
attachment.setPreviewUrl(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import com.ruoyi.common.config.RuoYiConfig;
|
||||
import com.ruoyi.common.constant.Constants;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.file.FileUploadUtils;
|
||||
import com.ruoyi.common.utils.file.FileUtils;
|
||||
@@ -94,21 +95,34 @@ public class CommonController {
|
||||
@PostMapping("/qiniuUpload")
|
||||
public AjaxResult qiniuUploadFile(MultipartFile file) throws Exception {
|
||||
try {
|
||||
// 上传并返回新文件名称
|
||||
if (file == null || file.isEmpty()) {
|
||||
return AjaxResult.error("上传文件不能为空");
|
||||
}
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
String fname = originalFilename.substring(0, originalFilename.lastIndexOf('.')) + "-" + UUID.randomUUID().toString().replace("-", "");
|
||||
String fkey = "http://" + QiNiuConfig.getInstance().getDomainOfBucket() + "/" + fname;
|
||||
String extension = "";
|
||||
if (originalFilename != null && originalFilename.lastIndexOf('.') >= 0) {
|
||||
extension = originalFilename.substring(originalFilename.lastIndexOf('.')).toLowerCase();
|
||||
}
|
||||
if (!extension.equals(".jpg") && !extension.equals(".jpeg")
|
||||
&& !extension.equals(".png") && !extension.equals(".webp")) {
|
||||
return AjaxResult.error("仅支持JPG、PNG和WebP图片");
|
||||
}
|
||||
String fname = "admin-images/" + DateUtils.datePath() + "/"
|
||||
+ UUID.randomUUID().toString().replace("-", "") + extension;
|
||||
String domain = QiNiuConfig.getInstance().getDomainOfBucket();
|
||||
if (!domain.startsWith("http://") && !domain.startsWith("https://")) {
|
||||
domain = "https://" + domain;
|
||||
}
|
||||
String fkey = domain.replaceAll("/$", "") + "/" + fname;
|
||||
if(!StringUtils.isNull(QiNiuConfig.getInstance().getPictureStyle()) && !QiNiuConfig.getInstance().getPictureStyle().equals("")){
|
||||
fkey = fkey + "?" + QiNiuConfig.getInstance().getPictureStyle();
|
||||
}
|
||||
try {
|
||||
QiNiuUtil.uploadMultipartFile(file, fname, false);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
if (!QiNiuUtil.uploadMultipartFile(file, fname, false)) {
|
||||
return AjaxResult.error("七牛云上传失败,请检查七牛配置或网络");
|
||||
}
|
||||
AjaxResult ajax = AjaxResult.success();
|
||||
ajax.put("url", fkey);
|
||||
ajax.put("fileName", file.getOriginalFilename());
|
||||
ajax.put("fileName", originalFilename);
|
||||
return ajax;
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error(e.getMessage());
|
||||
|
||||
@@ -10,6 +10,7 @@ public class QiNiuConfig {
|
||||
private String bucket;
|
||||
private Zone zone;
|
||||
private String domainOfBucket;
|
||||
private String uploadHost;
|
||||
private long expireInSeconds;
|
||||
private String pictureStyle;
|
||||
|
||||
@@ -23,6 +24,7 @@ public class QiNiuConfig {
|
||||
secretKey = prop.getProperty("qiniu.secret-key");
|
||||
bucket = prop.getProperty("qiniu.bucket");
|
||||
domainOfBucket = prop.getProperty("qiniu.domain-of-bucket");
|
||||
uploadHost = prop.getProperty("qiniu.upload-host", "https://upload.qiniup.com");
|
||||
expireInSeconds = Long.parseLong(prop.getProperty("qiniu.expire-in-seconds"));
|
||||
pictureStyle = prop.getProperty("qiniu.picture-style");
|
||||
String zoneName = prop.getProperty("qiniu.zone");
|
||||
@@ -88,6 +90,14 @@ public class QiNiuConfig {
|
||||
this.domainOfBucket = domainOfBucket;
|
||||
}
|
||||
|
||||
public String getUploadHost() {
|
||||
return uploadHost;
|
||||
}
|
||||
|
||||
public void setUploadHost(String uploadHost) {
|
||||
this.uploadHost = uploadHost;
|
||||
}
|
||||
|
||||
public long getExpireInSeconds() {
|
||||
return expireInSeconds;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ import com.qiniu.storage.BucketManager;
|
||||
import com.qiniu.storage.Configuration;
|
||||
import com.qiniu.storage.UploadManager;
|
||||
import com.qiniu.storage.model.DefaultPutRet;
|
||||
import com.qiniu.storage.model.FileInfo;
|
||||
import com.qiniu.util.Auth;
|
||||
import com.qiniu.util.StringMap;
|
||||
import com.qiniu.util.UrlSafeBase64;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
@@ -101,8 +103,19 @@ public class QiNiuUtil {
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
public static String fileUrl(String fileName) throws UnsupportedEncodingException {
|
||||
String encodedFileName = URLEncoder.encode(fileName, "utf-8");
|
||||
String publicUrl = String.format("%s/%s", QiNiuConfig.getInstance().getDomainOfBucket(), encodedFileName);
|
||||
String[] parts = fileName.split("/");
|
||||
StringBuilder encoded = new StringBuilder();
|
||||
for (String part : parts) {
|
||||
if (encoded.length() > 0) {
|
||||
encoded.append('/');
|
||||
}
|
||||
encoded.append(URLEncoder.encode(part, "utf-8").replace("+", "%20"));
|
||||
}
|
||||
String domain = QiNiuConfig.getInstance().getDomainOfBucket();
|
||||
if (!domain.startsWith("http://") && !domain.startsWith("https://")) {
|
||||
domain = "https://" + domain;
|
||||
}
|
||||
String publicUrl = String.format("%s/%s", domain.replaceAll("/$", ""), encoded);
|
||||
Auth auth = getAuth();
|
||||
long expireInSeconds = QiNiuConfig.getInstance().getExpireInSeconds();
|
||||
if(-1 == expireInSeconds){
|
||||
@@ -168,20 +181,46 @@ public class QiNiuUtil {
|
||||
return auth;
|
||||
}
|
||||
|
||||
/** 生成只允许上传指定任务截图的短期凭证。 */
|
||||
public static String createTaskImageUploadToken(String key, int expiresSeconds, long fileSizeLimit) {
|
||||
StringMap policy = new StringMap();
|
||||
policy.put("insertOnly", 1);
|
||||
policy.put("fsizeLimit", fileSizeLimit);
|
||||
policy.put("mimeLimit", "image/jpeg;image/png;image/webp");
|
||||
return getAuth().uploadToken(QiNiuConfig.getInstance().getBucket(), key, expiresSeconds, policy);
|
||||
}
|
||||
|
||||
/** 判断七牛对象是否真实存在。 */
|
||||
public static boolean exists(String key) {
|
||||
Configuration cfg = new Configuration(QiNiuConfig.getInstance().getZone());
|
||||
BucketManager bucketManager = new BucketManager(getAuth(), cfg);
|
||||
try {
|
||||
FileInfo info = bucketManager.stat(QiNiuConfig.getInstance().getBucket(), key);
|
||||
return info != null && info.fsize > 0;
|
||||
} catch (QiniuException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param key
|
||||
*/
|
||||
public static void delete(String key){
|
||||
public static boolean delete(String key){
|
||||
Configuration cfg = new Configuration(QiNiuConfig.getInstance().getZone());
|
||||
Auth auth = getAuth();
|
||||
BucketManager bucketManager = new BucketManager(auth, cfg);
|
||||
try {
|
||||
bucketManager.delete(QiNiuConfig.getInstance().getBucket(), key);
|
||||
return true;
|
||||
} catch (QiniuException ex) {
|
||||
if (ex.code() == 612) {
|
||||
return true;
|
||||
}
|
||||
//如果遇到异常,说明删除失败
|
||||
System.err.println(ex.code());
|
||||
System.err.println(ex.response.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.ruoyi.web.task;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.ruoyi.app.domain.AppPointTaskAttachment;
|
||||
import com.ruoyi.app.mapper.AppPointTaskSubmissionMapper;
|
||||
import com.ruoyi.web.qiniu.QiNiuUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
/** 每天清理超过24小时仍未绑定任务的七牛临时截图。 */
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
public class PointTaskAttachmentCleanupJob
|
||||
{
|
||||
private static final long TEMP_RETENTION_MILLIS = 24L * 60L * 60L * 1000L;
|
||||
|
||||
@Autowired
|
||||
private AppPointTaskSubmissionMapper submissionMapper;
|
||||
|
||||
@Scheduled(cron = "0 30 3 * * ?")
|
||||
public void cleanExpiredTaskImages()
|
||||
{
|
||||
Date beforeTime = new Date(System.currentTimeMillis() - TEMP_RETENTION_MILLIS);
|
||||
List<AppPointTaskAttachment> attachments = submissionMapper.selectExpiredTempAttachments(beforeTime);
|
||||
for (AppPointTaskAttachment attachment : attachments)
|
||||
{
|
||||
if (QiNiuUtil.delete(attachment.getObjectKey()))
|
||||
{
|
||||
submissionMapper.deleteTempAttachmentById(attachment.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user