消息订阅
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package com.czg.controller.admin;
|
package com.czg.controller.admin;
|
||||||
|
|
||||||
|
import com.czg.account.dto.WxMsgSubDTO;
|
||||||
import com.czg.account.dto.msg.ShopMsgEditDTO;
|
import com.czg.account.dto.msg.ShopMsgEditDTO;
|
||||||
import com.czg.account.dto.msg.ShopPushOpenIdEditDTO;
|
import com.czg.account.dto.msg.ShopPushOpenIdEditDTO;
|
||||||
import com.czg.account.entity.ShopPushOpenId;
|
import com.czg.account.entity.ShopPushOpenId;
|
||||||
@@ -14,6 +15,7 @@ import com.czg.validator.group.DefaultGroup;
|
|||||||
import com.mybatisflex.core.paginate.Page;
|
import com.mybatisflex.core.paginate.Page;
|
||||||
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 org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@@ -22,6 +24,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
*
|
*
|
||||||
* @author Administrator
|
* @author Administrator
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/admin/shopMsgPush")
|
@RequestMapping("/admin/shopMsgPush")
|
||||||
public class ShopMsgPushController {
|
public class ShopMsgPushController {
|
||||||
@@ -30,6 +33,32 @@ public class ShopMsgPushController {
|
|||||||
@Resource
|
@Resource
|
||||||
private ShopPushOpenIdService shopPushOpenIdService;
|
private ShopPushOpenIdService shopPushOpenIdService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 店铺推送状态获取
|
||||||
|
*/
|
||||||
|
@PostMapping("/subscribe")
|
||||||
|
public CzgResult<Void> subscribe(@RequestBody WxMsgSubDTO wxMsgSubDTO) {
|
||||||
|
log.info("接收到订阅消息接口调用,携带数据: {}", wxMsgSubDTO);
|
||||||
|
if (wxMsgSubDTO.getOpenId() == null || wxMsgSubDTO.getShopId() == null) {
|
||||||
|
return CzgResult.failure("shopId或openId缺失");
|
||||||
|
}
|
||||||
|
ShopPushOpenId entity = shopPushOpenIdService.getOne(QueryWrapper.create().eq(ShopPushOpenId::getOpenId, wxMsgSubDTO.getOpenId()).eq(ShopPushOpenId::getShopId, wxMsgSubDTO.getShopId()));
|
||||||
|
if (entity == null) {
|
||||||
|
entity = new ShopPushOpenId();
|
||||||
|
}
|
||||||
|
entity.setShopId(wxMsgSubDTO.getShopId());
|
||||||
|
entity.setOpenId(wxMsgSubDTO.getOpenId());
|
||||||
|
entity.setNickname(wxMsgSubDTO.getNickname());
|
||||||
|
entity.setAvatar(wxMsgSubDTO.getAvatar());
|
||||||
|
if (entity.getId() == null) {
|
||||||
|
shopPushOpenIdService.save(entity);
|
||||||
|
} else {
|
||||||
|
shopPushOpenIdService.updateById(entity);
|
||||||
|
}
|
||||||
|
return CzgResult.success();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 店铺推送状态获取
|
* 店铺推送状态获取
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
package com.czg.account.dto;
|
package com.czg.account.dto;
|
||||||
|
|
||||||
import cn.hutool.core.convert.Convert;
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
|
||||||
import com.alibaba.fastjson2.annotation.JSONField;
|
import com.alibaba.fastjson2.annotation.JSONField;
|
||||||
import com.czg.account.vo.HandoverCategoryListVo;
|
import com.czg.account.vo.HandoverCategoryListVo;
|
||||||
import com.czg.account.vo.HandoverProductListVo;
|
import com.czg.account.vo.HandoverProductListVo;
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.czg.account.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信订阅消息
|
||||||
|
*
|
||||||
|
* @author Tankaikai tankaikai@aliyun.com
|
||||||
|
* @since 1.0 2025-02-27
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class WxMsgSubDTO implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private Long shopId;
|
||||||
|
private String openId;
|
||||||
|
private String nickname;
|
||||||
|
private String avatar;
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import com.mybatisflex.annotation.Table;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -29,7 +30,7 @@ public class ShopPushOpenId implements Serializable {
|
|||||||
@Id(keyType = KeyType.Auto)
|
@Id(keyType = KeyType.Auto)
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
private Integer shopId;
|
private Long shopId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 微信openid
|
* 微信openid
|
||||||
|
|||||||
@@ -70,4 +70,10 @@ public interface ConsStockFlowService extends IService<ConsStockFlow> {
|
|||||||
* @return 分页数据
|
* @return 分页数据
|
||||||
*/
|
*/
|
||||||
Page<ConsStockFlowDTO> findConsStockFlowPage(ConsStockFlowParam param);
|
Page<ConsStockFlowDTO> findConsStockFlowPage(ConsStockFlowParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存库存变动记录
|
||||||
|
* @param entity 库存变动记录实体
|
||||||
|
*/
|
||||||
|
void saveFlow(ConsStockFlow entity);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.czg.product.service;
|
||||||
|
|
||||||
|
import com.czg.product.entity.ProductStockFlow;
|
||||||
|
import com.mybatisflex.core.service.IService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* +
|
||||||
|
* 商品库存流水服务类
|
||||||
|
*
|
||||||
|
* @author tankaikai
|
||||||
|
* @since 2025-03-14 15:44
|
||||||
|
*/
|
||||||
|
public interface ProductStockFlowService extends IService<ProductStockFlow> {
|
||||||
|
|
||||||
|
void saveFlow(ProductStockFlow entity);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.czg.product.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 敏感操作Service
|
||||||
|
*
|
||||||
|
* @author Tankaikai tankaikai@aliyun.com
|
||||||
|
* @since 1.0 2025-02-16
|
||||||
|
*/
|
||||||
|
public interface SensitiveOperationService {
|
||||||
|
|
||||||
|
void send(String operationDesc);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import com.czg.service.account.util.AliOssUtil;
|
|||||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
||||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.core.io.ResourceLoader;
|
import org.springframework.core.io.ResourceLoader;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -50,6 +51,8 @@ public class ShopMsgStateServiceImpl extends ServiceImpl<ShopMsgStateMapper, Sho
|
|||||||
private AliOssUtil aliOssUtil;
|
private AliOssUtil aliOssUtil;
|
||||||
@Resource
|
@Resource
|
||||||
private ShopPushOpenIdMapper shopPushOpenIdMapper;
|
private ShopPushOpenIdMapper shopPushOpenIdMapper;
|
||||||
|
@Value("${spring.profiles.active}")
|
||||||
|
private String active;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -98,7 +101,7 @@ public class ShopMsgStateServiceImpl extends ServiceImpl<ShopMsgStateMapper, Sho
|
|||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
org.springframework.core.io.Resource resource = resourceLoader.getResource("classpath:/static/logo.jpg");
|
org.springframework.core.io.Resource resource = resourceLoader.getResource("classpath:/static/logo.jpg");
|
||||||
InputStream inputStream = resource.getInputStream();
|
InputStream inputStream = resource.getInputStream();
|
||||||
String url = StrUtil.format("https://invoice.sxczgkj.cn/index/wechat/weuserk?shopId={}", shopId);
|
String url = StrUtil.format("https://invoice.sxczgkj.cn/index/wechat/weuserk?source=ysk&active={}&shopId={}", active, shopId);
|
||||||
QrCodeUtil.generate(url, new QrConfig(500, 500).
|
QrCodeUtil.generate(url, new QrConfig(500, 500).
|
||||||
setImg(ImageIO.read(inputStream)).setErrorCorrection(ErrorCorrectionLevel.H).setRatio(4), "png", outputStream);
|
setImg(ImageIO.read(inputStream)).setErrorCorrection(ErrorCorrectionLevel.H).setRatio(4), "png", outputStream);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package com.czg.service.product.mapper;
|
|||||||
import com.czg.product.entity.ConsInfo;
|
import com.czg.product.entity.ConsInfo;
|
||||||
import com.mybatisflex.core.BaseMapper;
|
import com.mybatisflex.core.BaseMapper;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 耗材信息
|
* 耗材信息
|
||||||
@@ -12,5 +15,5 @@ import org.apache.ibatis.annotations.Mapper;
|
|||||||
*/
|
*/
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface ConsInfoMapper extends BaseMapper<ConsInfo> {
|
public interface ConsInfoMapper extends BaseMapper<ConsInfo> {
|
||||||
|
List<String> findOpenIdList(@Param("shopId") Long shopId, @Param("type") String type);
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,8 @@ package com.czg.service.product.service.impl;
|
|||||||
|
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.core.bean.copier.CopyOptions;
|
import cn.hutool.core.bean.copier.CopyOptions;
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.thread.ThreadUtil;
|
||||||
import cn.hutool.core.util.NumberUtil;
|
import cn.hutool.core.util.NumberUtil;
|
||||||
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
@@ -21,10 +23,12 @@ import com.czg.product.vo.ConsCheckStockRecordVo;
|
|||||||
import com.czg.sa.StpKit;
|
import com.czg.sa.StpKit;
|
||||||
import com.czg.service.product.mapper.ConsInfoMapper;
|
import com.czg.service.product.mapper.ConsInfoMapper;
|
||||||
import com.czg.service.product.mapper.ConsStockFlowMapper;
|
import com.czg.service.product.mapper.ConsStockFlowMapper;
|
||||||
|
import com.czg.service.product.util.WxAccountUtil;
|
||||||
import com.czg.utils.PageUtil;
|
import com.czg.utils.PageUtil;
|
||||||
import com.mybatisflex.core.paginate.Page;
|
import com.mybatisflex.core.paginate.Page;
|
||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -44,6 +48,8 @@ import java.util.List;
|
|||||||
public class ConsStockFlowServiceImpl extends ServiceImpl<ConsStockFlowMapper, ConsStockFlow> implements ConsStockFlowService {
|
public class ConsStockFlowServiceImpl extends ServiceImpl<ConsStockFlowMapper, ConsStockFlow> implements ConsStockFlowService {
|
||||||
|
|
||||||
private final ConsInfoMapper consInfoMapper;
|
private final ConsInfoMapper consInfoMapper;
|
||||||
|
@Resource
|
||||||
|
private WxAccountUtil wxAccountUtil;
|
||||||
|
|
||||||
private QueryWrapper buildQueryWrapper(ConsStockFlowDTO param) {
|
private QueryWrapper buildQueryWrapper(ConsStockFlowDTO param) {
|
||||||
QueryWrapper queryWrapper = PageUtil.buildSortQueryWrapper();
|
QueryWrapper queryWrapper = PageUtil.buildSortQueryWrapper();
|
||||||
@@ -155,7 +161,7 @@ public class ConsStockFlowServiceImpl extends ServiceImpl<ConsStockFlowMapper, C
|
|||||||
}
|
}
|
||||||
entity.setSubTotal(NumberUtil.mul(winLossNumber, param.getPrice()));
|
entity.setSubTotal(NumberUtil.mul(winLossNumber, param.getPrice()));
|
||||||
entity.setRemark(param.getRemark());
|
entity.setRemark(param.getRemark());
|
||||||
super.save(entity);
|
saveFlow(entity);
|
||||||
consInfo.setStockNumber(entity.getAfterNumber());
|
consInfo.setStockNumber(entity.getAfterNumber());
|
||||||
consInfoMapper.update(consInfo);
|
consInfoMapper.update(consInfo);
|
||||||
}
|
}
|
||||||
@@ -199,7 +205,7 @@ public class ConsStockFlowServiceImpl extends ServiceImpl<ConsStockFlowMapper, C
|
|||||||
entity.setInOutItem(InOutItemEnum.DAMAGE_OUT.value());
|
entity.setInOutItem(InOutItemEnum.DAMAGE_OUT.value());
|
||||||
entity.setSubTotal(NumberUtil.mul(param.getNumber(), consInfo.getPrice()));
|
entity.setSubTotal(NumberUtil.mul(param.getNumber(), consInfo.getPrice()));
|
||||||
entity.setImgUrls(JSON.toJSONString(param.getImgUrls()));
|
entity.setImgUrls(JSON.toJSONString(param.getImgUrls()));
|
||||||
super.save(entity);
|
saveFlow(entity);
|
||||||
consInfo.setStockNumber(entity.getAfterNumber());
|
consInfo.setStockNumber(entity.getAfterNumber());
|
||||||
consInfoMapper.update(consInfo);
|
consInfoMapper.update(consInfo);
|
||||||
}
|
}
|
||||||
@@ -222,4 +228,27 @@ public class ConsStockFlowServiceImpl extends ServiceImpl<ConsStockFlowMapper, C
|
|||||||
return pageAs(PageUtil.buildPage(), queryWrapper, ConsStockFlowDTO.class);
|
return pageAs(PageUtil.buildPage(), queryWrapper, ConsStockFlowDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveFlow(ConsStockFlow entity) {
|
||||||
|
super.save(entity);
|
||||||
|
Long shopId = entity.getShopId();
|
||||||
|
BigDecimal afterNumber = entity.getAfterNumber();
|
||||||
|
ConsInfo consInfo = consInfoMapper.selectOneById(entity.getConId());
|
||||||
|
String shopName = StpKit.USER.getShopName();
|
||||||
|
BigDecimal conWarning = consInfo.getConWarning();
|
||||||
|
// 库存小于警告值,发送消息提醒
|
||||||
|
if (NumberUtil.isLess(afterNumber, conWarning)) {
|
||||||
|
List<String> openIdList = consInfoMapper.findOpenIdList(shopId, "con");
|
||||||
|
if (CollUtil.isEmpty(openIdList)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String conName = StrUtil.format("{}数量<预警值{}", consInfo.getConName(), conWarning);
|
||||||
|
ThreadUtil.execAsync(() -> {
|
||||||
|
openIdList.parallelStream().forEach(openId -> {
|
||||||
|
wxAccountUtil.sendStockMsg(shopName, conName, afterNumber, openId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@ import com.czg.product.entity.ProdGroup;
|
|||||||
import com.czg.product.entity.ProdGroupRelation;
|
import com.czg.product.entity.ProdGroupRelation;
|
||||||
import com.czg.product.entity.Product;
|
import com.czg.product.entity.Product;
|
||||||
import com.czg.product.service.ProdGroupService;
|
import com.czg.product.service.ProdGroupService;
|
||||||
|
import com.czg.product.service.SensitiveOperationService;
|
||||||
import com.czg.sa.StpKit;
|
import com.czg.sa.StpKit;
|
||||||
import com.czg.service.product.mapper.ProdGroupMapper;
|
import com.czg.service.product.mapper.ProdGroupMapper;
|
||||||
import com.czg.service.product.mapper.ProdGroupRelationMapper;
|
import com.czg.service.product.mapper.ProdGroupRelationMapper;
|
||||||
@@ -41,8 +42,8 @@ import java.util.List;
|
|||||||
public class ProdGroupServiceImpl extends ServiceImpl<ProdGroupMapper, ProdGroup> implements ProdGroupService {
|
public class ProdGroupServiceImpl extends ServiceImpl<ProdGroupMapper, ProdGroup> implements ProdGroupService {
|
||||||
|
|
||||||
private final ProdGroupRelationMapper prodGroupRelationMapper;
|
private final ProdGroupRelationMapper prodGroupRelationMapper;
|
||||||
|
|
||||||
private final ProductMapper productMapper;
|
private final ProductMapper productMapper;
|
||||||
|
private final SensitiveOperationService sensitiveOperationService;
|
||||||
|
|
||||||
private QueryWrapper buildQueryWrapper(ProdGroupDTO param) {
|
private QueryWrapper buildQueryWrapper(ProdGroupDTO param) {
|
||||||
QueryWrapper queryWrapper = PageUtil.buildSortQueryWrapper();
|
QueryWrapper queryWrapper = PageUtil.buildSortQueryWrapper();
|
||||||
@@ -152,6 +153,8 @@ public class ProdGroupServiceImpl extends ServiceImpl<ProdGroupMapper, ProdGroup
|
|||||||
.eq(ProdGroup::getId, id)
|
.eq(ProdGroup::getId, id)
|
||||||
.eq(ProdGroup::getShopId, shopId)
|
.eq(ProdGroup::getShopId, shopId)
|
||||||
.update();
|
.update();
|
||||||
|
ProdGroup prodGroup = mapper.selectOneById(id);
|
||||||
|
sensitiveOperationService.send("关闭分组:" + prodGroup.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -162,6 +165,8 @@ public class ProdGroupServiceImpl extends ServiceImpl<ProdGroupMapper, ProdGroup
|
|||||||
.eq(ProdGroup::getId, id)
|
.eq(ProdGroup::getId, id)
|
||||||
.eq(ProdGroup::getShopId, shopId)
|
.eq(ProdGroup::getShopId, shopId)
|
||||||
.update();
|
.update();
|
||||||
|
ProdGroup prodGroup = mapper.selectOneById(id);
|
||||||
|
sensitiveOperationService.send("开启分组:" + prodGroup.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,10 @@ import com.czg.product.dto.ProductDTO;
|
|||||||
import com.czg.product.entity.*;
|
import com.czg.product.entity.*;
|
||||||
import com.czg.product.enums.*;
|
import com.czg.product.enums.*;
|
||||||
import com.czg.product.param.*;
|
import com.czg.product.param.*;
|
||||||
|
import com.czg.product.service.ConsStockFlowService;
|
||||||
import com.czg.product.service.ProductService;
|
import com.czg.product.service.ProductService;
|
||||||
|
import com.czg.product.service.ProductStockFlowService;
|
||||||
|
import com.czg.product.service.SensitiveOperationService;
|
||||||
import com.czg.product.vo.ProductStatisticsVo;
|
import com.czg.product.vo.ProductStatisticsVo;
|
||||||
import com.czg.sa.StpKit;
|
import com.czg.sa.StpKit;
|
||||||
import com.czg.service.product.mapper.*;
|
import com.czg.service.product.mapper.*;
|
||||||
@@ -59,6 +62,9 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
|
|||||||
private final ConsInfoMapper consInfoMapper;
|
private final ConsInfoMapper consInfoMapper;
|
||||||
private final ConsStockFlowMapper consStockFlowMapper;
|
private final ConsStockFlowMapper consStockFlowMapper;
|
||||||
private final ProductStockFlowMapper productStockFlowMapper;
|
private final ProductStockFlowMapper productStockFlowMapper;
|
||||||
|
private final ProductStockFlowService productStockFlowService;
|
||||||
|
private final ConsStockFlowService consStockFlowService;
|
||||||
|
private final SensitiveOperationService sensitiveOperationService;
|
||||||
|
|
||||||
private QueryWrapper buildQueryWrapper(ProductDTO param) {
|
private QueryWrapper buildQueryWrapper(ProductDTO param) {
|
||||||
QueryWrapper queryWrapper = PageUtil.buildSortQueryWrapper();
|
QueryWrapper queryWrapper = PageUtil.buildSortQueryWrapper();
|
||||||
@@ -222,7 +228,7 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
|
|||||||
flow.setAfterNumber(NumberUtil.toBigDecimal(entity.getStockNumber()));
|
flow.setAfterNumber(NumberUtil.toBigDecimal(entity.getStockNumber()));
|
||||||
flow.setInOutType(InOutTypeEnum.IN.value());
|
flow.setInOutType(InOutTypeEnum.IN.value());
|
||||||
flow.setInOutItem(InOutItemEnum.WIN_IN.value());
|
flow.setInOutItem(InOutItemEnum.WIN_IN.value());
|
||||||
productStockFlowMapper.insert(flow);
|
productStockFlowService.saveFlow(flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -310,7 +316,7 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
flow.setAfterNumber(NumberUtil.toBigDecimal(entity.getStockNumber()));
|
flow.setAfterNumber(NumberUtil.toBigDecimal(entity.getStockNumber()));
|
||||||
productStockFlowMapper.insert(flow);
|
productStockFlowService.saveFlow(flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -331,6 +337,12 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
|
|||||||
String type = param.getType();
|
String type = param.getType();
|
||||||
Long id = param.getId();
|
Long id = param.getId();
|
||||||
Integer isSale = param.getIsSale();
|
Integer isSale = param.getIsSale();
|
||||||
|
String sensitiveOperation = "";
|
||||||
|
if (isSale == 1) {
|
||||||
|
sensitiveOperation = "上架";
|
||||||
|
} else {
|
||||||
|
sensitiveOperation = "下架";
|
||||||
|
}
|
||||||
if (ProductIsSaleTypeEnum.SKU.value().equals(type)) {
|
if (ProductIsSaleTypeEnum.SKU.value().equals(type)) {
|
||||||
ProdSku prodSku = prodSkuMapper.selectOneById(id);
|
ProdSku prodSku = prodSkuMapper.selectOneById(id);
|
||||||
if (prodSku == null) {
|
if (prodSku == null) {
|
||||||
@@ -346,6 +358,9 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
|
|||||||
.eq(Product::getShopId, shopId)
|
.eq(Product::getShopId, shopId)
|
||||||
.update();
|
.update();
|
||||||
}
|
}
|
||||||
|
Long productId = prodSku.getProductId();
|
||||||
|
Product product = mapper.selectOneById(productId);
|
||||||
|
sensitiveOperation = sensitiveOperation + "商品:" + product.getName() + " 规格:" + prodSku.getSpecInfo();
|
||||||
} else if (ProductIsSaleTypeEnum.PRODUCT.value().equals(type)) {
|
} else if (ProductIsSaleTypeEnum.PRODUCT.value().equals(type)) {
|
||||||
UpdateChain.of(Product.class)
|
UpdateChain.of(Product.class)
|
||||||
.set(Product::getIsSale, isSale)
|
.set(Product::getIsSale, isSale)
|
||||||
@@ -357,10 +372,10 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
|
|||||||
.eq(ProdSku::getProductId, id)
|
.eq(ProdSku::getProductId, id)
|
||||||
.eq(ProdSku::getShopId, shopId)
|
.eq(ProdSku::getShopId, shopId)
|
||||||
.update();
|
.update();
|
||||||
|
Product product = mapper.selectOneById(id);
|
||||||
|
sensitiveOperation = sensitiveOperation + "商品:" + product.getName();
|
||||||
}
|
}
|
||||||
|
sensitiveOperationService.send(sensitiveOperation);
|
||||||
prodSkuMapper.selectOneById(id);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -438,7 +453,7 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
|
|||||||
flow.setInOutType(InOutTypeEnum.OUT.value());
|
flow.setInOutType(InOutTypeEnum.OUT.value());
|
||||||
flow.setInOutItem(InOutItemEnum.DAMAGE_OUT.value());
|
flow.setInOutItem(InOutItemEnum.DAMAGE_OUT.value());
|
||||||
flow.setImgUrls(JSON.toJSONString(param.getImgUrls()));
|
flow.setImgUrls(JSON.toJSONString(param.getImgUrls()));
|
||||||
productStockFlowMapper.insert(flow);
|
productStockFlowService.saveFlow(flow);
|
||||||
// 如果绑定了耗材,则同步更新耗材库存
|
// 如果绑定了耗材,则同步更新耗材库存
|
||||||
List<ProdConsRelationDTO> consList = prodConsRelationMapper.selectListByProdId(param.getProductId());
|
List<ProdConsRelationDTO> consList = prodConsRelationMapper.selectListByProdId(param.getProductId());
|
||||||
if (CollUtil.isEmpty(consList)) {
|
if (CollUtil.isEmpty(consList)) {
|
||||||
@@ -465,6 +480,7 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
|
|||||||
entity.setImgUrls(JSON.toJSONString(param.getImgUrls()));
|
entity.setImgUrls(JSON.toJSONString(param.getImgUrls()));
|
||||||
entity.setRemark("【商品报损,自动报损相关耗材】" + StrUtil.nullToDefault(param.getRemark(), ""));
|
entity.setRemark("【商品报损,自动报损相关耗材】" + StrUtil.nullToDefault(param.getRemark(), ""));
|
||||||
consStockFlowMapper.insert(entity);
|
consStockFlowMapper.insert(entity);
|
||||||
|
consStockFlowService.saveFlow(entity);
|
||||||
consInfo.setStockNumber(entity.getAfterNumber());
|
consInfo.setStockNumber(entity.getAfterNumber());
|
||||||
UpdateChain.of(ConsInfo.class)
|
UpdateChain.of(ConsInfo.class)
|
||||||
.set(ConsInfo::getStockNumber, consInfo.getStockNumber())
|
.set(ConsInfo::getStockNumber, consInfo.getStockNumber())
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.czg.service.product.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
|
import cn.hutool.core.thread.ThreadUtil;
|
||||||
|
import cn.hutool.core.util.NumberUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.czg.product.entity.Product;
|
||||||
|
import com.czg.product.entity.ProductStockFlow;
|
||||||
|
import com.czg.product.service.ProductStockFlowService;
|
||||||
|
import com.czg.sa.StpKit;
|
||||||
|
import com.czg.service.product.mapper.ConsInfoMapper;
|
||||||
|
import com.czg.service.product.mapper.ProductMapper;
|
||||||
|
import com.czg.service.product.mapper.ProductStockFlowMapper;
|
||||||
|
import com.czg.service.product.util.WxAccountUtil;
|
||||||
|
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品库存流水服务实现类
|
||||||
|
*
|
||||||
|
* @author tankaikai
|
||||||
|
* @since 2025-03-14 15:45
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ProductStockFlowServiceImpl extends ServiceImpl<ProductStockFlowMapper, ProductStockFlow> implements ProductStockFlowService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ProductMapper productMapper;
|
||||||
|
@Resource
|
||||||
|
private ConsInfoMapper consInfoMapper;
|
||||||
|
@Resource
|
||||||
|
private WxAccountUtil wxAccountUtil;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveFlow(ProductStockFlow entity) {
|
||||||
|
mapper.insert(entity);
|
||||||
|
Long shopId = entity.getShopId();
|
||||||
|
BigDecimal afterNumber = entity.getAfterNumber();
|
||||||
|
Product product = productMapper.selectOneById(entity.getProductId());
|
||||||
|
String shopName = StpKit.USER.getShopName();
|
||||||
|
BigDecimal warnLine = Convert.toBigDecimal(product.getWarnLine());
|
||||||
|
// 库存小于警告值,发送消息提醒
|
||||||
|
if (NumberUtil.isLess(afterNumber, warnLine)) {
|
||||||
|
List<String> openIdList = consInfoMapper.findOpenIdList(shopId, "pro");
|
||||||
|
if (CollUtil.isEmpty(openIdList)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String conName = StrUtil.format("{}数量<预警值{}", product.getName(), warnLine);
|
||||||
|
ThreadUtil.execAsync(() -> {
|
||||||
|
openIdList.parallelStream().forEach(openId -> {
|
||||||
|
wxAccountUtil.sendStockMsg(shopName, conName, afterNumber, openId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.czg.service.product.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.thread.ThreadUtil;
|
||||||
|
import com.czg.product.service.SensitiveOperationService;
|
||||||
|
import com.czg.sa.StpKit;
|
||||||
|
import com.czg.service.product.mapper.ConsInfoMapper;
|
||||||
|
import com.czg.service.product.util.WxAccountUtil;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 敏感操作ServiceImpl
|
||||||
|
*
|
||||||
|
* @author tankaikai
|
||||||
|
* @since 2025-03-17 11:47
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class SensitiveOperationServiceImpl implements SensitiveOperationService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ConsInfoMapper consInfoMapper;
|
||||||
|
@Resource
|
||||||
|
private WxAccountUtil wxAccountUtil;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(String operationDesc) {
|
||||||
|
Long shopId = StpKit.USER.getShopId(0L);
|
||||||
|
String userName = StpKit.USER.getAccount();
|
||||||
|
List<String> openIdList = consInfoMapper.findOpenIdList(shopId, "ope");
|
||||||
|
if (CollUtil.isEmpty(openIdList)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ThreadUtil.execAsync(() -> {
|
||||||
|
wxAccountUtil.sendOperationMsg(openIdList, userName, operationDesc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package com.czg.service.product.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.http.HttpRequest;
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.czg.service.RedisService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信公众号消息推送
|
||||||
|
*
|
||||||
|
* @author tankaikai
|
||||||
|
* @since 2025-03-14 14:56
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class WxAccountUtil {
|
||||||
|
|
||||||
|
@Value("${wx.ysk.appId}")
|
||||||
|
private String appId = "wx212769170d2c6b2a";
|
||||||
|
@Value("${wx.ysk.secrete}")
|
||||||
|
private String secrete = "8492a7e8d55bbb1b57f5c8276ea1add0";
|
||||||
|
@Value("${wx.ysk.operationMsgTmpId}")
|
||||||
|
private String operationMsgTmpId;
|
||||||
|
@Value("${wx.ysk.warnMsgTmpId}")
|
||||||
|
private String warnMsgTmpId;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Lazy
|
||||||
|
private RedisService redisService;
|
||||||
|
|
||||||
|
static LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
linkedHashMap.put("40001", "获取 access_token 时 AppSecret 错误,或者 access_token 无效。请开发者认真比对 AppSecret 的正确性,或查看是否正在为恰当的公众号调用接口");
|
||||||
|
linkedHashMap.put("40003", "不合法的 OpenID ,请开发者确认 OpenID (该用户)是否已关注公众号,或是否是其他公众号的 OpenID");
|
||||||
|
linkedHashMap.put("40014", "不合法的 access_token ,请开发者认真比对 access_token 的有效性(如是否过期),或查看是否正在为恰当的公众号调用接口");
|
||||||
|
linkedHashMap.put("40037", "不合法的 template_id");
|
||||||
|
linkedHashMap.put("43101", "用户未订阅消息");
|
||||||
|
linkedHashMap.put("43107", "订阅消息能力封禁");
|
||||||
|
linkedHashMap.put("43108", "并发下发消息给同一个粉丝");
|
||||||
|
linkedHashMap.put("45168", "命中敏感词");
|
||||||
|
linkedHashMap.put("47003", "参数错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAccessToken() {
|
||||||
|
String accessToken = Convert.toStr(redisService.get("accessToken"));
|
||||||
|
accessToken = "90_OzMitqhQZ1EPbaqFqGiaCeIhCWCPerSs43dTSNVdriltnbu6F-13Yao0ByELKKP50LtyFo2Kw9xnjivQhNagYqQoJy_vsqP7Nk5l0gfBfOBZDqrlKSFyKKiiIVoBPScAJASGJ";
|
||||||
|
if (StrUtil.isNotEmpty(accessToken)) {
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
String resp = HttpUtil.get(StrUtil.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}", appId, secrete));
|
||||||
|
JSONObject respInfo = JSONObject.parseObject(resp);
|
||||||
|
if (!respInfo.containsKey("access_token")) {
|
||||||
|
log.warn("公众号获取token失败, 响应内容: {}", resp);
|
||||||
|
throw new RuntimeException(resp);
|
||||||
|
}
|
||||||
|
accessToken = respInfo.getString("access_token");
|
||||||
|
int expiresIn = respInfo.getInteger("expires_in");
|
||||||
|
redisService.set("accessToken", accessToken, expiresIn - 10);
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONObject sendTemplateMsg(String templateId, String toUserOpenId, Map<String, Object> data) {
|
||||||
|
log.info("开始发送微信模板消息, 接收用户openId: {}, 消息数据: {}", toUserOpenId, data);
|
||||||
|
String accessToken = getAccessToken();
|
||||||
|
|
||||||
|
JSONObject object1 = new JSONObject();
|
||||||
|
|
||||||
|
object1.put("template_id", templateId);
|
||||||
|
object1.put("touser", toUserOpenId);
|
||||||
|
object1.put("data", data);
|
||||||
|
|
||||||
|
String response = HttpRequest.post("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=".concat(accessToken)).body(object1.toString()).execute().body();
|
||||||
|
log.info("微信模板消息发送成功,响应内容:{}", response);
|
||||||
|
JSONObject resObj = JSONObject.parseObject(response);
|
||||||
|
if (ObjectUtil.isNotEmpty(resObj) && ObjectUtil.isNotNull(resObj) && "0".equals(resObj.get("errcode") + "")) {
|
||||||
|
return resObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException(linkedHashMap.getOrDefault(resObj.get("errcode") + "", "未知错误"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendOperationMsg(List<String> openIdList, String userName, String operationDesc) {
|
||||||
|
openIdList.forEach(openId -> {
|
||||||
|
Map<String, Object> data = new HashMap<String, Object>() {{
|
||||||
|
put("thing19", new HashMap<String, Object>() {{
|
||||||
|
put("value", userName);
|
||||||
|
}});
|
||||||
|
put("thing8", new HashMap<String, Object>() {{
|
||||||
|
put("value", operationDesc);
|
||||||
|
}});
|
||||||
|
put("time21", new HashMap<String, Object>() {{
|
||||||
|
put("value", DateUtil.format(DateUtil.date(), "yyyy-MM-dd HH:mm:ss"));
|
||||||
|
}});
|
||||||
|
}};
|
||||||
|
log.info("开始发送敏感操作消息, 接收用户openId: {}, 操作用户: {}, 操作描述: {}", openId, userName, operationDesc);
|
||||||
|
try {
|
||||||
|
sendTemplateMsg(operationMsgTmpId, openId, data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("发送失败, openId: {}, 响应: {}", openId, e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendStockMsg(String shopName, String productName, BigDecimal stockNum, String openId) {
|
||||||
|
String stockNumStr = stockNum.toPlainString();
|
||||||
|
Map<String, Object> data = new HashMap<String, Object>() {{
|
||||||
|
put("thing22", new HashMap<String, Object>() {{
|
||||||
|
put("value", shopName);
|
||||||
|
}});
|
||||||
|
put("thing4", new HashMap<String, Object>() {{
|
||||||
|
put("value", productName);
|
||||||
|
}});
|
||||||
|
put("number5", new HashMap<String, Object>() {{
|
||||||
|
put("value", stockNumStr);
|
||||||
|
}});
|
||||||
|
}};
|
||||||
|
log.info("开始发送库存预警消息, 接收用户openId: {}, 消息数据: {}", openId, data);
|
||||||
|
try {
|
||||||
|
sendTemplateMsg(warnMsgTmpId, openId, data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("发送失败, openId:{}, msg: {}", openId, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,4 +3,12 @@
|
|||||||
|
|
||||||
<mapper namespace="com.czg.service.product.mapper.ConsInfoMapper">
|
<mapper namespace="com.czg.service.product.mapper.ConsInfoMapper">
|
||||||
|
|
||||||
|
<select id="findOpenIdList" resultType="java.lang.String">
|
||||||
|
select
|
||||||
|
open_id
|
||||||
|
from tb_shop_push_open_id
|
||||||
|
where shop_id = #{shopId}
|
||||||
|
and status = 1
|
||||||
|
and type_info like concat('%', #{type}, '%')
|
||||||
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
Reference in New Issue
Block a user