叫号接口

This commit is contained in:
张松 2025-02-21 14:44:34 +08:00
parent 0e7475d894
commit 14a65c4787
31 changed files with 1594 additions and 1 deletions

View File

@ -0,0 +1,176 @@
package com.czg.controller.admin;
import com.czg.account.dto.calltable.*;
import com.czg.account.entity.CallConfig;
import com.czg.account.entity.CallQueue;
import com.czg.account.entity.CallTable;
import com.czg.account.service.CallTableService;
import com.czg.annotation.SaAdminCheckPermission;
import com.czg.resp.CzgResult;
import com.czg.sa.StpKit;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 叫号管理
*
* @author Administrator
*/
@RestController
@RequestMapping("/admin/callTable")
@Slf4j
public class CallTableController {
@Resource
private CallTableService callTableService;
/**
* 叫号桌型获取
*
* @param callTableId 叫号桌型id
* @param state 0禁用 1使用
* @return 分页数据
*/
@SaAdminCheckPermission(value = "callTable:list", name = "叫号桌型获取")
@GetMapping
public CzgResult<CallTablePage> get(Long callTableId, Integer state) {
return CzgResult.success(callTableService.get(StpKit.USER.getShopId(), callTableId, state));
}
/**
* 叫号桌型新增
*
* @param addCallTableDTO 新增数据
* @return 是否成功
*/
@SaAdminCheckPermission(value = "callTable:add", name = "叫号桌型添加")
@PostMapping
public CzgResult<Boolean> add(@Validated @RequestBody CallTableDTO addCallTableDTO) {
return CzgResult.success(callTableService.add(StpKit.USER.getShopId(), addCallTableDTO));
}
/**
* 叫号桌型修改
*
* @return 是否成功
*/
@SaAdminCheckPermission(value = "callTable:edit", name = "叫号桌型修改")
@PutMapping
public CzgResult<Boolean> update(@Validated @RequestBody UpdateCallTableDTO callTableDTO) {
return CzgResult.success(callTableService.update(StpKit.USER.getShopId(), callTableDTO));
}
/**
* 叫号桌型删除
*
* @return 是否成功
*/
@SaAdminCheckPermission(value = "callTable:del", name = "叫号桌型删除")
@DeleteMapping
public CzgResult<Boolean> delete(@Validated @RequestBody BaseCallTableDTO baseCallTableDTO) {
return CzgResult.success(callTableService.remove(new QueryWrapper().eq(CallTable::getShopId, StpKit.USER.getShopId()).eq(CallTable::getId, baseCallTableDTO.getCallTableId())));
}
/**
* 获取叫号号码
*/
@SaAdminCheckPermission(value = "callTable:takeNumber", name = "获取叫号号码")
@PostMapping("takeNumber")
public CzgResult<CallTableNumDTO> takeNumber(@Validated @RequestBody TakeNumberDTO takeNumberDTO) {
return CzgResult.success(callTableService.takeNumber(StpKit.USER.getShopId(), takeNumberDTO));
}
/**
* 获取叫号页面二维码
*
* @param callTableId 叫号桌型id
* @return base64编码
*/
@SaAdminCheckPermission(value = "callTable:takeNumberCode", name = "获取叫号页面二维码")
@GetMapping("takeNumberCode")
public CzgResult<String> takeNumberCode(@RequestParam Integer callTableId) {
return CzgResult.success(callTableService.takeNumberCode(StpKit.USER.getShopId(), callTableId));
}
/**
* 执行叫号
*
* @return 0失败 1成功 -1用户未订阅
*/
@SaAdminCheckPermission(value = "callTable:call", name = "执行叫号")
@PostMapping("call")
public CzgResult<Integer> call(@Validated @RequestBody CallQueueDTO callQueueDTO) {
try {
return CzgResult.success(callTableService.call(StpKit.USER.getShopId(), callQueueDTO));
} catch (Exception e) {
log.error("异常", e);
return CzgResult.success(1);
}
}
/**
* 修改叫号队列状态
*
* @return 是否成功
*/
@SaAdminCheckPermission(value = "callTable:updateState", name = "修改叫号队列状态")
@PutMapping("updateState")
public CzgResult<Boolean> confirm(@Validated @RequestBody UpdateCallQueueDTO updateCallQueueDTO) {
return CzgResult.success(callTableService.updateInfo(StpKit.USER.getShopId(), updateCallQueueDTO));
}
/**
* 获取叫号队列
*
* @param callTableId 桌型id
* @param state 状态 -1已取消 0排队中 1叫号中 2已入座 3 已过号
* @return 分页数据
*/
@SaAdminCheckPermission(value = "callTable:queue:list", name = "获取叫号队列")
@GetMapping("queue")
public CzgResult<Page<CallQueue>> getQueue(Long callTableId, Integer state) {
return CzgResult.success(callTableService.getQueue(StpKit.USER.getShopId(), callTableId, state));
}
/**
* 获取叫号记录列表
*
* @param callTableId 桌型id
* @return 数据
*/
@SaAdminCheckPermission(value = "callTable:callRecord:list", name = "获取叫号记录列表")
@GetMapping("callRecord")
public CzgResult<Page<CallRecordVO>> getCallRecord(Integer callTableId) {
return CzgResult.success(callTableService.getCallRecord(StpKit.USER.getShopId(), callTableId));
}
/**
* 获取叫号配置
*
* @return 配置信息
*/
@SaAdminCheckPermission(value = "callTable:config:list", name = "获取叫号配置")
@GetMapping("config")
public CzgResult<CallConfig> getConfig() {
return CzgResult.success(callTableService.getConfig(StpKit.USER.getShopId()));
}
/**
* 修改叫号配置
*
* @return 是否成功
*/
@SaAdminCheckPermission(value = "callTable:config:edit", name = "修改叫号配置")
@PutMapping("config")
public CzgResult<Boolean> updateConfig(@RequestBody UpdateConfigDTO configDTO) {
return CzgResult.success(callTableService.updateConfig(StpKit.USER.getShopId(), configDTO));
}
}

