记得上下班打卡 | git大法好,push需谨慎

Commit 4ed2482d authored by wangyifan's avatar wangyifan

拼团机器人:platform、kylin、slime、order、executor-main

parent 931332ff
......@@ -105,4 +105,36 @@ public class GroupBuyStatusConst {
private MemberStatus() {
}
}
/**
* 拼团机器人计划状态(团文档 robotPlanStatus)。
*/
public static class RobotPlanStatus {
/** 未处理 */
public static final Integer NONE = 0;
/** 已预约 */
public static final Integer SCHEDULED = 1;
/** 已完成 */
public static final Integer DONE = 2;
/** 已取消 */
public static final Integer CANCELLED = 3;
private RobotPlanStatus() {
}
}
/**
* 拼团机器人单条任务状态(robotTasks.status)。
*/
public static class RobotTaskStatus {
/** 待执行 */
public static final Integer PENDING = 0;
/** 已执行 */
public static final Integer DONE = 1;
/** 已取消 */
public static final Integer CANCELLED = 2;
private RobotTaskStatus() {
}
}
}
......@@ -45,6 +45,8 @@ public class KylinRedisConst {
public static final String GROUP_BUY_SMS_ENABLED = "kylin:groupBuy:sms:enabled";
/** 拼团 1h 提醒防重:key + groupOrderId */
public static final String GROUP_BUY_REMIND_1H = "kylin:groupBuy:remind1h:";
/** 拼团机器人总开关:无 key 或非 0/false 则开启;SET 为 0/false 关停 */
public static final String GROUP_BUY_ROBOT_ENABLED = "kylin:groupBuy:robot:enabled";
public static final String CAMERA_DEVICES = "kylin:cameraDevices:list";
public static final String CAMERA_DEVICES_FIELD = "kylin:cameraDevices:fieldId:";
......
......@@ -39,4 +39,7 @@ public class KylinGroupBuyMemberVo implements Serializable {
@ApiModelProperty(value = "头像")
private String avatar;
@ApiModelProperty(value = "是否机器人 1是(C端不返回;B端商家返回)")
private Integer isRobot;
}
......@@ -55,6 +55,12 @@ public class KylinGroupBuyOrderVo implements Serializable {
@ApiModelProperty(value = "是否已发送到期前1小时提醒 1已发 0/null未发")
private Integer remind1hSent;
@ApiModelProperty(value = "机器人计划状态 0未处理 1已预约 2已完成 3已取消(内部字段,C端不返回)")
private Integer robotPlanStatus;
@ApiModelProperty(value = "机器人预约任务(内部字段,C端不返回)")
private List<KylinGroupBuyRobotTaskVo> robotTasks;
@ApiModelProperty(value = "成员")
private List<KylinGroupBuyMemberVo> members;
}
......@@ -26,4 +26,7 @@ public class KylinGroupBuyPartnerOrderMemberVo implements Serializable {
@ApiModelProperty(value = "成员状态 -1待支付 0已加入 1已退款 2已取消")
private Integer status;
@ApiModelProperty(value = "是否机器人 1是")
private Integer isRobot;
}
package com.liquidnet.service.kylin.dto.vo.groupbuy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 拼团机器人预约任务(挂在团文档 robotTasks 上,仅内部调度使用)。
*/
@Data
public class KylinGroupBuyRobotTaskVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "计划执行时间 yyyyMMddHHmmss")
private String executeAt;
@ApiModelProperty(value = "状态 0待执行 1已执行 2已取消")
private Integer status;
}
package com.liquidnet.service.kylin.utils;
import com.liquidnet.service.kylin.constant.GroupBuyStatusConst;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyMemberVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyOrderVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyRobotTaskVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
/**
* 拼团机器人通用判定与预约清理(order / kylin / platform 共用)。
*/
public final class GroupBuyRobotUtil {
public static final String USER_ID_PREFIX = "robot_";
public static final String ORDER_TICKETS_ID_PREFIX = "ROBOT_";
private GroupBuyRobotUtil() {
}
public static boolean isRobot(KylinGroupBuyMemberVo member) {
if (member == null) {
return false;
}
if (Integer.valueOf(1).equals(member.getIsRobot())) {
return true;
}
if (StringUtils.startsWith(member.getOrderTicketsId(), ORDER_TICKETS_ID_PREFIX)) {
return true;
}
return StringUtils.startsWith(member.getUserId(), USER_ID_PREFIX);
}
public static boolean isRobotPlanPending(KylinGroupBuyOrderVo group) {
if (group == null) {
return false;
}
Integer status = group.getRobotPlanStatus();
return status == null || GroupBuyStatusConst.RobotPlanStatus.NONE.equals(status);
}
/** 仅取消未执行任务,不改 robotPlanStatus。 */
public static void cancelPendingTasks(KylinGroupBuyOrderVo group) {
if (group == null || CollectionUtils.isEmpty(group.getRobotTasks())) {
return;
}
for (KylinGroupBuyRobotTaskVo task : group.getRobotTasks()) {
if (task == null) {
continue;
}
if (task.getStatus() == null
|| GroupBuyStatusConst.RobotTaskStatus.PENDING.equals(task.getStatus())) {
task.setStatus(GroupBuyStatusConst.RobotTaskStatus.CANCELLED);
}
}
}
/**
* 取消未执行任务;计划若非已完成则置为已取消。
*/
public static void cancelPendingPlan(KylinGroupBuyOrderVo group) {
if (group == null) {
return;
}
cancelPendingTasks(group);
if (!GroupBuyStatusConst.RobotPlanStatus.DONE.equals(group.getRobotPlanStatus())) {
group.setRobotPlanStatus(GroupBuyStatusConst.RobotPlanStatus.CANCELLED);
}
}
}
......@@ -63,4 +63,7 @@ public interface FeignPlatformTaskClient {
@PostMapping("platform/groupBuy/remind1h")
ResponseDto<Boolean> groupBuyRemind1h();
@PostMapping("platform/groupBuy/robot")
ResponseDto<Boolean> groupBuyRobot();
}
......@@ -152,4 +152,14 @@ public class KylinTaskHandler {
}
}
@XxlJob(value = "sev-platform:groupBuyRobot")
public void groupBuyRobot() {
try {
XxlJobHelper.handleSuccess("结果:" + feignPlatformTaskClient.groupBuyRobot().getData());
} catch (Exception e) {
XxlJobHelper.log(e);
XxlJobHelper.handleFail();
}
}
}
......@@ -19,6 +19,7 @@ import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderTicketVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinPerformanceVo;
import com.liquidnet.service.kylin.service.IKylinGroupBuyOrderService;
import com.liquidnet.service.kylin.utils.DataUtils;
import com.liquidnet.service.kylin.utils.GroupBuyRobotUtil;
import com.liquidnet.service.kylin.utils.QueueUtils;
import com.mongodb.BasicDBObject;
import lombok.extern.slf4j.Slf4j;
......@@ -117,7 +118,10 @@ public class KylinGroupBuyOrderServiceImpl implements IKylinGroupBuyOrderService
b.setUserId(m.getUserId());
b.setAvatar(resolveAvatar(m.getUserId(), m.getAvatar(), avatarCache));
b.setNickname(m.getNickname());
// C 端不暴露机器人占位票单号
if (!GroupBuyRobotUtil.isRobot(m)) {
b.setOrderTicketsId(m.getOrderTicketsId());
}
memberUserList.add(b);
}
vo.setMemberBriefs(memberUserList);
......@@ -175,7 +179,10 @@ public class KylinGroupBuyOrderServiceImpl implements IKylinGroupBuyOrderService
mu.setUserId(m.getUserId());
mu.setNickname(m.getNickname());
mu.setAvatar(resolveAvatar(m.getUserId(), m.getAvatar(), avatarCache));
// C 端不暴露机器人占位票单号 / isRobot
if (!GroupBuyRobotUtil.isRobot(m)) {
mu.setOrderTicketsId(m.getOrderTicketsId());
}
members.add(mu);
}
vo.setMembers(members);
......@@ -210,10 +217,13 @@ public class KylinGroupBuyOrderServiceImpl implements IKylinGroupBuyOrderService
}
order.setStatus(GroupBuyStatusConst.OrderStatus.CANCELLED);
order.setUpdatedAt(now());
GroupBuyRobotUtil.cancelPendingPlan(order);
if (!CollectionUtils.isEmpty(order.getMembers())) {
for (KylinGroupBuyMemberVo member : order.getMembers()) {
if (GroupBuyStatusConst.MemberStatus.JOINED.equals(member.getStatus())) {
if (!GroupBuyRobotUtil.isRobot(member)) {
joinedToRefund.add(member);
}
member.setStatus(GroupBuyStatusConst.MemberStatus.REFUNDED);
} else if (GroupBuyStatusConst.MemberStatus.PENDING_PAY.equals(member.getStatus())) {
member.setStatus(GroupBuyStatusConst.MemberStatus.CANCELLED);
......
......@@ -17,6 +17,7 @@ import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyDiscountVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyMemberVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyOrderVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderTicketVo;
import com.liquidnet.service.kylin.utils.GroupBuyRobotUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -395,6 +396,8 @@ public class GroupBuyOrderHelper {
if (group.getCurrentCount() != null && group.getTargetCount() != null
&& group.getCurrentCount() >= group.getTargetCount()) {
group.setStatus(GroupBuyStatusConst.OrderStatus.SUCCESS);
GroupBuyRobotUtil.cancelPendingTasks(group);
group.setRobotPlanStatus(GroupBuyStatusConst.RobotPlanStatus.CANCELLED);
becameSuccess = true;
}
group.setUpdatedAt(now);
......
......@@ -36,4 +36,10 @@ public class PlatformGroupBuyTaskController {
public ResponseDto<Boolean> remind1h() {
return ResponseDto.success(groupBuyTaskService.remind1h());
}
@PostMapping("robot")
@ApiOperation("拼团机器人预约与执行任务")
public ResponseDto<Boolean> robot() {
return ResponseDto.success(groupBuyTaskService.robot());
}
}
......@@ -13,7 +13,9 @@ import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyMemberVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyOrderVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderTicketEntitiesVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderTicketVo;
import com.liquidnet.service.kylin.utils.GroupBuyRobotUtil;
import com.liquidnet.service.platform.utils.GroupBuyPlatformHelper;
import com.liquidnet.service.platform.utils.GroupBuyRobotHelper;
import com.liquidnet.service.platform.utils.GroupBuySmsSender;
import com.liquidnet.service.platform.utils.QueueUtils;
import lombok.extern.slf4j.Slf4j;
......@@ -46,10 +48,74 @@ public class PlatformGroupBuyTaskServiceImpl {
private GroupBuyPlatformHelper groupBuyPlatformHelper;
@Autowired
private GroupBuySmsSender groupBuySmsSender;
@Autowired
private GroupBuyRobotHelper groupBuyRobotHelper;
@Value("${liquidnet.service.kylin.url}")
private String kylinUrl;
/**
* 拼团机器人:先预约,再到点执行。
*/
public boolean robot() {
if (!groupBuyRobotHelper.isEnabled()) {
log.info("拼团机器人任务跳过:开关关闭");
return true;
}
int scheduled = 0;
int executed = 0;
int skipped = 0;
List<KylinGroupBuyOrderVo> scheduleList = groupBuyRobotHelper.findScheduleCandidates();
for (KylinGroupBuyOrderVo order : scheduleList) {
String groupOrderId = order.getGroupOrderId();
if (!groupBuyPlatformHelper.tryLock(groupOrderId)) {
skipped++;
continue;
}
try {
KylinGroupBuyOrderVo fresh = groupBuyPlatformHelper.loadFromMongo(groupOrderId);
if (fresh == null) {
skipped++;
continue;
}
Integer before = fresh.getRobotPlanStatus();
groupBuyRobotHelper.tryScheduleOne(fresh);
KylinGroupBuyOrderVo after = groupBuyPlatformHelper.loadFromMongo(groupOrderId);
if (after != null && after.getRobotPlanStatus() != null
&& !after.getRobotPlanStatus().equals(before == null
? GroupBuyStatusConst.RobotPlanStatus.NONE : before)) {
scheduled++;
}
} catch (Exception e) {
log.error("拼团机器人预约失败 groupOrderId={}", groupOrderId, e);
} finally {
groupBuyPlatformHelper.unlock(groupOrderId);
}
}
String now = now();
List<KylinGroupBuyOrderVo> executeList = groupBuyRobotHelper.findExecuteCandidates(now);
for (KylinGroupBuyOrderVo order : executeList) {
String groupOrderId = order.getGroupOrderId();
if (!groupBuyPlatformHelper.tryLock(groupOrderId)) {
skipped++;
continue;
}
try {
KylinGroupBuyOrderVo fresh = groupBuyPlatformHelper.loadFromMongo(groupOrderId);
groupBuyRobotHelper.tryExecuteOne(fresh, now);
executed++;
} catch (Exception e) {
log.error("拼团机器人执行失败 groupOrderId={}", groupOrderId, e);
} finally {
groupBuyPlatformHelper.unlock(groupOrderId);
}
}
log.info("拼团机器人任务结束 scheduleHit={}, scheduled={}, executeHit={}, executed={}, skipped={}",
scheduleList == null ? 0 : scheduleList.size(), scheduled,
executeList == null ? 0 : executeList.size(), executed, skipped);
return true;
}
public boolean timeout() {
String now = now();
Query query = Query.query(Criteria.where("status").is(GroupBuyStatusConst.OrderStatus.PENDING)
......@@ -78,6 +144,7 @@ public class PlatformGroupBuyTaskServiceImpl {
}
fresh.setStatus(GroupBuyStatusConst.OrderStatus.FAILED);
fresh.setUpdatedAt(now);
GroupBuyRobotUtil.cancelPendingPlan(fresh);
// 失败短信需在 JOINED 改成 REFUNDED 之前发送
try {
groupBuySmsSender.notifyFailed(fresh);
......@@ -90,7 +157,9 @@ public class PlatformGroupBuyTaskServiceImpl {
if (GroupBuyStatusConst.MemberStatus.PENDING_PAY.equals(member.getStatus())) {
member.setStatus(GroupBuyStatusConst.MemberStatus.CANCELLED);
} else if (GroupBuyStatusConst.MemberStatus.JOINED.equals(member.getStatus())) {
if (!GroupBuyRobotUtil.isRobot(member)) {
joinedOrderTicketIds.add(member.getOrderTicketsId());
}
member.setStatus(GroupBuyStatusConst.MemberStatus.REFUNDED);
}
}
......
......@@ -11,9 +11,13 @@ import com.liquidnet.service.base.constant.MQConst;
import com.liquidnet.service.base.constant.RedisKeyExpireConst;
import com.liquidnet.service.kylin.constant.GroupBuyStatusConst;
import com.liquidnet.service.kylin.constant.KylinRedisConst;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyActivityVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyMemberVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyOrderVo;
import com.liquidnet.service.kylin.dto.vo.middle.KylinTicketTimesVo;
import com.liquidnet.service.kylin.dto.vo.middle.KylinTicketVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderTicketVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinPerformanceVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -33,7 +37,7 @@ import java.util.Map;
import java.util.Set;
/**
* 拼团失败 / 1h 提醒短信:入队 {@link MQConst.KylinQueue#SMS_NOTICE}(与购票短信一致,异步)。
* 拼团成功 / 失败 / 1h 提醒短信:入队 {@link MQConst.KylinQueue#SMS_NOTICE}(与购票短信一致,异步)。
*/
@Component
@Slf4j
......@@ -45,6 +49,24 @@ public class GroupBuySmsSender {
private RedisDataSourceUtil redisDataSourceUtil;
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private DataUtils dataUtils;
public void notifySuccess(KylinGroupBuyOrderVo group) {
if (group == null || !isSmsEnabled()) {
return;
}
List<String> phones = collectPhones(group, true, false);
if (phones.isEmpty()) {
log.info("拼团成功短信无有效手机号 groupOrderId={}", group.getGroupOrderId());
return;
}
ObjectNode params = buildSuccessParams(group);
for (String phone : phones) {
enqueueSms(phone, SmsEnum.ADTemplate.SMS_510540063.name(), params,
group.getGroupOrderId(), "success");
}
}
public void notifyFailed(KylinGroupBuyOrderVo group) {
if (group == null || !isSmsEnabled()) {
......@@ -143,6 +165,57 @@ public class GroupBuySmsSender {
return ticket == null ? null : ticket.getUserMobile();
}
private ObjectNode buildSuccessParams(KylinGroupBuyOrderVo group) {
ObjectNode node = JsonUtils.OM().createObjectNode();
String name = "";
String time = "";
String ticket = "";
try {
KylinGroupBuyActivityVo activity = mongoTemplate.findOne(
Query.query(Criteria.where("activityId").is(group.getActivityId())),
KylinGroupBuyActivityVo.class, KylinGroupBuyActivityVo.class.getSimpleName());
if (activity != null && StringUtils.isNotBlank(activity.getPerformanceId())) {
KylinPerformanceVo p = dataUtils.getPerformanceVo(activity.getPerformanceId());
if (p != null) {
name = p.getTitle() == null ? "" : p.getTitle();
String[] titles = resolveTimeTicketTitle(p, group.getTimeId(), group.getTicketId());
time = titles[0];
ticket = titles[1];
}
}
} catch (Exception e) {
log.warn("拼团成功短信组装变量失败 groupOrderId={}", group.getGroupOrderId(), e);
}
node.put("name", name);
node.put("time", time);
node.put("ticket", ticket);
return node;
}
private String[] resolveTimeTicketTitle(KylinPerformanceVo p, String timeId, String ticketId) {
String timeTitle = "";
String ticketTitle = "";
if (p.getTicketTimeList() == null || timeId == null) {
return new String[]{timeTitle, ticketTitle};
}
for (KylinTicketTimesVo t : p.getTicketTimeList()) {
if (!timeId.equals(t.getTimeId())) {
continue;
}
timeTitle = t.getTitle() == null ? "" : t.getTitle();
if (t.getTicketList() != null && ticketId != null) {
for (KylinTicketVo tk : t.getTicketList()) {
if (ticketId.equals(tk.getTicketsId())) {
ticketTitle = tk.getTitle() == null ? "" : tk.getTitle();
break;
}
}
}
break;
}
return new String[]{timeTitle, ticketTitle};
}
private String resolveHours(KylinGroupBuyOrderVo group) {
try {
if (StringUtils.isNotBlank(group.getCreatedAt()) && StringUtils.isNotBlank(group.getExpiresAt())) {
......
package com.liquidnet.service.platform.utils;
import com.liquidnet.commons.lang.util.CollectionUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Random;
/**
* 随机昵称(逻辑对齐 adam {@code com.liquidnet.service.adam.util.NknameUtil})。
*/
public class NknameUtil {
private static final Logger log = LoggerFactory.getLogger(NknameUtil.class);
private static int bound;
private static final List<String> nknameList;
private static final String nknamePrefix = "宇航员";
private static final Random random = new Random();
static {
nknameList = reload();
}
public static List<String> reload() {
List<String> list = CollectionUtil.arrayListString();
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;
try {
inputStream = NknameUtil.class.getClassLoader().getResourceAsStream("META-FIL/library_nickname.csv");
inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
bufferedReader = new BufferedReader(inputStreamReader);
String line;
while ((line = bufferedReader.readLine()) != null) {
int l = StringUtils.length(line);
if (l == 0) {
break;
}
if (l > 11) {
line = line.substring(0, 11);
}
list.add(line);
}
if (!CollectionUtils.isEmpty(list)) {
bound = list.size();
}
} catch (IOException e) {
log.error("Read CSV[library_nickname.csv] failure.", e);
} finally {
try {
if (null != bufferedReader) bufferedReader.close();
if (null != inputStreamReader) inputStreamReader.close();
if (null != inputStream) inputStream.close();
} catch (IOException e) {
log.error("Close stream failure.", e);
}
}
log.info("Load nkname init:{}~{}", list.size(), bound);
return list;
}
public static String randomNkname() {
return nknamePrefix + nknameList.get(random.nextInt(bound));
}
}
......@@ -358,6 +358,7 @@ public class KylinGroupBuyPartnerServiceImpl implements IKylinGroupBuyPartnerSer
item.setNickname(m.getNickname());
item.setAvatar(m.getAvatar());
item.setStatus(m.getStatus());
item.setIsRobot(m.getIsRobot());
boolean isLeader = order.getLeaderUserId() != null && order.getLeaderUserId().equals(m.getUserId());
item.setIsLeader(isLeader ? 1 : 0);
members.add(item);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment