Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
484c40cdb5 | ||
|
|
8a8cef3005 | ||
|
|
38ea1478cb | ||
|
|
2046ad2486 | ||
|
|
0cdad226ea | ||
|
|
e179a910ba | ||
|
|
6b5c451f92 | ||
|
|
303db8bc49 |
@@ -0,0 +1,39 @@
|
|||||||
|
package com.czg.controller;
|
||||||
|
|
||||||
|
import com.czg.account.entity.ShopUser;
|
||||||
|
import com.czg.account.service.ShopInfoService;
|
||||||
|
import com.czg.account.service.ShopUserFlowService;
|
||||||
|
import com.czg.account.service.ShopUserService;
|
||||||
|
import com.czg.account.service.TestService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author GYJoker
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/notify/test")
|
||||||
|
public class TestController {
|
||||||
|
@Resource
|
||||||
|
private ShopUserFlowService shopUserFlowService;
|
||||||
|
@Resource
|
||||||
|
private ShopInfoService shopInfoService;
|
||||||
|
@Resource
|
||||||
|
private ShopUserService shopUserService;
|
||||||
|
@Resource
|
||||||
|
private TestService testService;
|
||||||
|
|
||||||
|
@RequestMapping("/hello")
|
||||||
|
public String hello() {
|
||||||
|
shopUserFlowService.list().forEach(item -> {
|
||||||
|
ShopUser shopUserInfo = shopUserService.getShopUserInfo(item.getShopId(), item.getUserId());
|
||||||
|
if (shopUserInfo != null) {
|
||||||
|
item.setShopUserId(shopUserInfo.getId());
|
||||||
|
shopUserFlowService.updateById(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// return testService.insertData();
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,18 +4,14 @@ import com.czg.account.dto.menu.MenuAddDTO;
|
|||||||
import com.czg.account.dto.menu.MenuDelDTO;
|
import com.czg.account.dto.menu.MenuDelDTO;
|
||||||
import com.czg.account.dto.menu.MenuEditDTO;
|
import com.czg.account.dto.menu.MenuEditDTO;
|
||||||
import com.czg.account.entity.CashMenu;
|
import com.czg.account.entity.CashMenu;
|
||||||
import com.czg.account.entity.QuickMenu;
|
|
||||||
import com.czg.account.entity.SysMenu;
|
import com.czg.account.entity.SysMenu;
|
||||||
import com.czg.account.service.QuickMenuService;
|
|
||||||
import com.czg.account.service.SysMenuService;
|
import com.czg.account.service.SysMenuService;
|
||||||
import com.czg.account.vo.MenuVO;
|
import com.czg.account.vo.MenuVO;
|
||||||
import com.czg.annotation.SaAdminCheckPermission;
|
import com.czg.annotation.SaAdminCheckPermission;
|
||||||
import com.czg.annotation.SaAdminCheckRole;
|
import com.czg.annotation.SaAdminCheckRole;
|
||||||
import com.czg.resp.CzgResult;
|
import com.czg.resp.CzgResult;
|
||||||
import com.czg.sa.StpKit;
|
import com.czg.sa.StpKit;
|
||||||
import com.mybatisflex.core.query.QueryWrapper;
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@@ -23,7 +19,6 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 菜单管理
|
* 菜单管理
|
||||||
*
|
|
||||||
* @author zs
|
* @author zs
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@@ -32,12 +27,9 @@ public class MenuController {
|
|||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private SysMenuService menuService;
|
private SysMenuService menuService;
|
||||||
@Resource
|
|
||||||
private QuickMenuService quickMenuService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前用户菜单列表
|
* 获取当前用户菜单列表
|
||||||
*
|
|
||||||
* @return 菜单结构
|
* @return 菜单结构
|
||||||
*/
|
*/
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@@ -48,7 +40,6 @@ public class MenuController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 收银机菜单
|
* 收银机菜单
|
||||||
*
|
|
||||||
* @return 所有菜单
|
* @return 所有菜单
|
||||||
*/
|
*/
|
||||||
@GetMapping("/list/cash")
|
@GetMapping("/list/cash")
|
||||||
@@ -58,7 +49,6 @@ public class MenuController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有菜单
|
* 获取所有菜单
|
||||||
*
|
|
||||||
* @return 菜单结构
|
* @return 菜单结构
|
||||||
*/
|
*/
|
||||||
@SaAdminCheckPermission(parentName = "菜单管理", value = "menu:list", name = "菜单列表")
|
@SaAdminCheckPermission(parentName = "菜单管理", value = "menu:list", name = "菜单列表")
|
||||||
@@ -72,7 +62,6 @@ public class MenuController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 菜单详情
|
* 菜单详情
|
||||||
*
|
|
||||||
* @return 菜单结构
|
* @return 菜单结构
|
||||||
*/
|
*/
|
||||||
@SaAdminCheckRole("管理员")
|
@SaAdminCheckRole("管理员")
|
||||||
@@ -84,7 +73,6 @@ public class MenuController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 菜单添加
|
* 菜单添加
|
||||||
*
|
|
||||||
* @return 是否成功
|
* @return 是否成功
|
||||||
*/
|
*/
|
||||||
@SaAdminCheckRole("管理员")
|
@SaAdminCheckRole("管理员")
|
||||||
@@ -96,7 +84,6 @@ public class MenuController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 菜单修改
|
* 菜单修改
|
||||||
*
|
|
||||||
* @return 是否成功
|
* @return 是否成功
|
||||||
*/
|
*/
|
||||||
@SaAdminCheckRole("管理员")
|
@SaAdminCheckRole("管理员")
|
||||||
@@ -108,15 +95,12 @@ public class MenuController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 菜单删除
|
* 菜单删除
|
||||||
*
|
|
||||||
* @return 是否成功
|
* @return 是否成功
|
||||||
*/
|
*/
|
||||||
@SaAdminCheckRole("管理员")
|
@SaAdminCheckRole("管理员")
|
||||||
@SaAdminCheckPermission(parentName = "菜单管理", value = "menu:del", name = "菜单删除")
|
@SaAdminCheckPermission(parentName = "菜单管理", value = "menu:del", name = "菜单删除")
|
||||||
@DeleteMapping()
|
@DeleteMapping()
|
||||||
@Transactional
|
|
||||||
public CzgResult<Boolean> edit(@RequestBody @Validated MenuDelDTO menuDelDTO) {
|
public CzgResult<Boolean> edit(@RequestBody @Validated MenuDelDTO menuDelDTO) {
|
||||||
quickMenuService.remove(QueryWrapper.create().eq(QuickMenu::getMenuId, menuDelDTO.getId()));
|
|
||||||
return CzgResult.success(menuService.removeById(menuDelDTO.getId()));
|
return CzgResult.success(menuService.removeById(menuDelDTO.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
package com.czg.controller.admin;
|
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
|
||||||
import com.czg.account.entity.QuickMenu;
|
|
||||||
import com.czg.account.service.QuickMenuService;
|
|
||||||
import com.czg.annotation.SaAdminCheckPermission;
|
|
||||||
import com.czg.resp.CzgResult;
|
|
||||||
import com.czg.sa.StpKit;
|
|
||||||
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.*;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 悬浮窗/快捷菜单
|
|
||||||
*
|
|
||||||
* @author ww
|
|
||||||
* @description
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/admin/quick")
|
|
||||||
@Slf4j
|
|
||||||
public class QuickMenuController {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private QuickMenuService quickMenuService;
|
|
||||||
|
|
||||||
@SaAdminCheckPermission(parentName = "悬浮窗", value = "quick:list", name = "悬浮窗-列表")
|
|
||||||
@GetMapping
|
|
||||||
public CzgResult<List<QuickMenu>> getQuickList(Integer status) {
|
|
||||||
List<QuickMenu> list = quickMenuService.list(QueryWrapper.create()
|
|
||||||
.eq(QuickMenu::getShopId, StpKit.USER.getShopId())
|
|
||||||
.eq(QuickMenu::getStatus, status)
|
|
||||||
.orderBy(QuickMenu::getSort, true));
|
|
||||||
if (CollUtil.isEmpty(list)) {
|
|
||||||
list = quickMenuService.list(QueryWrapper.create()
|
|
||||||
.eq(QuickMenu::getShopId, 1)
|
|
||||||
.eq(QuickMenu::getStatus, status)
|
|
||||||
.orderBy(QuickMenu::getSort, true));
|
|
||||||
}
|
|
||||||
return CzgResult.success(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SaAdminCheckPermission(parentName = "悬浮窗", value = "quick:list", name = "悬浮窗-新增")
|
|
||||||
@PostMapping
|
|
||||||
public CzgResult<Boolean> saveQuick(@RequestBody @Validated QuickMenu quickMenu) {
|
|
||||||
quickMenu.setShopId(StpKit.USER.getShopId());
|
|
||||||
return CzgResult.success(quickMenuService.save(quickMenu));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SaAdminCheckPermission(parentName = "悬浮窗", value = "quick:list", name = "悬浮窗-修改")
|
|
||||||
@PutMapping
|
|
||||||
public CzgResult<Boolean> updateQuick(@RequestBody @Validated QuickMenu quickMenu) {
|
|
||||||
return CzgResult.success(quickMenuService.update(quickMenu, QueryWrapper.create()
|
|
||||||
.eq(QuickMenu::getId, quickMenu.getId()).eq(QuickMenu::getShopId, StpKit.USER.getShopId())));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SaAdminCheckPermission(parentName = "悬浮窗", value = "quick:list", name = "悬浮窗-删除")
|
|
||||||
@DeleteMapping
|
|
||||||
public CzgResult<Boolean> deleteQuick(@RequestBody Set<Long> ids) {
|
|
||||||
return CzgResult.success(quickMenuService.remove(QueryWrapper.create().in(QuickMenu::getId, ids).eq(QuickMenu::getShopId, StpKit.USER.getShopId())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,6 @@ import com.czg.market.vo.InviteUserVO;
|
|||||||
import com.czg.market.vo.MkDistributionConfigVO;
|
import com.czg.market.vo.MkDistributionConfigVO;
|
||||||
import com.czg.resp.CzgResult;
|
import com.czg.resp.CzgResult;
|
||||||
import com.czg.sa.StpKit;
|
import com.czg.sa.StpKit;
|
||||||
import com.czg.task.DistributionTask;
|
|
||||||
import com.czg.utils.AssertUtil;
|
import com.czg.utils.AssertUtil;
|
||||||
import com.mybatisflex.core.paginate.Page;
|
import com.mybatisflex.core.paginate.Page;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -38,22 +37,6 @@ public class UDistributionController {
|
|||||||
private MkDistributionWithdrawFlowService withdrawFlowService;
|
private MkDistributionWithdrawFlowService withdrawFlowService;
|
||||||
@Resource
|
@Resource
|
||||||
private MkDistributionFlowService distributionFlowService;
|
private MkDistributionFlowService distributionFlowService;
|
||||||
@Resource
|
|
||||||
private DistributionTask distributionTask;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 分销员中心-获取配置
|
|
||||||
*/
|
|
||||||
@GetMapping("/task")
|
|
||||||
public CzgResult<String> task(@RequestParam Long shopId) {
|
|
||||||
try {
|
|
||||||
distributionTask.deliver(shopId);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return CzgResult.failure(e.getMessage());
|
|
||||||
}
|
|
||||||
return CzgResult.success("任务执行成功");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分销员中心-获取配置
|
* 分销员中心-获取配置
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import org.springframework.stereotype.Component;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* market服务 任务总调度
|
* market服务 任务总调度
|
||||||
*
|
|
||||||
* @author ww
|
* @author ww
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@@ -19,9 +18,9 @@ public class AAMarketTasks {
|
|||||||
|
|
||||||
|
|
||||||
// 分销延时发放
|
// 分销延时发放
|
||||||
@Scheduled(cron = "0 0 0/2 * * ? ")
|
@Scheduled(fixedRate = 30000)
|
||||||
public void distributionTask() {
|
public void distributionTask() {
|
||||||
distributionTask.deliver(null);
|
distributionTask.deliver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +29,6 @@ public class AAMarketTasks {
|
|||||||
public void birthdayGiftTask() {
|
public void birthdayGiftTask() {
|
||||||
birthdayGiftTask.deliver();
|
birthdayGiftTask.deliver();
|
||||||
}
|
}
|
||||||
|
|
||||||
//会员生日弹窗提醒重置 每年1月1日
|
//会员生日弹窗提醒重置 每年1月1日
|
||||||
@Scheduled(cron = "0 0 0 1 1 ?")
|
@Scheduled(cron = "0 0 0 1 1 ?")
|
||||||
public void birthdayGiftRemindTask() {
|
public void birthdayGiftRemindTask() {
|
||||||
@@ -41,7 +39,6 @@ public class AAMarketTasks {
|
|||||||
//优惠券 过期
|
//优惠券 过期
|
||||||
@Resource
|
@Resource
|
||||||
private CouponTask couponTask;
|
private CouponTask couponTask;
|
||||||
|
|
||||||
//每天每小时的30分 0秒 执行
|
//每天每小时的30分 0秒 执行
|
||||||
@Scheduled(cron = "0 30 * * * ? ")
|
@Scheduled(cron = "0 30 * * * ? ")
|
||||||
public void couponTask() {
|
public void couponTask() {
|
||||||
@@ -51,7 +48,6 @@ public class AAMarketTasks {
|
|||||||
//会员奖励发放
|
//会员奖励发放
|
||||||
@Resource
|
@Resource
|
||||||
private MemberTask memberTask;
|
private MemberTask memberTask;
|
||||||
|
|
||||||
//每天1点 0分 0秒 执行
|
//每天1点 0分 0秒 执行
|
||||||
@Scheduled(cron = "0 0 1 * * ? ")
|
@Scheduled(cron = "0 0 1 * * ? ")
|
||||||
public void memberTask() {
|
public void memberTask() {
|
||||||
@@ -61,7 +57,6 @@ public class AAMarketTasks {
|
|||||||
//满减活动/限时折扣 处理任务状态 定时任务
|
//满减活动/限时折扣 处理任务状态 定时任务
|
||||||
@Resource
|
@Resource
|
||||||
private ActivityStatusTask activityStatusTask;
|
private ActivityStatusTask activityStatusTask;
|
||||||
|
|
||||||
//每天0点 0分 1秒 执行
|
//每天0点 0分 1秒 执行
|
||||||
@Scheduled(cron = "1 0 0 * * ? ")
|
@Scheduled(cron = "1 0 0 * * ? ")
|
||||||
public void activityStatusTask() {
|
public void activityStatusTask() {
|
||||||
@@ -72,7 +67,6 @@ public class AAMarketTasks {
|
|||||||
//月累计 发送条数 累计金额
|
//月累计 发送条数 累计金额
|
||||||
@Resource
|
@Resource
|
||||||
private SmsShopMoneyTask smsShopMoneyTask;
|
private SmsShopMoneyTask smsShopMoneyTask;
|
||||||
|
|
||||||
//每月1号 0点 0分 1秒 执行
|
//每月1号 0点 0分 1秒 执行
|
||||||
@Scheduled(cron = "1 0 0 1 * ?")
|
@Scheduled(cron = "1 0 0 1 * ?")
|
||||||
public void smsShopMoneyTask() {
|
public void smsShopMoneyTask() {
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
package com.czg.task;
|
package com.czg.task;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import com.czg.account.entity.ShopInfo;
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import com.czg.account.entity.ShopUser;
|
||||||
import com.czg.account.service.ShopInfoService;
|
import com.czg.account.service.ShopInfoService;
|
||||||
|
import com.czg.account.service.ShopUserService;
|
||||||
import com.czg.constant.TableValueConstant;
|
import com.czg.constant.TableValueConstant;
|
||||||
import com.czg.constants.SystemConstants;
|
|
||||||
import com.czg.exception.CzgException;
|
|
||||||
import com.czg.market.entity.MkDistributionFlow;
|
import com.czg.market.entity.MkDistributionFlow;
|
||||||
import com.czg.market.service.MkDistributionFlowService;
|
import com.czg.market.service.MkDistributionFlowService;
|
||||||
import com.czg.market.service.MkDistributionUserService;
|
import com.czg.market.service.MkDistributionUserService;
|
||||||
import com.czg.market.service.OrderInfoService;
|
import com.czg.market.service.OrderInfoService;
|
||||||
import com.czg.order.entity.OrderInfo;
|
import com.czg.order.entity.OrderInfo;
|
||||||
import com.czg.service.market.enums.OrderStatusEnums;
|
import com.czg.service.market.enums.OrderStatusEnums;
|
||||||
|
import com.czg.utils.FunUtils;
|
||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -39,6 +40,8 @@ public class DistributionTask {
|
|||||||
private OrderInfoService orderInfoService;
|
private OrderInfoService orderInfoService;
|
||||||
@DubboReference
|
@DubboReference
|
||||||
private ShopInfoService shopInfoService;
|
private ShopInfoService shopInfoService;
|
||||||
|
@DubboReference
|
||||||
|
private ShopUserService shopUserService;
|
||||||
|
|
||||||
List<String> list = List.of(OrderStatusEnums.REFUND.getCode(), OrderStatusEnums.PART_REFUND.getCode());
|
List<String> list = List.of(OrderStatusEnums.REFUND.getCode(), OrderStatusEnums.PART_REFUND.getCode());
|
||||||
|
|
||||||
@@ -48,55 +51,44 @@ public class DistributionTask {
|
|||||||
*/
|
*/
|
||||||
// @Scheduled(cron = "0 0 0 * * ?")
|
// @Scheduled(cron = "0 0 0 * * ?")
|
||||||
// @Scheduled(fixedRate = 30000)
|
// @Scheduled(fixedRate = 30000)
|
||||||
public void deliver(Long shopId) {
|
public void deliver() {
|
||||||
LocalDateTime now = LocalDateTime.now();
|
// 1. 订单完成支付时(判断是否分销)产生流水记录。
|
||||||
List<ShopInfo> shopInfos = shopInfoService.list(QueryWrapper.create()
|
// 2. 判断入账时间。
|
||||||
.eq(ShopInfo::getIsDeleted, SystemConstants.OneZero.ZERO)
|
// 3. 如果是 0 天,再去判断商户余额是否足够。够则入账,不足则不管。
|
||||||
.isNotNull(ShopInfo::getExpireTime)
|
// 4. 流水增加应该入账的时间(订单产生时带入)
|
||||||
.lt(ShopInfo::getExpireTime, now)
|
// 5. 定时任务 应该是一天执行一次。查询待入账状态和应入账时间小于当前时间的记录,循环处理:并且判断商户余额是否足够,余额不足忽略处理;余额足够变为已入账并扣除商户余额。
|
||||||
.gt(ShopInfo::getAmount, BigDecimal.ZERO)
|
// 6. 订单产生退款时,去流水表查询该订单的流水记录,如果未入账改为已入账,并插入一条退款扣钱的流水。
|
||||||
.eq(ShopInfo::getId, shopId)
|
|
||||||
);
|
// shopInfo 查余额>0
|
||||||
if (CollUtil.isEmpty(shopInfos)) {
|
// 循环 shopId 查询 如果金额不足 终止
|
||||||
log.info("分销延时分账 无符合条件的店铺,无需处理分账");
|
//
|
||||||
return;
|
LocalDateTime localDateTime = DateUtil.date().toLocalDateTime();
|
||||||
}
|
distributionFlowService.list(new QueryWrapper()
|
||||||
for (ShopInfo shopInfo : shopInfos) {
|
.eq(MkDistributionFlow::getStatus, TableValueConstant.DistributionFlow.Status.PENDING.getCode()).le(MkDistributionFlow::getDeliverTime, localDateTime)).forEach(item -> {
|
||||||
boolean breakCurrentShopFlow = false;
|
FunUtils.safeRunVoid(() -> {
|
||||||
List<MkDistributionFlow> flowList = distributionFlowService.list(new QueryWrapper()
|
log.info("开始处理延时分账, id: {}, orderNo: {}, 类型: {}", item.getId(), item.getOrderNo(), item.getType());
|
||||||
.eq(MkDistributionFlow::getShopId, shopInfo.getId())
|
|
||||||
.eq(MkDistributionFlow::getStatus, TableValueConstant.DistributionFlow.Status.PENDING.getCode())
|
OrderInfo orderInfo = orderInfoService.getOne(new QueryWrapper().eq(OrderInfo::getOrderNo, item.getOrderNo()));
|
||||||
.le(MkDistributionFlow::getDeliverTime, now)
|
if (orderInfo == null) {
|
||||||
.orderBy(MkDistributionFlow::getId, true)
|
item.setStatus(TableValueConstant.DistributionFlow.Status.FAIL.getCode());
|
||||||
);
|
distributionFlowService.updateById(item);
|
||||||
for (MkDistributionFlow item : flowList) {
|
log.warn("订单不存在, 订单号: {}", item.getOrderNo());
|
||||||
if (breakCurrentShopFlow) {
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
try {
|
if (list.contains(orderInfo.getStatus())) {
|
||||||
log.info("分销延时分账, id: {}, orderNo: {}, 类型: {}", item.getId(), item.getOrderNo(), item.getType());
|
log.warn("订单已退款, 订单号: {}", item.getOrderNo());
|
||||||
OrderInfo orderInfo = orderInfoService.getOne(new QueryWrapper().eq(OrderInfo::getOrderNo, item.getOrderNo()));
|
distributionUserService.refund(orderInfo.getId(), orderInfo.getOrderNo());
|
||||||
if (orderInfo == null) {
|
} else {
|
||||||
item.setStatus(TableValueConstant.DistributionFlow.Status.FAIL.getCode());
|
item.setStatus(TableValueConstant.DistributionFlow.Status.SUCCESS.getCode());
|
||||||
distributionFlowService.updateById(item);
|
ShopUser shopUser = shopUserService.getById(item.getDistributionUserId());
|
||||||
log.warn("分销延时分账。订单不存在, 订单号: {}", item.getOrderNo());
|
distributionUserService.updateShopInfoAmount(orderInfo.getShopId(), item.getRewardAmount().negate(), orderInfo.getId(), TableValueConstant.DistributionAmountFlow.Type.SUB, "分销扣减");
|
||||||
continue;
|
distributionUserService.updateIncome(item.getRewardAmount().negate(), item.getRewardAmount(), BigDecimal.ZERO,
|
||||||
}
|
item.getDistributionUserId(), shopUser.getUserId(), item.getShopUserId(), item.getShopId(), item.getLevel());
|
||||||
if (list.contains(orderInfo.getStatus())) {
|
distributionFlowService.updateById(item);
|
||||||
log.warn("分销延时分账。订单已退款, 订单号: {}", item.getOrderNo());
|
|
||||||
distributionUserService.refund(orderInfo.getId(), orderInfo.getOrderNo());
|
|
||||||
} else {
|
|
||||||
item.setStatus(TableValueConstant.DistributionFlow.Status.SUCCESS.getCode());
|
|
||||||
distributionUserService.distributionUserAmount(item, orderInfo);
|
|
||||||
}
|
|
||||||
} catch (CzgException e) {
|
|
||||||
log.error("店铺{}:{}分销延时分账异常:{}", shopInfo.getId(), shopInfo.getShopName(), e.getMessage());
|
|
||||||
breakCurrentShopFlow = true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("店铺{}:{}分销延时分账异常", shopInfo.getId(), shopInfo.getShopName(), e);
|
|
||||||
breakCurrentShopFlow = true;
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,8 +50,6 @@ public class NotifyController {
|
|||||||
@Resource
|
@Resource
|
||||||
private MkShopConsumeDiscountRecordService consumeDiscountRecordService;
|
private MkShopConsumeDiscountRecordService consumeDiscountRecordService;
|
||||||
|
|
||||||
|
|
||||||
//新客立减清除数据 测试用
|
|
||||||
@RequestMapping("clear")
|
@RequestMapping("clear")
|
||||||
public String clear(@RequestParam Integer shopId) {
|
public String clear(@RequestParam Integer shopId) {
|
||||||
consumeDiscountRecordService.remove(new QueryWrapper().eq(MkShopConsumeDiscountRecord::getShopId, shopId));
|
consumeDiscountRecordService.remove(new QueryWrapper().eq(MkShopConsumeDiscountRecord::getShopId, shopId));
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class StatisticTaskController {
|
|||||||
private StatisticTask statisticTask;
|
private StatisticTask statisticTask;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础统计 预留重置统计
|
* 基础统计
|
||||||
*
|
*
|
||||||
* @param date 日期yyyy-MM-dd
|
* @param date 日期yyyy-MM-dd
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import com.czg.order.entity.MqLog;
|
|||||||
import com.czg.order.service.MqLogService;
|
import com.czg.order.service.MqLogService;
|
||||||
import com.czg.order.service.OrderInfoCustomService;
|
import com.czg.order.service.OrderInfoCustomService;
|
||||||
import com.czg.order.service.OrderInfoRpcService;
|
import com.czg.order.service.OrderInfoRpcService;
|
||||||
import com.czg.service.RedisService;
|
import com.czg.service.order.utils.FunUtil;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||||
@@ -31,7 +31,7 @@ public class OrderMqListener {
|
|||||||
@Resource
|
@Resource
|
||||||
private OrderInfoCustomService orderInfoCustomService;
|
private OrderInfoCustomService orderInfoCustomService;
|
||||||
@Resource
|
@Resource
|
||||||
private RedisService redisService;
|
private FunUtil funUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单上菜
|
* 订单上菜
|
||||||
@@ -44,7 +44,7 @@ public class OrderMqListener {
|
|||||||
info = info.replace("UP_ORDER_DETAIL:", "");
|
info = info.replace("UP_ORDER_DETAIL:", "");
|
||||||
log.info("接收到修改菜品状态mq, info: {}", info);
|
log.info("接收到修改菜品状态mq, info: {}", info);
|
||||||
String finalInfo = info;
|
String finalInfo = info;
|
||||||
redisService.debounce("UP_ORDER_DETAIL:" + info, 5, () -> {
|
funUtil.debounce("UP_ORDER_DETAIL:" + info, 5, () -> {
|
||||||
orderInfoCustomService.updateOrderDetailStatus(Long.valueOf(finalInfo));
|
orderInfoCustomService.updateOrderDetailStatus(Long.valueOf(finalInfo));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import com.czg.config.RabbitConstants;
|
|||||||
import com.czg.config.RedisCst;
|
import com.czg.config.RedisCst;
|
||||||
import com.czg.order.entity.MqLog;
|
import com.czg.order.entity.MqLog;
|
||||||
import com.czg.order.service.MqLogService;
|
import com.czg.order.service.MqLogService;
|
||||||
import com.czg.service.RedisService;
|
|
||||||
import com.czg.service.order.print.PrinterHandler;
|
import com.czg.service.order.print.PrinterHandler;
|
||||||
|
import com.czg.service.order.utils.FunUtil;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -27,8 +28,11 @@ public class PrintMqListener {
|
|||||||
@Resource
|
@Resource
|
||||||
private MqLogService mqLogService;
|
private MqLogService mqLogService;
|
||||||
@Resource
|
@Resource
|
||||||
private RedisService redisService;
|
private FunUtil funUtil;
|
||||||
|
|
||||||
|
// 注入自定义线程池(建议单独配置,避免使用默认线程池)
|
||||||
|
@Resource
|
||||||
|
private ThreadPoolTaskExecutor asyncExecutor;
|
||||||
@Lazy
|
@Lazy
|
||||||
@Resource
|
@Resource
|
||||||
private PrinterHandler printerHandler;
|
private PrinterHandler printerHandler;
|
||||||
@@ -59,11 +63,38 @@ public class PrintMqListener {
|
|||||||
throw new RuntimeException("订单打印失败,未传递orderId");
|
throw new RuntimeException("订单打印失败,未传递orderId");
|
||||||
}
|
}
|
||||||
Boolean printOrder = jsonObject.getBoolean("printOrder");
|
Boolean printOrder = jsonObject.getBoolean("printOrder");
|
||||||
redisService.runFunAndCheckKey(() -> {
|
funUtil.runFunAndCheckKey(() -> {
|
||||||
printerHandler.handler(orderId, printOrder != null && !printOrder ? PrinterHandler.PrintTypeEnum.ONE : PrinterHandler.PrintTypeEnum.ONE_AND_ORDER);
|
printerHandler.handler(orderId, printOrder != null && !printOrder ? PrinterHandler.PrintTypeEnum.ONE : PrinterHandler.PrintTypeEnum.ONE_AND_ORDER);
|
||||||
return null;
|
return null;
|
||||||
}, RedisCst.getLockKey("orderPrint", orderId));
|
}, RedisCst.getLockKey("orderPrint", orderId));
|
||||||
});
|
});
|
||||||
|
// // 使用异步线程池执行延迟任务,不阻塞当前消费者线程
|
||||||
|
// CompletableFuture.runAsync(() -> {
|
||||||
|
// try {
|
||||||
|
// // 延迟3秒处理
|
||||||
|
// TimeUnit.SECONDS.sleep(3);
|
||||||
|
// // 执行核心打印逻辑
|
||||||
|
// invokeFun("orderPrint", "java.order", req, (data) -> {
|
||||||
|
// JSONObject jsonObject = JSONObject.parseObject(data);
|
||||||
|
// String orderId = jsonObject.getString("orderId");
|
||||||
|
// if (orderId == null) {
|
||||||
|
// throw new RuntimeException("订单打印失败,未传递orderId");
|
||||||
|
// }
|
||||||
|
// Boolean printOrder = jsonObject.getBoolean("printOrder");
|
||||||
|
// funUtil.runFunAndCheckKey(() -> {
|
||||||
|
// printerHandler.handler(orderId, printOrder != null && !printOrder ? PrinterHandler.PrintTypeEnum.ONE : PrinterHandler.PrintTypeEnum.ONE_AND_ORDER);
|
||||||
|
// return null;
|
||||||
|
// }, RedisCst.getLockKey("orderPrint", orderId));
|
||||||
|
// });
|
||||||
|
// } catch (InterruptedException e) {
|
||||||
|
// Thread.currentThread().interrupt();
|
||||||
|
// // 记录中断日志
|
||||||
|
// log.warn("打印任务被中断,req:{}", req, e);
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// // 记录业务异常日志
|
||||||
|
// log.error("打印任务处理失败,req:{}", req, e);
|
||||||
|
// }
|
||||||
|
// }, asyncExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,7 +106,7 @@ public class PrintMqListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 叫号打印
|
* 交班打印
|
||||||
*/
|
*/
|
||||||
@RabbitListener(queues = {"${spring.profiles.active}-" + RabbitConstants.Queue.CALL_TABLE_PRINT_QUEUE})
|
@RabbitListener(queues = {"${spring.profiles.active}-" + RabbitConstants.Queue.CALL_TABLE_PRINT_QUEUE})
|
||||||
public void callTablePrint(String id) {
|
public void callTablePrint(String id) {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class OTimeTask {
|
|||||||
@Resource
|
@Resource
|
||||||
private CashierCartService cartService;
|
private CashierCartService cartService;
|
||||||
@Resource
|
@Resource
|
||||||
private OrderPaymentService paymentService;
|
private OrderPaymentService orderPaymentService;
|
||||||
@Resource
|
@Resource
|
||||||
private GbOrderService gbOrderService;
|
private GbOrderService gbOrderService;
|
||||||
@Resource
|
@Resource
|
||||||
@@ -117,7 +117,7 @@ public class OTimeTask {
|
|||||||
|
|
||||||
LocalDateTime tenMinutesAgo = LocalDateTime.now().minusMinutes(10);
|
LocalDateTime tenMinutesAgo = LocalDateTime.now().minusMinutes(10);
|
||||||
LocalDateTime thirdDayAgo = LocalDateTime.now().minusDays(3);
|
LocalDateTime thirdDayAgo = LocalDateTime.now().minusDays(3);
|
||||||
List<OrderPayment> list = paymentService.list(QueryWrapper.create()
|
List<OrderPayment> list = orderPaymentService.list(QueryWrapper.create()
|
||||||
.gt(OrderPayment::getUpdateTime, thirdDayAgo)
|
.gt(OrderPayment::getUpdateTime, thirdDayAgo)
|
||||||
.lt(OrderPayment::getUpdateTime, tenMinutesAgo)
|
.lt(OrderPayment::getUpdateTime, tenMinutesAgo)
|
||||||
.in(OrderPayment::getSourceType, ware)
|
.in(OrderPayment::getSourceType, ware)
|
||||||
|
|||||||
@@ -25,23 +25,7 @@ spring:
|
|||||||
port: 5672
|
port: 5672
|
||||||
username: chaozg
|
username: chaozg
|
||||||
password: chaozg123
|
password: chaozg123
|
||||||
# 关键优化:解决MissedHeartbeatException 心跳超时问题
|
|
||||||
connection-timeout: 10000 # 连接超时时间(10秒,避免连接建立过慢)
|
|
||||||
requested-heartbeat: 30 # 心跳间隔调整为30秒(原60秒过长,降低超时概率;过短易误触发)
|
|
||||||
# 自动重连配置(Spring AMQP 自带,关键兜底)
|
|
||||||
publisher-returns: true
|
|
||||||
template:
|
|
||||||
retry:
|
|
||||||
enabled: true # 开启消息发送重试
|
|
||||||
max-attempts: 3 # 最大重试次数
|
|
||||||
initial-interval: 3000 # 首次重试间隔2秒
|
|
||||||
multiplier: 1.5 # 重试间隔倍增因子
|
|
||||||
listener:
|
|
||||||
simple:
|
|
||||||
retry:
|
|
||||||
enabled: true # 开启消费者重试
|
|
||||||
max-attempts: 3 # 消费者最大重试次数
|
|
||||||
acknowledge-mode: auto # 确认模式(可根据业务改为manual)
|
|
||||||
dubbo:
|
dubbo:
|
||||||
application:
|
application:
|
||||||
name: order-server
|
name: order-server
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class FilteredNacosRegistry extends NacosRegistry {
|
|||||||
public void register(URL url) {
|
public void register(URL url) {
|
||||||
// 1. 获取原始注册的方法列表
|
// 1. 获取原始注册的方法列表
|
||||||
String originalMethods = url.getParameter("methods");
|
String originalMethods = url.getParameter("methods");
|
||||||
// log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), originalMethods);
|
log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), originalMethods);
|
||||||
if (originalMethods != null && !originalMethods.isEmpty()) {
|
if (originalMethods != null && !originalMethods.isEmpty()) {
|
||||||
// 2. 过滤黑名单中的方法名
|
// 2. 过滤黑名单中的方法名
|
||||||
List<String> filteredMethods = Arrays.stream(originalMethods.split(","))
|
List<String> filteredMethods = Arrays.stream(originalMethods.split(","))
|
||||||
@@ -67,12 +67,12 @@ public class FilteredNacosRegistry extends NacosRegistry {
|
|||||||
// 3. 处理过滤后的结果
|
// 3. 处理过滤后的结果
|
||||||
if (filteredMethods.isEmpty()) {
|
if (filteredMethods.isEmpty()) {
|
||||||
// 若所有方法都被过滤,直接终止注册(可选:根据业务决定是否保留服务注册)
|
// 若所有方法都被过滤,直接终止注册(可选:根据业务决定是否保留服务注册)
|
||||||
// log.info("【过滤提示】服务 {} 所有方法均被过滤,终止注册", url.getServiceInterface());
|
log.info("【过滤提示】服务 {} 所有方法均被过滤,终止注册", url.getServiceInterface());
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// 替换 URL 中的 methods 参数为过滤后的列表
|
// 替换 URL 中的 methods 参数为过滤后的列表
|
||||||
url = url.addParameter("methods", String.join(",", filteredMethods));
|
url = url.addParameter("methods", String.join(",", filteredMethods));
|
||||||
// log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), filteredMethods);
|
log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), filteredMethods);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.czg.config;
|
package com.czg.config;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import com.mybatisflex.core.audit.AuditManager;
|
import com.mybatisflex.core.audit.AuditManager;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -18,24 +17,8 @@ public class MybatisFlexConfig {
|
|||||||
//设置 SQL 审计收集器
|
//设置 SQL 审计收集器
|
||||||
AuditManager.setMessageCollector(auditMessage ->
|
AuditManager.setMessageCollector(auditMessage ->
|
||||||
log.info("[sql] time: {}, size: {}, sql:\n{}",
|
log.info("[sql] time: {}, size: {}, sql:\n{}",
|
||||||
auditMessage.getElapsedTime(), auditMessage.getQueryCount(), compressSql(auditMessage.getFullSql())));
|
auditMessage.getElapsedTime(), auditMessage.getQueryCount(), auditMessage.getFullSql())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 精简SQL:去除多余换行、制表符、连续空格,保留语法必需空格
|
|
||||||
*
|
|
||||||
* @param originalSql 原始带换行/空格的SQL
|
|
||||||
* @return 精简后的SQL
|
|
||||||
*/
|
|
||||||
public static String compressSql(String originalSql) {
|
|
||||||
if (StrUtil.isBlank(originalSql)) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
// 1. 替换所有换行、制表符为单个空格
|
|
||||||
String tempSql = originalSql.replaceAll("\\r\\n|\\r|\\n|\\t", " ");
|
|
||||||
// 2. 替换多个连续空格为单个空格
|
|
||||||
tempSql = tempSql.replaceAll("\\s+", " ");
|
|
||||||
// 3. 去除首尾空格
|
|
||||||
return tempSql.trim();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,14 @@ import jakarta.annotation.Resource;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author GYJoker
|
* @author GYJoker
|
||||||
@@ -651,106 +650,4 @@ public class RedisService {
|
|||||||
}
|
}
|
||||||
return JSON.parseArray(jsonStr, type);
|
return JSON.parseArray(jsonStr, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static int retryCount = 5;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行任务并保证锁唯一
|
|
||||||
*
|
|
||||||
* @param supplier 业务逻辑
|
|
||||||
* @param lockKey Redis锁的Key
|
|
||||||
* @return 业务逻辑返回值
|
|
||||||
*/
|
|
||||||
public <T> T runFunAndCheckKey(Supplier<T> supplier, String lockKey) {
|
|
||||||
String lockValue = String.valueOf(System.nanoTime() + Thread.currentThread().threadId());
|
|
||||||
try {
|
|
||||||
// 尝试获取锁,超时时间 5 秒,防止死锁
|
|
||||||
boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
|
|
||||||
int count = 0;
|
|
||||||
// 初始等待 10ms
|
|
||||||
int retryDelay = 10;
|
|
||||||
|
|
||||||
while (!lock) {
|
|
||||||
// 最多重试 10 次,大约 10 秒
|
|
||||||
if (count++ > 50) {
|
|
||||||
throw new RuntimeException("系统繁忙, 稍后再试");
|
|
||||||
}
|
|
||||||
Thread.sleep(retryDelay);
|
|
||||||
// 指数退避,最大等待 200ms
|
|
||||||
retryDelay = Math.min(retryDelay * 2, 200);
|
|
||||||
lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行任务
|
|
||||||
return supplier.get();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
throw new RuntimeException("线程被中断", e);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("执行出错:{}", e.getMessage(), e);
|
|
||||||
throw e;
|
|
||||||
} finally {
|
|
||||||
// 释放锁(使用 Lua 脚本确保原子性)
|
|
||||||
unlock(lockKey, lockValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用 Lua 脚本确保释放锁的原子性
|
|
||||||
*
|
|
||||||
* @param lockKey 锁的 Key
|
|
||||||
* @param lockValue 当前线程的锁值
|
|
||||||
*/
|
|
||||||
private void unlock(String lockKey, String lockValue) {
|
|
||||||
String luaScript =
|
|
||||||
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
|
|
||||||
"return redis.call('del', KEYS[1]) " +
|
|
||||||
"else return 0 end";
|
|
||||||
redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class),
|
|
||||||
Collections.singletonList(lockKey), lockValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T, R> R runFunAndRetry(
|
|
||||||
Supplier<R> function,
|
|
||||||
Function<R, Boolean> check, Consumer<R> errFun) {
|
|
||||||
R result = function.get();
|
|
||||||
boolean flag = check.apply(result);
|
|
||||||
|
|
||||||
while (flag && retryCount-- > 0) {
|
|
||||||
result = function.get();
|
|
||||||
flag = check.apply(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flag) {
|
|
||||||
errFun.accept(result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 防抖函数:在指定秒数内相同 Key 的任务只会执行一次
|
|
||||||
*
|
|
||||||
* @param key 防抖使用的 Redis Key
|
|
||||||
* @param seconds 防抖时间(秒)
|
|
||||||
* @param task 要执行的业务逻辑
|
|
||||||
* @return true 执行了任务;false 在防抖期内被拦截
|
|
||||||
*/
|
|
||||||
public boolean debounce(String key, long seconds, Runnable task) {
|
|
||||||
try {
|
|
||||||
Boolean success = redisTemplate.opsForValue().setIfAbsent(
|
|
||||||
key, "1", seconds, TimeUnit.SECONDS
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Boolean.TRUE.equals(success)) {
|
|
||||||
task.run();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("防抖函数执行失败 key={} err={}", key, e.getMessage(), e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,13 +285,4 @@ public class ShopInfoEditDTO {
|
|||||||
*/
|
*/
|
||||||
private Integer isCountStick;
|
private Integer isCountStick;
|
||||||
|
|
||||||
/**
|
|
||||||
* 企业id
|
|
||||||
*/
|
|
||||||
private String weworkId;
|
|
||||||
/**
|
|
||||||
* 企业接入链接
|
|
||||||
*/
|
|
||||||
private String weworkUrl;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
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 jakarta.validation.constraints.NotNull;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 悬浮窗配置 实体类。
|
|
||||||
*
|
|
||||||
* @author ww
|
|
||||||
* @since 2025-12-29
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Table("tb_quick_menu")
|
|
||||||
public class QuickMenu implements Serializable {
|
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
@Id(keyType = KeyType.Auto)
|
|
||||||
private Integer id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 店铺Id
|
|
||||||
*/
|
|
||||||
private Long shopId;
|
|
||||||
/**
|
|
||||||
* 菜单图标
|
|
||||||
*/
|
|
||||||
private String url;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 菜单Id
|
|
||||||
*/
|
|
||||||
@NotNull(message = "关联菜单不能为空")
|
|
||||||
private Long menuId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 排序
|
|
||||||
*/
|
|
||||||
private Integer sort;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 状态 1-启用 0-禁用
|
|
||||||
*/
|
|
||||||
private Integer status;
|
|
||||||
|
|
||||||
@Column(onInsertValue = "now()")
|
|
||||||
private LocalDateTime createTime;
|
|
||||||
|
|
||||||
@Column(onInsertValue = "now()", onUpdateValue = "now()")
|
|
||||||
private LocalDateTime updateTime;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -135,13 +135,5 @@ public class ShopConfig implements Serializable {
|
|||||||
|
|
||||||
private String dingAppKey;
|
private String dingAppKey;
|
||||||
private String dingAppSecret;
|
private String dingAppSecret;
|
||||||
/**
|
|
||||||
* 企业id
|
|
||||||
*/
|
|
||||||
private String weworkId;
|
|
||||||
/**
|
|
||||||
* 企业接入链接
|
|
||||||
*/
|
|
||||||
private String weworkUrl;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -363,15 +363,4 @@ public class ShopInfo implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private BigDecimal amount;
|
private BigDecimal amount;
|
||||||
|
|
||||||
/**
|
|
||||||
* 企业id
|
|
||||||
*/
|
|
||||||
@Column(ignore = true)
|
|
||||||
private String weworkId;
|
|
||||||
/**
|
|
||||||
* 企业接入链接
|
|
||||||
*/
|
|
||||||
@Column(ignore = true)
|
|
||||||
private String weworkUrl;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package com.czg.account.service;
|
|
||||||
|
|
||||||
import com.mybatisflex.core.service.IService;
|
|
||||||
import com.czg.account.entity.QuickMenu;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 悬浮窗配置 服务层。
|
|
||||||
*
|
|
||||||
* @author ww
|
|
||||||
* @since 2025-12-29
|
|
||||||
*/
|
|
||||||
public interface QuickMenuService extends IService<QuickMenu> {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -7,12 +7,10 @@ import com.czg.exception.CzgException;
|
|||||||
import com.czg.market.dto.MkDistributionUserDTO;
|
import com.czg.market.dto.MkDistributionUserDTO;
|
||||||
import com.czg.market.dto.MkDistributionWithdrawFlowDTO;
|
import com.czg.market.dto.MkDistributionWithdrawFlowDTO;
|
||||||
import com.czg.market.entity.MkDistributionConfig;
|
import com.czg.market.entity.MkDistributionConfig;
|
||||||
import com.czg.market.entity.MkDistributionFlow;
|
|
||||||
import com.czg.market.entity.MkDistributionUser;
|
import com.czg.market.entity.MkDistributionUser;
|
||||||
import com.czg.market.vo.DistributionCenterShopVO;
|
import com.czg.market.vo.DistributionCenterShopVO;
|
||||||
import com.czg.market.vo.InviteUserVO;
|
import com.czg.market.vo.InviteUserVO;
|
||||||
import com.czg.order.dto.MkDistributionPayDTO;
|
import com.czg.order.dto.MkDistributionPayDTO;
|
||||||
import com.czg.order.entity.OrderInfo;
|
|
||||||
import com.mybatisflex.core.paginate.Page;
|
import com.mybatisflex.core.paginate.Page;
|
||||||
import com.mybatisflex.core.service.IService;
|
import com.mybatisflex.core.service.IService;
|
||||||
|
|
||||||
@@ -125,8 +123,6 @@ public interface MkDistributionUserService extends IService<MkDistributionUser>
|
|||||||
|
|
||||||
void refund(Long orderId, String orderNo);
|
void refund(Long orderId, String orderNo);
|
||||||
|
|
||||||
void distributionUserAmount(MkDistributionFlow item, OrderInfo orderInfo);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发放分销奖励
|
* 发放分销奖励
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,267 @@
|
|||||||
|
|
||||||
|
package com.czg.order.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单表 实体类。
|
||||||
|
*
|
||||||
|
* @author ww
|
||||||
|
* @since 2025-02-13
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class OrderInfoDTO implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单编号
|
||||||
|
* pc 收银机客户端 PC+雪花ID
|
||||||
|
* wechat 微信小程序 WX+雪花ID
|
||||||
|
* alipay 支付宝小程序 ALI+雪花ID
|
||||||
|
* admin-pc PC管理端 WEB+雪花ID
|
||||||
|
* admin-app APP管理端 APP+雪花ID
|
||||||
|
*/
|
||||||
|
private String orderNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 店铺Id
|
||||||
|
*/
|
||||||
|
private Long shopId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户Id user_info表的id
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退单金额
|
||||||
|
*/
|
||||||
|
private BigDecimal refundAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单原金额 不含折扣价格
|
||||||
|
*/
|
||||||
|
private BigDecimal originAmount;
|
||||||
|
/**
|
||||||
|
* 抹零金额 减免多少钱
|
||||||
|
*/
|
||||||
|
private BigDecimal roundAmount;
|
||||||
|
/**
|
||||||
|
* 优惠总金额
|
||||||
|
*/
|
||||||
|
private BigDecimal discountAllAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单金额 (扣除各类折扣)
|
||||||
|
*/
|
||||||
|
private BigDecimal orderAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实际支付金额
|
||||||
|
*/
|
||||||
|
private BigDecimal payAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 积分抵扣金额
|
||||||
|
*/
|
||||||
|
private BigDecimal pointsDiscountAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用的积分数量
|
||||||
|
*/
|
||||||
|
private Integer pointsNum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品优惠券抵扣金额
|
||||||
|
*/
|
||||||
|
private BigDecimal productCouponDiscountAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户使用的卡券
|
||||||
|
*/
|
||||||
|
private String couponInfoList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 满减活动抵扣金额
|
||||||
|
*/
|
||||||
|
private BigDecimal discountActAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 折扣金额
|
||||||
|
*/
|
||||||
|
private BigDecimal discountAmount;
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * 折扣比例
|
||||||
|
// */
|
||||||
|
// private BigDecimal discountRatio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打包费
|
||||||
|
*/
|
||||||
|
private BigDecimal packFee;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 台桌Id
|
||||||
|
*/
|
||||||
|
private String tableCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 台桌名称
|
||||||
|
*/
|
||||||
|
private String tableName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单类型-
|
||||||
|
* cash收银(除小程序以外 都属于收银)
|
||||||
|
* miniapp小程序
|
||||||
|
*/
|
||||||
|
private String orderType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台类型 pc 收银机客户端 wechat 微信小程序 alipay 支付宝小程序 admin-pc PC管理端 admin-app APP管理端
|
||||||
|
*/
|
||||||
|
private String platformType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用餐模式 堂食 dine-in 外带 take-out 外卖 take-away
|
||||||
|
*/
|
||||||
|
private String dineMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付模式:
|
||||||
|
* 后付费 after-pay
|
||||||
|
* 先付费 before-pay
|
||||||
|
* 无桌码 no-table
|
||||||
|
*/
|
||||||
|
private String payMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付类型
|
||||||
|
* 主扫 main-scan
|
||||||
|
* 被扫 back-scan
|
||||||
|
* 微信小程序 wechat-mini
|
||||||
|
* 支付宝小程序 alipay-mini
|
||||||
|
* 会员支付 vip-pay
|
||||||
|
* 现金支付 cash-pay
|
||||||
|
*/
|
||||||
|
private String payType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态: unpaid-待支付;in-production 制作中;wait_out 待取餐;;done-订单完成;refunding-申请退单;refund-退单;part_refund 部分退单;cancelled-取消订单
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 折扣信息 json
|
||||||
|
*/
|
||||||
|
private String discountInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 限时折扣信息 json
|
||||||
|
*/
|
||||||
|
private String limitRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否支持退款,1支持退单, 0不支持退单
|
||||||
|
*/
|
||||||
|
private Integer refundAble;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付时间
|
||||||
|
*/
|
||||||
|
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime paidTime;
|
||||||
|
|
||||||
|
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付订单号
|
||||||
|
* tb_order_payment.id
|
||||||
|
* tb_shop_user_flow.id
|
||||||
|
*/
|
||||||
|
private Long payOrderId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 交易日期
|
||||||
|
*/
|
||||||
|
private String tradeDay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取餐码
|
||||||
|
*/
|
||||||
|
private String takeCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 员工id
|
||||||
|
*/
|
||||||
|
private Long staffId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前订单下单次数
|
||||||
|
*/
|
||||||
|
private Integer placeNum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用餐人数
|
||||||
|
*/
|
||||||
|
private Integer seatNum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 餐位费
|
||||||
|
*/
|
||||||
|
private BigDecimal seatAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退款备注
|
||||||
|
*/
|
||||||
|
private String refundRemark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否使用了霸王餐
|
||||||
|
*/
|
||||||
|
private Integer isFreeDine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否等叫 0 否 1 等叫
|
||||||
|
*/
|
||||||
|
private Integer isWaitCall;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 挂账人id
|
||||||
|
*/
|
||||||
|
private Long creditBuyerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否回收站 0-否,1回收站
|
||||||
|
*/
|
||||||
|
private Integer isDel;
|
||||||
|
private String failMsg;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.czg.order.entity;
|
package com.czg.order.entity;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.alibaba.fastjson2.JSONArray;
|
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
|
||||||
import com.czg.order.dto.LimitRateDTO;
|
import com.czg.order.dto.LimitRateDTO;
|
||||||
import com.mybatisflex.annotation.Column;
|
import com.mybatisflex.annotation.Column;
|
||||||
import com.mybatisflex.annotation.Id;
|
import com.mybatisflex.annotation.Id;
|
||||||
@@ -300,10 +298,8 @@ public class OrderInfo implements Serializable {
|
|||||||
private Integer isDel;
|
private Integer isDel;
|
||||||
|
|
||||||
private String failMsg;
|
private String failMsg;
|
||||||
/**
|
|
||||||
* 打印状态 Json格式
|
|
||||||
*/
|
|
||||||
private String printStatus;
|
|
||||||
|
|
||||||
|
|
||||||
public String getRefundRemark() {
|
public String getRefundRemark() {
|
||||||
@@ -346,41 +342,4 @@ public class OrderInfo implements Serializable {
|
|||||||
// 如果需要加上抹零金额,可以取消下面这行注释
|
// 如果需要加上抹零金额,可以取消下面这行注释
|
||||||
// .add(this.getRoundAmount() != null ? this.getRoundAmount() : BigDecimal.ZERO);
|
// .add(this.getRoundAmount() != null ? this.getRoundAmount() : BigDecimal.ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JSONArray getPrintStatusAsArray() {
|
|
||||||
if (StrUtil.isBlank(printStatus)) {
|
|
||||||
return new JSONArray();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return JSONArray.parseArray(printStatus.trim());
|
|
||||||
} catch (Exception e) {
|
|
||||||
return new JSONArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void upPrintStatus(JSONObject printJson, boolean isPrintSuccess) {
|
|
||||||
String currentDeviceId = printJson.getString("id");
|
|
||||||
JSONArray oldPrintStatusArray = getPrintStatusAsArray();
|
|
||||||
// 3. 初始化新的打印状态JSON数组(用于存储处理后的结果)
|
|
||||||
JSONArray newPrintStatusArray = new JSONArray();
|
|
||||||
// 场景1:打印成功 - 移除原有数组中与当前设备ID一致的记录,保留其余记录
|
|
||||||
if (oldPrintStatusArray != null && !oldPrintStatusArray.isEmpty()) {
|
|
||||||
for (int i = 0; i < oldPrintStatusArray.size(); i++) {
|
|
||||||
JSONObject deviceObj = oldPrintStatusArray.getJSONObject(i);
|
|
||||||
String deviceId = deviceObj.getString("id");
|
|
||||||
// 仅保留非当前设备ID的记录
|
|
||||||
if (currentDeviceId != null && !currentDeviceId.equals(deviceId)) {
|
|
||||||
newPrintStatusArray.add(deviceObj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isPrintSuccess) {
|
|
||||||
newPrintStatusArray.add(printJson);
|
|
||||||
}
|
|
||||||
if (!newPrintStatusArray.isEmpty()) {
|
|
||||||
printStatus = newPrintStatusArray.toJSONString();
|
|
||||||
} else {
|
|
||||||
printStatus = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import com.czg.order.entity.PrintMachineLog;
|
|||||||
* @since 2025-03-11
|
* @since 2025-03-11
|
||||||
*/
|
*/
|
||||||
public interface PrintMachineLogService extends IService<PrintMachineLog> {
|
public interface PrintMachineLogService extends IService<PrintMachineLog> {
|
||||||
void save(Long orderId, PrintMachine config, String bizType, String printContent, String respJson);
|
void save(PrintMachine config, String bizType, String printContent, Object respJson);
|
||||||
|
|
||||||
void save(PrintMachine config, String bizType, String printContent, String respJson);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,6 @@ public class OrderDetailSmallVO implements Serializable {
|
|||||||
private LocalDateTime startOrderTime;
|
private LocalDateTime startOrderTime;
|
||||||
private LocalDateTime dishOutTime;
|
private LocalDateTime dishOutTime;
|
||||||
private LocalDateTime foodServeTime;
|
private LocalDateTime foodServeTime;
|
||||||
private Integer isTemporary;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,11 +133,6 @@ public class OrderInfoVo implements Serializable {
|
|||||||
* 备注
|
* 备注
|
||||||
*/
|
*/
|
||||||
private String remark;
|
private String remark;
|
||||||
/**
|
|
||||||
* 打印状态 Json格式
|
|
||||||
* [{"id":"124","name":"111","time":"2025-12-29 11:05:18"},{"id":"111","name":"标签","time":"2025-12-29 11:05:30"}]
|
|
||||||
*/
|
|
||||||
private String printStatus;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否使用了霸王餐
|
* 是否使用了霸王餐
|
||||||
|
|||||||
@@ -42,6 +42,8 @@
|
|||||||
<pinyin.version>2.5.1</pinyin.version>
|
<pinyin.version>2.5.1</pinyin.version>
|
||||||
<IJPay.version>2.9.10</IJPay.version>
|
<IJPay.version>2.9.10</IJPay.version>
|
||||||
<netty.version>4.1.128.Final</netty.version>
|
<netty.version>4.1.128.Final</netty.version>
|
||||||
|
<wechatpay.version>0.2.17</wechatpay.version>
|
||||||
|
<apipay-v3.version>3.1.65.ALL</apipay-v3.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@@ -268,6 +270,18 @@
|
|||||||
<artifactId>netty-codec-mqtt</artifactId>
|
<artifactId>netty-codec-mqtt</artifactId>
|
||||||
<version>${netty.version}</version>
|
<version>${netty.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.wechatpay-apiv3</groupId>
|
||||||
|
<artifactId>wechatpay-java</artifactId>
|
||||||
|
<version>${wechatpay.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alipay.sdk</groupId>
|
||||||
|
<artifactId>alipay-sdk-java-v3</artifactId>
|
||||||
|
<version>${apipay-v3.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|||||||
35
cash-sdk/aggregation-pay/pom.xml
Normal file
35
cash-sdk/aggregation-pay/pom.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.czg</groupId>
|
||||||
|
<artifactId>cash-sdk</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>aggregation-pay</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>聚合支付</name>
|
||||||
|
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.wechatpay-apiv3</groupId>
|
||||||
|
<artifactId>wechatpay-java</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alipay.sdk</groupId>
|
||||||
|
<artifactId>alipay-sdk-java-v3</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.czg.alipay;
|
||||||
|
|
||||||
|
import com.alipay.v3.ApiClient;
|
||||||
|
import com.alipay.v3.ApiException;
|
||||||
|
import com.alipay.v3.Configuration;
|
||||||
|
import com.alipay.v3.api.AlipayOpenAgentApi;
|
||||||
|
import com.alipay.v3.model.AlipayOpenAgentCreateDefaultResponse;
|
||||||
|
import com.alipay.v3.model.AlipayOpenAgentCreateModel;
|
||||||
|
import com.alipay.v3.model.AlipayOpenAgentCreateResponseModel;
|
||||||
|
import com.alipay.v3.model.ContactModel;
|
||||||
|
import com.alipay.v3.util.model.AlipayConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝进件管理
|
||||||
|
*
|
||||||
|
* <a href="https://opendocs.alipay.com/isv/03l20p?pathHash=07138a27">...</a>
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/29 14:11
|
||||||
|
*/
|
||||||
|
public class AlipayEntryManager {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws ApiException {
|
||||||
|
ApiClient defaultClient = Configuration.getDefaultApiClient();
|
||||||
|
// 初始化alipay参数(全局设置一次)
|
||||||
|
AlipayConfig alipayConfig = new AlipayConfig();
|
||||||
|
alipayConfig.setServerUrl("https://openapi.alipay.com");
|
||||||
|
alipayConfig.setAppId("2021006121646825");
|
||||||
|
alipayConfig.setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiQkrz+emAuS1mB3KKDOMmAZRd/BlPbh7fAIHAqAj1+QCZNcV3o2BTLIIqnuKpSlFXDG3uDzp2VsBxcizXuBbFyPGylnD9CgCj5abyh3+FIHPAZ2IM3TtpqImZ0TSPGXrMli4Nir7MvZktgccCqQKCC4o6iaDGz+UwWwJUIPna8fm2tiTZ+KH150CZbKVj4ZGNpBh5XSV/1dRgyQIV9D/EwSbkZ0n6VgKQLJBi0C2UE3QB17aL1Ir6+gDXIDbknN8O7GUD3aMGdThYdSRUb5wp9CZ5qfV7vCS/CgaRo38nhH3NOzkTL+7v0m1ZDHPmqEkn9VzZN6sCQdL7PoAOjHOCwIDAQAB");
|
||||||
|
alipayConfig.setPrivateKey("MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCz3FkdffxZibdwis9W7eOW1dEjGAbSvRDL2ikfeCIW5KZNoIjUqxI0mIoUlLRRKO71QLHZS1Vb2aJp8jeOAqIPa8e76HTneQEzk3FGA8gpraSGvbadHWzvxnmYKsts1TBiEZQL82ySJXhQTJvZ6jyDM7s6wHHUnrH+Qi29QpppQ1sxsoJeCtajUgRg3btD6XbBcyFAX3pzM56Kw9eaIjZoD8WToZOM/Y3sqNL2uo8lLqcIpTrI7Pq5ZOspmBQ+t8v3rS9IdDZZMvd0trzS67AXwHz8rKPBT/lL1A4iHnXCHUvktusX1fPs3/RGY/a7PIddaBLnfY0GcueE16K7QcrXAgMBAAECggEAbSdT2eckp75BWoaTcGEs1tRqeM7TDT/6moyKmnOQ1K3tE31SrSYpBUxxuC3LBNo/sw2RIZtrcTOyMnPyLTgB3DP/4lUf5X51MTTQ8LnI1ypvh+pIki9Sdm3QS33lOOZk149tdpdDk6ozyx/DEcvq74EMpoo2SuAIi5LkKVDrXuehvGA/WeXtpmuPgqRFdIA+JBlA3knHk5XEQY/k2Y31gq5oCwNL9iT9OAZqVkukE6EnvCXE9t2rAV4/snYilaf/UaO+ktgEwSbPBQ8YKlovDAarMBbGtgr6E174A9djlPyR+W/fgx8rlTwSWtieb9MkO8LN3KSxgVs0kY5U8OHg8QKBgQDljJq9kTFHare+W/fAXdUy3tJprfNQCAii6s+GuDfTQiviVQDtWmdtHAN+xU3to7MepvVhwHsqtQnZXKTtZuwwxn82FNl7A5RYD3GVFW+wG6AsGLIdESrWxySoL6Kx8QmNpMEVg8acT/ywzW/RnUXS5vU7GIi8GA0vtyBo24R9KQKBgQDIlf/R9+iNk9oXlbB/k4um9eVvBBS7l5cx4E5Id5Dpp4kGZfPZEa7oDsEUstZZM9mgQLJK/noNWbcf0+BohCR5ux7SC12qIoxwN3k4BzTDqrS8BzFuVVp5PELUsf/uCbRn05iMzpiDUhj3Vde04wvjHYIobfKlZO2HeSWXCpUH/wKBgQC8wSuU6ck90pEY5QMKmZ3wYK1g3PsQOirv3Gmde+nbu7PePsuuYQJfBAQTwCZeXJezgtKP+PjOm2Nn6vhrhpB9YxvD2s0ijET1TG23i5L1myHQYNZFdJJnXgXUjqcX7v5ODMYA7QTqEBPXRnbGRK7fx66rU3dMQ/LD46+wyaFeUQKBgA4QTk53dkuu6SSsLyLSwoDjTsHY5Gc+urAZjQORtoxbXcUgEtfOYJgOqMT9wP+iHgkZYCbX7tDO0IMfxOUvFqueTgvmFhwergAUM6CVCMMLTf689l9JBr3nVrw4+rvC3G5HLLP6rEDQ2cVFtIkPPj8fS4fwJYopKGpOOS9843QbAoGBAMoHH8LqoZ50FLsospx/hJe24Cd8wCgQTXSa/hMqNZ1999JDhftMt7R0ZdB1he2LReACe0K9ntBU4H4u225zZ3wZlyOfoyerAHuLK/ysNlgIIzblZlOxbBJ64Kul8leXzlYy3tOZuZ997KqBcWPCE3LUBBNvM6E3blJUnlmJAVoi");
|
||||||
|
defaultClient.setAlipayConfig(alipayConfig);
|
||||||
|
AlipayOpenAgentApi api = new AlipayOpenAgentApi();
|
||||||
|
AlipayOpenAgentCreateModel data = new AlipayOpenAgentCreateModel();
|
||||||
|
ContactModel contactInfo = new ContactModel();
|
||||||
|
contactInfo.setContactEmail("zhangsan@alipy.com");
|
||||||
|
contactInfo.setContactName("张三");
|
||||||
|
contactInfo.setContactMobile("18866668888");
|
||||||
|
data.setContactInfo(contactInfo);
|
||||||
|
data.setOrderTicket("00ee2d475f374ad097ee0f1ac223fX00");
|
||||||
|
data.setAccount("test@alipay.com");
|
||||||
|
try {
|
||||||
|
AlipayOpenAgentCreateResponseModel response = api.create(data);
|
||||||
|
System.out.println(response);
|
||||||
|
} catch (ApiException e) {
|
||||||
|
AlipayOpenAgentCreateDefaultResponse errorObject = (AlipayOpenAgentCreateDefaultResponse) e.getErrorObject();
|
||||||
|
System.out.println("调用失败:" + errorObject);
|
||||||
|
System.out.println("调用失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.czg.alipay.dto;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 联系人信息
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/29 14:21
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AlipayCreateContactInfoDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 联系人名称
|
||||||
|
*/
|
||||||
|
@JSONField(name = "contact_name")
|
||||||
|
private String contactName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 联系人手机号
|
||||||
|
*/
|
||||||
|
@JSONField(name = "contact_mobile")
|
||||||
|
private String contactMobile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 联系人邮箱
|
||||||
|
*/
|
||||||
|
@JSONField(name = "contact_email")
|
||||||
|
private String contactEmail;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.czg.alipay.dto;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开启代商户签约、创建应用事务
|
||||||
|
* 在 ISV 代商户进行应用创建、产品签约时,用于开启一个操作事务,必须是第一个调用的接口。
|
||||||
|
* 场景1:ISV 代商户进行应用创建、产品签约,最后提交事务后需要商户确认才能完成流程;
|
||||||
|
* 场景2:服务市场订购及授权,使用订单授权凭证order_ticket开启预授权模式,该模式下提交事务后无需商户确认。
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/29 14:19
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AlipayCreateDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* isv代操作的商户账号,可以是支付宝账号,也可以是pid(2088开头)
|
||||||
|
*/
|
||||||
|
@JSONField(name = "account")
|
||||||
|
private String account;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 商户联系人信息,包含联系人名称、手机、邮箱信息。联系人信息将用于接受签约后的重要通知,如确认协议、到期提醒等。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "contact_info")
|
||||||
|
private AlipayCreateContactInfoDto contactInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 订单授权凭证。若传入本参数,则对应事务提交后进入预授权模式。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "order_ticket")
|
||||||
|
private String orderTicket;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.czg.wechat;
|
||||||
|
|
||||||
|
import com.czg.wechat.dto.config.WechatPayConfigDto;
|
||||||
|
import com.wechat.pay.java.core.Config;
|
||||||
|
import com.wechat.pay.java.core.RSAPublicKeyConfig;
|
||||||
|
import com.wechat.pay.java.service.file.FileUploadService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付 配置
|
||||||
|
*
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 09:33
|
||||||
|
*/
|
||||||
|
public class WechatConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取微信支付配置
|
||||||
|
* @param dto 微信支付配置
|
||||||
|
* @return 微信支付配置
|
||||||
|
*/
|
||||||
|
public static Config getRsaConfig(WechatPayConfigDto dto) {
|
||||||
|
return new RSAPublicKeyConfig.Builder()
|
||||||
|
.merchantId(dto.getMerchantId())
|
||||||
|
.privateKey(dto.getPrivateKey())
|
||||||
|
.publicKey(dto.getPublicKey())
|
||||||
|
.publicKeyId(dto.getPublicKeyId())
|
||||||
|
.merchantSerialNumber(dto.getSerialNumber())
|
||||||
|
.apiV3Key(dto.getApiV3Key())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FileUploadService getFileUploadService(Config config) {
|
||||||
|
return new FileUploadService.Builder()
|
||||||
|
.config(config)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件上传服务
|
||||||
|
* @param dto 微信支付配置
|
||||||
|
* @return 文件上传服务
|
||||||
|
*/
|
||||||
|
public static FileUploadService getFileUploadService(WechatPayConfigDto dto) {
|
||||||
|
return getFileUploadService(getRsaConfig(dto));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.czg.wechat;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付加解密
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 10:58
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class WechatEncrypt {
|
||||||
|
|
||||||
|
public static String rsaEncryptOaep(String message, PublicKey publicKey) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||||
|
byte[] data = message.getBytes(StandardCharsets.UTF_8);
|
||||||
|
byte[] cipherData = cipher.doFinal(data);
|
||||||
|
return Base64.getEncoder().encodeToString(cipherData);
|
||||||
|
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||||
|
log.error("当前Java环境不支持RSA v1.5/OAEP");
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
log.error("无效的证书");
|
||||||
|
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
||||||
|
log.error("加密原串的长度不能超过214字节");
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getFileBytesSha256(byte[] input) throws NoSuchAlgorithmException {
|
||||||
|
return bytesToHex(calculateSha256(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心方法:计算字节数组的 SHA-256 哈希值
|
||||||
|
* @param input 待计算的字节数组
|
||||||
|
* @return SHA-256 哈希值(32 字节的字节数组)
|
||||||
|
*/
|
||||||
|
private static byte[] calculateSha256(byte[] input) throws NoSuchAlgorithmException {
|
||||||
|
// 获取 SHA-256 消息摘要实例(Java 21 完全兼容)
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
// 计算哈希值
|
||||||
|
return digest.digest(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 辅助方法:将字节数组转换为十六进制字符串(SHA-256 结果常用格式)
|
||||||
|
* @param bytes 待转换的字节数组
|
||||||
|
* @return 十六进制字符串(SHA-256 对应 64 位字符串)
|
||||||
|
*/
|
||||||
|
private static String bytesToHex(byte[] bytes) {
|
||||||
|
StringBuilder hexString = new StringBuilder();
|
||||||
|
// Java 21 可使用 var 简化声明
|
||||||
|
for (var b : bytes) {
|
||||||
|
// 将字节转换为两位十六进制数,不足补 0
|
||||||
|
String hex = Integer.toHexString(0xff & b);
|
||||||
|
if (hex.length() == 1) {
|
||||||
|
hexString.append('0');
|
||||||
|
}
|
||||||
|
hexString.append(hex);
|
||||||
|
}
|
||||||
|
return hexString.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
package com.czg.wechat;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.alibaba.fastjson2.JSONWriter;
|
||||||
|
import com.czg.wechat.dto.config.WechatPayConfigDto;
|
||||||
|
import com.czg.wechat.dto.req.entry.*;
|
||||||
|
import com.czg.wechat.dto.req.entry.business.WechatEntryBusinessReqDto;
|
||||||
|
import com.czg.wechat.dto.req.entry.business.WechatEntryIdentityReqDto;
|
||||||
|
import com.czg.wechat.dto.req.entry.business.WechatEntryLicenseReqDto;
|
||||||
|
import com.czg.wechat.dto.req.entry.business.sales.WechatEntrySalesInfoReqDto;
|
||||||
|
import com.czg.wechat.dto.req.entry.business.sales.WechatEntryStoreInfoReqDto;
|
||||||
|
import com.wechat.pay.java.core.Config;
|
||||||
|
import com.wechat.pay.java.core.cipher.PrivacyEncryptor;
|
||||||
|
import com.wechat.pay.java.service.file.FileUploadService;
|
||||||
|
import com.wechat.pay.java.service.file.model.FileUploadResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付进件 管理
|
||||||
|
* 参考地址 <a href="https://pay.weixin.qq.com/doc/v3/partner/4012719997">...</a>
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 10:57
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class WechatEntryManager {
|
||||||
|
|
||||||
|
public static void queryBankList(WechatPayConfigDto configDto, Integer offset, Integer limit) {
|
||||||
|
WechatReqUtils.getReq(configDto, "/v3/capital/capitallhh/banks/personal-banking", Map.of("offset", offset, "limit", limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户进件
|
||||||
|
*
|
||||||
|
* @param configDto 配置
|
||||||
|
* @param reqDto 请求参数
|
||||||
|
*/
|
||||||
|
public static void entryMerchant(WechatPayConfigDto configDto, WechatEntryReqDto reqDto) {
|
||||||
|
String params = JSONObject.toJSONString(reqDto, JSONWriter.Feature.IgnoreEmpty);
|
||||||
|
|
||||||
|
// String string = WechatReqUtils.encryptReqParam(configDto, "/v3/applyment4sub/applyment/", params);
|
||||||
|
|
||||||
|
WechatReqUtils.postReq(configDto, "/v3/applyment4sub/applyment/", params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传图片
|
||||||
|
*
|
||||||
|
* @param configDto 配置
|
||||||
|
* @param url 图片URL
|
||||||
|
* @return 图片ID
|
||||||
|
*/
|
||||||
|
public static String uploadImage(WechatPayConfigDto configDto, String url) {
|
||||||
|
// 校验入参
|
||||||
|
if (configDto == null || url == null || url.trim().isEmpty()) {
|
||||||
|
log.error("上传图片失败:配置或URL参数为空");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
FileUploadService service = WechatConfig.getFileUploadService(configDto);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取图片字节数组
|
||||||
|
byte[] bytes = downloadImage(url);
|
||||||
|
if (bytes.length == 0) {
|
||||||
|
log.error("下载的图片内容为空,URL:{}", url);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 计算SHA256
|
||||||
|
String sha256Hex = WechatEncrypt.getFileBytesSha256(bytes);
|
||||||
|
|
||||||
|
// 3. 构造元数据(从URL提取文件名,或自定义)
|
||||||
|
JSONObject meta = new JSONObject();
|
||||||
|
meta.put("sha256", sha256Hex);
|
||||||
|
// 从URL提取文件名,若提取失败则使用默认名
|
||||||
|
String fileName = extractFileNameFromUrl(url);
|
||||||
|
meta.put("filename", fileName);
|
||||||
|
|
||||||
|
// 4. 上传图片到微信接口
|
||||||
|
FileUploadResponse uploadResponse = service.uploadImage(
|
||||||
|
configDto.getDomain() + "/v3/merchant/media/upload",
|
||||||
|
meta.toJSONString(),
|
||||||
|
fileName,
|
||||||
|
bytes
|
||||||
|
);
|
||||||
|
|
||||||
|
return uploadResponse.getMediaId();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("微信上传图片报错,URL:{},错误信息:{}", url, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] downloadImage(String url) throws Exception {
|
||||||
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(url))
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<byte[]> response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
|
||||||
|
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
throw new RuntimeException("Failed to download image, status code: " + response.statusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.body();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从URL中提取文件名
|
||||||
|
*
|
||||||
|
* @param url 图片URL
|
||||||
|
* @return 提取的文件名,失败则返回默认名
|
||||||
|
*/
|
||||||
|
private static String extractFileNameFromUrl(String url) {
|
||||||
|
try {
|
||||||
|
if (url.contains("/")) {
|
||||||
|
String fileName = url.substring(url.lastIndexOf("/") + 1);
|
||||||
|
// 如果文件名包含参数,截取?之前的部分
|
||||||
|
if (fileName.contains("?")) {
|
||||||
|
fileName = fileName.substring(0, fileName.indexOf("?"));
|
||||||
|
}
|
||||||
|
// 如果提取的文件名有效,直接返回
|
||||||
|
if (fileName.contains(".")) {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("提取文件名失败,使用默认文件名", e);
|
||||||
|
}
|
||||||
|
// 默认文件名
|
||||||
|
return "upload_" + System.currentTimeMillis() + ".png";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
WechatPayConfigDto dto = new WechatPayConfigDto()
|
||||||
|
.setMerchantId("1643779408")
|
||||||
|
.setApiV3Key("a92baac5eb7a36ed8ec198113e769a03")
|
||||||
|
.setSerialNumber("4DE9BAC2EA584C3F274F694C9753CA814C4E9BF4")
|
||||||
|
.setPublicKey("""
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtbeWXEEjBaYtw2OyM+SC
|
||||||
|
aCEMbryRXi4duKxx3vYG4rUVix+d5/Jz7Khev4Upml9zC+Xxvv/G9bHWAUyolzqD
|
||||||
|
wefahGurIxr43r4GJVnQ4i5G6BbBVw5d4Vuz0y/9Zd14zmc/EmBpT0Ml26H7tOZl
|
||||||
|
n1LSbQ4xNFkrRKrNEcExBLxkCd+K5K2TREZznywIi0izbHImvuzM8IneuR51FiqK
|
||||||
|
pdFnAjTwb126EIj6ECkL6KLCl8RWqpfiX8SFiolSQLs1/w79O0sIUk96X2zWpnoW
|
||||||
|
rTDFatPif/VEKl3y2dTlxxDxoZtVi48yTDW00OMzVl5D67oX3FVK0KsjHJSCfGlZ
|
||||||
|
6wIDAQAB
|
||||||
|
-----END PUBLIC KEY-----""")
|
||||||
|
.setPrivateKey("""
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDqnAZhTxT572fo
|
||||||
|
6wvSr8Rt0vRXg+EFKC6UvUiNE24oocQU5fjedX9KL/+fmoqay/xqIxvxvCNFTs4J
|
||||||
|
zlMqavSl6bMWCpjvf/Ry82JmN1v2kJEO4lgP8BsEiqlUpObCH8BMAVUOn1j+9q4Q
|
||||||
|
uZJJcbtRvec2fNweDM8EJp4B7RlUdDbHm86lfcDVp8iini7VjDp6D7aHT+C8A8OT
|
||||||
|
ugxQIquDec778wVd9r2Sv3+t6rAzFs+n+Zu++2xtFEPhO5N0wjrLHaukl+9crU+1
|
||||||
|
lktjDzcPd07SwGZ+A+3BTmW3UCramI3506e/3MWBECB7ge+gM4URAV0nJJCLH/Im
|
||||||
|
WgEvPMr5AgMBAAECggEBAKv+wraoMWqiVv1tA/froAgbtcJLDranJK8qrXuvmPz0
|
||||||
|
yzm+91qvrSgIVFEADUk67swo/R2Vng37nhWWS2Y3jy/rSr2H+2Lp3Z5ATA0/3I3A
|
||||||
|
onfU/FaC4mvL9CP32KzMdj/CYkccDzSsSCQ+x75MQNXGcTGDDCSDo2kZnpEu73j3
|
||||||
|
aqvO1jbqTGWigRfjOIaIhStjQIT8nEm/3mJ4f5dM9M6FMz33mhax8EahSgvdahYB
|
||||||
|
t45iaGOWw81OJhmry47EvpwjXBl7jtO2HX3LiLbq5Ebcwu1zqDz5NM7ttnnGAqWC
|
||||||
|
6y7JN5Vt4wPYrCydiUDe7dj0looffr2yw6MkNfYjLGECgYEA+FAvbEInQEi9YguS
|
||||||
|
CQtLHngqvYeai66tvyykog9o38KHnPGx2Myf+rn/Hcp7KNRfjd5G34CCNg7KLnrx
|
||||||
|
YIYh6+2bY3jirzdYUxuNKGbvM4gky/6M/P9zHF/uALKOE02yArdqemf4UxUvrYCc
|
||||||
|
JdXsAMqvbpdvW1aGgNRB32YCkG0CgYEA8d89vawsCjNCEUh0VWTMoBLFoex3qBMZ
|
||||||
|
rfzYQeBo6bDCRlAlUVzl+ouBUxSYxP/U8rzeNaRzGUwRLmlGMjyIr58FBlHsg2cR
|
||||||
|
DlsX3HVCUjpS6sgPXOqakdiLfhMcHZAspislSyVfeS3TbUWiA45+5PuNUq+EZYwl
|
||||||
|
ESsB1+yfRT0CgYEA0Ct49ksnWM8iZbXJgeeD3FFlk2rBd2TDqEem5W4Bv8T3p+0/
|
||||||
|
6b7yR2HyrGj5gys3yFmWFP1JLESN3xWWkhMhEQcrg+LuN3Iwi8vHNR3GXu892f7W
|
||||||
|
96q4OAt8Hf2S+j/igkB99YyANDbIt63gOh/zMF67X/14j5wkOpC3gK+maqkCgYEA
|
||||||
|
s7nIrPoUt3ejLiiCmTmPe5q3VDzcJP4cZNau8zSHgK6hjZHcSPsYwPWMoWl6o1fe
|
||||||
|
qoiBLacHB9MoKS58xLOKdcVZ/Ho/ntylJd+2eVCAeY1xM5h5IfgJ5znbXVFh4O3S
|
||||||
|
357L1Wzt5qOQqW/GlZH65LevKbPWU4axvHISqpnfN5kCgYEAqiqLuAPu84VSsqsE
|
||||||
|
GFh25t+1f0JY1sNfilE3/t9AdQeeCFv/5z9KB1kMt3a5ZFeVonsFIvCyaOJjhSkj
|
||||||
|
4HCB94vWS7NuN5G9r874lMaPbZYQGwrcVaf265tN7cYYr3gUx1qB6+u+fh/kcft1
|
||||||
|
BBmTzhb0zp5k8ngwAkA1g/LK204=
|
||||||
|
-----END PRIVATE KEY-----""")
|
||||||
|
.setPublicKeyId("PUB_KEY_ID_0116437794082025111000382377001000")
|
||||||
|
.setDomain("https://api.mch.weixin.qq.com");
|
||||||
|
|
||||||
|
queryBankList(dto, 0, 10);
|
||||||
|
|
||||||
|
// String string = uploadImage(dto, "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/indexs/shuangbackground.png");
|
||||||
|
// log.info("图片上传成功:{}", string);
|
||||||
|
|
||||||
|
Config config = WechatConfig.getRsaConfig(dto);
|
||||||
|
PrivacyEncryptor encryptor = config.createEncryptor();
|
||||||
|
|
||||||
|
WechatEntryReqDto reqDto = new WechatEntryReqDto()
|
||||||
|
.setBusinessCode("MER_20231025110010000010000000000001");
|
||||||
|
|
||||||
|
WechatEntryContactReqDto contactInfo = new WechatEntryContactReqDto()
|
||||||
|
.setContactType("LEGAL")
|
||||||
|
.setContactName(encryptor.encrypt("张三"))
|
||||||
|
.setContactIdType("IDCARD")
|
||||||
|
.setContactIdNumber(encryptor.encrypt("110101199001011234"))
|
||||||
|
.setContactIdDocCopy("https://czg-qr-order.oss-cn-beijing.aliyuncs.com/indexs/shuangbackground.png")
|
||||||
|
.setContactIdDocCopyBack("https://czg-qr-order.oss-cn-beijing.aliyuncs.com/indexs/shuangbackground.png")
|
||||||
|
.setContactPeriodBegin("2023-10-25")
|
||||||
|
.setContactPeriodEnd("2024-10-25")
|
||||||
|
.setMobilePhone(encryptor.encrypt("13888888888"))
|
||||||
|
.setContactEmail(encryptor.encrypt("123456@qq.com"));
|
||||||
|
|
||||||
|
reqDto.setContactInfo(contactInfo);
|
||||||
|
|
||||||
|
WechatEntrySubjectReqDto subjectInfo = new WechatEntrySubjectReqDto()
|
||||||
|
.setSubjectType("SUBJECT_TYPE_INDIVIDUAL");
|
||||||
|
|
||||||
|
WechatEntryLicenseReqDto licenseInfo = new WechatEntryLicenseReqDto()
|
||||||
|
.setLicenseCopy("https://czg-qr-order.oss-cn-beijing.aliyuncs.com/indexs/shuangbackground.png")
|
||||||
|
.setLicenseNumber("110101199001011234")
|
||||||
|
.setMerchantName("张三商行")
|
||||||
|
.setLegalPerson(encryptor.encrypt("张三"))
|
||||||
|
.setLicenseAddress("北京")
|
||||||
|
.setPeriodBegin("2023-10-25")
|
||||||
|
.setPeriodEnd("2024-10-25");
|
||||||
|
subjectInfo.setBusinessLicenseInfo(licenseInfo);
|
||||||
|
|
||||||
|
WechatEntryIdentityReqDto identityInfo = new WechatEntryIdentityReqDto()
|
||||||
|
.setIdHolderType("LEGAL");
|
||||||
|
subjectInfo.setIdentityInfo(identityInfo);
|
||||||
|
|
||||||
|
reqDto.setSubjectInfo(subjectInfo);
|
||||||
|
|
||||||
|
WechatEntryBusinessReqDto businessInfo = new WechatEntryBusinessReqDto()
|
||||||
|
.setMerchantShortname("张三商行")
|
||||||
|
.setServicePhone("13888888888");
|
||||||
|
WechatEntrySalesInfoReqDto salesInfo = new WechatEntrySalesInfoReqDto()
|
||||||
|
.setSalesScenesType(List.of("SALES_SCENES_STORE"));
|
||||||
|
WechatEntryStoreInfoReqDto storeInfo = new WechatEntryStoreInfoReqDto()
|
||||||
|
.setBizStoreName("张三商行")
|
||||||
|
.setBizAddressCode("110101")
|
||||||
|
.setBizStoreAddress("北京")
|
||||||
|
.setStoreEntrancePic(List.of("https://czg-qr-order.oss-cn-beijing.aliyuncs.com/indexs/shuangbackground.png"))
|
||||||
|
.setIndoorPic(List.of("https://czg-qr-order.oss-cn-beijing.aliyuncs.com/indexs/shuangbackground.png"));
|
||||||
|
salesInfo.setBizStoreInfo(storeInfo);
|
||||||
|
businessInfo.setSalesInfo(salesInfo);
|
||||||
|
|
||||||
|
reqDto.setBusinessInfo(businessInfo);
|
||||||
|
|
||||||
|
WechatEntrySettleReqDto settleInfo = new WechatEntrySettleReqDto()
|
||||||
|
.setSettlementId("719")
|
||||||
|
.setQualificationType("IDCARD")
|
||||||
|
.setQualifications(List.of("110101199001011234"))
|
||||||
|
.setActivitiesId("20191030111cff5b5e");
|
||||||
|
|
||||||
|
reqDto.setSettlementInfo(settleInfo);
|
||||||
|
|
||||||
|
WechatEntryBankAccountReqDto bankAccountInfo = new WechatEntryBankAccountReqDto()
|
||||||
|
.setBankAccountType("BANK_ACCOUNT_TYPE_CORPORATE")
|
||||||
|
.setAccountBank("ICBC")
|
||||||
|
.setBankName("中国工商银行")
|
||||||
|
.setAccountName(encryptor.encrypt("张三"))
|
||||||
|
.setAccountNumber(encryptor.encrypt("110101199001011234"));
|
||||||
|
|
||||||
|
reqDto.setBankAccountInfo(bankAccountInfo);
|
||||||
|
|
||||||
|
// entryMerchant(dto, reqDto);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.czg.wechat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 09:15
|
||||||
|
*/
|
||||||
|
public class WechatPayManager {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package com.czg.wechat;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.http.HttpRequest;
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import com.czg.exception.CzgException;
|
||||||
|
import com.czg.wechat.dto.config.WechatPayConfigDto;
|
||||||
|
import com.wechat.pay.java.core.Config;
|
||||||
|
import com.wechat.pay.java.core.cipher.Signer;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信请求工具类
|
||||||
|
*
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/29 10:02
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class WechatReqUtils {
|
||||||
|
|
||||||
|
public static String postReq(WechatPayConfigDto configDto, String url, String body) {
|
||||||
|
return req(configDto, url, "POST", body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getReq(WechatPayConfigDto configDto, String url, Map<String, Object> params) {
|
||||||
|
url = Arrays.stream(params.entrySet().toArray(new Map.Entry[0]))
|
||||||
|
.map(entry -> entry.getKey() + "=" + URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8))
|
||||||
|
.collect(Collectors.joining("&", url + "?", ""));
|
||||||
|
return req(configDto, url, "GET", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String req(WechatPayConfigDto configDto, String url, String method, String body) {
|
||||||
|
long timestamp = getTimestamp();
|
||||||
|
String nonce = getNonceStr();
|
||||||
|
String signature = encryptReqParam(configDto, method, url, body, timestamp, nonce);
|
||||||
|
String authorization = String.format("WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\",timestamp=\"%d\",serial_no=\"%s\"",
|
||||||
|
configDto.getMerchantId(), nonce, signature, timestamp, configDto.getSerialNumber());
|
||||||
|
|
||||||
|
HttpRequest request = switch (method) {
|
||||||
|
case "POST" -> HttpUtil.createPost(configDto.getDomain() + url)
|
||||||
|
.header("Authorization", authorization)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Wechatpay-Serial", configDto.getPublicKeyId())
|
||||||
|
.body(body);
|
||||||
|
|
||||||
|
case "GET" -> HttpUtil.createGet(configDto.getDomain() + url)
|
||||||
|
.header("Authorization", authorization)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Wechatpay-Serial", configDto.getPublicKeyId());
|
||||||
|
default -> throw new CzgException("不支持的请求方法");
|
||||||
|
};
|
||||||
|
String s = request.execute().body();
|
||||||
|
log.info("微信支付请求:url = {}, method: {}, body: {}, resp: {}", url, method, body, s);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加密请求参数
|
||||||
|
*
|
||||||
|
* @param configDto 配置
|
||||||
|
* @param method 请求方法
|
||||||
|
* @param url 请求地址
|
||||||
|
* @param body 请求报文主体
|
||||||
|
* @return 加密后的报文
|
||||||
|
* <p>
|
||||||
|
* 签名方法
|
||||||
|
* HTTP请求方法\n
|
||||||
|
* URL\n
|
||||||
|
* 请求时间戳\n
|
||||||
|
* 请求随机串\n
|
||||||
|
* 请求报文主体\n
|
||||||
|
*/
|
||||||
|
public static String encryptReqParam(WechatPayConfigDto configDto, String method, String url, String body, long timestamp, String nonce) {
|
||||||
|
String encryptStr = String.format("%s\n%s\n%d\n%s\n%s\n",
|
||||||
|
method, url, timestamp, nonce, body);
|
||||||
|
log.info("encryptStr = {}", encryptStr);
|
||||||
|
|
||||||
|
Config config = WechatConfig.getRsaConfig(configDto);
|
||||||
|
Signer signer = config.createSigner();
|
||||||
|
String signature = signer.sign(encryptStr).getSign();
|
||||||
|
log.info("签名 signature:{}", signature);
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取随机串
|
||||||
|
*/
|
||||||
|
private static String getNonceStr() {
|
||||||
|
return UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取时间戳
|
||||||
|
*/
|
||||||
|
private static long getTimestamp() {
|
||||||
|
return System.currentTimeMillis() / 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.czg.wechat.dto.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 09:21
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatPayConfigDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户号
|
||||||
|
*/
|
||||||
|
private String merchantId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Api V3密钥
|
||||||
|
*/
|
||||||
|
private String apiV3Key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户证书序列号
|
||||||
|
*/
|
||||||
|
private String serialNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户私钥
|
||||||
|
*/
|
||||||
|
private String privateKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户公钥
|
||||||
|
*/
|
||||||
|
private String publicKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户公钥 ID
|
||||||
|
*/
|
||||||
|
private String publicKeyId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付域名
|
||||||
|
* <a href="https://api.mch.weixin.qq.com"></a>
|
||||||
|
*/
|
||||||
|
private String domain;
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信进件-补充信息
|
||||||
|
*
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 13:43
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryAdditionReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 法定代表人开户承诺函
|
||||||
|
* 模板下载地址 <a href="https://kf.qq.com/faq/191018yUFjEj191018Vfmaei.html">...</a>
|
||||||
|
* 通过图片上传完成后 MediaID
|
||||||
|
*/
|
||||||
|
@JSONField(name = "legal_person_commitment")
|
||||||
|
private String legalPersonCommitment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 法定代表人开户意愿视频
|
||||||
|
* 通过视频上传完成后 MediaID
|
||||||
|
*/
|
||||||
|
@JSONField(name = "legal_person_video")
|
||||||
|
private String legalPersonVideo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 补充材料
|
||||||
|
* 最多可上传5张照片
|
||||||
|
* 通过图片上传完成后 MediaID
|
||||||
|
*/
|
||||||
|
@JSONField(name = "business_addition_pics")
|
||||||
|
private List<String> businessAdditionPics;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 补充说明 512字以内
|
||||||
|
*/
|
||||||
|
@JSONField(name = "business_addition_msg")
|
||||||
|
private String businessAdditionMsg;
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信进件-银行账号
|
||||||
|
*
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 13:42
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryBankAccountReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 账户类型
|
||||||
|
* 1、若主体为企业/政府机关/事业单位/社会组织,可填写:对公银行账户;
|
||||||
|
* 2、若主体为个体户,可选择填写:对公银行账户或经营者个人银行卡
|
||||||
|
* 可选取值 BANK_ACCOUNT_TYPE_CORPORATE: 对公银行账户 BANK_ACCOUNT_TYPE_PERSONAL: 经营者个人银行卡
|
||||||
|
*/
|
||||||
|
@JSONField(name = "bank_account_type")
|
||||||
|
private String bankAccountType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 开户名称
|
||||||
|
* 1、选择“经营者个人银行卡”时,开户名称必须与“经营者证件姓名”一致;
|
||||||
|
* 2、选择“对公银行账户”时,开户名称必须与营业执照上的“商户名称”一致;
|
||||||
|
* 3、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。
|
||||||
|
* <a href="https://pay.weixin.qq.com/doc/v3/partner/4013059044">...</a>
|
||||||
|
*/
|
||||||
|
@JSONField(name = "account_name")
|
||||||
|
private String accountName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 开户银行
|
||||||
|
* 对私银行调用:查询支持个人业务的银行列表API
|
||||||
|
* 对公银行调用:查询支持对公业务的银行列表API
|
||||||
|
*/
|
||||||
|
@JSONField(name = "account_bank")
|
||||||
|
private String accountBank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 开户银行银行号
|
||||||
|
* 1、根据开户银行查询接口中的“是否需要填写支行”判断是否需要填写。如为其他银行,开户银行全称(含支行)和开户银行联行号二选一;
|
||||||
|
* 2、详细需调用查询支行列表API查看查询结果。 <a href="https://pay.weixin.qq.com/doc/v3/partner/4012697673">...</a>
|
||||||
|
*/
|
||||||
|
@JSONField(name = "bank_branch_id")
|
||||||
|
private String bankBranchId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 开户银行全称(含支行)
|
||||||
|
* 1、根据开户银行查询接口中的“是否需要填写支行”判断是否需要填写。如为其他银行,开户银行全称(含支行)和开户银行联行号二选一;
|
||||||
|
* 2、详细需调用查询支行列表API查看查询结果。 <a href="https://pay.weixin.qq.com/doc/v3/partner/4012697673">...</a>
|
||||||
|
*/
|
||||||
|
@JSONField(name = "bank_name")
|
||||||
|
private String bankName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 开户银行账号
|
||||||
|
* 1、选择“经营者个人银行卡”时,开户账号为经营者个人银行卡号;
|
||||||
|
* 2、选择“对公银行账户”时,开户账号为对公银行账号;
|
||||||
|
* 3、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。
|
||||||
|
* <a href="https://pay.weixin.qq.com/doc/v3/partner/4013059044">...</a>
|
||||||
|
*/
|
||||||
|
@JSONField(name = "account_number")
|
||||||
|
private String accountNumber;
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进件 联系人信息
|
||||||
|
*
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 13:38
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryContactReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 超级管理员类型
|
||||||
|
* 主体为“个体工商户/企业/政府机关/事业单位/社会组织”,可选择:LEGAL:经营者/法定代表人,SUPER:经办人 。(经办人:经商户授权办理微信支付业务的人员)。
|
||||||
|
* 可选取值
|
||||||
|
* LEGAL: 经营者/法定代表人
|
||||||
|
* SUPER: 经办人
|
||||||
|
*/
|
||||||
|
@JSONField(name = "contact_type")
|
||||||
|
private String contactType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 超级管理员姓名
|
||||||
|
* 1、长度为2-100个字符;
|
||||||
|
* 2、前后不能有空格、制表符、换行符;
|
||||||
|
* 3、不能仅含数字、特殊字符;
|
||||||
|
* 4、仅能填写数字、英文字母、汉字及特殊字符。
|
||||||
|
* 该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "contact_name")
|
||||||
|
private String contactName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 超级管理员证件类型 当超级管理员类型是经办人时,请上传超级管理员证件类型。
|
||||||
|
* 可选取值
|
||||||
|
* IDENTIFICATION_TYPE_IDCARD: 中国大陆居民-身份证
|
||||||
|
* IDENTIFICATION_TYPE_OVERSEA_PASSPORT: 其他国家或地区居民-护照
|
||||||
|
* IDENTIFICATION_TYPE_HONGKONG_PASSPORT: 中国香港居民-来往内地通行证
|
||||||
|
* IDENTIFICATION_TYPE_MACAO_PASSPORT: 中国澳门居民-来往内地通行证
|
||||||
|
* IDENTIFICATION_TYPE_TAIWAN_PASSPORT: 中国台湾居民-来往大陆通行证
|
||||||
|
* IDENTIFICATION_TYPE_FOREIGN_RESIDENT: 外国人居留证
|
||||||
|
* IDENTIFICATION_TYPE_HONGKONG_MACAO_RESIDENT: 港澳居民居住证
|
||||||
|
* IDENTIFICATION_TYPE_TAIWAN_RESIDENT: 台湾居民居住证
|
||||||
|
*/
|
||||||
|
@JSONField(name = "contact_id_type")
|
||||||
|
private String contactIdType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 超级管理员身份证件号码
|
||||||
|
* 1、当超级管理员类型是经办人时,请上传超级管理员证件号码;
|
||||||
|
* 2、可传身份证、来往内地通行证、来往大陆通行证、护照等证件号码,号码规范如下:
|
||||||
|
* 身份证(限中国大陆居民):17位数字+1位数字|X;
|
||||||
|
* 护照(限境外人士):4-15位 数字|字母|连字符;
|
||||||
|
* 中国香港居民--来往内地通行证:H/h开头+8或10位数字/字母;
|
||||||
|
* 中国澳门居民--来往内地通行证:M/m开头+8或10位数字/字母;
|
||||||
|
* 中国台湾居民--来往大陆通行证:8位数字或10位数字;
|
||||||
|
* 外国人居留证:15位 数字|字母;
|
||||||
|
* 台湾居民居住证/港澳居民居住证:17位数字+1位数字|X;
|
||||||
|
* 3、超级管理员签约时,校验微信号绑定的银行卡实名信息,是否与该证件号码一致;
|
||||||
|
* 4、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "contact_id_number")
|
||||||
|
private String contactIdNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 超级管理员证件正面照片
|
||||||
|
* 1、当超级管理员类型是经办人时,请上传超级管理员证件的正面照片;
|
||||||
|
* 2、若证件类型为身份证,请上传人像面照片;
|
||||||
|
* 3、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS;
|
||||||
|
* 4、请上传彩色照片或彩色扫描件或复印件(需加盖公章鲜章),可添加“微信支付”相关水印(如微信支付认证),见【指引文档】;
|
||||||
|
* 5、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "contact_id_doc_copy")
|
||||||
|
private String contactIdDocCopy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 超级管理员证件反面照片
|
||||||
|
* 1、当超级管理员类型是经办人时,请上传超级管理员证件的反面照片;
|
||||||
|
* 2、若证件类型为护照,无需上传反面照片;
|
||||||
|
* 3、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS;
|
||||||
|
* 4、请上传彩色照片或彩色扫描件,复印件需加盖公章鲜章,可添加“微信支付”相关水印(如微信支付认证),见【指引文档】;
|
||||||
|
* 5、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "contact_id_doc_copy_back")
|
||||||
|
private String contactIdDocCopyBack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 超级管理员证件有效期开始时间
|
||||||
|
* 1、当超级管理员类型是经办人时,请上传证件有效期开始时间;
|
||||||
|
* 2、请按照示例值填写,日期格式应满足合法的YYYY-MM-DD格式;
|
||||||
|
* 3、开始时间不能小于1900-01-01,开始时间不能大于等于当前日期。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "contact_period_begin")
|
||||||
|
private String contactPeriodBegin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 超级管理员证件有效期截止时间
|
||||||
|
* 1、当超级管理员类型是经办人时,请上传证件有效期结束时间;
|
||||||
|
* 2、请按照示例值填写,日期格式应满足合法的YYYY-MM-DD格式,若证件有效期为长期,请填写:长期;
|
||||||
|
* 3、结束时间大于开始时间。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "contact_period_end")
|
||||||
|
private String contactPeriodEnd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 联系手机
|
||||||
|
* 1、前后不能有空格、制表符、换行符;
|
||||||
|
* 2、需满足以下任一条件:
|
||||||
|
* 11位数字的手机号码;
|
||||||
|
* 5-20位数字、连字符“-”、加号“+”;
|
||||||
|
* 3、用于接收微信支付的重要管理信息及日常操作验证码;
|
||||||
|
* 4、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "mobile_phone")
|
||||||
|
private String mobilePhone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 联系邮箱
|
||||||
|
* 1、用于接收微信支付的开户邮件及日常业务通知;
|
||||||
|
* 2、需要带@,遵循邮箱格式校验 ,该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "contact_email")
|
||||||
|
private String contactEmail;
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import com.czg.wechat.dto.req.entry.business.WechatEntryBusinessReqDto;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信进件请求参数
|
||||||
|
* 参考地址 <a href="https://pay.weixin.qq.com/doc/v3/partner/4012719997">...</a>
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 13:36
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 业务申请编号
|
||||||
|
*/
|
||||||
|
@JSONField(name = "business_code")
|
||||||
|
private String businessCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 超级管理员信息
|
||||||
|
*/
|
||||||
|
@JSONField(name = "contact_info")
|
||||||
|
private WechatEntryContactReqDto contactInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 主体资料
|
||||||
|
*/
|
||||||
|
@JSONField(name = "subject_info")
|
||||||
|
private WechatEntrySubjectReqDto subjectInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 经营资料
|
||||||
|
* 商家的经营业务信息、售卖商品/提供服务场景信息
|
||||||
|
*/
|
||||||
|
@JSONField(name = "business_info")
|
||||||
|
private WechatEntryBusinessReqDto businessInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 结算规则
|
||||||
|
* 请填写商家的结算费率规则、特殊资质等信息
|
||||||
|
*/
|
||||||
|
@JSONField(name = "settlement_info")
|
||||||
|
private WechatEntrySettleReqDto settlementInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 结算银行账户
|
||||||
|
*/
|
||||||
|
@JSONField(name = "bank_account_info")
|
||||||
|
private WechatEntryBankAccountReqDto bankAccountInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 补充材料
|
||||||
|
* 根据实际审核情况,会额外要求商家提供指定的补充资料
|
||||||
|
*/
|
||||||
|
@JSONField(name = "addition_info")
|
||||||
|
private WechatEntryAdditionReqDto additionInfo;
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进件 结算信息
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 13:38
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntrySettleReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 入驻结算规则ID
|
||||||
|
* 请选择结算规则ID,详细参见费率结算规则对照表
|
||||||
|
* <a href="https://kf.qq.com/faq/220228IJb2UV220228uEjU3Q.html">...</a>
|
||||||
|
*/
|
||||||
|
@JSONField(name = "settlement_id")
|
||||||
|
private String settlementId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 所属行业
|
||||||
|
* <a href="https://kf.qq.com/faq/220228IJb2UV220228uEjU3Q.html">...</a>
|
||||||
|
*/
|
||||||
|
@JSONField(name = "qualification_type")
|
||||||
|
private String qualificationType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 特殊资质图片
|
||||||
|
* 1、仅当商户选择了必需提交特殊资质的行业时,需要提供该项资料;若商户选择了无需特殊资质的行业,或未选择行业时,无需提交该项资料,详情查看《费率结算规则对照表》;
|
||||||
|
* 2、最多可上传5张照片,请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "qualifications")
|
||||||
|
private List<String> qualifications;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 优惠费率活动ID
|
||||||
|
* 如果商户有意向报名优惠费率活动,该字段【必填】。详细参见《优惠费率活动对照表》
|
||||||
|
* <a href="https://pay.weixin.qq.com/doc/v3/partner/4012082816">...</a>
|
||||||
|
*/
|
||||||
|
@JSONField(name = "activities_id")
|
||||||
|
private String activitiesId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 优惠费率活动值
|
||||||
|
* 根据优惠费率活动规则,由合作伙伴自定义填写,支持两个小数点,需在优惠费率活动ID指定费率范围内
|
||||||
|
* (1)2023年7月17日-9月17日,各合作伙伴平台可选择只传入“活动费率值”,或分别传入“信用卡优惠活动费率值” 与 “非信用卡优惠活动费率值”,只传入“活动费率值”的情况下,平台将协助将申请单中的 “优惠活动费率值” 回填入新增的 “信用卡优惠活动费率值” 与 “非信用卡优惠活动费率值”中 ;
|
||||||
|
* (2)2023年9月18日起,平台将不再提供如上兼容能力,届时仅能分别传入“信用卡优惠活动费率值” 与 “非信用卡优惠活动费率值”,否则接口将会报错。为避免影响正常进件,请在兼容期间完成相关调整。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "activities_rate")
|
||||||
|
private String activitiesRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 优惠费率活动补充材料
|
||||||
|
* 1、根据所选优惠费率活动,提供相关材料,详细参见《优惠费率活动对照表》;<a href="https://pay.weixin.qq.com/doc/v3/partner/4012082816">...</a>
|
||||||
|
* 2、最多可上传5张照片,请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "activities_additions")
|
||||||
|
private List<String> activitiesAdditions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 非信用卡活动费率值
|
||||||
|
* 用户支付方式为借记卡时收取的手续费费率:
|
||||||
|
* 1、若填写了优惠费率活动ID,则该字段【必填】;
|
||||||
|
* 2、仅能填入2位以内小数,且在优惠费率活动ID指定费率范围内。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "debit_activities_rate")
|
||||||
|
private String debitActivitiesRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 信用卡活动费率值
|
||||||
|
* 用户支付方式为信用卡时收取的手续费费率:
|
||||||
|
* 1、若填写了优惠费率活动ID,则该字段【必填】;
|
||||||
|
* 2、仅能填入2位以内小数,且在优惠费率活动ID指定费率范围内。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "credit_activities_rate")
|
||||||
|
private String creditActivitiesRate;
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import com.czg.wechat.dto.req.entry.business.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进件 主体资料
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 13:40
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntrySubjectReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 主体类型 主体类型需与营业执照/登记证书上一致,可参考选择主体指引。<a href="https://kf.qq.com/faq/180910IBZVnQ180910naQ77b.html">...</a>
|
||||||
|
* 可选取值
|
||||||
|
* SUBJECT_TYPE_INDIVIDUAL: (个体户)营业执照上的主体类型一般为个体户、个体工商户、个体经营
|
||||||
|
* SUBJECT_TYPE_ENTERPRISE: (企业)营业执照上的主体类型一般为有限公司、有限责任公司
|
||||||
|
* SUBJECT_TYPE_GOVERNMENT: (政府机关)包括各级、各类政府机关,如机关党委、税务、民政、人社、工商、商务、市监等
|
||||||
|
* SUBJECT_TYPE_INSTITUTIONS: (事业单位)包括国内各类事业单位,如:医疗、教育、学校等单位
|
||||||
|
* SUBJECT_TYPE_OTHERS:(社会组织) 包括社会团体、民办非企业、基金会、基层群众性自治组织、农村集体经济组织等组织
|
||||||
|
*/
|
||||||
|
@JSONField(name = "subject_type")
|
||||||
|
private String subjectType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 是否是金融机构
|
||||||
|
* 【是否是金融机构】 【选填】,请根据申请主体的实际情况填写,可参考选择金融机构指引:
|
||||||
|
* 1、若商户主体是金融机构,则填写:true;
|
||||||
|
* 2、若商户主体不是金融机构,则填写:false。
|
||||||
|
* 若未传入将默认填写:false。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "finance_institution")
|
||||||
|
private Boolean financeInstitution;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 营业执照 当前不允许小微注册,所以【必填】
|
||||||
|
* 1、主体为个体户/企业,【必填】;
|
||||||
|
* 2、请上传“营业执照”,需年检章齐全,当年注册除外;
|
||||||
|
*/
|
||||||
|
@JSONField(name = "business_license_info")
|
||||||
|
private WechatEntryLicenseReqDto businessLicenseInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 登记证书 主体为政府机关/事业单位/其他组织时,【必填】。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "certificate_info")
|
||||||
|
private WechatEntryCertificateReqDto certificateInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 单位证明函照片
|
||||||
|
* 1、主体类型为政府机关、事业单位选传:
|
||||||
|
* (1)若上传,则审核通过后即可签约,无需汇款验证;
|
||||||
|
* (2)若未上传,则审核通过后,需汇款验证。
|
||||||
|
* 2、主体为个体户、企业、其他组织等,不需要上传本字段;
|
||||||
|
* 3、请参照示例图打印单位证明函,全部信息需打印,不支持手写商户信息,并加盖公章; <a href="https://kf.qq.com/faq/200114u2y2yQ200114uEz26z.html">...</a>
|
||||||
|
* 4、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "certificate_letter_copy")
|
||||||
|
private String certificateLetterCopy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 金融机构许可证信息 当主体是金融机构时,【必填】。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "finance_institution_info")
|
||||||
|
private WechatEntryFinanceInstitutionReqDto financeInstitutionInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 经营者/法定代表人身份证件
|
||||||
|
* 1、个体户:请上传经营者的身份证件;
|
||||||
|
* 2、企业/社会组织:请上传法定代表人的身份证件;
|
||||||
|
* 3、政府机关/事业单位:请上传法定代表人/经办人的身份证件。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "identity_info")
|
||||||
|
private WechatEntryIdentityReqDto identityInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 最终受益人信息列表(UBO)
|
||||||
|
* 1、主体类型个体户/社会组织/政府机关/事业单位时,无需填写
|
||||||
|
* 2、主体类型为企业时,按照下述要求填写
|
||||||
|
* 1)若经营者/法定代表人不是最终受益所有人,则需提填写受益所有人信息,最多上传4个。
|
||||||
|
* 2)若经营者/法定代表人是最终受益所有人之一,可在此填写其他受益所有人信息,最多上传3个。
|
||||||
|
* 根据国家相关法律法规,需要提供公司受益所有人信息,受益所有人需符合至少以下条件之一:
|
||||||
|
* ▪️直接或者间接拥有超过25%公司股权或者表决权的自然人。
|
||||||
|
* ▪️通过人事、财务等其他方式对公司进行控制的自然人。
|
||||||
|
* ▪️公司的高级管理人员,包括公司的经理、副经理、财务负责人、上市公司董事会秘书和公司章程规定的其他人员。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "ubo_info_list")
|
||||||
|
private List<WechatEntryUboInfoReqDto> uboInfoList;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry.business;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import com.czg.wechat.dto.req.entry.business.sales.WechatEntrySalesInfoReqDto;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信进件 商户信息 经营资料
|
||||||
|
*
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 13:41
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryBusinessReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 商户简称 在支付完成页向买家展示,需与微信经营类目相关
|
||||||
|
* 1、请输入1-64个字符;
|
||||||
|
* 2、前后不能有空格、制表符、换行符;
|
||||||
|
* 3、不能仅含特殊字符;
|
||||||
|
* 4、仅能填写数字、英文字母、汉字及特殊字符;
|
||||||
|
* 5、仅支持utf-8格式;
|
||||||
|
* 6、简称要求
|
||||||
|
* (1)不支持单纯以人名来命名,若为个体户经营,可用“个体户+经营者名称”或“经营者名称+业务”命名,如“个体户张三”或“张三餐饮店”;
|
||||||
|
* (2)不支持无实际意义的文案,如“XX特约商户”、“800”、“XX客服电话XXX”。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "merchant_shortname")
|
||||||
|
private String merchantShortname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 客服电话 将在交易记录中向买家展示,请确保电话畅通以便平台回拨确认
|
||||||
|
* 1、前后不能有空格、制表符、换行符;
|
||||||
|
* 2、需满足以下任一条件;
|
||||||
|
* 11位数字的手机号码;
|
||||||
|
* 5-20位数字、连字符“-”、加号“+”。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "service_phone")
|
||||||
|
private String servicePhone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 经营场景
|
||||||
|
*/
|
||||||
|
@JSONField(name = "sales_info")
|
||||||
|
private WechatEntrySalesInfoReqDto salesInfo;
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry.business;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登记证书
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 15:07
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryCertificateReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 登记证书照片
|
||||||
|
* 1、照片应正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS;
|
||||||
|
* 2、上传彩色照片、彩色扫描件,复印件需加盖公章鲜章;
|
||||||
|
* 3、水印仅限于微信支付业务相关;
|
||||||
|
* 4、指引与示例可参考【指引文档】;
|
||||||
|
* 5、请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "cert_copy")
|
||||||
|
private String certCopy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 登记证书类型
|
||||||
|
* 1、主体为“政府机关/事业单位/社会组织”时,请上传登记证书类型;
|
||||||
|
* 2、主体为“个体工商户/企业”时,不填;
|
||||||
|
* 当主体为事业单位时,选择此枚举值:
|
||||||
|
* CERTIFICATE_TYPE_2388:事业单位法人证书
|
||||||
|
* 当主体为政府机关,选择此枚举值:
|
||||||
|
* CERTIFICATE_TYPE_2389:统一社会信用代码证书
|
||||||
|
* 当主体为社会组织,选择以下枚举值之一:
|
||||||
|
* CERTIFICATE_TYPE_2389:统一社会信用代码证书
|
||||||
|
* CERTIFICATE_TYPE_2394:社会团体法人登记证书
|
||||||
|
* CERTIFICATE_TYPE_2395:民办非企业单位登记证书
|
||||||
|
* CERTIFICATE_TYPE_2396:基金会法人登记证书
|
||||||
|
* CERTIFICATE_TYPE_2520:执业许可证/执业证
|
||||||
|
* CERTIFICATE_TYPE_2521:基层群众性自治组织特别法人统一社会信用代码证
|
||||||
|
* CERTIFICATE_TYPE_2522:农村集体经济组织登记证
|
||||||
|
* CERTIFICATE_TYPE_2399:宗教活动场所登记证
|
||||||
|
* CERTIFICATE_TYPE_2400:政府部门下发的其他有效证明文件
|
||||||
|
*/
|
||||||
|
@JSONField(name = "cert_type")
|
||||||
|
private String certType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 证书号 请输入与所选证书类型相匹配且符合国家标准规范的证书号,其中除政府证明文件外,需满足18位阿拉伯数字或大写英文字母(不得包含英文字母I/O/Z/S/V)
|
||||||
|
*/
|
||||||
|
@JSONField(name = "cert_number")
|
||||||
|
private String certNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 商户名称 请填写登记证书上的商户名称
|
||||||
|
* 1、长度为2-128个字符;
|
||||||
|
* 2、前后不能有空格、制表符、换行符;
|
||||||
|
* 3、不能仅含数字、特殊字符;
|
||||||
|
* 4、仅能填写数字、英文字母、汉字及特殊字符;
|
||||||
|
* 5、仅支持utf-8格式。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "merchant_name")
|
||||||
|
private String merchantName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 注册地址 请填写登记证书的注册地址
|
||||||
|
* 1、长度为4-128个字符;
|
||||||
|
* 2、前后不能有空格、制表符、换行符;
|
||||||
|
* 3、不能仅含数字、特殊字符;
|
||||||
|
* 4、仅能填写数字、英文字母、汉字及特殊字符;
|
||||||
|
* 5、仅支持utf-8格式。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "company_address")
|
||||||
|
private String companyAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 法定代表人 请填写登记证书上的法定代表人姓名
|
||||||
|
* 1、长度为2-100个字符;
|
||||||
|
* 2、前后不能有空格、制表符、换行符;
|
||||||
|
* 3、不能仅含特殊字符;
|
||||||
|
* 4、仅能填写数字、英文字母、汉字及特殊字符。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "legal_person")
|
||||||
|
private String legalPerson;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 有效期限开始日期 建议填写营业执照的有效期限开始时间,若该字段未填写,系统将会查询国家工商信息填入。需注意若工商信息查询不到,则会被审核驳回。
|
||||||
|
* 1、日期格式应满足合法的YYYY-MM-DD格式;
|
||||||
|
* 2、开始时间不能小于1900-01-01;
|
||||||
|
* 3、开始时间不能大于等于当前日期。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "period_begin")
|
||||||
|
private String periodBegin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 营业期限结束日期 建议填写营业执照的有效期限结束时间,若该字段未填写,系统将会查询国家工商信息填入。需注意若工商信息查询不到,则会被审核驳回。
|
||||||
|
* 1、日期格式应满足合法的YYYY-MM-DD格式或长期;
|
||||||
|
* 2、结束时间需大于开始时间。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "period_end")
|
||||||
|
private String periodEnd;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry.business;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 金融机构许可证信息
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 15:21
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryFinanceInstitutionReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 金融机构类型 金融机构类型需与营业执照/登记证书上一致,可参考选择金融机构指引。
|
||||||
|
* BANK_AGENT:银行业, 适用于商业银行、政策性银行、农村合作银行、村镇银行、开发性金融机构等
|
||||||
|
* PAYMENT_AGENT:支付机构, 适用于非银行类支付机构
|
||||||
|
* INSURANCE:保险业, 适用于保险、保险中介、保险代理、保险经纪等保险类业务
|
||||||
|
* TRADE_AND_SETTLE:交易及结算类金融机构, 适用于交易所、登记结算类机构、银行卡清算机构、资金清算中心等
|
||||||
|
* OTHER:其他金融机构, 适用于财务公司、信托公司、金融资产管理公司、金融租赁公司、汽车金融公司、贷款公司、货币经纪公司、消费金融公司、证券业、金融控股公司、股票、期货、货币兑换、小额贷款公司、金融资产管理、担保公司、商业保理公司、典当行、融资租赁公司、财经咨询等其他金融业务
|
||||||
|
*/
|
||||||
|
@JSONField(name = "finance_type")
|
||||||
|
private String financeType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 必填
|
||||||
|
* 金融机构许可证图片
|
||||||
|
* 1、照片应正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS;
|
||||||
|
* 2、上传彩色照片、彩色扫描件,复印件需加盖公章鲜章;
|
||||||
|
* 3、水印仅限于微信支付业务相关;
|
||||||
|
* 4、根据所属金融机构类型的许可证要求提供,详情查看金融机构指引; <a href="https://kf.qq.com/faq/220215IrMRZ3220215n6buiU.html">...</a>
|
||||||
|
* 5、请提供为“申请商家主体”所属的许可证,可授权使用总公司/分公司的特殊资质;
|
||||||
|
* 6、最多可上传5张照片,请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "finance_license_pics")
|
||||||
|
private List<String> financeLicensePics;
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry.business;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import com.czg.wechat.dto.req.entry.id.WechatEntryIdCardReqDto;
|
||||||
|
import com.czg.wechat.dto.req.entry.id.WechatEntryIdDocInfoReqDto;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 经营者/法定代表人身份证件
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 15:24
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryIdentityReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 证件持有人类型
|
||||||
|
* 1、主体类型为政府机关/事业单位时选传:
|
||||||
|
* (1)若上传的是法定代表人证件,则不需要上传该字段。
|
||||||
|
* (2)若因政策保密等原因,无法提供法定代表人证件时,可上传经办人。 (经办人:经商户授权办理微信支付业务的人员,授权范围包括但不限于签约,入驻过程需完成账户验证)。
|
||||||
|
* 2、主体类型为小微/个人卖家/企业/个体户/社会组织时,默认为经营者/法定代表人,不需要上传该字段。
|
||||||
|
* LEGAL: 经营者/法定代表人
|
||||||
|
* SUPER: 经办人
|
||||||
|
*/
|
||||||
|
@JSONField(name = "id_holder_type")
|
||||||
|
private String idHolderType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 证件类型
|
||||||
|
* 1、当证件持有人类型为法定代表人时,填写。其他情况,无需上传;
|
||||||
|
* 2、个体户/企业/事业单位/社会组织:可选择任一证件类型,政府机关仅支持中国大陆居民-身份证类型。
|
||||||
|
* 可选取值
|
||||||
|
* IDENTIFICATION_TYPE_IDCARD: 中国大陆居民-身份证
|
||||||
|
* IDENTIFICATION_TYPE_OVERSEA_PASSPORT: 其他国家或地区居民-护照
|
||||||
|
* IDENTIFICATION_TYPE_HONGKONG_PASSPORT: 中国香港居民-来往内地通行证
|
||||||
|
* IDENTIFICATION_TYPE_MACAO_PASSPORT: 中国澳门居民-来往内地通行证
|
||||||
|
* IDENTIFICATION_TYPE_TAIWAN_PASSPORT: 中国台湾居民-来往大陆通行证
|
||||||
|
* IDENTIFICATION_TYPE_FOREIGN_RESIDENT: 外国人居留证
|
||||||
|
* IDENTIFICATION_TYPE_HONGKONG_MACAO_RESIDENT: 港澳居民居住证
|
||||||
|
* IDENTIFICATION_TYPE_TAIWAN_RESIDENT: 台湾居民居住证
|
||||||
|
*/
|
||||||
|
@JSONField(name = "id_doc_type")
|
||||||
|
private String idDocType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 法定代表人说明函
|
||||||
|
* 1、当证件持有人类型为经办人时,必须上传。其他情况,无需上传;
|
||||||
|
* 2、若因特殊情况,无法提供法定代表人证件时,请参照示例图打印法定代表人说明函,全部信息需打印,不支持手写商户信息,并加盖公章; <a href="https://kf.qq.com/faq/220127aUzAju220127UfiuQr.html">...</a>
|
||||||
|
* 3、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "authorize_letter_copy")
|
||||||
|
private String authorizeLetterCopy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 身份证信息 当证件持有人类型为经营者/法定代表人且证件类型为“身份证”时填写。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "id_card_info")
|
||||||
|
private WechatEntryIdCardReqDto idCardInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 其他类型证件信息 当证件持有人类型为经营者/法定代表人且证件类型不为“身份证”时填写。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "id_doc_info")
|
||||||
|
private WechatEntryIdDocInfoReqDto idDocInfo;
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry.business;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 营业执照信息
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 14:59
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryLicenseReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 营业执照照片
|
||||||
|
* 1、照片应正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS;
|
||||||
|
* 2、上传彩色照片、彩色扫描件,复印件需加盖公章鲜章;
|
||||||
|
* 3、水印仅限于微信支付业务相关;
|
||||||
|
* 4、指引与示例可参考【指引文档】;
|
||||||
|
* 5、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "license_copy")
|
||||||
|
private String licenseCopy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 注册号/统一社会信用代码
|
||||||
|
*/
|
||||||
|
@JSONField(name = "license_number")
|
||||||
|
private String licenseNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 商户名称
|
||||||
|
* 1、长度为2-128个字符;
|
||||||
|
* 2、前后不能有空格、制表符、换行符;
|
||||||
|
* 3、不能仅含数字、特殊字符;
|
||||||
|
* 4、仅能填写数字、英文字母、汉字及特殊字符;
|
||||||
|
* 5、仅支持utf-8格式;
|
||||||
|
* 6、个体户证件为以下情况时,按照个体户XXX命名(XXX是营业执照经营人姓名):营业执照登记名称为空、仅含数字、仅含特殊字符、“无”、“无字号”;
|
||||||
|
* 7、个体户不能使用“企业”“公司”或“农民专业合作社”结尾。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "merchant_name")
|
||||||
|
private String merchantName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 个体户经营者/法定代表人姓名 请填写营业执照的经营者/法定代表人姓名
|
||||||
|
* 1、长度为2-100个字符;
|
||||||
|
* 2、前后不能有空格、制表符、换行符;
|
||||||
|
* 3、不能仅含特殊字符;
|
||||||
|
* 4、仅能填写数字、英文字母、汉字及特殊字符。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "legal_person")
|
||||||
|
private String legalPerson;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 注册地址 建议填写营业执照的注册地址,若该字段未填写,系统将会查询国家工商信息填入。需注意若工商信息查询不到,则会被审核驳回。
|
||||||
|
* 1、长度为4-128个字符;
|
||||||
|
* 2、前后不能有空格、制表符、换行符;
|
||||||
|
* 3、不能仅含数字、特殊字符;
|
||||||
|
* 4、仅能填写数字、英文字母、汉字及特殊字符;
|
||||||
|
* 5、仅支持utf-8格式。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "license_address")
|
||||||
|
private String licenseAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 有效期限开始日期 建议填写营业执照的有效期限开始时间,若该字段未填写,系统将会查询国家工商信息填入。需注意若工商信息查询不到,则会被审核驳回。
|
||||||
|
* 1、日期格式应满足合法的YYYY-MM-DD格式;
|
||||||
|
* 2、开始时间不能小于1900-01-01;
|
||||||
|
* 3、开始时间不能大于等于当前日期。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "period_begin")
|
||||||
|
private String periodBegin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 营业期限结束日期 建议填写营业执照的有效期限结束时间,若该字段未填写,系统将会查询国家工商信息填入。需注意若工商信息查询不到,则会被审核驳回。
|
||||||
|
* 1、日期格式应满足合法的YYYY-MM-DD格式或长期;
|
||||||
|
* 2、结束时间需大于开始时间。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "period_end")
|
||||||
|
private String periodEnd;
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry.business;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最终受益人信息
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 15:50
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryUboInfoReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 证件类型 请填写受益人的证件类型。枚举值:
|
||||||
|
* 可选取值
|
||||||
|
* IDENTIFICATION_TYPE_IDCARD: 中国大陆居民-身份证
|
||||||
|
* IDENTIFICATION_TYPE_OVERSEA_PASSPORT: 其他国家或地区居民-护照
|
||||||
|
* IDENTIFICATION_TYPE_HONGKONG_PASSPORT: 中国香港居民-来往内地通行证
|
||||||
|
* IDENTIFICATION_TYPE_MACAO_PASSPORT: 中国澳门居民-来往内地通行证
|
||||||
|
* IDENTIFICATION_TYPE_TAIWAN_PASSPORT: 中国台湾居民-来往大陆通行证
|
||||||
|
* IDENTIFICATION_TYPE_FOREIGN_RESIDENT: 外国人居留证
|
||||||
|
* IDENTIFICATION_TYPE_HONGKONG_MACAO_RESIDENT: 港澳居民居住证
|
||||||
|
* IDENTIFICATION_TYPE_TAIWAN_RESIDENT: 台湾居民居住证
|
||||||
|
*/
|
||||||
|
@JSONField(name = "ubo_id_doc_type")
|
||||||
|
private String uboIdDocType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 必填
|
||||||
|
* 证件正面照片
|
||||||
|
* 1、请上传受益人证件的正面照片;
|
||||||
|
* 2、若证件类型为身份证,请上传人像面照片;
|
||||||
|
* 3、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS;
|
||||||
|
* 4、请上传彩色照片or彩色扫描件,复印件需加盖公章鲜章,可添加“微信支付”相关水印(如微信支付认证),见【指引文档】;
|
||||||
|
* 5、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "ubo_id_doc_copy")
|
||||||
|
private String uboIdDocCopy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 证件反面照片
|
||||||
|
* 1、请上传受益人证件的反面照片;
|
||||||
|
* 2、若证件类型为身份证,请上传国徽面照片;
|
||||||
|
* 3、若证件类型为护照,无需上传反面照片;
|
||||||
|
* 4、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS;
|
||||||
|
* 5、请上传彩色照片or彩色扫描件,复印件需加盖公章鲜章,可添加“微信支付”相关水印(如微信支付认证),见【指引文档】;
|
||||||
|
* 6、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "ubo_id_doc_copy_back")
|
||||||
|
private String uboIdDocCopyBack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 必填
|
||||||
|
* 证件姓名
|
||||||
|
* 1、长度为2-100个字符;
|
||||||
|
* 2、前后不能有空格、制表符、换行符;
|
||||||
|
* 3、不能仅含数字、特殊字符;
|
||||||
|
* 4、仅能填写数字、英文字母、汉字及特殊字符;
|
||||||
|
* 5、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "ubo_id_doc_name")
|
||||||
|
private String uboIdDocName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 必填
|
||||||
|
* 证件号码
|
||||||
|
* 1、证件号码为证件正反面中一致的号码;
|
||||||
|
* 2、证件号码为18位或15位,或港澳居民来往内地通行证为9位;
|
||||||
|
* 3、证件号码为英文字母X或x,或港澳居民来往内地通行证为英文字母X或x;
|
||||||
|
* 4、证件号码为英文字母x或X,或港澳居民来往内地通行证为英文字母x或X;
|
||||||
|
* 5、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥
|
||||||
|
*/
|
||||||
|
@JSONField(name = "ubo_id_doc_number")
|
||||||
|
private String uboIdDocNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 必填
|
||||||
|
* 证件居住地址
|
||||||
|
* 1、请按照身份证住址填写,如广东省深圳市南山区xx路xx号xx室;
|
||||||
|
* 2、长度为4-128个字符;
|
||||||
|
* 3、前后不能有空格、制表符、换行符;
|
||||||
|
* 4、不能仅含数字、特殊字符;
|
||||||
|
* 5、仅能填写数字、英文字母、汉字及特殊字符;
|
||||||
|
* 6、仅支持utf-8格式;
|
||||||
|
* 7、 该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "ubo_id_doc_address")
|
||||||
|
private String uboIdDocAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 必填
|
||||||
|
* 证件有效期开始时间
|
||||||
|
* 1、日期格式应满足合法的YYYY-MM-DD格式;
|
||||||
|
* 2、开始时间不能小于1900-01-01;
|
||||||
|
* 3、开始时间不能大于等于当前日期。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "ubo_period_begin")
|
||||||
|
private String uboPeriodBegin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 必填
|
||||||
|
* 证件有效期结束时间
|
||||||
|
* 1、日期格式应满足合法的YYYY-MM-DD格式或长期;
|
||||||
|
* 2、结束时间需大于开始时间。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "ubo_period_end")
|
||||||
|
private String uboPeriodEnd;
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry.business.sales;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进件-经营资料-经营场景-App场景
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 14:26
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryAppInfoReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* App截图
|
||||||
|
* 1、请提供APP首页截图、尾页截图、应用内截图、支付页截图各1张;
|
||||||
|
* 2、请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "app_pics")
|
||||||
|
private List<String> appPics;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 商家应用AppID
|
||||||
|
* 1、服务商应用AppID与商家应用AppID,二选一【必填】;
|
||||||
|
* 2、可填写与商家主体一致且已认证的应用AppID,需是已认证的App;
|
||||||
|
* 3、审核通过后,系统将发起特约商家商户号与该AppID的绑定(即配置为sub_appid),服务商随后可在发起支付时选择传入该AppID,以完成支付,并获取sub_openid用于数据统计,营销等业务场景。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "app_sub_appid")
|
||||||
|
private String appSubAppid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 服务商应用AppID
|
||||||
|
* 1、服务商应用AppID与商家应用AppID,二选一【必填】;
|
||||||
|
* 2、可填写当前服务商商户号已绑定的应用AppID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "app_appid")
|
||||||
|
private String appAppid;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry.business.sales;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进件-经营资料-经营场景-小程序场景
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 14:23
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryMiniProgramReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 服务商小程序AppID
|
||||||
|
* 1、服务商小程序AppID与商家小程序AppID,二选一必填;
|
||||||
|
* 2、可填写当前服务商商户号已绑定的小程序AppID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "mini_program_appid")
|
||||||
|
private String miniProgramAppid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 商家小程序AppID
|
||||||
|
* 1、服务商小程序AppID与商家小程序AppID,二选一必填;
|
||||||
|
* 2、请填写已认证的小程序AppID;
|
||||||
|
* 3、完成进件后,系统发起特约商户号与该AppID的绑定(即配置为sub_appid可在发起支付时传入)
|
||||||
|
* (1)若AppID主体与商家主体/服务商主体一致,则直接完成绑定;
|
||||||
|
* (2)若AppID主体与商家主体/服务商主体不一致,则商户签约时显示《联合营运承诺函》,并且AppID的管理员需登录公众平台确认绑定意愿。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "mini_program_sub_appid")
|
||||||
|
private String miniProgramSubAppid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 小程序截图
|
||||||
|
* 1、请提供展示商品/服务的页面截图/设计稿(最多5张),若小程序未建设完善或未上线 请务必提供;
|
||||||
|
* 2、请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "mini_program_pics")
|
||||||
|
private List<String> miniProgramPics;
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry.business.sales;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进件-经营资料-经营场景-服务号或公众号景
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 14:20
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryMpInfoReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 服务号或公众号页面截图
|
||||||
|
* 1、请提供展示商品/服务的页面截图/设计稿(最多5张),若服务号或公众号未建设完善或未上线请务必提供;
|
||||||
|
* 2、请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "mp_pics")
|
||||||
|
private List<String> mpPics;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 商家服务号或公众号AppID
|
||||||
|
* 1、服务商服务号或公众号AppID、商家服务号或公众号AppID,二选一【必填】;
|
||||||
|
* 2、可填写与商家主体一致且已认证的服务号或公众号AppID,需是已认证的服务号、政府或媒体类型的公众号;
|
||||||
|
* 3、审核通过后,系统将发起特约商家商户号与该AppID的绑定(即配置为sub_appid),服务商随后可在发起支付时选择传入该appid,以完成支付,并获取sub_openid用于数据统计,营销等业务场景。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "mp_sub_appid")
|
||||||
|
private String mpSubAppid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 服务商服务号或公众号AppID
|
||||||
|
* 1、服务商服务号或公众号AppID、商家服务号或公众号AppID,二选一【必填】;
|
||||||
|
* 2、可填写当前服务商商户号已绑定的服务号或公众号AppID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "mp_appid")
|
||||||
|
private String mpAppid;
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry.business.sales;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进件-经营场景
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 14:07
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntrySalesInfoReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 经营场景类型
|
||||||
|
* 1、请勾选实际售卖商品/提供服务场景(至少一项),以便为你开通需要的支付权限;
|
||||||
|
* 2、建议只勾选目前必须的场景,以便尽快通过入驻审核,其他支付权限可在入驻后再根据实际需要发起申请
|
||||||
|
* 可选取值
|
||||||
|
* SALES_SCENES_STORE: 线下场所
|
||||||
|
* SALES_SCENES_MP: 服务号与公众号
|
||||||
|
* SALES_SCENES_MINI_PROGRAM: 小程序
|
||||||
|
* SALES_SCENES_WEB: 互联网网站
|
||||||
|
* SALES_SCENES_APP: App
|
||||||
|
* SALES_SCENES_WEWORK: 企业微信
|
||||||
|
*/
|
||||||
|
@JSONField(name = "sales_scenes_type")
|
||||||
|
private List<String> salesScenesType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 线下场所场景
|
||||||
|
* 1、审核通过后,服务商可帮商户发起付款码支付、JSAPI支付;
|
||||||
|
* 2、当"经营场景类型"选择"SALES_SCENES_STORE",该场景资料【必填】。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "biz_store_info")
|
||||||
|
private WechatEntryStoreInfoReqDto bizStoreInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 服务号或公众号场景
|
||||||
|
* 1、审核通过后,服务商可帮商家发起JSAPI支付;
|
||||||
|
* 2、当"经营场景类型"选择"SALES_SCENES_MP",该场景资料【必填】。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "mp_info")
|
||||||
|
private WechatEntryMpInfoReqDto mpInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 小程序场景
|
||||||
|
* 1、审核通过后,服务商可帮商家发起JSAPI支付;
|
||||||
|
* 2、当"经营场景类型"选择"SALES_SCENES_MINI_PROGRAM",该场景资料必填。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "mini_program_info")
|
||||||
|
private WechatEntryMiniProgramReqDto miniProgramInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* App场景
|
||||||
|
* 1、审核通过后,服务商可帮商家发起App支付;
|
||||||
|
* 2、当"经营场景类型"选择"SALES_SCENES_APP",该场景资料必填。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "app_info")
|
||||||
|
private WechatEntryAppInfoReqDto appInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 互联网网站场景
|
||||||
|
* 1、审核通过后,服务商可帮商家发起JSAPI支付;
|
||||||
|
* 2、当"经营场景类型"选择"SALES_SCENES_WEB",该场景资料必填。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "web_info")
|
||||||
|
private WechatEntryWebInfoReqDto webInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 企业微信场景
|
||||||
|
* 1、审核通过后,服务商可帮商家发起JSAPI支付;
|
||||||
|
* 2、当"经营场景类型"选择"SALES_SCENES_WEWORK",该场景资料必填。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "wework_info")
|
||||||
|
private WechatEntryWeworkInfoReqDto weworkInfo;
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry.business.sales;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进件-经营资料-经营场景-线下场所场景
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 14:10
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryStoreInfoReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 线下场所名称 请填写门店名称
|
||||||
|
* 1、长度为1-50个字符;
|
||||||
|
* 2、前后不能有空格、制表符、换行符;
|
||||||
|
* 3、不能仅含数字、特殊字符;
|
||||||
|
* 4、仅能填写数字、英文字母、汉字及特殊字符;
|
||||||
|
* 5、仅支持utf-8格式。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "biz_store_name")
|
||||||
|
private String bizStoreName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 线下场所省市编码
|
||||||
|
* 1、只能由数字组成;
|
||||||
|
* 2、详细参见微信支付提供的省市对照表。 <a href="https://pay.weixin.qq.com/doc/v3/partner/4012082815">...</a>
|
||||||
|
*/
|
||||||
|
@JSONField(name = "biz_address_code")
|
||||||
|
private String bizAddressCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 线下场所地址 请填写详细的经营场所信息,如有多个场所,选择一个主要场所填写即可。
|
||||||
|
* 1、长度为4-512个字符;
|
||||||
|
* 2、前后不能有空格、制表符、换行符;
|
||||||
|
* 3、不能仅含数字、特殊字符;
|
||||||
|
* 4、仅能填写数字、英文字母、汉字及特殊字符;
|
||||||
|
* 5、仅支持utf-8格式
|
||||||
|
*/
|
||||||
|
@JSONField(name = "biz_store_address")
|
||||||
|
private String bizStoreAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 线下场所门头照片
|
||||||
|
* 1、请上传门头正面照片(要求门店招牌、门框完整、清晰、可辨识);若为停车场等无固定门头照片的经营场所,可上传岗亭/出入闸口。具体参考【指引文档】;
|
||||||
|
* 2、请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "store_entrance_pic")
|
||||||
|
private List<String> storeEntrancePic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 线下场所内部照片
|
||||||
|
* 1、请上传门店内部环境照片(可辨识经营内容)。若为停车场等无固定门头的经营场所,可上传停车场内部照片。具体参考【指引文档】;
|
||||||
|
* 2、请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "indoor_pic")
|
||||||
|
private List<String> indoorPic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 线下场所对应的商家AppID
|
||||||
|
* 1、可填写与商家主体一致且已认证的服务号或公众号、小程序、APP的AppID,其中服务号或公众号AppID需是已认证的服务号、政府或媒体类型的公众号;
|
||||||
|
* 2、审核通过后,系统将额外为商家开通付款码支付、JSAPI支付的自有交易权限,并完成商家商户号与该AppID的绑定。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "biz_sub_appid")
|
||||||
|
private String bizSubAppid;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry.business.sales;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进件-经营资料-经营场景-互联网网站场景
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 14:29
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryWebInfoReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 互联网网站域名
|
||||||
|
* 1、如为PC端商城、智能终端等场景,可上传官网链接;
|
||||||
|
* 2、网站域名需ICP备案,若备案主体与申请主体不同,请上传加盖公章的网站授权函。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "domain")
|
||||||
|
private String domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 网站授权函
|
||||||
|
* 1、若备案主体与申请主体不同,请务必上传加盖公章的网站授权函.doc; <a href="https://gtimg.wechatpay.cn/resource/xres/mmpaydoc/static/attachment/bb55b16e3825a5952a531d55fba0f382/%E7%BD%91%E7%AB%99%E6%8E%88%E6%9D%83%E5%87%BD.doc">...</a>
|
||||||
|
* 2、请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "web_authorisation")
|
||||||
|
private String webAuthorisation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 互联网网站对应的商家AppID
|
||||||
|
* 1、可填写已认证的服务号或公众号、小程序、APP的AppID,其中服务号或公众号AppID需是已认证的服务号、政府或媒体类型的公众号;
|
||||||
|
* 2、完成进件后,系统发起特约商户号与该AppID的绑定(即配置为sub_appid,可在发起支付时传入)
|
||||||
|
* (1)若APPID主体与商家主体一致,则直接完成绑定;
|
||||||
|
* (2)若APPID主体与商家主体不一致,则商户签约时显示《联合营运承诺函》,并且 AppID的管理员需登录公众平台确认绑定意愿;( 暂不支持绑定异主体的应用APPID)。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "web_appid")
|
||||||
|
private String webAppid;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry.business.sales;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进件-经营资料-经营场景-企业微信场景
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 14:32
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryWeworkInfoReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 商家企业微信CorpID
|
||||||
|
* 1、可填写与商家主体一致且已认证的企业微信CorpID;
|
||||||
|
* 2、审核通过后,系统将为商家开通企业微信专区的自有交易权限,并完成商家商户号与该AppID的绑定,商家可自行发起交易。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "sub_corp_id")
|
||||||
|
private String subCorpId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 必填
|
||||||
|
* 企业微信页面截图
|
||||||
|
* 1、最多可上传5张照片;
|
||||||
|
* 2、请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "wework_pics")
|
||||||
|
private String[] weworkPics;
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry.id;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 身份证信息
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 15:29
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryIdCardReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 身份证人像面照片
|
||||||
|
* 1、请上传个体户经营者/法定代表人的身份证人像面照片;
|
||||||
|
* 2、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS;
|
||||||
|
* 3、请上传彩色照片or彩色扫描件,复印件需加盖公章鲜章,可添加“微信支付”相关水印(如微信支付认证),见【指引文档】;
|
||||||
|
* 4、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "id_card_copy")
|
||||||
|
private String idCardCopy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 身份证国徽面照片
|
||||||
|
* 、请上传个体户经营者/法定代表人的身份证国徽面照片;
|
||||||
|
* 2、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS;
|
||||||
|
* 3、请上传彩色照片or彩色扫描件,复印件需加盖公章鲜章,可添加“微信支付”相关水印(如微信支付认证),见【指引文档】;
|
||||||
|
* 4、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "id_card_national")
|
||||||
|
private String idCardNational;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 身份证姓名
|
||||||
|
* 1、请填写个体户经营者/法定代表人对应身份证的姓名;
|
||||||
|
* 2、长度为2-100个字符;
|
||||||
|
* 3、前后不能有空格、制表符、换行符;
|
||||||
|
* 4、不能仅含数字、特殊字符;
|
||||||
|
* 5、仅能填写数字、英文字母、汉字及特殊字符;
|
||||||
|
* 6、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。
|
||||||
|
* <a href="https://pay.weixin.qq.com/doc/v3/partner/4013059044">加密</a>
|
||||||
|
*/
|
||||||
|
@JSONField(name = "id_card_name")
|
||||||
|
private String idCardName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 必填
|
||||||
|
* 身份证号码
|
||||||
|
* 1、请填写个体户经营者/法定代表人对应身份证的号码;
|
||||||
|
* 2、17位数字+1位数字|X ,该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。
|
||||||
|
* <a href="https://pay.weixin.qq.com/doc/v3/partner/4013059044">加密</a>
|
||||||
|
*/
|
||||||
|
@JSONField(name = "id_card_number")
|
||||||
|
private String idCardNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 身份证居住地址
|
||||||
|
* 1、主体类型为企业时,需要填写。其他主体类型,无需上传;
|
||||||
|
* 2、请按照身份证住址填写,如广东省深圳市南山区xx路xx号xx室;
|
||||||
|
* 3、长度为4-128个字符;
|
||||||
|
* 4、前后不能有空格、制表符、换行符;
|
||||||
|
* 5、不能仅含数字、特殊字符;
|
||||||
|
* 6、仅能填写数字、英文字母、汉字及特殊字符;
|
||||||
|
* 7、仅支持utf-8格式;
|
||||||
|
* 8、 该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。
|
||||||
|
* <a href="https://pay.weixin.qq.com/doc/v3/partner/4013059044">加密</a>
|
||||||
|
*/
|
||||||
|
@JSONField(name = "id_card_address")
|
||||||
|
private String idCardAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 必填
|
||||||
|
* 身份证有效期开始时间
|
||||||
|
* 1、请填写身份证有效期开始时间,格式为yyyy-MM-dd;
|
||||||
|
* 2、开始时间不能小于1900-01-01;
|
||||||
|
* 3、开始时间不能大于等于当前日期。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "card_period_begin")
|
||||||
|
private String cardPeriodBegin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 必填
|
||||||
|
* 身份证有效期截止时间
|
||||||
|
* 1、日期格式应满足合法的YYYY-MM-DD格式或长期;
|
||||||
|
* 2、结束时间需大于开始时间。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "card_period_end")
|
||||||
|
private String cardPeriodEnd;
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.czg.wechat.dto.req.entry.id;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 其他类型证件信息
|
||||||
|
* @author yjjie
|
||||||
|
* @date 2025/12/26 15:42
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class WechatEntryIdDocInfoReqDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 证件正面照片
|
||||||
|
* 1、证件类型不为“身份证”时,上传证件正面照片;
|
||||||
|
* 2、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS;
|
||||||
|
* 3、请上传彩色照片or彩色扫描件,复印件需加盖公章鲜章,可添加“微信支付”相关水印(如微信支付认证),见【指引文档】;
|
||||||
|
* 4、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "id_doc_copy")
|
||||||
|
private String idDocCopy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 证件反面照片
|
||||||
|
* 1、若证件类型为往来通行证、外国人居留证、港澳居民居住证、台湾居民居住证时,上传证件反面照片;
|
||||||
|
* 2、若证件类型为护照,无需上传反面照片;
|
||||||
|
* 3、正面拍摄、清晰、四角完整、无反光或遮挡;不得翻拍、截图、镜像、PS;
|
||||||
|
* 4、请上传彩色照片or彩色扫描件,复印件需加盖公章鲜章,可添加“微信支付”相关水印(如微信支付认证),见【指引文档】;
|
||||||
|
* 5、可上传1张图片,请填写通过图片上传API预先上传图片生成好的MediaID。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "id_doc_copy_back")
|
||||||
|
private String idDocCopyBack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 证件姓名
|
||||||
|
* 1、请填写经营者/法定代表人的证件姓名;
|
||||||
|
* 2、长度为2-100个字符;
|
||||||
|
* 3、前后不能有空格、制表符、换行符;
|
||||||
|
* 4、不能仅含数字、特殊字符;
|
||||||
|
* 5、仅能填写数字、英文字母、汉字及特殊字符;
|
||||||
|
* 6、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "id_doc_name")
|
||||||
|
private String idDocName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【必填】
|
||||||
|
* 证件号码
|
||||||
|
* 1、请填写经营者/法定代表人的证件号码:
|
||||||
|
* 护照(限境外人士):4-15位 数字|字母|连字符;
|
||||||
|
* 中国香港居民--来往内地通行证:H/h开头+8或10位数字/字母;
|
||||||
|
* 中国澳门居民--来往内地通行证:M/m开头+8或10位数字/字母;
|
||||||
|
* 中国台湾居民--来往大陆通行证:8位数字或10位数字;
|
||||||
|
* 外国人居留证:15位 数字|字母;
|
||||||
|
* 台湾居民居住证/港澳居民居住证:17位数字+1位数字|X;
|
||||||
|
* 2、该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "id_doc_number")
|
||||||
|
private String idDocNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【选填】
|
||||||
|
* 证件居住地址
|
||||||
|
* 1、主体类型为企业时,需要填写。其他主体类型,无需上传;
|
||||||
|
* 2、请按照身份证住址填写,如广东省深圳市南山区xx路xx号xx室;
|
||||||
|
* 3、长度为4-128个字符;
|
||||||
|
* 4、前后不能有空格、制表符、换行符;
|
||||||
|
* 5、不能仅含数字、特殊字符;
|
||||||
|
* 6、仅能填写数字、英文字母、汉字及特殊字符;
|
||||||
|
* 7、仅支持utf-8格式;
|
||||||
|
* 8、 该字段需要使用微信支付公钥加密(推荐),请参考获取微信支付公钥ID说明以及微信支付公钥加密敏感信息指引,也可以使用微信支付平台证书公钥加密,参考获取平台证书序列号、平台证书加密敏感信息指引。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "id_doc_address")
|
||||||
|
private String idDocAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 必填
|
||||||
|
* 证件有效期开始时间
|
||||||
|
* 1、日期格式应满足合法的YYYY-MM-DD格式;
|
||||||
|
* 2、开始时间不能小于1900-01-01;
|
||||||
|
* 3、开始时间不能大于等于当前日期。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "doc_period_begin")
|
||||||
|
private String docPeriodBegin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 必填
|
||||||
|
* 证件有效期结束时间
|
||||||
|
* 1、日期格式应满足合法的YYYY-MM-DD格式或长期;
|
||||||
|
* 2、结束时间需大于开始时间。
|
||||||
|
*/
|
||||||
|
@JSONField(name = "doc_period_end")
|
||||||
|
private String docPeriodEnd;
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
<description>第三方内容</description>
|
<description>第三方内容</description>
|
||||||
<modules>
|
<modules>
|
||||||
<module>czg-pay</module>
|
<module>czg-pay</module>
|
||||||
|
<module>aggregation-pay</module>
|
||||||
</modules>
|
</modules>
|
||||||
<artifactId>cash-sdk</artifactId>
|
<artifactId>cash-sdk</artifactId>
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package com.czg.service.account.mapper;
|
|
||||||
|
|
||||||
import com.mybatisflex.core.BaseMapper;
|
|
||||||
import com.czg.account.entity.QuickMenu;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 悬浮窗配置 映射层。
|
|
||||||
*
|
|
||||||
* @author ww
|
|
||||||
* @since 2025-12-29
|
|
||||||
*/
|
|
||||||
public interface QuickMenuMapper extends BaseMapper<QuickMenu> {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -14,9 +14,9 @@ import com.czg.config.RedisCst;
|
|||||||
import com.czg.constants.ParamCodeCst;
|
import com.czg.constants.ParamCodeCst;
|
||||||
import com.czg.exception.CzgException;
|
import com.czg.exception.CzgException;
|
||||||
import com.czg.resp.CzgResult;
|
import com.czg.resp.CzgResult;
|
||||||
import com.czg.service.RedisService;
|
|
||||||
import com.czg.service.account.mapper.CallQueueMapper;
|
import com.czg.service.account.mapper.CallQueueMapper;
|
||||||
import com.czg.service.account.mapper.CallTableMapper;
|
import com.czg.service.account.mapper.CallTableMapper;
|
||||||
|
import com.czg.service.account.util.FunUtil;
|
||||||
import com.czg.service.account.util.WechatMiniMsgUtil;
|
import com.czg.service.account.util.WechatMiniMsgUtil;
|
||||||
import com.czg.system.dto.SysParamsDTO;
|
import com.czg.system.dto.SysParamsDTO;
|
||||||
import com.czg.system.service.SysParamsService;
|
import com.czg.system.service.SysParamsService;
|
||||||
@@ -51,9 +51,9 @@ public class CallTableServiceImpl extends ServiceImpl<CallTableMapper, CallTable
|
|||||||
@Resource
|
@Resource
|
||||||
private ShopUserService shopUserService;
|
private ShopUserService shopUserService;
|
||||||
@Resource
|
@Resource
|
||||||
private StringRedisTemplate stringRedisTemplate;
|
private FunUtil funUtil;
|
||||||
@Resource
|
@Resource
|
||||||
private RedisService redisService;
|
private StringRedisTemplate stringRedisTemplate;
|
||||||
@Resource
|
@Resource
|
||||||
private ShopInfoService shopInfoService;
|
private ShopInfoService shopInfoService;
|
||||||
@Resource
|
@Resource
|
||||||
@@ -228,13 +228,13 @@ public class CallTableServiceImpl extends ServiceImpl<CallTableMapper, CallTable
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getCallNumber(Long shopId, CallTable callTable) {
|
private String getCallNumber(Long shopId, CallTable callTable) {
|
||||||
return redisService.runFunAndCheckKey(() -> {
|
return funUtil.runFunAndCheckKey(() -> {
|
||||||
String callNumKey = RedisCst.getTableCallNumKey(shopId, callTable.getId());
|
String callNumKey = RedisCst.getTableCallNumKey(shopId, callTable.getId());
|
||||||
String value = stringRedisTemplate.opsForValue().get(callNumKey);
|
String value = stringRedisTemplate.opsForValue().get(callNumKey);
|
||||||
AtomicReference<String> newVal = new AtomicReference<>("");
|
AtomicReference<String> newVal = new AtomicReference<>("");
|
||||||
// 初始化
|
// 初始化
|
||||||
if (StrUtil.isBlank(value)) {
|
if (StrUtil.isBlank(value)) {
|
||||||
Boolean setFlag = RedisService.runFunAndRetry(() -> stringRedisTemplate.opsForValue().setIfAbsent(callNumKey, callTable.getStart().toString()), flag -> !flag,
|
Boolean setFlag = FunUtil.runFunAndRetry(() -> stringRedisTemplate.opsForValue().setIfAbsent(callNumKey, callTable.getStart().toString()), flag -> !flag,
|
||||||
_ -> newVal.set(stringRedisTemplate.opsForValue().get(callNumKey)));
|
_ -> newVal.set(stringRedisTemplate.opsForValue().get(callNumKey)));
|
||||||
|
|
||||||
if (setFlag) {
|
if (setFlag) {
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package com.czg.service.account.service.impl;
|
|
||||||
|
|
||||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
|
||||||
import com.czg.account.entity.QuickMenu;
|
|
||||||
import com.czg.account.service.QuickMenuService;
|
|
||||||
import com.czg.service.account.mapper.QuickMenuMapper;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 悬浮窗配置 服务层实现。
|
|
||||||
*
|
|
||||||
* @author ww
|
|
||||||
* @since 2025-12-29
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
public class QuickMenuServiceImpl extends ServiceImpl<QuickMenuMapper, QuickMenu> implements QuickMenuService{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -99,7 +99,7 @@ public class ShopInfoServiceImpl extends ServiceImpl<ShopInfoMapper, ShopInfo> i
|
|||||||
if (shopInfo == null) {
|
if (shopInfo == null) {
|
||||||
throw new CzgException("店铺不存在");
|
throw new CzgException("店铺不存在");
|
||||||
}
|
}
|
||||||
if (shopInfo.getExpireTime() != null && (LocalDateTime.now().isAfter(shopInfo.getExpireTime()))) {
|
if (shopInfo.getExpireTime() != null && (DateUtil.date().toLocalDateTime().isAfter(shopInfo.getExpireTime()))) {
|
||||||
throw new CzgException("店铺已过期,请联系商家");
|
throw new CzgException("店铺已过期,请联系商家");
|
||||||
}
|
}
|
||||||
if (SystemConstants.OneZero.ZERO == shopInfo.getOnSale() || shopInfo.getStatus() != SystemConstants.OneZero.ONE) {
|
if (SystemConstants.OneZero.ZERO == shopInfo.getOnSale() || shopInfo.getStatus() != SystemConstants.OneZero.ONE) {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import com.czg.market.vo.InviteUserVO;
|
|||||||
import com.czg.market.vo.MemberConfigVO;
|
import com.czg.market.vo.MemberConfigVO;
|
||||||
import com.czg.order.entity.OrderInfo;
|
import com.czg.order.entity.OrderInfo;
|
||||||
import com.czg.service.account.mapper.ShopUserMapper;
|
import com.czg.service.account.mapper.ShopUserMapper;
|
||||||
import com.czg.utils.FunUtils;
|
|
||||||
import com.czg.utils.PageUtil;
|
import com.czg.utils.PageUtil;
|
||||||
import com.github.pagehelper.PageHelper;
|
import com.github.pagehelper.PageHelper;
|
||||||
import com.github.pagehelper.PageInfo;
|
import com.github.pagehelper.PageInfo;
|
||||||
@@ -62,7 +61,6 @@ public class ShopUserServiceImpl extends ServiceImpl<ShopUserMapper, ShopUser> i
|
|||||||
private MemberLevelConfigService memberLevelConfigService;
|
private MemberLevelConfigService memberLevelConfigService;
|
||||||
@DubboReference
|
@DubboReference
|
||||||
private TbMemberConfigService memberConfigService;
|
private TbMemberConfigService memberConfigService;
|
||||||
|
|
||||||
private ShopUser getUserInfo(Long shopUserId) {
|
private ShopUser getUserInfo(Long shopUserId) {
|
||||||
ShopUser shopUser = queryChain().eq(ShopUser::getId, shopUserId).one();
|
ShopUser shopUser = queryChain().eq(ShopUser::getId, shopUserId).one();
|
||||||
if (shopUser == null) {
|
if (shopUser == null) {
|
||||||
@@ -242,13 +240,9 @@ public class ShopUserServiceImpl extends ServiceImpl<ShopUserMapper, ShopUser> i
|
|||||||
shopUser.setBirthDay(null);
|
shopUser.setBirthDay(null);
|
||||||
}
|
}
|
||||||
shopUser.setNickName(userInfo.getNickName());
|
shopUser.setNickName(userInfo.getNickName());
|
||||||
// if (shopUser.getJoinTime() == null) {
|
if (shopUser.getJoinTime() == null) {
|
||||||
// shopUser.setJoinTime(LocalDateTime.now());
|
shopUser.setJoinTime(LocalDateTime.now());
|
||||||
// }
|
|
||||||
boolean b = saveOrUpdate(shopUser);
|
|
||||||
if (b) {
|
|
||||||
FunUtils.transactionSafeRun(() -> memberConfigService.joinMemberByCondition(shopId, userId, shopUser));
|
|
||||||
}
|
}
|
||||||
return b;
|
return saveOrUpdate(shopUser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import com.czg.market.entity.MkPointsUser;
|
|||||||
import com.czg.market.entity.MkShopCouponRecord;
|
import com.czg.market.entity.MkShopCouponRecord;
|
||||||
import com.czg.market.service.MkPointsUserService;
|
import com.czg.market.service.MkPointsUserService;
|
||||||
import com.czg.market.service.MkShopCouponRecordService;
|
import com.czg.market.service.MkShopCouponRecordService;
|
||||||
import com.czg.market.service.TbMemberConfigService;
|
|
||||||
import com.czg.resp.CzgResult;
|
import com.czg.resp.CzgResult;
|
||||||
import com.czg.service.RedisService;
|
import com.czg.service.RedisService;
|
||||||
import com.czg.service.account.mapper.ShopConfigMapper;
|
import com.czg.service.account.mapper.ShopConfigMapper;
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.czg.service.account.util;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Administrator
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class FunUtil {
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
public static int retryCount = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行任务并保证锁唯一
|
||||||
|
* @param supplier 业务逻辑
|
||||||
|
* @param lockKey Redis锁的Key
|
||||||
|
* @return 业务逻辑返回值
|
||||||
|
*/
|
||||||
|
public <T> T runFunAndCheckKey(Supplier<T> supplier, String lockKey) {
|
||||||
|
String lockValue = String.valueOf(System.nanoTime() + Thread.currentThread().threadId());
|
||||||
|
try {
|
||||||
|
// 尝试获取锁,超时时间 5 秒,防止死锁
|
||||||
|
boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
|
||||||
|
int count = 0;
|
||||||
|
// 初始等待 10ms
|
||||||
|
int retryDelay = 10;
|
||||||
|
|
||||||
|
while (!lock) {
|
||||||
|
// 最多重试 10 次,大约 10 秒
|
||||||
|
if (count++ > 50) {
|
||||||
|
throw new RuntimeException("系统繁忙, 稍后再试");
|
||||||
|
}
|
||||||
|
Thread.sleep(retryDelay);
|
||||||
|
// 指数退避,最大等待 200ms
|
||||||
|
retryDelay = Math.min(retryDelay * 2, 200);
|
||||||
|
lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行任务
|
||||||
|
return supplier.get();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new RuntimeException("线程被中断", e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("执行出错:{}", e.getMessage(), e);
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
// 释放锁(使用 Lua 脚本确保原子性)
|
||||||
|
unlock(lockKey, lockValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 Lua 脚本确保释放锁的原子性
|
||||||
|
* @param lockKey 锁的 Key
|
||||||
|
* @param lockValue 当前线程的锁值
|
||||||
|
*/
|
||||||
|
private void unlock(String lockKey, String lockValue) {
|
||||||
|
String luaScript =
|
||||||
|
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
|
||||||
|
"return redis.call('del', KEYS[1]) " +
|
||||||
|
"else return 0 end";
|
||||||
|
redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class),
|
||||||
|
Collections.singletonList(lockKey), lockValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<?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.QuickMenuMapper">
|
|
||||||
|
|
||||||
</mapper>
|
|
||||||
@@ -85,6 +85,8 @@ public class MkDistributionUserServiceImpl extends ServiceImpl<MkDistributionUse
|
|||||||
private ShopUserService shopUserService;
|
private ShopUserService shopUserService;
|
||||||
@DubboReference
|
@DubboReference
|
||||||
private UserInfoService userInfoService;
|
private UserInfoService userInfoService;
|
||||||
|
@DubboReference
|
||||||
|
private OrderPaymentService orderPaymentService;
|
||||||
@Resource
|
@Resource
|
||||||
private OrderInfoService orderInfoService;
|
private OrderInfoService orderInfoService;
|
||||||
@DubboReference
|
@DubboReference
|
||||||
@@ -704,16 +706,6 @@ public class MkDistributionUserServiceImpl extends ServiceImpl<MkDistributionUse
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public void distributionUserAmount(MkDistributionFlow item, OrderInfo orderInfo) {
|
|
||||||
ShopUser shopUser = shopUserService.getById(item.getDistributionUserId());
|
|
||||||
updateShopInfoAmount(orderInfo.getShopId(), item.getRewardAmount().negate(), orderInfo.getId(), TableValueConstant.DistributionAmountFlow.Type.SUB, "分销扣减");
|
|
||||||
updateIncome(item.getRewardAmount().negate(), item.getRewardAmount(), BigDecimal.ZERO,
|
|
||||||
item.getDistributionUserId(), shopUser.getUserId(), item.getShopUserId(), item.getShopId(), item.getLevel());
|
|
||||||
distributionFlowService.updateById(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void distribute(Long sourceId, String orderNo, BigDecimal amount, Long sourceUserId, Long shopId, String type) {
|
public void distribute(Long sourceId, String orderNo, BigDecimal amount, Long sourceUserId, Long shopId, String type) {
|
||||||
MkDistributionDeliver deliver = new MkDistributionDeliver().setSourceId(sourceId).setOrderNo(orderNo).setShopId(shopId).setType(type).setStatus("success");
|
MkDistributionDeliver deliver = new MkDistributionDeliver().setSourceId(sourceId).setOrderNo(orderNo).setShopId(shopId).setType(type).setStatus("success");
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ public class MkPointsUserServiceImpl extends ServiceImpl<MkPointsUserMapper, MkP
|
|||||||
.shopId(pointsUser.getShopId())
|
.shopId(pointsUser.getShopId())
|
||||||
.shopUserId(pointsUser.getShopUserId())
|
.shopUserId(pointsUser.getShopUserId())
|
||||||
.floatType(PointsConstant.SUB.getValue())
|
.floatType(PointsConstant.SUB.getValue())
|
||||||
.floatPoints(-floatPoints)
|
.floatPoints(floatPoints)
|
||||||
.balancePoints(pointsUser.getPointBalance())
|
.balancePoints(pointsUser.getPointBalance())
|
||||||
.sourceId(orderId.toString())
|
.sourceId(orderId.toString())
|
||||||
.content(reason)
|
.content(reason)
|
||||||
|
|||||||
@@ -175,9 +175,9 @@ public class MkShopRechargeServiceImpl extends ServiceImpl<MkShopRechargeMapper,
|
|||||||
|
|
||||||
// 标准充值
|
// 标准充值
|
||||||
if (rechargeDetailId != null) {
|
if (rechargeDetailId != null) {
|
||||||
MkShopRechargeDetail rechargeDetail = shopRechargeDetailService.getById(rechargeDetailId);
|
|
||||||
shopUserMoneyEditDTO.setMoney(rechargeDetail.getAmount());
|
|
||||||
FunUtils.asyncSafeRunVoid(() -> {
|
FunUtils.asyncSafeRunVoid(() -> {
|
||||||
|
MkShopRechargeDetail rechargeDetail = shopRechargeDetailService.getById(rechargeDetailId);
|
||||||
|
shopUserMoneyEditDTO.setMoney(rechargeDetail.getAmount());
|
||||||
// 赠送金额
|
// 赠送金额
|
||||||
ShopUserMoneyEditDTO shopUserMoneyEditRewardDTO = new ShopUserMoneyEditDTO()
|
ShopUserMoneyEditDTO shopUserMoneyEditRewardDTO = new ShopUserMoneyEditDTO()
|
||||||
.setId(shopUserId)
|
.setId(shopUserId)
|
||||||
|
|||||||
@@ -361,7 +361,6 @@ public class TbMemberConfigServiceImpl extends ServiceImpl<TbMemberConfigMapper,
|
|||||||
memberExpFlowService.save(expFlow);
|
memberExpFlowService.save(expFlow);
|
||||||
|
|
||||||
upShopUser.setExperience(shopUser.getExperience() + exp);
|
upShopUser.setExperience(shopUser.getExperience() + exp);
|
||||||
shopUser.setExperience(upShopUser.getExperience());
|
|
||||||
// 修改会员等级
|
// 修改会员等级
|
||||||
MemberLevelConfig nextConfig = levelConfigService.getOne(new QueryWrapper().eq(MemberLevelConfig::getShopId, shopUser.getMainShopId())
|
MemberLevelConfig nextConfig = levelConfigService.getOne(new QueryWrapper().eq(MemberLevelConfig::getShopId, shopUser.getMainShopId())
|
||||||
.gt(MemberLevelConfig::getExperienceValue, levelVO.getExperienceValue()).orderBy(MemberLevelConfig::getExperienceValue, true).limit(1));
|
.gt(MemberLevelConfig::getExperienceValue, levelVO.getExperienceValue()).orderBy(MemberLevelConfig::getExperienceValue, true).limit(1));
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
package com.czg.service.order.print;
|
package com.czg.service.order.print;
|
||||||
|
|
||||||
import cn.hutool.core.date.DateUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
import cn.hutool.core.text.UnicodeUtil;
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.http.HttpUtil;
|
|
||||||
import cn.hutool.json.JSONObject;
|
|
||||||
import cn.hutool.json.JSONUtil;
|
|
||||||
import com.czg.account.dto.HandoverRecordDTO;
|
import com.czg.account.dto.HandoverRecordDTO;
|
||||||
|
import com.czg.account.entity.HandoverRecord;
|
||||||
import com.czg.account.entity.PrintMachine;
|
import com.czg.account.entity.PrintMachine;
|
||||||
import com.czg.account.entity.ShopInfo;
|
import com.czg.account.entity.ShopInfo;
|
||||||
|
import com.czg.account.service.ShopInfoService;
|
||||||
import com.czg.order.entity.OrderDetail;
|
import com.czg.order.entity.OrderDetail;
|
||||||
import com.czg.order.entity.OrderInfo;
|
import com.czg.order.entity.OrderInfo;
|
||||||
import com.czg.service.order.enums.OrderStatusEnums;
|
import com.czg.service.order.enums.OrderStatusEnums;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.codec.digest.DigestUtils;
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
@@ -21,21 +20,24 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Administrator
|
* @author Administrator
|
||||||
* 接口文档 <a href="https://help.feieyun.com/home/doc/zh;nav=1-1">
|
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class FeiPrinter extends PrinterHandler implements PrinterImpl {
|
public class FeiPrinter extends PrinterHandler implements PrinterImpl {
|
||||||
|
@Resource
|
||||||
|
private RestTemplate restTemplate;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ShopInfoService shopInfoService;
|
||||||
|
|
||||||
// API 地址
|
// API 地址
|
||||||
private static final String URL = "http://api.feieyun.cn/Api/Open/";
|
private static final String URL = "http://api.feieyun.cn/Api/Open/";
|
||||||
@@ -70,8 +72,10 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
String remark = orderDetail.getRemark();
|
String remark = orderDetail.getRemark();
|
||||||
String content = buildDishPrintData(false, getPickupNum(orderInfo), orderInfo.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
|
String content = buildDishPrintData(false, getPickupNum(orderInfo), orderInfo.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
|
||||||
orderDetail.getProductName(), orderDetail.getSkuName(), orderDetail.getNum(), remark, orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
|
orderDetail.getProductName(), orderDetail.getSkuName(), orderDetail.getNum(), remark, orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
|
||||||
String o = sendPrintRequest(machine.getAddress(), content, null, "1");
|
Object o = sendPrintRequest(machine.getAddress(), content, null, "1");
|
||||||
printMachineLogService.save(orderInfo.getId(), machine, "新订单", content, o);
|
printMachineLogService.save(machine, "新订单", content, o);
|
||||||
|
|
||||||
|
// printMachineLogService.save(machine, "新订单", , );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -80,8 +84,8 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
String remark = orderDetail.getRemark();
|
String remark = orderDetail.getRemark();
|
||||||
String content = buildDishPrintData(true, getPickupNum(orderInfo), orderInfo.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
|
String content = buildDishPrintData(true, getPickupNum(orderInfo), orderInfo.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
|
||||||
orderDetail.getProductName(), orderDetail.getSkuName(), orderDetail.getReturnNum(), remark, orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
|
orderDetail.getProductName(), orderDetail.getSkuName(), orderDetail.getReturnNum(), remark, orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
|
||||||
String o = sendPrintRequest(machine.getAddress(), content, null, "1");
|
Object o = sendPrintRequest(machine.getAddress(), content, null, "1");
|
||||||
printMachineLogService.save(orderInfo.getId(), machine, "退款单", content, o);
|
printMachineLogService.save(machine, "退款单", content, o);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,8 +106,8 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
.setRemark(orderInfo.getRemark())
|
.setRemark(orderInfo.getRemark())
|
||||||
.setDiscountAmount(orderInfo.getOriginAmount().subtract(orderInfo.getPayAmount()).toPlainString());
|
.setDiscountAmount(orderInfo.getOriginAmount().subtract(orderInfo.getPayAmount()).toPlainString());
|
||||||
String string = buildOrderPrintData(printInfoDTO, detailList);
|
String string = buildOrderPrintData(printInfoDTO, detailList);
|
||||||
String o = sendPrintRequest(machine.getAddress(), string, null, printerNum);
|
Object o = sendPrintRequest(machine.getAddress(), string, null, printerNum);
|
||||||
printMachineLogService.save(orderInfo.getId(), machine, "结算单", string, o);
|
printMachineLogService.save(machine, "退款单", string, o);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,24 +134,24 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
.setRemark(orderInfo.getRemark())
|
.setRemark(orderInfo.getRemark())
|
||||||
.setDiscountAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee()).subtract(orderInfo.getPayAmount())).toPlainString());
|
.setDiscountAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee()).subtract(orderInfo.getPayAmount())).toPlainString());
|
||||||
printInfoDTO.setPrintTitle(printInfoDTO.getPrintTitle());
|
printInfoDTO.setPrintTitle(printInfoDTO.getPrintTitle());
|
||||||
if (orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0) {
|
if(orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0){
|
||||||
printInfoDTO.setSeatNum(orderInfo.getSeatNum().toString());
|
printInfoDTO.setSeatNum(orderInfo.getSeatNum().toString());
|
||||||
printInfoDTO.setSeatAmount(orderInfo.getSeatAmount().toPlainString());
|
printInfoDTO.setSeatAmount(orderInfo.getSeatAmount().toPlainString());
|
||||||
}
|
}
|
||||||
if (orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0) {
|
if(orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0){
|
||||||
printInfoDTO.setPackFee(orderInfo.getPackFee().toPlainString());
|
printInfoDTO.setPackFee(orderInfo.getPackFee().toPlainString());
|
||||||
}
|
}
|
||||||
|
|
||||||
String string = buildOrderPrintData(printInfoDTO, detailList);
|
String string = buildOrderPrintData(printInfoDTO, detailList);
|
||||||
String resp = sendPrintRequest(machine.getAddress(), string, null, printerNum);
|
Object resp = sendPrintRequest(machine.getAddress(), string, null, printerNum);
|
||||||
printMachineLogService.save(orderInfo.getId(), machine, "结算单", string, resp);
|
printMachineLogService.save(machine, "结算单", string, resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void callNumPrint(PrintMachine machine, String callNum, String shopName, String tableName, String tableNote, String preNum, String codeUrl, LocalDateTime takeTime, String shopNote) {
|
protected void callNumPrint(PrintMachine machine, String callNum, String shopName, String tableName, String tableNote, String preNum, String codeUrl, LocalDateTime takeTime, String shopNote) {
|
||||||
String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一条新的排号记录\"}";
|
String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一条新的排号记录\"}";
|
||||||
String data = buildCallTicketData(shopName, tableName, callNum, preNum, codeUrl, shopNote, takeTime);
|
String data = buildCallTicketData(shopName, tableName, callNum, preNum, codeUrl, shopNote, takeTime);
|
||||||
String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, "1");
|
Object resp = sendPrintRequest(machine.getAddress(), data, voiceJson, "1");
|
||||||
printMachineLogService.save(machine, "叫号单", data, resp);
|
printMachineLogService.save(machine, "叫号单", data, resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +170,7 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String sendPrintRequest(String address, String metaPrintData, String voiceData, String printerNum) {
|
public <R> R sendPrintRequest(String address, String metaPrintData, String voiceData, String printerNum) {
|
||||||
log.info("飞蛾打印机开始发送打印请求, 设备地址: {}, 元数据: {}", address, metaPrintData);
|
log.info("飞蛾打印机开始发送打印请求, 设备地址: {}, 元数据: {}", address, metaPrintData);
|
||||||
String time = String.valueOf(System.currentTimeMillis() / 1000);
|
String time = String.valueOf(System.currentTimeMillis() / 1000);
|
||||||
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
||||||
@@ -185,68 +189,12 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
|
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
|
||||||
|
|
||||||
String result = restTemplate.postForObject(URL, requestEntity, String.class);
|
String result = restTemplate.postForObject(URL, requestEntity, String.class);
|
||||||
log.info("飞鹅打印结果: {}", result);
|
log.info("打印结果: {}", result);
|
||||||
return result;
|
return (R) result;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("打印请求失败: {}", e.getMessage());
|
log.error("打印请求失败: {}", e.getMessage());
|
||||||
throw new RuntimeException("飞蛾打印请求失败", e);
|
throw new RuntimeException("飞蛾打印请求失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查飞鹅打印机 打印任务是否已打印
|
|
||||||
*
|
|
||||||
* @param printOrderId 打印订单编号
|
|
||||||
* @return null-未知错误,true-已打印,false-未打印
|
|
||||||
*/
|
|
||||||
public Boolean checkFPrintStatus(String printOrderId) {
|
|
||||||
String time = String.valueOf(System.currentTimeMillis() / 1000);
|
|
||||||
Map<String, Object> paramMap = new HashMap<>();
|
|
||||||
paramMap.put("user", USER);
|
|
||||||
paramMap.put("stime", time);
|
|
||||||
paramMap.put("sig", signature(time));
|
|
||||||
paramMap.put("apiname", "Open_queryOrderState");
|
|
||||||
paramMap.put("orderid", printOrderId);
|
|
||||||
Boolean ret;
|
|
||||||
try {
|
|
||||||
String resp = HttpUtil.post(URL, paramMap, 1000 * 5);
|
|
||||||
//成功 {"msg":"ok","ret":0,"data":true,"serverExecutedTime":4}
|
|
||||||
//失败 {"msg":"ok","ret":0,"data":false,"serverExecutedTime":4}
|
|
||||||
JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp));
|
|
||||||
log.info("飞鹅打印机 打印任务状态响应: {}", json);
|
|
||||||
ret = json.getBool("data");
|
|
||||||
} catch (Exception e) {
|
|
||||||
ret = null;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查飞鹅打印机是否在线
|
|
||||||
*
|
|
||||||
* @param sn 设备编号
|
|
||||||
* @return 在线,工作状态正常。/离线。/未知错误
|
|
||||||
*/
|
|
||||||
public String checkOnline(String sn) {
|
|
||||||
String time = String.valueOf(System.currentTimeMillis() / 1000);
|
|
||||||
Map<String, Object> paramMap = new HashMap<>();
|
|
||||||
paramMap.put("user", USER);
|
|
||||||
paramMap.put("stime", time);
|
|
||||||
paramMap.put("sig", signature(time));
|
|
||||||
paramMap.put("apiname", "Open_queryPrinterStatus");
|
|
||||||
paramMap.put("sn", sn);
|
|
||||||
String msg;
|
|
||||||
try {
|
|
||||||
String resp = HttpUtil.post(URL, paramMap, 1000 * 5);
|
|
||||||
//成功 开机 {"msg":"ok","ret":0,"data":"在线,工作状态正常。","serverExecutedTime":4}
|
|
||||||
//成功 离线 {"msg":"ok","ret":0,"data":"离线。","serverExecutedTime":7}
|
|
||||||
JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp));
|
|
||||||
log.info("飞鹅打印机状态响应: {}", json);
|
|
||||||
msg = json.getStr("data");
|
|
||||||
} catch (Exception e) {
|
|
||||||
msg = "未知错误";
|
|
||||||
}
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import lombok.ToString;
|
|||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.dubbo.config.annotation.DubboReference;
|
import org.apache.dubbo.config.annotation.DubboReference;
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -50,9 +49,10 @@ public abstract class PrinterHandler {
|
|||||||
@Setter
|
@Setter
|
||||||
protected PrinterHandler nextPrinter;
|
protected PrinterHandler nextPrinter;
|
||||||
protected String printerBrand;
|
protected String printerBrand;
|
||||||
|
// 创建 ThreadLocal 变量
|
||||||
|
private static final ThreadLocal<String> ERR_MSG = ThreadLocal.withInitial(() -> "");
|
||||||
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
protected RestTemplate restTemplate;
|
|
||||||
@Resource
|
@Resource
|
||||||
protected OrderDetailService orderDetailService;
|
protected OrderDetailService orderDetailService;
|
||||||
@Resource
|
@Resource
|
||||||
@@ -84,11 +84,7 @@ public abstract class PrinterHandler {
|
|||||||
@Getter
|
@Getter
|
||||||
public enum PrintTypeEnum {
|
public enum PrintTypeEnum {
|
||||||
HANDOVER("交班", "handover"),
|
HANDOVER("交班", "handover"),
|
||||||
ORDER("订单", "order"),
|
ORDER("订单", "order"), ONE("菜品", "one"), CALL("叫号", "call"), ONE_AND_ORDER("菜品和结算单同时打印", "oneAndOrder"), PRE_ORDER("预结算单", "preOrder");
|
||||||
ONE("菜品", "one"),
|
|
||||||
CALL("叫号", "call"),
|
|
||||||
ONE_AND_ORDER("菜品和结算单同时打印", "oneAndOrder"),
|
|
||||||
PRE_ORDER("预结算单", "preOrder");
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String code;
|
private final String code;
|
||||||
|
|
||||||
@@ -161,7 +157,7 @@ public abstract class PrinterHandler {
|
|||||||
|
|
||||||
if (StrUtil.isNotEmpty(printMethod)) {
|
if (StrUtil.isNotEmpty(printMethod)) {
|
||||||
List<String> arrayList = switch (printMethod) {
|
List<String> arrayList = switch (printMethod) {
|
||||||
case "all" -> Arrays.asList("one", "normal", "all");
|
case "all" ->Arrays.asList("one", "normal", "all");
|
||||||
case "one" -> Arrays.asList("one", "all");
|
case "one" -> Arrays.asList("one", "all");
|
||||||
case "normal" -> Arrays.asList("normal", "all");
|
case "normal" -> Arrays.asList("normal", "all");
|
||||||
default -> new ArrayList<>();
|
default -> new ArrayList<>();
|
||||||
@@ -177,6 +173,10 @@ public abstract class PrinterHandler {
|
|||||||
wrapper.like(PrintMachine::getPrintType, printType);
|
wrapper.like(PrintMachine::getPrintType, printType);
|
||||||
}
|
}
|
||||||
List<PrintMachine> list = printMachineService.list(wrapper);
|
List<PrintMachine> list = printMachineService.list(wrapper);
|
||||||
|
// for (PrintMachine item : list) {
|
||||||
|
// //实际打印以传递的参数为准
|
||||||
|
// item.setPrintMethod(printMethod);
|
||||||
|
// }
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
log.error("店铺未配置打印机,店铺id: {}", shopId);
|
log.error("店铺未配置打印机,店铺id: {}", shopId);
|
||||||
return list;
|
return list;
|
||||||
@@ -188,8 +188,7 @@ public abstract class PrinterHandler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理打印
|
* 处理打印
|
||||||
*
|
* @param data 传递的数据
|
||||||
* @param data 传递的数据
|
|
||||||
* @param printTypeEnum order returnOrder preOrder one call handover
|
* @param printTypeEnum order returnOrder preOrder one call handover
|
||||||
*/
|
*/
|
||||||
public void handler(String data, PrintTypeEnum printTypeEnum) {
|
public void handler(String data, PrintTypeEnum printTypeEnum) {
|
||||||
@@ -315,7 +314,7 @@ public abstract class PrinterHandler {
|
|||||||
item.setNum(item.getNum().subtract(printDetailInfo.getPrintNum()));
|
item.setNum(item.getNum().subtract(printDetailInfo.getPrintNum()));
|
||||||
item.setReturnNum(item.getReturnNum().subtract(printDetailInfo.getPrintReturnNum()));
|
item.setReturnNum(item.getReturnNum().subtract(printDetailInfo.getPrintReturnNum()));
|
||||||
orderDetails.add(item);
|
orderDetails.add(item);
|
||||||
} else {
|
}else {
|
||||||
orderDetails.add(item);
|
orderDetails.add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,37 +334,34 @@ public abstract class PrinterHandler {
|
|||||||
log.info("准备开始打印交班");
|
log.info("准备开始打印交班");
|
||||||
if (data instanceof HandoverRecordDTO record) {
|
if (data instanceof HandoverRecordDTO record) {
|
||||||
handoverPrint(machine, record);
|
handoverPrint(machine, record);
|
||||||
} else {
|
}else {
|
||||||
throw new RuntimeException("传递数据类型有误");
|
throw new RuntimeException("传递数据类型有误");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PrintTypeEnum.ORDER:
|
case PrintTypeEnum.ORDER:
|
||||||
log.info("准备开始打印订单");
|
log.info("准备开始打印订单");
|
||||||
if (data instanceof OrderInfo orderInfo) {
|
if (data instanceof OrderInfo orderInfo) {
|
||||||
redisService.set("order:print:" + orderInfo.getId(),"", 180);
|
|
||||||
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
|
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
|
||||||
onlyFrontDesk(machine, false, orderInfo, orderDetailList);
|
onlyFrontDesk(machine, false, orderInfo, orderDetailList);
|
||||||
} else {
|
}else {
|
||||||
throw new RuntimeException("传递数据类型有误");
|
throw new RuntimeException("传递数据类型有误");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PrintTypeEnum.PRE_ORDER:
|
case PrintTypeEnum.PRE_ORDER:
|
||||||
log.info("准备开始打印预结算订单");
|
log.info("准备开始打印预结算订单");
|
||||||
if (data instanceof OrderInfo orderInfo) {
|
if (data instanceof OrderInfo orderInfo) {
|
||||||
redisService.set("order:print:" + orderInfo.getId(),"", 180);
|
|
||||||
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
|
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
|
||||||
onlyFrontDesk(machine, true, orderInfo, orderDetailList);
|
onlyFrontDesk(machine, true, orderInfo, orderDetailList);
|
||||||
} else {
|
}else {
|
||||||
throw new RuntimeException("传递数据类型有误");
|
throw new RuntimeException("传递数据类型有误");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PrintTypeEnum.ONE:
|
case PrintTypeEnum.ONE:
|
||||||
log.info("准备开始打印菜品单");
|
log.info("准备开始打印菜品单");
|
||||||
if (data instanceof OrderInfo orderInfo) {
|
if (data instanceof OrderInfo orderInfo) {
|
||||||
redisService.set("order:print:" + orderInfo.getId(),"", 180);
|
|
||||||
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
|
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
|
||||||
onlyKitchen(machine, orderInfo, orderDetailList);
|
onlyKitchen(machine, orderInfo, orderDetailList);
|
||||||
} else {
|
}else {
|
||||||
throw new RuntimeException("传递数据类型有误");
|
throw new RuntimeException("传递数据类型有误");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -373,14 +369,13 @@ public abstract class PrinterHandler {
|
|||||||
log.info("准备开始打印叫号单");
|
log.info("准备开始打印叫号单");
|
||||||
if (data instanceof CallQueue queue) {
|
if (data instanceof CallQueue queue) {
|
||||||
onlyCallNumPrint(machine, queue);
|
onlyCallNumPrint(machine, queue);
|
||||||
} else {
|
}else {
|
||||||
throw new RuntimeException("传递数据类型有误");
|
throw new RuntimeException("传递数据类型有误");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PrintTypeEnum.ONE_AND_ORDER:
|
case PrintTypeEnum.ONE_AND_ORDER:
|
||||||
log.info("准备开始打印菜品以及结算单");
|
log.info("准备开始打印菜品以及结算单");
|
||||||
if (data instanceof OrderInfo orderInfo) {
|
if (data instanceof OrderInfo orderInfo) {
|
||||||
redisService.set("order:print:" + orderInfo.getId(),"", 180);
|
|
||||||
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
|
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
|
||||||
switch (machine.getPrintMethod()) {
|
switch (machine.getPrintMethod()) {
|
||||||
case "all":
|
case "all":
|
||||||
@@ -398,7 +393,7 @@ public abstract class PrinterHandler {
|
|||||||
default:
|
default:
|
||||||
throw new RuntimeException("打印方法有误");
|
throw new RuntimeException("打印方法有误");
|
||||||
}
|
}
|
||||||
} else {
|
}else {
|
||||||
throw new RuntimeException("传递数据类型有误");
|
throw new RuntimeException("传递数据类型有误");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,7 +448,17 @@ public abstract class PrinterHandler {
|
|||||||
log.info("台位费商品,不打印");
|
log.info("台位费商品,不打印");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// ProdSku sku = prodSkuService.getById(item.getSkuId());
|
||||||
|
// if (isTemporary == 0 && sku == null) {
|
||||||
|
// log.error("商品不存在, id: {}", item.getSkuId());
|
||||||
|
// return;
|
||||||
|
// } else if (isTemporary == 1) {
|
||||||
|
// sku = new ProdSku();
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// String remark = StrUtil.isNotBlank(sku.getSpecInfo()) ? sku.getSpecInfo() : "";
|
||||||
|
// item.setRemark(remark);
|
||||||
if (item.getReturnNum().compareTo(BigDecimal.ZERO) > 0) {
|
if (item.getReturnNum().compareTo(BigDecimal.ZERO) > 0) {
|
||||||
returnDishesPrint(orderInfo, item, machine);
|
returnDishesPrint(orderInfo, item, machine);
|
||||||
}
|
}
|
||||||
@@ -464,9 +469,8 @@ public abstract class PrinterHandler {
|
|||||||
|
|
||||||
// 保存已打印信息
|
// 保存已打印信息
|
||||||
OrderDetail orderDetail = detailMap.get(item.getId());
|
OrderDetail orderDetail = detailMap.get(item.getId());
|
||||||
redisService.set(RedisCst.getPrintOrderDetailKey(orderInfo.getId(), item.getId()),
|
redisService.set(RedisCst.getPrintOrderDetailKey(orderInfo.getId(), item.getId()), JSONObject.toJSONString(new PrintDetailInfo().setPrint(item.getIsPrint() == 1).setDetailId(item.getId())
|
||||||
JSONObject.toJSONString(new PrintDetailInfo().setPrint(item.getIsPrint() == 1).setDetailId(item.getId())
|
.setPrintNum(orderDetail.getNum()).setPrintReturnNum(orderDetail.getReturnNum())), 3600 * 24);
|
||||||
.setPrintNum(orderDetail.getNum()).setPrintReturnNum(orderDetail.getReturnNum())), 3600 * 24);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import cn.hutool.json.JSONUtil;
|
|||||||
import com.alibaba.fastjson2.JSONArray;
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.czg.account.dto.HandoverRecordDTO;
|
import com.czg.account.dto.HandoverRecordDTO;
|
||||||
|
import com.czg.account.entity.HandoverRecord;
|
||||||
import com.czg.order.entity.OrderDetail;
|
import com.czg.order.entity.OrderDetail;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
@@ -68,9 +69,10 @@ public interface PrinterImpl {
|
|||||||
* @param metaPrintData 打印元数据
|
* @param metaPrintData 打印元数据
|
||||||
* @param voiceData 语音信息
|
* @param voiceData 语音信息
|
||||||
* @param printNum 打印联数
|
* @param printNum 打印联数
|
||||||
|
* @param <R> 返回数据类型
|
||||||
* @return 打印结果
|
* @return 打印结果
|
||||||
*/
|
*/
|
||||||
String sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum);
|
<R> R sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前打印机标签信息
|
* 获取当前打印机标签信息
|
||||||
@@ -558,6 +560,12 @@ public interface PrinterImpl {
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println("水煮肉片".length());
|
||||||
|
System.out.println(StrUtil.repeat(' ', 8));
|
||||||
|
System.out.println(StrUtil.fillAfter("水煮肉片", ' ', 21));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取填充字符串, 并且换行
|
* 获取填充字符串, 并且换行
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import cn.hutool.core.date.DateUtil;
|
|||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.crypto.SecureUtil;
|
import cn.hutool.crypto.SecureUtil;
|
||||||
import cn.hutool.http.HttpUtil;
|
|
||||||
import com.czg.account.dto.HandoverRecordDTO;
|
import com.czg.account.dto.HandoverRecordDTO;
|
||||||
|
import com.czg.account.entity.HandoverRecord;
|
||||||
import com.czg.account.entity.PrintMachine;
|
import com.czg.account.entity.PrintMachine;
|
||||||
import com.czg.account.entity.ShopInfo;
|
import com.czg.account.entity.ShopInfo;
|
||||||
|
import com.czg.account.service.ShopInfoService;
|
||||||
import com.czg.order.entity.OrderDetail;
|
import com.czg.order.entity.OrderDetail;
|
||||||
import com.czg.order.entity.OrderInfo;
|
import com.czg.order.entity.OrderInfo;
|
||||||
import com.czg.service.order.enums.OrderStatusEnums;
|
import com.czg.service.order.enums.OrderStatusEnums;
|
||||||
@@ -19,14 +20,16 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 博实结-云享印打印机
|
* 云享印打印机
|
||||||
* 接口文档 <a href="https://bsj2.yuque.com/bsj/izhmfn/rr8b5g?#ZbE6s">
|
*
|
||||||
* @author Administrator
|
* @author Administrator
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -42,6 +45,12 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
//APPSECRET
|
//APPSECRET
|
||||||
private static final String APP_SECRET = "2022bsjZF544GAH";
|
private static final String APP_SECRET = "2022bsjZF544GAH";
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ShopInfoService shopInfoService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RestTemplate restTemplate;
|
||||||
|
|
||||||
public YxyPrinter() {
|
public YxyPrinter() {
|
||||||
super("云想印");
|
super("云想印");
|
||||||
}
|
}
|
||||||
@@ -66,7 +75,7 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum) {
|
public <R> R sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum) {
|
||||||
log.info("开始请求云享印,请求数据:{}, {}", voiceData, metaPrintData);
|
log.info("开始请求云享印,请求数据:{}, {}", voiceData, metaPrintData);
|
||||||
//设备名称
|
//设备名称
|
||||||
//行为方式 1:只打印数据 2:只播放信息 3:打印数据并播放信息
|
//行为方式 1:只打印数据 2:只播放信息 3:打印数据并播放信息
|
||||||
@@ -78,9 +87,10 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
String time = String.valueOf(System.currentTimeMillis());
|
String time = String.valueOf(System.currentTimeMillis());
|
||||||
String uuid = UUID.randomUUID().toString();
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
Map<String, String> param = getToken(time, uuid);
|
||||||
//参数
|
//参数
|
||||||
MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
|
MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
|
||||||
multiValueMap.add("token", getToken(time, uuid));
|
multiValueMap.add("token", param.get("TOKEN"));
|
||||||
multiValueMap.add("devName", address);
|
multiValueMap.add("devName", address);
|
||||||
multiValueMap.add("actWay", 3);
|
multiValueMap.add("actWay", 3);
|
||||||
multiValueMap.add("cn", printNum);
|
multiValueMap.add("cn", printNum);
|
||||||
@@ -98,7 +108,7 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
httpEntity, String.class);
|
httpEntity, String.class);
|
||||||
|
|
||||||
log.info("请求云享印成功,响应数据: {}", httpResponse);
|
log.info("请求云享印成功,响应数据: {}", httpResponse);
|
||||||
return httpResponse;
|
return (R) httpResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -106,8 +116,10 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
String buildDishPrintData = buildDishPrintData(false, getPickupNum(orderInfo), DateUtil.format(orderDetail.getCreateTime(), "yyyy-MM-dd HH:mm:ss"), orderDetail.getProductName(), orderDetail.getSkuName(),
|
String buildDishPrintData = buildDishPrintData(false, getPickupNum(orderInfo), DateUtil.format(orderDetail.getCreateTime(), "yyyy-MM-dd HH:mm:ss"), orderDetail.getProductName(), orderDetail.getSkuName(),
|
||||||
orderDetail.getNum(), orderDetail.getRemark(), orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
|
orderDetail.getNum(), orderDetail.getRemark(), orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
|
||||||
String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
|
String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
|
||||||
String resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1");
|
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
|
||||||
printMachineLogService.save(orderInfo.getId(), machine, "新订单", buildDishPrintData, resp);
|
Object resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1");
|
||||||
|
// sendPrintRequest(voiceJson, 3, 1, machine.getAddress(), data);
|
||||||
|
printMachineLogService.save(machine, "新订单", buildDishPrintData, resp);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,9 +127,10 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
protected void returnDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine) {
|
protected void returnDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine) {
|
||||||
String buildDishPrintData = buildDishPrintData(true, getPickupNum(orderInfo), DateUtil.format(orderDetail.getCreateTime(), "yyyy-MM-dd HH:mm:ss"), orderDetail.getProductName(), orderDetail.getSkuName(),
|
String buildDishPrintData = buildDishPrintData(true, getPickupNum(orderInfo), DateUtil.format(orderDetail.getCreateTime(), "yyyy-MM-dd HH:mm:ss"), orderDetail.getProductName(), orderDetail.getSkuName(),
|
||||||
orderDetail.getReturnNum(), orderDetail.getRemark(), orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
|
orderDetail.getReturnNum(), orderDetail.getRemark(), orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
|
||||||
|
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
|
||||||
String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
|
String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
|
||||||
String resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1");
|
Object resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1");
|
||||||
printMachineLogService.save(orderInfo.getId(), machine, "退款单", buildDishPrintData, resp);
|
printMachineLogService.save(machine, "退款单", buildDishPrintData, resp);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,17 +141,18 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
.setOrderNo(orderInfo.getOrderNo()).setTradeDate(DateUtil.format(orderInfo.getCreateTime(), "yyyy-MM-dd HH:mm:ss")).setOperator("【POS-1】001").setPayAmount(orderInfo.getPayAmount().toPlainString())
|
.setOrderNo(orderInfo.getOrderNo()).setTradeDate(DateUtil.format(orderInfo.getCreateTime(), "yyyy-MM-dd HH:mm:ss")).setOperator("【POS-1】001").setPayAmount(orderInfo.getPayAmount().toPlainString())
|
||||||
.setOriginalAmount(orderInfo.getOriginAmount().toPlainString()).setReturn(isReturn(orderInfo))
|
.setOriginalAmount(orderInfo.getOriginAmount().toPlainString()).setReturn(isReturn(orderInfo))
|
||||||
.setBalance(balance).setPayType((ObjectUtil.isEmpty(orderInfo.getPayType()) || ObjectUtil.isNull(orderInfo.getPayType()) ? "" : orderInfo.getPayType())).setIntegral("0")
|
.setBalance(balance).setPayType((ObjectUtil.isEmpty(orderInfo.getPayType()) || ObjectUtil.isNull(orderInfo.getPayType()) ? "" : orderInfo.getPayType())).setIntegral("0")
|
||||||
.setOutNumber(orderInfo.getTakeCode()).setPrintTitle("退款单")
|
.setOutNumber(orderInfo.getTakeCode()).setPrintTitle("结算单")
|
||||||
.setRemark(orderInfo.getRemark()).setDiscountAmount(orderInfo.getOriginAmount().subtract(orderInfo.getPayAmount()).toPlainString());
|
.setRemark(orderInfo.getRemark()).setDiscountAmount(orderInfo.getOriginAmount().subtract(orderInfo.getPayAmount()).toPlainString());
|
||||||
|
|
||||||
String data = buildOrderPrintData(printInfoDTO, detailList);
|
String data = buildOrderPrintData(printInfoDTO, detailList);
|
||||||
String voiceJson = "{\"PbizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
|
String voiceJson = "{\"PbizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
|
||||||
|
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
|
||||||
String printerNum = "1";
|
String printerNum = "1";
|
||||||
if (StrUtil.isNotBlank(machine.getPrintQty())) {
|
if (StrUtil.isNotBlank(machine.getPrintQty())) {
|
||||||
printerNum = machine.getPrintQty().split("\\^")[1];
|
printerNum = machine.getPrintQty().split("\\^")[1];
|
||||||
}
|
}
|
||||||
String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, printerNum);
|
String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, printerNum);
|
||||||
printMachineLogService.save(orderInfo.getId(), machine, "退款单", data, resp);
|
printMachineLogService.save(machine, "新订单", data, resp);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,38 +173,33 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
.setRemark(orderInfo.getRemark())
|
.setRemark(orderInfo.getRemark())
|
||||||
.setDiscountAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee()).subtract(orderInfo.getPayAmount())).toPlainString());
|
.setDiscountAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee()).subtract(orderInfo.getPayAmount())).toPlainString());
|
||||||
printInfoDTO.setPrintTitle(printInfoDTO.getPrintTitle());
|
printInfoDTO.setPrintTitle(printInfoDTO.getPrintTitle());
|
||||||
if (orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0) {
|
if(orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0){
|
||||||
printInfoDTO.setSeatNum(orderInfo.getSeatNum().toString());
|
printInfoDTO.setSeatNum(orderInfo.getSeatNum().toString());
|
||||||
printInfoDTO.setSeatAmount(orderInfo.getSeatAmount().toPlainString());
|
printInfoDTO.setSeatAmount(orderInfo.getSeatAmount().toPlainString());
|
||||||
}
|
}
|
||||||
if (orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0) {
|
if(orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0){
|
||||||
printInfoDTO.setPackFee(orderInfo.getPackFee().toPlainString());
|
printInfoDTO.setPackFee(orderInfo.getPackFee().toPlainString());
|
||||||
}
|
}
|
||||||
|
|
||||||
String data = buildOrderPrintData(printInfoDTO, detailList);
|
String data = buildOrderPrintData(printInfoDTO, detailList);
|
||||||
String voiceJson = "{\"PbizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
|
String voiceJson = "{\"PbizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
|
||||||
|
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
|
||||||
String printerNum = "1";
|
String printerNum = "1";
|
||||||
if (StrUtil.isNotBlank(machine.getPrintQty())) {
|
if (StrUtil.isNotBlank(machine.getPrintQty())) {
|
||||||
printerNum = machine.getPrintQty().split("\\^")[1];
|
printerNum = machine.getPrintQty().split("\\^")[1];
|
||||||
}
|
}
|
||||||
String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, printerNum);
|
String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, printerNum);
|
||||||
printMachineLogService.save(orderInfo.getId(), machine, "结算单", data, resp);
|
// printMachineLogService.save(machine, printType, data, resp);
|
||||||
|
printMachineLogService.save(machine, "新订单", data, resp);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 叫号单打印
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
protected void callNumPrint(PrintMachine machine, String callNum, String shopName, String tableName, String tableNote, String preNum, String codeUrl, LocalDateTime takeTime, String shopNote) {
|
protected void callNumPrint(PrintMachine machine, String callNum, String shopName, String tableName, String tableNote, String preNum, String codeUrl, LocalDateTime takeTime, String shopNote) {
|
||||||
String resp = buildCallTicketData(shopName, tableName, callNum, preNum, codeUrl, shopNote, takeTime);
|
String resp = buildCallTicketData(shopName, tableName, callNum, preNum, codeUrl, shopNote, takeTime);
|
||||||
sendPrintRequest(machine.getAddress(), resp, null, "1");
|
sendPrintRequest(machine.getAddress(), resp, null, "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 交班单打印
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
protected void handoverPrint(PrintMachine machine, HandoverRecordDTO record) {
|
protected void handoverPrint(PrintMachine machine, HandoverRecordDTO record) {
|
||||||
String string = buildHandoverData(record);
|
String string = buildHandoverData(record);
|
||||||
@@ -205,9 +214,9 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
* @param requestId 请求ID,自定义
|
* @param requestId 请求ID,自定义
|
||||||
* @return token信息
|
* @return token信息
|
||||||
*/
|
*/
|
||||||
private static String getToken(String timestamp, String requestId) {
|
private static Map<String, String> getToken(String timestamp, String requestId) {
|
||||||
StringBuilder token = new StringBuilder();
|
StringBuilder token = new StringBuilder();
|
||||||
// StringBuilder encode = new StringBuilder();
|
StringBuilder encode = new StringBuilder();
|
||||||
SortedMap<String, Object> map = new TreeMap<>();
|
SortedMap<String, Object> map = new TreeMap<>();
|
||||||
map.put("appId", APP_ID);
|
map.put("appId", APP_ID);
|
||||||
map.put("timestamp", timestamp);
|
map.put("timestamp", timestamp);
|
||||||
@@ -217,38 +226,13 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
|||||||
String key = next.getKey();
|
String key = next.getKey();
|
||||||
Object value = next.getValue();
|
Object value = next.getValue();
|
||||||
token.append(key).append(value);
|
token.append(key).append(value);
|
||||||
// encode.append(key).append("=").append(value).append("&");
|
encode.append(key).append("=").append(value).append("&");
|
||||||
}
|
}
|
||||||
// Map<String, String> finalMap = new HashMap<>();
|
Map<String, String> finalMap = new HashMap<>();
|
||||||
// finalMap.put("ENCODE", encode.toString());
|
finalMap.put("ENCODE", encode.toString());
|
||||||
// finalMap.put("TOKEN", SecureUtil.md5(token + APP_SECRET).toUpperCase());
|
finalMap.put("TOKEN", SecureUtil.md5(token + APP_SECRET).toUpperCase());
|
||||||
// return finalMap;
|
return finalMap;
|
||||||
return SecureUtil.md5(token + APP_SECRET).toUpperCase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查打印状态
|
|
||||||
*
|
|
||||||
* @param devName 设备名称,(唯一) 对应配置表中的address字段即(IP地址/打印机编号)
|
|
||||||
* @param taskId 打印任务id,用于复查打印状态,云想印=orderId
|
|
||||||
*/
|
|
||||||
public String checkPrintStatus(String devName, String taskId) {
|
|
||||||
String time = String.valueOf(System.currentTimeMillis());
|
|
||||||
String uuid = UUID.randomUUID().toString();
|
|
||||||
|
|
||||||
Map<String, Object> paramMap = new HashMap<>();
|
|
||||||
|
|
||||||
paramMap.put("devName", devName);
|
|
||||||
paramMap.put("orderId", taskId);
|
|
||||||
paramMap.put("token", getToken(time, uuid));
|
|
||||||
paramMap.put("appId", APP_ID);
|
|
||||||
paramMap.put("timestamp", time);
|
|
||||||
paramMap.put("requestId", uuid);
|
|
||||||
paramMap.put("userCode", USER_CODE);
|
|
||||||
String s = HttpUtil.get("https://ioe.car900.com/v1/openApi/dev/findOrder.json", paramMap, 1000 * 5);
|
|
||||||
log.info("云想印打印机:{},任务:{}状态:{}", devName, taskId, s);
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public class DistributionPayServiceImpl implements DistributionPayService {
|
|||||||
private final BigDecimal MONEY_RATE = new BigDecimal("100");
|
private final BigDecimal MONEY_RATE = new BigDecimal("100");
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private OrderPaymentService paymentService;
|
private OrderPaymentService orderPaymentService;
|
||||||
@Resource
|
@Resource
|
||||||
private MkDistributionConfigService configService;
|
private MkDistributionConfigService configService;
|
||||||
@Resource
|
@Resource
|
||||||
@@ -74,7 +74,7 @@ public class DistributionPayServiceImpl implements DistributionPayService {
|
|||||||
.setPayType(PayTypeConstants.PayType.PAY)
|
.setPayType(PayTypeConstants.PayType.PAY)
|
||||||
.setOrderNo(payParam.getPlatformType() + IdUtil.getSnowflakeNextId())
|
.setOrderNo(payParam.getPlatformType() + IdUtil.getSnowflakeNextId())
|
||||||
.setAmount(isRecharge ? payParam.getAmount() : detail.getPayAmount());
|
.setAmount(isRecharge ? payParam.getAmount() : detail.getPayAmount());
|
||||||
paymentService.save(orderPayment);
|
orderPaymentService.save(orderPayment);
|
||||||
|
|
||||||
InitInfo initInfo = new InitInfo().setConfig(detail);
|
InitInfo initInfo = new InitInfo().setConfig(detail);
|
||||||
if (isRecharge) {
|
if (isRecharge) {
|
||||||
|
|||||||
@@ -1516,9 +1516,6 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean printOrder(Long shopId, OrderInfoPrintDTO orderInfoPrintDTO) {
|
public Boolean printOrder(Long shopId, OrderInfoPrintDTO orderInfoPrintDTO) {
|
||||||
if (redisService.hasKey("order:print:" + orderInfoPrintDTO.getId())) {
|
|
||||||
throw new CzgException("网络打印机正在尝试打印中。如需重打,请稍后再试!");
|
|
||||||
}
|
|
||||||
OrderInfo orderInfo = orderInfoService.getOne(new QueryWrapper().eq(OrderInfo::getShopId, shopId).eq(OrderInfo::getId, orderInfoPrintDTO.getId()));
|
OrderInfo orderInfo = orderInfoService.getOne(new QueryWrapper().eq(OrderInfo::getShopId, shopId).eq(OrderInfo::getId, orderInfoPrintDTO.getId()));
|
||||||
if (orderInfo == null) {
|
if (orderInfo == null) {
|
||||||
throw new CzgException("订单信息不存在");
|
throw new CzgException("订单信息不存在");
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ public class OrderPaymentServiceImpl extends ServiceImpl<OrderPaymentMapper, Ord
|
|||||||
@Override
|
@Override
|
||||||
public BigDecimal countMemberInAmount(Long shopId, Long shopUserId) {
|
public BigDecimal countMemberInAmount(Long shopId, Long shopUserId) {
|
||||||
return getOneAs(QueryWrapper.create().select("IFNULL(sum(amount), 0) as total_amount")
|
return getOneAs(QueryWrapper.create().select("IFNULL(sum(amount), 0) as total_amount")
|
||||||
.eq(OrderPayment::getShopId, shopId)
|
.eq(OrderPayment::getShopId, 143)
|
||||||
.eq(OrderPayment::getSourceType, PayTypeConstants.SourceType.MEMBER_IN)
|
.eq(OrderPayment::getSourceType, PayTypeConstants.SourceType.MEMBER_IN)
|
||||||
.eq(OrderPayment::getPayType, PayTypeConstants.PayType.PAY)
|
.eq(OrderPayment::getPayType, PayTypeConstants.PayType.PAY)
|
||||||
.eq(OrderPayment::getSourceId, shopUserId)
|
.eq(OrderPayment::getSourceId, 127452)
|
||||||
.eq(OrderPayment::getPayStatus, PayTypeConstants.PayStatus.SUCCESS), BigDecimal.class);
|
.eq(OrderPayment::getPayStatus, PayTypeConstants.PayStatus.SUCCESS), BigDecimal.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,36 +2,32 @@ package com.czg.service.order.service.impl;
|
|||||||
|
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.core.date.DateUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
import cn.hutool.core.map.MapProxy;
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import cn.hutool.core.text.UnicodeUtil;
|
||||||
import cn.hutool.core.thread.ThreadUtil;
|
import cn.hutool.core.thread.ThreadUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import cn.hutool.crypto.SecureUtil;
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.czg.account.entity.PrintMachine;
|
import com.czg.account.entity.PrintMachine;
|
||||||
import com.czg.config.RedisCst;
|
import com.github.pagehelper.PageHelper;
|
||||||
import com.czg.market.service.OrderInfoService;
|
import com.github.pagehelper.PageInfo;
|
||||||
import com.czg.order.entity.OrderInfo;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||||
import com.czg.order.entity.PrintMachineLog;
|
import com.czg.order.entity.PrintMachineLog;
|
||||||
import com.czg.order.service.PrintMachineLogService;
|
import com.czg.order.service.PrintMachineLogService;
|
||||||
import com.czg.service.RedisService;
|
|
||||||
import com.czg.service.order.mapper.PrintMachineLogMapper;
|
import com.czg.service.order.mapper.PrintMachineLogMapper;
|
||||||
import com.czg.service.order.print.FeiPrinter;
|
|
||||||
import com.czg.service.order.print.YxyPrinter;
|
|
||||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 店铺小票打印记录ServiceImpl
|
* 店铺小票打印记录ServiceImpl
|
||||||
@@ -42,47 +38,139 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMapper, PrintMachineLog> implements PrintMachineLogService {
|
public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMapper, PrintMachineLog> implements PrintMachineLogService{
|
||||||
|
//请求地址
|
||||||
|
private static final String URL_STR = "https://ioe.car900.com/v1/openApi/dev/customPrint.json";
|
||||||
|
//APPID
|
||||||
|
private static final String APP_ID = "ZF544";
|
||||||
|
//USERCODE
|
||||||
|
private static final String USER_CODE = "ZF544";
|
||||||
|
//APPSECRET
|
||||||
|
private static final String APP_SECRET = "2022bsjZF544GAH";
|
||||||
|
|
||||||
@Resource
|
public static final String URL = "http://api.feieyun.cn/Api/Open/";//不需要修改
|
||||||
private OrderInfoService orderInfoService;
|
|
||||||
@Lazy
|
|
||||||
@Resource
|
|
||||||
private YxyPrinter yxyPrinter;
|
|
||||||
@Lazy
|
|
||||||
@Resource
|
|
||||||
private FeiPrinter feiPrinter;
|
|
||||||
@Resource
|
|
||||||
private RedisService redisService;
|
|
||||||
|
|
||||||
Map<Integer, String> yxxStatusMap = Map.of(
|
public static final String USER = "chaozhanggui2022@163.com";//*必填*:账号名
|
||||||
0, "离线(设备上线后自动补打)",
|
public static final String UKEY = "UfWkhXxSkeSSscsU";//*必填*: 飞鹅云后台注册账号后生成的UKEY 【备注:这不是填打印机的KEY】
|
||||||
1, "在线",
|
public static final String SN = "960238952";//*必填*:打印机编号,必须要在管理后台里添加打印机或调用API接口添加之后,才能调用API
|
||||||
2, "获取失败",
|
/**
|
||||||
3, "未激活",
|
* 获取TOKEN值
|
||||||
4, "设备已禁用");
|
*
|
||||||
|
* @param timestamp 时间戳,13位
|
||||||
@Async
|
* @param requestId 请求ID,自定义
|
||||||
@Override
|
* @return
|
||||||
public void save(PrintMachine config, String bizType, String printContent, String respJson) {
|
*/
|
||||||
if (config == null) {
|
private static Map<String, String> getToken(String timestamp, String requestId) {
|
||||||
return;
|
StringBuilder token = new StringBuilder();
|
||||||
|
StringBuilder encode = new StringBuilder();
|
||||||
|
SortedMap<String, Object> map = new TreeMap<>();
|
||||||
|
map.put("appId", APP_ID);
|
||||||
|
map.put("timestamp", timestamp);
|
||||||
|
map.put("requestId", requestId);
|
||||||
|
map.put("userCode", USER_CODE);
|
||||||
|
for (Map.Entry<String, Object> next : map.entrySet()) {
|
||||||
|
String key = next.getKey();
|
||||||
|
Object value = next.getValue();
|
||||||
|
token.append(key).append(value);
|
||||||
|
encode.append(key).append("=").append(value).append("&");
|
||||||
}
|
}
|
||||||
save(null, config, bizType, printContent, respJson);
|
System.out.println("token" + token);
|
||||||
|
Map<String, String> finalMap = new HashMap<>();
|
||||||
|
finalMap.put("ENCODE", encode.toString());
|
||||||
|
System.out.println("+++++++++++++++" + token + APP_SECRET);
|
||||||
|
finalMap.put("TOKEN", SecureUtil.md5(token + APP_SECRET).toUpperCase());
|
||||||
|
return finalMap;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 检查打印状态
|
||||||
|
*
|
||||||
|
* @param devName 设备名称,(唯一) 对应配置表中的address字段即(IP地址/打印机编号)
|
||||||
|
* @param taskId 打印任务id,用于复查打印状态,云想印=orderId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String checkPrintStatus(String devName, String taskId) {
|
||||||
|
String time = String.valueOf(System.currentTimeMillis());
|
||||||
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
Map<String, String> param = getToken(time, uuid);
|
||||||
|
String token = param.get("TOKEN");
|
||||||
|
Map<String, Object> paramMap = new HashMap<>();
|
||||||
|
|
||||||
|
paramMap.put("devName", devName);
|
||||||
|
paramMap.put("orderId", taskId);
|
||||||
|
paramMap.put("token", token);
|
||||||
|
paramMap.put("appId", APP_ID);
|
||||||
|
paramMap.put("timestamp", time);
|
||||||
|
paramMap.put("requestId", uuid);
|
||||||
|
paramMap.put("userCode", USER_CODE);
|
||||||
|
|
||||||
|
return HttpUtil.get("https://ioe.car900.com/v1/openApi/dev/findOrder.json", paramMap, 1000 * 5);
|
||||||
|
}
|
||||||
|
private static String signature(String USER, String UKEY, String STIME) {
|
||||||
|
return DigestUtils.sha1Hex(USER + UKEY + STIME);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 检查飞鹅打印机打印任务是否已打印
|
||||||
|
*
|
||||||
|
* @param printOrderId 打印订单编号
|
||||||
|
* @return null-未知错误,true-已打印,false-未打印
|
||||||
|
*/
|
||||||
|
public static Boolean checkFPrintStatus(String printOrderId) {
|
||||||
|
String STIME = String.valueOf(System.currentTimeMillis() / 1000);
|
||||||
|
Map<String, Object> paramMap = new HashMap<>();
|
||||||
|
paramMap.put("user", USER);
|
||||||
|
paramMap.put("stime", STIME);
|
||||||
|
paramMap.put("sig", signature(USER, UKEY, STIME));
|
||||||
|
paramMap.put("apiname", "Open_queryOrderState");
|
||||||
|
paramMap.put("orderid", printOrderId);
|
||||||
|
Boolean ret;
|
||||||
|
try {
|
||||||
|
String resp = HttpUtil.post(URL, paramMap, 1000 * 5);
|
||||||
|
//成功 {"msg":"ok","ret":0,"data":true,"serverExecutedTime":4}
|
||||||
|
//失败 {"msg":"ok","ret":0,"data":false,"serverExecutedTime":4}
|
||||||
|
JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp));
|
||||||
|
ret = json.getBool("data");
|
||||||
|
} catch (Exception e) {
|
||||||
|
ret = null;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 检查飞鹅打印机是否在线
|
||||||
|
*
|
||||||
|
* @param sn 设备编号
|
||||||
|
* @return 在线,工作状态正常。/离线。/未知错误
|
||||||
|
*/
|
||||||
|
public static String checkOnline(String sn) {
|
||||||
|
String STIME = String.valueOf(System.currentTimeMillis() / 1000);
|
||||||
|
Map<String, Object> paramMap = new HashMap<>();
|
||||||
|
paramMap.put("user", USER);
|
||||||
|
paramMap.put("stime", STIME);
|
||||||
|
paramMap.put("sig", signature(USER, UKEY, STIME));
|
||||||
|
paramMap.put("apiname", "Open_queryPrinterStatus");
|
||||||
|
paramMap.put("sn", sn);
|
||||||
|
String msg;
|
||||||
|
try {
|
||||||
|
String resp = HttpUtil.post(URL, paramMap, 1000 * 5);
|
||||||
|
//成功 开机 {"msg":"ok","ret":0,"data":"在线,工作状态正常。","serverExecutedTime":4}
|
||||||
|
//成功 离线 {"msg":"ok","ret":0,"data":"离线。","serverExecutedTime":7}
|
||||||
|
JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp));
|
||||||
|
msg = json.getStr("data");
|
||||||
|
} catch (Exception e) {
|
||||||
|
msg = "未知错误";
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 保存打印记录
|
* 保存打印记录
|
||||||
*
|
*
|
||||||
* @param orderId 订单Id
|
|
||||||
* @param config 打印机配置
|
* @param config 打印机配置
|
||||||
* @param bizType 业务类型
|
* @param bizType 业务类型
|
||||||
* @param printContent 打印内容
|
* @param printContent 打印内容
|
||||||
* @param respJson 打印机响应结果
|
* @param respJson 打印机响应结果
|
||||||
*/
|
*/
|
||||||
@Async
|
@Async
|
||||||
@Override
|
public void save(PrintMachine config, String bizType, String printContent, Object respJson) {
|
||||||
public void save(Long orderId, PrintMachine config, String bizType, String printContent, String respJson) {
|
|
||||||
if (config == null) {
|
if (config == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -91,41 +179,51 @@ public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMappe
|
|||||||
int failFlag = 0;
|
int failFlag = 0;
|
||||||
String respCode = "0";
|
String respCode = "0";
|
||||||
String respMsg = "打印中";
|
String respMsg = "打印中";
|
||||||
JSONObject resp = JSONObject.parseObject(respJson);
|
|
||||||
|
Map<Integer, String> yxxStatusMap = MapUtil.builder(0, "离线(设备上线后自动补打)").put(1, "在线").put(2, "获取失败").put(3, "未激活").put(4, "设备已禁用").build();
|
||||||
// 云想印
|
// 云想印
|
||||||
if ("云想印".equals(config.getContentType())) {
|
if ("云享印".equals(config.getContentType())) {
|
||||||
int code = resp.getIntValue("code");
|
cn.hutool.json.JSONObject resp = JSONUtil.parseObj(respJson);
|
||||||
JSONObject respData = resp.getJSONObject("data");
|
int code = resp.getInt("code");
|
||||||
JSONObject data = respData.getJSONObject("data");
|
cn.hutool.json.JSONObject data = resp.getJSONObject("data").getJSONObject("data");
|
||||||
//设备状态,0: 离线, 1: 在线, 2: 获取失败, 3:未激活, 4:设备已禁用
|
//设备状态,0: 离线, 1: 在线, 2: 获取失败, 3:未激活, 4:设备已禁用
|
||||||
int status = data.getIntValue("status");
|
int status = data.getInt("status");
|
||||||
if (code != 0) {
|
if (code != 0) {
|
||||||
failFlag = 1;
|
failFlag = 1;
|
||||||
respCode = code + "";
|
respCode = code + "";
|
||||||
respMsg = resp.getString("msg");
|
respMsg = resp.getStr("msg");
|
||||||
} else if (status != 1) {
|
} else if (status != 1) {
|
||||||
failFlag = 1;
|
failFlag = 1;
|
||||||
respCode = code + "";
|
respCode = code + "";
|
||||||
|
|
||||||
respMsg = status + "_" + yxxStatusMap.get(status);
|
respMsg = status + "_" + yxxStatusMap.get(status);
|
||||||
}
|
}
|
||||||
if (code == 0) {
|
if (code == 0) {
|
||||||
entity.setTaskId(respData.getString("orderId"));
|
String taskId = resp.getJSONObject("data").getStr("orderId");
|
||||||
|
entity.setTaskId(taskId);
|
||||||
}
|
}
|
||||||
// 飞鹅云打印机暂时没有适配,先return不做打印记录
|
// 飞鹅云打印机暂时没有适配,先return不做打印记录
|
||||||
} else if ("飞鹅".equals(config.getContentType())) {
|
} else if ("飞鹅".equals(config.getContentType())) {
|
||||||
int ret = resp.getIntValue("ret");
|
cn.hutool.json.JSONObject resp = JSONUtil.parseObj(respJson);
|
||||||
|
int ret = resp.getInt("ret");
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
failFlag = 1;
|
failFlag = 1;
|
||||||
respCode = ret + "";
|
respCode = ret + "";
|
||||||
respMsg = resp.getString("msg");
|
respMsg = resp.getStr("msg");
|
||||||
} else {
|
} else {
|
||||||
entity.setTaskId(resp.getString("data"));
|
String printOrderId = resp.getStr("data");
|
||||||
|
entity.setTaskId(printOrderId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 其他打印机暂时没有适配,先return不做打印记录
|
// 其他打印机暂时没有适配,先return不做打印记录
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
entity.setBizType(bizType);
|
entity.setBizType(bizType);
|
||||||
|
// entity.setCreateUserId(config.getCurrentUserId());
|
||||||
|
// entity.setCreateUserName(config.getCurrentUserName());
|
||||||
|
// if (StrUtil.isNotBlank(config.getCurrentUserNickName())) {
|
||||||
|
// entity.setCreateUserName(StrUtil.concat(true, config.getCurrentUserNickName(), " | ", config.getCurrentUserName()));
|
||||||
|
// }
|
||||||
entity.setPrintContent(printContent);
|
entity.setPrintContent(printContent);
|
||||||
entity.setCreateTime(DateUtil.date().toLocalDateTime());
|
entity.setCreateTime(DateUtil.date().toLocalDateTime());
|
||||||
if (failFlag == 0) {
|
if (failFlag == 0) {
|
||||||
@@ -135,213 +233,66 @@ public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMappe
|
|||||||
entity.setRespCode(respCode);
|
entity.setRespCode(respCode);
|
||||||
entity.setRespMsg(respMsg);
|
entity.setRespMsg(respMsg);
|
||||||
super.save(entity);
|
super.save(entity);
|
||||||
ThreadUtil.execAsync(() -> checkPrintStatus(orderId, config, entity));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// 云想印
|
||||||
* 类级别成员变量:基于虚拟线程的固定大小(5)定时线程池
|
if ("云享印".equals(config.getContentType())) {
|
||||||
* // Java 21+ 虚拟线程工厂,支持命名
|
// 延迟3ms,复查打印状态 (用户可以根据设备信息查询到当前设备的在线情况(注:该接口只能提供参考,设备的离线状态是在设备离线3分钟后才会生效))
|
||||||
*/
|
ThreadUtil.safeSleep(1000 * 5);
|
||||||
private final ScheduledExecutorService virtualThreadScheduler = Executors.newScheduledThreadPool(
|
String jsonStr = checkPrintStatus(config.getAddress(), entity.getTaskId());
|
||||||
5,
|
cn.hutool.json.JSONObject resp = JSONUtil.parseObj(jsonStr);
|
||||||
Thread.ofVirtual().name("print-query-vt-", 0).factory()
|
int code = resp.getInt("code");
|
||||||
);
|
if (code == 0) {
|
||||||
|
cn.hutool.json.JSONObject data = resp.getJSONObject("data");
|
||||||
/**
|
boolean status = data.containsKey("status");
|
||||||
* 打印机状态查询(解决 retryFuture 爆红问题 + 虚拟线程 + 轮询重试)
|
if (!status) {
|
||||||
*
|
|
||||||
* @param orderId 订单Id
|
|
||||||
* @param config 打印机配置
|
|
||||||
* @param entity 打印日志实体
|
|
||||||
*/
|
|
||||||
public void checkPrintStatus(Long orderId, PrintMachine config, PrintMachineLog entity) {
|
|
||||||
// 最大重试次数
|
|
||||||
int maxRetryTimes = 5;
|
|
||||||
AtomicInteger executedTimes = new AtomicInteger(0);
|
|
||||||
|
|
||||||
// 原子引用包装ScheduledFuture,用于后续取消轮询
|
|
||||||
AtomicReference<ScheduledFuture<?>> retryFutureRef = new AtomicReference<>();
|
|
||||||
|
|
||||||
// 核心查询任务(修正后,逻辑内聚)
|
|
||||||
Runnable printQueryTask = () -> {
|
|
||||||
int currentTimes = executedTimes.incrementAndGet();
|
|
||||||
boolean isPrintSuccess = false;
|
|
||||||
boolean isLastTask = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. 云想印打印机状态查询
|
|
||||||
if ("云想印".equals(config.getContentType())) {
|
|
||||||
String jsonStr = yxyPrinter.checkPrintStatus(config.getAddress(), entity.getTaskId());
|
|
||||||
log.info("云想印打印状态查询结果(第{}次,虚拟线程:{}):{}",
|
|
||||||
currentTimes, Thread.currentThread().getName(), jsonStr);
|
|
||||||
JSONObject resp = JSONObject.parseObject(jsonStr);
|
|
||||||
int code = resp.getIntValue("code");
|
|
||||||
if (code == 0) {
|
|
||||||
JSONObject data = resp.getJSONObject("data");
|
|
||||||
if (data.containsKey("status")) {
|
|
||||||
isPrintSuccess = data.getBooleanValue("status", false);
|
|
||||||
updatePrintLogEntity(entity, isPrintSuccess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 2. 飞鹅云打印机状态查询
|
|
||||||
else if ("飞鹅".equals(config.getContentType())) {
|
|
||||||
Boolean success = feiPrinter.checkFPrintStatus(entity.getTaskId());
|
|
||||||
if (success == null) {
|
|
||||||
entity.setFailFlag(1);
|
|
||||||
entity.setRespMsg("打印失败,未知错误");
|
|
||||||
entity.setPrintTime(null);
|
|
||||||
} else if (success) {
|
|
||||||
isPrintSuccess = true;
|
|
||||||
updatePrintLogEntity(entity, true);
|
|
||||||
} else {
|
|
||||||
String msg = feiPrinter.checkOnline(entity.getAddress());
|
|
||||||
if (msg.indexOf("在线,工作状态正常") > 0) {
|
|
||||||
isPrintSuccess = true;
|
|
||||||
updatePrintLogEntity(entity, true);
|
|
||||||
} else {
|
|
||||||
isPrintSuccess = false;
|
|
||||||
entity.setFailFlag(1);
|
|
||||||
entity.setPrintTime(null);
|
|
||||||
entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", msg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.info("打印类型为其他类型,终止打印状态查询轮询任务");
|
|
||||||
ScheduledFuture<?> future = retryFutureRef.get();
|
|
||||||
if (future != null && !future.isCancelled()) {
|
|
||||||
boolean cancelSuccess = future.cancel(false); // 取消后续轮询(不中断当前任务)
|
|
||||||
log.info("其他打印类型,取消轮询任务:{}", cancelSuccess ? "成功" : "失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 打印成功:取消后续轮询任务
|
|
||||||
if (isPrintSuccess) {
|
|
||||||
isLastTask = true;
|
|
||||||
ScheduledFuture<?> future = retryFutureRef.get();
|
|
||||||
if (future != null && !future.isCancelled()) {
|
|
||||||
future.cancel(false); // 不中断当前任务,仅取消后续任务
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
boolean success = data.getBool("status", false);
|
||||||
// 4. 达到最大重试次数:取消后续轮询任务
|
if (entity.getFailFlag() == 0 && success) {
|
||||||
if (currentTimes >= maxRetryTimes) {
|
entity.setFailFlag(0);
|
||||||
isLastTask = true;
|
entity.setRespMsg("打印成功");
|
||||||
ScheduledFuture<?> future = retryFutureRef.get();
|
entity.setPrintTime(entity.getCreateTime());
|
||||||
if (future != null && !future.isCancelled()) {
|
} else if (entity.getFailFlag() == 1 && success) {
|
||||||
future.cancel(false);
|
entity.setFailFlag(0);
|
||||||
}
|
entity.setPrintTime(DateUtil.date().toLocalDateTime());
|
||||||
}
|
entity.setRespMsg("打印成功");
|
||||||
|
// 如果设备在线 and 休眠5秒后查询结果是未打印,即视为设备已离线,云端3分钟后才会同步到离线信息
|
||||||
} catch (Exception e) {
|
} else if (entity.getFailFlag() == 0 && !success) {
|
||||||
log.error("第{}次打印机状态查询异常(虚拟线程:{})",
|
entity.setFailFlag(1);
|
||||||
currentTimes, Thread.currentThread().getName(), e);
|
entity.setPrintTime(null);
|
||||||
// 异常时达到最大重试次数,同样取消任务
|
entity.setRespMsg("0_离线(设备上线后自动补打)");
|
||||||
if (currentTimes >= maxRetryTimes) {
|
} else {
|
||||||
ScheduledFuture<?> future = retryFutureRef.get();
|
entity.setFailFlag(1);
|
||||||
if (future != null && !future.isCancelled()) {
|
entity.setPrintTime(null);
|
||||||
boolean cancelSuccess = future.cancel(false);
|
entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", entity.getRespMsg()));
|
||||||
log.info("查询异常且达到最大重试次数,取消轮询任务:{}", cancelSuccess ? "成功" : "失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
//仅当是最后一次任务时,才执行更新操作
|
|
||||||
if (isLastTask) {
|
|
||||||
super.updateById(entity);
|
|
||||||
updateOrderEntity(orderId, config, isPrintSuccess);
|
|
||||||
}
|
}
|
||||||
|
super.updateById(entity);
|
||||||
}
|
}
|
||||||
};
|
// 飞鹅云打印机
|
||||||
|
} else if ("飞鹅".equals(config.getContentType())) {
|
||||||
// 修正:统一使用scheduleAtFixedRate,避免任务重复执行
|
ThreadUtil.safeSleep(1000 * 5);
|
||||||
// 首次延迟10秒执行,后续每隔30秒执行一次(符合原逻辑的首次查询延迟,后续轮询间隔)
|
Boolean success = checkFPrintStatus(entity.getTaskId());
|
||||||
ScheduledFuture<?> retryFuture = virtualThreadScheduler.scheduleAtFixedRate(
|
if (success == null) {
|
||||||
printQueryTask,
|
entity.setFailFlag(1);
|
||||||
10,
|
entity.setRespMsg("打印失败,未知错误");
|
||||||
30,
|
} else if (success) {
|
||||||
TimeUnit.SECONDS
|
entity.setFailFlag(0);
|
||||||
);
|
entity.setPrintTime(DateUtil.date().toLocalDateTime());
|
||||||
|
entity.setRespMsg("打印成功");
|
||||||
// 修正:先赋值AtomicReference,再让任务可能执行,避免线程安全隐患
|
|
||||||
retryFutureRef.set(retryFuture);
|
|
||||||
// 修正:关闭钩子仅注册一次(通过静态代码块或类初始化时注册,避免重复注册)
|
|
||||||
registerShutdownHookOnce();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新状态
|
|
||||||
*/
|
|
||||||
private void updateOrderEntity(Long orderId, PrintMachine config, boolean isPrintSuccess) {
|
|
||||||
redisService.runFunAndCheckKey(() -> {
|
|
||||||
OrderInfo orderInfo = orderInfoService.getOne(query().select(OrderInfo::getPrintStatus).eq(OrderInfo::getId, orderId));
|
|
||||||
if (orderInfo == null) {
|
|
||||||
orderInfo = new OrderInfo();
|
|
||||||
}
|
|
||||||
JSONObject jsonObject = new JSONObject();
|
|
||||||
jsonObject.put("id", config.getId());
|
|
||||||
jsonObject.put("name", config.getName());
|
|
||||||
jsonObject.put("time", LocalDateTimeUtil.formatNormal(LocalDateTime.now()));
|
|
||||||
orderInfo.upPrintStatus(jsonObject, isPrintSuccess);
|
|
||||||
orderInfoService.update(orderInfo, query().eq(OrderInfo::getId, orderId));
|
|
||||||
return orderInfo;
|
|
||||||
}, RedisCst.getLockKey("UP_ORDER_PRINT", orderId));
|
|
||||||
redisService.del("order:print:" + orderId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 统一更新打印日志实体
|
|
||||||
*
|
|
||||||
* @param entity 打印日志实体
|
|
||||||
* @param isPrintSuccess 是否打印成功
|
|
||||||
*/
|
|
||||||
private void updatePrintLogEntity(PrintMachineLog entity, boolean isPrintSuccess) {
|
|
||||||
if (isPrintSuccess) {
|
|
||||||
entity.setFailFlag(0);
|
|
||||||
entity.setRespMsg("打印成功");
|
|
||||||
entity.setPrintTime(entity.getFailFlag() == 0 ? entity.getCreateTime() : DateUtil.date().toLocalDateTime());
|
|
||||||
} else {
|
|
||||||
entity.setFailFlag(1);
|
|
||||||
entity.setPrintTime(null);
|
|
||||||
if (entity.getFailFlag() == 0) {
|
|
||||||
entity.setRespMsg("0_离线(设备上线后自动补打)");
|
|
||||||
} else {
|
} else {
|
||||||
entity.setRespMsg(entity.getRespMsg());
|
String msg = checkOnline(entity.getAddress());
|
||||||
}
|
if (msg.indexOf("在线,工作状态正常") > 0) {
|
||||||
}
|
entity.setFailFlag(0);
|
||||||
}
|
entity.setPrintTime(DateUtil.date().toLocalDateTime());
|
||||||
|
entity.setRespMsg("打印成功");
|
||||||
// 静态标识,确保关闭钩子仅注册一次
|
} else {
|
||||||
private static volatile boolean shutdownHookRegistered = false;
|
entity.setFailFlag(1);
|
||||||
// 锁对象,保证线程安全
|
entity.setPrintTime(null);
|
||||||
private static final Object HOOK_LOCK = new Object();
|
entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", msg));
|
||||||
|
|
||||||
/**
|
|
||||||
* 统一注册JVM关闭钩子(仅执行一次)
|
|
||||||
*/
|
|
||||||
private void registerShutdownHookOnce() {
|
|
||||||
if (!shutdownHookRegistered) {
|
|
||||||
synchronized (HOOK_LOCK) {
|
|
||||||
// 双重校验锁,避免多线程下重复注册
|
|
||||||
if (!shutdownHookRegistered) {
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
|
||||||
if (virtualThreadScheduler != null && !virtualThreadScheduler.isShutdown()) {
|
|
||||||
virtualThreadScheduler.shutdown();
|
|
||||||
try {
|
|
||||||
if (!virtualThreadScheduler.awaitTermination(10, TimeUnit.SECONDS)) {
|
|
||||||
log.warn("虚拟线程调度器10秒内未关闭,强制关闭...");
|
|
||||||
virtualThreadScheduler.shutdownNow();
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
log.error("等待虚拟线程调度器终止时被中断,强制关闭", e);
|
|
||||||
virtualThreadScheduler.shutdownNow();
|
|
||||||
Thread.currentThread().interrupt(); // 保留中断状态
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, "PrinterScheduler-ShutdownHook"));
|
|
||||||
shutdownHookRegistered = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
super.updateById(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package com.czg.service.order.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.func.Func0;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Administrator
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class FunUtil {
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
public static int retryCount = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行任务并保证锁唯一
|
||||||
|
* @param supplier 业务逻辑
|
||||||
|
* @param lockKey Redis锁的Key
|
||||||
|
* @return 业务逻辑返回值
|
||||||
|
*/
|
||||||
|
public <T> T runFunAndCheckKey(Supplier<T> supplier, String lockKey) {
|
||||||
|
String lockValue = String.valueOf(System.nanoTime() + Thread.currentThread().threadId());
|
||||||
|
try {
|
||||||
|
// 尝试获取锁,超时时间 5 秒,防止死锁
|
||||||
|
boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
|
||||||
|
int count = 0;
|
||||||
|
// 初始等待 10ms
|
||||||
|
int retryDelay = 10;
|
||||||
|
|
||||||
|
while (!lock) {
|
||||||
|
// 最多重试 10 次,大约 10 秒
|
||||||
|
if (count++ > 50) {
|
||||||
|
throw new RuntimeException("系统繁忙, 稍后再试");
|
||||||
|
}
|
||||||
|
Thread.sleep(retryDelay);
|
||||||
|
// 指数退避,最大等待 200ms
|
||||||
|
retryDelay = Math.min(retryDelay * 2, 200);
|
||||||
|
lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行任务
|
||||||
|
return supplier.get();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new RuntimeException("线程被中断", e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("执行出错:{}", e.getMessage(), e);
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
// 释放锁(使用 Lua 脚本确保原子性)
|
||||||
|
unlock(lockKey, lockValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 Lua 脚本确保释放锁的原子性
|
||||||
|
* @param lockKey 锁的 Key
|
||||||
|
* @param lockValue 当前线程的锁值
|
||||||
|
*/
|
||||||
|
private void unlock(String lockKey, String lockValue) {
|
||||||
|
String luaScript =
|
||||||
|
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
|
||||||
|
"return redis.call('del', KEYS[1]) " +
|
||||||
|
"else return 0 end";
|
||||||
|
redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class),
|
||||||
|
Collections.singletonList(lockKey), lockValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 防抖函数:在指定秒数内相同 Key 的任务只会执行一次
|
||||||
|
* @param key 防抖使用的 Redis Key
|
||||||
|
* @param seconds 防抖时间(秒)
|
||||||
|
* @param task 要执行的业务逻辑
|
||||||
|
* @return true 执行了任务;false 在防抖期内被拦截
|
||||||
|
*/
|
||||||
|
public boolean debounce(String key, long seconds, Runnable task) {
|
||||||
|
try {
|
||||||
|
Boolean success = redisTemplate.opsForValue().setIfAbsent(
|
||||||
|
key, "1", seconds, TimeUnit.SECONDS
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Boolean.TRUE.equals(success)) {
|
||||||
|
task.run();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("防抖函数执行失败 key={} err={}", key, e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -153,10 +153,6 @@
|
|||||||
<foreach item="item" collection="idList" separator="," open="(" close=")">
|
<foreach item="item" collection="idList" separator="," open="(" close=")">
|
||||||
#{item}
|
#{item}
|
||||||
</foreach>
|
</foreach>
|
||||||
or t1.sync_id in
|
|
||||||
<foreach item="item" collection="idList" separator="," open="(" close=")">
|
|
||||||
#{item}
|
|
||||||
</foreach>
|
|
||||||
</if>
|
</if>
|
||||||
order by t1.sort desc,t1.id desc
|
order by t1.sort desc,t1.id desc
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
Reference in New Issue
Block a user