View File

@ -6,6 +6,8 @@ package com.czg.config;
* @author Administrator
*/
public interface RedisCst {
String LOCK_KEY = "LOCK:";
String LOGIN_CODE = "login:code:";
String SYS_LOG_KEY = "sys:log:";
@ -14,4 +16,21 @@ public interface RedisCst {
String SMS_CODE = "sms:code:";
// 店铺会员动态支付码
String SHOP_USER_DYNAMIC_CODE = "shop:user:dynamic:code:";
// 排队取号全局号码
String TABLE_CALL_NUMBER = "TABLE_CALL_NUMBER:";
static String getLockKey(String sign, Object... args) {
StringBuilder key = new StringBuilder(LOCK_KEY + ":" + sign + ":");
for (Object arg : args) {
if (arg != null) {
key.append(":").append(arg);
}
}
return key.toString();
}
static String getTableCallNumKey(Long shopId, Long callTableId) {
return TABLE_CALL_NUMBER + shopId + ":" + callTableId;
}
}

View File

@ -0,0 +1,17 @@
package com.czg.account.dto.calltable;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author Administrator
*/
@Data
public class BaseCallTableDTO {
/**
* 叫号桌型id
*/
@NotNull(message = "台桌id不为空")
private Integer callTableId;
}

View File

@ -0,0 +1,17 @@
package com.czg.account.dto.calltable;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author Administrator
*/
@Data
public class CallQueueDTO{
/**
* 叫号队列id
*/
@NotNull
private Integer callQueueId;
}

View File

@ -0,0 +1,17 @@
package com.czg.account.dto.calltable;
import com.czg.account.entity.CallQueue;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author Administrator
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class CallQueueListDTO extends CallQueue {
/**
* 等待人数
*/
private Long waitingCount;
}

View File

@ -0,0 +1,22 @@
package com.czg.account.dto.calltable;
import com.czg.account.entity.CallQueue;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author Administrator
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class CallRecordVO extends CallQueue {
/**
* 备注
*/
private String note;
/**
* 已过秒数
*/
private Long sinceAt;
}

View File

@ -0,0 +1,55 @@
package com.czg.account.dto.calltable;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author Administrator
*/
@Data
public class CallTableDTO {
/**
* 台桌名称
*/
@NotEmpty(message = "台桌名称不为空")
private String name;
/**
* 备注
*/
private String note;
/**
* 等待时间
*/
@NotNull(message = "等待时间不为空")
@Min(1)
private Integer waitTime;
/**
* 前缀
*/
@NotBlank(message = "前缀不为空")
private String prefix;
/**
* 起始号码
*/
@NotNull(message = "起始号码不为空")
@Min(1)
private Integer start;
/**
* 临近几桌提醒
*/
@NotNull(message = "最近号码不为空")
@Min(1)
private Integer nearNum;
/**
* 是否顺延
*/
private Integer isPostpone;
/**
* 顺延数量
*/
private Integer postponeNum;
}

View File

@ -0,0 +1,17 @@
package com.czg.account.dto.calltable;
import com.czg.account.entity.CallTable;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author Administrator
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class CallTableInfoDTO extends CallTable {
/**
* 已经排号人数
*/
private long totalCount;
}

View File

@ -0,0 +1,24 @@
package com.czg.account.dto.calltable;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author Administrator
*/
@Data
@Accessors(chain = true)
public class CallTableNumDTO {
/**
* 桌型名称
*/
private String tableName;
/**
* 桌型备注
*/
private String tableNote;
/**
* 号码
*/
private String callNum;
}

View File

@ -0,0 +1,18 @@
package com.czg.account.dto.calltable;
import com.czg.account.entity.CallTable;
import com.mybatisflex.core.paginate.Page;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author Administrator
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class CallTablePage extends Page<CallTable> {
/**
* 总数
*/
private long totalCount;
}

View File

@ -0,0 +1,33 @@
package com.czg.account.dto.calltable;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author Administrator
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class TakeNumberDTO extends BaseCallTableDTO{
/**
* 对应小程序用户id
*/
private Integer userId;
/**
* 手机号
*/
@NotEmpty
@Pattern(regexp = "^1\\d{10}$|^(0\\d{2,3}-?|\\(0\\d{2,3}\\))?[1-9]\\d{4,7}(-\\d{1,8})?$",message = "手机号码格式错误")
private String phone;
/**
* 备注
*/
private String note;
/**
* 姓名
*/
private String name;
}

View File

@ -0,0 +1,21 @@
package com.czg.account.dto.calltable;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
/**
* @author Administrator
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Getter
public class UpdateCallQueueDTO extends CallQueueDTO{
/**
* 状态 -1已取消 0排队中 1叫号中 2已入座 3 已过号
*/
@NotNull
private Integer state;
}

