配置智能柜锁

This commit is contained in:
dqz 2025-03-21 16:59:17 +08:00
parent 7cf2f4dd0c
commit 48e1b88c22
59 changed files with 1782 additions and 71 deletions

View File

@ -0,0 +1,76 @@
package com.agileboot.admin.controller.cabinet;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
import com.agileboot.common.core.base.BaseController;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import com.agileboot.domain.cabinet.cell.CabinetCellApplicationService;
import com.agileboot.domain.cabinet.cell.command.AddCabinetCellCommand;
import com.agileboot.domain.cabinet.cell.command.UpdateCabinetCellCommand;
import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity;
import com.agileboot.domain.cabinet.cell.dto.CabinetCellDTO;
import com.agileboot.domain.cabinet.cell.query.SearchCabinetCellQuery;
import com.agileboot.domain.common.command.BulkOperationCommand;
import io.swagger.v3.oas.annotations.Operation;
import java.util.List;
import javax.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/cabinet/cell")
@RequiredArgsConstructor
@Validated
public class CabinetCellController extends BaseController {
private final CabinetCellApplicationService cabinetCellApplicationService;
@Operation(summary = "格口列表")
@GetMapping
public ResponseDTO<PageDTO<CabinetCellDTO>> list(SearchCabinetCellQuery<CabinetCellEntity> query) {
PageDTO<CabinetCellDTO> page = cabinetCellApplicationService.getCabinetCellList(query);
return ResponseDTO.ok(page);
}
@Operation(summary = "新增格口")
@AccessLog(title = "格口管理", businessType = BusinessTypeEnum.ADD)
@PostMapping
public ResponseDTO<Void> add(@Validated @RequestBody AddCabinetCellCommand command) {
cabinetCellApplicationService.addCabinetCell(command);
return ResponseDTO.ok();
}
@Operation(summary = "修改格口")
@AccessLog(title = "格口管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/{cellId}")
public ResponseDTO<Void> edit(@PathVariable Long cellId, @Validated @RequestBody UpdateCabinetCellCommand command) {
command.setCellId(cellId);
cabinetCellApplicationService.updateCabinetCell(command);
return ResponseDTO.ok();
}
@Operation(summary = "删除格口")
@AccessLog(title = "格口管理", businessType = BusinessTypeEnum.DELETE)
@DeleteMapping("/{ids}")
public ResponseDTO<Void> remove(@PathVariable @NotNull List<Long> ids) {
cabinetCellApplicationService.deleteCabinetCell(new BulkOperationCommand<>(ids));
return ResponseDTO.ok();
}
@Operation(summary = "配置格口商品")
@AccessLog(title = "格口管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/configureGoodsCells/{cellId}/{goodsId}")
public ResponseDTO<Void> configureGoodsCells(@PathVariable Long cellId, @PathVariable Long goodsId) {
cabinetCellApplicationService.configureGoodsCells(cellId, goodsId);
return ResponseDTO.ok();
}
}

View File

@ -0,0 +1,90 @@
package com.agileboot.admin.controller.cabinet;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
import com.agileboot.common.core.base.BaseController;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import com.agileboot.domain.cabinet.cell.CabinetCellApplicationService;
import com.agileboot.domain.cabinet.cell.dto.CabinetCellDTO;
import com.agileboot.domain.cabinet.smartCabinet.dto.AllCabinetDataDTO;
import com.agileboot.domain.common.command.BulkOperationCommand;
import com.agileboot.domain.cabinet.smartCabinet.SmartCabinetApplicationService;
import com.agileboot.domain.cabinet.smartCabinet.command.AddSmartCabinetCommand;
import com.agileboot.domain.cabinet.smartCabinet.command.UpdateSmartCabinetCommand;
import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetEntity;
import com.agileboot.domain.cabinet.smartCabinet.dto.SmartCabinetDTO;
import com.agileboot.domain.cabinet.smartCabinet.query.SearchSmartCabinetQuery;
import io.swagger.v3.oas.annotations.Operation;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/cabinet/smartCabinet")
@RequiredArgsConstructor
@Validated
public class SmartCabinetController extends BaseController {
private final SmartCabinetApplicationService smartCabinetApplicationService;
private final CabinetCellApplicationService cabinetCellApplicationService;
@Operation(summary = "智能柜列表")
@GetMapping
public ResponseDTO<PageDTO<SmartCabinetDTO>> list(SearchSmartCabinetQuery<SmartCabinetEntity> query) {
PageDTO<SmartCabinetDTO> page = smartCabinetApplicationService.getSmartCabinetList(query);
return ResponseDTO.ok(page);
}
@Operation(summary = "新增智能柜")
@AccessLog(title = "智能柜管理", businessType = BusinessTypeEnum.ADD)
@PostMapping
public ResponseDTO<Void> add(@Validated @RequestBody AddSmartCabinetCommand command) {
smartCabinetApplicationService.addSmartCabinet(command);
return ResponseDTO.ok();
}
@Operation(summary = "修改智能柜")
@AccessLog(title = "智能柜管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/{id}")
public ResponseDTO<Void> edit(@PathVariable Long id, @Validated @RequestBody UpdateSmartCabinetCommand command) {
command.setCabinetId(id);
smartCabinetApplicationService.updateSmartCabinet(command);
return ResponseDTO.ok();
}
@Operation(summary = "删除智能柜")
@AccessLog(title = "智能柜管理", businessType = BusinessTypeEnum.DELETE)
@DeleteMapping("/{ids}")
public ResponseDTO<Void> remove(@PathVariable @NotNull List<Long> ids) {
smartCabinetApplicationService.deleteSmartCabinet(new BulkOperationCommand<>(ids));
return ResponseDTO.ok();
}
@Operation(summary = "获取所有柜机和格口数据")
@GetMapping("/all")
public ResponseDTO<AllCabinetDataDTO> getAllCabinetsWithCells() {
List<CabinetCellDTO> cells = cabinetCellApplicationService.selectAll().stream()
.filter(cell -> cell.getAvailableStatus().equals(1) && cell.getUsageStatus().equals(1))
.map(CabinetCellDTO::new)
.collect(Collectors.toList());
List<SmartCabinetDTO> cabinets = smartCabinetApplicationService.selectAll().stream()
.filter(cabinet -> cells.stream().anyMatch(cell -> cell.getCabinetId().equals(cabinet.getCabinetId())))
.map(SmartCabinetDTO::new)
.collect(Collectors.toList());
return ResponseDTO.ok(new AllCabinetDataDTO(cabinets, cells));
}
}

View File

@ -0,0 +1,55 @@
package com.agileboot.admin.controller.common;
import com.agileboot.infrastructure.cache.RedisUtil;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private String redisPort;
@Autowired
private RedisUtil redisUtil;
@GetMapping("/time")
public String getTime() {
return "version 0.0.1 time:" + String.valueOf(new Date().getTime());
}
@GetMapping("/redisIp")
public String getRedisIp() {
return "Redis服务ip:" + redisHost + " 端口:" + redisPort;
}
@GetMapping("/testRedis")
public String testRedisConnection() {
try {
String testKey = "shop-back-end:test";
long timestamp = new Date().getTime();
// 写入测试值
redisUtil.setCacheObject(testKey, timestamp);
// 读取测试值
Long storedValue = redisUtil.getCacheObject(testKey);
if (storedValue != null && storedValue == timestamp) {
return "Redis连接正常读写测试通过";
}
return "Redis读写测试异常读取值与写入值不匹配";
} catch (RedisConnectionFailureException e) {
return "Redis连接失败" + e.getMessage();
} catch (Exception e) {
return "Redis操作异常" + e.getMessage();
}
}
}

View File

@ -1,6 +1,7 @@
package com.agileboot.admin.controller.qywx;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
import com.agileboot.admin.customize.service.QywxScheduleJob;
import com.agileboot.common.core.base.BaseController;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.common.core.page.PageDTO;
@ -12,10 +13,15 @@ import com.agileboot.domain.qywx.department.command.UpdateDepartmentCommand;
import com.agileboot.domain.qywx.department.db.QyDepartmentEntity;
import com.agileboot.domain.qywx.department.dto.QyDepartmentDTO;
import com.agileboot.domain.qywx.department.query.SearchQyDepartmentQuery;
import com.agileboot.domain.qywx.template.command.UpdateTemplateCommand;
import com.agileboot.domain.qywx.template.db.QyTemplateEntity;
import io.swagger.v3.oas.annotations.Operation;
import java.util.List;
import javax.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@ -24,15 +30,18 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/qywx/departments")
@RequiredArgsConstructor
@Validated
@Slf4j
public class QyDepartmentController extends BaseController {
private final DepartmentApplicationService departmentApplicationService;
private final QywxScheduleJob qywxScheduleJob;
@Operation(summary = "部门列表")
@GetMapping
@ -65,4 +74,16 @@ public class QyDepartmentController extends BaseController {
departmentApplicationService.deleteDepartment(new BulkOperationCommand<>(ids));
return ResponseDTO.ok();
}
@GetMapping("/syncDepartmentInfo")
public String syncDepartmentInfo(@RequestParam String appid) {
try {
qywxScheduleJob.syncDepartmentInfo(appid);
return "success";
} catch (Exception e) {
log.error("syncDepartmentInfo error", e);
return e.getLocalizedMessage();
}
}
}

View File

@ -137,7 +137,7 @@ public class SecurityConfig {
.antMatchers("/login", "/register", "/getConfig", "/captchaImage", "/api/**", "/file/**").anonymous()
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js",
"/profile/**").permitAll()
.antMatchers("/qywx/**").permitAll()
.antMatchers("/qywx/**", "/test/**").permitAll()
// TODO this is danger.
.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()

