当前位置: 首页 > news >正文

Redis分布式锁使用以及对接支付宝,paypal,strip跨境支付

本章重点在于如何使用redis的分布式锁来锁定库存。减少超卖,同时也对接了支付宝,paypal,strip跨境支付

第一步先建立一个商品表
 

CREATE TABLE `sys_product` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`code` varchar(60) DEFAULT NULL COMMENT '商品编码',`name` varchar(60) DEFAULT NULL COMMENT '商品名字',`image` varchar(200) DEFAULT NULL COMMENT '商品图片',`inventory` int(11) DEFAULT NULL COMMENT '商品库存',`points` int(11) DEFAULT NULL COMMENT '商品积分',`amount` decimal(10,0) DEFAULT NULL COMMENT '商品金额',`create_time` datetime NOT NULL COMMENT '创建时间',`create_user` varchar(60) DEFAULT NULL COMMENT '创建人',`update_time` datetime DEFAULT NULL COMMENT '修改时间',`update_user` varchar(60) DEFAULT NULL COMMENT '修改人',`is_delete` int(2) DEFAULT NULL COMMENT '是否删除',PRIMARY KEY (`id`),UNIQUE KEY `id` (`id`) USING BTREE COMMENT '主键'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='产品表';

对应的sql文件里面有各个信息,有名字,库存,还有积分。这几个概念很重要

页面如下