View File

@ -0,0 +1,45 @@
package com.czg.account.dto.calltable;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author Administrator
*/
@Data
public class UpdateCallTableDTO{
/**
* 桌型id
*/
@NotNull(message = "台桌id不为空")
private Integer callTableId;
/**
* 桌型名称
*/
private String name;
/**
* 桌型备注
*/
private String note;
/**
* 每桌等待时间
*/
@Min(1)
private Integer waitTime;
/**
* 前缀
*/
private String prefix;
/**
* 叫号起始号码
*/
@Min(1)
private Integer start;
/**
* 临近几桌提醒
*/
@Min(1)
private Integer nearNum;
}

View File

@ -0,0 +1,22 @@
package com.czg.account.dto.calltable;
import lombok.Data;
/**
* @author Administrator
*/
@Data
public class UpdateConfigDTO {
/**
* 是否线上取号
*/
private Integer isOnline;
/**
* 背景图片
*/
private String bgCover;
/**
* 临近几桌提醒
*/
private Integer nearNum;
}

View File

@ -0,0 +1,82 @@
package com.czg.account.entity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.io.Serial;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 叫号配置表 实体类
*
* @author zs
* @since 2025-02-21
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table("tb_call_config")
public class CallConfig implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Id(keyType = KeyType.Auto)
private Long id;
/**
* 小程序页面地址
*/
private String pageAddress;
/**
* 线上取号 1是 0否
*/
private Integer isOnline;
/**
* 背景图片
*/
private String bgCover;
/**
* 成功提示
*/
private String successMsg;
/**
* 临近提示
*/
private String nearMsg;
/**
* 过号提示
*/
private String callingMsg;
/**
* 店铺id
*/
private Long shopId;
/**
* 临近几桌提醒
*/
private Integer nearNum;
@Column(onInsertValue = "now()")
private LocalDateTime createTime;
@Column(onInsertValue = "now()", onUpdateValue = "now()")
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,126 @@
package com.czg.account.entity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.io.Serial;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 叫号排号队列表 实体类
*
* @author zs
* @since 2025-02-21
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table("tb_call_queue")
public class CallQueue implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Id(keyType = KeyType.Auto)
private Long id;
private Long userId;
private String openId;
/**
* 叫号台桌类型id
*/
private Long callTableId;
/**
* 手机号
*/
private String phone;
/**
* 姓名
*/
private String name;
/**
* 店铺名称
*/
private String shopName;
/**
* 店铺id
*/
private Long shopId;
/**
* -1已取消 0排队中 1叫号中 2已入座 3 已过号
*/
private Integer state;
/**
* 订阅提醒 0未订阅 1已订阅
*/
private Integer subState;
/**
* 备注
*/
private String note;
/**
* 叫号号码
*/
private String callNum;
/**
* 创建年月日
*/
private String createDay;
/**
* 是否已经顺延 0 未顺延 1已顺延一次 2顺延一次仍然过号
*/
private Integer isPostpone;
/**
* 排号时间
*/
@Column(onInsertValue = "now()")
private LocalDateTime createTime;
/**
* 叫号时间
*/
private LocalDateTime callTime;
/**
* 叫号次数
*/
private Integer callCount;
/**
* 过号时间
*/
private LocalDateTime passTime;
/**
* 取消时间
*/
private LocalDateTime cancelTime;
/**
* 确认时间
*/
private LocalDateTime confirmTime;
}

View File

@ -0,0 +1,97 @@
package com.czg.account.entity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.io.Serial;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 叫号桌型表 实体类
*
* @author zs
* @since 2025-02-21
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table("tb_call_table")
public class CallTable implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Id(keyType = KeyType.Auto)
private Long id;
/**
* 店铺id
*/
private Long shopId;
/**
* 名称
*/
private String name;
/**
* 描述
*/
private String note;
/**
* 等待时间分钟
*/
private Integer waitTime;
/**
* 前缀
*/
private String prefix;
/**
* 起始号码
*/
private Integer start;
/**
* 临近几桌提醒
*/
private Integer nearNum;
/**
* 0禁用 1使用
*/
private Integer state;
/**
* 二维码地址
*/
private String qrcode;
/**
* 顺延号码数量
*/
private Integer postponeNum;
/**
* 顺延号码数量
*/
private Integer isPostpone;
@Column(onInsertValue = "now()")
private LocalDateTime createTime;
@Column(onInsertValue = "now()", onUpdateValue = "now()")
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,14 @@
package com.czg.account.service;
import com.mybatisflex.core.service.IService;
import com.czg.account.entity.CallConfig;
/**
* 叫号配置表 服务层
*
* @author zs
* @since 2025-02-21
*/
public interface CallConfigService extends IService<CallConfig> {
}

View File

@ -0,0 +1,14 @@
package com.czg.account.service;
import com.mybatisflex.core.service.IService;
import com.czg.account.entity.CallQueue;
/**
* 叫号排号队列表 服务层
*
* @author zs
* @since 2025-02-21
*/
public interface CallQueueService extends IService<CallQueue> {
}

View File