View File

@ -1,6 +1,8 @@
package com.agileboot.admin.customize.service;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.agileboot.domain.common.command.BulkOperationCommand;
import com.agileboot.domain.qywx.accessToken.AccessTokenApplicationService;
import com.agileboot.domain.qywx.accessToken.db.QyAccessTokenEntity;
import com.agileboot.domain.qywx.api.QywxApiUtil;
@ -10,6 +12,8 @@ import com.agileboot.domain.qywx.api.response.DepartmentListResponse;
import com.agileboot.domain.qywx.authCorpInfo.AuthCorpInfoApplicationService;
import com.agileboot.domain.qywx.authCorpInfo.db.QyAuthCorpInfoEntity;
import com.agileboot.domain.qywx.department.DepartmentApplicationService;
import com.agileboot.domain.qywx.department.command.AddDepartmentCommand;
import com.agileboot.domain.qywx.department.command.UpdateDepartmentCommand;
import com.agileboot.domain.qywx.department.db.QyDepartmentEntity;
import com.agileboot.domain.qywx.template.TemplateApplicationService;
import com.agileboot.domain.qywx.template.command.UpdateTemplateCommand;
@ -23,6 +27,7 @@ import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@ -59,6 +64,9 @@ public class QywxScheduleJob {
try {
QyTemplateEntity template = templateApplicationService.getByAppid(appid);
if (null == template) {
return;
}
String suiteAccessToken = templateApplicationService.getSuiteAccessToken(template.getSuiteId(), template.getSecret(),
template.getSuiteTicket());
@ -95,13 +103,13 @@ public class QywxScheduleJob {
return;
}
accessTokenApplicationService.getAccessToken(authCorpInfo.getSuiteId(), authCorpInfo.getPermanentCode(), appid);
accessTokenApplicationService.getAccessToken(authCorpInfo.getCorpid(), authCorpInfo.getPermanentCode(), appid);
} catch (Exception e) {
log.error("getAccessToken error", e);
}
}
@Scheduled(cron = "0 0 * * * *")
@Scheduled(cron = "0 30 * * * *")
public void syncDepartmentInfoTask() {
try {
syncDepartmentInfo(appid);
@ -188,39 +196,63 @@ public class QywxScheduleJob {
})
.collect(Collectors.toList());
// 执行数据库操作
if (!toAdd.isEmpty()) {
List<QyDepartmentEntity> newDeptList = toAdd.stream()
List<AddDepartmentCommand> newDeptList = toAdd.stream()
.map(d -> {
QyDepartmentEntity entity = new QyDepartmentEntity();
entity.setDepartmentId(String.valueOf(d.getId()));
entity.setName(d.getName());
entity.setNameEn(d.getName_en());
AddDepartmentCommand command = new AddDepartmentCommand();
command.setDepartmentId(String.valueOf(d.getId()));
command.setName(d.getName());
command.setNameEn(d.getName_en());
String leader = String.join(",", d.getDepartment_leader());
if (leader.length() > 255) {
leader = leader.substring(0, 255);
}
entity.setDepartmentLeader(leader);
entity.setParentid(String.valueOf(d.getParentid()));
entity.setAppid(appid);
entity.setEnable("1");
return entity;
command.setDepartmentLeader(leader);
command.setParentid(String.valueOf(d.getParentid()));
command.setAppid(appid);
command.setEnable("1");
command.setCreatorId(0L);
command.setCreateTime(new Date());
command.setUpdaterId(0L);
command.setUpdateTime(new Date());
command.setDeleted(false);
return command;
})
.collect(Collectors.toList());
departmentApplicationService.batchInsertDepartments(newDeptList);
for (AddDepartmentCommand command : newDeptList) {
log.info("syncDepartmentInfo new: {}", JSONUtil.toJsonStr(command));
departmentApplicationService.addDepartment(command);
}
}
if (!toUpdate.isEmpty()) {
departmentApplicationService.batchUpdateDepartments(toUpdate);
List<UpdateDepartmentCommand> updateDeptList = toUpdate.stream()
.map(d -> {
UpdateDepartmentCommand command = new UpdateDepartmentCommand();
BeanUtils.copyProperties(d, command);
return command;
})
.collect(Collectors.toList());
log.info("syncDepartmentInfo updateDeptList: {}", updateDeptList);
for (UpdateDepartmentCommand command : updateDeptList) {
departmentApplicationService.updateDepartment(command);
}
}
if (!toRemove.isEmpty()) {
departmentApplicationService.batchDeleteDepartments(
BulkOperationCommand<Integer> command = new BulkOperationCommand<>(
toRemove.stream().map(QyDepartmentEntity::getId).collect(Collectors.toList())
);
log.info("syncDepartmentInfo command: {}", command);
departmentApplicationService.deleteDepartment(command);
}
}
} catch (Exception e) {
log.error("syncDepartmentInfo error", e);
}
}
}

View File

@ -1,8 +1,13 @@
package com.agileboot.api.controller;
import com.agileboot.infrastructure.cache.RedisUtil;
import java.util.Date;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@ -12,8 +17,44 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/api/monitor")
public class MonitorController {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private String redisPort;
@Autowired
private RedisUtil redisUtil;
@GetMapping("/time")
public String getTime() {
return "version 0.0.1 time:" + String.valueOf(new Date().getTime());
}
@GetMapping("/redisIp")
public String getRedisIp() {
return "Redis服务ip:" + redisHost + " 端口:" + redisPort;
}
@GetMapping("/testRedis")
public String testRedisConnection() {
try {
String testKey = "shop-back-end:test";
long timestamp = new Date().getTime();
// 写入测试值
redisUtil.setCacheObject(testKey, timestamp);
// 读取测试值
Long storedValue = redisUtil.getCacheObject(testKey);
if (storedValue != null && storedValue == timestamp) {
return "Redis连接正常读写测试通过";
}
return "Redis读写测试异常读取值与写入值不匹配";
} catch (RedisConnectionFailureException e) {
return "Redis连接失败" + e.getMessage();
} catch (Exception e) {
return "Redis操作异常" + e.getMessage();
}
}
}

View File

@ -0,0 +1,32 @@
package com.agileboot.api.controller;
import com.agileboot.domain.mqtt.BCCCalculator;
import com.agileboot.domain.mqtt.MqttService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/mqtt")
@CrossOrigin(origins = "*", allowedHeaders = "*")
@RequiredArgsConstructor
@Slf4j
public class MqttController {
private final MqttService mqttService;
@GetMapping("/opencell")
public String opencell(@RequestParam String data) {
try {
mqttService.publish(data);
return "success";
} catch (Exception e) {
log.error("getPermanentCode error", e);
return e.getLocalizedMessage();
}
}
}

View File

@ -2,7 +2,10 @@ package com.agileboot.api.controller;
import com.agileboot.common.core.base.BaseController;
import com.agileboot.domain.shop.order.dto.CreateOrderResult;
import com.agileboot.domain.shop.order.dto.GetOrdersByOpenIdDTO;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.agileboot.common.core.dto.ResponseDTO;
@ -30,4 +33,16 @@ public class OrderController extends BaseController {
CreateOrderResult result = orderApplicationService.createOrder(command);
return ResponseDTO.ok(result);
}
@PostMapping("/openCabinet/{orderId}/{orderGoodsId}")
public ResponseDTO<?> openCabinet(@PathVariable Long orderId, @PathVariable Long orderGoodsId) {
orderApplicationService.openOrderGoodsCabinet(orderId, orderGoodsId);
return ResponseDTO.ok();
}
@GetMapping("/user/{openid}")
public ResponseDTO<GetOrdersByOpenIdDTO> getOrdersByOpenId(@PathVariable String openid) {
GetOrdersByOpenIdDTO result = orderApplicationService.getOrdersByOpenId(openid);
return ResponseDTO.ok(result);
}
}

View File

