ocr 数签子
登录更新了用户名称的问题 更新用户信息的问题
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package com.czg.controller.admin;
|
||||
|
||||
import com.czg.market.dto.MkShopRechargeDTO;
|
||||
import com.czg.market.entity.MkShopConsumeDiscountRecord;
|
||||
import com.czg.market.entity.MkShopRechargeFlow;
|
||||
import com.czg.market.service.MkRechargeFlowService;
|
||||
import com.czg.market.service.MkShopConsumeDiscountRecordService;
|
||||
@@ -30,12 +29,6 @@ public class ShopRechargeController {
|
||||
@Resource
|
||||
private MkShopConsumeDiscountRecordService shopConsumeDiscountRecordService;
|
||||
|
||||
@GetMapping("/test")
|
||||
public CzgResult<MkShopConsumeDiscountRecord> get(@RequestParam Long shopId) {
|
||||
// return CzgResult.success(shopConsumeDiscountRecordService.get(shopId));
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置信息获取
|
||||
* 权限标识: activate:list
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.hutool.core.util.URLUtil;
|
||||
import com.czg.log.annotation.OperationLog;
|
||||
import com.czg.product.dto.ConsStockFlowDTO;
|
||||
import com.czg.product.dto.OcrDTO;
|
||||
import com.czg.product.entity.MkOcrService;
|
||||
import com.czg.product.param.ConsCheckStockParam;
|
||||
import com.czg.product.param.ConsInOutStockHeadParam;
|
||||
import com.czg.product.param.ConsReportDamageParam;
|
||||
@@ -37,6 +38,8 @@ public class ConsStockFlowController {
|
||||
|
||||
@Resource
|
||||
private ConsStockFlowService consStockFlowService;
|
||||
@Resource
|
||||
private MkOcrService ocrService;
|
||||
|
||||
/**
|
||||
* 入库单识别
|
||||
@@ -46,17 +49,18 @@ public class ConsStockFlowController {
|
||||
URI uri = new URI(ocrDTO.getUrl());
|
||||
URL url = uri.toURL();
|
||||
InputStream stream = URLUtil.getStream(url);
|
||||
return CzgResult.success(consStockFlowService.ocr(FileUtil.getName(ocrDTO.getUrl()), stream));
|
||||
return CzgResult.success(ocrService.ocr(FileUtil.getName(ocrDTO.getUrl()), stream, "cons"));
|
||||
}
|
||||
|
||||
/**
|
||||
* ocr识别结果
|
||||
*
|
||||
* @param id ocrId
|
||||
* @return 识别结果
|
||||
*/
|
||||
@GetMapping("/ocrResult")
|
||||
public CzgResult<ConsInOutStockHeadParam> ocrResult(@RequestParam Long id) {
|
||||
return CzgResult.success(consStockFlowService.ocrDetail(id));
|
||||
return CzgResult.success(ocrService.ocrDetail(id));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.czg.controller.admin;
|
||||
|
||||
import com.czg.product.service.MkOcrCountStickService;
|
||||
import com.czg.resp.CzgResult;
|
||||
import com.czg.utils.AssertUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* 拍照数签子
|
||||
* @author ww
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/admin/stick")
|
||||
public class StickCountController {
|
||||
|
||||
@Resource
|
||||
private MkOcrCountStickService stickCountService;
|
||||
|
||||
/**
|
||||
* 文件上传并返回点数统计结果
|
||||
*
|
||||
* @param file 上传的图片文件
|
||||
* @return 点数统计结果
|
||||
*/
|
||||
@PostMapping("/count")
|
||||
public CzgResult<Integer> uploadAndCount(MultipartFile file) throws IOException {
|
||||
AssertUtil.isNull(file, "上传文件不能为空");
|
||||
// 9. 返回成功结果
|
||||
return CzgResult.success(stickCountService.getCountStick(file.getBytes(), file.getOriginalFilename()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.czg.product.entity;
|
||||
|
||||
import com.mybatisflex.annotation.Id;
|
||||
import com.mybatisflex.annotation.Table;
|
||||
import java.io.Serializable;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 数签子的外链渠道 实体类。
|
||||
*
|
||||
* @author ww
|
||||
* @since 2025-12-24
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("mk_ocr_count_stick")
|
||||
public class MkOcrCountStick implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 渠道名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 标记
|
||||
*/
|
||||
private String mark;
|
||||
|
||||
/**
|
||||
* 地址
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 状态 0/1
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
private Integer sort;
|
||||
|
||||
private String token;
|
||||
private String account;
|
||||
private String pwd;
|
||||
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.czg.product.entity;
|
||||
|
||||
import com.czg.product.param.ConsInOutStockHeadParam;
|
||||
import com.mybatisflex.core.service.IService;
|
||||
import com.czg.market.entity.MkOcr;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* ocr识别结果 服务层。
|
||||
*
|
||||
@@ -11,4 +14,7 @@ import com.czg.market.entity.MkOcr;
|
||||
*/
|
||||
public interface MkOcrService extends IService<MkOcr> {
|
||||
|
||||
ConsInOutStockHeadParam ocrDetail(Long id);
|
||||
|
||||
Integer ocr(String originalFilename, InputStream inputStream, String type);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import com.czg.product.vo.ConsCheckStockRecordVo;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.mybatisflex.core.service.IService;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -25,7 +24,6 @@ public interface ConsStockFlowService extends IService<ConsStockFlow> {
|
||||
* 手动入库
|
||||
*
|
||||
* @param param 手动出库入参
|
||||
* @return
|
||||
*/
|
||||
ConsInOutStockHeadParam inStock(ConsInOutStockHeadParam param);
|
||||
|
||||
@@ -78,8 +76,4 @@ public interface ConsStockFlowService extends IService<ConsStockFlow> {
|
||||
* @param entity 库存变动记录实体
|
||||
*/
|
||||
void saveFlow(ConsStockFlow entity);
|
||||
|
||||
Integer ocr(String originalFilename, InputStream inputStream);
|
||||
|
||||
ConsInOutStockHeadParam ocrDetail(Long id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.czg.product.service;
|
||||
|
||||
import com.czg.product.entity.MkOcrCountStick;
|
||||
import com.mybatisflex.core.service.IService;
|
||||
|
||||
/**
|
||||
* 数签子的外链渠道 服务层。
|
||||
*
|
||||
* @author ww
|
||||
* @since 2025-12-24
|
||||
*/
|
||||
public interface MkOcrCountStickService extends IService<MkOcrCountStick> {
|
||||
|
||||
int getCountStick(byte[] stream, String fileName);
|
||||
}
|
||||
@@ -34,7 +34,6 @@ public class AliOcrUtil {
|
||||
* <p>使用凭据初始化账号Client</p>
|
||||
*
|
||||
* @return Client
|
||||
* @throws Exception
|
||||
*/
|
||||
public static com.aliyun.bailian20231229.Client createClient() {
|
||||
|
||||
@@ -114,7 +113,7 @@ public class AliOcrUtil {
|
||||
// 复制代码运行请自行打印 API 的返回值
|
||||
DescribeFileResponse describeFileResponse = client.describeFileWithOptions("llm-9zg04s7wlbvi32tq", fileId, headers, runtime);
|
||||
|
||||
log.info("file status: {}", describeFileResponse.getBody());
|
||||
log.info("file status: {}", describeFileResponse.getStatusCode());
|
||||
return describeFileResponse.getBody().getData() != null && "FILE_IS_READY".equals(describeFileResponse.getBody().getData().getStatus());
|
||||
} catch (Exception error) {
|
||||
throw new RuntimeException(error);
|
||||
@@ -154,7 +153,8 @@ public class AliOcrUtil {
|
||||
}
|
||||
|
||||
|
||||
public static String appCall(byte[] bytes, String fileName) {
|
||||
//地址 https://bailian.console.aliyun.com
|
||||
public static String appCall(byte[] bytes, String fileName, String detail) {
|
||||
String id;
|
||||
try {
|
||||
id = getSessionId(bytes, fileName);
|
||||
@@ -164,7 +164,7 @@ public class AliOcrUtil {
|
||||
ApplicationParam param = ApplicationParam.builder()
|
||||
.apiKey("sk-2343af4413834ad1ab43b036e3a903de")
|
||||
.appId("cd612ac509a4499f8ac68a656532d4ae")
|
||||
.prompt("你是一名票据OCR结构化专家,请从我提供的票据图片中智能提取信息并只输出JSON,不得添加解释、不补充不存在内容、不得返回空字符串、字段缺失填null、字段不可省略、数字一律用字符串,使用以下固定JSON结构:{\"documentType\":\"\",\"orderNumber\":\"\",\"date\":\"\",\"customerName\":\"\",\"operator\":\"\",\"items\":[{\"conName\":\"\",\"spec\":\"\",\"unitName\":\"\",\"inOutNumber\":\"\",\"purchasePrice\":\"\",\"subTotal\":\"\"}],\"totalAmount\":\"\",\"remark\":\"\"}。字段映射规则:documentType对应单据类型/销售单/采购单/出货单;orderNumber对应单号/编号/No;date对应日期/开单日期;customerName对应客户名称/收货单位/供应商;operator对应业务员/经办人/制单人/操作员;items.conName对应品名/名称;items.spec对应规格/型号;items.unitName对应单位;items.inOutNumber对应数量;items.purchasePrice对应单价;items.subTotal对应金额/小计;totalAmount对应总金额/合计金额;remark对应备注。严禁生成图片中不存在的字段内容,看不清或未出现的字段必须为null,不允许推测或补全,不得生成多余字段;items只能根据识别到的行生成,不得虚构。必须能识别旋转、倾斜、模糊、撕裂、光照差异、列顺序混乱、无表格线等情况并尽量恢复信息。最终输出必须是纯JSON,不得包含任何非JSON字符。")
|
||||
.prompt(detail)
|
||||
.ragOptions(RagOptions.builder()
|
||||
.sessionFileIds(List.of(id))
|
||||
.build())
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package com.czg.service.account.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.czg.account.dto.ShopConfigDTO;
|
||||
import com.czg.account.entity.ShopConfig;
|
||||
import com.czg.account.entity.ShopInfo;
|
||||
import com.czg.account.service.ShopConfigService;
|
||||
import com.czg.exception.CzgException;
|
||||
import com.czg.sa.StpKit;
|
||||
import com.czg.service.RedisService;
|
||||
import com.czg.service.account.mapper.ShopConfigMapper;
|
||||
import com.czg.service.account.mapper.ShopInfoMapper;
|
||||
import com.czg.utils.PageUtil;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -90,19 +87,10 @@ public class ShopConfigServiceImpl extends ServiceImpl<ShopConfigMapper, ShopCon
|
||||
});
|
||||
|
||||
if (isEnable == 0 && !onyUpValid) {
|
||||
|
||||
updateChain().or(or -> {
|
||||
or.eq(ShopConfig::getId, mainShopId);
|
||||
}).or(or -> {
|
||||
or.in(ShopConfig::getId, childShopIdList);
|
||||
}).set(property, 0).update();
|
||||
updateChain().in(ShopConfig::getId, childShopIdList).set(property, 0).update();
|
||||
}else {
|
||||
if ("all".equals(useShopType)) {
|
||||
updateChain().or(or -> {
|
||||
or.eq(ShopConfig::getId, mainShopId);
|
||||
}).or(or -> {
|
||||
or.in(ShopConfig::getId, childShopIdList);
|
||||
}).set(property, 1).update();
|
||||
updateChain().in(ShopConfig::getId, childShopIdList).set(property, 1).update();
|
||||
}else {
|
||||
if (shopIdList.isEmpty()) {
|
||||
updateChain().eq(ShopConfig::getId, mainShopId).set(property, 1).update();
|
||||
@@ -115,7 +103,6 @@ public class ShopConfigServiceImpl extends ServiceImpl<ShopConfigMapper, ShopCon
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extistList.forEach(item -> redisService.del("shopInfo::" + item));
|
||||
redisService.del("shopInfo::" + mainShopId);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.alibaba.fastjson2.JSONObject;
|
||||
import com.czg.account.dto.auth.GetPhoneDTO;
|
||||
import com.czg.account.dto.auth.LoginTokenDTO;
|
||||
import com.czg.account.dto.auth.UserAuthorizationLoginDTO;
|
||||
import com.czg.account.dto.auth.WechatRawDataDTO;
|
||||
import com.czg.account.entity.UserInfo;
|
||||
import com.czg.account.service.UserAuthorizationService;
|
||||
import com.czg.account.service.UserInfoService;
|
||||
@@ -95,19 +94,13 @@ public class UserAuthorizationServiceImpl implements UserAuthorizationService {
|
||||
openId = wechatAuthUtil.getSessionKeyOrOpenId(userAuthorizationLoginDTO.getCode(), false);
|
||||
userInfo = userInfoService.queryChain().eq(UserInfo::getWechatOpenId, openId).one();
|
||||
userInfo = userInfo == null ? new UserInfo() : userInfo;
|
||||
if (StrUtil.isNotBlank(userAuthorizationLoginDTO.getRawData())) {
|
||||
WechatRawDataDTO wechatRawDataDTO = JSONObject.parseObject(userAuthorizationLoginDTO.getRawData(), WechatRawDataDTO.class);
|
||||
userInfo.setHeadImg(wechatRawDataDTO.getAvatarUrl());
|
||||
userInfo.setNickName(StrUtil.isNotBlank(wechatRawDataDTO.getNickName()) ? wechatRawDataDTO.getNickName() : "微信用户");
|
||||
} else {
|
||||
userInfo.setNickName("微信用户");
|
||||
}
|
||||
userInfo.setNickName(StrUtil.isNotBlank(userInfo.getNickName()) ? userInfo.getNickName() : "微信用户");
|
||||
userInfo.setWechatOpenId(openId);
|
||||
} else {
|
||||
openId = alipayUtil.getOpenId(userAuthorizationLoginDTO.getCode(), false);
|
||||
userInfo = userInfoService.queryChain().eq(UserInfo::getAlipayOpenId, openId).one();
|
||||
userInfo = userInfo == null ? new UserInfo() : userInfo;
|
||||
userInfo.setNickName("支付宝用户");
|
||||
userInfo.setNickName(StrUtil.isNotBlank(userInfo.getNickName()) ? userInfo.getNickName() : "支付宝用户");
|
||||
userInfo.setAlipayOpenId(openId);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.czg.account.dto.user.userinfo.UserInfoEditDTO;
|
||||
import com.czg.account.dto.user.userinfo.UserInfoPwdEditDTO;
|
||||
import com.czg.account.entity.ShopUser;
|
||||
import com.czg.account.entity.UserInfo;
|
||||
import com.czg.account.service.ShopInfoService;
|
||||
import com.czg.account.service.UserInfoService;
|
||||
import com.czg.config.RedisCst;
|
||||
import com.czg.exception.CzgException;
|
||||
@@ -38,6 +39,8 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
|
||||
@Resource
|
||||
private ShopUserMapper shopUserMapper;
|
||||
@Resource
|
||||
private ShopInfoService shopInfoService;
|
||||
@Resource
|
||||
private RedisService redisService;
|
||||
@Resource
|
||||
private AcAccountUtil acAccountUtil;
|
||||
@@ -62,9 +65,10 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
|
||||
UserInfo userInfo = getById(userId);
|
||||
BeanUtil.copyProperties(userInfoEditDTO, userInfo);
|
||||
if (updateById(userInfo)) {
|
||||
if (shopId != -1L) {
|
||||
if (shopId != 0L) {
|
||||
ShopUser shopUser = BeanUtil.copyProperties(userInfo, ShopUser.class);
|
||||
return shopUserMapper.updateByQuery(shopUser, new QueryWrapper().eq(ShopUser::getSourceShopId, shopId).eq(ShopUser::getUserId, userId)) > 0;
|
||||
Long mainIdByShopId = shopInfoService.getMainIdByShopId(shopId);
|
||||
return shopUserMapper.updateByQuery(shopUser, new QueryWrapper().eq(ShopUser::getMainShopId, mainIdByShopId).eq(ShopUser::getUserId, userId)) > 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.czg.service.product.mapper;
|
||||
|
||||
import com.mybatisflex.core.BaseMapper;
|
||||
import com.czg.product.entity.MkOcrCountStick;
|
||||
|
||||
/**
|
||||
* 数签子的外链渠道 映射层。
|
||||
*
|
||||
* @author ww
|
||||
* @since 2025-12-24
|
||||
*/
|
||||
public interface MkOcrCountStickMapper extends BaseMapper<MkOcrCountStick> {
|
||||
|
||||
}
|
||||
@@ -1,370 +1,279 @@
|
||||
package com.czg.service.product.service.impl;
|
||||
package com.czg.service.product.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
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.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.czg.exception.CzgException;
|
||||
import com.czg.market.entity.MkOcr;
|
||||
import com.czg.product.entity.MkOcrService;
|
||||
import com.czg.product.dto.ConsStockFlowDTO;
|
||||
import com.czg.product.dto.SaleOrderDTO;
|
||||
import com.czg.product.entity.ConsInfo;
|
||||
import com.czg.product.entity.ConsStockFlow;
|
||||
import com.czg.product.enums.InOutItemEnum;
|
||||
import com.czg.product.enums.InOutTypeEnum;
|
||||
import com.czg.product.param.*;
|
||||
import com.czg.product.service.ConsStockFlowService;
|
||||
import com.czg.product.vo.ConsCheckStockRecordVo;
|
||||
import com.czg.sa.StpKit;
|
||||
import com.czg.service.product.mapper.ConsInfoMapper;
|
||||
import com.czg.service.product.mapper.ConsStockFlowMapper;
|
||||
import com.czg.service.product.mapper.ProductMapper;
|
||||
import com.czg.service.product.util.WxAccountUtil;
|
||||
import com.czg.utils.AliOcrUtil;
|
||||
import com.czg.utils.PageUtil;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
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.StrUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.czg.exception.CzgException;
|
||||
import com.czg.product.dto.ConsStockFlowDTO;
|
||||
import com.czg.product.entity.ConsInfo;
|
||||
import com.czg.product.entity.ConsStockFlow;
|
||||
import com.czg.product.enums.InOutItemEnum;
|
||||
import com.czg.product.enums.InOutTypeEnum;
|
||||
import com.czg.product.param.*;
|
||||
import com.czg.product.service.ConsStockFlowService;
|
||||
import com.czg.product.vo.ConsCheckStockRecordVo;
|
||||
import com.czg.sa.StpKit;
|
||||
import com.czg.service.product.mapper.ConsInfoMapper;
|
||||
import com.czg.service.product.mapper.ConsStockFlowMapper;
|
||||
import com.czg.service.product.mapper.ProductMapper;
|
||||
import com.czg.service.product.util.WxAccountUtil;
|
||||
import com.czg.utils.PageUtil;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 耗材库存变动记录
|
||||
*
|
||||
* @author Tankaikai tankaikai@aliyun.com
|
||||
* @since 1.0 2025-02-21
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ConsStockFlowServiceImpl extends ServiceImpl<ConsStockFlowMapper, ConsStockFlow> implements ConsStockFlowService {
|
||||
/**
|
||||
* 耗材库存变动记录
|
||||
*
|
||||
* @author Tankaikai tankaikai@aliyun.com
|
||||
* @since 1.0 2025-02-21
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ConsStockFlowServiceImpl extends ServiceImpl<ConsStockFlowMapper, ConsStockFlow> implements ConsStockFlowService {
|
||||
|
||||
private final ConsInfoMapper consInfoMapper;
|
||||
private final ProductMapper productMapper;
|
||||
@Resource
|
||||
private WxAccountUtil wxAccountUtil;
|
||||
@Resource
|
||||
private MkOcrService ocrService;
|
||||
private final ConsInfoMapper consInfoMapper;
|
||||
private final ProductMapper productMapper;
|
||||
@Resource
|
||||
private WxAccountUtil wxAccountUtil;
|
||||
|
||||
private QueryWrapper buildQueryWrapper(ConsStockFlowDTO param) {
|
||||
QueryWrapper queryWrapper = PageUtil.buildSortQueryWrapper();
|
||||
private QueryWrapper buildQueryWrapper(ConsStockFlowDTO param) {
|
||||
QueryWrapper queryWrapper = PageUtil.buildSortQueryWrapper();
|
||||
/*if (StrUtil.isNotEmpty(param.getName())) {
|
||||
queryWrapper.like(ConsStockFlow::getName, param.getName());
|
||||
}*/
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
queryWrapper.eq(ConsStockFlow::getShopId, shopId);
|
||||
queryWrapper.orderBy(ConsStockFlow::getId, false);
|
||||
return queryWrapper;
|
||||
}
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
queryWrapper.eq(ConsStockFlow::getShopId, shopId);
|
||||
queryWrapper.orderBy(ConsStockFlow::getId, false);
|
||||
return queryWrapper;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ConsInOutStockHeadParam inStock(ConsInOutStockHeadParam param) {
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
Long createUserId = StpKit.USER.getLoginIdAsLong();
|
||||
String createUserName = StpKit.USER.getAccount();
|
||||
ConsStockFlow head = BeanUtil.copyProperties(param, ConsStockFlow.class);
|
||||
List<ConsStockFlow> entityList = BeanUtil.copyToList(param.getBodyList(), ConsStockFlow.class);
|
||||
List<ConsStockFlow> insertList = new ArrayList<>();
|
||||
List<ConsInfo> updateStockList = new ArrayList<>();
|
||||
for (ConsInOutStockBodyParam entity : param.getBodyList()) {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ConsInOutStockHeadParam inStock(ConsInOutStockHeadParam param) {
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
Long createUserId = StpKit.USER.getLoginIdAsLong();
|
||||
String createUserName = StpKit.USER.getAccount();
|
||||
ConsStockFlow head = BeanUtil.copyProperties(param, ConsStockFlow.class);
|
||||
List<ConsStockFlow> entityList = BeanUtil.copyToList(param.getBodyList(), ConsStockFlow.class);
|
||||
List<ConsStockFlow> insertList = new ArrayList<>();
|
||||
List<ConsInfo> updateStockList = new ArrayList<>();
|
||||
for (ConsInOutStockBodyParam entity : param.getBodyList()) {
|
||||
|
||||
ConsStockFlow consStockFlow = BeanUtil.copyProperties(entity, ConsStockFlow.class);
|
||||
BeanUtil.copyProperties(head, entity, CopyOptions.create().ignoreNullValue());
|
||||
consStockFlow.setShopId(shopId);
|
||||
consStockFlow.setInOutType(InOutTypeEnum.IN.value());
|
||||
consStockFlow.setInOutItem(InOutItemEnum.MANUAL_IN.value());
|
||||
consStockFlow.setCreateUserId(createUserId);
|
||||
consStockFlow.setCreateUserName(createUserName);
|
||||
consStockFlow.setVendorId(param.getVendorId());
|
||||
String conId = entity.getConId();
|
||||
ConsInfo consInfo;
|
||||
if (StrUtil.isBlank(entity.getConId())) {
|
||||
consInfo = consInfoMapper.selectOneByQuery(new QueryWrapper().like(ConsInfo::getConName, entity.getConName())
|
||||
.eq(ConsInfo::getShopId, shopId).limit(1));
|
||||
if (consInfo == null) {
|
||||
entity.setFailReason("耗材不存在");
|
||||
}else if (!consInfo.getConUnit().equals(entity.getUnitName())) {
|
||||
entity.setFailReason("耗材单位不匹配");
|
||||
consInfo = null;
|
||||
}
|
||||
|
||||
entity.setConId(consInfo == null ? null : consInfo.getId().toString());
|
||||
consStockFlow.setConId(consInfo == null ? null : consInfo.getId());
|
||||
}else {
|
||||
consInfo = consInfoMapper.selectOneById(conId);
|
||||
}
|
||||
ConsStockFlow consStockFlow = BeanUtil.copyProperties(entity, ConsStockFlow.class);
|
||||
BeanUtil.copyProperties(head, entity, CopyOptions.create().ignoreNullValue());
|
||||
consStockFlow.setShopId(shopId);
|
||||
consStockFlow.setInOutType(InOutTypeEnum.IN.value());
|
||||
consStockFlow.setInOutItem(InOutItemEnum.MANUAL_IN.value());
|
||||
consStockFlow.setCreateUserId(createUserId);
|
||||
consStockFlow.setCreateUserName(createUserName);
|
||||
consStockFlow.setVendorId(param.getVendorId());
|
||||
String conId = entity.getConId();
|
||||
ConsInfo consInfo;
|
||||
if (StrUtil.isBlank(entity.getConId())) {
|
||||
consInfo = consInfoMapper.selectOneByQuery(new QueryWrapper().like(ConsInfo::getConName, entity.getConName())
|
||||
.eq(ConsInfo::getShopId, shopId).limit(1));
|
||||
if (consInfo == null) {
|
||||
continue;
|
||||
entity.setFailReason("耗材不存在");
|
||||
} else if (!consInfo.getConUnit().equals(entity.getUnitName())) {
|
||||
entity.setFailReason("耗材单位不匹配");
|
||||
consInfo = null;
|
||||
}
|
||||
consStockFlow.setBeforeNumber(consInfo.getStockNumber());
|
||||
consStockFlow.setAfterNumber(NumberUtil.add(consStockFlow.getBeforeNumber(), entity.getInOutNumber()));
|
||||
insertList.add(consStockFlow);
|
||||
consInfo.setStockNumber(consStockFlow.getAfterNumber());
|
||||
updateStockList.add(consInfo);
|
||||
}
|
||||
if (!insertList.isEmpty()) {
|
||||
mapper.insertBatchSelective(insertList, 50);
|
||||
}
|
||||
for (ConsInfo consInfo : updateStockList) {
|
||||
consInfoMapper.update(consInfo);
|
||||
}
|
||||
|
||||
return param;
|
||||
entity.setConId(consInfo == null ? null : consInfo.getId().toString());
|
||||
consStockFlow.setConId(consInfo == null ? null : consInfo.getId());
|
||||
} else {
|
||||
consInfo = consInfoMapper.selectOneById(conId);
|
||||
}
|
||||
if (consInfo == null) {
|
||||
continue;
|
||||
}
|
||||
consStockFlow.setBeforeNumber(consInfo.getStockNumber());
|
||||
consStockFlow.setAfterNumber(NumberUtil.add(consStockFlow.getBeforeNumber(), entity.getInOutNumber()));
|
||||
insertList.add(consStockFlow);
|
||||
consInfo.setStockNumber(consStockFlow.getAfterNumber());
|
||||
updateStockList.add(consInfo);
|
||||
}
|
||||
if (!insertList.isEmpty()) {
|
||||
mapper.insertBatchSelective(insertList, 50);
|
||||
}
|
||||
for (ConsInfo consInfo : updateStockList) {
|
||||
consInfoMapper.update(consInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void outStock(ConsInOutStockHeadParam param) {
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
Long createUserId = StpKit.USER.getLoginIdAsLong();
|
||||
String createUserName = StpKit.USER.getAccount();
|
||||
ConsStockFlow head = BeanUtil.copyProperties(param, ConsStockFlow.class);
|
||||
List<ConsStockFlow> entityList = BeanUtil.copyToList(param.getBodyList(), ConsStockFlow.class);
|
||||
List<ConsStockFlow> insertList = new ArrayList<>();
|
||||
List<ConsInfo> updateStockList = new ArrayList<>();
|
||||
for (ConsStockFlow entity : entityList) {
|
||||
BeanUtil.copyProperties(head, entity, CopyOptions.create().ignoreNullValue());
|
||||
entity.setInOutNumber(NumberUtil.sub(BigDecimal.ZERO, entity.getInOutNumber()));
|
||||
entity.setShopId(shopId);
|
||||
entity.setInOutType(InOutTypeEnum.OUT.value());
|
||||
entity.setInOutItem(InOutItemEnum.MANUAL_OUT.value());
|
||||
entity.setCreateUserId(createUserId);
|
||||
entity.setCreateUserName(createUserName);
|
||||
Long conId = entity.getConId();
|
||||
ConsInfo consInfo = consInfoMapper.selectOneById(conId);
|
||||
if (consInfo == null) {
|
||||
throw new CzgException(StrUtil.format("耗材{}不存在", entity.getConName()));
|
||||
}
|
||||
entity.setBeforeNumber(consInfo.getStockNumber());
|
||||
entity.setAfterNumber(NumberUtil.add(entity.getBeforeNumber(), entity.getInOutNumber()));
|
||||
insertList.add(entity);
|
||||
consInfo.setStockNumber(entity.getAfterNumber());
|
||||
updateStockList.add(consInfo);
|
||||
}
|
||||
mapper.insertBatchSelective(insertList, 50);
|
||||
for (ConsInfo consInfo : updateStockList) {
|
||||
consInfoMapper.update(consInfo);
|
||||
}
|
||||
}
|
||||
return param;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void checkStock(ConsCheckStockParam param) {
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
Long createUserId = StpKit.USER.getLoginIdAsLong();
|
||||
String createUserName = StpKit.USER.getAccount();
|
||||
ConsStockFlow entity = new ConsStockFlow();
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void outStock(ConsInOutStockHeadParam param) {
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
Long createUserId = StpKit.USER.getLoginIdAsLong();
|
||||
String createUserName = StpKit.USER.getAccount();
|
||||
ConsStockFlow head = BeanUtil.copyProperties(param, ConsStockFlow.class);
|
||||
List<ConsStockFlow> entityList = BeanUtil.copyToList(param.getBodyList(), ConsStockFlow.class);
|
||||
List<ConsStockFlow> insertList = new ArrayList<>();
|
||||
List<ConsInfo> updateStockList = new ArrayList<>();
|
||||
for (ConsStockFlow entity : entityList) {
|
||||
BeanUtil.copyProperties(head, entity, CopyOptions.create().ignoreNullValue());
|
||||
entity.setInOutNumber(NumberUtil.sub(BigDecimal.ZERO, entity.getInOutNumber()));
|
||||
entity.setShopId(shopId);
|
||||
entity.setInOutType(InOutTypeEnum.OUT.value());
|
||||
entity.setInOutItem(InOutItemEnum.MANUAL_OUT.value());
|
||||
entity.setCreateUserId(createUserId);
|
||||
entity.setCreateUserName(createUserName);
|
||||
entity.setShopId(shopId);
|
||||
entity.setConId(param.getConId());
|
||||
entity.setConName(param.getConName());
|
||||
entity.setPurchasePrice(param.getPrice());
|
||||
ConsInfo consInfo = consInfoMapper.selectOneById(param.getConId());
|
||||
Long conId = entity.getConId();
|
||||
ConsInfo consInfo = consInfoMapper.selectOneById(conId);
|
||||
if (consInfo == null) {
|
||||
throw new CzgException(StrUtil.format("耗材{}不存在", entity.getConName()));
|
||||
}
|
||||
BigDecimal winLossNumber = NumberUtil.sub(param.getActualNumber(), param.getStockNumber());
|
||||
if (!NumberUtil.equals(winLossNumber, param.getWinLossNumber())) {
|
||||
throw new CzgException(StrUtil.format("耗材{}库存在发生变动,请刷新后重试", entity.getConName()));
|
||||
}
|
||||
entity.setBeforeNumber(consInfo.getStockNumber());
|
||||
entity.setInOutNumber(winLossNumber);
|
||||
entity.setAfterNumber(NumberUtil.add(entity.getBeforeNumber(), entity.getInOutNumber()));
|
||||
if (NumberUtil.isLess(winLossNumber, BigDecimal.ZERO)) {
|
||||
entity.setInOutType(InOutTypeEnum.OUT.value());
|
||||
entity.setInOutItem(InOutItemEnum.LOSS_OUT.value());
|
||||
} else {
|
||||
entity.setInOutType(InOutTypeEnum.IN.value());
|
||||
entity.setInOutItem(InOutItemEnum.WIN_IN.value());
|
||||
}
|
||||
entity.setSubTotal(NumberUtil.mul(winLossNumber, param.getPrice()));
|
||||
entity.setRemark(param.getRemark());
|
||||
saveFlow(entity);
|
||||
insertList.add(entity);
|
||||
consInfo.setStockNumber(entity.getAfterNumber());
|
||||
updateStockList.add(consInfo);
|
||||
}
|
||||
mapper.insertBatchSelective(insertList, 50);
|
||||
for (ConsInfo consInfo : updateStockList) {
|
||||
consInfoMapper.update(consInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<ConsCheckStockRecordVo> getCheckStockRecordPage(Long conId) {
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
return super.pageAs(PageUtil.buildPage(), query().eq(ConsStockFlow::getShopId, shopId).eq(ConsStockFlow::getConId, conId).orderBy(ConsStockFlow::getId, false), ConsCheckStockRecordVo.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConsCheckStockRecordVo> getCheckStockRecordList(Long conId) {
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
return super.mapper.selectListByQueryAs(query().eq(ConsStockFlow::getShopId, shopId).eq(ConsStockFlow::getConId, conId).orderBy(ConsStockFlow::getId, false), ConsCheckStockRecordVo.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDamage(ConsReportDamageParam param) {
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
Long createUserId = StpKit.USER.getLoginIdAsLong();
|
||||
String createUserName = StpKit.USER.getAccount();
|
||||
ConsInfo consInfo = consInfoMapper.selectOneById(param.getConId());
|
||||
if (consInfo == null) {
|
||||
throw new CzgException("耗材不存在");
|
||||
}
|
||||
ConsStockFlow entity = new ConsStockFlow();
|
||||
entity.setCreateUserId(createUserId);
|
||||
entity.setCreateUserName(createUserName);
|
||||
entity.setShopId(shopId);
|
||||
entity.setConId(param.getConId());
|
||||
entity.setConName(consInfo.getConName());
|
||||
entity.setPurchasePrice(consInfo.getPrice());
|
||||
BigDecimal balance = NumberUtil.sub(consInfo.getStockNumber(), param.getNumber());
|
||||
if (NumberUtil.isLess(balance, BigDecimal.ZERO)) {
|
||||
throw new CzgException(StrUtil.format("耗材{}报损数量不能大于当前库存{}", entity.getConName(), consInfo.getStockNumber()));
|
||||
}
|
||||
entity.setBeforeNumber(consInfo.getStockNumber());
|
||||
entity.setInOutNumber(NumberUtil.sub(BigDecimal.ZERO, param.getNumber()));
|
||||
entity.setAfterNumber(balance);
|
||||
entity.setInOutType(InOutTypeEnum.OUT.value());
|
||||
entity.setInOutItem(InOutItemEnum.DAMAGE_OUT.value());
|
||||
entity.setSubTotal(NumberUtil.mul(param.getNumber(), consInfo.getPrice()));
|
||||
entity.setImgUrls(JSON.toJSONString(param.getImgUrls()));
|
||||
saveFlow(entity);
|
||||
consInfo.setStockNumber(entity.getAfterNumber());
|
||||
consInfoMapper.update(consInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<ConsStockFlowDTO> findConsStockFlowPage(ConsStockFlowParam param) {
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
PageHelper.startPage(PageUtil.buildPageHelp());
|
||||
return PageUtil.convert(new PageInfo<>(mapper.findConsStockFlowPage(shopId, param.getInOutType(), param.getInOutItem(), param.getConId())));
|
||||
}
|
||||
|
||||
@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 = "";
|
||||
try {
|
||||
shopName = StpKit.USER.getShopName();
|
||||
} catch (Exception e) {
|
||||
log.error("获取店铺名称失败");
|
||||
}
|
||||
if (StrUtil.isEmpty(shopName)) {
|
||||
shopName = productMapper.getShopName(shopId);
|
||||
}
|
||||
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);
|
||||
String finalShopName = shopName;
|
||||
ThreadUtil.execAsync(() -> {
|
||||
openIdList.parallelStream().forEach(openId -> {
|
||||
wxAccountUtil.sendStockMsg(finalShopName, conName, afterNumber, openId);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer ocr(String originalFilename, InputStream inputStream) {
|
||||
Long shopId = StpKit.USER.getShopId();
|
||||
byte[] readAllBytes = null;
|
||||
try {
|
||||
readAllBytes = inputStream.readAllBytes();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
String md5 = DigestUtil.md5Hex(readAllBytes);
|
||||
MkOcr ocr = ocrService.getOne(new QueryWrapper().eq(MkOcr::getShopId, shopId).eq(MkOcr::getMd5, md5));
|
||||
if (ocr != null) {
|
||||
return ocr.getId();
|
||||
}
|
||||
MkOcr mkOcr = new MkOcr();
|
||||
mkOcr.setShopId(shopId);
|
||||
mkOcr.setMd5(md5);
|
||||
ocrService.save(mkOcr);
|
||||
byte[] finalReadAllBytes1 = readAllBytes;
|
||||
ThreadUtil.execAsync(() -> {
|
||||
try {
|
||||
String infoStr = AliOcrUtil.appCall(finalReadAllBytes1, originalFilename);
|
||||
SaleOrderDTO saleOrderDTO = JSONObject.parseObject(infoStr, SaleOrderDTO.class);
|
||||
|
||||
ArrayList<ConsInOutStockBodyParam> bodyList = new ArrayList<>();
|
||||
Set<String> nameList = saleOrderDTO.getItems().stream().map(SaleOrderDTO.Item::getConName).collect(Collectors.toSet());
|
||||
Map<String, ConsInfo> consInfoMap = new HashMap<>();
|
||||
if (!nameList.isEmpty()) {
|
||||
consInfoMap = consInfoMapper.selectListByQuery(new QueryWrapper().in(ConsInfo::getConName, nameList).eq(ConsInfo::getShopId, shopId))
|
||||
.stream().collect(Collectors.toMap(ConsInfo::getConName, consInfo -> consInfo));
|
||||
}
|
||||
ArrayList<SaleOrderDTO.Item> unInCons = new ArrayList<>();
|
||||
for (SaleOrderDTO.Item item : saleOrderDTO.getItems()) {
|
||||
ConsInOutStockBodyParam bodyParam = new ConsInOutStockBodyParam()
|
||||
.setConName(item.getConName())
|
||||
.setPurchasePrice(new BigDecimal(item.getPurchasePrice()))
|
||||
.setUnitName(item.getUnitName())
|
||||
.setSubTotal(new BigDecimal(item.getSubTotal()))
|
||||
.setInOutNumber(new BigDecimal(item.getInOutNumber()));
|
||||
ConsInfo consInfo = consInfoMap.get(item.getConName());
|
||||
if (consInfo != null) {
|
||||
unInCons.add(item);
|
||||
bodyParam.setConId(consInfo.getId().toString());
|
||||
}
|
||||
bodyList.add(bodyParam);
|
||||
}
|
||||
|
||||
ConsInOutStockHeadParam headParam = new ConsInOutStockHeadParam();
|
||||
headParam.setBatchNo(saleOrderDTO.getOrderNumber())
|
||||
.setInOutDate(LocalDate.parse(saleOrderDTO.getDate()))
|
||||
.setAmountPayable(new BigDecimal(saleOrderDTO.getTotalAmount()))
|
||||
.setBodyList(bodyList)
|
||||
.setUnInCons(unInCons)
|
||||
.setOcrSaleOrder(saleOrderDTO);
|
||||
|
||||
|
||||
mkOcr.setStatus("SUCCESS");
|
||||
mkOcr.setResp(JSON.toJSONString(headParam));
|
||||
}catch (Exception e) {
|
||||
mkOcr.setErr(e.getMessage());
|
||||
mkOcr.setStatus("FAILED");
|
||||
log.error("ocr失败:", e);
|
||||
}finally {
|
||||
ocrService.updateById(mkOcr);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return mkOcr.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConsInOutStockHeadParam ocrDetail(Long id) {
|
||||
MkOcr mkOcr = ocrService.getOne(new QueryWrapper().eq(MkOcr::getShopId, StpKit.USER.getShopId()).eq(MkOcr::getId, id));
|
||||
if (StrUtil.isNotBlank(mkOcr.getResp())) {
|
||||
return JSONObject.parseObject(mkOcr.getResp(), ConsInOutStockHeadParam.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void checkStock(ConsCheckStockParam param) {
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
Long createUserId = StpKit.USER.getLoginIdAsLong();
|
||||
String createUserName = StpKit.USER.getAccount();
|
||||
ConsStockFlow entity = new ConsStockFlow();
|
||||
entity.setCreateUserId(createUserId);
|
||||
entity.setCreateUserName(createUserName);
|
||||
entity.setShopId(shopId);
|
||||
entity.setConId(param.getConId());
|
||||
entity.setConName(param.getConName());
|
||||
entity.setPurchasePrice(param.getPrice());
|
||||
ConsInfo consInfo = consInfoMapper.selectOneById(param.getConId());
|
||||
if (consInfo == null) {
|
||||
throw new CzgException(StrUtil.format("耗材{}不存在", entity.getConName()));
|
||||
}
|
||||
BigDecimal winLossNumber = NumberUtil.sub(param.getActualNumber(), param.getStockNumber());
|
||||
if (!NumberUtil.equals(winLossNumber, param.getWinLossNumber())) {
|
||||
throw new CzgException(StrUtil.format("耗材{}库存在发生变动,请刷新后重试", entity.getConName()));
|
||||
}
|
||||
entity.setBeforeNumber(consInfo.getStockNumber());
|
||||
entity.setInOutNumber(winLossNumber);
|
||||
entity.setAfterNumber(NumberUtil.add(entity.getBeforeNumber(), entity.getInOutNumber()));
|
||||
if (NumberUtil.isLess(winLossNumber, BigDecimal.ZERO)) {
|
||||
entity.setInOutType(InOutTypeEnum.OUT.value());
|
||||
entity.setInOutItem(InOutItemEnum.LOSS_OUT.value());
|
||||
} else {
|
||||
entity.setInOutType(InOutTypeEnum.IN.value());
|
||||
entity.setInOutItem(InOutItemEnum.WIN_IN.value());
|
||||
}
|
||||
entity.setSubTotal(NumberUtil.mul(winLossNumber, param.getPrice()));
|
||||
entity.setRemark(param.getRemark());
|
||||
saveFlow(entity);
|
||||
consInfo.setStockNumber(entity.getAfterNumber());
|
||||
consInfoMapper.update(consInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<ConsCheckStockRecordVo> getCheckStockRecordPage(Long conId) {
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
return super.pageAs(PageUtil.buildPage(), query().eq(ConsStockFlow::getShopId, shopId).eq(ConsStockFlow::getConId, conId).orderBy(ConsStockFlow::getId, false), ConsCheckStockRecordVo.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConsCheckStockRecordVo> getCheckStockRecordList(Long conId) {
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
return super.mapper.selectListByQueryAs(query().eq(ConsStockFlow::getShopId, shopId).eq(ConsStockFlow::getConId, conId).orderBy(ConsStockFlow::getId, false), ConsCheckStockRecordVo.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDamage(ConsReportDamageParam param) {
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
Long createUserId = StpKit.USER.getLoginIdAsLong();
|
||||
String createUserName = StpKit.USER.getAccount();
|
||||
ConsInfo consInfo = consInfoMapper.selectOneById(param.getConId());
|
||||
if (consInfo == null) {
|
||||
throw new CzgException("耗材不存在");
|
||||
}
|
||||
ConsStockFlow entity = new ConsStockFlow();
|
||||
entity.setCreateUserId(createUserId);
|
||||
entity.setCreateUserName(createUserName);
|
||||
entity.setShopId(shopId);
|
||||
entity.setConId(param.getConId());
|
||||
entity.setConName(consInfo.getConName());
|
||||
entity.setPurchasePrice(consInfo.getPrice());
|
||||
BigDecimal balance = NumberUtil.sub(consInfo.getStockNumber(), param.getNumber());
|
||||
if (NumberUtil.isLess(balance, BigDecimal.ZERO)) {
|
||||
throw new CzgException(StrUtil.format("耗材{}报损数量不能大于当前库存{}", entity.getConName(), consInfo.getStockNumber()));
|
||||
}
|
||||
entity.setBeforeNumber(consInfo.getStockNumber());
|
||||
entity.setInOutNumber(NumberUtil.sub(BigDecimal.ZERO, param.getNumber()));
|
||||
entity.setAfterNumber(balance);
|
||||
entity.setInOutType(InOutTypeEnum.OUT.value());
|
||||
entity.setInOutItem(InOutItemEnum.DAMAGE_OUT.value());
|
||||
entity.setSubTotal(NumberUtil.mul(param.getNumber(), consInfo.getPrice()));
|
||||
entity.setImgUrls(JSON.toJSONString(param.getImgUrls()));
|
||||
saveFlow(entity);
|
||||
consInfo.setStockNumber(entity.getAfterNumber());
|
||||
consInfoMapper.update(consInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<ConsStockFlowDTO> findConsStockFlowPage(ConsStockFlowParam param) {
|
||||
Long shopId = StpKit.USER.getShopId(0L);
|
||||
PageHelper.startPage(PageUtil.buildPageHelp());
|
||||
return PageUtil.convert(new PageInfo<>(mapper.findConsStockFlowPage(shopId, param.getInOutType(), param.getInOutItem(), param.getConId())));
|
||||
}
|
||||
|
||||
@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 = "";
|
||||
try {
|
||||
shopName = StpKit.USER.getShopName();
|
||||
} catch (Exception e) {
|
||||
log.error("获取店铺名称失败");
|
||||
}
|
||||
if (StrUtil.isEmpty(shopName)) {
|
||||
shopName = productMapper.getShopName(shopId);
|
||||
}
|
||||
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);
|
||||
String finalShopName = shopName;
|
||||
ThreadUtil.execAsync(() -> {
|
||||
openIdList.parallelStream().forEach(openId -> {
|
||||
wxAccountUtil.sendStockMsg(finalShopName, conName, afterNumber, openId);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.czg.service.product.service.impl;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.czg.constants.SystemConstants;
|
||||
import com.czg.product.entity.MkOcrCountStick;
|
||||
import com.czg.product.service.MkOcrCountStickService;
|
||||
import com.czg.service.RedisService;
|
||||
import com.czg.service.product.mapper.MkOcrCountStickMapper;
|
||||
import com.czg.service.product.util.AISmartCountUtils;
|
||||
import com.czg.service.product.util.MuSmartCountUtils;
|
||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数签子的外链渠道 服务层实现。
|
||||
*
|
||||
* @author ww
|
||||
* @since 2025-12-24
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MkOcrCountStickServiceImpl extends ServiceImpl<MkOcrCountStickMapper, MkOcrCountStick> implements MkOcrCountStickService {
|
||||
|
||||
@Resource
|
||||
private RedisService redisService;
|
||||
|
||||
@Override
|
||||
public int getCountStick(byte[] fileByte, String fileName) {
|
||||
List<MkOcrCountStick> list = list(query().eq(MkOcrCountStick::getStatus, SystemConstants.OneZero.ONE).orderBy(MkOcrCountStick::getSort, true));
|
||||
for (MkOcrCountStick mkOcrCountStick : list) {
|
||||
if ("znds".equals(mkOcrCountStick.getMark())) {
|
||||
try {
|
||||
String tickCount = AISmartCountUtils.getTickCount(mkOcrCountStick.getToken(), fileByte, fileName);
|
||||
if (StrUtil.isNotBlank(tickCount)) {
|
||||
return Integer.parseInt(tickCount);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
mkOcrCountStick.setStatus(SystemConstants.OneZero.ZERO);
|
||||
log.error("智能点数失败", e);
|
||||
updateById(mkOcrCountStick);
|
||||
}
|
||||
} else if ("ygmh".equals(mkOcrCountStick.getMark())) {
|
||||
try {
|
||||
String token = getToken(mkOcrCountStick.getAccount(), mkOcrCountStick.getPwd());
|
||||
return MuSmartCountUtils.detectBambooStick(Base64.encode(fileByte), token);
|
||||
} catch (Exception e) {
|
||||
mkOcrCountStick.setStatus(SystemConstants.OneZero.ZERO);
|
||||
log.error("智能点数失败", e);
|
||||
updateById(mkOcrCountStick);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 一个木涵 获取token
|
||||
private String getToken(String username, String password) {
|
||||
Object token = redisService.get("ocr:ygmh:token");
|
||||
if (token != null) {
|
||||
return token.toString();
|
||||
}
|
||||
String muToken = MuSmartCountUtils.login(username, password);
|
||||
//时间为3个月减去60秒
|
||||
redisService.set("ocr:ygmh:token", muToken, 7775940);
|
||||
return muToken;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,148 @@
|
||||
package com.czg.service.product.service.impl;
|
||||
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.czg.product.dto.SaleOrderDTO;
|
||||
import com.czg.product.entity.ConsInfo;
|
||||
import com.czg.product.param.ConsInOutStockBodyParam;
|
||||
import com.czg.product.param.ConsInOutStockHeadParam;
|
||||
import com.czg.sa.StpKit;
|
||||
import com.czg.service.product.mapper.ConsInfoMapper;
|
||||
import com.czg.service.product.mapper.MkOcrMapper;
|
||||
import com.czg.utils.AliOcrUtil;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||
import com.czg.market.entity.MkOcr;
|
||||
import com.czg.product.entity.MkOcrService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* ocr识别结果 服务层实现。
|
||||
*
|
||||
* @author zs
|
||||
* @since 2025-11-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MkOcrServiceImpl extends ServiceImpl<MkOcrMapper, MkOcr> implements MkOcrService{
|
||||
public class MkOcrServiceImpl extends ServiceImpl<MkOcrMapper, MkOcr> implements MkOcrService {
|
||||
|
||||
@Resource
|
||||
private ConsInfoMapper consInfoMapper;
|
||||
|
||||
String consOcrDetail = "你是一名票据OCR结构化专家,请从我提供的票据图片中智能提取信息并只输出JSON,不得添加解释、不补充不存在内容、不得返回空字符串、字段缺失填null、字段不可省略、数字一律用字符串," +
|
||||
"使用以下固定JSON结构:{\"documentType\":\"\",\"orderNumber\":\"\",\"date\":\"\",\"customerName\":\"\",\"operator\":\"\"," +
|
||||
"\"items\":[{\"conName\":\"\",\"spec\":\"\",\"unitName\":\"\",\"inOutNumber\":\"\",\"purchasePrice\":\"\",\"subTotal\":\"\"}]," +
|
||||
"\"totalAmount\":\"\",\"remark\":\"\"}。字段映射规则:documentType对应单据类型/销售单/采购单/出货单;orderNumber对应单号/编号/No;" +
|
||||
"date对应日期/开单日期;customerName对应客户名称/收货单位/供应商;operator对应业务员/经办人/制单人/操作员;items.conName对应品名/名称;" +
|
||||
"items.spec对应规格/型号;items.unitName对应单位;items.inOutNumber对应数量;items.purchasePrice对应单价;items.subTotal对应金额/小计;" +
|
||||
"totalAmount对应总金额/合计金额;remark对应备注。严禁生成图片中不存在的字段内容,看不清或未出现的字段必须为null,不允许推测或补全,不得生成多余字段;" +
|
||||
"items只能根据识别到的行生成,不得虚构。必须能识别旋转、倾斜、模糊、撕裂、光照差异、列顺序混乱、无表格线等情况并尽量恢复信息。" +
|
||||
"最终输出必须是纯JSON,不得包含任何非JSON字符。";
|
||||
|
||||
@Override
|
||||
public ConsInOutStockHeadParam ocrDetail(Long id) {
|
||||
MkOcr mkOcr = getOne(new QueryWrapper().eq(MkOcr::getShopId, StpKit.USER.getShopId()).eq(MkOcr::getId, id));
|
||||
if (StrUtil.isNotBlank(mkOcr.getResp())) {
|
||||
return JSONObject.parseObject(mkOcr.getResp(), ConsInOutStockHeadParam.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer ocr(String originalFilename, InputStream inputStream, String type) {
|
||||
Long shopId = StpKit.USER.getShopId();
|
||||
byte[] readAllBytes = null;
|
||||
try {
|
||||
readAllBytes = inputStream.readAllBytes();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
String md5 = DigestUtil.md5Hex(readAllBytes);
|
||||
MkOcr ocr = getOne(new QueryWrapper().eq(MkOcr::getShopId, shopId).eq(MkOcr::getMd5, md5));
|
||||
if (ocr != null) {
|
||||
return ocr.getId();
|
||||
}
|
||||
MkOcr mkOcr = new MkOcr();
|
||||
mkOcr.setShopId(shopId);
|
||||
mkOcr.setMd5(md5);
|
||||
save(mkOcr);
|
||||
byte[] finalReadAllBytes1 = readAllBytes;
|
||||
ThreadUtil.execAsync(() -> {
|
||||
try {
|
||||
if ("cons".equals(type)) {
|
||||
mkOcr.setResp(ocrCons(finalReadAllBytes1, originalFilename, shopId));
|
||||
} else {
|
||||
|
||||
}
|
||||
mkOcr.setStatus("SUCCESS");
|
||||
} catch (Exception e) {
|
||||
mkOcr.setErr(e.getMessage());
|
||||
mkOcr.setStatus("FAILED");
|
||||
log.error("ocr失败:", e);
|
||||
} finally {
|
||||
updateById(mkOcr);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return mkOcr.getId();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 识别 入库单
|
||||
*/
|
||||
private String ocrCons(byte[] finalReadAllBytes1, String originalFilename, Long shopId) {
|
||||
String infoStr = AliOcrUtil.appCall(finalReadAllBytes1, originalFilename, consOcrDetail);
|
||||
SaleOrderDTO saleOrderDTO = JSONObject.parseObject(infoStr, SaleOrderDTO.class);
|
||||
|
||||
ArrayList<ConsInOutStockBodyParam> bodyList = new ArrayList<>();
|
||||
Set<String> nameList = saleOrderDTO.getItems().stream().map(SaleOrderDTO.Item::getConName).collect(Collectors.toSet());
|
||||
Map<String, ConsInfo> consInfoMap = new HashMap<>();
|
||||
if (!nameList.isEmpty()) {
|
||||
consInfoMap = consInfoMapper.selectListByQuery(new QueryWrapper().in(ConsInfo::getConName, nameList).eq(ConsInfo::getShopId, shopId))
|
||||
.stream().collect(Collectors.toMap(ConsInfo::getConName, consInfo -> consInfo));
|
||||
}
|
||||
ArrayList<SaleOrderDTO.Item> unInCons = new ArrayList<>();
|
||||
for (SaleOrderDTO.Item item : saleOrderDTO.getItems()) {
|
||||
ConsInOutStockBodyParam bodyParam = new ConsInOutStockBodyParam()
|
||||
.setConName(item.getConName())
|
||||
.setPurchasePrice(new BigDecimal(item.getPurchasePrice()))
|
||||
.setUnitName(item.getUnitName())
|
||||
.setSubTotal(new BigDecimal(item.getSubTotal()))
|
||||
.setInOutNumber(new BigDecimal(item.getInOutNumber()));
|
||||
ConsInfo consInfo = consInfoMap.get(item.getConName());
|
||||
if (consInfo != null) {
|
||||
unInCons.add(item);
|
||||
bodyParam.setConId(consInfo.getId().toString());
|
||||
}
|
||||
bodyList.add(bodyParam);
|
||||
}
|
||||
|
||||
ConsInOutStockHeadParam headParam = new ConsInOutStockHeadParam();
|
||||
headParam.setBatchNo(saleOrderDTO.getOrderNumber())
|
||||
.setInOutDate(LocalDate.parse(saleOrderDTO.getDate()))
|
||||
.setAmountPayable(new BigDecimal(saleOrderDTO.getTotalAmount()))
|
||||
.setBodyList(bodyList)
|
||||
.setUnInCons(unInCons)
|
||||
.setOcrSaleOrder(saleOrderDTO);
|
||||
return JSON.toJSONString(headParam);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.czg.service.product.util;
|
||||
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.czg.exception.CzgException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
/**
|
||||
* 智能点数上传工具类
|
||||
* @author ww
|
||||
*/
|
||||
@Slf4j
|
||||
public class AISmartCountUtils {
|
||||
// 基础配置
|
||||
private static final String BASE_URL = "https://api.aismartcount.com/api/v1";
|
||||
private static final int TIMEOUT = 30000;
|
||||
|
||||
/**
|
||||
* 示例:完整调用流程
|
||||
*/
|
||||
public static String getTickCount(String token, byte[] file, String fileName) throws Exception {
|
||||
String uploadResponse = uploadImage(token, file, fileName);
|
||||
return countImageDots(token, uploadResponse);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传图片接口
|
||||
* 完全使用你提供的header(String, String)方法设置请求头
|
||||
*
|
||||
* @param imageData 图片数据(InputStream / byte[] / File)
|
||||
* @return 上传响应结果
|
||||
* @throws Exception 上传异常
|
||||
*/
|
||||
private static String uploadImage(String token, Object imageData, String fileName) {
|
||||
|
||||
// 1. 构建请求URL
|
||||
String uploadUrl = BASE_URL + "/upload/image";
|
||||
|
||||
// 2. 构建POST请求并设置基础参数
|
||||
HttpRequest postRequest = HttpUtil.createPost(uploadUrl)
|
||||
.header("authorization", token)
|
||||
.timeout(TIMEOUT);
|
||||
|
||||
// 3. 根据数据类型设置文件上传参数
|
||||
if (imageData instanceof File files) {
|
||||
postRequest.form("file", files);
|
||||
} else if (imageData instanceof InputStream input) {
|
||||
postRequest.form("file", input, fileName);
|
||||
} else if (imageData instanceof byte[] bytes) {
|
||||
postRequest.form("file", bytes, fileName);
|
||||
} else {
|
||||
throw new IllegalArgumentException("不支持的图片数据类型:" + (imageData == null ? "null" : imageData.getClass().getName()));
|
||||
}
|
||||
String body = postRequest.execute().body();
|
||||
// 解析上传响应并校验状态码
|
||||
JSONObject uploadResult = JSONUtil.parseObj(body);
|
||||
|
||||
// 1. 校验响应码(核心)
|
||||
int code = uploadResult.getInt("code", -1);
|
||||
if (code != 200) {
|
||||
log.error("智能点数 图片上传失败 {}", uploadResult);
|
||||
throw new CzgException("图片上传失败");
|
||||
}
|
||||
JSONObject dataObj = uploadResult.getJSONObject("data");
|
||||
if (dataObj == null) {
|
||||
log.error("智能点数 图片上传响应中未找到data字段 {}", uploadResult);
|
||||
throw new CzgException("图片上传失败");
|
||||
}
|
||||
String imgUrl = dataObj.getStr("url");
|
||||
if (StrUtil.isBlank(imgUrl)) {
|
||||
log.error("智能点数 图片上传成功,但未返回图片URL {}", uploadResult);
|
||||
throw new CzgException("图片上传成功,但未返回图片URL");
|
||||
}
|
||||
return imgUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计图片点数接口
|
||||
* 手动拼接URL参数,使用header方法设置授权头
|
||||
*
|
||||
* @param imgUrl 图片URL
|
||||
* @return 统计响应结果
|
||||
* @throws Exception 请求异常
|
||||
*/
|
||||
private static String countImageDots(String token, String imgUrl) throws Exception {
|
||||
String countUrl = BASE_URL + "/count/count-image-dots" +
|
||||
"?img_url=" + encodeParam(imgUrl) +
|
||||
"&type=stick&secure=true&upload_source=pc";
|
||||
|
||||
// 2. 构建请求(仅使用你提供的方法)
|
||||
String countResponse = HttpUtil.createPost(countUrl)
|
||||
.header("authorization", token)
|
||||
.timeout(TIMEOUT)
|
||||
.execute()
|
||||
.body();
|
||||
JSONObject countResult = JSONUtil.parseObj(countResponse);
|
||||
int countCode = countResult.getInt("code", -1);
|
||||
if (countCode != 200) {
|
||||
log.error("智能点数 点数统计失败,未知错误 {}", countResult);
|
||||
throw new CzgException("点数统计失败,未知错误");
|
||||
}
|
||||
// 3. 执行请求并返回结果
|
||||
return countResult.getStr("total_count");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通用URL参数编码
|
||||
*
|
||||
* @param param 要编码的参数
|
||||
* @return 编码后的字符串
|
||||
*/
|
||||
private static String encodeParam(String param) {
|
||||
if (StrUtil.isBlank(param)) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
return URLEncoder.encode(param, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("URL参数编码失败: " + param, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.czg.service.product.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.czg.exception.CzgException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
/**
|
||||
* 一个木涵
|
||||
*
|
||||
* @author: ww
|
||||
*/
|
||||
@Slf4j
|
||||
public class MuSmartCountUtils {
|
||||
// 基础配置
|
||||
private static final String LOGIN_URL = "https://uapi.woobx.cn/user/login";
|
||||
private static final String DETECT_URL = "https://uapi.woobx.cn/app/object-det/bamboo-stick";
|
||||
// 请求超时时间(毫秒)
|
||||
private static final int TIMEOUT = 10000;
|
||||
|
||||
/**
|
||||
* 登录接口 - 获取认证Token(线程安全)
|
||||
*
|
||||
* @return 认证Token
|
||||
*/
|
||||
public static String login(String username, String password) {
|
||||
// 构建登录请求
|
||||
HttpResponse response = HttpRequest.post(LOGIN_URL)
|
||||
.form("username", username)
|
||||
.form("password", password)
|
||||
.timeout(TIMEOUT)
|
||||
.execute();
|
||||
// 校验响应状态
|
||||
if (!response.isOk()) {
|
||||
log.error("登录失败,响应码:{},数据:{}", response.getStatus(), response.body());
|
||||
throw new CzgException("登录失败");
|
||||
}
|
||||
|
||||
// 解析响应结果
|
||||
String responseStr = response.body();
|
||||
JSONObject result = JSON.parseObject(responseStr);
|
||||
if (200 != result.getIntValue("code")) {
|
||||
log.error("登录失败,响应码:{},数据:{}", response.getStatus(), result);
|
||||
throw new CzgException("登录失败");
|
||||
}
|
||||
|
||||
// 提取并缓存Token
|
||||
String token = result.getJSONObject("data").getString("authToken");
|
||||
if (StrUtil.isBlank(token)) {
|
||||
log.error("登录失败,响应码:{},数据:{}", response.getStatus(), result);
|
||||
throw new CzgException("登录失败");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 竹签识别核心方法(自动处理Token过期重试)
|
||||
*
|
||||
* @param imageBase64 图片Base64编码字符串
|
||||
* @return 识别结果JSON对象
|
||||
* @throws Exception 识别异常
|
||||
*/
|
||||
public static int detectBambooStick(String imageBase64, String token) throws Exception {
|
||||
// 参数校验
|
||||
if (StrUtil.isBlank(imageBase64)) {
|
||||
throw new CzgException("图片Base64编码不能为空");
|
||||
}
|
||||
// 第一次调用识别接口
|
||||
JSONObject detectResult = doDetectRequest(imageBase64, token);
|
||||
return MuSmartCountUtils.countBambooStick(detectResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行竹签识别请求(核心请求逻辑)
|
||||
*
|
||||
* @param imageBase64 图片Base64编码
|
||||
* @param token 认证Token
|
||||
* @return 识别结果
|
||||
*/
|
||||
private static JSONObject doDetectRequest(String imageBase64, String token){
|
||||
// 构建识别请求
|
||||
HttpResponse response = HttpRequest.post(DETECT_URL)
|
||||
.header("authorization", "Bearer " + token)
|
||||
.form("image", imageBase64)
|
||||
.form("base64", "true")
|
||||
.timeout(TIMEOUT)
|
||||
.execute();
|
||||
|
||||
// 校验响应状态
|
||||
if (!response.isOk()) {
|
||||
throw new RuntimeException("识别请求失败,响应码:" + response.getStatus());
|
||||
}
|
||||
|
||||
// 解析响应结果
|
||||
String responseStr = response.body();
|
||||
return JSON.parseObject(responseStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析识别结果,统计竹签数量(自定义分数阈值)
|
||||
*
|
||||
* @param detectResult 识别结果JSON
|
||||
* @return 竹签数量
|
||||
*/
|
||||
private static int countBambooStick(JSONObject detectResult) {
|
||||
// 参数校验
|
||||
if (detectResult == null || 200 != detectResult.getIntValue("code")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
JSONArray dataArray = detectResult.getJSONArray("data");
|
||||
if (dataArray == null || dataArray.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
return dataArray.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.czg.service.product.mapper.MkOcrCountStickMapper">
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user