|
|
@@ -0,0 +1,367 @@
|
|
|
+package com.fs.his.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.date.DateUtil;
|
|
|
+import com.fs.common.core.domain.R;
|
|
|
+import com.fs.common.core.redis.RedisCache;
|
|
|
+import com.fs.common.utils.DateUtils;
|
|
|
+import com.fs.common.utils.uuid.IdUtils;
|
|
|
+import com.fs.his.config.IntegralConfig;
|
|
|
+import com.fs.his.constant.IntegralTaskConstants;
|
|
|
+import com.fs.his.domain.FsUser;
|
|
|
+import com.fs.his.domain.FsUserIntegralBatch;
|
|
|
+import com.fs.his.domain.FsUserIntegralLogs;
|
|
|
+import com.fs.his.domain.FsUserNewTask;
|
|
|
+import com.fs.his.enums.FsUserIntegralLogTypeEnum;
|
|
|
+import com.fs.his.mapper.FsUserIntegralBatchMapper;
|
|
|
+import com.fs.his.mapper.FsUserIntegralLogsMapper;
|
|
|
+import com.fs.his.mapper.FsUserMapper;
|
|
|
+import com.fs.his.mapper.FsUserNewTaskMapper;
|
|
|
+import com.fs.his.param.FsUserAddIntegralTemplateParam;
|
|
|
+import com.fs.his.param.IntegralBrowseCompleteParam;
|
|
|
+import com.fs.his.param.IntegralBrowseStartParam;
|
|
|
+import com.fs.his.service.IFsUserIntegralLogsService;
|
|
|
+import com.fs.his.service.IFsUserSignService;
|
|
|
+import com.fs.his.service.IIntegralTaskService;
|
|
|
+import com.fs.his.utils.IntegralConfigHelper;
|
|
|
+import com.fs.his.utils.IntegralTaskScopeHelper;
|
|
|
+import com.fs.system.service.ISysConfigService;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import java.time.Instant;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.temporal.ChronoUnit;
|
|
|
+import java.util.*;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class IntegralTaskServiceImpl implements IIntegralTaskService {
|
|
|
+
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(IntegralTaskServiceImpl.class);
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ISysConfigService configService;
|
|
|
+ @Autowired
|
|
|
+ private FsUserMapper fsUserMapper;
|
|
|
+ @Autowired
|
|
|
+ private FsUserIntegralLogsMapper fsUserIntegralLogsMapper;
|
|
|
+ @Autowired
|
|
|
+ private FsUserNewTaskMapper fsUserNewTaskMapper;
|
|
|
+ @Autowired
|
|
|
+ private IFsUserIntegralLogsService userIntegralLogsService;
|
|
|
+ @Autowired
|
|
|
+ private IFsUserSignService userSignService;
|
|
|
+ @Autowired
|
|
|
+ private RedisCache redisCache;
|
|
|
+ @Autowired
|
|
|
+ private FsUserIntegralBatchMapper fsUserIntegralBatchMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R getTaskCenter(Long userId, String platform) {
|
|
|
+ IntegralConfig config = IntegralConfigHelper.load(configService);
|
|
|
+ FsUser user = fsUserMapper.selectFsUserByUserId(userId);
|
|
|
+ if (user == null) {
|
|
|
+ return R.error("用户不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ String normalizedPlatform = IntegralTaskScopeHelper.normalizePlatform(platform);
|
|
|
+ Map<String, String> taskDisplayScope = IntegralTaskScopeHelper.resolveTaskDisplayScope(config);
|
|
|
+
|
|
|
+ String signJson = configService.selectConfigByKey("his.sign");
|
|
|
+ Long signNum = userSignService.getSign(user);
|
|
|
+ Boolean isDaySign = userSignService.isDaySign(user);
|
|
|
+ String popupKey = IntegralTaskConstants.REDIS_SIGN_POPUP_PREFIX + userId + ":" + LocalDate.now();
|
|
|
+ boolean needPopup = !Boolean.TRUE.equals(isDaySign) && redisCache.getCacheObject(popupKey) == null;
|
|
|
+
|
|
|
+ Map<String, Object> sign = new HashMap<>();
|
|
|
+ sign.put("signNum", signNum);
|
|
|
+ sign.put("isDaySign", isDaySign);
|
|
|
+ sign.put("needPopup", needPopup);
|
|
|
+ sign.put("configs", signJson);
|
|
|
+ sign.put("visibleScope", IntegralTaskScopeHelper.getTaskDisplayScope(taskDisplayScope, IntegralTaskConstants.TASK_SIGN));
|
|
|
+ sign.put("hidden", !IntegralTaskScopeHelper.isTaskVisible(taskDisplayScope, IntegralTaskConstants.TASK_SIGN, normalizedPlatform));
|
|
|
+
|
|
|
+ List<Map<String, Object>> newbieTasks = filterTasksByPlatform(buildNewbieTasks(userId, config, taskDisplayScope), taskDisplayScope, normalizedPlatform);
|
|
|
+ List<Map<String, Object>> dailyTasks = filterTasksByPlatform(buildDailyTasks(userId, config, taskDisplayScope), taskDisplayScope, normalizedPlatform);
|
|
|
+
|
|
|
+ Map<String, Object> data = new HashMap<>();
|
|
|
+ data.put("platform", normalizedPlatform);
|
|
|
+ data.put("sign", sign);
|
|
|
+ data.put("newbieTasks", newbieTasks);
|
|
|
+ data.put("dailyTasks", dailyTasks);
|
|
|
+ data.put("integralBalance", user.getIntegral());
|
|
|
+ data.put("expireTip", buildExpireTip(config));
|
|
|
+ return R.ok().put("data", data);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R startBrowse(Long userId, IntegralBrowseStartParam param) {
|
|
|
+ IntegralConfig config = IntegralConfigHelper.load(configService);
|
|
|
+ List<FsUserIntegralLogs> todayLogs = fsUserIntegralLogsMapper.selectFsUserIntegralLogsByUserIdAndLogType(
|
|
|
+ userId, FsUserIntegralLogTypeEnum.TYPE_13.getValue(), LocalDate.now());
|
|
|
+ if (todayLogs.size() >= config.getIntegralProductDailyLimit()) {
|
|
|
+ return R.error("今日浏览商城积分已达上限");
|
|
|
+ }
|
|
|
+ String token = IdUtils.simpleUUID();
|
|
|
+ Map<String, Object> cache = new HashMap<>();
|
|
|
+ cache.put("userId", userId);
|
|
|
+ cache.put("startTime", System.currentTimeMillis());
|
|
|
+ cache.put("pageType", param.getPageType());
|
|
|
+ cache.put("goodsId", param.getGoodsId());
|
|
|
+ cache.put("platform", param.getPlatform());
|
|
|
+ redisCache.setCacheObject(IntegralTaskConstants.REDIS_BROWSE_PREFIX + token, cache, 5, TimeUnit.MINUTES);
|
|
|
+ return R.ok().put("browseToken", token).put("requiredSeconds", config.getIntegralProductBrowseSeconds());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public R completeBrowse(Long userId, IntegralBrowseCompleteParam param) {
|
|
|
+ if (StringUtils.isBlank(param.getBrowseToken())) {
|
|
|
+ return R.error("browseToken不能为空");
|
|
|
+ }
|
|
|
+ String key = IntegralTaskConstants.REDIS_BROWSE_PREFIX + param.getBrowseToken();
|
|
|
+ Map<String, Object> cache = redisCache.getCacheObject(key);
|
|
|
+ if (cache == null) {
|
|
|
+ return R.error("浏览计时已过期,请重新开始");
|
|
|
+ }
|
|
|
+ Object cacheUserId = cache.get("userId");
|
|
|
+ if (cacheUserId == null || !userId.equals(Long.valueOf(cacheUserId.toString()))) {
|
|
|
+ return R.error("浏览计时无效");
|
|
|
+ }
|
|
|
+ IntegralConfig config = IntegralConfigHelper.load(configService);
|
|
|
+ long startTime = Long.parseLong(cache.get("startTime").toString());
|
|
|
+ long elapsedSeconds = (System.currentTimeMillis() - startTime) / 1000;
|
|
|
+ if (elapsedSeconds < config.getIntegralProductBrowseSeconds()) {
|
|
|
+ return R.error("浏览时长不足" + config.getIntegralProductBrowseSeconds() + "秒");
|
|
|
+ }
|
|
|
+ redisCache.deleteObject(key);
|
|
|
+
|
|
|
+ FsUserAddIntegralTemplateParam templateParam = new FsUserAddIntegralTemplateParam();
|
|
|
+ templateParam.setUserId(userId);
|
|
|
+ templateParam.setLogType(FsUserIntegralLogTypeEnum.TYPE_13.getValue());
|
|
|
+ Object goodsId = cache.get("goodsId");
|
|
|
+ templateParam.setBusinessId(goodsId != null ? goodsId.toString() : LocalDate.now().toString());
|
|
|
+ templateParam.setPlatform(param.getPlatform());
|
|
|
+ R result = userIntegralLogsService.addIntegralTemplate(templateParam);
|
|
|
+ if (!isSuccess(result)) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ markTaskDone(userId, IntegralTaskConstants.TASK_BROWSE_MALL, true);
|
|
|
+ return enrichTaskToast(result, "浏览商城60秒", config.getIntegralProduct());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public void tryGrantFirstPurchase(Long userId, Long orderId) {
|
|
|
+ if (userId == null || orderId == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ List<FsUserIntegralLogs> exists = fsUserIntegralLogsMapper.selectFsUserIntegralLogsByUserIdAndLogType(
|
|
|
+ userId, FsUserIntegralLogTypeEnum.TYPE_32.getValue(), null);
|
|
|
+ if (!exists.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ FsUserAddIntegralTemplateParam param = new FsUserAddIntegralTemplateParam();
|
|
|
+ param.setUserId(userId);
|
|
|
+ param.setLogType(FsUserIntegralLogTypeEnum.TYPE_32.getValue());
|
|
|
+ param.setBusinessId(orderId.toString());
|
|
|
+ R r = userIntegralLogsService.addIntegralTemplate(param);
|
|
|
+ if (isSuccess(r)) {
|
|
|
+ markTaskDone(userId, IntegralTaskConstants.TASK_FIRST_PURCHASE, false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public void tryGrantCourseCommentIntegral(Long userId, Long videoId, Long commentId) {
|
|
|
+ if (userId == null || videoId == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ List<FsUserIntegralLogs> exists = fsUserIntegralLogsMapper.selectFsUserIntegralLogsByUserIdAndLogType(
|
|
|
+ userId, FsUserIntegralLogTypeEnum.TYPE_33.getValue(), null);
|
|
|
+ for (FsUserIntegralLogs item : exists) {
|
|
|
+ if (videoId.toString().equals(item.getBusinessId())) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ FsUserAddIntegralTemplateParam param = new FsUserAddIntegralTemplateParam();
|
|
|
+ param.setUserId(userId);
|
|
|
+ param.setLogType(FsUserIntegralLogTypeEnum.TYPE_33.getValue());
|
|
|
+ param.setBusinessId(videoId.toString());
|
|
|
+ param.setRemark(commentId != null ? commentId.toString() : null);
|
|
|
+ userIntegralLogsService.addIntegralTemplate(param);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public void processExpiredIntegralBatches() {
|
|
|
+ List<FsUserIntegralBatch> batches = fsUserIntegralBatchMapper.selectExpiredBatches(new Date(), 500);
|
|
|
+ for (FsUserIntegralBatch batch : batches) {
|
|
|
+ if (batch.getRemain() == null || batch.getRemain() <= 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ FsUser user = fsUserMapper.selectFsUserByUserId(batch.getUserId());
|
|
|
+ if (user == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ long deduct = batch.getRemain();
|
|
|
+ long newBalance = Math.max(0, user.getIntegral() - deduct);
|
|
|
+ FsUser userMap = new FsUser();
|
|
|
+ userMap.setUserId(batch.getUserId());
|
|
|
+ userMap.setIntegral(newBalance);
|
|
|
+ fsUserMapper.updateFsUser(userMap);
|
|
|
+
|
|
|
+ fsUserIntegralBatchMapper.updateRemain(batch.getBatchId(), 0L);
|
|
|
+
|
|
|
+ FsUserIntegralLogs logs = new FsUserIntegralLogs();
|
|
|
+ logs.setIntegral(-deduct);
|
|
|
+ logs.setUserId(batch.getUserId());
|
|
|
+ logs.setBalance(newBalance);
|
|
|
+ logs.setLogType(FsUserIntegralLogTypeEnum.TYPE_7.getValue());
|
|
|
+ logs.setBusinessId(batch.getBatchId().toString());
|
|
|
+ logs.setCreateTime(new Date());
|
|
|
+ logs.setRemark("积分过期失效");
|
|
|
+ fsUserIntegralLogsMapper.insertFsUserIntegralLogs(logs);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void markSignPopupShown(Long userId) {
|
|
|
+ String popupKey = IntegralTaskConstants.REDIS_SIGN_POPUP_PREFIX + userId + ":" + LocalDate.now();
|
|
|
+ redisCache.setCacheObject(popupKey, "1", 1, TimeUnit.DAYS);
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Map<String, Object>> buildNewbieTasks(Long userId, IntegralConfig config, Map<String, String> taskDisplayScope) {
|
|
|
+ List<Map<String, Object>> tasks = new ArrayList<>();
|
|
|
+ tasks.add(buildLifetimeTask(IntegralTaskConstants.TASK_REGISTER, "首次注册登录",
|
|
|
+ config.getIntegralRegister(), hasLogType(userId, 20), taskDisplayScope));
|
|
|
+ tasks.add(buildLifetimeTask(IntegralTaskConstants.TASK_DOWNLOAD_APP, "首次下载APP",
|
|
|
+ config.getDownloadAppIntegral(), hasLogType(userId, 28), taskDisplayScope));
|
|
|
+ tasks.add(buildLifetimeTask(IntegralTaskConstants.TASK_FIRST_PURCHASE, "首次完成购物",
|
|
|
+ config.getIntegralFirstPurchase(), hasLogType(userId, 32), taskDisplayScope));
|
|
|
+ tasks.add(buildLifetimeTask(IntegralTaskConstants.TASK_INVITED, "填写朋友邀请码",
|
|
|
+ config.getIntegralInvited(), hasLogType(userId, 19), taskDisplayScope));
|
|
|
+ hideCompletedNewbieTasks(tasks, userId);
|
|
|
+ return tasks;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Map<String, Object>> buildDailyTasks(Long userId, IntegralConfig config, Map<String, String> taskDisplayScope) {
|
|
|
+ List<Map<String, Object>> tasks = new ArrayList<>();
|
|
|
+ int shareCount = countTodayLog(userId, 3);
|
|
|
+ tasks.add(buildDailyTask(IntegralTaskConstants.TASK_SHARE, "分享得积分",
|
|
|
+ config.getIntegralShare(), shareCount, config.getIntegralShareDailyLimit(), taskDisplayScope));
|
|
|
+
|
|
|
+ int courseCount = countTodayLog(userId, 10);
|
|
|
+ tasks.add(buildDailyTask(IntegralTaskConstants.TASK_COURSE, "学习首页课程",
|
|
|
+ config.getIntegralCourse(), courseCount, config.getIntegralCourseDailyLimit(), taskDisplayScope));
|
|
|
+
|
|
|
+ int browseCount = countTodayLog(userId, 13);
|
|
|
+ tasks.add(buildDailyTask(IntegralTaskConstants.TASK_BROWSE_MALL, "浏览商城60秒",
|
|
|
+ config.getIntegralProduct(), browseCount, config.getIntegralProductDailyLimit(), taskDisplayScope));
|
|
|
+
|
|
|
+ int inviteCount = countTodayLog(userId, 18);
|
|
|
+ tasks.add(buildDailyTask(IntegralTaskConstants.TASK_INVITE, "邀好友享积分",
|
|
|
+ config.getIntegralInvite(), inviteCount, config.getIntegralInviteDailyLimit(), taskDisplayScope));
|
|
|
+ return tasks;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Map<String, Object>> filterTasksByPlatform(List<Map<String, Object>> tasks, Map<String, String> taskDisplayScope, String platform) {
|
|
|
+ List<Map<String, Object>> visibleTasks = new ArrayList<>();
|
|
|
+ for (Map<String, Object> task : tasks) {
|
|
|
+ if (Boolean.TRUE.equals(task.get("hidden"))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String code = task.get("code") == null ? null : task.get("code").toString();
|
|
|
+ if (IntegralTaskScopeHelper.isTaskVisible(taskDisplayScope, code, platform)) {
|
|
|
+ visibleTasks.add(task);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return visibleTasks;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> buildLifetimeTask(String code, String title, Integer reward, boolean finished, Map<String, String> taskDisplayScope) {
|
|
|
+ Map<String, Object> task = new HashMap<>();
|
|
|
+ task.put("code", code);
|
|
|
+ task.put("title", title);
|
|
|
+ task.put("reward", reward == null ? 0 : reward);
|
|
|
+ task.put("status", finished ? 2 : 0);
|
|
|
+ task.put("hidden", false);
|
|
|
+ task.put("visibleScope", IntegralTaskScopeHelper.getTaskDisplayScope(taskDisplayScope, code));
|
|
|
+ return task;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> buildDailyTask(String code, String title, Integer reward, int doneCount, int limitCount, Map<String, String> taskDisplayScope) {
|
|
|
+ Map<String, Object> task = new HashMap<>();
|
|
|
+ task.put("code", code);
|
|
|
+ task.put("title", title);
|
|
|
+ task.put("reward", reward == null ? 0 : reward);
|
|
|
+ task.put("doneCount", doneCount);
|
|
|
+ task.put("limitCount", limitCount);
|
|
|
+ int status = 0;
|
|
|
+ if (doneCount >= limitCount) {
|
|
|
+ status = 3;
|
|
|
+ } else if (doneCount > 0) {
|
|
|
+ status = 2;
|
|
|
+ }
|
|
|
+ task.put("status", status);
|
|
|
+ task.put("visibleScope", IntegralTaskScopeHelper.getTaskDisplayScope(taskDisplayScope, code));
|
|
|
+ return task;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void hideCompletedNewbieTasks(List<Map<String, Object>> tasks, Long userId) {
|
|
|
+ FsUserNewTask newTask = fsUserNewTaskMapper.selectFsUserNewTaskByUserId(userId);
|
|
|
+ if (newTask == null || newTask.getCreateTime() == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ long hours = ChronoUnit.HOURS.between(newTask.getCreateTime().toInstant(), Instant.now());
|
|
|
+ if (hours < 24) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ for (Map<String, Object> task : tasks) {
|
|
|
+ if (Integer.valueOf(2).equals(task.get("status"))) {
|
|
|
+ task.put("hidden", true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean hasLogType(Long userId, int logType) {
|
|
|
+ List<FsUserIntegralLogs> logs = fsUserIntegralLogsMapper.selectFsUserIntegralLogsByUserIdAndLogType(userId, logType, null);
|
|
|
+ return logs != null && !logs.isEmpty();
|
|
|
+ }
|
|
|
+
|
|
|
+ private int countTodayLog(Long userId, int logType) {
|
|
|
+ List<FsUserIntegralLogs> logs = fsUserIntegralLogsMapper.selectFsUserIntegralLogsByUserIdAndLogType(userId, logType, LocalDate.now());
|
|
|
+ return logs == null ? 0 : logs.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildExpireTip(IntegralConfig config) {
|
|
|
+ if (config.getIntegralExpireDays() == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return "积分自获得起" + config.getIntegralExpireDays() + "天内有效,过期自动失效";
|
|
|
+ }
|
|
|
+
|
|
|
+ private void markTaskDone(Long userId, String taskCode, boolean daily) {
|
|
|
+ String period = daily ? LocalDate.now().toString() : "lifetime";
|
|
|
+ String key = IntegralTaskConstants.REDIS_TASK_DONE_PREFIX + userId + ":" + taskCode + ":" + period;
|
|
|
+ redisCache.setCacheObject(key, "1", daily ? 1 : 3650, TimeUnit.DAYS);
|
|
|
+ }
|
|
|
+
|
|
|
+ static R enrichTaskToast(R result, String taskName, Integer reward) {
|
|
|
+ if (result == null) {
|
|
|
+ return R.ok().put("taskName", taskName).put("rewardPoints", reward).put("toast", taskName + " +" + reward + "积分");
|
|
|
+ }
|
|
|
+ result.put("taskName", taskName);
|
|
|
+ result.put("rewardPoints", reward);
|
|
|
+ result.put("toast", taskName + " +" + reward + "积分");
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isSuccess(R r) {
|
|
|
+ return r != null && r.get("code") != null && Integer.valueOf(200).equals(Integer.valueOf(r.get("code").toString()));
|
|
|
+ }
|
|
|
+}
|