@ -9,6 +9,7 @@ import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.common.exception.error.ErrorCode.Client;
import com.agileboot.common.utils.OpenSignUtil;
import com.agileboot.domain.shop.order.OrderApplicationService;
import com.agileboot.domain.shop.payment.dto.PaymentCallbackRequest;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
@ -16,6 +17,7 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.*;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.CrossOrigin;
@ -34,9 +36,12 @@ import org.springframework.web.client.RestTemplate;
@RequiredArgsConstructor
@RequestMapping("/api/payment")
public class PaymentController {
private final OrderApplicationService orderApplicationService;
// 新增回调接口
@PostMapping("/callback")
public String paymentCallback(HttpServletRequest request, @RequestBody String requestBody) {
log.info("支付回调requestBody{}", requestBody);
try {
// 1. 参数解析
PaymentCallbackRequest callbackReq = parseCallbackRequest(requestBody);
@ -47,16 +52,21 @@ public class PaymentController {
boolean signValid = OpenSignUtil.checkOpenSign(appKey, callbackReq.getSign(), requestBody);
if (!signValid) {
log.error("签名验证失败:{}", requestBody);
log.error("支付回调签名验证失败:{}", requestBody);
return "fail";
}
// 3. 业务处理需要实现幂等性校验
handlePaymentSuccess(
bizContent.getBiz_order_id(),
bizContent.getTotal_amount(),
bizContent.getTrade_id()
);
if (bizContent.getTrade_status().equals("SUCCESS")) {
// 3. 业务处理需要实现幂等性校验
handlePaymentSuccess(
bizContent.getBiz_order_id(),
bizContent.getTotal_amount(),
bizContent.getTrade_id(),
bizContent.getTrade_pay_time()
);
} else {
log.error("支付订单失败requestBody{}", requestBody);
}
return "success";
} catch (Exception e) {
@ -113,7 +123,12 @@ public class PaymentController {
return "wxshop202503081132";
}
private void handlePaymentSuccess(String bizOrderId, Integer amount, String tradeId) {
private void handlePaymentSuccess(String bizOrderId, Integer amount, String tradeId, String tradePayTime) {
// 实现订单状态更新和幂等性校验
if (StringUtils.isNotBlank(bizOrderId) && bizOrderId.startsWith("wxshop-")) {
// 订单号格式为 wxshop-1-time提取中间的订单号
String orderId = bizOrderId.split("-")[1];
orderApplicationService.handlePaymentSuccess(Long.valueOf(orderId), amount, tradeId, tradePayTime);
}
}
}

View File

@ -1,6 +1,7 @@
package com.agileboot.api.controller;
import com.agileboot.api.response.ShopGoodsResponse;
import com.agileboot.common.constant.WeixinConstants;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.domain.shop.category.CategoryApplicationService;
import com.agileboot.domain.shop.category.dto.ShopCategoryDTO;
@ -12,6 +13,7 @@ import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.view.RedirectView;
@RestController
@RequestMapping("/api/shop")
@ -25,10 +27,27 @@ public class ShopController {
public ResponseDTO<ShopGoodsResponse> getShopGoodsInfo() {
// 获取商品列表
List<ShopGoodsEntity> goodsList = goodsApplicationService.getGoodsAll();
goodsList.forEach(goods -> {
// 最多只能购买一件
if (!goods.getStock().equals(0)) {
goods.setStock(1);
}
});
// 获取分类列表
List<ShopCategoryDTO> categoryList = categoryApplicationService.getCategoryAll();
return ResponseDTO.ok(new ShopGoodsResponse(goodsList, categoryList, "0"));
}
@GetMapping("/wechatAuth")
public RedirectView wechatAuthRedirect() {
String authUrl = "https://open.weixin.qq.com/connect/oauth2/authorize"
+ "?appid=" + WeixinConstants.appid
+ "&redirect_uri=http%3A%2F%2Fwxshop.ab98.cn%2Fshop"
+ "&response_type=code"
+ "&scope=snsapi_base"
+ "&state=STATE#wechat_redirect";
return new RedirectView(authUrl);
}
}

View File

@ -1,6 +1,8 @@
package com.agileboot.common.constant;
public class WeixinConstants {
public static String appid = "wx04e357a5a0900f24";
public static String secret = "2a5a8b6ad3654a05f9fdd36524279a50";
// public static String appid = "wx04e357a5a0900f24";
// public static String secret = "2a5a8b6ad3654a05f9fdd36524279a50";
public static String appid = "wx9922dfbb0d4cd7bb";
public static String secret = "7c7ef0dbb90b6be2abc8c269357f980a";
}

View File

@ -1,16 +1,19 @@
package com.agileboot.common.utils;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.Md5Crypt;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.stream.Collectors;
import static cn.hutool.crypto.SecureUtil.md5;
@ -38,10 +41,20 @@ public class OpenSignUtil {
return Objects.equals(generateSign, sign);
}
private static List<String> getKVList(Map<String, String> sortedParams) {
return sortedParams.entrySet().stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.toList());
public static List<String> getKVList(Map<String, String> params) {
if(MapUtils.isEmpty(params)) {
return Collections.emptyList();
}
List<String> kvPairs = new ArrayList<>();
for(String key : params.keySet()) {
String value = params.get(key);
if(value == null) {
continue;
}
String kv = key + "=" + value;
kvPairs.add(kv);
}
return kvPairs;
}
public static String openSign(String appKey, Map<String, String> params) {
@ -54,10 +67,22 @@ public class OpenSignUtil {
sourceText = sourceText + "&app_key=" + appKey;
log.info("sourceText:{}", sourceText);
try {
return md5(Arrays.toString(sourceText.getBytes(StandardCharsets.UTF_8)));
// MessageDigest md = MessageDigest.getInstance("MD5");
// String res = new String(md.digest(sourceText.getBytes("utf-8")));
String res = md5(sourceText);
log.info("md5:{}", res);
return res;
} catch (Exception e) {
return null;
}
}
public static void main(String[] args) {
String body = "biz_content=%7B%22uid%22%3A%22174246"
+ "2448492%22%2C%22trade_id%22%3A%221063669415%22%2C%22total_amount%22%3A1%2C%22extra%22%3A%22%22%2C%22trade_pay_time%22%3A%222025-03-20+17%3A20%3A55%22%2C%22trade_status%22%3A%22SUCCESS%22%2C%22pay_type%22%3A%22116%22%2C%22callback_content%22%3A%22%3Cxml%3E%3Cappid%3E%3C%21%5BCDATA%5Bwx9922dfbb0d4cd7bb%5D%5D%3E%3C%2Fappid%3E%5Cn%3Cattach%3E%3C%21%5BCDATA%5B%257B%2522biz_id%2522%253A%2522wxshop%2522%252C%2522trade_id%2522%253A%25221063669415%2522%257D%5D%5D%3E%3C%2Fattach%3E%5Cn%3Cbank_type%3E%3C%21%5BCDATA%5BOTHERS%5D%5D%3E%3C%2Fbank_type%3E%5Cn%3Ccash_fee%3E%3C%21%5BCDATA%5B1%5D%5D%3E%3C%2Fcash_fee%3E%5Cn%3Cfee_type%3E%3C%21%5BCDATA%5BCNY%5D%5D%3E%3C%2Ffee_type%3E%5Cn%3Cis_subscribe%3E%3C%21%5BCDATA%5BN%5D%5D%3E%3C%2Fis_subscribe%3E%5Cn%3Cmch_id%3E%3C%21%5BCDATA%5B1625101806%5D%5D%3E%3C%2Fmch_id%3E%5Cn%3Cnonce_str%3E%3C%21%5BCDATA%5B24NffiTHxNYm0ppw3QE9WezmzJQDnJQV%5D%5D%3E%3C%2Fnonce_str%3E%5Cn%3Copenid%3E%3C%21%5BCDATA%5BoMRxw6Eum0DB1IjI_pEX_yrawBHw%5D%5D%3E%3C%2Fopenid%3E%5Cn%3Cout_trade_no%3E%3C%21%5BCDATA"
+ "%5Bwxshop-10-1742462448212%5D%5D%3E%3C%2Fout_trade_no%3E%5Cn%3Cresult_code%3E%3C%21%5BCDATA%5BSUCCESS%5D%5D%3E%3C%2Fresult_code%3E%5Cn%3Creturn_code%3E%3C%21%5BCDATA%5BSUCCESS%5D%5D%3E%3C%2Freturn_code%3E%5Cn%3Csign%3E%3C%21%5BCDATA%5B9CE8A123437E591166DDAF92A750C122%5D%5D%3E%3C%2Fsign%3E%5Cn%3Ctime_end%3E%3C%21%5BCDATA%5B20250320172055%5D%5D%3E%3C%2Ftime_end%3E%5Cn%3Ctotal_fee%3E1%3C%2Ftotal_fee%3E%5Cn%3Ctrade_type%3E%3C%21%5BCDATA%5BJSAPI%5D%5D%3E%3C%2Ftrade_type%3E%5Cn%3Ctransaction_id%3E%3C%21%5BCDATA%5B4200002695202503209706830758%5D%5D%3E%3C%2Ftransaction_id%3E%5Cn%3C%2Fxml%3E%22%2C%22title%22%3A%22%E5%95%86%E5%93%81%E8%AE%A2%E5%8D%95%E6%94%AF%E4%BB%98%22%2C%22biz_order_id%22%3A%22wxshop-10-1742462448212%22%7D&nonce_str=6d80bd6b542a499994adf4ee4c1e89c4&sign=f1ffc9cfc61cf60d8a1acbac0399cd0b&biz_id=wxshop&sign_type=MD5&version=1.0×tamp=1742462460390";
Boolean res = checkOpenSign("wxshop202503081132", "f1ffc9cfc61cf60d8a1acbac0399cd0b", body);
log.info("res:{}", res);
}
}

View File

@ -35,6 +35,11 @@
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
</dependencies>

View File

@ -0,0 +1,72 @@
package com.agileboot.domain.cabinet.cell;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.domain.common.command.BulkOperationCommand;
import com.agileboot.domain.cabinet.cell.command.AddCabinetCellCommand;
import com.agileboot.domain.cabinet.cell.command.UpdateCabinetCellCommand;
import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity;
import com.agileboot.domain.cabinet.cell.db.CabinetCellService;
import com.agileboot.domain.cabinet.cell.dto.CabinetCellDTO;
import com.agileboot.domain.cabinet.cell.model.CabinetCellModel;
import com.agileboot.domain.cabinet.cell.model.CabinetCellModelFactory;
import com.agileboot.domain.cabinet.cell.query.SearchCabinetCellQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
@Slf4j
@Service
@RequiredArgsConstructor
public class CabinetCellApplicationService {
private final CabinetCellService cabinetCellService;
private final CabinetCellModelFactory cabinetCellModelFactory;
public PageDTO<CabinetCellDTO> getCabinetCellList(SearchCabinetCellQuery<CabinetCellEntity> query) {
Page<CabinetCellEntity> page = cabinetCellService.getCellList(query);
List<CabinetCellDTO> dtoList = page.getRecords().stream()
.map(CabinetCellDTO::new)
.collect(Collectors.toList());
return new PageDTO<>(dtoList, page.getTotal());
}
public void addCabinetCell(AddCabinetCellCommand command) {
CabinetCellModel model = cabinetCellModelFactory.create();
model.loadAddCommand(command);
model.insert();
}
public void updateCabinetCell(UpdateCabinetCellCommand command) {
CabinetCellModel model = cabinetCellModelFactory.loadById(command.getCellId());
model.loadUpdateCommand(command);
model.updateById();
}
public void deleteCabinetCell(BulkOperationCommand<Long> command) {
for (Long cellId : command.getIds()) {
CabinetCellModel model = cabinetCellModelFactory.loadById(cellId);
model.deleteById();
}
}
public List<CabinetCellEntity> selectAll() {
return cabinetCellService.selectAll();
}
public void configureGoodsCells(Long cellId, Long goodsId) {
List<CabinetCellEntity> cells = cabinetCellService.selectByGoodsId(goodsId);
for (CabinetCellEntity cell : cells) {
cell.setGoodsId(null);
cell.setUsageStatus(1);
cell.updateById();
}
CabinetCellModel model = cabinetCellModelFactory.loadById(cellId);
model.setGoodsId(goodsId);
model.setUsageStatus(2);
model.updateById();
}
}

View File

@ -0,0 +1,11 @@
package com.agileboot.domain.cabinet.cell.command;
import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class AddCabinetCellCommand extends CabinetCellEntity {
}

View File

@ -0,0 +1,16 @@
package com.agileboot.domain.cabinet.cell.command;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class UpdateCabinetCellCommand extends AddCabinetCellCommand {
@NotNull
@PositiveOrZero
private Long cellId;
}

View File

@ -0,0 +1,68 @@
package com.agileboot.domain.cabinet.cell.db;
import com.agileboot.common.core.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 柜机格口信息表
* </p>
*
* @author valarchie
* @since 2025-03-17
*/
@Getter
@Setter
@TableName("cabinet_cell")
@ApiModel(value = "CabinetCellEntity对象", description = "柜机格口信息表")
public class CabinetCellEntity extends BaseEntity<CabinetCellEntity> {
private static final long serialVersionUID = 1L;
@ApiModelProperty("格口唯一ID")
@TableId(value = "cell_id", type = IdType.AUTO)
private Long cellId;
@ApiModelProperty("关联柜机ID")
@TableField("cabinet_id")
private Long cabinetId;
@ApiModelProperty("格口号")
@TableField("cell_no")
private Integer cellNo;
@ApiModelProperty("针脚序号")
@TableField("pin_no")
private Integer pinNo;
@ApiModelProperty("格口类型1小格 2中格 3大格 4超大格")
@TableField("cell_type")
private Integer cellType;
@ApiModelProperty("使用状态1空闲 2已占用")
@TableField("usage_status")
private Integer usageStatus;
@ApiModelProperty("可用状态1正常 2故障")
@TableField("available_status")
private Integer availableStatus;
@ApiModelProperty("关联商品ID")
@TableField("goods_id")
private Long goodsId;
@Override
public Serializable pkVal() {
return this.cellId;
}
}

View File

@ -0,0 +1,47 @@
package com.agileboot.domain.cabinet.cell.db;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/**
* <p>
* 柜机格口信息表 Mapper 接口
* </p>
*
* @author valarchie
* @since 2025-03-17
*/
public interface CabinetCellMapper extends BaseMapper<CabinetCellEntity> {
@Select("SELECT * " +
"FROM cabinet_cell " +
"${ew.customSqlSegment}")
Page<CabinetCellEntity> getCellList(
Page<CabinetCellEntity> page,
@Param(Constants.WRAPPER) Wrapper<CabinetCellEntity> queryWrapper
);
@Select("SELECT * " +
"FROM cabinet_cell " +
"WHERE available_status = 1 " +
"ORDER BY cell_id DESC " +
"LIMIT 1")
CabinetCellEntity selectFirstEnabledCell();
@Select("SELECT * " +
"FROM cabinet_cell " +
"WHERE available_status = 1 " +
"ORDER BY cell_id DESC")
List<CabinetCellEntity> selectAll();
@Select("SELECT * " +
"FROM cabinet_cell " +
"WHERE goods_id = #{goodsId} " +
"ORDER BY cell_id DESC")
List<CabinetCellEntity> selectByGoodsId(@Param("goodsId")Long goodsId);
}

View File

@ -0,0 +1,23 @@
package com.agileboot.domain.cabinet.cell.db;
import com.agileboot.common.core.page.AbstractPageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* <p>
* 柜机格口信息表 服务类
* </p>
*
* @author valarchie
* @since 2025-03-17
*/
public interface CabinetCellService extends IService<CabinetCellEntity> {
Page<CabinetCellEntity> getCellList(AbstractPageQuery<CabinetCellEntity> query);
List<CabinetCellEntity> selectAll();
List<CabinetCellEntity> selectByGoodsId(Long goodsId);
}

View File

@ -0,0 +1,36 @@
package com.agileboot.domain.cabinet.cell.db;
import com.agileboot.common.core.page.AbstractPageQuery;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.List;
import org.springframework.stereotype.Service;
/**
* <p>
* 柜机格口信息表 服务实现类
* </p>
*
* @author valarchie
* @since 2025-03-17
*/
@Service
public class CabinetCellServiceImpl extends ServiceImpl<CabinetCellMapper, CabinetCellEntity> implements CabinetCellService {
@Override
public Page<CabinetCellEntity> getCellList(AbstractPageQuery<CabinetCellEntity> query) {
return baseMapper.getCellList(query.toPage(), query.toQueryWrapper());
}
@Override
public List<CabinetCellEntity> selectAll() {
LambdaQueryWrapper<CabinetCellEntity> wrapper = new LambdaQueryWrapper<>();
return this.list(wrapper);
}
@Override
public List<CabinetCellEntity> selectByGoodsId(Long goodsId) {
return baseMapper.selectByGoodsId(goodsId);
}
}

View File

@ -0,0 +1,78 @@
package com.agileboot.domain.cabinet.cell.dto;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.common.annotation.ExcelColumn;
import com.agileboot.common.annotation.ExcelSheet;
import com.agileboot.domain.common.cache.CacheCenter;
import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity;
import com.agileboot.domain.system.user.db.SysUserEntity;
import lombok.Data;
@ExcelSheet(name = "柜机格口信息列表")
@Data
public class CabinetCellDTO {
public CabinetCellDTO(CabinetCellEntity entity) {
if (entity != null) {
BeanUtil.copyProperties(entity, this);
/* SysUserEntity creator = CacheCenter.userCache.getObjectById(entity.getOperId());
if (creator != null) {
this.operator = creator.getUsername();
}*/
}
}
@ExcelColumn(name = "格口ID")
private Long cellId;
@ExcelColumn(name = "关联柜机ID")
private Long cabinetId;
@ExcelColumn(name = "格口号")
private Integer cellNo;
@ExcelColumn(name = "针脚序号")
private Integer pinNo;
@ExcelColumn(name = "格口类型")
private Integer cellType;
@ExcelColumn(name = "使用状态")
private Integer usageStatus;
@ExcelColumn(name = "可用状态")
private Integer availableStatus;
@ExcelColumn(name = "关联商品ID")
private Long goodsId;
@ExcelColumn(name = "操作人")
private String operator;
private String convertUsageStatus(Integer status) {
switch (status) {
case 1: return "空闲";
case 2: return "已占用";
default: return "未知";
}
}
private String convertAvailableStatus(Integer status) {
switch (status) {
case 1: return "正常";
case 2: return "故障";
default: return "未知";
}
}
private String convertCellType(Integer type) {
switch (type) {
case 1: return "小格";
case 2: return "中格";
case 3: return "大格";
case 4: return "超大格";
default: return "未知类型";
}
}
}

View File

@ -0,0 +1,41 @@
package com.agileboot.domain.cabinet.cell.model;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.cabinet.cell.command.AddCabinetCellCommand;
import com.agileboot.domain.cabinet.cell.command.UpdateCabinetCellCommand;
import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity;
import com.agileboot.domain.cabinet.cell.db.CabinetCellService;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class CabinetCellModel extends CabinetCellEntity {
private CabinetCellService cabinetCellService;
public CabinetCellModel(CabinetCellEntity entity, CabinetCellService cabinetCellService) {
this(cabinetCellService);
if (entity != null) {
BeanUtil.copyProperties(entity, this);
}
}
public CabinetCellModel(CabinetCellService cabinetCellService) {
this.cabinetCellService = cabinetCellService;
}
public void loadAddCommand(AddCabinetCellCommand command) {
if (command != null) {
BeanUtil.copyProperties(command, this, "cellId");
}
}
public void loadUpdateCommand(UpdateCabinetCellCommand command) {
if (command != null) {
loadAddCommand(command);
}
}
}

View File

@ -0,0 +1,27 @@
package com.agileboot.domain.cabinet.cell.model;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity;
import com.agileboot.domain.cabinet.cell.db.CabinetCellService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class CabinetCellModelFactory {
private final CabinetCellService cabinetCellService;
public CabinetCellModel loadById(Long cellId) {
CabinetCellEntity entity = cabinetCellService.getById(cellId);
if (entity == null) {
throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, cellId, "柜机格口");
}
return new CabinetCellModel(entity, cabinetCellService);
}
public CabinetCellModel create() {
return new CabinetCellModel(cabinetCellService);
}
}

View File

@ -0,0 +1,40 @@
package com.agileboot.domain.cabinet.cell.query;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.page.AbstractPageQuery;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class SearchCabinetCellQuery<T> extends AbstractPageQuery<T> {
private Long cellId;
private Long cabinetId;
private Integer cellNo;
private Integer cellType;
private Integer usageStatus;
private Integer availableStatus;
private Date startTime;
private Date endTime;
@Override
public QueryWrapper<T> addQueryCondition() {
QueryWrapper<T> queryWrapper = new QueryWrapper<>();
queryWrapper
.eq(cellId != null, "cell_id", cellId)
.eq(cabinetId != null, "cabinet_id", cabinetId)
.eq(cellNo != null, "cell_no", cellNo)
.eq(cellType != null, "cell_type", cellType)
.eq(usageStatus != null, "usage_status", usageStatus)
.eq(availableStatus != null, "available_status", availableStatus)
.between(startTime != null && endTime != null, "create_time", startTime, endTime);
this.timeRangeColumn = "create_time";
return queryWrapper;
}
}

View File

@ -0,0 +1,55 @@
package com.agileboot.domain.cabinet.smartCabinet;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.domain.common.command.BulkOperationCommand;
import com.agileboot.domain.cabinet.smartCabinet.command.AddSmartCabinetCommand;
import com.agileboot.domain.cabinet.smartCabinet.command.UpdateSmartCabinetCommand;
import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetEntity;
import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetService;
import com.agileboot.domain.cabinet.smartCabinet.dto.SmartCabinetDTO;
import com.agileboot.domain.cabinet.smartCabinet.model.SmartCabinetModel;
import com.agileboot.domain.cabinet.smartCabinet.model.SmartCabinetModelFactory;
import com.agileboot.domain.cabinet.smartCabinet.query.SearchSmartCabinetQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class SmartCabinetApplicationService {
private final SmartCabinetService smartCabinetService;
private final SmartCabinetModelFactory smartCabinetModelFactory;
public PageDTO<SmartCabinetDTO> getSmartCabinetList(SearchSmartCabinetQuery<SmartCabinetEntity> query) {
Page<SmartCabinetEntity> page = smartCabinetService.getCabinetList(query);
List<SmartCabinetDTO> dtoList = page.getRecords().stream()
.map(SmartCabinetDTO::new)
.collect(Collectors.toList());
return new PageDTO<>(dtoList, page.getTotal());
}
public void addSmartCabinet(AddSmartCabinetCommand command) {
SmartCabinetModel model = smartCabinetModelFactory.create();
model.loadAddCommand(command);
model.insert();
}
public void updateSmartCabinet(UpdateSmartCabinetCommand command) {
SmartCabinetModel model = smartCabinetModelFactory.loadById(command.getCabinetId());
model.loadUpdateCommand(command);
model.updateById();
}
public void deleteSmartCabinet(BulkOperationCommand<Long> command) {
for (Long cabinetId : command.getIds()) {
SmartCabinetModel model = smartCabinetModelFactory.loadById(cabinetId);
model.deleteById();
}
}
public List<SmartCabinetEntity> selectAll() {
return smartCabinetService.selectAll();
}
}

View File

@ -0,0 +1,11 @@
package com.agileboot.domain.cabinet.smartCabinet.command;
import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class AddSmartCabinetCommand extends SmartCabinetEntity {
}

View File

@ -0,0 +1,15 @@
package com.agileboot.domain.cabinet.smartCabinet.command;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class UpdateSmartCabinetCommand extends AddSmartCabinetCommand {
@NotNull
@PositiveOrZero
private Long cabinetId;
}

View File

@ -0,0 +1,60 @@
package com.agileboot.domain.cabinet.smartCabinet.db;
import com.agileboot.common.core.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 智能柜信息表
* </p>
*
* @author valarchie
* @since 2025-03-17
*/
@Getter
@Setter
@TableName("smart_cabinet")
@ApiModel(value = "SmartCabinetEntity对象", description = "智能柜信息表")
public class SmartCabinetEntity extends BaseEntity<SmartCabinetEntity> {
private static final long serialVersionUID = 1L;
@ApiModelProperty("柜机唯一ID")
@TableId(value = "cabinet_id", type = IdType.AUTO)
private Long cabinetId;
@ApiModelProperty("柜机名称")
@TableField("cabinet_name")
private String cabinetName;
@ApiModelProperty("柜机类型0主柜 1副柜")
@TableField("cabinet_type")
private Integer cabinetType;
@ApiModelProperty("柜机模版编号")
@TableField("template_no")
private String templateNo;
@ApiModelProperty("锁控板序号")
@TableField("lock_control_no")
private Integer lockControlNo;
@ApiModelProperty("柜机位置")
@TableField("location")
private Integer location;
@Override
public Serializable pkVal() {
return this.cabinetId;
}
}

View File

@ -0,0 +1,50 @@
package com.agileboot.domain.cabinet.smartCabinet.db;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* <p>
* 智能柜信息表 Mapper 接口
* </p>
*
* @author valarchie
* @since 2025-03-17
*/
public interface SmartCabinetMapper extends BaseMapper<SmartCabinetEntity> {
@Select("SELECT cabinet_id, cabinet_name, cabinet_type, template_no, lock_control_no, location " +
"FROM smart_cabinet " +
"${ew.customSqlSegment}")
Page<SmartCabinetEntity> getCabinetList(
Page<SmartCabinetEntity> page,
@Param(Constants.WRAPPER) Wrapper<SmartCabinetEntity> queryWrapper
);
@Select("SELECT * " +
"FROM smart_cabinet " +
"WHERE cabinet_type = '0' " +
"ORDER BY create_time DESC " +
"LIMIT 1")
SmartCabinetEntity selectFirstEnabledCabinet();
@Select("SELECT * " +
"FROM smart_cabinet " +
"WHERE cabinet_type = '0' " +
"ORDER BY create_time DESC")
List<SmartCabinetEntity> selectAllEnabled();
@Select("SELECT * FROM smart_cabinet WHERE template_no = #{templateNo} LIMIT 1")
SmartCabinetEntity selectByTemplateNo(String templateNo);
@Select("SELECT * FROM smart_cabinet WHERE cabinet_name = #{cabinetName} LIMIT 1")
SmartCabinetEntity selectByCabinetName(String cabinetName);
// 新增根据柜体ID查询的方法
@Select("SELECT * FROM smart_cabinet WHERE cabinet_id = #{cabinetId} LIMIT 1")
SmartCabinetEntity selectByCabinetId(Long cabinetId);
}

View File

@ -0,0 +1,26 @@
package com.agileboot.domain.cabinet.smartCabinet.db;
import com.agileboot.common.core.page.AbstractPageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* <p>
* 智能柜信息表 服务类
* </p>
*
* @author valarchie
* @since 2025-03-17
*/
public interface SmartCabinetService extends IService<SmartCabinetEntity> {
Page<SmartCabinetEntity> getCabinetList(AbstractPageQuery<SmartCabinetEntity> query);
List<SmartCabinetEntity> selectAll();
SmartCabinetEntity getFirstEnabledCabinet();
SmartCabinetEntity getByCabinetId(Long cabinetId);
}

View File

@ -0,0 +1,42 @@
package com.agileboot.domain.cabinet.smartCabinet.db;
import com.agileboot.common.core.page.AbstractPageQuery;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.List;
import org.springframework.stereotype.Service;
/**
* <p>
* 智能柜信息表 服务实现类
* </p>
*
* @author valarchie
* @since 2025-03-17
*/
@Service
public class SmartCabinetServiceImpl extends ServiceImpl<SmartCabinetMapper, SmartCabinetEntity> implements SmartCabinetService {
@Override
public Page<SmartCabinetEntity> getCabinetList(AbstractPageQuery<SmartCabinetEntity> query) {
return this.page(query.toPage(), query.toQueryWrapper());
}
@Override
public List<SmartCabinetEntity> selectAll() {
LambdaQueryWrapper<SmartCabinetEntity> wrapper = new LambdaQueryWrapper<>();
return this.list(wrapper);
}
@Override
public SmartCabinetEntity getFirstEnabledCabinet() {
return baseMapper.selectFirstEnabledCabinet();
}
@Override
public SmartCabinetEntity getByCabinetId(Long cabinetId) {
return baseMapper.selectByCabinetId(cabinetId);
}
}

View File

@ -0,0 +1,17 @@
package com.agileboot.domain.cabinet.smartCabinet.dto;
import com.agileboot.domain.cabinet.cell.dto.CabinetCellDTO;
import java.util.List;
import lombok.Data;
@Data
public class AllCabinetDataDTO {
private List<SmartCabinetDTO> smartCabinetList;
private List<CabinetCellDTO> cells;
public AllCabinetDataDTO(List<SmartCabinetDTO> cabinets, List<CabinetCellDTO> cells) {
this.smartCabinetList = cabinets;
this.cells = cells;
}
}

View File

@ -0,0 +1,46 @@
package com.agileboot.domain.cabinet.smartCabinet.dto;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.common.annotation.ExcelColumn;
import com.agileboot.common.annotation.ExcelSheet;
import com.agileboot.domain.common.cache.CacheCenter;
import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetEntity;
import com.agileboot.domain.system.user.db.SysUserEntity;
import lombok.Data;
@ExcelSheet(name = "智能柜信息表")
@Data
public class SmartCabinetDTO {
public SmartCabinetDTO(SmartCabinetEntity entity) {
if (entity != null) {
BeanUtil.copyProperties(entity, this);
/* SysUserEntity creator = CacheCenter.userCache.getObjectById(entity.getOperId());
if (creator != null) {
this.operator = creator.getUsername();
}*/
}
}
@ExcelColumn(name = "柜机唯一ID")
private Long cabinetId;
@ExcelColumn(name = "柜机名称")
private String cabinetName;
@ExcelColumn(name = "柜机类型0主柜 1副柜")
private Integer cabinetType;
@ExcelColumn(name = "柜机模版编号")
private String templateNo;
@ExcelColumn(name = "锁控板序号")
private Integer lockControlNo;
@ExcelColumn(name = "柜机位置")
private Integer location;
@ExcelColumn(name = "操作人")
private String operator;
}

View File

@ -0,0 +1,39 @@
package com.agileboot.domain.cabinet.smartCabinet.model;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.domain.cabinet.smartCabinet.command.AddSmartCabinetCommand;
import com.agileboot.domain.cabinet.smartCabinet.command.UpdateSmartCabinetCommand;
import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetEntity;
import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetService;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class SmartCabinetModel extends SmartCabinetEntity {
private SmartCabinetService smartCabinetService;
public SmartCabinetModel(SmartCabinetEntity entity, SmartCabinetService smartCabinetService) {
this(smartCabinetService);
if (entity != null) {
BeanUtil.copyProperties(entity, this);
}
}
public SmartCabinetModel(SmartCabinetService smartCabinetService) {
this.smartCabinetService = smartCabinetService;
}
public void loadAddCommand(AddSmartCabinetCommand command) {
if (command != null) {
BeanUtil.copyProperties(command, this, "cabinetId");
}
}
public void loadUpdateCommand(UpdateSmartCabinetCommand command) {
if (command != null) {
loadAddCommand(command);
}
}
}

View File

@ -0,0 +1,27 @@
package com.agileboot.domain.cabinet.smartCabinet.model;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetEntity;
import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class SmartCabinetModelFactory {
private final SmartCabinetService smartCabinetService;
public SmartCabinetModel loadById(Long cabinetId) {
SmartCabinetEntity entity = smartCabinetService.getById(cabinetId);
if (entity == null) {
throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, cabinetId, "智能柜");
}
return new SmartCabinetModel(entity, smartCabinetService);
}
public SmartCabinetModel create() {
return new SmartCabinetModel(smartCabinetService);
}
}

View File

@ -0,0 +1,34 @@
package com.agileboot.domain.cabinet.smartCabinet.query;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.page.AbstractPageQuery;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class SearchSmartCabinetQuery<T> extends AbstractPageQuery<T> {
private String cabinetName;
private Integer cabinetType;
private String templateNo;
private Date startTime;
private Date endTime;
@Override
public QueryWrapper<T> addQueryCondition() {
QueryWrapper<T> queryWrapper = new QueryWrapper<>();
queryWrapper
.like(StrUtil.isNotEmpty(cabinetName), "cabinet_name", cabinetName)
.eq(cabinetType != null, "cabinet_type", cabinetType)
.eq(StrUtil.isNotEmpty(templateNo), "template_no", templateNo)
.between(startTime != null && endTime != null, "create_time", startTime, endTime);
this.timeRangeColumn = "create_time";
return queryWrapper;
}
}

View File

@ -0,0 +1,65 @@
package com.agileboot.domain.mqtt;
public class BCCCalculator {
/**
* 计算十六进制字符串的BCC校验位异或校验
* @param hexData 输入的十六进制字符串 "8A010011"
* @return BCC校验位的十六进制字符串 "9A"
*/
public static String calculateBCC(String hexData) {
// 检查输入合法性
if (hexData == null || hexData.isEmpty()) {
throw new IllegalArgumentException("输入不能为空");
}
if (hexData.length() % 2 != 0) {
throw new IllegalArgumentException("输入的十六进制长度必须为偶数");
}
// 将十六进制字符串转换为字节数组
byte[] bytes = hexStringToByteArray(hexData);
// 计算异或校验
byte bcc = 0x00; // 初始值
for (byte b : bytes) {
bcc ^= b;
}
// 将校验位转换为十六进制字符串
return String.format("%02X", bcc & 0xFF);
}
/**
* 将十六进制字符串转换为字节数组
*/
public static byte[] hexStringToByteArray(String hex) {
int len = hex.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
// 处理每两个字符为一个字节兼容大小写
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i + 1), 16));
}
return data;
}
/**
* 将字节数组转换为十六进制字符串
* @param bytes
* @return
*/
public static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
public static void main(String[] args) {
// 示例计算 "8A010011" 的BCC校验位
String hexData = "8A010111";
String bcc = calculateBCC(hexData);
System.out.println("BCC校验位: " + bcc); // 输出 9A
}
}

View File

@ -0,0 +1,71 @@
package com.agileboot.domain.mqtt;
import org.eclipse.paho.client.mqttv3.*;
public class MqttDemo {
private static String serverUrl = "tcp://221.7.159.46:1883";
private static String userName = "lock";
private static String password = "lock@ys#6785$";
/**
* 将十六进制字符串转换为字节数组
* @param hex
* @return
*/
private static byte[] hexStringToBytes(String hex) {
int len = hex.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i + 1), 16));
}
return data;
}
/**
* 将字节数组转换为十六进制字符串
* @param bytes
* @return
*/
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
public static class AuthSubscriber implements MqttCallback {
@Override
public void messageArrived(String topic, MqttMessage message) {
System.out.println("收到消息: " + bytesToHex(message.getPayload()));
}
@Override public void connectionLost(Throwable cause) {}
@Override public void deliveryComplete(IMqttDeliveryToken token) {}
}
public static void main(String[] args) throws MqttException, InterruptedException {
MqttClient client = new MqttClient(serverUrl, "pub-client");
// 创建连接选项并设置账号密码
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(userName); // 用户名
options.setPassword(password.toCharArray()); // 密码需转为字符数组
// 设置订阅回调实现
client.setCallback(new AuthSubscriber());
client.connect(options); // 使用带认证的选项连接
client.subscribe("lock/up/S4202414S"); // 订阅主题
client.publish("lock/down/S4202414S", new MqttMessage(hexStringToBytes("8A0101119B"))); // 向主题发送数据
Thread.sleep(10000); // 延时,测试订阅主题收到消息
client.disconnect();
}
}

