Commit cca930797a069277c45cca0964d84d8089895c67

Authored by 张志伟
1 parent 80738b3b

补全join和exit的逻辑

doc/v2.0/update.sql
... ... @@ -21,8 +21,7 @@ CREATE TABLE IF NOT EXISTS fw_valhalla.pub_stand_staff_info
21 21 group_id bigint not null comment '集团id',
22 22 create_time datetime not null,
23 23 update_time datetime null,
24   - constraint pub_stand_staff_info_staff_id_uindex
25   - unique (staff_id) comment '用户id'
  24 + constraint pub_stand_staff_info_staff_id_group_id_uindex
  25 + unique (staff_id,group_id) comment '集团用户'
26 26 )
27 27 comment '公共池站岗配置';
28   -
... ...
fw-valhalla-server/src/main/resources/application.yml
... ... @@ -12,8 +12,8 @@ spring:
12 12 ttl: 10m
13 13 use-json: true
14 14 cache:
15   - - name: auth:url
16   - ttl: 5m
  15 + - name: pub:stand
  16 + ttl: 12h
17 17  
18 18 cloud:
19 19 nacos:
... ...
fw-valhalla-service/src/main/java/cn/fw/valhalla/service/bus/pub/PubDistributeBizService.java 0 → 100644
  1 +package cn.fw.valhalla.service.bus.pub;
  2 +
  3 +import cn.fw.common.cache.CacheExtender;
  4 +import cn.fw.common.cache.locker.DistributedLocker;
  5 +import cn.fw.valhalla.rpc.ehr.EhrRpcService;
  6 +import cn.fw.valhalla.service.data.PubStandStaffInfoService;
  7 +import lombok.AccessLevel;
  8 +import lombok.Getter;
  9 +import lombok.RequiredArgsConstructor;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +import org.apache.commons.lang3.tuple.Pair;
  12 +import org.redisson.api.RLock;
  13 +import org.springframework.beans.factory.annotation.Value;
  14 +import org.springframework.data.redis.core.BoundListOperations;
  15 +import org.springframework.data.redis.core.StringRedisTemplate;
  16 +import org.springframework.stereotype.Service;
  17 +import org.springframework.util.Assert;
  18 +
  19 +import java.time.MonthDay;
  20 +import java.util.concurrent.TimeUnit;
  21 +
  22 +/**
  23 + * 公共池站岗分配
  24 + *
  25 + * @author : kurisu
  26 + * @version : 1.0
  27 + * @className : PubDBizService
  28 + * @description : 公共池站岗分配
  29 + * @date : 2023-03-11 15:55
  30 + */
  31 +@Service
  32 +@Slf4j
  33 +@RequiredArgsConstructor
  34 +public class PubDistributeBizService {
  35 + private final EhrRpcService ehrRpcService;
  36 + private final PubStandStaffInfoService pubStandStaffInfoService;
  37 + private final CacheExtender cache;
  38 + private final StringRedisTemplate redisTemplate;
  39 + private final DistributedLocker distributedLocker;
  40 + @Value("${spring.cache.custom.global-prefix}:stand:pub")
  41 + @Getter(AccessLevel.PRIVATE)
  42 + private String keyPrefix;
  43 + /**
  44 + * 执行分配
  45 + *
  46 + * @param groupId
  47 + */
  48 + public void distribute(Long groupId) {
  49 + final String key = generateKey(groupId);
  50 + final String lockKey = String.format("pub:distribute:%s", groupId);
  51 + Pair<Boolean, RLock> lockPair = distributedLocker.tryLock(lockKey, TimeUnit.MINUTES, 0, -1);
  52 + try {
  53 + if (Boolean.TRUE.equals(lockPair.getLeft())) {
  54 + BoundListOperations<String, String> ops = redisTemplate.boundListOps(key);
  55 + String id = ops.leftPop();
  56 + }
  57 + } finally {
  58 + if (lockPair.getRight().isLocked()) {
  59 + lockPair.getRight().unlock();
  60 + }
  61 + }
  62 +
  63 + }
  64 +
  65 + private String generateKey(final Long groupId) {
  66 + Assert.notNull(groupId, "groupId cannot be null");
  67 + String day = MonthDay.now().toString().replace("-", "");
  68 + return String.format("%s:%s:%s", getKeyPrefix(), day, groupId);
  69 + }
  70 +}