代码如下

 /*** 新增产品** @param sysProduct 产品* @return 结果*/@Overridepublic int insertSysProduct(SysProduct sysProduct) {// 然后生成产品编码,生成一个6位的英文字母sysProduct.setCreateTime(DateUtils.getNowDate());// 首先先生成一个随机数,然后调用方法,如果生成重复则会一直生成直接到不重复String getProductCode = getProductCode();SysProduct one = this.lambdaQuery().eq(SysProduct::getCode, getProductCode).one();if (StringUtils.isNotNull(one)) {// 说明重复了,需要重新生成递归getProductCode = getProductCode();}// 同时也要判定一下商品名字是否重复,如果名字重复则无法添加Long countName = this.lambdaQuery().eq(SysProduct::getName, sysProduct.getName()).count();if (countName > 0) {throw new ServiceException("名字已经重复,无法添加");}// 如果是空的则直接插入数据sysProduct.setCode(getProductCode);// 创建人sysProduct.setCreateUser(SecurityUtils.getLoginUser().getUserId());// 修改人sysProduct.setUpdateUser(SecurityUtils.getLoginUser().getUserId());// 修改时间return sysProductMapper.insertSysProduct(sysProduct);}/*** 生产一个随机数** @return 返回值*/public String getProductCode() {// 生成6位随机英文字母StringBuilder sb = new StringBuilder();for (int i = 0; i < NUMBER_6; i++) {sb.append(ALPHABET.charAt(RandomUtils.nextInt(0, ALPHABET.length())));}return sb.toString();}/*** 修改产品** @param sysProduct 产品* @return 结果*/@Overridepublic int updateSysProduct(SysProduct sysProduct) {// 同时也要判定一下商品名字是否重复,如果名字重复则无法添加Long countName = this.lambdaQuery().eq(SysProduct::getName, sysProduct.getName()).ne(SysProduct::getId, sysProduct.getId()).count();if (countName > 0) {throw new ServiceException("名字已经重复,无法添加");}sysProduct.setUpdateTime(DateUtils.getNowDate());return sysProductMapper.updateSysProduct(sysProduct);}

第二步创建订单表

CREATE TABLE `sys_order` (`id` bigint(20) NOT NULL COMMENT '主键',`order_id` bigint(20) DEFAULT NULL COMMENT '订单id',`points` int(11) DEFAULT NULL COMMENT '订单积分',`amount` decimal(2,0) DEFAULT NULL COMMENT '订单金额',`number` int(11) DEFAULT NULL COMMENT '数量',`status` int(11) DEFAULT '0' COMMENT '订单状态(0 未支付 1支付中 2支付成功)',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',`update_time` datetime DEFAULT NULL COMMENT '修改时间',`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',`is_delete` int(2) DEFAULT NULL COMMENT '是否删除',PRIMARY KEY (`id`),UNIQUE KEY `id` (`id`) USING BTREE COMMENT '主键',KEY `order_id` (`order_id`) USING BTREE COMMENT '订单id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='订单表';CREATE TABLE `sys_order_item` (`id` bigint(20) NOT NULL COMMENT '主键id',`order_id` bigint(20) DEFAULT NULL COMMENT '订单id',`order_item_id` bigint(20) DEFAULT NULL COMMENT '订单详情id',`product_name` varchar(30) DEFAULT NULL COMMENT '商品名字',`product_number` int(11) DEFAULT NULL COMMENT '商品数量',`product_amount` decimal(2,0) DEFAULT NULL COMMENT '商品金额',`product_code` varchar(30) DEFAULT NULL COMMENT '商品编码',`product_image` varchar(255) DEFAULT NULL COMMENT '商品图片',`product_points` varchar(30) DEFAULT NULL COMMENT '商品积分',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',`update_time` datetime DEFAULT NULL COMMENT '创建时间',`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',`is_delete` int(2) DEFAULT NULL COMMENT '是否删除',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='订单详情表';

第三步我们要生成一个订单提前准备往rabbitmq按照延迟队列插件

作用:就是给订单加一个过期时间,当这个订单创建后,有一个过期时间当这个时间过期后自动关闭订单,修改状态,可以利用rabbitmq的延迟插件可以很好的实现,当然也有其他方案,比如redis,定时任务。

第一步先看rabbimq版本。

第二步到gitHub上找到对应的版本的延迟队列

Releases · rabbitmq/rabbitmq-delayed-message-exchange

第三步直接将这个拖到服务器上

第四步:我的rabbitmq安装在docker里面,所以直接在docker按照延迟插件

执行这个命令是copy这个路径到docker中rabbitmq的内部容器的plugins

docker cp /usr/rabbitmq_delayed_message/rabbitmq_delayed_message_exchange-v4.0.7.ez rabbit:/plugins/

执行完毕后出现

Successfully copied 44kB to rabbit:/plugins/

然后到docker内部容器看是否安装成功

 docker exec -it rabbit /bin/bash

cd plugins

进到plugins

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

然后执行exit退出容器,在重启rabbitmq

 docker restart fe46628fec68

然后到rabbitmq控制界面查看是否有也有这个插件

说明OK了

第四步创建订单代码

点击创建订单,传递一个库存,和产品id。然后添加订单表有一个主表和明细表

主表存订单总金额,总积分,总库存。

明细表存在一些商品的信息。

前端代码如下:

<template><div class="app-container"><el-form:model="queryParams"ref="queryForm"size="small":inline="true"v-show="showSearch"label-width="68px"><el-form-item label="商品编码" prop="code"><el-inputv-model="queryParams.code"placeholder="请输入商品编码"clearable@keyup.enter.native="handleQuery"/></el-form-item><el-form-item label="商品名字" prop="name"><el-inputv-model="queryParams.name"placeholder="请输入商品名字"clearable@keyup.enter.native="handleQuery"/></el-form-item><el-form-item><el-buttontype="primary"icon="el-icon-search"size="mini"@click="handleQuery">搜索</el-button><el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button></el-form-item></el-form><el-row :gutter="10" class="mb8"><el-col :span="1.5"><el-buttontype="primary"plainicon="el-icon-plus"size="mini"@click="handleAdd"v-hasPermi="['system:product:add']">新增</el-button></el-col><el-col :span="1.5"><el-buttontype="success"plainicon="el-icon-edit"size="mini":disabled="single"@click="handleUpdate"v-hasPermi="['system:product:edit']">修改</el-button></el-col><el-col :span="1.5"><el-buttontype="danger"plainicon="el-icon-delete"size="mini":disabled="multiple"@click="handleDelete"v-hasPermi="['system:product:remove']">删除</el-button></el-col><el-col :span="1.5"><el-buttontype="warning"plainicon="el-icon-download"size="mini"@click="handleExport"v-hasPermi="['system:product:export']">导出</el-button></el-col><el-dialogtitle="确认订单":visible.sync="orderDialogVisible"width="25%"custom-class="custom-order-dialog"><el-table :data="selectedProducts" border><el-table-columnproperty="name"label="商品名称"width="200"></el-table-column><el-table-column property="inventory" label="商品库存" width="200"><template slot-scope="scope"><el-input v-model="scope.row.inventory" size="small"></el-input></template></el-table-column><!-- 可以根据需要添加更多列 --></el-table><span slot="footer" class="dialog-footer"><el-button @click="orderDialogVisible = false">取消</el-button><el-button type="primary" @click="submitOrder">确定</el-button></span></el-dialog><!-- 创建订单按钮 --><el-col :span="1.5"><el-button @click="handleCreateOrder">创建订单</el-button></el-col><right-toolbar:showSearch.sync="showSearch"@queryTable="getList"></right-toolbar></el-row><el-tablev-loading="loading":data="productList"@selection-change="handleSelectionChange"><el-table-column type="selection" width="55" align="center" /><el-table-column label="商品编码" align="center" prop="code" /><el-table-column label="商品名字" align="center" prop="name" /><el-table-column label="商品图片" align="center" prop="image" width="100"><template slot-scope="scope"><image-preview :src="scope.row.image" :width="50" :height="50" /></template></el-table-column><el-table-column label="商品库存" align="center" prop="inventory" /><el-table-column label="商品积分" align="center" prop="points" /><el-table-column label="商品金额" align="center" prop="amount" /><el-table-columnlabel="操作"align="center"class-name="small-padding fixed-width"><template slot-scope="scope"><el-buttonsize="mini"type="text"icon="el-icon-edit"@click="handleUpdate(scope.row)"v-hasPermi="['system:product:edit']">修改</el-button><el-buttonsize="mini"type="text"icon="el-icon-delete"@click="handleDelete(scope.row)"v-hasPermi="['system:product:remove']">删除</el-button></template></el-table-column></el-table><paginationv-show="total > 0":total="total":page.sync="queryParams.pageNum":limit.sync="queryParams.pageSize"@pagination="getList"/><!-- 添加或修改产品对话框 --><el-dialog :title="title" :visible.sync="open" width="500px" append-to-body><el-form ref="form" :model="form" :rules="rules" label-width="80px"><el-form-item label="商品名字" prop="name"><el-input v-model="form.name" placeholder="请输入商品名字" /></el-form-item><el-form-item label="商品图片" prop="image"><image-upload v-model="form.image" /></el-form-item><el-form-item label="商品库存" prop="inventory"><el-input v-model="form.inventory" placeholder="请输入商品库存" /></el-form-item><el-form-item label="商品积分" prop="points"><el-input v-model="form.points" placeholder="请输入商品积分" /></el-form-item><el-form-item label="商品金额" prop="amount"><el-input v-model="form.amount" placeholder="请输入商品金额" /></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button type="primary" @click="submitForm">确 定</el-button><el-button @click="cancel">取 消</el-button></div></el-dialog></div>
</template><script>
import {listProduct,getProduct,delProduct,addProduct,updateProduct,
} from "@/api/system/product";
import { createOrder } from "@/api/system/order";export default {name: "Product",data() {return {// 其他已有属性...orderDialogVisible: false, // 控制订单对话框的可见性selectedProducts: [], // 存储选中的产品// 遮罩层loading: true,// 选中数组ids: [],// 非单个禁用single: true,// 非多个禁用multiple: true,// 显示搜索条件showSearch: true,// 总条数total: 0,// 产品表格数据productList: [],// 弹出层标题title: "",// 是否显示弹出层open: false,// 查询参数queryParams: {pageNum: 1,pageSize: 10,code: null,name: null,image: null,inventory: null,points: null,amount: null,updateUser: null,},// 表单参数form: {},// 表单校验rules: {createTime: [{ required: true, message: "创建时间不能为空", trigger: "blur" },],},};},created() {this.getList();},methods: {/** 查询产品列表 */getList() {this.loading = true;listProduct(this.queryParams).then((response) => {this.productList = response.rows;this.total = response.total;this.loading = false;});},// 取消按钮cancel() {this.open = false;this.reset();},// 表单重置reset() {this.form = {id: null,code: null,name: null,image: null,inventory: null,points: null,amount: null,createTime: null,createUser: null,updateTime: null,updateUser: null,isDelete: null,};this.resetForm("form");},/** 搜索按钮操作 */handleQuery() {this.queryParams.pageNum = 1;this.getList();},/** 重置按钮操作 */resetQuery() {this.resetForm("queryForm");this.handleQuery();},// 多选框选中数据handleSelectionChange(selection) {this.ids = selection.map((item) => item.id);this.single = selection.length !== 1;this.multiple = !selection.length;this.selectedProducts = selection; // 直接赋值选中的产品列表},/** 新增按钮操作 */handleAdd() {this.reset();this.open = true;this.title = "添加产品";},/** 修改按钮操作 */handleUpdate(row) {this.reset();const id = row.id || this.ids;getProduct(id).then((response) => {this.form = response.data;this.open = true;this.title = "修改产品";});},/** 提交按钮 */submitForm() {this.$refs["form"].validate((valid) => {if (valid) {if (this.form.id != null) {updateProduct(this.form).then((response) => {this.$modal.msgSuccess("修改成功");this.open = false;this.getList();});} else {addProduct(this.form).then((response) => {this.$modal.msgSuccess("新增成功");this.open = false;this.getList();});}}});},/** 删除按钮操作 */handleDelete(row) {const ids = row.id || this.ids;this.$modal.confirm('是否确认删除产品编号为"' + ids + '"的数据项?').then(function () {return delProduct(ids);}).then(() => {this.getList();this.$modal.msgSuccess("删除成功");}).catch(() => {});},/** 导出按钮操作 */handleExport() {this.download("system/product/export",{...this.queryParams,},`product_${new Date().getTime()}.xlsx`);},/** 创建订单 */handleCreateOrder() {console.log("参数" + this.selectedProducts.length);if (this.selectedProducts.length === 0) {this.$message({message: "请选择至少一个商品!",type: "warning",});return;}this.orderDialogVisible = true; // 打开订单对话框},/** 提交订单 */submitOrder() {const sysCreateOrderDTO = this.selectedProducts.map((product) => ({productId: product.id,inventory: product.inventory,}));createOrder(sysCreateOrderDTO).then(() => {this.$modal.msgSuccess("订单创建成功");this.orderDialogVisible = false; // 关闭订单对话框this.getList();// 刷新产品列表}).catch((error) => {this.$modal.msgError(error);});},},
};
</script>
<style scoped>
/* 自定义订单对话框样式 */
.custom-order-dialog {/* 父容器样式 */
}
.custom-order-dialog .product-list {list-style: none;padding: 0;margin: 0;
}
.custom-order-dialog .product-item {display: flex;align-items: center;margin-bottom: 10px;
}
.custom-order-dialog .product-item i {margin-right: 8px;color: #67c23a; /* 或者选择其他颜色 */
}
</style>
// 新增订单
export function createOrder(sysCreateOrderDTO) {return request({url: '/system/order',method: 'post',data: sysCreateOrderDTO})
}
import request from '@/utils/request'// 查询产品列表
export function listProduct(query) {return request({url: '/system/product/list',method: 'get',params: query})
}// 查询产品详细
export function getProduct(id) {return request({url: '/system/product/' + id,method: 'get'})
}// 新增产品
export function addProduct(data) {return request({url: '/system/product',method: 'post',data: data})
}// 修改产品
export function updateProduct(data) {return request({url: '/system/product',method: 'put',data: data})
}// 删除产品
export function delProduct(id) {return request({url: '/system/product/' + id,method: 'delete'})
}

后端代码如下

controller

 /*** 新增订单* @param sysCreateOrderDTO 入参* @return 结果*/@PreAuthorize("@ss.hasPermi('system:order:add')")@Log(title = "订单", businessType = BusinessType.INSERT)@PostMappingpublic AjaxResult add(@RequestBody List<SysCreateOrderDTO> sysCreateOrderDTO) {return toAjax(sysOrderService.insertSysOrder(sysCreateOrderDTO));}

 entity

package com.ruoyi.system.domain.dto;import lombok.Data;/*** @author hu* @desc 创建订单的DTO*/
@Data
public class SysCreateOrderDTO {/*** 产品id*/private Long productId;/*** 库存*/private Long inventory;
}

service

 /*** 新增订单** @param sysCreateOrderDTO 添加的订单信息* @return 结果*/@Override@Transactional(rollbackFor = Exception.class)public Boolean insertSysOrder(List<SysCreateOrderDTO> sysCreateOrderDTO) {// 第一步拿到川产品id进行逗号拆分if (StringUtils.isNull(sysCreateOrderDTO)) {throw new ServiceException("缺少必要的参数");}// 拿到对应的产品idList<Long> productIds = sysCreateOrderDTO.stream().map(item -> item.getProductId()).collect(Collectors.toList());// 拿到全部产品id// 然后查询产品是否存在如果产品存在,则开始存入到数据库并且订单是状态是未支付。List<SysProduct> sysProducts = sysProductService.lambdaQuery().in(SysProduct::getId, productIds).eq(SysProduct::getIsDelete, 0).list();// 然后判定是否存在if (sysProducts.size() != productIds.size()) {throw new ServiceException("请确保产品是否存在");}// 然后判定一下传递的库存是否大于商品原来的库存// 获取积分AtomicLong points = new AtomicLong(0);AtomicLong amounts = new AtomicLong(0);AtomicLong numbers = new AtomicLong(0);sysProducts.forEach(item -> {sysCreateOrderDTO.forEach(info -> {if (item.getId().equals(info.getProductId())) {if (item.getInventory() < info.getInventory()) {throw new ServiceException("请确保库存是否充足");}// 积分等于库存*单个积分Long point = info.getInventory() * item.getPoints();// 金额,等于单价*出库Long amount = info.getInventory() * item.getAmount();points.set(points.get() + point);amounts.set(amounts.get() + amount);numbers.set(numbers.get() + info.getInventory());}});});// 如果产品存在,这时候开始创建订单,并且给一个过期时间SysOrder sysOrder = new SysOrder();// 订单idSnowFlakeGenerateIdWorker snowFlakeGenerateIdWorker =new SnowFlakeGenerateIdWorker(0L, 0L);String id = snowFlakeGenerateIdWorker.generateNextId();// 订单idsysOrder.setOrderId(Long.valueOf(id));// 积分 不是空的sysOrder.setPoints(points.get());// 金额sysOrder.setAmount(amounts.get());// 数量sysOrder.setNumber(numbers.get());// 订单状态sysOrder.setStatus(0);// 创建时间sysOrder.setCreateTime(DateUtils.getNowDate());// 修改时间sysOrder.setUpdateTime(DateUtils.getNowDate());// 创建人sysOrder.setCreateUser(SecurityUtils.getLoginUser().getUserId());// 修改人sysOrder.setUpdateUser(SecurityUtils.getLoginUser().getUserId());// 然后创建订单详情表List<SysOrderItem> orderItems = new ArrayList<>();sysProducts.forEach(productInfo -> {SysOrderItem sysOrderItem = new SysOrderItem();// 订单idsysOrderItem.setOrderId(sysOrder.getOrderId());// 订单详情idsysOrderItem.setOrderItemId(Long.valueOf(snowFlakeGenerateIdWorker.generateNextId()));// 商品名字sysOrderItem.setProductName(productInfo.getName());// 商品数量sysOrderItem.setProductNumber(sysCreateOrderDTO.stream().filter(item -> item.getProductId().equals(productInfo.getId())).findFirst().get().getInventory());// 商品金额sysOrderItem.setProductAmount(productInfo.getAmount());// 商品编码sysOrderItem.setProductCode(productInfo.getCode());// 商品图片sysOrderItem.setProductImage(productInfo.getImage());// 商品积分sysOrderItem.setProductPoints(productInfo.getPoints());// 创建人sysOrderItem.setCreateUser(SecurityUtils.getLoginUser().getUserId());// 修改人sysOrderItem.setUpdateUser(SecurityUtils.getLoginUser().getUserId());// 修改时间sysOrderItem.setUpdateTime(DateUtils.getNowDate());// 创建时间sysOrderItem.setCreateTime(DateUtils.getNowDate());// 放到集合里面orderItems.add(sysOrderItem);});Boolean result = this.save(sysOrder) && sysOrderItemService.saveBatch(orderItems);// 创建一个订单过期时间,直接放到redis里面默认1个小时吧,如果一个小时到了,然后修改一下订单状态,这时候同时发送给rabbitmq做延迟队列redisTemplate.opsForValue().set(Constants.ORDER_PREFIX + sysOrder.getOrderId(), "0", 1, TimeUnit.HOURS);// 如果是空这时候同时需要给rabbitmq发送消息,这个消息是延迟消息,所以需要创建队列rabbitTemplate.convertAndSend(IsoMqConstant.ORDER_DELAY_EXCHANGE, IsoMqConstant.ORDER_DELAY_ROUTING_KEY, sysOrder.getOrderId(), message -> {message.getMessageProperties().setDelay(1 * DAYS_PER_SECOND);return message;});return result;}

注意使用了rabbitmq有延迟队列,就是主要用于处理订单过期时间的

package com.ruoyi.system.mq;import com.rabbitmq.client.Channel;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.SysOrder;
import com.ruoyi.system.enums.SysOrderStatusEnum;
import com.ruoyi.system.service.ISysOrderService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;import java.io.IOException;import static com.ruoyi.common.mq.IsoMqConstant.ORDER_DELAY_QUEUE;/*** @author hu* @desc 订单消费者* @date 2025/4/27 15:06*/
@Component
@Slf4j
@AllArgsConstructor
public class SysOrderConsumer {private final ISysOrderService sysOrderService;/*** 延迟消费者** @param channel* @param message* @param orderId*/@RabbitListener(queues = ORDER_DELAY_QUEUE)public void orderPayDelayConsumer(Channel channel, Message message, Long orderId) {try {log.info("订单延迟队列开始了:{}", orderId);// 然后拿到订单状态,这时候需要查询一下,如果订单状态是未支付,则修改订单状态为已取消SysOrder sysOrder = sysOrderService.lambdaQuery().eq(SysOrder::getOrderId, orderId).eq(SysOrder::getIsDelete, 0).eq(SysOrder::getStatus, 0).one();if (StringUtils.isNotNull(sysOrder)) {sysOrderService.lambdaUpdate().set(SysOrder::getStatus, SysOrderStatusEnum.CANCELLED.getCode()).eq(SysOrder::getOrderId, orderId).update();}channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);} catch (Exception e) {log.error("发送了异常", e.getMessage());try {channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);} catch (IOException ex) {throw new RuntimeException(ex);}}}
}
package com.ruoyi.system.config;import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import static com.ruoyi.common.mq.IsoMqConstant.*;/*** @author hu* @desc rabbitmq  配置*/
@Configuration
public class RabbitmqConfig {/*** 延迟交换机** @return 返回*/@Beanpublic DirectExchange delayExchange() {return ExchangeBuilder.directExchange(ORDER_DELAY_EXCHANGE).delayed() //设置delay属性为true 默认是true.durable(true).build();}/*** 延迟队列** @return 返回*/@Beanpublic Queue delayQueue() {return new Queue(ORDER_DELAY_QUEUE);}/*** 绑定** @param delayQueue    延迟队列* @param delayExchange 延迟交换机* @return 返回值*/@Beanpublic Binding delayBinding(Queue delayQueue, DirectExchange delayExchange) {return BindingBuilder.bind(delayQueue).to(delayExchange).with(ORDER_DELAY_ROUTING_KEY);}
}

结果点击确定就能实现结果如下

未完待续。

相关文章:

Redis分布式锁使用以及对接支付宝,paypal,strip跨境支付

本章重点在于如何使用redis的分布式锁来锁定库存。减少超卖&#xff0c;同时也对接了支付宝&#xff0c;paypal&#xff0c;strip跨境支付 第一步先建立一个商品表 CREATE TABLE sys_product (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键,code varchar(60) DEFAUL…...

沙箱逃逸(Python沙盒逃逸深度解析)

沙箱逃逸&#xff08;Python沙盒逃逸深度解析&#xff09; 一、沙盒逃逸的核心目标 执行系统命令 通过调用os.system、subprocess.Popen等函数执行Shell命令&#xff0c;例如读取文件或反弹Shell。 文件操作 读取敏感文件&#xff08;如/etc/passwd&#xff09;、写入后门文件…...

k8s-Pod生命周期

初始化容器 初始化容器是在pod的主容器启动之前要运行的容器&#xff0c;主要是做一些主容器的前置工作&#xff0c;它具有两大特征&#xff1a; 1. 初始化容器必须运行完成直至结束&#xff0c;若某初始化容器运行失败&#xff0c;那么kubernetes需要重启它直到成功完成 2. 初…...

基于Springboot + vue实现的中医院问诊系统

项目描述 本系统包含管理员、医生、用户三个角色。 管理员角色&#xff1a; 用户管理&#xff1a;管理系统中所有用户的信息&#xff0c;包括添加、删除和修改用户。 配置管理&#xff1a;管理系统配置参数&#xff0c;如上传图片的路径等。 权限管理&#xff1a;分配和管理…...

computed计算值为什么还可以依赖另外一个computed计算值?

在 Vue&#xff08;或类似的响应式框架&#xff09;中&#xff0c;computed 计算属性之所以可以依赖另一个 computed 属性&#xff0c;是因为&#xff1a; ✅ 本质上 computed 是响应式依赖的“派生值” 每个 computed 本质上就是一个 基于其他响应式数据计算出来的值。 当你在…...

近期实践总结

一、计算机二级考试到底教会了我们什么&#xff1f; 1、概况 根据本人复习、考试的经验&#xff0c;不难发现里面的试题或多或少有些死板&#xff08;甚至可以说落后于时代&#xff09;&#xff0c;当今时代已经不是二十年前什么都需要手搓的时代了&#xff0c;引擎、集成类软…...

Arduion 第一天,变量的详细解析

Arduino变量详解与嵌入式开发扩展 一、变量基础篇 1.1 变量声明与初始化 <ARDUINO>int ledPin 13; // 声明并初始化float sensorValue; // 先声明后赋值unsigned long startTime; // 无符号长整型void setup() {sensorValue analogRead(A0) *…...

【每日八股】复习 MySQL Day3:锁

文章目录 昨日内容复习MySQL 使用 B 树作为索引的优势是什么&#xff1f;索引有哪几种&#xff1f;什么是最左匹配原则&#xff1f;索引区分度&#xff1f;联合索引如何排序&#xff1f;使用索引有哪些缺陷&#xff1f;什么时候需要建立索引&#xff0c;什么时候不需要&#xf…...

2025年KBS新算法 SCI1区TOP:长颖燕麦优化算法AOO,深度解析+性能实测

目录 1.摘要2.算法原理3.结果展示4.参考文献5.文章&代码获取 1.摘要 本文提出了一种新颖的元启发式算法——长颖燕麦优化算法&#xff08;AOO&#xff09;&#xff0c;该算法灵感来自动画燕麦在环境中的自然行为。AOO模拟了长颖燕麦的三种独特行为&#xff1a;(i) 通过自然…...

1.4 点云数据获取方式——结构光相机

图1-4-1结构光相机 结构光相机作为获取三维点云数据的关键设备,其工作原理基于主动式测量技术。通过投射已知图案,如条纹、点阵、格雷码等,至物体表面,这些图案会因物体表面的高度变化而发生变形。与此同时,利用相机从特定...

2025.4.29总结

工作&#xff1a;最近手头活变得多起来了&#xff0c;毕竟要测两个版本&#xff0c;有时候觉得很奇怪&#xff0c;活少的时候&#xff0c;又想让别人多分点活&#xff0c;活多的时候&#xff0c;又会有些许不自然。这种反差往往伴随着项目的节奏&#xff0c;伴随着两个极端。所…...

初探RAG

源码 核心工作流程 读取文件的内容将内容保存在向量数据库检索向量数据库用户的问题用户问题 上下文【向量数据】 > LLM 读取文件内容【pdf为例】 from pdfminer.high_level import extract_pages from pdfminer.layout import LTTextContainerclass PDFFileLoader():d…...

AIGC(生成式AI)技术全景图:从文本到图像的革命

AIGC&#xff08;生成式AI&#xff09;技术全景图&#xff1a;从文本到图像的革命 前言 生成式人工智能&#xff08;AIGC&#xff09;正以惊人的速度重塑数字内容的生产方式。从GPT系列模型的文本生成&#xff0c;到Stable Diffusion的图像创作&#xff0c;再到Sora的视频合成…...

通信协议:数字世界的隐形语言——从基础认知到工程实践-优雅草卓伊凡

通信协议&#xff1a;数字世界的隐形语言——从基础认知到工程实践-优雅草卓伊凡 一、理解通信协议&#xff1a;数字世界的”隐形语法” 1.1 通信协议的不可见性与现实存在 通信协议如同空气中的无线电波&#xff0c;虽然看不见摸不着&#xff0c;却实实在在支撑着现代数字世…...

RPC复习

RPC复习 RPC (远程过程调用) 全面解析一、RPC 定义与核心作用1. 什么是RPC&#xff1f;2. 核心作用 二、主流RPC框架对比三、RPC适用场景四、RPC的缺陷五、RPC vs REST vs GraphQL六、Java实现案例&#xff1a;使用Dubbo框架案例描述1. 环境准备2. 定义服务接口3. 服务提供方实…...

Express 文件上传不迷路:req.files 一次性讲明白

前言 在开发后台接口的江湖中,文件上传堪称“隐藏副本”,难度不大但坑点极多。本来只想优雅接收一张图片,结果 undefined、报错、路径错乱轮番登场,逼得人想重拾卖烤红薯的梦想。别慌,本文将用轻松幽默的方式,深入拆解 req.files.file 的每个属性,从前端表单到后台处理…...

Leetcode 3530. Maximum Profit from Valid Topological Order in DAG

Leetcode 3530. Maximum Profit from Valid Topological Order in DAG 1. 解题思路2. 代码实现 题目链接&#xff1a;3530. Maximum Profit from Valid Topological Order in DAG 1. 解题思路 这一题的整体思路就是一个动态规划的思路&#xff0c;我们只需要在当前可以访问的…...

Mysql中索引的知识

Mysql中的索引的定义和种类 核心概念&#xff1a;索引是什么&#xff1f; 想象一下你有一本很厚的书&#xff0c;你想找到其中关于某个特定主题的内容。你有两种方法&#xff1a; 从头到尾翻阅整本书&#xff1a;这就像数据库中的全表扫描 (Full Table Scan)。如果书很长&…...

VSCode Verilog编辑仿真环境搭建

VSCode Verilog环境搭建 下载Iverilog安装Iverilog验证安装VS Code安装插件 下载Iverilog 官网下载Iverilog 安装Iverilog 一定要勾选这两项 建议勾选这两项 验证安装 运行Windows PowerShell输入命令&#xff1a;iverilog输入命令&#xff1a;Get-Command gtkwave …...

linux修改环境变量

添加环境变量注意事项。 vim ~/.bashrc 添加环境变量时&#xff0c;需要source ~/.bashrc后才能有效。同时只对当前shell窗口有效&#xff0c;当打开另外的shell窗口时&#xff0c;需要重新source才能起效。 1.修改bashrc文件后 2.source后打开另一个shell窗口则无效&#xff…...

为什么要学习《金刚经》

《金刚经》作为佛教般若经典的核心&#xff0c;以"缘起性空"为思想根基&#xff0c;通过佛陀与须菩提的对话&#xff0c;揭示了破除执著、见真实相的智慧。 以下从核心要义、精髓段落和现实应用三个维度进行解读&#xff1a; 一、核心思想精髓 1. "凡所有相&am…...

【阿里云大模型高级工程师ACP习题集】2.7 通过微调增强模型能力 (上篇)(⭐️⭐️⭐️ 重点章节!!!)

习题集: 【单选题】在大模型微调中,与提示工程和RAG相比,微调的独特优势在于( ) A. 无需外部工具即可提升模型表现 B. 能让模型学习特定领域知识,提升底层能力 C. 可以更高效地检索知识 D. 能直接提升模型的知识边界,无需训练 【多选题】以下关于机器学习和传统编程的说…...

Docker 容器双网卡访问物理雷达网络教程

作者&#xff1a; 陈梓洋 环境&#xff1a; ubuntu 22.04lts 时间&#xff1a; 2025年4月29日 Docker 容器双网卡访问物理雷达网络教程 这个教程适用于这样的场景&#xff1a;容器保留原有 ROS 通信网络&#xff08;如 bridge 网络&#xff09;&#xff0c;同时需要访问一个物…...

C++:Lambda表达式

C&#xff1a;Lambda表达式 C中lambda的基本语法1. 捕获列表&#xff08;Capture List&#xff09;2. 示例代码示例 1&#xff1a;简单的lambda示例 2&#xff1a;捕获变量示例 3&#xff1a;按引用捕获示例 4&#xff1a;捕获所有变量示例 5&#xff1a;作为函数参数 3. lambd…...

Vim 中替换字符或文本

在 Vim 中替换字符或文本可以使用 替换命令&#xff08;substitute&#xff09;&#xff0c;其基本语法为&#xff1a; :[range]s/old/new/[flags]1. 基本替换 命令说明:s/foo/bar/替换当前行的第一个 foo 为 bar:s/foo/bar/g替换当前行的 所有 foo 为 bar:%s/foo/bar/g替换 …...

Thinkphp开发自适应职业学生证书查询系统职业资格等级会员证书管理网站

主要功能介绍 1.PHP MYSQL开发,开源,方便二次开发。 2.后台管理界面清新 3.可批量导入导出数据,格式为:JsoN、CSV、 Excel等。 4.自适应手机端,PC端 5.数据修改,添加,删除非常方便,手机上就可以解决 6.可以增加管理员权限等 7.界面可以个性定制开发...

OpenAI Embedding 和密集检索(如 BERT/DPR)进行语义相似度搜索有什么区别和联系

OpenAI Embedding 和密集检索&#xff08;如 BERT/DPR&#xff09;其实是“同一种思想的不同实现”&#xff0c;它们都属于Dense Retrieval&#xff08;密集向量检索&#xff09;&#xff0c;只不过使用的模型、部署方式和调用方式不同。 &#x1f9e0; 首先搞清楚&#xff1a;…...

C语言复习笔记--数据在内存中的存储

今天我们来复习一下数据在内存中的存储方式.话不多说进入正题. 整数在内存中的存储 整数的2进制表⽰⽅法有三种&#xff0c;即原码、反码和补码.三种表⽰⽅法均有符号位和数值位两部分&#xff0c;符号位都是⽤0表⽰“正”&#xff0c;⽤1表⽰“负”&#xff0c;⽽数值位最⾼位…...

笔试专题(十二)

文章目录 主持人调度题解代码 小红的ABC题解代码 不相邻取数题解代码 空调遥控题解代码 主持人调度 题目链接 题解 1. 排序 2. 先按左端点的大小进行排序&#xff0c;保证时间是连续的&#xff0c;如果后一个点的左端点大于等于前一个点的右端点就是和法的&#xff0c;否则…...

Tauri(2.5.1)+Leptos(0.7.8)开发桌面应用---后台调用Python Matplotlib绘制图形

Rust语言最接近Python Matplotlib绘图库的应该是Plotters&#xff0c;但是试用下来还是没有Matplotlib效果好&#xff0c;所以尝试在Tauri Leptos项目中&#xff0c;后台调用Python Matplotlib绘制图形&#xff0c;并返回给前端Leptos展示。 具体效果如下&#xff1a; 1. 前端…...

Qemu-STM32(十七):STM32F103加入AFIO控制器

概述 本文主要描述了在Qemu平台中&#xff0c;如何添加STM32F103的AFIO控制器模拟代码&#xff0c;AFIO是属于GPIO引脚复用配置的功能。 参考资料 STM32F1XX TRM手册&#xff0c;手册编号&#xff1a;RM0008 添加步骤 1、在hw/arm/Kconfig文件中添加STM32F1XX_AFIO&#x…...

刀客doc:小红书商业技术负责人苍响离职

根据大厂日爆的爆料&#xff0c;小红书商业化再度迎来高层人事变动&#xff0c;原商业平台技术负责人苍响&#xff08;薯名&#xff09;&#xff0c;职级L2&#xff0c;已于本月正式离职&#xff0c;其下属团队现由电商业务负责人接管。 根据刀客doc获得的资料&#xff0c;苍响…...

CC52.【C++ Cont】滑动窗口

目录 1.题目 2.分析 方法1:暴力枚举 方法2:暴力枚举的优化:"同向双指针",也称滑动窗口 前置知识 核心操作 例子解释 代码 提交结果 1.题目 LCR 008. 长度最小的子数组 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其和 ≥ target…...

linux中由于编译选项-D_OS64BIT导致的核心已转储问题

linux中由于编译选项-D_OS64BIT导致的核心已转储问题排查解决&#xff1a; 原因&#xff1a; a.so b.so a.so使用b.so 程序1 程序2 使用a.so 程序1运行正常&#xff0c;程序2启动后提示核心已转储。 程序1和程序2运行的代码都一致&#xff0c;只执行创建xApplication app&…...

Ubuntu搭建 Nginx以及Keepalived 实现 主备

目录 前言1. 基本知识2. Keepalived3. 脚本配置4. Nginx前言 🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF 爬虫神器,无代码爬取,就来:bright.cn Java基本知识: java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)【Java项目】实战CRU…...

Knife4j 接口文档添加登录验证流程分析

Knife4j 接口文档添加登录验证流程是非常必要的&#xff0c;否则接口文档一旦暴露到外面是很危险的&#xff0c;今天我们详细分析一下。在1.9.6的版本时&#xff0c;作者把swagger-bootstrap-ui项目重命名为Knife4j&#xff0c;今天分析2.0.6版本。 1、pom.xml文件引入 <pa…...

C++之string

string 是C中常见的一个用于处理和操作字符串的类。一直有用它来存储字符串&#xff0c;今天来介绍介绍一下它的定义和一些基本用法吧。 1、头文件 #include <string>2、定义和初始化 #include <iostream> #include <string> using namespace std;int main…...

【含文档+PPT+源码】基于SSM的电影数据挖掘与分析可视化系统设计与实现

项目介绍 本课程演示的是一款基于SSM的电影数据挖掘与分析可视化系统设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本…...

DeepSeek提示词技巧

以下是使用Deepseek等AI工具时提升效果的提示词模式总结&#xff0c;涵盖核心规律、应用场景及效果对比&#xff1a; 规律一&#xff1a;结构化提示&#xff08;解决模糊性问题&#xff09; 问题&#xff1a;开放式问题导致回答笼统 模式&#xff1a;角色任务约束输出格式 例子…...

软考高项(信息系统项目管理师)第 4 版全章节核心考点解析(第4版课程精华版)

一、核心输入输出速记体系&#xff08;力扬老师独家口诀&#xff09; &#xff08;一&#xff09;规划阶段万能输入&#xff08;4 要素&#xff09; 口诀&#xff1a;章程计划&#xff0c;组织事业 ✅ 精准对应&#xff08;ITTO 核心输入&#xff09;&#xff1a; 章程&#…...

【linux网络】网络基础概念

1. 初始协议 1.1 OSI 七层模型 OSI&#xff08;Open System Interconnection&#xff0c;开放系统互连&#xff09;七层网络模型称为开放式系统互联参考模型&#xff0c;是一个逻辑上的定义和规范&#xff1b; 把网络从逻辑上分为了 7 层. 每一层都有相关、相对应的物理设备&a…...

【PyTorch动态计算图实战解析】从原理到高效开发

目录 前言技术背景与价值当前技术痛点解决方案概述目标读者说明一、技术原理剖析核心概念图解核心作用讲解关键技术模块说明技术选型对比二、实战演示环境配置要求核心代码实现案例1:梯度计算可视化案例2:动态控制流案例3:自定义反向传播运行结果验证三、性能对比测试方法论…...

【专题五】位运算(1):常见位运算操作总结

&#x1f4dd;前言说明&#xff1a; 本专栏主要记录本人的基础算法学习以及LeetCode刷题记录&#xff0c;按专题划分每题主要记录&#xff1a;&#xff08;1&#xff09;本人解法 本人屎山代码&#xff1b;&#xff08;2&#xff09;优质解法 优质代码&#xff1b;&#xff…...

react-native-vector-icons打包报错并且提示:copyReactNativeVectorIconFonts相关信息

使用react-native-vector-icons&#xff0c;打包时会报&#xff1a; FAILURE: Build failed with an exception. * What went wrong: Some problems were found with the configuration of task :app:copyReactNativeVec torIconFonts (type Copy).- Gradle detected a proble…...

【Sqlalchemy Model转换成Pydantic Model示例】

【Sqlalchemy Model转换成Pydantic Model示例】 由于Sqlalchemy和Pydantic的模型字段类型可能有差异, 所以需要一个通用的装换类 def sqlalchemy_to_pydantic_v2(sqlalchemy_model, pydantic_model):"""通用函数&#xff0c;将 SQLAlchemy 模型实例转换为 Pyd…...

探索语音增强中的多尺度时间频率卷积网络(TFCM):代码解析与概念介绍

探索语音增强中的多尺度时间频率卷积网络&#xff08;TFCM&#xff09;&#xff1a;代码解析与概念介绍 在现代音频处理领域&#xff0c;语音增强技术始终是一个备受关注的研究热点。其主要目标是通过去除背景噪声、回声或其他混杂信号&#xff0c;提升语音通话或录音的质量。…...

私有知识库 Coco AI 实战(四):打造 ES 索引参数小助手

经过前面的实战&#xff0c;我们已经能够快速、准确的检索 ES 官方文档的内容了&#xff0c;但是还是要自己去阅读。既然 Coco AI 能与大模型交互&#xff0c;我们何不直接用大模型来阅读文档&#xff0c;回答我们的问题&#xff1f; 模型配置 我们进入 Coco AI 管理后台&…...

苍穹外卖心得体会

1 登录认证 技术点&#xff1a;JWT令牌技术&#xff08;JSON Web Token&#xff09; JWT&#xff08;JSON Web Token&#xff09;是一种令牌技术&#xff0c;主要由三部分组成&#xff1a;Header头部、Payload载荷和Signature签名。Header头部存储令牌的类型&#xff08;如JW…...

Ubuntu上搭建python环境并安装第三方库

Ubuntu上搭建python环境并安装第三方库 更新系统包列表安装Python和pip验证安装创建并使用虚拟环境虚拟环境中安装第三方库编写并测试python脚本运行.py文件 使用Ubuntu 24.04搭建python环境并安装第三方库 更新系统包列表 sudo apt update安装Python和pip sudo apt install…...

redis高阶2 高性能

灵魂拷问&#xff1a; redis 为什么块&#xff1f; 纯内存访问单线程避免上下文切换渐进式ReHash , 缓存时间戳 Redis 的 key 和 value组织结构&#xff1a; 全局哈希表&#xff1a; 为了实现从键到值的快速访问。 redis 使用了一个哈希表 来保存所有键值对。一个哈希表 其实就…...