View File

@ -0,0 +1,82 @@
package com.agileboot.domain.mqtt;
import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.stereotype.Service;
import org.eclipse.paho.client.mqttv3.*;
@Slf4j
@Service
@RequiredArgsConstructor
public class MqttService implements MqttCallback {
private static final String SERVER_URL = "tcp://221.7.159.46:1883";
private static final String USERNAME = "lock";
private static final String PASSWORD = "lock@ys#6785$";
private static final String TOPIC_FILTER = "lock/up/S4202414S";
private static final String TOPIC = "lock/down/S4202414S";
private MqttClient client;
// 设置自定义消息处理器
@Setter
private MessageHandler messageHandler; // 自定义消息处理器接口
public interface MessageHandler {
void handleMessage(String topic, String hexPayload);
}
@PostConstruct
public void init() throws MqttException {
connect();
subscribe(TOPIC_FILTER);
setMessageHandler((String topic, String hexPayload) -> {
log.info("收到消息 topic: {}, hexPayload: {}", topic, hexPayload);
});
}
public void connect() throws MqttException {
client = new MqttClient(SERVER_URL, MqttClient.generateClientId(), new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(USERNAME);
options.setPassword(PASSWORD.toCharArray());
options.setCleanSession(true);
options.setAutomaticReconnect(true);
client.setCallback(this);
client.connect(options);
log.info("连接 MQTT 服务器 {}", SERVER_URL);
}
public void subscribe(String topic) throws MqttException {
client.subscribe(topic);
}
public void publish(String data) throws MqttException {
String bcc = BCCCalculator.calculateBCC(data);
MqttMessage message = new MqttMessage(BCCCalculator.hexStringToByteArray(data + bcc));
client.publish(TOPIC, message);
}
public void disconnect() throws MqttException {
client.disconnect();
}
@Override
public void messageArrived(String topic, MqttMessage message) {
if (messageHandler != null) {
String hexString = BCCCalculator.bytesToHex(message.getPayload());
messageHandler.handleMessage(topic, hexString);
}
}
@Override
public void connectionLost(Throwable cause) {
System.err.println("连接丢失: " + cause.getMessage());
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {}
}

View File

@ -20,11 +20,8 @@ public class QyAccessTokenServiceImpl extends ServiceImpl<QyAccessTokenMapper, Q
@Override
public Page<QyAccessTokenEntity> getAccessTokenList(AbstractPageQuery<QyAccessTokenEntity> query) {
LambdaQueryWrapper<QyAccessTokenEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(QyAccessTokenEntity::getEnable, "1")
.orderByDesc(QyAccessTokenEntity::getGettokenTime);
return this.page(query.toPage(), wrapper);
return this.page(query.toPage(), query.toQueryWrapper());
}
@Override

View File

@ -20,9 +20,8 @@ public class QyAuthServiceImpl extends ServiceImpl<QyAuthMapper, QyAuthEntity> i
@Override
public Page<QyAuthEntity> getAuthList(AbstractPageQuery<QyAuthEntity> query) {
LambdaQueryWrapper<QyAuthEntity> wrapper = new LambdaQueryWrapper<>();
return this.page(query.toPage(), wrapper);
return this.page(query.toPage(), query.toQueryWrapper());
}
@Override

View File

@ -20,8 +20,7 @@ public class QyAuthCorpInfoServiceImpl extends ServiceImpl<QyAuthCorpInfoMapper,
@Override
public Page<QyAuthCorpInfoEntity> getAuthCorpInfoList(AbstractPageQuery<QyAuthCorpInfoEntity> query) {
LambdaQueryWrapper<QyAuthCorpInfoEntity> wrapper = new LambdaQueryWrapper<>();
return this.page(query.toPage(), wrapper);
return this.page(query.toPage(), query.toQueryWrapper());
}
@Override

View File

@ -13,9 +13,8 @@ public class QyDepartmentServiceImpl extends ServiceImpl<QyDepartmentMapper, QyD
@Override
public Page<QyDepartmentEntity> getDepartmentList(AbstractPageQuery<QyDepartmentEntity> query) {
LambdaQueryWrapper<QyDepartmentEntity> wrapper = new LambdaQueryWrapper<>();
return this.page(query.toPage(), wrapper);
return this.page(query.toPage(), query.toQueryWrapper());
}
@Override

View File

@ -20,9 +20,7 @@ public class QyMessageServiceImpl extends ServiceImpl<QyMessageMapper, QyMessage
@Override
public Page<QyMessageEntity> getMessageList(AbstractPageQuery<QyMessageEntity> query) {
LambdaQueryWrapper<QyMessageEntity> wrapper = new LambdaQueryWrapper<>();
return this.page(query.toPage(), wrapper);
return this.page(query.toPage(), query.toQueryWrapper());
}
@Override

View File

@ -15,11 +15,7 @@ public class QyTemplateServiceImpl extends ServiceImpl<QyTemplateMapper, QyTempl
@Override
public Page<QyTemplateEntity> getTemplateList(AbstractPageQuery<QyTemplateEntity> query) {
LambdaQueryWrapper<QyTemplateEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(QyTemplateEntity::getEnable, "1")
.orderByDesc(QyTemplateEntity::getGettokenTime);
return this.page(query.toPage(), wrapper);
return this.page(query.toPage(), query.toQueryWrapper());
}
@Override

View File

@ -21,11 +21,7 @@ public class QyUserServiceImpl extends ServiceImpl<QyUserMapper, QyUserEntity> i
@Override
public Page<QyUserEntity> getUserList(AbstractPageQuery<QyUserEntity> query) {
LambdaQueryWrapper<QyUserEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(QyUserEntity::getEnable, "1")
.orderByDesc(QyUserEntity::getId);
return this.page(query.toPage(), wrapper);
return this.page(query.toPage(), query.toQueryWrapper());
}
@Override

View File

@ -1,5 +1,7 @@
package com.agileboot.domain.shop.goods.db;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -7,5 +9,11 @@ import lombok.EqualsAndHashCode;
@Data
public class SearchGoodsDO extends ShopGoodsEntity {
@TableField("category_name")
private String categoryName;
@TableField("cabinet_name")
private String cabinetName;
@TableField("cell_no")
private Integer cellNo;
}

View File

@ -24,9 +24,12 @@ public interface ShopGoodsMapper extends BaseMapper<ShopGoodsEntity> {
* @return 商品分页列表
*/
@Select("SELECT g.goods_id, g.goods_name, g.category_id, g.price, " +
"g.stock, g.status, g.cover_img, c.category_name " +
"g.stock, g.status, g.cover_img, c.category_name, " +
"sc.cabinet_name, cc.cell_no " +
"FROM shop_goods g " +
"LEFT JOIN shop_category c ON g.category_id = c.category_id " +
"LEFT JOIN cabinet_cell cc ON g.goods_id = cc.goods_id " +
"LEFT JOIN smart_cabinet sc ON cc.cabinet_id = sc.cabinet_id " +
"${ew.customSqlSegment}")
Page<SearchGoodsDO> getGoodsList(
Page<SearchGoodsDO> page,

View File

@ -46,8 +46,8 @@ public class ShopGoodsDTO {
@ExcelColumn(name = "分类ID")
private Long categoryId;
/* @ExcelColumn(name = "分类名称")
private String categoryName;*/
@ExcelColumn(name = "分类名称")
private String categoryName;
@ExcelColumn(name = "销售价格")
private BigDecimal price;
@ -72,4 +72,9 @@ public class ShopGoodsDTO {
@ExcelColumn(name = "备注")
private String remark;
@ExcelColumn(name = "柜机名称")
private String cabinetName;
@ExcelColumn(name = "格口号")
private Integer cellNo;
}

View File

@ -1,11 +1,14 @@
package com.agileboot.domain.shop.order;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity;
import com.agileboot.domain.cabinet.cell.db.CabinetCellService;
import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetEntity;
import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetService;
import com.agileboot.domain.common.command.BulkOperationCommand;
import com.agileboot.domain.mqtt.MqttService;
import com.agileboot.domain.shop.goods.db.ShopGoodsEntity;
import com.agileboot.domain.shop.goods.db.ShopGoodsService;
import com.agileboot.domain.shop.order.command.SubmitOrderCommand;
@ -14,26 +17,26 @@ import com.agileboot.domain.shop.order.db.ShopOrderService;
import com.agileboot.domain.shop.order.db.ShopOrderGoodsEntity;
import com.agileboot.domain.shop.order.db.ShopOrderGoodsService;
import com.agileboot.domain.shop.order.dto.CreateOrderResult;
import com.agileboot.domain.shop.order.dto.ShopOrderDTO;
import com.agileboot.domain.shop.order.dto.GetOrdersByOpenIdDTO;
import com.agileboot.domain.shop.order.model.OrderModel;
import com.agileboot.domain.shop.order.model.OrderModelFactory;
import com.agileboot.domain.shop.order.model.OrderGoodsModel;
import com.agileboot.domain.shop.order.model.OrderGoodsModelFactory;
import com.agileboot.domain.shop.order.query.SearchShopOrderQuery;
import com.agileboot.domain.shop.payment.PaymentApplicationService;
import com.agileboot.domain.shop.payment.dto.WxJsApiPreCreateRequest;
import com.agileboot.domain.shop.payment.dto.WxJsApiPreCreateResponse;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.time.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderApplicationService {
@ -44,6 +47,9 @@ public class OrderApplicationService {
private final OrderModelFactory orderModelFactory;
private final OrderGoodsModelFactory orderGoodsModelFactory;
private final PaymentApplicationService paymentApplicationService;
private final CabinetCellService cabinetCellService;
private final SmartCabinetService smartCabinetService;
private final MqttService mqttService;
/*public PageDTO<ShopOrderDTO> getOrderList(SearchShopOrderQuery<> query) {
Page<ShopOrderEntity> page = orderService.page(query.toPage(), query.toQueryWrapper());
@ -51,6 +57,45 @@ public class OrderApplicationService {
return new PageDTO<>(dtoList, page.getTotal());
}*/
public void openOrderGoodsCabinet(Long orderId, Long orderGoodsId) {
OrderModel orderModel = orderModelFactory.loadById(orderId);
// 验证订单状态
if (orderModel.getStatus() != 2) {
throw new ApiException(ErrorCode.Client.COMMON_FORBIDDEN_TO_CALL, "订单状态不允许操作");
}
ShopOrderGoodsEntity goodsEntity = orderGoodsService.getById(orderGoodsId);
if (null == goodsEntity || !orderId.equals(goodsEntity.getOrderId())) {
throw new ApiException(ErrorCode.Client.COMMON_FORBIDDEN_TO_CALL, "订单商品不存在");
}
if (goodsEntity.getStatus() != 1) {
throw new ApiException(ErrorCode.Client.COMMON_FORBIDDEN_TO_CALL, "订单商品状态不允许操作");
}
List<CabinetCellEntity> cabinetCellEntityList = cabinetCellService.selectByGoodsId(goodsEntity.getGoodsId());
if (null == cabinetCellEntityList || cabinetCellEntityList.isEmpty()) {
throw new ApiException(ErrorCode.Client.COMMON_FORBIDDEN_TO_CALL, "商品未绑定柜子");
}
CabinetCellEntity cabinetCellEntity = cabinetCellEntityList.get(0);
SmartCabinetEntity smartCabinet = smartCabinetService.getById(cabinetCellEntity.getCabinetId());
if (null == smartCabinet) {
throw new ApiException(ErrorCode.Client.COMMON_FORBIDDEN_TO_CALL, "柜子不存在");
}
// 发送指令
String mqttDate = "8A";
mqttDate += String.format("%02X", smartCabinet.getLockControlNo());
mqttDate += String.format("%02X", cabinetCellEntity.getPinNo());
mqttDate += "11";
try {
mqttService.publish(mqttDate);
} catch (Exception e) {
log.error("mqtt publish error", e);
throw new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "发送指令失败");
}
}
@Transactional
public CreateOrderResult createOrder(SubmitOrderCommand command) {
List<ShopOrderGoodsEntity> goodsList = command.getGoodsList();
@ -80,9 +125,9 @@ public class OrderApplicationService {
private WxJsApiPreCreateRequest buildPaymentRequest(OrderModel orderModel) {
WxJsApiPreCreateRequest request = new WxJsApiPreCreateRequest();
request.setIp("222.218.10.217");
request.setIp("111.59.237.29");
request.setOpenid(orderModel.getOpenid()); //
request.setBiz_order_id(String.valueOf(orderModel.getOrderId())); // 使用订单唯一编号
request.setBiz_order_id("wxshop-" + orderModel.getOrderId() + "-" + new Date().getTime()); // 使用订单唯一编号
// 金额转换元转分并四舍五入
BigDecimal amountInFen = orderModel.getTotalAmount()
.multiply(new BigDecimal("100"))
@ -90,7 +135,7 @@ public class OrderApplicationService {
request.setPay_amount(amountInFen.toPlainString()); // 单位转换为分
request.setTitle("商品订单支付");
request.setNotify_url("http://wxshop.ab98.cn/shop-back-end/api/payment/callback");
request.setNotify_url("http://wxshop.ab98.cn/shop-api/api/payment/callback");
request.setBiz_id("wxshop");
request.setUcid(orderModel.getUcid());
request.setExtra("");
@ -171,4 +216,57 @@ public class OrderApplicationService {
model.deleteById();
}
}
public void handlePaymentSuccess(Long orderId, Integer amount, String tradeId, String tradePayTime) {
OrderModel orderModel = orderModelFactory.loadById(orderId);
// 状态校验
orderModel.validateStatusTransition(2); // 2代表已支付
// 更新订单状态
orderModel.setStatus(2);
orderModel.setPayStatus(2);
orderModel.setTradeId(tradeId);
try {
// if (tradePayTime.contains("+")) {
// tradePayTime = tradePayTime.replace("+", " ");
// }
orderModel.setPayTime(DateUtil.parse(tradePayTime));
} catch (Exception e) {
log.error("支付时间转换失败", e);
}
orderModel.updateById();
QueryWrapper<ShopOrderGoodsEntity> orderGoodsQueryWrapper = new QueryWrapper<>();
orderGoodsQueryWrapper.eq("order_id", orderId);
List<ShopOrderGoodsEntity> orderGoods = orderGoodsService.list(orderGoodsQueryWrapper);
// 发送指令
orderGoods.forEach(g -> {
openOrderGoodsCabinet(orderId, g.getOrderGoodsId());
});
}
public static void main(String[] args) {
int[] testCases = {0, 15, 22, 255, 256, -1}; // 包含边界值正常值超范围值和负数
for (int num : testCases) {
String hex = String.format("%02X", num);
System.out.println(num + "" + hex);
}
}
public GetOrdersByOpenIdDTO getOrdersByOpenId(String openid) {
QueryWrapper<ShopOrderEntity> orderQueryWrapper = new QueryWrapper<>();
orderQueryWrapper.eq("openid", openid);
List<ShopOrderEntity> orderList = orderService.list(orderQueryWrapper);
QueryWrapper<ShopOrderGoodsEntity> orderGoodsQueryWrapper = new QueryWrapper<>();
orderGoodsQueryWrapper.in("order_id", orderList.stream().map(ShopOrderEntity::getOrderId).collect(Collectors.toList()));
List<ShopOrderGoodsEntity> orderGoods = orderGoodsService.list(orderGoodsQueryWrapper);
QueryWrapper<ShopGoodsEntity> goodsQueryWrapper = new QueryWrapper<>();
goodsQueryWrapper.in("goods_id", orderGoods.stream().map(ShopOrderGoodsEntity::getGoodsId).collect(Collectors.toList()));
List<ShopGoodsEntity> goods = goodsService.list(goodsQueryWrapper);
return new GetOrdersByOpenIdDTO(orderList, orderGoods, goods);
}
}

View File

@ -52,7 +52,7 @@ public class ShopOrderGoodsEntity extends BaseEntity<ShopOrderGoodsEntity> {
@TableField("total_amount")
private BigDecimal totalAmount;
@ApiModelProperty("商品状态1正常 2已退货 3已换货")
@ApiModelProperty("商品状态1正常 2已退货 3已换货 4已完成")
@TableField("`status`")
private Integer status;

View File

@ -0,0 +1,16 @@
package com.agileboot.domain.shop.order.dto;
import com.agileboot.domain.shop.goods.db.ShopGoodsEntity;
import com.agileboot.domain.shop.order.db.ShopOrderEntity;
import com.agileboot.domain.shop.order.db.ShopOrderGoodsEntity;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class GetOrdersByOpenIdDTO {
List<ShopOrderEntity> orders;
List<ShopOrderGoodsEntity> orderGoods;
List<ShopGoodsEntity> goods;
}

View File

@ -7,10 +7,13 @@ import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.agileboot.domain.shop.payment.dto.WxJsApiPreCreateRequest;
import com.agileboot.domain.shop.payment.dto.WxJsApiPreCreateResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class PaymentApplicationService {
@ -19,6 +22,7 @@ public class PaymentApplicationService {
try {
String jsonBody = JSONUtil.toJsonStr(request);
log.info("callJsApiPreCreate 请求body{}", jsonBody);
// 使用try-with-resources自动关闭连接
try (HttpResponse httpResponse = HttpUtil.createPost(gatewayUrl)
@ -30,6 +34,7 @@ public class PaymentApplicationService {
// 获取HTTP状态码和响应体
int status = httpResponse.getStatus();
String result = httpResponse.body();
log.info("callJsApiPreCreate 响应body{}", result);
// 先校验HTTP状态码
if (status != HttpStatus.HTTP_OK) {
@ -41,7 +46,8 @@ public class PaymentApplicationService {
throw new RuntimeException("支付网关返回非JSON格式响应" + result);
}
WxJsApiPreCreateResponse response = JSONUtil.toBean(result, WxJsApiPreCreateResponse.class);
ObjectMapper mapper = new ObjectMapper();
WxJsApiPreCreateResponse response = mapper.readValue(result, WxJsApiPreCreateResponse.class);
if (response.getAppId() != null) {
return response;
}

View File

@ -0,0 +1,5 @@
<?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.agileboot.domain.cabinet.cell.db.CabinetCellMapper">
</mapper>

View File

@ -0,0 +1,5 @@
<?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.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetMapper">
</mapper>

View File

@ -61,7 +61,7 @@ public class CodeGenerator {
//生成的类 放在orm子模块下的/target/generated-code目录底下
.module("/agileboot-orm/target/generated-code")
.parentPackage("com.agileboot")
.tableName("qy_user")
.tableName("cabinet_cell")
// 决定是否继承基类
.isExtendsFromBaseEntity(true)
.build();

View File

@ -47,6 +47,7 @@
<maven.compiler.plugin.version>3.1</maven.compiler.plugin.version>
<maven.surefire.plugin.version>2.5</maven.surefire.plugin.version>
<maven.war.plugin.version>3.1.0</maven.war.plugin.version>
<mqttv3.version>1.2.5</mqttv3.version>
</properties>
<!-- 依赖声明 -->

53
sql/20250317.sql Normal file
View File

@ -0,0 +1,53 @@
DROP TABLE IF EXISTS `smart_cabinet`;
CREATE TABLE `smart_cabinet` (
`cabinet_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '柜机唯一ID',
`cabinet_name` VARCHAR(100) NOT NULL COMMENT '柜机名称',
`cabinet_type` TINYINT NOT NULL DEFAULT 0 COMMENT '柜机类型0主柜 1副柜',
`template_no` VARCHAR(50) NOT NULL COMMENT '柜机模版编号',
`lock_control_no` INT NOT NULL COMMENT '锁控板序号',
`location` INT NOT NULL COMMENT '柜机位置',
`creator_id` BIGINT NOT NULL DEFAULT 0 COMMENT '创建者ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater_id` BIGINT NOT NULL DEFAULT 0 COMMENT '更新者ID',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标志0存在 1删除',
PRIMARY KEY (`cabinet_id`),
KEY `idx_template_no` (`template_no`),
KEY `idx_location` (`location`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='智能柜信息表';
DROP TABLE IF EXISTS `cabinet_cell`;
CREATE TABLE `cabinet_cell` (
`cell_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '格口唯一ID',
`cabinet_id` BIGINT NOT NULL COMMENT '关联柜机ID',
`goods_id` BIGINT DEFAULT NULL COMMENT '关联商品ID',
`cell_no` INT NOT NULL COMMENT '格口号',
`pin_no` INT NOT NULL COMMENT '针脚序号',
`cell_type` TINYINT NOT NULL DEFAULT 1 COMMENT '格口类型1小格 2中格 3大格 4超大格',
`usage_status` TINYINT NOT NULL DEFAULT 1 COMMENT '使用状态1空闲 2已占用',
`available_status` TINYINT NOT NULL DEFAULT 1 COMMENT '可用状态1正常 2故障',
`creator_id` BIGINT NOT NULL DEFAULT 0 COMMENT '创建者ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater_id` BIGINT NOT NULL DEFAULT 0 COMMENT '更新者ID',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标志0存在 1删除',
PRIMARY KEY (`cell_id`),
KEY `idx_cabinet` (`cabinet_id`),
KEY `idx_usage_status` (`usage_status`),
KEY `idx_available_status` (`available_status`),
KEY `idx_goods` (`goods_id`),
CONSTRAINT `fk_cell_cabinet` FOREIGN KEY (`cabinet_id`) REFERENCES `smart_cabinet` (`cabinet_id`),
CONSTRAINT `fk_cell_goods` FOREIGN KEY (`goods_id`) REFERENCES `shop_goods` (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='柜机格口信息表';
INSERT INTO sys_menu
(menu_name, menu_type, router_name, parent_id, `path`, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted)
VALUES('柜机管理', 2, '', 0, '/cabinet', 0, '', '{"title":"柜机管理","icon":"fa-solid:server","showLink":true,"showParent":true,"rank":2}', 1, '', 1, '2025-03-17 17:55:52', NULL, NULL, 0);
INSERT INTO sys_menu
(menu_name, menu_type, router_name, parent_id, `path`, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted)
VALUES('柜机列表', 1, 'SmartCabinet', 69, '/cabinet/smart-cabinet/index', 0, '', '{"title":"柜机列表","showLink":true,"showParent":true,"rank":1}', 1, '', 1, '2025-03-17 17:58:10', 1, '2025-03-17 17:58:21', 0);
INSERT INTO sys_menu
(menu_name, menu_type, router_name, parent_id, `path`, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted)
VALUES('格口管理', 1, 'CabinetCell', 69, '/cabinet/cabinet-cell/index', 0, '', '{"title":"格口管理","showParent":true,"rank":2}', 1, '', 1, '2025-03-18 17:23:47', NULL, NULL, 0);