@ -0,0 +1,39 @@
package com.czg.account.service;
import com.czg.account.dto.calltable.*;
import com.czg.account.entity.CallConfig;
import com.czg.account.entity.CallQueue;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.service.IService;
import com.czg.account.entity.CallTable;
/**
* 叫号桌型表 服务层
*
* @author zs
* @since 2025-02-21
*/
public interface CallTableService extends IService<CallTable> {
CallTablePage get(Long shopId, Long callTableId, Integer state);
boolean add(Long shopId, CallTableDTO addCallTableDTO);
boolean update(Long shopId, UpdateCallTableDTO callTableDTO);
CallTableNumDTO takeNumber(Long shopId, TakeNumberDTO takeNumberDTO);
String takeNumberCode(Long shopId, Integer callTableId);
Integer call(Long shopId, CallQueueDTO callQueueDTO);
boolean updateInfo(Long shopId, UpdateCallQueueDTO updateCallQueueDTO);
Page<CallQueue> getQueue(Long shopId, Long callTableId, Integer state);
Page<CallRecordVO> getCallRecord(Long shopId, Integer callTableId);
CallConfig getConfig(Long shopId);
boolean updateConfig(Long shopId, UpdateConfigDTO configDTO);
}

View File

@ -0,0 +1,14 @@
package com.czg.service.account.mapper;
import com.mybatisflex.core.BaseMapper;
import com.czg.account.entity.CallConfig;
/**
* 叫号配置表 映射层
*
* @author zs
* @since 2025-02-21
*/
public interface CallConfigMapper extends BaseMapper<CallConfig> {
}

View File

@ -0,0 +1,19 @@
package com.czg.service.account.mapper;
import com.czg.account.dto.calltable.CallRecordVO;
import com.mybatisflex.core.BaseMapper;
import com.czg.account.entity.CallQueue;
import com.mybatisflex.core.paginate.Page;
/**
* 叫号排号队列表 映射层
*
* @author zs
* @since 2025-02-21
*/
public interface CallQueueMapper extends BaseMapper<CallQueue> {
Page<CallRecordVO> selectCallRecord();
long selectCallRecord_COUNT();
}

View File

@ -0,0 +1,14 @@
package com.czg.service.account.mapper;
import com.mybatisflex.core.BaseMapper;
import com.czg.account.entity.CallTable;
/**
* 叫号桌型表 映射层
*
* @author zs
* @since 2025-02-21
*/
public interface CallTableMapper extends BaseMapper<CallTable> {
}

View File

@ -0,0 +1,18 @@
package com.czg.service.account.service.impl;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.czg.account.entity.CallConfig;
import com.czg.account.service.CallConfigService;
import com.czg.service.account.mapper.CallConfigMapper;
import org.springframework.stereotype.Service;
/**
* 叫号配置表 服务层实现
*
* @author zs
* @since 2025-02-21
*/
@Service
public class CallConfigServiceImpl extends ServiceImpl<CallConfigMapper, CallConfig> implements CallConfigService{
}

View File

@ -0,0 +1,18 @@
package com.czg.service.account.service.impl;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.czg.account.entity.CallQueue;
import com.czg.account.service.CallQueueService;
import com.czg.service.account.mapper.CallQueueMapper;
import org.springframework.stereotype.Service;
/**
* 叫号排号队列表 服务层实现
*
* @author zs
* @since 2025-02-21
*/
@Service
public class CallQueueServiceImpl extends ServiceImpl<CallQueueMapper, CallQueue> implements CallQueueService{
}

View File

