|
|
@@ -0,0 +1,249 @@
|
|
|
+package com.fs.course.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
+import com.fs.common.core.domain.R;
|
|
|
+import com.fs.common.core.redis.RedisCache;
|
|
|
+import com.fs.common.utils.StringUtils;
|
|
|
+import com.fs.course.config.CourseConfig;
|
|
|
+import com.fs.course.domain.FsUserCompanyQw;
|
|
|
+import com.fs.course.domain.FsUserCourse;
|
|
|
+import com.fs.course.domain.FsUserCourseVideo;
|
|
|
+import com.fs.course.mapper.FsUserCompanyQwMapper;
|
|
|
+import com.fs.course.mapper.FsUserCourseMapper;
|
|
|
+import com.fs.course.mapper.FsUserCourseVideoMapper;
|
|
|
+import com.fs.course.param.FsUserCourseVideoAddKfUParam;
|
|
|
+import com.fs.course.service.ICourseProjectSalesBindService;
|
|
|
+import com.fs.course.support.CourseProjectEquivalence;
|
|
|
+import com.fs.course.support.CourseProjectSalesBindConstants;
|
|
|
+import com.fs.enums.ExceptionCodeEnum;
|
|
|
+import com.fs.system.service.ISysConfigService;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.collections4.CollectionUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class CourseProjectSalesBindServiceImpl implements ICourseProjectSalesBindService {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ISysConfigService configService;
|
|
|
+ @Autowired
|
|
|
+ private RedisCache redisCache;
|
|
|
+ @Autowired
|
|
|
+ private FsUserCompanyQwMapper fsUserCompanyQwMapper;
|
|
|
+ @Autowired
|
|
|
+ private FsUserCourseVideoMapper fsUserCourseVideoMapper;
|
|
|
+ @Autowired
|
|
|
+ private FsUserCourseMapper fsUserCourseMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean isLimitActive(CourseConfig config) {
|
|
|
+ if (config == null || !Boolean.TRUE.equals(config.getProjectRepeatLimitEnabled())) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Date effectiveTime = config.getProjectRepeatLimitEffectiveTime();
|
|
|
+ if (effectiveTime == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return !new Date().before(effectiveTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CourseConfig loadCourseConfig() {
|
|
|
+ String json = configService.selectConfigByKey("course.config");
|
|
|
+ if (StringUtils.isEmpty(json)) {
|
|
|
+ return new CourseConfig();
|
|
|
+ }
|
|
|
+ return JSONUtil.toBean(json, CourseConfig.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean isPilotCourse(Long videoId, CourseConfig config) {
|
|
|
+ if (videoId == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (config != null && Boolean.FALSE.equals(config.getPilotCourseSkipRepeatLimit())) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ FsUserCourseVideo video = getVideoCached(videoId);
|
|
|
+ if (video == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (video.getIsFirst() != null && video.getIsFirst() == 1) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return StringUtils.isNotEmpty(video.getTitle()) && video.getTitle().contains("先导课");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R checkAndBind(FsUserCourseVideoAddKfUParam param) {
|
|
|
+ if (param == null || param.getUserId() == null || param.getCompanyUserId() == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ CourseConfig config = loadCourseConfig();
|
|
|
+ if (!isLimitActive(config)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isPilotCourse(param.getVideoId(), config)) {
|
|
|
+ log.debug("按项目重粉限制跳过先导课 userId={}, videoId={}", param.getUserId(), param.getVideoId());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ Long projectId = resolveCourseProjectId(param);
|
|
|
+ if (projectId == null || projectId == 0L) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Long> equivalentProjectIds = CourseProjectEquivalence.equivalentProjectIds(projectId);
|
|
|
+ if (CollectionUtils.isEmpty(equivalentProjectIds)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ Long currentCompanyUserId = param.getCompanyUserId();
|
|
|
+ FsUserCompanyQw matchedBind = null;
|
|
|
+
|
|
|
+ for (Long equivalentProjectId : equivalentProjectIds) {
|
|
|
+ FsUserCompanyQw existBind = getBindRecord(param.getUserId(), equivalentProjectId);
|
|
|
+ if (existBind == null || existBind.getCompanyUserId() == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!existBind.getCompanyUserId().equals(currentCompanyUserId)) {
|
|
|
+ log.info("按项目重粉限制拦截 userId={}, projectId={}, boundSales={}, currentSales={}, firstBindTime={}",
|
|
|
+ param.getUserId(), equivalentProjectId, existBind.getCompanyUserId(),
|
|
|
+ currentCompanyUserId, existBind.getFirstBindTime());
|
|
|
+ return R.error(ExceptionCodeEnum.USER_PROJECT_OTHER_SALES_BOUND.getCode(),
|
|
|
+ ExceptionCodeEnum.USER_PROJECT_OTHER_SALES_BOUND.getDescription());
|
|
|
+ }
|
|
|
+ matchedBind = existBind;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (matchedBind != null) {
|
|
|
+ for (Long equivalentProjectId : equivalentProjectIds) {
|
|
|
+ cacheBind(param.getUserId(), equivalentProjectId, matchedBind.getCompanyUserId());
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return tryFirstBind(param, projectId, currentCompanyUserId);
|
|
|
+ }
|
|
|
+
|
|
|
+ private R tryFirstBind(FsUserCourseVideoAddKfUParam param, Long projectId, Long currentCompanyUserId) {
|
|
|
+ Long qwUserId = parseQwUserId(param.getQwUserId());
|
|
|
+ try {
|
|
|
+ fsUserCompanyQwMapper.insertOrUpdate(
|
|
|
+ param.getUserId(),
|
|
|
+ projectId,
|
|
|
+ qwUserId,
|
|
|
+ currentCompanyUserId,
|
|
|
+ param.getCompanyId(),
|
|
|
+ param.getQwExternalId(),
|
|
|
+ param.getCourseId(),
|
|
|
+ param.getVideoId()
|
|
|
+ );
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("按项目销售绑定写入失败 userId={}, projectId={}", param.getUserId(), projectId, e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ FsUserCompanyQw bind = fsUserCompanyQwMapper.selectByUserAndProject(param.getUserId(), projectId);
|
|
|
+ if (bind != null && bind.getCompanyUserId() != null
|
|
|
+ && !bind.getCompanyUserId().equals(currentCompanyUserId)) {
|
|
|
+ log.info("按项目重粉限制并发拦截 userId={}, projectId={}, boundSales={}, currentSales={}, firstBindTime={}",
|
|
|
+ param.getUserId(), projectId, bind.getCompanyUserId(), currentCompanyUserId, bind.getFirstBindTime());
|
|
|
+ return R.error(ExceptionCodeEnum.USER_PROJECT_OTHER_SALES_BOUND.getCode(),
|
|
|
+ ExceptionCodeEnum.USER_PROJECT_OTHER_SALES_BOUND.getDescription());
|
|
|
+ }
|
|
|
+
|
|
|
+ cacheBind(param.getUserId(), projectId, currentCompanyUserId);
|
|
|
+ log.info("按项目销售首次绑定 userId={}, projectId={}, companyUserId={}, firstBindTime={}",
|
|
|
+ param.getUserId(), projectId, currentCompanyUserId,
|
|
|
+ bind != null ? bind.getFirstBindTime() : null);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private FsUserCompanyQw getBindRecord(Long userId, Long projectId) {
|
|
|
+ Long cachedCompanyUserId = redisCache.getCacheObject(CourseProjectSalesBindConstants.bindCacheKey(userId, projectId));
|
|
|
+ if (cachedCompanyUserId != null) {
|
|
|
+ FsUserCompanyQw cached = new FsUserCompanyQw();
|
|
|
+ cached.setFsUserId(userId);
|
|
|
+ cached.setProjectId(projectId);
|
|
|
+ cached.setCompanyUserId(cachedCompanyUserId);
|
|
|
+ return cached;
|
|
|
+ }
|
|
|
+
|
|
|
+ FsUserCompanyQw existBind = fsUserCompanyQwMapper.selectByUserAndProject(userId, projectId);
|
|
|
+ if (existBind != null && existBind.getCompanyUserId() != null) {
|
|
|
+ cacheBind(userId, projectId, existBind.getCompanyUserId());
|
|
|
+ }
|
|
|
+ return existBind;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void cacheBind(Long userId, Long projectId, Long companyUserId) {
|
|
|
+ if (userId == null || projectId == null || companyUserId == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ redisCache.setCacheObject(
|
|
|
+ CourseProjectSalesBindConstants.bindCacheKey(userId, projectId),
|
|
|
+ companyUserId,
|
|
|
+ CourseProjectSalesBindConstants.BIND_CACHE_DAYS,
|
|
|
+ TimeUnit.DAYS
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ private Long resolveCourseProjectId(FsUserCourseVideoAddKfUParam param) {
|
|
|
+ if (param.getCourseId() != null) {
|
|
|
+ FsUserCourse course = fsUserCourseMapper.selectFsUserCourseByCourseId(param.getCourseId());
|
|
|
+ if (course != null && course.getProject() != null && course.getProject() != 0L) {
|
|
|
+ return course.getProject();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (param.getVideoId() != null) {
|
|
|
+ FsUserCourseVideo video = getVideoCached(param.getVideoId());
|
|
|
+ if (video != null && video.getCourseId() != null) {
|
|
|
+ FsUserCourse course = fsUserCourseMapper.selectFsUserCourseByCourseId(video.getCourseId());
|
|
|
+ if (course != null && course.getProject() != null && course.getProject() != 0L) {
|
|
|
+ return course.getProject();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0L;
|
|
|
+ }
|
|
|
+
|
|
|
+ private FsUserCourseVideo getVideoCached(Long videoId) {
|
|
|
+ String cacheKey = CourseProjectSalesBindConstants.videoMetaCacheKey(videoId);
|
|
|
+ FsUserCourseVideo cached = redisCache.getCacheObject(cacheKey);
|
|
|
+ if (cached != null) {
|
|
|
+ return cached;
|
|
|
+ }
|
|
|
+ FsUserCourseVideo video = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoId(videoId);
|
|
|
+ if (video != null) {
|
|
|
+ FsUserCourseVideo meta = new FsUserCourseVideo();
|
|
|
+ meta.setVideoId(video.getVideoId());
|
|
|
+ meta.setCourseId(video.getCourseId());
|
|
|
+ meta.setTitle(video.getTitle());
|
|
|
+ meta.setIsFirst(video.getIsFirst());
|
|
|
+ redisCache.setCacheObject(cacheKey, meta,
|
|
|
+ CourseProjectSalesBindConstants.VIDEO_META_CACHE_HOURS, TimeUnit.HOURS);
|
|
|
+ return meta;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Long parseQwUserId(String qwUserId) {
|
|
|
+ if (StringUtils.isEmpty(qwUserId)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ return Long.parseLong(qwUserId);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|