Add points task submission and review workflow
This commit is contained in:
48
docs/point-task-setup.md
Normal file
48
docs/point-task-setup.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# 积分任务中心上线说明
|
||||
|
||||
## 1. 初始化数据库
|
||||
|
||||
在目标业务数据库执行:
|
||||
|
||||
```text
|
||||
sql/point_task.sql
|
||||
```
|
||||
|
||||
脚本会创建任务、提交、附件、审核日志表,为积分流水增加业务幂等字段,并添加“积分任务”和“任务审核”后台菜单。
|
||||
|
||||
## 2. 七牛云配置
|
||||
|
||||
确认 `ruoyi-admin/src/main/resources/qiniu.properties` 中以下配置与七牛空间一致:
|
||||
|
||||
```properties
|
||||
qiniu.bucket=blog
|
||||
qiniu.zone=zone0
|
||||
qiniu.domain-of-bucket=img.yidaima.cn
|
||||
qiniu.upload-host=https://upload.qiniup.com
|
||||
```
|
||||
|
||||
任务截图使用客户端直传。后端签发10分钟有效、绑定单个对象Key、限制5MB且仅允许 JPG/PNG/WebP 的上传凭证。
|
||||
|
||||
上传凭证接口按IP限制每分钟最多20次;每个用户最多保留9张未提交截图,每天最多申请30张截图凭证。
|
||||
|
||||
后端每天凌晨 03:30 自动清理超过24小时仍未绑定任务的七牛临时截图和数据库临时记录。
|
||||
|
||||
## 3. 微信公众平台合法域名
|
||||
|
||||
在小程序后台配置:
|
||||
|
||||
- `uploadFile` 合法域名:`https://upload.qiniup.com`
|
||||
- `downloadFile` 合法域名:七牛图片域名,例如 `https://img.yidaima.cn`
|
||||
- `request` 合法域名:现有业务接口域名
|
||||
|
||||
如果七牛空间区域发生变化,请同步修改 `qiniu.upload-host` 和微信小程序合法域名。
|
||||
|
||||
## 4. 发布顺序
|
||||
|
||||
1. 执行数据库脚本。
|
||||
2. 发布 RuoYi 后端。
|
||||
3. 发布 RuoYi 管理端。
|
||||
4. 在后台创建一次性或每日任务。
|
||||
5. 发布小程序前端。
|
||||
|
||||
未执行数据库脚本前,不要调用新的任务接口。
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,10 @@ public class AppIntegralRecord extends BaseEntity {
|
||||
private Date integralTime;
|
||||
private Long fId;
|
||||
private Long resourceId;
|
||||
/** 业务类型,用于积分操作幂等。 */
|
||||
private String bizType;
|
||||
/** 业务记录主键。 */
|
||||
private Long bizId;
|
||||
|
||||
/**
|
||||
* 资源标题
|
||||
@@ -123,6 +127,22 @@ public class AppIntegralRecord extends BaseEntity {
|
||||
public void setResourceId(Long resourceId) {
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
public String getBizType() {
|
||||
return bizType;
|
||||
}
|
||||
|
||||
public void setBizType(String bizType) {
|
||||
this.bizType = bizType;
|
||||
}
|
||||
|
||||
public Long getBizId() {
|
||||
return bizId;
|
||||
}
|
||||
|
||||
public void setBizId(Long bizId) {
|
||||
this.bizId = bizId;
|
||||
}
|
||||
|
||||
public String getResourceTitle() {
|
||||
return resourceTitle;
|
||||
@@ -141,6 +161,8 @@ public class AppIntegralRecord extends BaseEntity {
|
||||
.append("integralNumber", getIntegralNumber())
|
||||
.append("userId", getUserId())
|
||||
.append("resourceId", getResourceId())
|
||||
.append("bizType", getBizType())
|
||||
.append("bizId", getBizId())
|
||||
.append("resourceTitle", getResourceTitle())
|
||||
.append("integralTime", getIntegralTime())
|
||||
.toString();
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.ruoyi.app.domain;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
/** 积分任务。 */
|
||||
public class AppPointTask extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private String taskName;
|
||||
private String taskType;
|
||||
private String cycleType;
|
||||
private String coverUrl;
|
||||
private String description;
|
||||
private String operationGuide;
|
||||
private String exampleImages;
|
||||
private Integer rewardPoints;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date startTime;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date endTime;
|
||||
private Integer allowResubmit;
|
||||
private String status;
|
||||
private Integer sortOrder;
|
||||
|
||||
/** 当前用户在本周期的状态,非数据库字段。 */
|
||||
private Integer currentStatus;
|
||||
private Boolean canSubmit;
|
||||
private Long currentSubmissionId;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date nextAvailableAt;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public String getTaskName() { return taskName; }
|
||||
public void setTaskName(String taskName) { this.taskName = taskName; }
|
||||
public String getTaskType() { return taskType; }
|
||||
public void setTaskType(String taskType) { this.taskType = taskType; }
|
||||
public String getCycleType() { return cycleType; }
|
||||
public void setCycleType(String cycleType) { this.cycleType = cycleType; }
|
||||
public String getCoverUrl() { return coverUrl; }
|
||||
public void setCoverUrl(String coverUrl) { this.coverUrl = coverUrl; }
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
public String getOperationGuide() { return operationGuide; }
|
||||
public void setOperationGuide(String operationGuide) { this.operationGuide = operationGuide; }
|
||||
public String getExampleImages() { return exampleImages; }
|
||||
public void setExampleImages(String exampleImages) { this.exampleImages = exampleImages; }
|
||||
public Integer getRewardPoints() { return rewardPoints; }
|
||||
public void setRewardPoints(Integer rewardPoints) { this.rewardPoints = rewardPoints; }
|
||||
public Date getStartTime() { return startTime; }
|
||||
public void setStartTime(Date startTime) { this.startTime = startTime; }
|
||||
public Date getEndTime() { return endTime; }
|
||||
public void setEndTime(Date endTime) { this.endTime = endTime; }
|
||||
public Integer getAllowResubmit() { return allowResubmit; }
|
||||
public void setAllowResubmit(Integer allowResubmit) { this.allowResubmit = allowResubmit; }
|
||||
public String getStatus() { return status; }
|
||||
public void setStatus(String status) { this.status = status; }
|
||||
public Integer getSortOrder() { return sortOrder; }
|
||||
public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
|
||||
public Integer getCurrentStatus() { return currentStatus; }
|
||||
public void setCurrentStatus(Integer currentStatus) { this.currentStatus = currentStatus; }
|
||||
public Boolean getCanSubmit() { return canSubmit; }
|
||||
public void setCanSubmit(Boolean canSubmit) { this.canSubmit = canSubmit; }
|
||||
public Long getCurrentSubmissionId() { return currentSubmissionId; }
|
||||
public void setCurrentSubmissionId(Long currentSubmissionId) { this.currentSubmissionId = currentSubmissionId; }
|
||||
public Date getNextAvailableAt() { return nextAvailableAt; }
|
||||
public void setNextAvailableAt(Date nextAvailableAt) { this.nextAvailableAt = nextAvailableAt; }
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.ruoyi.app.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
/** 积分任务截图附件。 */
|
||||
public class AppPointTaskAttachment implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private Long submissionId;
|
||||
private Long userId;
|
||||
private String objectKey;
|
||||
private String originalName;
|
||||
private Long fileSize;
|
||||
private String fileType;
|
||||
private String uploadStatus;
|
||||
private Integer submitBatch;
|
||||
private Integer isCurrent;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
/** 七牛临时访问地址,非数据库字段。 */
|
||||
private String previewUrl;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public Long getSubmissionId() { return submissionId; }
|
||||
public void setSubmissionId(Long submissionId) { this.submissionId = submissionId; }
|
||||
public Long getUserId() { return userId; }
|
||||
public void setUserId(Long userId) { this.userId = userId; }
|
||||
public String getObjectKey() { return objectKey; }
|
||||
public void setObjectKey(String objectKey) { this.objectKey = objectKey; }
|
||||
public String getOriginalName() { return originalName; }
|
||||
public void setOriginalName(String originalName) { this.originalName = originalName; }
|
||||
public Long getFileSize() { return fileSize; }
|
||||
public void setFileSize(Long fileSize) { this.fileSize = fileSize; }
|
||||
public String getFileType() { return fileType; }
|
||||
public void setFileType(String fileType) { this.fileType = fileType; }
|
||||
public String getUploadStatus() { return uploadStatus; }
|
||||
public void setUploadStatus(String uploadStatus) { this.uploadStatus = uploadStatus; }
|
||||
public Integer getSubmitBatch() { return submitBatch; }
|
||||
public void setSubmitBatch(Integer submitBatch) { this.submitBatch = submitBatch; }
|
||||
public Integer getIsCurrent() { return isCurrent; }
|
||||
public void setIsCurrent(Integer isCurrent) { this.isCurrent = isCurrent; }
|
||||
public Date getCreateTime() { return createTime; }
|
||||
public void setCreateTime(Date createTime) { this.createTime = createTime; }
|
||||
public String getPreviewUrl() { return previewUrl; }
|
||||
public void setPreviewUrl(String previewUrl) { this.previewUrl = previewUrl; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ruoyi.app.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/** 积分任务审核日志。 */
|
||||
public class AppPointTaskAuditLog implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private Long submissionId;
|
||||
private Integer beforeStatus;
|
||||
private Integer afterStatus;
|
||||
private String operatorName;
|
||||
private String remark;
|
||||
private Date createTime;
|
||||
|
||||
public Long getSubmissionId() { return submissionId; }
|
||||
public void setSubmissionId(Long submissionId) { this.submissionId = submissionId; }
|
||||
public Integer getBeforeStatus() { return beforeStatus; }
|
||||
public void setBeforeStatus(Integer beforeStatus) { this.beforeStatus = beforeStatus; }
|
||||
public Integer getAfterStatus() { return afterStatus; }
|
||||
public void setAfterStatus(Integer afterStatus) { this.afterStatus = afterStatus; }
|
||||
public String getOperatorName() { return operatorName; }
|
||||
public void setOperatorName(String operatorName) { this.operatorName = operatorName; }
|
||||
public String getRemark() { return remark; }
|
||||
public void setRemark(String remark) { this.remark = remark; }
|
||||
public Date getCreateTime() { return createTime; }
|
||||
public void setCreateTime(Date createTime) { this.createTime = createTime; }
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.ruoyi.app.domain;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
/** 积分任务提交记录。 */
|
||||
public class AppPointTaskSubmission extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private String submissionNo;
|
||||
private Long taskId;
|
||||
private Long userId;
|
||||
private String cycleKey;
|
||||
private Integer rewardPoints;
|
||||
private Integer status;
|
||||
private Integer attemptCount;
|
||||
private String rejectReason;
|
||||
private String reviewBy;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date reviewTime;
|
||||
private Long rewardRecordId;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date firstSubmitTime;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date lastSubmitTime;
|
||||
|
||||
private String taskName;
|
||||
private String taskType;
|
||||
private String cycleType;
|
||||
private String userName;
|
||||
private String nickName;
|
||||
private List<String> attachmentKeys;
|
||||
private List<AppPointTaskAttachment> attachments;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public String getSubmissionNo() { return submissionNo; }
|
||||
public void setSubmissionNo(String submissionNo) { this.submissionNo = submissionNo; }
|
||||
public Long getTaskId() { return taskId; }
|
||||
public void setTaskId(Long taskId) { this.taskId = taskId; }
|
||||
public Long getUserId() { return userId; }
|
||||
public void setUserId(Long userId) { this.userId = userId; }
|
||||
public String getCycleKey() { return cycleKey; }
|
||||
public void setCycleKey(String cycleKey) { this.cycleKey = cycleKey; }
|
||||
public Integer getRewardPoints() { return rewardPoints; }
|
||||
public void setRewardPoints(Integer rewardPoints) { this.rewardPoints = rewardPoints; }
|
||||
public Integer getStatus() { return status; }
|
||||
public void setStatus(Integer status) { this.status = status; }
|
||||
public Integer getAttemptCount() { return attemptCount; }
|
||||
public void setAttemptCount(Integer attemptCount) { this.attemptCount = attemptCount; }
|
||||
public String getRejectReason() { return rejectReason; }
|
||||
public void setRejectReason(String rejectReason) { this.rejectReason = rejectReason; }
|
||||
public String getReviewBy() { return reviewBy; }
|
||||
public void setReviewBy(String reviewBy) { this.reviewBy = reviewBy; }
|
||||
public Date getReviewTime() { return reviewTime; }
|
||||
public void setReviewTime(Date reviewTime) { this.reviewTime = reviewTime; }
|
||||
public Long getRewardRecordId() { return rewardRecordId; }
|
||||
public void setRewardRecordId(Long rewardRecordId) { this.rewardRecordId = rewardRecordId; }
|
||||
public Date getFirstSubmitTime() { return firstSubmitTime; }
|
||||
public void setFirstSubmitTime(Date firstSubmitTime) { this.firstSubmitTime = firstSubmitTime; }
|
||||
public Date getLastSubmitTime() { return lastSubmitTime; }
|
||||
public void setLastSubmitTime(Date lastSubmitTime) { this.lastSubmitTime = lastSubmitTime; }
|
||||
public String getTaskName() { return taskName; }
|
||||
public void setTaskName(String taskName) { this.taskName = taskName; }
|
||||
public String getTaskType() { return taskType; }
|
||||
public void setTaskType(String taskType) { this.taskType = taskType; }
|
||||
public String getCycleType() { return cycleType; }
|
||||
public void setCycleType(String cycleType) { this.cycleType = cycleType; }
|
||||
public String getUserName() { return userName; }
|
||||
public void setUserName(String userName) { this.userName = userName; }
|
||||
public String getNickName() { return nickName; }
|
||||
public void setNickName(String nickName) { this.nickName = nickName; }
|
||||
public List<String> getAttachmentKeys() { return attachmentKeys; }
|
||||
public void setAttachmentKeys(List<String> attachmentKeys) { this.attachmentKeys = attachmentKeys; }
|
||||
public List<AppPointTaskAttachment> getAttachments() { return attachments; }
|
||||
public void setAttachments(List<AppPointTaskAttachment> attachments) { this.attachments = attachments; }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ruoyi.app.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/** 七牛任务截图上传凭证请求。 */
|
||||
public class AppPointTaskUploadRequest implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String fileName;
|
||||
private Long fileSize;
|
||||
private String contentType;
|
||||
|
||||
public String getFileName() { return fileName; }
|
||||
public void setFileName(String fileName) { this.fileName = fileName; }
|
||||
public Long getFileSize() { return fileSize; }
|
||||
public void setFileSize(Long fileSize) { this.fileSize = fileSize; }
|
||||
public String getContentType() { return contentType; }
|
||||
public void setContentType(String contentType) { this.contentType = contentType; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.ruoyi.app.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.app.domain.AppPointTask;
|
||||
|
||||
public interface AppPointTaskMapper
|
||||
{
|
||||
AppPointTask selectAppPointTaskById(Long id);
|
||||
List<AppPointTask> selectAppPointTaskList(AppPointTask task);
|
||||
List<AppPointTask> selectActiveAppPointTaskList();
|
||||
int insertAppPointTask(AppPointTask task);
|
||||
int updateAppPointTask(AppPointTask task);
|
||||
int deleteAppPointTaskByIds(Long[] ids);
|
||||
int countSubmissionByTaskIds(Long[] ids);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.ruoyi.app.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Date;
|
||||
import com.ruoyi.app.domain.AppPointTaskAttachment;
|
||||
import com.ruoyi.app.domain.AppPointTaskAuditLog;
|
||||
import com.ruoyi.app.domain.AppPointTaskSubmission;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface AppPointTaskSubmissionMapper
|
||||
{
|
||||
AppPointTaskSubmission selectSubmissionById(Long id);
|
||||
AppPointTaskSubmission selectSubmissionByIdForUpdate(Long id);
|
||||
AppPointTaskSubmission selectByTaskUserCycle(@Param("taskId") Long taskId,
|
||||
@Param("userId") Long userId,
|
||||
@Param("cycleKey") String cycleKey);
|
||||
List<AppPointTaskSubmission> selectSubmissionList(AppPointTaskSubmission submission);
|
||||
List<AppPointTaskSubmission> selectMySubmissionList(@Param("userId") Long userId);
|
||||
int insertSubmission(AppPointTaskSubmission submission);
|
||||
int updateSubmission(AppPointTaskSubmission submission);
|
||||
|
||||
int insertTempAttachment(AppPointTaskAttachment attachment);
|
||||
Long lockUserForUpload(@Param("userId") Long userId);
|
||||
int countTempAttachmentsByUser(@Param("userId") Long userId);
|
||||
int countAttachmentsByUserSince(@Param("userId") Long userId, @Param("since") String since);
|
||||
List<AppPointTaskAttachment> selectTempAttachments(@Param("userId") Long userId,
|
||||
@Param("keys") List<String> keys);
|
||||
int bindAttachments(@Param("userId") Long userId,
|
||||
@Param("submissionId") Long submissionId,
|
||||
@Param("submitBatch") Integer submitBatch,
|
||||
@Param("keys") List<String> keys);
|
||||
int markAttachmentsNotCurrent(Long submissionId);
|
||||
List<AppPointTaskAttachment> selectAttachmentsBySubmissionId(Long submissionId);
|
||||
List<AppPointTaskAttachment> selectExpiredTempAttachments(Date beforeTime);
|
||||
int deleteTempAttachmentById(Long id);
|
||||
int insertAuditLog(AppPointTaskAuditLog auditLog);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.ruoyi.app.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.app.domain.AppPointTask;
|
||||
import com.ruoyi.app.domain.AppPointTaskAttachment;
|
||||
import com.ruoyi.app.domain.AppPointTaskSubmission;
|
||||
|
||||
public interface IAppPointTaskService
|
||||
{
|
||||
AppPointTask selectAppPointTaskById(Long id);
|
||||
List<AppPointTask> selectAppPointTaskList(AppPointTask task);
|
||||
List<AppPointTask> selectActiveTaskList(Long userId);
|
||||
AppPointTask selectActiveTaskDetail(Long id, Long userId);
|
||||
int insertAppPointTask(AppPointTask task);
|
||||
int updateAppPointTask(AppPointTask task);
|
||||
int deleteAppPointTaskByIds(Long[] ids);
|
||||
|
||||
AppPointTaskAttachment createTempAttachment(AppPointTaskAttachment attachment);
|
||||
AppPointTaskSubmission submitTask(Long taskId, AppPointTaskSubmission request, Long userId);
|
||||
AppPointTaskSubmission resubmitTask(Long submissionId, AppPointTaskSubmission request, Long userId);
|
||||
List<AppPointTaskSubmission> selectMySubmissionList(Long userId);
|
||||
AppPointTaskSubmission selectMySubmissionDetail(Long id, Long userId);
|
||||
List<AppPointTaskSubmission> selectSubmissionList(AppPointTaskSubmission submission);
|
||||
AppPointTaskSubmission selectSubmissionDetail(Long id);
|
||||
int approveSubmission(Long id, String operator);
|
||||
int rejectSubmission(Long id, String rejectReason, String operator);
|
||||
int revokeSubmission(Long id, String reason, String operator);
|
||||
}
|
||||
@@ -0,0 +1,544 @@
|
||||
package com.ruoyi.app.service.impl;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import com.ruoyi.app.domain.AppIntegralRecord;
|
||||
import com.ruoyi.app.domain.AppPointTask;
|
||||
import com.ruoyi.app.domain.AppPointTaskAttachment;
|
||||
import com.ruoyi.app.domain.AppPointTaskAuditLog;
|
||||
import com.ruoyi.app.domain.AppPointTaskSubmission;
|
||||
import com.ruoyi.app.mapper.AppIntegralRecordMapper;
|
||||
import com.ruoyi.app.mapper.AppPointTaskMapper;
|
||||
import com.ruoyi.app.mapper.AppPointTaskSubmissionMapper;
|
||||
import com.ruoyi.app.service.IAppPointTaskService;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
import com.ruoyi.system.mapper.SysUserMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class AppPointTaskServiceImpl implements IAppPointTaskService
|
||||
{
|
||||
private static final String CYCLE_ONCE = "ONCE";
|
||||
private static final String CYCLE_DAILY = "DAILY";
|
||||
private static final int STATUS_PENDING = 0;
|
||||
private static final int STATUS_APPROVED = 1;
|
||||
private static final int STATUS_REJECTED = 2;
|
||||
private static final int STATUS_REVOKED = 3;
|
||||
private static final int MAX_TEMP_ATTACHMENTS_PER_USER = 9;
|
||||
private static final int MAX_DAILY_ATTACHMENTS_PER_USER = 30;
|
||||
private static final ZoneId SHANGHAI_ZONE = ZoneId.of("Asia/Shanghai");
|
||||
|
||||
@Autowired
|
||||
private AppPointTaskMapper taskMapper;
|
||||
@Autowired
|
||||
private AppPointTaskSubmissionMapper submissionMapper;
|
||||
@Autowired
|
||||
private AppIntegralRecordMapper integralRecordMapper;
|
||||
@Autowired
|
||||
private SysUserMapper userMapper;
|
||||
|
||||
@Override
|
||||
public AppPointTask selectAppPointTaskById(Long id)
|
||||
{
|
||||
return taskMapper.selectAppPointTaskById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AppPointTask> selectAppPointTaskList(AppPointTask task)
|
||||
{
|
||||
return taskMapper.selectAppPointTaskList(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AppPointTask> selectActiveTaskList(Long userId)
|
||||
{
|
||||
List<AppPointTask> tasks = taskMapper.selectActiveAppPointTaskList();
|
||||
for (AppPointTask task : tasks)
|
||||
{
|
||||
populateUserState(task, userId);
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppPointTask selectActiveTaskDetail(Long id, Long userId)
|
||||
{
|
||||
AppPointTask task = taskMapper.selectAppPointTaskById(id);
|
||||
if (task == null)
|
||||
{
|
||||
throw new ServiceException("任务不存在");
|
||||
}
|
||||
populateUserState(task, userId);
|
||||
if (!isTaskActive(task, DateUtils.getNowDate()))
|
||||
{
|
||||
task.setCanSubmit(false);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertAppPointTask(AppPointTask task)
|
||||
{
|
||||
normalizeAndValidateTask(task);
|
||||
Date now = DateUtils.getNowDate();
|
||||
task.setCreateTime(now);
|
||||
task.setUpdateTime(now);
|
||||
return taskMapper.insertAppPointTask(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateAppPointTask(AppPointTask task)
|
||||
{
|
||||
if (task.getId() == null)
|
||||
{
|
||||
throw new ServiceException("任务不存在");
|
||||
}
|
||||
AppPointTask current = taskMapper.selectAppPointTaskById(task.getId());
|
||||
if (current == null)
|
||||
{
|
||||
throw new ServiceException("任务不存在");
|
||||
}
|
||||
normalizeAndValidateTask(task);
|
||||
if (taskMapper.countSubmissionByTaskIds(new Long[] { task.getId() }) > 0
|
||||
&& (!current.getRewardPoints().equals(task.getRewardPoints())
|
||||
|| !current.getCycleType().equals(task.getCycleType())))
|
||||
{
|
||||
throw new ServiceException("任务已有用户提交,不能修改奖励积分或任务周期");
|
||||
}
|
||||
task.setUpdateTime(DateUtils.getNowDate());
|
||||
return taskMapper.updateAppPointTask(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteAppPointTaskByIds(Long[] ids)
|
||||
{
|
||||
if (ids == null || ids.length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (taskMapper.countSubmissionByTaskIds(ids) > 0)
|
||||
{
|
||||
throw new ServiceException("任务已有用户提交,请停用任务,不要删除");
|
||||
}
|
||||
return taskMapper.deleteAppPointTaskByIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public AppPointTaskAttachment createTempAttachment(AppPointTaskAttachment attachment)
|
||||
{
|
||||
Long userId = attachment.getUserId();
|
||||
if (userId == null || submissionMapper.lockUserForUpload(userId) == null)
|
||||
{
|
||||
throw new ServiceException("用户不存在,请重新登录");
|
||||
}
|
||||
if (submissionMapper.countTempAttachmentsByUser(userId) >= MAX_TEMP_ATTACHMENTS_PER_USER)
|
||||
{
|
||||
throw new ServiceException("未提交截图已达上限,请先完成任务提交或等待临时截图清理后再试");
|
||||
}
|
||||
String todayStart = LocalDate.now(SHANGHAI_ZONE) + " 00:00:00";
|
||||
if (submissionMapper.countAttachmentsByUserSince(userId, todayStart) >= MAX_DAILY_ATTACHMENTS_PER_USER)
|
||||
{
|
||||
throw new ServiceException("今日上传次数已达上限,请明日再试");
|
||||
}
|
||||
attachment.setUploadStatus("TEMP");
|
||||
attachment.setSubmitBatch(0);
|
||||
attachment.setIsCurrent(0);
|
||||
attachment.setCreateTime(DateUtils.getNowDate());
|
||||
if (submissionMapper.insertTempAttachment(attachment) != 1)
|
||||
{
|
||||
throw new ServiceException("创建上传记录失败,请重试");
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public AppPointTaskSubmission submitTask(Long taskId, AppPointTaskSubmission request, Long userId)
|
||||
{
|
||||
AppPointTask task = requireActiveTask(taskId);
|
||||
List<String> keys = normalizeAttachmentKeys(request.getAttachmentKeys());
|
||||
String cycleKey = cycleKey(task);
|
||||
if (submissionMapper.selectByTaskUserCycle(taskId, userId, cycleKey) != null)
|
||||
{
|
||||
throw new ServiceException(CYCLE_DAILY.equals(task.getCycleType())
|
||||
? "今日已经提交过该任务" : "该任务已经提交过");
|
||||
}
|
||||
assertTempAttachments(userId, keys);
|
||||
|
||||
Date now = DateUtils.getNowDate();
|
||||
AppPointTaskSubmission submission = new AppPointTaskSubmission();
|
||||
submission.setSubmissionNo("RW" + DateUtils.dateTimeNow() + IdUtils.fastSimpleUUID().substring(0, 6).toUpperCase());
|
||||
submission.setTaskId(taskId);
|
||||
submission.setUserId(userId);
|
||||
submission.setCycleKey(cycleKey);
|
||||
submission.setRewardPoints(task.getRewardPoints());
|
||||
submission.setRemark(normalizeRemark(request.getRemark()));
|
||||
submission.setStatus(STATUS_PENDING);
|
||||
submission.setAttemptCount(1);
|
||||
submission.setFirstSubmitTime(now);
|
||||
submission.setLastSubmitTime(now);
|
||||
submission.setCreateBy(String.valueOf(userId));
|
||||
submission.setCreateTime(now);
|
||||
submission.setUpdateBy(String.valueOf(userId));
|
||||
submission.setUpdateTime(now);
|
||||
try
|
||||
{
|
||||
if (submissionMapper.insertSubmission(submission) != 1)
|
||||
{
|
||||
throw new ServiceException("任务提交失败,请重试");
|
||||
}
|
||||
}
|
||||
catch (DuplicateKeyException ex)
|
||||
{
|
||||
throw new ServiceException("该任务本周期已经提交过");
|
||||
}
|
||||
bindAttachments(userId, submission.getId(), 1, keys);
|
||||
insertAudit(submission.getId(), null, STATUS_PENDING, String.valueOf(userId), "用户提交任务");
|
||||
return selectSubmissionDetail(submission.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public AppPointTaskSubmission resubmitTask(Long submissionId, AppPointTaskSubmission request, Long userId)
|
||||
{
|
||||
AppPointTaskSubmission current = submissionMapper.selectSubmissionByIdForUpdate(submissionId);
|
||||
if (current == null || !userId.equals(current.getUserId()))
|
||||
{
|
||||
throw new ServiceException("任务记录不存在");
|
||||
}
|
||||
if (current.getStatus() != STATUS_REJECTED)
|
||||
{
|
||||
throw new ServiceException("只有审核未通过的任务可以重新提交");
|
||||
}
|
||||
AppPointTask task = requireActiveTask(current.getTaskId());
|
||||
if (CYCLE_DAILY.equals(task.getCycleType()) && !cycleKey(task).equals(current.getCycleKey()))
|
||||
{
|
||||
throw new ServiceException("该每日任务周期已结束,请提交今日任务");
|
||||
}
|
||||
List<String> keys = normalizeAttachmentKeys(request.getAttachmentKeys());
|
||||
assertTempAttachments(userId, keys);
|
||||
int nextBatch = current.getAttemptCount() + 1;
|
||||
submissionMapper.markAttachmentsNotCurrent(submissionId);
|
||||
bindAttachments(userId, submissionId, nextBatch, keys);
|
||||
|
||||
current.setRemark(normalizeRemark(request.getRemark()));
|
||||
current.setStatus(STATUS_PENDING);
|
||||
current.setAttemptCount(nextBatch);
|
||||
current.setRejectReason(null);
|
||||
current.setReviewBy(null);
|
||||
current.setReviewTime(null);
|
||||
current.setLastSubmitTime(DateUtils.getNowDate());
|
||||
current.setUpdateBy(String.valueOf(userId));
|
||||
current.setUpdateTime(DateUtils.getNowDate());
|
||||
submissionMapper.updateSubmission(current);
|
||||
insertAudit(submissionId, STATUS_REJECTED, STATUS_PENDING, String.valueOf(userId), "用户重新提交任务");
|
||||
return selectSubmissionDetail(submissionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AppPointTaskSubmission> selectMySubmissionList(Long userId)
|
||||
{
|
||||
return submissionMapper.selectMySubmissionList(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppPointTaskSubmission selectMySubmissionDetail(Long id, Long userId)
|
||||
{
|
||||
AppPointTaskSubmission submission = selectSubmissionDetail(id);
|
||||
if (submission == null || !userId.equals(submission.getUserId()))
|
||||
{
|
||||
throw new ServiceException("任务记录不存在");
|
||||
}
|
||||
return submission;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AppPointTaskSubmission> selectSubmissionList(AppPointTaskSubmission submission)
|
||||
{
|
||||
return submissionMapper.selectSubmissionList(submission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppPointTaskSubmission selectSubmissionDetail(Long id)
|
||||
{
|
||||
AppPointTaskSubmission submission = submissionMapper.selectSubmissionById(id);
|
||||
if (submission != null)
|
||||
{
|
||||
submission.setAttachments(submissionMapper.selectAttachmentsBySubmissionId(id));
|
||||
}
|
||||
return submission;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int approveSubmission(Long id, String operator)
|
||||
{
|
||||
AppPointTaskSubmission submission = requirePendingSubmission(id);
|
||||
AppPointTask task = taskMapper.selectAppPointTaskById(submission.getTaskId());
|
||||
if (task == null)
|
||||
{
|
||||
throw new ServiceException("关联任务不存在");
|
||||
}
|
||||
if (userMapper.addIntegralByUserId(submission.getRewardPoints(), submission.getUserId()) != 1)
|
||||
{
|
||||
throw new ServiceException("积分发放失败,请重试");
|
||||
}
|
||||
AppIntegralRecord record = insertIntegralRecord(submission, 0L, task.getTaskName() + "奖励", "POINT_TASK");
|
||||
submission.setStatus(STATUS_APPROVED);
|
||||
submission.setRewardRecordId(record.getId());
|
||||
submission.setRejectReason(null);
|
||||
submission.setReviewBy(operator);
|
||||
submission.setReviewTime(DateUtils.getNowDate());
|
||||
submission.setUpdateBy(operator);
|
||||
submission.setUpdateTime(DateUtils.getNowDate());
|
||||
int rows = submissionMapper.updateSubmission(submission);
|
||||
insertAudit(id, STATUS_PENDING, STATUS_APPROVED, operator, "审核通过并发放" + submission.getRewardPoints() + "积分");
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int rejectSubmission(Long id, String rejectReason, String operator)
|
||||
{
|
||||
String reason = StringUtils.trim(rejectReason);
|
||||
if (StringUtils.isEmpty(reason))
|
||||
{
|
||||
throw new ServiceException("审核不通过时必须填写原因");
|
||||
}
|
||||
AppPointTaskSubmission submission = requirePendingSubmission(id);
|
||||
submission.setStatus(STATUS_REJECTED);
|
||||
submission.setRejectReason(reason);
|
||||
submission.setReviewBy(operator);
|
||||
submission.setReviewTime(DateUtils.getNowDate());
|
||||
submission.setUpdateBy(operator);
|
||||
submission.setUpdateTime(DateUtils.getNowDate());
|
||||
int rows = submissionMapper.updateSubmission(submission);
|
||||
insertAudit(id, STATUS_PENDING, STATUS_REJECTED, operator, reason);
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int revokeSubmission(Long id, String reason, String operator)
|
||||
{
|
||||
String revokeReason = StringUtils.trim(reason);
|
||||
if (StringUtils.isEmpty(revokeReason))
|
||||
{
|
||||
throw new ServiceException("撤销奖励时必须填写原因");
|
||||
}
|
||||
AppPointTaskSubmission submission = submissionMapper.selectSubmissionByIdForUpdate(id);
|
||||
if (submission == null || submission.getStatus() != STATUS_APPROVED)
|
||||
{
|
||||
throw new ServiceException("只有已通过的任务可以撤销奖励");
|
||||
}
|
||||
AppPointTask task = taskMapper.selectAppPointTaskById(submission.getTaskId());
|
||||
if (userMapper.delIntegralByUserId(submission.getRewardPoints(), submission.getUserId()) != 1)
|
||||
{
|
||||
throw new ServiceException("用户当前积分不足,无法撤销奖励");
|
||||
}
|
||||
insertIntegralRecord(submission, 1L, "撤销:" + (task == null ? "积分任务" : task.getTaskName()), "POINT_TASK_REVOKE");
|
||||
submission.setStatus(STATUS_REVOKED);
|
||||
submission.setRejectReason(revokeReason);
|
||||
submission.setReviewBy(operator);
|
||||
submission.setReviewTime(DateUtils.getNowDate());
|
||||
submission.setUpdateBy(operator);
|
||||
submission.setUpdateTime(DateUtils.getNowDate());
|
||||
int rows = submissionMapper.updateSubmission(submission);
|
||||
insertAudit(id, STATUS_APPROVED, STATUS_REVOKED, operator, revokeReason);
|
||||
return rows;
|
||||
}
|
||||
|
||||
private void populateUserState(AppPointTask task, Long userId)
|
||||
{
|
||||
AppPointTaskSubmission submission = submissionMapper.selectByTaskUserCycle(task.getId(), userId, cycleKey(task));
|
||||
if (submission == null)
|
||||
{
|
||||
task.setCanSubmit(true);
|
||||
return;
|
||||
}
|
||||
task.setCurrentStatus(submission.getStatus());
|
||||
task.setCurrentSubmissionId(submission.getId());
|
||||
task.setCanSubmit(submission.getStatus() == STATUS_REJECTED);
|
||||
if (CYCLE_DAILY.equals(task.getCycleType()) && submission.getStatus() == STATUS_APPROVED)
|
||||
{
|
||||
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("Asia/Shanghai"));
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 1);
|
||||
calendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||
calendar.set(Calendar.MINUTE, 0);
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
task.setNextAvailableAt(calendar.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
private AppPointTask requireActiveTask(Long id)
|
||||
{
|
||||
AppPointTask task = taskMapper.selectAppPointTaskById(id);
|
||||
Date now = DateUtils.getNowDate();
|
||||
if (task == null || !"0".equals(task.getStatus()))
|
||||
{
|
||||
throw new ServiceException("任务不存在或已停用");
|
||||
}
|
||||
if (task.getStartTime() != null && task.getStartTime().after(now))
|
||||
{
|
||||
throw new ServiceException("任务尚未开始");
|
||||
}
|
||||
if (task.getEndTime() != null && task.getEndTime().before(now))
|
||||
{
|
||||
throw new ServiceException("任务已经结束");
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
private boolean isTaskActive(AppPointTask task, Date now)
|
||||
{
|
||||
return task != null && "0".equals(task.getStatus())
|
||||
&& (task.getStartTime() == null || !task.getStartTime().after(now))
|
||||
&& (task.getEndTime() == null || !task.getEndTime().before(now));
|
||||
}
|
||||
|
||||
private AppPointTaskSubmission requirePendingSubmission(Long id)
|
||||
{
|
||||
AppPointTaskSubmission submission = submissionMapper.selectSubmissionByIdForUpdate(id);
|
||||
if (submission == null)
|
||||
{
|
||||
throw new ServiceException("任务记录不存在");
|
||||
}
|
||||
if (submission.getStatus() != STATUS_PENDING)
|
||||
{
|
||||
throw new ServiceException("该任务已经处理,请勿重复操作");
|
||||
}
|
||||
return submission;
|
||||
}
|
||||
|
||||
private String cycleKey(AppPointTask task)
|
||||
{
|
||||
return CYCLE_DAILY.equals(task.getCycleType()) ? LocalDate.now(SHANGHAI_ZONE).toString() : CYCLE_ONCE;
|
||||
}
|
||||
|
||||
private List<String> normalizeAttachmentKeys(List<String> keys)
|
||||
{
|
||||
if (keys == null)
|
||||
{
|
||||
throw new ServiceException("请上传任务截图");
|
||||
}
|
||||
Set<String> unique = new LinkedHashSet<>();
|
||||
for (String key : keys)
|
||||
{
|
||||
if (StringUtils.isNotEmpty(StringUtils.trim(key)))
|
||||
{
|
||||
unique.add(StringUtils.trim(key));
|
||||
}
|
||||
}
|
||||
if (unique.isEmpty() || unique.size() > 3)
|
||||
{
|
||||
throw new ServiceException("请上传1至3张任务截图");
|
||||
}
|
||||
return new ArrayList<>(unique);
|
||||
}
|
||||
|
||||
private void assertTempAttachments(Long userId, List<String> keys)
|
||||
{
|
||||
if (submissionMapper.selectTempAttachments(userId, keys).size() != keys.size())
|
||||
{
|
||||
throw new ServiceException("截图无效或已经被其他任务使用,请重新上传");
|
||||
}
|
||||
}
|
||||
|
||||
private void bindAttachments(Long userId, Long submissionId, Integer batch, List<String> keys)
|
||||
{
|
||||
if (submissionMapper.bindAttachments(userId, submissionId, batch, keys) != keys.size())
|
||||
{
|
||||
throw new ServiceException("任务截图绑定失败,请重新上传");
|
||||
}
|
||||
}
|
||||
|
||||
private void insertAudit(Long submissionId, Integer before, Integer after, String operator, String remark)
|
||||
{
|
||||
AppPointTaskAuditLog log = new AppPointTaskAuditLog();
|
||||
log.setSubmissionId(submissionId);
|
||||
log.setBeforeStatus(before);
|
||||
log.setAfterStatus(after);
|
||||
log.setOperatorName(operator);
|
||||
log.setRemark(remark);
|
||||
log.setCreateTime(DateUtils.getNowDate());
|
||||
submissionMapper.insertAuditLog(log);
|
||||
}
|
||||
|
||||
private AppIntegralRecord insertIntegralRecord(AppPointTaskSubmission submission, Long isAdd, String source, String bizType)
|
||||
{
|
||||
AppIntegralRecord record = new AppIntegralRecord();
|
||||
record.setSource(source);
|
||||
record.setIsAdd(isAdd);
|
||||
record.setIntegralNumber(submission.getRewardPoints().longValue());
|
||||
record.setUserId(submission.getUserId());
|
||||
record.setIntegralTime(DateUtils.getNowDate());
|
||||
record.setBizType(bizType);
|
||||
record.setBizId(submission.getId());
|
||||
if (integralRecordMapper.insertAppIntegralRecord(record) != 1)
|
||||
{
|
||||
throw new ServiceException("积分流水记录失败,请重试");
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
private void normalizeAndValidateTask(AppPointTask task)
|
||||
{
|
||||
task.setTaskName(StringUtils.trim(task.getTaskName()));
|
||||
task.setDescription(StringUtils.trim(task.getDescription()));
|
||||
task.setOperationGuide(StringUtils.trim(task.getOperationGuide()));
|
||||
if (StringUtils.isEmpty(task.getTaskName()) || task.getTaskName().length() > 80)
|
||||
{
|
||||
throw new ServiceException("任务名称需填写1至80个字");
|
||||
}
|
||||
if (!CYCLE_ONCE.equals(task.getCycleType()) && !CYCLE_DAILY.equals(task.getCycleType()))
|
||||
{
|
||||
throw new ServiceException("任务周期不正确");
|
||||
}
|
||||
if (task.getRewardPoints() == null || task.getRewardPoints() <= 0 || task.getRewardPoints() > 100000)
|
||||
{
|
||||
throw new ServiceException("奖励积分必须在1至100000之间");
|
||||
}
|
||||
if (task.getStartTime() != null && task.getEndTime() != null && !task.getStartTime().before(task.getEndTime()))
|
||||
{
|
||||
throw new ServiceException("结束时间必须晚于开始时间");
|
||||
}
|
||||
task.setAllowResubmit(1);
|
||||
if (task.getSortOrder() == null || task.getSortOrder() < 0)
|
||||
{
|
||||
task.setSortOrder(0);
|
||||
}
|
||||
if (!"1".equals(task.getStatus()))
|
||||
{
|
||||
task.setStatus("0");
|
||||
}
|
||||
if (StringUtils.isEmpty(task.getTaskType()))
|
||||
{
|
||||
task.setTaskType("OTHER");
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeRemark(String remark)
|
||||
{
|
||||
String value = StringUtils.trim(remark);
|
||||
if (value != null && value.length() > 300)
|
||||
{
|
||||
throw new ServiceException("补充说明不能超过300个字");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="integralNumber" column="integral_number" />
|
||||
<result property="userId" column="user_id" />
|
||||
<result property="resourceId" column="resource_id" />
|
||||
<result property="bizType" column="biz_type" />
|
||||
<result property="bizId" column="biz_id" />
|
||||
<result property="resourceTitle" column="resource_title" />
|
||||
<result property="integralTime" column="integral_time" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectAppIntegralRecordVo">
|
||||
select a.id, a.source, a.is_add, a.integral_number, a.user_id, a.resource_id, a.integral_time, r.resource_title
|
||||
select a.id, a.source, a.is_add, a.integral_number, a.user_id, a.resource_id,
|
||||
a.biz_type, a.biz_id, a.integral_time, r.resource_title
|
||||
from app_integral_record a
|
||||
left join app_resource r on a.resource_id = r.id
|
||||
</sql>
|
||||
@@ -68,6 +71,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="integralNumber != null">integral_number,</if>
|
||||
<if test="userId != null">user_id,</if>
|
||||
<if test="resourceId != null">resource_id,</if>
|
||||
<if test="bizType != null">biz_type,</if>
|
||||
<if test="bizId != null">biz_id,</if>
|
||||
<if test="integralTime != null">integral_time,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
@@ -76,6 +81,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="integralNumber != null">#{integralNumber},</if>
|
||||
<if test="userId != null">#{userId},</if>
|
||||
<if test="resourceId != null">#{resourceId},</if>
|
||||
<if test="bizType != null">#{bizType},</if>
|
||||
<if test="bizId != null">#{bizId},</if>
|
||||
<if test="integralTime != null">#{integralTime},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
@@ -88,6 +95,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="integralNumber != null">integral_number = #{integralNumber},</if>
|
||||
<if test="userId != null">user_id = #{userId},</if>
|
||||
<if test="resourceId != null">resource_id = #{resourceId},</if>
|
||||
<if test="bizType != null">biz_type = #{bizType},</if>
|
||||
<if test="bizId != null">biz_id = #{bizId},</if>
|
||||
<if test="integralTime != null">integral_time = #{integralTime},</if>
|
||||
</trim>
|
||||
where id = #{id}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.app.mapper.AppPointTaskMapper">
|
||||
<resultMap type="AppPointTask" id="AppPointTaskResult">
|
||||
<result property="id" column="id" />
|
||||
<result property="taskName" column="task_name" />
|
||||
<result property="taskType" column="task_type" />
|
||||
<result property="cycleType" column="cycle_type" />
|
||||
<result property="coverUrl" column="cover_url" />
|
||||
<result property="description" column="description" />
|
||||
<result property="operationGuide" column="operation_guide" />
|
||||
<result property="exampleImages" column="example_images" />
|
||||
<result property="rewardPoints" column="reward_points" />
|
||||
<result property="startTime" column="start_time" />
|
||||
<result property="endTime" column="end_time" />
|
||||
<result property="allowResubmit" column="allow_resubmit" />
|
||||
<result property="status" column="status" />
|
||||
<result property="sortOrder" column="sort_order" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectTaskVo">
|
||||
select id, task_name, task_type, cycle_type, cover_url, description, operation_guide,
|
||||
example_images, reward_points, start_time, end_time, allow_resubmit, status,
|
||||
sort_order, create_by, create_time, update_by, update_time
|
||||
from app_point_task
|
||||
</sql>
|
||||
|
||||
<select id="selectAppPointTaskById" resultMap="AppPointTaskResult">
|
||||
<include refid="selectTaskVo" /> where id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="selectAppPointTaskList" parameterType="AppPointTask" resultMap="AppPointTaskResult">
|
||||
<include refid="selectTaskVo" />
|
||||
<where>
|
||||
<if test="taskName != null and taskName != ''">and task_name like concat('%', #{taskName}, '%')</if>
|
||||
<if test="taskType != null and taskType != ''">and task_type = #{taskType}</if>
|
||||
<if test="cycleType != null and cycleType != ''">and cycle_type = #{cycleType}</if>
|
||||
<if test="status != null and status != ''">and status = #{status}</if>
|
||||
</where>
|
||||
order by sort_order asc, id desc
|
||||
</select>
|
||||
|
||||
<select id="selectActiveAppPointTaskList" resultMap="AppPointTaskResult">
|
||||
<include refid="selectTaskVo" />
|
||||
where status = '0'
|
||||
and (start_time is null or start_time <= now())
|
||||
and (end_time is null or end_time >= now())
|
||||
order by sort_order asc, id desc
|
||||
</select>
|
||||
|
||||
<insert id="insertAppPointTask" parameterType="AppPointTask" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into app_point_task
|
||||
(task_name, task_type, cycle_type, cover_url, description, operation_guide, example_images,
|
||||
reward_points, start_time, end_time, allow_resubmit, status, sort_order,
|
||||
create_by, create_time, update_by, update_time)
|
||||
values
|
||||
(#{taskName}, #{taskType}, #{cycleType}, #{coverUrl}, #{description}, #{operationGuide}, #{exampleImages},
|
||||
#{rewardPoints}, #{startTime}, #{endTime}, #{allowResubmit}, #{status}, #{sortOrder},
|
||||
#{createBy}, #{createTime}, #{updateBy}, #{updateTime})
|
||||
</insert>
|
||||
|
||||
<update id="updateAppPointTask" parameterType="AppPointTask">
|
||||
update app_point_task
|
||||
<set>
|
||||
<if test="taskName != null">task_name = #{taskName},</if>
|
||||
<if test="taskType != null">task_type = #{taskType},</if>
|
||||
<if test="cycleType != null">cycle_type = #{cycleType},</if>
|
||||
<if test="coverUrl != null">cover_url = #{coverUrl},</if>
|
||||
<if test="description != null">description = #{description},</if>
|
||||
<if test="operationGuide != null">operation_guide = #{operationGuide},</if>
|
||||
<if test="exampleImages != null">example_images = #{exampleImages},</if>
|
||||
<if test="rewardPoints != null">reward_points = #{rewardPoints},</if>
|
||||
start_time = #{startTime},
|
||||
end_time = #{endTime},
|
||||
<if test="allowResubmit != null">allow_resubmit = #{allowResubmit},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
<if test="sortOrder != null">sort_order = #{sortOrder},</if>
|
||||
<if test="updateBy != null">update_by = #{updateBy},</if>
|
||||
update_time = #{updateTime}
|
||||
</set>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="deleteAppPointTaskByIds">
|
||||
delete from app_point_task where id in
|
||||
<foreach item="id" collection="array" open="(" separator="," close=")">#{id}</foreach>
|
||||
</delete>
|
||||
|
||||
<select id="countSubmissionByTaskIds" resultType="int">
|
||||
select count(1) from app_point_task_submission where task_id in
|
||||
<foreach item="id" collection="array" open="(" separator="," close=")">#{id}</foreach>
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,201 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.app.mapper.AppPointTaskSubmissionMapper">
|
||||
<resultMap type="AppPointTaskSubmission" id="SubmissionResult">
|
||||
<result property="id" column="id" />
|
||||
<result property="submissionNo" column="submission_no" />
|
||||
<result property="taskId" column="task_id" />
|
||||
<result property="userId" column="user_id" />
|
||||
<result property="cycleKey" column="cycle_key" />
|
||||
<result property="rewardPoints" column="reward_points" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="status" column="status" />
|
||||
<result property="attemptCount" column="attempt_count" />
|
||||
<result property="rejectReason" column="reject_reason" />
|
||||
<result property="reviewBy" column="review_by" />
|
||||
<result property="reviewTime" column="review_time" />
|
||||
<result property="rewardRecordId" column="reward_record_id" />
|
||||
<result property="firstSubmitTime" column="first_submit_time" />
|
||||
<result property="lastSubmitTime" column="last_submit_time" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="taskName" column="task_name" />
|
||||
<result property="taskType" column="task_type" />
|
||||
<result property="cycleType" column="cycle_type" />
|
||||
<result property="userName" column="user_name" />
|
||||
<result property="nickName" column="nick_name" />
|
||||
</resultMap>
|
||||
|
||||
<resultMap type="AppPointTaskAttachment" id="AttachmentResult">
|
||||
<result property="id" column="id" />
|
||||
<result property="submissionId" column="submission_id" />
|
||||
<result property="userId" column="user_id" />
|
||||
<result property="objectKey" column="object_key" />
|
||||
<result property="originalName" column="original_name" />
|
||||
<result property="fileSize" column="file_size" />
|
||||
<result property="fileType" column="file_type" />
|
||||
<result property="uploadStatus" column="upload_status" />
|
||||
<result property="submitBatch" column="submit_batch" />
|
||||
<result property="isCurrent" column="is_current" />
|
||||
<result property="createTime" column="create_time" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectSubmissionVo">
|
||||
select s.id, s.submission_no, s.task_id, s.user_id, s.cycle_key, s.reward_points,
|
||||
s.remark, s.status, s.attempt_count, s.reject_reason, s.review_by, s.review_time,
|
||||
s.reward_record_id, s.first_submit_time, s.last_submit_time,
|
||||
s.create_by, s.create_time, s.update_by, s.update_time,
|
||||
t.task_name, t.task_type, t.cycle_type, u.user_name, u.nick_name
|
||||
from app_point_task_submission s
|
||||
left join app_point_task t on t.id = s.task_id
|
||||
left join sys_user u on u.user_id = s.user_id
|
||||
</sql>
|
||||
|
||||
<select id="selectSubmissionById" resultMap="SubmissionResult">
|
||||
<include refid="selectSubmissionVo" /> where s.id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="selectSubmissionByIdForUpdate" resultMap="SubmissionResult">
|
||||
select id, submission_no, task_id, user_id, cycle_key, reward_points, remark, status,
|
||||
attempt_count, reject_reason, review_by, review_time, reward_record_id,
|
||||
first_submit_time, last_submit_time, create_by, create_time, update_by, update_time
|
||||
from app_point_task_submission where id = #{id} for update
|
||||
</select>
|
||||
|
||||
<select id="selectByTaskUserCycle" resultMap="SubmissionResult">
|
||||
select id, submission_no, task_id, user_id, cycle_key, reward_points, remark, status,
|
||||
attempt_count, reject_reason, review_by, review_time, reward_record_id,
|
||||
first_submit_time, last_submit_time, create_by, create_time, update_by, update_time
|
||||
from app_point_task_submission
|
||||
where task_id = #{taskId} and user_id = #{userId} and cycle_key = #{cycleKey}
|
||||
</select>
|
||||
|
||||
<select id="selectSubmissionList" parameterType="AppPointTaskSubmission" resultMap="SubmissionResult">
|
||||
<include refid="selectSubmissionVo" />
|
||||
<where>
|
||||
<if test="submissionNo != null and submissionNo != ''">and s.submission_no like concat('%', #{submissionNo}, '%')</if>
|
||||
<if test="taskId != null">and s.task_id = #{taskId}</if>
|
||||
<if test="taskName != null and taskName != ''">and t.task_name like concat('%', #{taskName}, '%')</if>
|
||||
<if test="status != null">and s.status = #{status}</if>
|
||||
<if test="cycleKey != null and cycleKey != ''">and s.cycle_key = #{cycleKey}</if>
|
||||
<if test="userName != null and userName != ''">
|
||||
and (u.user_name like concat('%', #{userName}, '%') or u.nick_name like concat('%', #{userName}, '%'))
|
||||
</if>
|
||||
<if test="params.beginTime != null and params.beginTime != ''">
|
||||
and date_format(s.last_submit_time, '%Y%m%d') >= date_format(#{params.beginTime}, '%Y%m%d')
|
||||
</if>
|
||||
<if test="params.endTime != null and params.endTime != ''">
|
||||
and date_format(s.last_submit_time, '%Y%m%d') <= date_format(#{params.endTime}, '%Y%m%d')
|
||||
</if>
|
||||
</where>
|
||||
order by s.last_submit_time desc, s.id desc
|
||||
</select>
|
||||
|
||||
<select id="selectMySubmissionList" resultMap="SubmissionResult">
|
||||
<include refid="selectSubmissionVo" />
|
||||
where s.user_id = #{userId}
|
||||
order by s.last_submit_time desc, s.id desc
|
||||
</select>
|
||||
|
||||
<insert id="insertSubmission" parameterType="AppPointTaskSubmission" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into app_point_task_submission
|
||||
(submission_no, task_id, user_id, cycle_key, reward_points, remark, status, attempt_count,
|
||||
reject_reason, review_by, review_time, reward_record_id, first_submit_time, last_submit_time,
|
||||
create_by, create_time, update_by, update_time)
|
||||
values
|
||||
(#{submissionNo}, #{taskId}, #{userId}, #{cycleKey}, #{rewardPoints}, #{remark}, #{status}, #{attemptCount},
|
||||
#{rejectReason}, #{reviewBy}, #{reviewTime}, #{rewardRecordId}, #{firstSubmitTime}, #{lastSubmitTime},
|
||||
#{createBy}, #{createTime}, #{updateBy}, #{updateTime})
|
||||
</insert>
|
||||
|
||||
<update id="updateSubmission" parameterType="AppPointTaskSubmission">
|
||||
update app_point_task_submission
|
||||
<set>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
<if test="attemptCount != null">attempt_count = #{attemptCount},</if>
|
||||
reject_reason = #{rejectReason},
|
||||
review_by = #{reviewBy},
|
||||
review_time = #{reviewTime},
|
||||
<if test="rewardRecordId != null">reward_record_id = #{rewardRecordId},</if>
|
||||
<if test="lastSubmitTime != null">last_submit_time = #{lastSubmitTime},</if>
|
||||
<if test="updateBy != null">update_by = #{updateBy},</if>
|
||||
update_time = #{updateTime}
|
||||
</set>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<insert id="insertTempAttachment" parameterType="AppPointTaskAttachment" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into app_point_task_attachment
|
||||
(submission_id, user_id, object_key, original_name, file_size, file_type,
|
||||
upload_status, submit_batch, is_current, create_time)
|
||||
values
|
||||
(null, #{userId}, #{objectKey}, #{originalName}, #{fileSize}, #{fileType},
|
||||
'TEMP', 0, 0, #{createTime})
|
||||
</insert>
|
||||
|
||||
<select id="lockUserForUpload" resultType="long">
|
||||
select user_id from sys_user where user_id = #{userId} for update
|
||||
</select>
|
||||
|
||||
<select id="countTempAttachmentsByUser" resultType="int">
|
||||
select count(1)
|
||||
from app_point_task_attachment
|
||||
where user_id = #{userId} and upload_status = 'TEMP'
|
||||
</select>
|
||||
|
||||
<select id="countAttachmentsByUserSince" resultType="int">
|
||||
select count(1)
|
||||
from app_point_task_attachment
|
||||
where user_id = #{userId} and create_time >= #{since}
|
||||
</select>
|
||||
|
||||
<select id="selectTempAttachments" resultMap="AttachmentResult">
|
||||
select id, submission_id, user_id, object_key, original_name, file_size, file_type,
|
||||
upload_status, submit_batch, is_current, create_time
|
||||
from app_point_task_attachment
|
||||
where user_id = #{userId} and upload_status = 'TEMP' and object_key in
|
||||
<foreach item="key" collection="keys" open="(" separator="," close=")">#{key}</foreach>
|
||||
</select>
|
||||
|
||||
<update id="bindAttachments">
|
||||
update app_point_task_attachment
|
||||
set submission_id = #{submissionId}, upload_status = 'BOUND', submit_batch = #{submitBatch}, is_current = 1
|
||||
where user_id = #{userId} and upload_status = 'TEMP' and object_key in
|
||||
<foreach item="key" collection="keys" open="(" separator="," close=")">#{key}</foreach>
|
||||
</update>
|
||||
|
||||
<update id="markAttachmentsNotCurrent">
|
||||
update app_point_task_attachment set is_current = 0 where submission_id = #{submissionId}
|
||||
</update>
|
||||
|
||||
<select id="selectAttachmentsBySubmissionId" resultMap="AttachmentResult">
|
||||
select id, submission_id, user_id, object_key, original_name, file_size, file_type,
|
||||
upload_status, submit_batch, is_current, create_time
|
||||
from app_point_task_attachment
|
||||
where submission_id = #{submissionId}
|
||||
order by submit_batch desc, id asc
|
||||
</select>
|
||||
|
||||
<select id="selectExpiredTempAttachments" resultMap="AttachmentResult">
|
||||
select id, submission_id, user_id, object_key, original_name, file_size, file_type,
|
||||
upload_status, submit_batch, is_current, create_time
|
||||
from app_point_task_attachment
|
||||
where upload_status = 'TEMP' and create_time < #{beforeTime}
|
||||
order by id asc
|
||||
limit 500
|
||||
</select>
|
||||
|
||||
<delete id="deleteTempAttachmentById">
|
||||
delete from app_point_task_attachment where id = #{id} and upload_status = 'TEMP'
|
||||
</delete>
|
||||
|
||||
<insert id="insertAuditLog" parameterType="AppPointTaskAuditLog">
|
||||
insert into app_point_task_audit_log
|
||||
(submission_id, before_status, after_status, operator_name, remark, create_time)
|
||||
values
|
||||
(#{submissionId}, #{beforeStatus}, #{afterStatus}, #{operatorName}, #{remark}, #{createTime})
|
||||
</insert>
|
||||
</mapper>
|
||||
41
ruoyi-ui/src/api/app/pointTask.js
Normal file
41
ruoyi-ui/src/api/app/pointTask.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listPointTask(query) {
|
||||
return request({ url: '/app/pointTask/list', method: 'get', params: query })
|
||||
}
|
||||
|
||||
export function getPointTask(id) {
|
||||
return request({ url: '/app/pointTask/' + id, method: 'get' })
|
||||
}
|
||||
|
||||
export function addPointTask(data) {
|
||||
return request({ url: '/app/pointTask', method: 'post', data })
|
||||
}
|
||||
|
||||
export function updatePointTask(data) {
|
||||
return request({ url: '/app/pointTask', method: 'put', data })
|
||||
}
|
||||
|
||||
export function delPointTask(ids) {
|
||||
return request({ url: '/app/pointTask/' + ids, method: 'delete' })
|
||||
}
|
||||
|
||||
export function listPointTaskSubmission(query) {
|
||||
return request({ url: '/app/pointTask/submission/list', method: 'get', params: query })
|
||||
}
|
||||
|
||||
export function getPointTaskSubmission(id) {
|
||||
return request({ url: '/app/pointTask/submission/' + id, method: 'get' })
|
||||
}
|
||||
|
||||
export function approvePointTaskSubmission(id) {
|
||||
return request({ url: '/app/pointTask/submission/' + id + '/approve', method: 'post' })
|
||||
}
|
||||
|
||||
export function rejectPointTaskSubmission(id, rejectReason) {
|
||||
return request({ url: '/app/pointTask/submission/' + id + '/reject', method: 'post', data: { rejectReason } })
|
||||
}
|
||||
|
||||
export function revokePointTaskSubmission(id, rejectReason) {
|
||||
return request({ url: '/app/pointTask/submission/' + id + '/revoke', method: 'post', data: { rejectReason } })
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="component-upload-image">
|
||||
<el-upload
|
||||
drag
|
||||
:drag="!compact"
|
||||
multiple
|
||||
:action="uploadImgUrl"
|
||||
:on-success="handleUploadSuccess"
|
||||
@@ -14,13 +14,18 @@
|
||||
:show-file-list="true"
|
||||
:headers="headers"
|
||||
:file-list="fileList"
|
||||
list-type="picture"
|
||||
:list-type="compact ? 'picture-card' : 'picture'"
|
||||
:on-preview="handlePictureCardPreview"
|
||||
:class="{hide: this.fileList.length >= this.limit}"
|
||||
:class="{ hide: this.fileList.length >= this.limit, 'is-compact': compact }"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template v-if="compact">
|
||||
<i class="el-icon-plus"></i>
|
||||
</template>
|
||||
<template v-else>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
</template>
|
||||
</el-upload>
|
||||
|
||||
<!-- 上传提示 -->
|
||||
@@ -74,6 +79,11 @@ export default {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 紧凑图片卡片模式,适合弹窗或栅格表单
|
||||
compact: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -99,7 +109,6 @@ export default {
|
||||
const list = Array.isArray(val) ? val : this.value.split(',');
|
||||
// 然后将数组转为对象数组
|
||||
this.fileList = list.map(item => {
|
||||
console.log(item)
|
||||
if (typeof item === "string") {
|
||||
item = { name: item, url: item };
|
||||
}
|
||||
@@ -179,6 +188,7 @@ export default {
|
||||
},
|
||||
// 上传失败
|
||||
handleUploadError() {
|
||||
if (this.number > 0) this.number--;
|
||||
this.$modal.msgError("上传图片失败,请重试");
|
||||
this.$modal.closeLoading();
|
||||
},
|
||||
@@ -216,6 +226,9 @@ export default {
|
||||
::v-deep.hide .el-upload--picture-card {
|
||||
display: none;
|
||||
}
|
||||
::v-deep.hide .el-upload-dragger {
|
||||
display: none;
|
||||
}
|
||||
// 去掉动画效果
|
||||
::v-deep .el-list-enter-active,
|
||||
::v-deep .el-list-leave-active {
|
||||
|
||||
93
ruoyi-ui/src/views/app/pointTask/index.vue
Normal file
93
ruoyi-ui/src/views/app/pointTask/index.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true">
|
||||
<el-form-item label="任务名称" prop="taskName">
|
||||
<el-input v-model="queryParams.taskName" placeholder="请输入任务名称" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="任务周期" prop="cycleType">
|
||||
<el-select v-model="queryParams.cycleType" placeholder="全部" clearable>
|
||||
<el-option label="单次任务" value="ONCE" />
|
||||
<el-option label="每日任务" value="DAILY" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="全部" clearable>
|
||||
<el-option label="启用" value="0" />
|
||||
<el-option label="停用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5"><el-button v-hasPermi="['app:pointTask:add']" type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增任务</el-button></el-col>
|
||||
<right-toolbar :show-search.sync="showSearch" @queryTable="getList" />
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="taskList">
|
||||
<el-table-column label="任务名称" prop="taskName" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="周期" align="center" width="100">
|
||||
<template slot-scope="scope"><el-tag size="small" :type="scope.row.cycleType === 'DAILY' ? 'warning' : 'success'">{{ scope.row.cycleType === 'DAILY' ? '每日任务' : '单次任务' }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="奖励积分" prop="rewardPoints" align="center" width="100" />
|
||||
<el-table-column label="活动时间" min-width="285"><template slot-scope="scope">{{ scope.row.startTime || '立即开始' }} 至 {{ scope.row.endTime || '长期有效' }}</template></el-table-column>
|
||||
<el-table-column label="状态" align="center" width="90"><template slot-scope="scope"><el-tag size="small" :type="scope.row.status === '0' ? 'success' : 'info'">{{ scope.row.status === '0' ? '启用' : '停用' }}</el-tag></template></el-table-column>
|
||||
<el-table-column label="排序" prop="sortOrder" align="center" width="75" />
|
||||
<el-table-column label="操作" align="center" width="150" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-hasPermi="['app:pointTask:edit']" type="text" icon="el-icon-edit" size="mini" @click="handleUpdate(scope.row)">修改</el-button>
|
||||
<el-button v-hasPermi="['app:pointTask:remove']" type="text" icon="el-icon-delete" size="mini" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<el-dialog :title="title" :visible.sync="open" width="760px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="110px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="16"><el-form-item label="任务名称" prop="taskName"><el-input v-model="form.taskName" maxlength="80" show-word-limit /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="奖励积分" prop="rewardPoints"><el-input-number v-model="form.rewardPoints" :min="1" :max="100000" style="width:100%" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="任务周期" prop="cycleType"><el-select v-model="form.cycleType" style="width:100%"><el-option label="单次任务" value="ONCE" /><el-option label="每日任务" value="DAILY" /></el-select></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="状态"><el-radio-group v-model="form.status"><el-radio label="0">启用</el-radio><el-radio label="1">停用</el-radio></el-radio-group></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="开始时间"><el-date-picker v-model="form.startTime" value-format="yyyy-MM-dd HH:mm:ss" type="datetime" placeholder="留空立即开始" style="width:100%" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="结束时间"><el-date-picker v-model="form.endTime" value-format="yyyy-MM-dd HH:mm:ss" type="datetime" placeholder="留空长期有效" style="width:100%" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="任务封面"><qiniu-image-upload v-model="form.coverUrl" compact :limit="1" :file-size="5" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="示例截图"><qiniu-image-upload v-model="form.exampleImages" compact :limit="3" :file-size="5" /></el-form-item></el-col>
|
||||
<el-col :span="24"><el-form-item label="任务说明"><el-input v-model="form.description" type="textarea" :rows="3" maxlength="1000" show-word-limit /></el-form-item></el-col>
|
||||
<el-col :span="24"><el-form-item label="操作步骤"><el-input v-model="form.operationGuide" type="textarea" :rows="5" :placeholder="operationGuidePlaceholder" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="排序"><el-input-number v-model="form.sortOrder" :min="0" style="width:100%" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div slot="footer"><el-button @click="open=false">取消</el-button><el-button type="primary" :loading="submitting" @click="submitForm">确定</el-button></div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listPointTask, getPointTask, addPointTask, updatePointTask, delPointTask } from '@/api/app/pointTask'
|
||||
|
||||
export default {
|
||||
name: 'PointTask',
|
||||
data() {
|
||||
return {
|
||||
loading: false, submitting: false, showSearch: true, total: 0, taskList: [], open: false, title: '', operationGuidePlaceholder: '例如:\n1. 扫描二维码进群\n2. 截图群聊页面\n3. 上传截图并等待审核', queryParams: { pageNum: 1, pageSize: 10, taskName: undefined, cycleType: undefined, status: undefined }, form: {},
|
||||
rules: { taskName: [{ required: true, message: '请输入任务名称', trigger: 'blur' }], cycleType: [{ required: true, message: '请选择任务周期', trigger: 'change' }], rewardPoints: [{ required: true, message: '请输入奖励积分', trigger: 'blur' }] }
|
||||
}
|
||||
},
|
||||
created() { this.getList() },
|
||||
methods: {
|
||||
getList() { this.loading = true; listPointTask(this.queryParams).then(res => { this.taskList = res.rows || []; this.total = res.total || 0 }).finally(() => { this.loading = false }) },
|
||||
reset() { this.form = { id: undefined, taskName: '', cycleType: 'ONCE', coverUrl: '', description: '', operationGuide: '', exampleImages: '', rewardPoints: 10, startTime: null, endTime: null, status: '0', sortOrder: 0 } },
|
||||
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
|
||||
resetQuery() { this.resetForm('queryForm'); this.handleQuery() },
|
||||
handleAdd() { this.reset(); this.title = '新增积分任务'; this.open = true },
|
||||
handleUpdate(row) { this.reset(); getPointTask(row.id).then(res => { this.form = res.data; this.title = '修改积分任务'; this.open = true }) },
|
||||
handleDelete(row) { this.$modal.confirm('确认删除任务“' + row.taskName + '”吗?').then(() => delPointTask(row.id)).then(() => { this.$modal.msgSuccess('删除成功'); this.getList() }).catch(() => {}) },
|
||||
submitForm() { this.$refs.form.validate(valid => { if (!valid) return; this.submitting = true; const action = this.form.id ? updatePointTask : addPointTask; action(this.form).then(() => { this.$modal.msgSuccess(this.form.id ? '修改成功' : '新增成功'); this.open = false; this.getList() }).finally(() => { this.submitting = false }) }) }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
97
ruoyi-ui/src/views/app/pointTaskReview/index.vue
Normal file
97
ruoyi-ui/src/views/app/pointTaskReview/index.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true">
|
||||
<el-form-item label="任务名称" prop="taskName"><el-input v-model="queryParams.taskName" clearable placeholder="任务名称" @keyup.enter.native="handleQuery" /></el-form-item>
|
||||
<el-form-item label="用户" prop="userName"><el-input v-model="queryParams.userName" clearable placeholder="账号或昵称" @keyup.enter.native="handleQuery" /></el-form-item>
|
||||
<el-form-item label="状态" prop="status"><el-select v-model="queryParams.status" clearable placeholder="全部"><el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item>
|
||||
<el-form-item label="周期" prop="cycleKey"><el-input v-model="queryParams.cycleKey" clearable placeholder="ONCE或2026-09-16" /></el-form-item>
|
||||
<el-form-item><el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button><el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button></el-form-item>
|
||||
</el-form>
|
||||
<right-toolbar :show-search.sync="showSearch" @queryTable="getList" />
|
||||
|
||||
<el-table v-loading="loading" :data="submissionList">
|
||||
<el-table-column label="提交单号" prop="submissionNo" min-width="185" />
|
||||
<el-table-column label="任务" prop="taskName" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="用户" min-width="130"><template slot-scope="scope"><div>{{ scope.row.nickName || scope.row.userName }}</div><small class="muted">ID: {{ scope.row.userId }}</small></template></el-table-column>
|
||||
<el-table-column label="周期" prop="cycleKey" align="center" width="110" />
|
||||
<el-table-column label="积分" prop="rewardPoints" align="center" width="75" />
|
||||
<el-table-column label="提交次数" prop="attemptCount" align="center" width="85" />
|
||||
<el-table-column label="状态" align="center" width="100"><template slot-scope="scope"><el-tag :type="statusType(scope.row.status)" size="small">{{ statusLabel(scope.row.status) }}</el-tag></template></el-table-column>
|
||||
<el-table-column label="提交时间" prop="lastSubmitTime" width="165" />
|
||||
<el-table-column label="操作" align="center" width="225" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-hasPermi="['app:pointTaskReview:query']" type="text" icon="el-icon-view" size="mini" @click="handleView(scope.row)">详情</el-button>
|
||||
<el-button v-if="scope.row.status === 0" v-hasPermi="['app:pointTaskReview:edit']" type="text" icon="el-icon-check" size="mini" @click="handleApprove(scope.row)">通过</el-button>
|
||||
<el-button v-if="scope.row.status === 0" v-hasPermi="['app:pointTaskReview:edit']" type="text" icon="el-icon-close" size="mini" @click="openReason(scope.row, 'reject')">驳回</el-button>
|
||||
<el-button v-if="scope.row.status === 1" v-hasPermi="['app:pointTaskReview:revoke']" type="text" icon="el-icon-refresh-left" size="mini" @click="openReason(scope.row, 'revoke')">撤销</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<el-dialog title="任务提交详情" :visible.sync="detailOpen" width="820px" append-to-body>
|
||||
<el-descriptions v-if="detail.id" :column="2" border>
|
||||
<el-descriptions-item label="提交单号" :span="2">{{ detail.submissionNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务名称">{{ detail.taskName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="奖励积分">{{ detail.rewardPoints }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用户">{{ detail.nickName || detail.userName }}({{ detail.userId }})</el-descriptions-item>
|
||||
<el-descriptions-item label="任务周期">{{ detail.cycleKey }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态"><el-tag :type="statusType(detail.status)" size="small">{{ statusLabel(detail.status) }}</el-tag></el-descriptions-item>
|
||||
<el-descriptions-item label="提交次数">{{ detail.attemptCount }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用户说明" :span="2"><div class="pre-wrap">{{ detail.remark || '-' }}</div></el-descriptions-item>
|
||||
<el-descriptions-item v-if="detail.rejectReason" label="驳回/撤销原因" :span="2">{{ detail.rejectReason }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<h4>任务截图</h4>
|
||||
<div class="image-list">
|
||||
<div v-for="item in detail.attachments || []" :key="item.id" class="image-card" :class="{ history: item.isCurrent !== 1 }">
|
||||
<el-image :src="item.previewUrl" :preview-src-list="previewUrls" fit="cover" />
|
||||
<div>{{ item.isCurrent === 1 ? '当前截图' : '历史第' + item.submitBatch + '次' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="footer"><el-button @click="detailOpen=false">关闭</el-button></div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog :title="reasonMode === 'reject' ? '驳回任务' : '撤销积分奖励'" :visible.sync="reasonOpen" width="520px" append-to-body>
|
||||
<el-input v-model="reason" type="textarea" :rows="4" maxlength="300" show-word-limit :placeholder="reasonMode === 'reject' ? '请填写审核未通过原因' : '请填写撤销原因'" />
|
||||
<el-alert v-if="reasonMode === 'revoke'" title="撤销将扣回已发放积分;用户积分不足时操作会失败。" type="warning" :closable="false" show-icon style="margin-top:15px" />
|
||||
<div slot="footer"><el-button @click="reasonOpen=false">取消</el-button><el-button type="primary" :loading="submitting" @click="submitReason">确定</el-button></div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listPointTaskSubmission, getPointTaskSubmission, approvePointTaskSubmission, rejectPointTaskSubmission, revokePointTaskSubmission } from '@/api/app/pointTask'
|
||||
|
||||
export default {
|
||||
name: 'PointTaskReview',
|
||||
data() {
|
||||
return {
|
||||
loading: false, submitting: false, showSearch: true, total: 0, submissionList: [], detailOpen: false, detail: {}, reasonOpen: false, reason: '', reasonMode: 'reject', currentRow: {},
|
||||
statusOptions: [{ value: 0, label: '待审核' }, { value: 1, label: '已通过' }, { value: 2, label: '已驳回' }, { value: 3, label: '已撤销' }],
|
||||
queryParams: { pageNum: 1, pageSize: 10, taskName: undefined, userName: undefined, status: undefined, cycleKey: undefined }
|
||||
}
|
||||
},
|
||||
computed: { previewUrls() { return (this.detail.attachments || []).map(item => item.previewUrl).filter(Boolean) } },
|
||||
created() { this.getList() },
|
||||
methods: {
|
||||
getList() { this.loading = true; listPointTaskSubmission(this.queryParams).then(res => { this.submissionList = res.rows || []; this.total = res.total || 0 }).finally(() => { this.loading = false }) },
|
||||
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
|
||||
resetQuery() { this.resetForm('queryForm'); this.handleQuery() },
|
||||
handleView(row) { getPointTaskSubmission(row.id).then(res => { this.detail = res.data; this.detailOpen = true }) },
|
||||
handleApprove(row) { this.$modal.confirm('确认通过该任务并发放 ' + row.rewardPoints + ' 积分吗?').then(() => approvePointTaskSubmission(row.id)).then(() => { this.$modal.msgSuccess('审核通过,积分已发放'); this.getList() }).catch(() => {}) },
|
||||
openReason(row, mode) { this.currentRow = row; this.reasonMode = mode; this.reason = ''; this.reasonOpen = true },
|
||||
submitReason() { const reason = String(this.reason || '').trim(); if (!reason) { this.$modal.msgError('请填写原因'); return } this.submitting = true; const action = this.reasonMode === 'reject' ? rejectPointTaskSubmission : revokePointTaskSubmission; action(this.currentRow.id, reason).then(() => { this.$modal.msgSuccess(this.reasonMode === 'reject' ? '已驳回' : '奖励已撤销'); this.reasonOpen = false; this.getList() }).finally(() => { this.submitting = false }) },
|
||||
statusLabel(status) { const item = this.statusOptions.find(v => v.value === status); return item ? item.label : '未知' },
|
||||
statusType(status) { return { 0: 'warning', 1: 'success', 2: 'danger', 3: 'info' }[status] || 'info' }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.muted { color: #909399; }
|
||||
.pre-wrap { white-space: pre-wrap; }
|
||||
.image-list { display: flex; flex-wrap: wrap; gap: 14px; }
|
||||
.image-card { width: 150px; text-align: center; color: #606266; }
|
||||
.image-card .el-image { width: 150px; height: 150px; border-radius: 6px; }
|
||||
.image-card.history { opacity: .58; }
|
||||
</style>
|
||||
216
sql/point_task.sql
Normal file
216
sql/point_task.sql
Normal file
@@ -0,0 +1,216 @@
|
||||
-- 积分任务中心:一次性/每日任务、七牛截图、人工审核和积分发放。
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `app_point_task` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '任务ID',
|
||||
`task_name` varchar(80) NOT NULL COMMENT '任务名称',
|
||||
`task_type` varchar(32) NOT NULL DEFAULT 'OTHER' COMMENT 'GROUP_JOIN/ARTICLE_SHARE/OTHER',
|
||||
`cycle_type` varchar(16) NOT NULL COMMENT 'ONCE一次性 DAILY每日一次',
|
||||
`cover_url` varchar(500) DEFAULT NULL COMMENT '任务封面',
|
||||
`description` varchar(1000) DEFAULT NULL COMMENT '任务说明',
|
||||
`operation_guide` text COMMENT '操作步骤',
|
||||
`example_images` varchar(2000) DEFAULT NULL COMMENT '示例截图,逗号分隔',
|
||||
`reward_points` int NOT NULL COMMENT '奖励积分',
|
||||
`start_time` datetime DEFAULT NULL COMMENT '开始时间',
|
||||
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
|
||||
`allow_resubmit` tinyint NOT NULL DEFAULT 1 COMMENT '驳回后允许重提 0否 1是',
|
||||
`status` char(1) NOT NULL DEFAULT '0' COMMENT '0启用 1停用',
|
||||
`sort_order` int NOT NULL DEFAULT 0 COMMENT '排序',
|
||||
`create_by` varchar(64) DEFAULT '',
|
||||
`create_time` datetime NOT NULL,
|
||||
`update_by` varchar(64) DEFAULT '',
|
||||
`update_time` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_point_task_status_time` (`status`, `start_time`, `end_time`, `sort_order`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分任务表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `app_point_task_submission` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '提交ID',
|
||||
`submission_no` varchar(40) NOT NULL COMMENT '提交单号',
|
||||
`task_id` bigint NOT NULL COMMENT '任务ID',
|
||||
`user_id` bigint NOT NULL COMMENT '用户ID',
|
||||
`cycle_key` varchar(16) NOT NULL COMMENT 'ONCE或yyyy-MM-dd',
|
||||
`reward_points` int NOT NULL COMMENT '提交时奖励积分快照',
|
||||
`remark` varchar(300) DEFAULT NULL COMMENT '用户说明',
|
||||
`status` tinyint NOT NULL DEFAULT 0 COMMENT '0待审核 1已通过 2已驳回 3已撤销',
|
||||
`attempt_count` int NOT NULL DEFAULT 1 COMMENT '提交次数',
|
||||
`reject_reason` varchar(300) DEFAULT NULL COMMENT '驳回或撤销原因',
|
||||
`review_by` varchar(64) DEFAULT NULL COMMENT '审核人',
|
||||
`review_time` datetime DEFAULT NULL COMMENT '审核时间',
|
||||
`reward_record_id` bigint DEFAULT NULL COMMENT '奖励积分流水ID',
|
||||
`first_submit_time` datetime NOT NULL COMMENT '首次提交时间',
|
||||
`last_submit_time` datetime NOT NULL COMMENT '最近提交时间',
|
||||
`create_by` varchar(64) DEFAULT '',
|
||||
`create_time` datetime NOT NULL,
|
||||
`update_by` varchar(64) DEFAULT '',
|
||||
`update_time` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_point_task_submission_no` (`submission_no`),
|
||||
UNIQUE KEY `uk_point_task_user_cycle` (`task_id`, `user_id`, `cycle_key`),
|
||||
KEY `idx_point_task_submission_status` (`status`, `last_submit_time`),
|
||||
KEY `idx_point_task_submission_user` (`user_id`, `last_submit_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分任务提交表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `app_point_task_attachment` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '附件ID',
|
||||
`submission_id` bigint DEFAULT NULL COMMENT '提交ID,上传阶段为空',
|
||||
`user_id` bigint NOT NULL COMMENT '用户ID',
|
||||
`object_key` varchar(500) NOT NULL COMMENT '七牛对象Key',
|
||||
`original_name` varchar(255) DEFAULT NULL COMMENT '原文件名',
|
||||
`file_size` bigint DEFAULT NULL COMMENT '文件大小',
|
||||
`file_type` varchar(64) DEFAULT NULL COMMENT 'MIME类型',
|
||||
`upload_status` varchar(16) NOT NULL DEFAULT 'TEMP' COMMENT 'TEMP/BOUND',
|
||||
`submit_batch` int NOT NULL DEFAULT 0 COMMENT '第几次提交',
|
||||
`is_current` tinyint NOT NULL DEFAULT 0 COMMENT '是否当前审核附件',
|
||||
`create_time` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_point_task_object_key` (`object_key`),
|
||||
KEY `idx_point_task_attachment_submission` (`submission_id`, `submit_batch`, `is_current`),
|
||||
KEY `idx_point_task_attachment_temp` (`upload_status`, `create_time`),
|
||||
KEY `idx_point_task_attachment_user_quota` (`user_id`, `upload_status`, `create_time`),
|
||||
KEY `idx_point_task_attachment_user_daily` (`user_id`, `create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分任务截图附件表';
|
||||
|
||||
-- 已有积分任务表时补充用户上传配额查询索引。
|
||||
SET @attachment_quota_index_exists := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'app_point_task_attachment'
|
||||
AND INDEX_NAME = 'idx_point_task_attachment_user_quota'
|
||||
);
|
||||
SET @attachment_quota_index_sql := IF(@attachment_quota_index_exists = 0,
|
||||
'ALTER TABLE `app_point_task_attachment` ADD KEY `idx_point_task_attachment_user_quota` (`user_id`, `upload_status`, `create_time`)',
|
||||
'SELECT 1');
|
||||
PREPARE attachment_quota_index_stmt FROM @attachment_quota_index_sql;
|
||||
EXECUTE attachment_quota_index_stmt;
|
||||
DEALLOCATE PREPARE attachment_quota_index_stmt;
|
||||
|
||||
SET @attachment_daily_index_exists := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'app_point_task_attachment'
|
||||
AND INDEX_NAME = 'idx_point_task_attachment_user_daily'
|
||||
);
|
||||
SET @attachment_daily_index_sql := IF(@attachment_daily_index_exists = 0,
|
||||
'ALTER TABLE `app_point_task_attachment` ADD KEY `idx_point_task_attachment_user_daily` (`user_id`, `create_time`)',
|
||||
'SELECT 1');
|
||||
PREPARE attachment_daily_index_stmt FROM @attachment_daily_index_sql;
|
||||
EXECUTE attachment_daily_index_stmt;
|
||||
DEALLOCATE PREPARE attachment_daily_index_stmt;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `app_point_task_audit_log` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`submission_id` bigint NOT NULL,
|
||||
`before_status` tinyint DEFAULT NULL,
|
||||
`after_status` tinyint NOT NULL,
|
||||
`operator_name` varchar(64) NOT NULL,
|
||||
`remark` varchar(500) DEFAULT NULL,
|
||||
`create_time` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_point_task_audit_submission` (`submission_id`, `create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分任务审核日志表';
|
||||
|
||||
-- 积分流水业务幂等字段。
|
||||
SET @biz_type_exists := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'app_integral_record' AND COLUMN_NAME = 'biz_type'
|
||||
);
|
||||
SET @biz_type_sql := IF(@biz_type_exists = 0,
|
||||
'ALTER TABLE `app_integral_record` ADD COLUMN `biz_type` varchar(32) DEFAULT NULL COMMENT ''业务类型'' AFTER `resource_id`',
|
||||
'SELECT 1');
|
||||
PREPARE biz_type_stmt FROM @biz_type_sql;
|
||||
EXECUTE biz_type_stmt;
|
||||
DEALLOCATE PREPARE biz_type_stmt;
|
||||
|
||||
SET @biz_id_exists := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'app_integral_record' AND COLUMN_NAME = 'biz_id'
|
||||
);
|
||||
SET @biz_id_sql := IF(@biz_id_exists = 0,
|
||||
'ALTER TABLE `app_integral_record` ADD COLUMN `biz_id` bigint DEFAULT NULL COMMENT ''业务记录ID'' AFTER `biz_type`',
|
||||
'SELECT 1');
|
||||
PREPARE biz_id_stmt FROM @biz_id_sql;
|
||||
EXECUTE biz_id_stmt;
|
||||
DEALLOCATE PREPARE biz_id_stmt;
|
||||
|
||||
SET @biz_index_exists := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'app_integral_record' AND INDEX_NAME = 'uk_integral_biz'
|
||||
);
|
||||
SET @biz_index_sql := IF(@biz_index_exists = 0,
|
||||
'ALTER TABLE `app_integral_record` ADD UNIQUE KEY `uk_integral_biz` (`biz_type`, `biz_id`)',
|
||||
'SELECT 1');
|
||||
PREPARE biz_index_stmt FROM @biz_index_sql;
|
||||
EXECUTE biz_index_stmt;
|
||||
DEALLOCATE PREPARE biz_index_stmt;
|
||||
|
||||
-- 后台菜单。
|
||||
SET @point_task_parent_id := COALESCE(
|
||||
(SELECT parent_id FROM sys_menu WHERE perms = 'app:resource:list' LIMIT 1),
|
||||
(SELECT parent_id FROM sys_menu WHERE perms = 'app:article:list' LIMIT 1),
|
||||
0
|
||||
);
|
||||
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, is_frame, is_cache,
|
||||
menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '积分任务', @point_task_parent_id, 30, 'pointTask', 'app/pointTask/index', '', 1, 0,
|
||||
'C', '0', '0', 'app:pointTask:list', 'guide', 'admin', NOW(), '', NULL, '积分任务配置'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'app:pointTask:list');
|
||||
|
||||
SET @point_task_menu_id := (SELECT menu_id FROM sys_menu WHERE perms = 'app:pointTask:list' LIMIT 1);
|
||||
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, is_frame, is_cache,
|
||||
menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '任务查询', @point_task_menu_id, 1, '', '', '', 1, 0, 'F', '0', '0',
|
||||
'app:pointTask:query', '#', 'admin', NOW(), '', NULL, ''
|
||||
WHERE @point_task_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'app:pointTask:query');
|
||||
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, is_frame, is_cache,
|
||||
menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '任务新增', @point_task_menu_id, 2, '', '', '', 1, 0, 'F', '0', '0',
|
||||
'app:pointTask:add', '#', 'admin', NOW(), '', NULL, ''
|
||||
WHERE @point_task_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'app:pointTask:add');
|
||||
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, is_frame, is_cache,
|
||||
menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '任务修改', @point_task_menu_id, 3, '', '', '', 1, 0, 'F', '0', '0',
|
||||
'app:pointTask:edit', '#', 'admin', NOW(), '', NULL, ''
|
||||
WHERE @point_task_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'app:pointTask:edit');
|
||||
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, is_frame, is_cache,
|
||||
menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '任务删除', @point_task_menu_id, 4, '', '', '', 1, 0, 'F', '0', '0',
|
||||
'app:pointTask:remove', '#', 'admin', NOW(), '', NULL, ''
|
||||
WHERE @point_task_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'app:pointTask:remove');
|
||||
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, is_frame, is_cache,
|
||||
menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '任务审核', @point_task_parent_id, 31, 'pointTaskReview', 'app/pointTaskReview/index', '', 1, 0,
|
||||
'C', '0', '0', 'app:pointTaskReview:list', 'audit', 'admin', NOW(), '', NULL, '积分任务审核'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'app:pointTaskReview:list');
|
||||
|
||||
SET @point_review_menu_id := (SELECT menu_id FROM sys_menu WHERE perms = 'app:pointTaskReview:list' LIMIT 1);
|
||||
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, is_frame, is_cache,
|
||||
menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '审核查询', @point_review_menu_id, 1, '', '', '', 1, 0, 'F', '0', '0',
|
||||
'app:pointTaskReview:query', '#', 'admin', NOW(), '', NULL, ''
|
||||
WHERE @point_review_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'app:pointTaskReview:query');
|
||||
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, is_frame, is_cache,
|
||||
menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '任务审核操作', @point_review_menu_id, 2, '', '', '', 1, 0, 'F', '0', '0',
|
||||
'app:pointTaskReview:edit', '#', 'admin', NOW(), '', NULL, ''
|
||||
WHERE @point_review_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'app:pointTaskReview:edit');
|
||||
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, is_frame, is_cache,
|
||||
menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '撤销任务奖励', @point_review_menu_id, 3, '', '', '', 1, 0, 'F', '0', '0',
|
||||
'app:pointTaskReview:revoke', '#', 'admin', NOW(), '', NULL, ''
|
||||
WHERE @point_review_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'app:pointTaskReview:revoke');
|
||||
Reference in New Issue
Block a user