@ -0,0 +1,537 @@
package com.czg.service.account.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import com.czg.account.dto.calltable.*;
import com.czg.account.entity.*;
import com.czg.account.service.*;
import com.czg.config.RedisCst;
import com.czg.exception.ApiNotPrintException;
import com.czg.resp.CzgResult;
import com.czg.service.account.mapper.CallQueueMapper;
import com.czg.service.account.util.FunUtil;
import com.czg.system.dto.SysParamsDTO;
import com.czg.system.service.SysParamsService;
import com.czg.utils.JoinQueryWrapper;
import com.czg.utils.PageUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.czg.service.account.mapper.CallTableMapper;
import jakarta.annotation.Resource;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
/**
* 叫号桌型表 服务层实现
*
* @author zs
* @since 2025-02-21
*/
@Service
public class CallTableServiceImpl extends ServiceImpl<CallTableMapper, CallTable> implements CallTableService {
// @DubboReference
private SysParamsService sysParamsService;
@Resource
private CallQueueService callQueueService;
@Resource
private ShopUserService shopUserService;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private ShopInfoService shopInfoService;
@Resource
private CallConfigService callConfigService;
@Resource
private CallQueueMapper callQueueMapper;
@Override
public CallTablePage get(Long shopId, Long callTableId, Integer state) {
QueryWrapper query = new QueryWrapper()
.eq(CallTable::getShopId, shopId)
.eq(CallTable::getState, 1);
if (callTableId != null) {
query.eq(CallTable::getId, callTableId);
}
if (state != null) {
query.eq(CallTable::getState, state);
}
Page<CallTable> pageInfo = page(PageUtil.buildPage(), query);
List<CallTable> info = new ArrayList<>();
long totalCount = 0L;
for (CallTable item : pageInfo.getRecords()) {
long count = callQueueService.queryChain()
.eq(CallQueue::getCallTableId, item.getId())
.eq(CallQueue::getShopId, shopId)
.eq(CallQueue::getCreateDay, DateUtil.today())
.in(CallQueue::getState, 0, 1).count();
totalCount += count;
CallTableInfoDTO callTableInfoDTO = BeanUtil.copyProperties(item, CallTableInfoDTO.class);
callTableInfoDTO.setTotalCount(count);
info.add(callTableInfoDTO);
}
CallTablePage callTablePage = BeanUtil.copyProperties(pageInfo, CallTablePage.class);
callTablePage.setRecords(info);
callTablePage.setTotalCount(totalCount);
return callTablePage;
}
@Override
public boolean add(Long shopId, CallTableDTO addCallTableDTO) {
long count = queryChain()
.eq(CallTable::getShopId, shopId)
.and(q -> {
q.eq(CallTable::getName, addCallTableDTO.getName()).or(q1 -> {
q1.eq(CallTable::getPrefix, addCallTableDTO.getPrefix());
});
}).count();
if (count > 0) {
throw new ApiNotPrintException("名称或前缀已存在");
}
CallTable callTable = BeanUtil.copyProperties(addCallTableDTO, CallTable.class);
callTable.setShopId(shopId);
return save(callTable);
}
@Override
public boolean update(Long shopId, UpdateCallTableDTO callTableDTO) {
CallTable callTable = queryChain()
.eq(CallTable::getShopId, shopId)
.eq(CallTable::getId, callTableDTO.getCallTableId()).one();
if (StrUtil.isNotBlank(callTableDTO.getName())) {
long count = queryChain()
.eq(CallTable::getShopId, shopId)
.ne(CallTable::getId, callTableDTO.getCallTableId())
.eq(CallTable::getName, callTableDTO.getName()).count();
if (count > 0) {
throw new ApiNotPrintException("名称已存在");
}
}
if (StrUtil.isNotBlank(callTableDTO.getPrefix())) {
long count = queryChain()
.eq(CallTable::getShopId, shopId)
.ne(CallTable::getId, callTableDTO.getCallTableId())
.eq(CallTable::getPrefix, callTableDTO.getPrefix()).count();
if (count > 0) {
throw new ApiNotPrintException("前缀已存在");
}
}
if (callTable == null) {
throw new ApiNotPrintException("桌型不存在");
}
CallTable newInfo = BeanUtil.copyProperties(callTableDTO, CallTable.class);
newInfo.setId(callTable.getId());
return updateById(newInfo);
}
@Override
public CallTableNumDTO takeNumber(Long shopId, TakeNumberDTO takeNumberDTO) {
ShopInfo shopInfo = shopInfoService.getById(shopId);
CallTable callTable = queryChain()
.eq(CallTable::getShopId, shopId)
.eq(CallTable::getId, takeNumberDTO.getCallTableId()).one();
if (callTable == null) {
throw new ApiNotPrintException("桌型不存在");
}
// 拿取系统内部用户信息
CallQueue callQueue;
if (takeNumberDTO.getUserId() != null) {
ShopUser shopUser = shopUserService.queryChain()
.eq(ShopUser::getStatus, 1)
.eq(ShopUser::getShopId, shopId)
.eq(ShopUser::getId, takeNumberDTO.getUserId()).one();
if (shopUser == null) {
throw new ApiNotPrintException("用户不存在");
}
callQueue = callQueueService.queryChain()
.eq(CallQueue::getUserId, shopUser.getId())
.eq(CallQueue::getShopId, shopId)
.in(CallQueue::getState, 0, 1)
.eq(CallQueue::getCreateDay, DateUtil.today())
.ne(CallQueue::getIsPostpone, 2)
.eq(CallQueue::getCallTableId, takeNumberDTO.getCallTableId()).one();
if (callQueue != null) {
throw new ApiNotPrintException("当前用户已取号");
}
callQueue = BeanUtil.copyProperties(takeNumberDTO, CallQueue.class);
callQueue.setPhone(StrUtil.isBlank(takeNumberDTO.getPhone()) ? shopUser.getPhone() : takeNumberDTO.getPhone());
// callQueue.setOpenId(shopUser.getMiniOpenId());
} else {
// if (StrUtil.isBlank(takeNumberDTO.getPhone()) || StrUtil.isBlank(takeNumberDTO.getOpenId())) {
// throw new ApiNotPrintException("手机号或openId不能为空");
// }
callQueue = callQueueService.queryChain()
.eq(CallQueue::getPhone, takeNumberDTO.getPhone())
.eq(CallQueue::getShopId, shopId)
.eq(CallQueue::getCreateDay, DateUtil.today())
.in(CallQueue::getState, 0, 1)
.ne(CallQueue::getIsPostpone, 2)
.eq(CallQueue::getCallTableId, takeNumberDTO.getCallTableId()).one();
if (callQueue != null) {
throw new ApiNotPrintException("当前用户已取号");
}
callQueue = BeanUtil.copyProperties(takeNumberDTO, CallQueue.class);
callQueue.setPhone(takeNumberDTO.getPhone());
// callQueue.setOpenId(takeNumberDTO.getOpenId());
callQueue.setSubState(0);
}
callQueue.setCreateDay(DateUtil.today());
callQueue.setCallNum(getCallNumber(shopId, callTable));
callQueue.setShopId(shopId);
callQueue.setShopName(shopInfo.getShopName());
callQueueService.save(callQueue);
// todo 打印排号票信息
// rabbitMsgUtils.printCallNumTicket(callQueue.getId(), callQueue.getShopId());
return new CallTableNumDTO().setTableName(callTable.getName()).setTableNote(callTable.getNote())
.setCallNum(callQueue.getCallNum());
}
private String getCallNumber(Long shopId, CallTable callTable) {
return FunUtil.runFunAndCheckKey(() -> {
String callNumKey = RedisCst.getTableCallNumKey(shopId, callTable.getId());
String value = stringRedisTemplate.opsForValue().get(callNumKey);
AtomicReference<String> newVal = new AtomicReference<>("");
// 初始化
if (StrUtil.isBlank(value)) {
Boolean setFlag = FunUtil.runFunAndRetry(() -> stringRedisTemplate.opsForValue().setIfAbsent(callNumKey, callTable.getStart().toString()), flag -> !flag,
_ -> newVal.set(stringRedisTemplate.opsForValue().get(callNumKey)));
if (setFlag) {
return callTable.getPrefix() + callTable.getStart();
} else if (StrUtil.isNotBlank(newVal.get())) {
value = String.valueOf((Integer.parseInt(newVal.get()) + 1));
stringRedisTemplate.opsForValue().set(callNumKey, value);
return callTable.getPrefix() + value;
} else {
throw new ApiNotPrintException("生成排队号码失败");
}
} else {
value = String.valueOf((Integer.parseInt(value) + 1));
stringRedisTemplate.opsForValue().set(callNumKey, value);
return callTable.getPrefix() + value;
}
}, stringRedisTemplate, RedisCst.getLockKey("UPDATE_TABLE", shopId, callTable.getId()));
}
@Override
public String takeNumberCode(Long shopId, Integer callTableId) {
// 创建二维码配置对象设置宽度和高度为400
QrConfig config = new QrConfig(400, 400);
// 使用字节数组输出流来存储二维码图片
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 生成二维码图片输出到字节数组输出流
CzgResult<SysParamsDTO> paramsByCode = sysParamsService.getParamsByCode("call_page_url");
if (paramsByCode.getData() == null) {
throw new ApiNotPrintException("页面路径未配置");
}
QrCodeUtil.generate(StrUtil.format(paramsByCode.getData().getParamValue(), shopId, ""), config, "png", outputStream);
// 将图片转换为 Base64 字符串
String base64 = Base64.getEncoder().encodeToString(outputStream.toByteArray());
// 返回Base64格式的图片字符串
return "data:image/png;base64," + base64;
}
@Override
public Integer call(Long shopId, CallQueueDTO callQueueDTO) {
CallQueue callQueue = callQueueService.queryChain()
.notIn(CallQueue::getState, -1, 2)
.eq(CallQueue::getCreateDay, DateUtil.today())
.eq(CallQueue::getId, callQueueDTO.getCallQueueId())
.eq(CallQueue::getShopId, shopId)
.one();
if (callQueue == null) {
throw new ApiNotPrintException("叫号用户不存在");
}
callQueue.setState(1);
callQueue.setCallCount(callQueue.getCallCount() + 1);
callQueueService.updateById(callQueue);
if (callQueue.getSubState().equals(0)) {
return -1;
}
// 发送模板消息通知用户
ShopInfo shopInfo = shopInfoService.getById(shopId);
if (shopInfo == null) {
throw new ApiNotPrintException("店铺信息不存在");
}
List<CallQueue> current = callQueueService.queryChain()
.eq(CallQueue::getCallTableId, callQueue.getCallTableId())
.eq(CallQueue::getCreateDay, DateUtil.today())
.and(r -> {
r.eq(CallQueue::getState, 1)
.or(r1 -> {
r1.eq(CallQueue::getState, 0);
});
})
.orderBy(CallQueue::getCreateTime, true)
.page(new Page<>(1, 1)).getRecords();
if (StrUtil.isBlank(callQueue.getOpenId())) {
return -1;
}
// wxMiniUtils.sendCurrentOrNearCallMsg(shopInfo.getShopName(), getStrByState(Integer.valueOf(callQueue.getState())),
// callQueue.getCallNum(), current.isEmpty() ? "" : current.get(0).getCallNum(), "排号信息", callQueue.getOpenId(), false);
CallConfig config = getConfig(shopId);
// 临近用户提醒
List<CallQueue> nearList = callQueueService.queryChain()
.eq(CallQueue::getCallTableId, callQueue.getCallTableId())
.eq(CallQueue::getCreateDay, DateUtil.today())
.gt(CallQueue::getId, current.getFirst().getId())
.page(new Page<>(config.getNearNum(), 1)).getRecords();
if (!nearList.isEmpty()) {
CallQueue nearQueue = nearList.getFirst();
// wxMiniUtils.sendCurrentOrNearCallMsg(shopInfo.getShopName(), getStrByState(Integer.valueOf(nearQueue.getState())),
// nearQueue.getCallNum(), current.isEmpty() ? "" : current.get(0).getCallNum(), "排号信息", nearQueue.getOpenId(), true);
}
return 1;
}
@Override
public boolean updateInfo(Long shopId, UpdateCallQueueDTO updateCallQueueDTO) {
CallQueue callQueue = callQueueService.queryChain()
.eq(CallQueue::getShopId, shopId)
.eq(CallQueue::getCreateDay, DateUtil.today())
.eq(CallQueue::getId, updateCallQueueDTO.getCallQueueId()).one();
if (callQueue == null) {
throw new ApiNotPrintException("叫号用户不存在");
}
switch (updateCallQueueDTO.getState()) {
case -1:
callQueue.setCancelTime(DateUtil.date().toLocalDateTime());
break;
case 0:
callQueue.setState(0);
break;
case 1:
if (callQueue.getSubState().equals(0)) {
throw new ApiNotPrintException("当前用户未订阅微信提醒");
}
callQueue.setState(1);
callQueue.setCallCount(callQueue.getCallCount() + 1);
callQueue.setCallTime(DateUtil.date().toLocalDateTime());
break;
case 2:
callQueue.setConfirmTime(DateUtil.date().toLocalDateTime());
break;
case 3:
callQueue.setPassTime(DateUtil.date().toLocalDateTime());
// 已经顺延
callQueue.setIsPostpone(callQueue.getIsPostpone() == null ? 1 : callQueue.getIsPostpone() == 0 ? 1 : 2);
ShopInfo shopInfo = shopInfoService.getById(callQueue.getShopId());
if (shopInfo == null) {
throw new ApiNotPrintException("店铺信息不存在");
}
if (StrUtil.isBlank(callQueue.getOpenId()) && callQueue.getSubState() != 1) {
break;
}
CallTable callTable = getById(callQueue.getCallTableId());
Integer isPostpone = callTable.getIsPostpone();
Integer postponeNum = callTable.getPostponeNum();
// 判断是否需要顺延, 暂时注释
if (false && callQueue.getIsPostpone() == 0 && isPostpone != null && isPostpone == 1 && postponeNum != null && postponeNum > 0) {
// 查询当前桌以及顺延桌数
List<CallQueue> current = callQueueService.queryChain()
.eq(CallQueue::getCallTableId, callQueue.getCallTableId())
.eq(CallQueue::getCreateDay, DateUtil.today())
.eq(CallQueue::getShopId, callTable.getShopId())
.ge(CallQueue::getId, callQueue.getId())
.orderBy(CallQueue::getCreateTime, true)
.page(new Page<>(1, postponeNum + 1)) // 获取当前桌和顺延的桌数
.getRecords();
// 确保有足够的桌可以顺延
if (current.size() > 1) {
// 获取当前桌以及顺延桌
CallQueue currentTable = BeanUtil.copyProperties(current.getFirst(), CallQueue.class);
// 顺延替换信息将每一张顺延桌向前移动
for (int i = 0; i < current.size() - 1; i++) {
exchangeCallQueueInfo(current.get(i), current.get(i + 1)); // 当前桌替换为顺延桌
}
exchangeCallQueueInfo(current.getLast(), currentTable);
callQueue = current.getLast();
// 更新数据库中的桌号信息
callQueueService.updateBatch(current);
}
}
List<CallQueue> current = callQueueService.queryChain()
.eq(CallQueue::getCallTableId, callQueue.getCallTableId())
.eq(CallQueue::getCreateDay, DateUtil.today())
.and(r -> {
r.eq(CallQueue::getState, 1)
.or(q -> {
q.eq(CallQueue::getState, 0);
});
})
.orderBy(CallQueue::getCreateTime, true)
.page(new Page<>(1, 1)).getRecords();
// wxMiniUtils.sendPassCallMsg(shopInfo.getShopName(), getStrByState(Integer.valueOf(updateCallQueueDTO.getState())),
// callQueue.getCallNum(), current.isEmpty() ? "" : current.get(0).getCallNum(), "即将过号", callQueue.getOpenId());
break;
default:
throw new ApiNotPrintException("错误类型");
}
callQueue.setState(updateCallQueueDTO.getState());
return callQueueService.updateById(callQueue);
}
private void exchangeCallQueueInfo(CallQueue setCallQueue, CallQueue copyCallQueue) {
setCallQueue.setOpenId(copyCallQueue.getOpenId());
setCallQueue.setState(copyCallQueue.getState());
setCallQueue.setSubState(copyCallQueue.getSubState());
setCallQueue.setCreateTime(copyCallQueue.getCreateTime());
setCallQueue.setName(copyCallQueue.getName());
setCallQueue.setNote(copyCallQueue.getNote());
setCallQueue.setCallNum(copyCallQueue.getCallNum());
setCallQueue.setCallTime(copyCallQueue.getCallTime());
setCallQueue.setCallCount(copyCallQueue.getCallCount());
setCallQueue.setPassTime(copyCallQueue.getPassTime());
setCallQueue.setCancelTime(copyCallQueue.getCancelTime());
setCallQueue.setUserId(copyCallQueue.getUserId());
setCallQueue.setConfirmTime(copyCallQueue.getConfirmTime());
}
@Override
public Page<CallQueue> getQueue(Long shopId, Long callTableId, Integer state) {
List<Long> tableIds;
if (callTableId != null) {
tableIds = Collections.singletonList(callTableId);
} else {
List<CallTable> list = queryChain().eq(CallTable::getShopId, shopId).eq(CallTable::getState, 1).list();
if (list.isEmpty()) {
return new Page<>();
}
tableIds = list.stream()
.map(CallTable::getId)
.collect(Collectors.toList());
}
QueryWrapper query = callQueueService.queryChain()
.eq(CallQueue::getShopId, shopId)
.eq(CallQueue::getCreateDay, DateUtil.today())
.in(CallQueue::getCallTableId, tableIds);
if (state != null) {
query.eq(CallQueue::getState, state);
} else {
query.in(CallQueue::getState, 0, 1);
}
Page<CallQueue> pageInfo = callQueueService.page(PageUtil.buildPage(), query
.orderBy(CallQueue::getCreateTime, true)
.orderBy(CallQueue::getState, false));
List<CallQueue> list1 = pageInfo.getRecords();
// 创建返回的结果集
List<CallQueue> resultList = new ArrayList<>();
// 遍历每一个叫号中的记录计算前面状态为"0" (排队中) 的人数
for (CallQueue calling : list1) {
// 计算前面等待的人数 (状态为"0"且在叫号记录创建时间之前的)
long waitingCount = 0;
if (calling.getState() == 0) {
waitingCount = list1.stream()
.filter(item -> item.getState() == 0 || item.getState() == 1) // 过滤出状态为"排队中"
.filter(item -> item.getCreateTime().isBefore(calling.getCreateTime())) // 时间在当前叫号之前
.count();
}
// 创建一个Map来保存叫号中的记录及其前面排队的人数
CallQueueListDTO callQueueListDTO = BeanUtil.copyProperties(calling, CallQueueListDTO.class);
callQueueListDTO.setWaitingCount(waitingCount);
// 将该map加入结果集
resultList.add(callQueueListDTO);
}
pageInfo.setRecords(resultList);
// 返回结果列表
return pageInfo;
}
@Override
public Page<CallRecordVO> getCallRecord(Long shopId, Integer callTableId) {
QueryWrapper queryWrapper = new JoinQueryWrapper().eq(CallQueue::getShopId, shopId).in(CallQueue::getState, 3, 2, 1).orderBy(CallQueue::getCreateTime, false);
if (callTableId != null) {
queryWrapper.eq(CallQueue::getCallTableId, callTableId);
}
return callQueueMapper.xmlPaginate("selectCallRecord", PageUtil.buildPage(), queryWrapper);
}
@Override
public CallConfig getConfig(Long shopId) {
CallConfig config = callConfigService.queryChain().eq(CallConfig::getShopId, shopId).one();
if (config == null) {
config = new CallConfig();
config.setShopId(shopId);
callConfigService.save(config);
config = callConfigService.queryChain().eq(CallConfig::getShopId, shopId).one();
}
return config;
}
@Override
public boolean updateConfig(Long shopId, UpdateConfigDTO configDTO) {
CallConfig config = callConfigService.queryChain().eq(CallConfig::getShopId, shopId).one();
if (config == null) {
throw new ApiNotPrintException("未查询到配置信息");
}
CallConfig tbCallConfig = BeanUtil.copyProperties(configDTO, CallConfig.class);
tbCallConfig.setId(config.getId());
return callConfigService.updateById(tbCallConfig);
}
}

