Commit cca930797a069277c45cca0964d84d8089895c67
1 parent
80738b3b
补全join和exit的逻辑
Showing
6 changed files
with
299 additions
and
5 deletions
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
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 | ... | ... |