... ...
fw-valhalla-service/src/main/java/cn/fw/valhalla/service/bus/pub/PubStandBizService.java
1 1 package cn.fw.valhalla.service.bus.pub;
2 2  
3 3 import cn.fw.common.cache.CacheExtender;
  4 +import cn.fw.common.cache.locker.DistributedLocker;
  5 +import cn.fw.common.web.annotation.DisLock;
  6 +import cn.fw.erp.sdk.api.result.UserRoleDataRange;
  7 +import cn.fw.valhalla.common.constant.RoleCode;
  8 +import cn.fw.valhalla.common.utils.DateUtil;
  9 +import cn.fw.valhalla.domain.db.pub.PubStandStaffInfo;
  10 +import cn.fw.valhalla.domain.enums.PubStandRang;
  11 +import cn.fw.valhalla.domain.enums.PubStandType;
4 12 import cn.fw.valhalla.rpc.ehr.EhrRpcService;
  13 +import cn.fw.valhalla.rpc.erp.UserRoleRpcService;
  14 +import cn.fw.valhalla.rpc.erp.dto.UserInfoDTO;
  15 +import cn.fw.valhalla.rpc.oop.OopService;
  16 +import cn.fw.valhalla.rpc.oop.dto.ShopDTO;
5 17 import cn.fw.valhalla.service.data.PubStandStaffInfoService;
  18 +import lombok.AccessLevel;
  19 +import lombok.Getter;
6 20 import lombok.RequiredArgsConstructor;
7 21 import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.beans.factory.annotation.Value;
  23 +import org.springframework.data.redis.core.BoundListOperations;
  24 +import org.springframework.data.redis.core.StringRedisTemplate;
8 25 import org.springframework.stereotype.Service;
  26 +import org.springframework.transaction.PlatformTransactionManager;
  27 +import org.springframework.transaction.TransactionDefinition;
  28 +import org.springframework.transaction.TransactionStatus;
  29 +import org.springframework.transaction.annotation.Transactional;
  30 +import org.springframework.util.Assert;
  31 +import org.springframework.util.CollectionUtils;
  32 +
  33 +import java.time.LocalDate;
  34 +import java.time.MonthDay;
  35 +import java.util.*;
  36 +import java.util.concurrent.CompletableFuture;
  37 +
  38 +import static cn.fw.common.businessvalidator.Validator.BV;
9 39  
10 40 /**
11 41 * PubStandBizService
... ... @@ -21,6 +51,159 @@ import org.springframework.stereotype.Service;
21 51 @RequiredArgsConstructor
22 52 public class PubStandBizService {
23 53 private final EhrRpcService ehrRpcService;
  54 + private final OopService oopService;
  55 + private final UserRoleRpcService userRoleRpcService;
24 56 private final PubStandStaffInfoService pubStandStaffInfoService;
25 57 private final CacheExtender cache;
  58 + private final StringRedisTemplate redisTemplate;
  59 + private final DistributedLocker distributedLocker;
  60 + private final PlatformTransactionManager platformTransactionManager;
  61 + private final TransactionDefinition transactionDefinition;
  62 + @Value("${spring.cache.custom.global-prefix}:stand:pub")
  63 + @Getter(AccessLevel.PRIVATE)
  64 + private String keyPrefix;
  65 +
  66 + /**
  67 + * 同步站岗队列
  68 + *
  69 + * @param groupId
  70 + */
  71 + public void syncQueue(final Long groupId) {
  72 + String key = generateKey(groupId);
  73 + List<PubStandStaffInfo> staffList = pubStandStaffInfoService.queryLiningStaff(groupId);
  74 + if (CollectionUtils.isEmpty(staffList)) {
  75 + return;
  76 + }
  77 + String[] idArr = staffList.stream().map(PubStandStaffInfo::getId).map(String::valueOf).toArray(String[]::new);
  78 + redisTemplate.delete(key);
  79 + redisTemplate.opsForList().rightPushAll(key, idArr);
  80 + Date ashita = DateUtil.localDateTime2Date(LocalDate.now().plusDays(1).atStartOfDay().plusHours(1L));
  81 + redisTemplate.expireAt(key, ashita);
  82 + }
  83 +
  84 + /**
  85 + * 加入队列
  86 + *
  87 + * @param staffId
  88 + */
  89 + @Transactional(rollbackFor = Exception.class)
  90 + public void join(final Long staffId) {
  91 + this.join(staffId, true);
  92 + }
  93 +
  94 + /**
  95 + * 加入队列
  96 + *
  97 + * @param staffId
  98 + */
  99 + @DisLock(prefix = "#this.getKeyPrefix()", key = "#staffId", message = "请勿重复站岗")
  100 + public boolean join(final Long staffId, final boolean throwError) {
  101 + TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
  102 + try {
  103 + final UserInfoDTO user = ehrRpcService.user(staffId);
  104 + BV.notNull(user, () -> "人员信息读取失败,请稍后重试");
  105 + List<UserRoleDataRange> range = userRoleRpcService.getUserRoleDataRange(staffId, RoleCode.FWGW);
  106 + BV.isNotEmpty(range, () -> "非服务顾问无法站岗");
  107 + UserRoleDataRange dataRange = range.get(0);
  108 + final Long shopId = dataRange.getRangeValue();
  109 + ShopDTO shop = oopService.shop(shopId);
  110 + BV.notNull(shop, () -> "门店信息读取失败,请稍后重试");
  111 + final String shopName = shop.getShortName();
  112 + boolean hasTodo = hasAnyTodo(staffId);
  113 + BV.isFalse(hasTodo, () -> "还有未完成的跟进待办,无法站岗");
  114 + final Long groupId = user.getGroupId();
  115 +
  116 + Optional<PubStandStaffInfo> staffInfo = pubStandStaffInfoService.queryStaffByGroupId(staffId, groupId);
  117 + PubStandStaffInfo info = staffInfo.orElseGet(() -> this.generateStandInfo(user))
  118 + .setShopId(shopId)
  119 + .setShopName(shopName)
  120 + .setLining(Boolean.TRUE)
  121 + .setQueueable(Boolean.TRUE);
  122 + this.join(info);
  123 + platformTransactionManager.commit(transactionStatus);
  124 + return true;
  125 + } catch (Exception e) {
  126 + platformTransactionManager.rollback(transactionStatus);
  127 + e.printStackTrace();
  128 + if (throwError) {
  129 + throw e;
  130 + }
  131 + return false;
  132 + }
  133 + }
  134 +
  135 + /**
  136 + * 退出队列
  137 + *
  138 + * @param userId
  139 + * @param groupId
  140 + */
  141 + @Transactional(rollbackFor = Exception.class)
  142 + public void exitStand(final Long userId, final Long groupId) {
  143 + Optional<PubStandStaffInfo> staffInfo = pubStandStaffInfoService.queryStaffByGroupId(userId, groupId);
  144 + staffInfo.ifPresent(r -> {
  145 + r.setLining(Boolean.FALSE);
  146 + r.setNoInvolved(Boolean.FALSE);
  147 + pubStandStaffInfoService.updateById(r);
  148 + CompletableFuture.runAsync(() -> syncQueue(groupId));
  149 + });
  150 + }
  151 +
  152 + /**
  153 + * 加入队列
  154 + *
  155 + * @param info
  156 + */
  157 + private void join(final PubStandStaffInfo info) {
  158 + final String key = generateKey(info.getGroupId());
  159 + final Boolean hasKey = redisTemplate.hasKey(key);
  160 + BoundListOperations<String, String> ops = redisTemplate.boundListOps(key);
  161 +
  162 + final boolean lining = Boolean.TRUE.equals(info.getLining());
  163 + final boolean queueable = Boolean.TRUE.equals(info.getQueueable());
  164 + final boolean noInvolved = Boolean.TRUE.equals(info.getNoInvolved());
  165 +
  166 + final String id = String.valueOf(info.getId());
  167 + if (!queueable || noInvolved) {
  168 + return;
  169 + }
  170 + if (lining) {
  171 + List<String> stringList = Optional.ofNullable(ops.range(0, -1)).orElse(new ArrayList<>());
  172 + boolean isLining = new HashSet<>(stringList).contains(id);
  173 + if (isLining) {
  174 + return;
  175 + }
  176 + }
  177 + info.setLining(Boolean.TRUE);
  178 + pubStandStaffInfoService.saveOrUpdate(info);
  179 + ops.rightPush(id);
  180 + Date ashita = DateUtil.localDateTime2Date(LocalDate.now().plusDays(1).atStartOfDay().plusHours(1L));
  181 + if (!Boolean.TRUE.equals(hasKey)) {
  182 + redisTemplate.expireAt(key, ashita);
  183 + }
  184 + }
  185 +
  186 + private boolean hasAnyTodo(final Long staffId) {
  187 + //todo 跟进系统 跟进结果返回生于的跟进数量并且提供主动查询的接口
  188 + return true;
  189 + }
  190 +
  191 + private PubStandStaffInfo generateStandInfo(UserInfoDTO infoDTO) {
  192 + return new PubStandStaffInfo()
  193 + .setLining(Boolean.FALSE)
  194 + .setQueueable(Boolean.FALSE)
  195 + .setNoInvolved(Boolean.FALSE)
  196 + .setStandRang(PubStandRang.SHOP)
  197 + .setStandType(PubStandType.PUB)
  198 + .setGroupId(infoDTO.getGroupId())
  199 + .setStaffId(infoDTO.getId())
  200 + .setStaffName(infoDTO.getUserName());
  201 + }
  202 +
  203 +
  204 + private String generateKey(final Long groupId) {
  205 + Assert.notNull(groupId, "groupId cannot be null");
  206 + String day = MonthDay.now().toString().replace("-", "");
  207 + return String.format("%s:%s:%s", getKeyPrefix(), day, groupId);
  208 + }