View File

@ -65,7 +65,7 @@ public class ShopUserServiceImpl extends ServiceImpl<ShopUserMapper, ShopUser> i
});
});
// queryWrapper.and(JoinQueryWrapper.column(UserInfo::getNickName).like(key).or(JoinQueryWrapper.column(UserInfo::getPhone).like(key)));
queryWrapper.and(column(UserInfo::getNickName).like(key).or(column(UserInfo::getPhone).like(key)));
}
if (isVip != null) {

View File

@ -0,0 +1,64 @@
package com.czg.service.account.util;
import com.czg.exception.ApiNotPrintException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @author Administrator
*/
@Slf4j
public class FunUtil {
public static int retryCount = 5;
public static<T> T runFunAndCheckKey(Supplier<T> supplier, StringRedisTemplate redisTemplate, String lockKey) {
try{
// 设置分布式锁
boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.MILLISECONDS));
int count = 0;
while (!lock) {
if (count++ > 100) {
throw new ApiNotPrintException("系统繁忙, 稍后再试");
}
Thread.sleep(20);
lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.MILLISECONDS));
}
return supplier.get();
} catch (RuntimeException e){
log.info("执行出错:{}", e.getMessage());
throw e;
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally{
redisTemplate.delete(lockKey);
}
}
public static <T, R> R runFunAndRetry(
Supplier<R> function,
Function<R, Boolean> check, Consumer<R> errFun) {
log.info("工具类开始执行函数");
R result = function.get();
boolean flag = check.apply(result);
log.info("执行结果: {}", result);
while (flag && retryCount-- > 0) {
log.info("执行函数失败, 剩余尝试次数{}", retryCount);
result = function.get();
log.info("执行结果: {}", result);
flag = check.apply(result);
}
if (flag) {
errFun.accept(result);
}
return result;
}
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.czg.service.account.mapper.CallConfigMapper">
</mapper>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.czg.service.account.mapper.CallQueueMapper">
<select id="selectCallRecord" resultType="com.czg.account.dto.calltable.CallRecordVO">
select tb_call_queue.*, tb_call_table.note, TIMESTAMPDIFF(SECOND, tb_call_queue.create_time, NOW()) as since_at
from tb_call_queue
left join tb_call_table on tb_call_queue.call_table_id = tb_call_table.id
${qwSql}
limit ${pageOffset}, ${pageSize}
</select>
<select id="selectCallRecord_COUNT" resultType="java.lang.Long">
select count(1)
from tb_call_queue
left join tb_call_table on tb_call_queue.call_table_id = tb_call_table.id
${qwSql}
</select>
</mapper>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.czg.service.account.mapper.CallTableMapper">
</mapper>