26 209 }
... ...
fw-valhalla-service/src/main/java/cn/fw/valhalla/service/data/PubStandStaffInfoService.java
... ... @@ -3,6 +3,9 @@ package cn.fw.valhalla.service.data;
3 3 import cn.fw.valhalla.domain.db.pub.PubStandStaffInfo;
4 4 import com.baomidou.mybatisplus.extension.service.IService;
5 5  
  6 +import java.util.List;
  7 +import java.util.Optional;
  8 +
6 9  
7 10 /**
8 11 * 公共池站岗人员配置
... ... @@ -14,4 +17,20 @@ import com.baomidou.mybatisplus.extension.service.IService;
14 17 * @date : 2023-03-10 11:43
15 18 */
16 19 public interface PubStandStaffInfoService extends IService<PubStandStaffInfo> {
  20 + /**
  21 + * 查询站岗中的人员
  22 + *
  23 + * @param groupId
  24 + * @return
  25 + */
  26 + List<PubStandStaffInfo> queryLiningStaff(final Long groupId);
  27 +
  28 + /**
  29 + * 通过人员id和集团查询信息
  30 + *
  31 + * @param staffId
  32 + * @param groupId
  33 + * @return
  34 + */
  35 + Optional<PubStandStaffInfo> queryStaffByGroupId(final Long staffId, final Long groupId);
17 36 }
18 37 \ No newline at end of file
... ...
fw-valhalla-service/src/main/java/cn/fw/valhalla/service/data/impl/PubStandStaffInfoServiceImpl.java
... ... @@ -3,9 +3,13 @@ package cn.fw.valhalla.service.data.impl;
3 3 import cn.fw.valhalla.dao.mapper.PubStandStaffInfoMapper;
4 4 import cn.fw.valhalla.domain.db.pub.PubStandStaffInfo;
5 5 import cn.fw.valhalla.service.data.PubStandStaffInfoService;
  6 +import com.baomidou.mybatisplus.core.toolkit.Wrappers;
6 7 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
7 8 import org.springframework.stereotype.Service;
8 9  
  10 +import java.util.List;
  11 +import java.util.Optional;
  12 +
9 13  
10 14 /**
11 15 * 公共池人员配置
... ... @@ -18,4 +22,23 @@ import org.springframework.stereotype.Service;
18 22 */
19 23 @Service
20 24 public class PubStandStaffInfoServiceImpl extends ServiceImpl<PubStandStaffInfoMapper, PubStandStaffInfo> implements PubStandStaffInfoService {
  25 +
  26 + @Override
  27 + public List<PubStandStaffInfo> queryLiningStaff(Long groupId) {
  28 + return list(Wrappers.<PubStandStaffInfo>lambdaQuery()
  29 + .eq(PubStandStaffInfo::getQueueable, Boolean.TRUE)
  30 + .eq(PubStandStaffInfo::getNoInvolved, Boolean.FALSE)
  31 + .eq(PubStandStaffInfo::getLining, Boolean.TRUE)
  32 + .eq(PubStandStaffInfo::getGroupId, groupId)
  33 + );
  34 + }
  35 +
  36 + @Override
  37 + public Optional<PubStandStaffInfo> queryStaffByGroupId(Long staffId, Long groupId) {
  38 + PubStandStaffInfo info = getOne(Wrappers.<PubStandStaffInfo>lambdaQuery()
  39 + .eq(PubStandStaffInfo::getStaffId, staffId)
  40 + .eq(PubStandStaffInfo::getGroupId, groupId)
  41 + );
  42 + return Optional.ofNullable(info);
  43 + }
21 44 }
22 45 \ No newline at end of file
... ...