提交 60c7b9bd 编写于 作者: 小傅哥's avatar 小傅哥

feat:更新功能

上级
# 命令执行 docker-compose up -d
version: '3.9'
services:
mysql:
image: mysql:8.0.32
container_name: mysql
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
TZ: Asia/Shanghai
# MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' # 可配置无密码,注意配置 SPRING_DATASOURCE_PASSWORD=
MYSQL_ROOT_PASSWORD: 123456
MYSQL_USER: xfg
MYSQL_PASSWORD: 123456
depends_on:
- mysql-job-dbdata
ports:
- "13306:3306"
volumes:
- ./sql:/docker-entrypoint-initdb.d
volumes_from:
- mysql-job-dbdata
# 自动加载数据
mysql-job-dbdata:
image: alpine:3.18.2
container_name: mysql-job-dbdata
volumes:
- /var/lib/mysql
redis:
image: redis:7.2.0
container_name: redis
ports:
- 6379:6379
volumes:
- ./redis.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
\ No newline at end of file
bind 0.0.0.0
port 6379
requirepass 123456
\ No newline at end of file
# ************************************************************
# Sequel Ace SQL dump
# 版本号: 20050
#
# https://sequel-ace.com/
# https://github.com/Sequel-Ace/Sequel-Ace
#
# 主机: localhost (MySQL 8.0.32)
# 数据库: road_map
# 生成时间: 2023-08-13 10:25:48 +0000
# ************************************************************
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
SET NAMES utf8mb4;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE='NO_AUTO_VALUE_ON_ZERO', SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
# 转储表 user_order
# ------------------------------------------------------------
CREATE database if NOT EXISTS `road_map` default character set utf8mb4 collate utf8mb4_0900_ai_ci;
use `road_map`;
DROP TABLE IF EXISTS `user_order`;
CREATE TABLE `user_order` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID;【必须保留自增ID,不要将一些有随机特性的字段值设计为主键,例如order_id,会导致innodb内部page分裂和大量随机I/O,性能下降】int 大约21亿左右,超过会报错。bigint 大约9千亿左右。',
`user_name` varchar(64) NOT NULL COMMENT '用户姓名;',
`user_id` varchar(24) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户编号;',
`user_mobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户电话;使用varchar(20)存储手机号,不要使用整型。手机号不会做数学计算、涉及到区号或者国家代号,可能出现+-()、支持模糊查询,例如:like“135%”',
`sku` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品编号',
`sku_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品名称',
`order_id` varchar(64) NOT NULL COMMENT '订单ID',
`quantity` int NOT NULL DEFAULT '1' COMMENT '商品数量;整形定义中不显示规定显示长度,比如使用 INT,而不使用 INT(4)',
`unit_price` decimal(10,2) NOT NULL COMMENT '商品价格;小数类型为 decimal,禁止使用 float、double',
`discount_amount` decimal(10,2) NOT NULL COMMENT '折扣金额;',
`tax` decimal(4,2) NOT NULL COMMENT '费率金额;',
`total_amount` decimal(10,2) NOT NULL COMMENT '支付金额;(商品的总金额 - 折扣) * (1 - 费率)',
`order_date` datetime NOT NULL COMMENT '订单日期;timestamp的时间范围在1970-01-01 00:00:01到2038-01-01 00:00:00之间',
`order_status` tinyint(1) NOT NULL COMMENT '订单状态;0 创建、1完成、2掉单、3关单 【不要使用 enum 要使用 tinyint 替代。0-80 范围,都可以使用 tinyint】',
`is_delete` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删单;0未删除,1已删除 【表达是否概念的字段必须使用is_】',
`uuid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '唯一索引;分布式下全局唯一,用于binlog 同步 ES 方便使用',
`ipv4` int unsigned NOT NULL DEFAULT '2130706433' COMMENT '设备地址;存储IPV4地址,通过MySQL 函数转换,inet_ntoa、inet_aton 示例;SELECT INET_ATON(‘209.207.224.40′); 3520061480 SELECT INET_NTOA(3520061480); 209.207.224.40所有字段定义为NOT NULL,并设置默认值,因为null值的字段会导致每一行都占用额外存储空间\\n数据迁移容易出错,在聚合函数计算结果偏差(如count结果不准)并且null的列使索引/索引统计/值比较都更加复杂,MySQL内部需要进行特殊处理,表中有较多空字段的时候,数据库性能下降严重。开发中null只能采用is null或is not null检索,而不能采用=、in、<、<>、!=、not in这些操作符号。如:where name!=’abc’,如果存在name为null值的记录,查询结果就不会包含name为null值的记录',
`ipv6` varbinary(16) NOT NULL COMMENT '设备地址;存储IPV6地址,VARBINARY(16) 插入:INET6_ATON(''2001:0db8:85a3:0000:0000:8a2e:0370:7334'') 查询:SELECT INET6_NTOA(ip_address) ',
`ext_data` json NOT NULL COMMENT '扩展数据;记录下单时用户的设备环境等信息(核心业务字段,要单独拆表)。【select user_name, ext_data, ext_data->>''$.device'', ext_data->>''$.device.machine'' from `user_order`;】',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uq_orderid` (`order_id`),
UNIQUE KEY `uq_uuid` (`uuid`),
KEY `idx_order_date` (`order_date`),
KEY `idx_sku_unit_price_total_amount` (`sku`,`unit_price`,`total_amount`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
LOCK TABLES `user_order` WRITE;
/*!40000 ALTER TABLE `user_order` DISABLE KEYS */;
INSERT INTO `user_order` (`id`, `user_name`, `user_id`, `user_mobile`, `sku`, `sku_name`, `order_id`, `quantity`, `unit_price`, `discount_amount`, `tax`, `total_amount`, `order_date`, `order_status`, `is_delete`, `uuid`, `ipv4`, `ipv6`, `ext_data`, `update_time`, `create_time`)
VALUES
(1,'小傅哥','U001','13512345678','SKU001','Mac Pro M2 贴膜','ORD001',2,10.99,2.00,0.50,19.48,'2023-08-12 10:00:00',0,0,'uuid001',2130706433,X'20010DB885A3000000008A2E03707334','{\"device\": {\"machine\": \"IPhone 14 Pro\", \"location\": \"shanghai\"}}','2023-08-12 10:00:00','2023-08-12 10:00:00'),
(2,'福禄娃','U002','13698765432','SKU002','IPad mini4 外套','ORD002',1,25.99,0.00,1.50,24.49,'2023-08-12 11:30:00',1,0,'uuid002',2130706433,X'20010DB885A3000000008A2E03707334','{\"device\": {\"machine\": \"PC Windows\", \"location\": \"BeiJing\"}}','2023-08-12 11:30:00','2023-08-12 11:30:00'),
(3,'拎瓢冲','U003','13755555555','SKU003','数据线','ORD003',3,9.99,1.50,0.00,26.97,'2023-08-12 13:45:00',0,0,'uuid003',2130706433,X'20010DB885A3000000008A2E03707334','{\"device\": {\"machine\": \"PC Windows\", \"location\": \"BeiJing\"}}','2023-08-12 13:45:00','2023-08-12 13:45:00'),
(4,'熏5null','U004','13812345678','SKU004','U盘','ORD004',1,15.99,0.00,0.75,15.24,'2023-08-12 14:20:00',1,0,'uuid004',2130706433,X'20010DB885A3000000008A2E03707334','{\"device\": {\"machine\": \"PC Windows\", \"location\": \"BeiJing\"}}','2023-08-12 14:20:00','2023-08-12 14:20:00'),
(5,'温柔一刀','U005','13999999999','SKU005','坐垫','ORD005',2,12.50,1.25,0.25,23.75,'2023-08-12 15:55:00',0,0,'uuid005',2130706433,X'20010DB885A3000000008A2E03707334','{\"device\": {\"machine\": \"PC Windows\", \"location\": \"BeiJing\"}}','2023-08-12 15:55:00','2023-08-12 15:55:00'),
(9,'小傅哥','U001','13512345678','SKU001','Mac Pro M2 贴膜','ORD0101',2,10.99,2.00,0.50,19.48,'2023-08-12 10:00:00',0,0,'uuid010',2130706433,X'20010DB885A3000000008A2E03707334','{\"device\": {\"machine\": \"IPhone 14 Pro\", \"location\": \"shanghai\"}}','2023-08-12 10:00:00','2023-08-12 10:00:00');
/*!40000 ALTER TABLE `user_order` ENABLE KEYS */;
UNLOCK TABLES;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.bugstack</groupId>
<artifactId>xfg-dev-tech-redis</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>xfg-dev-tech-app</module>
<module>xfg-dev-tech-infrastructure</module>
<module>xfg-dev-tech-domain</module>
<module>xfg-dev-tech-trigger</module>
<module>xfg-dev-tech-types</module>
</modules>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.12</version>
<relativePath/>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!-- # 多数据源路由配置
# mysql 5.x driver-class-name: com.mysql.jdbc.Driver mysql-connector-java 5.1.34
# mysql 8.x driver-class-name: com.mysql.cj.jdbc.Driver mysql-connector-java 8.0.22-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.28</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.4</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>xfg-dev-tech</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.5</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- 统一设定POM版本信息插件 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.7</version>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bugstack</groupId>
<artifactId>xfg-dev-tech-redis</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>xfg-dev-tech-app</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- # 多数据源路由配置
# mysql 5.x driver-class-name: com.mysql.jdbc.Driver mysql-connector-java 5.1.34
# mysql 8.x driver-class-name: com.mysql.cj.jdbc.Driver mysql-connector-java 8.0.22-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.bugstack</groupId>
<artifactId>xfg-dev-tech-infrastructure</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.bugstack</groupId>
<artifactId>xfg-dev-tech-trigger</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>xfg-dev-tech-app</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/**</include>
</includes>
</resource>
</resources>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/**</include>
</includes>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.6</version>
<configuration>
<skipTests>true</skipTests>
<testFailureIgnore>false</testFailureIgnore>
<includes>
<include>**/*Test.java</include>
</includes>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>cn.bugstack.xfg.dev.tech.Application</mainClass>
<layout>JAR</layout>
</configuration>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package cn.bugstack.xfg.dev.tech;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 应用入口
* @create 2023-09-03 14:06
*/
@SpringBootApplication
@Configurable
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class);
}
}
package cn.bugstack.xfg.dev.tech.config;
import cn.bugstack.xfg.dev.tech.infrastructure.trigger.mq.RedisTopicListener01;
import cn.bugstack.xfg.dev.tech.types.RedisTopic;
import org.redisson.Redisson;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.redisson.api.listener.MessageListener;
import org.redisson.config.Config;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description Redis 客户端,使用 Redisson <a href="https://github.com/redisson/redisson">Redisson</a>
* @create 2023-09-03 16:51
*/
@Configuration
@EnableConfigurationProperties(RedisClientConfigProperties.class)
public class RedisClientConfig {
@Bean("redissonClient")
public RedissonClient redissonClient(ConfigurableApplicationContext applicationContext, RedisClientConfigProperties properties) {
Config config = new Config();
// 根据需要可以设定编解码器;https://github.com/redisson/redisson/wiki/4.-%E6%95%B0%E6%8D%AE%E5%BA%8F%E5%88%97%E5%8C%96
// config.setCodec(new RedisCodec());
config.useSingleServer()
.setAddress("redis://" + properties.getHost() + ":" + properties.getPort())
.setPassword(properties.getPassword())
.setConnectionPoolSize(properties.getPoolSize())
.setConnectionMinimumIdleSize(properties.getMinIdleSize())
.setIdleConnectionTimeout(properties.getIdleTimeout())
.setConnectTimeout(properties.getConnectTimeout())
.setRetryAttempts(properties.getRetryAttempts())
.setRetryInterval(properties.getRetryInterval())
.setPingConnectionInterval(properties.getPingInterval())
.setKeepAlive(properties.isKeepAlive())
;
RedissonClient redissonClient = Redisson.create(config);
// 添加监听
String[] beanNamesForType = applicationContext.getBeanNamesForType(MessageListener.class);
for (String beanName : beanNamesForType) {
MessageListener bean = applicationContext.getBean(beanName, MessageListener.class);
Class<?> beanClass = bean.getClass();
if (beanClass.isAnnotationPresent(RedisTopic.class)) {
RedisTopic redisTopic = beanClass.getAnnotation(RedisTopic.class);
RTopic topic = redissonClient.getTopic(redisTopic.topic());
topic.addListener(String.class, bean);
// 动态创建 bean 对象,注入到 spring 容器,bean 的名称为 redisTopic,对象为 RTopic
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
beanFactory.registerSingleton(redisTopic.topic(), topic);
}
}
return redissonClient;
}
/**
* 手动配置
*/
@Bean("testRedisTopic")
public RTopic testRedisTopicListener(RedissonClient redissonClient, RedisTopicListener01 redisTopicListener) {
RTopic topic = redissonClient.getTopic("xfg-dev-tech-topic");
topic.addListener(String.class, redisTopicListener);
return topic;
}
}
package cn.bugstack.xfg.dev.tech.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description Redis 连接配置 <a href="https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter">redisson-spring-boot-starter</a>
* @create 2023-09-03 16:51
*/
@Data
@ConfigurationProperties(prefix = "redis.sdk.config", ignoreInvalidFields = true)
public class RedisClientConfigProperties {
/** host:ip */
private String host;
/** 端口 */
private int port;
/** 账密 */
private String password;
/** 设置连接池的大小,默认为64 */
private int poolSize = 64;
/** 设置连接池的最小空闲连接数,默认为10 */
private int minIdleSize = 10;
/** 设置连接的最大空闲时间(单位:毫秒),超过该时间的空闲连接将被关闭,默认为10000 */
private int idleTimeout = 10000;
/** 设置连接超时时间(单位:毫秒),默认为10000 */
private int connectTimeout = 10000;
/** 设置连接重试次数,默认为3 */
private int retryAttempts = 3;
/** 设置连接重试的间隔时间(单位:毫秒),默认为1000 */
private int retryInterval = 1000;
/** 设置定期检查连接是否可用的时间间隔(单位:毫秒),默认为0,表示不进行定期检查 */
private int pingInterval = 0;
/** 设置是否保持长连接,默认为true */
private boolean keepAlive = true;
}
package cn.bugstack.xfg.dev.tech.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import org.redisson.client.codec.BaseCodec;
import org.redisson.client.protocol.Decoder;
import org.redisson.client.protocol.Encoder;
import java.io.IOException;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description Redisson 编码器;<a href="https://github.com/redisson/redisson/wiki/4.-%E6%95%B0%E6%8D%AE%E5%BA%8F%E5%88%97%E5%8C%96">redisson 数据序列化</a>
* @create 2023-09-03 16:51
*/
public class RedisCodec extends BaseCodec {
private final Encoder encoder = in -> {
ByteBuf out = ByteBufAllocator.DEFAULT.buffer();
try {
ByteBufOutputStream os = new ByteBufOutputStream(out);
JSON.writeJSONString(os, in, SerializerFeature.WriteClassName);
return os.buffer();
} catch (IOException e) {
out.release();
throw e;
} catch (Exception e) {
out.release();
throw new IOException(e);
}
};
private final Decoder<Object> decoder = (buf, state) -> JSON.parseObject(new ByteBufInputStream(buf), Object.class);
@Override
public Decoder<Object> getValueDecoder() {
return decoder;
}
@Override
public Encoder getValueEncoder() {
return encoder;
}
}
package cn.bugstack.xfg.dev.tech.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.concurrent.*;
@Slf4j
@EnableAsync
@Configuration
@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
public class ThreadPoolConfig {
@Bean
@ConditionalOnMissingBean(ThreadPoolExecutor.class)
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties properties) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// 实例化策略
RejectedExecutionHandler handler;
switch (properties.getPolicy()){
case "AbortPolicy":
handler = new ThreadPoolExecutor.AbortPolicy();
break;
case "DiscardPolicy":
handler = new ThreadPoolExecutor.DiscardPolicy();
break;
case "DiscardOldestPolicy":
handler = new ThreadPoolExecutor.DiscardOldestPolicy();
break;
case "CallerRunsPolicy":
handler = new ThreadPoolExecutor.CallerRunsPolicy();
break;
default:
handler = new ThreadPoolExecutor.AbortPolicy();
break;
}
// 创建线程池
return new ThreadPoolExecutor(properties.getCorePoolSize(),
properties.getMaxPoolSize(),
properties.getKeepAliveTime(),
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(properties.getBlockQueueSize()),
Executors.defaultThreadFactory(),
handler);
}
}
package cn.bugstack.xfg.dev.tech.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "thread.pool.executor.config", ignoreInvalidFields = true)
public class ThreadPoolConfigProperties {
/** 核心线程数 */
private Integer corePoolSize = 20;
/** 最大线程数 */
private Integer maxPoolSize = 200;
/** 最大等待时间 */
private Long keepAliveTime = 10L;
/** 最大队列数 */
private Integer blockQueueSize = 5000;
/*
* AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
* DiscardPolicy:直接丢弃任务,但是不会抛出异常
* DiscardOldestPolicy:将最早进入队列的任务删除,之后再尝试加入队列的任务被拒绝
* CallerRunsPolicy:如果任务添加线程池失败,那么主线程自己执行该任务
* */
private String policy = "AbortPolicy";
}
server:
port: 8091
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:13306/road_map?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&serverTimezone=UTC&useSSL=true
driver-class-name: com.mysql.cj.jdbc.Driver
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat
mybatis:
mapper-locations: classpath:/mybatis/mapper/*.xml
config-location: classpath:/mybatis/config/mybatis-config.xml
redis:
sdk:
config:
host: localhost
port: 6379
password: 123456
pool-size: 10
min-idle-size: 5
idle-timeout: 30000
connect-timeout: 5000
retry-attempts: 3
retry-interval: 1000
ping-interval: 60000
keep-alive: true
thread:
pool:
executor:
config:
core-pool-size: 20
max-pool-size: 200
keep-alive-time: 10
block-queue-size: 5000
policy: CallerRunsPolicy
logging:
level:
root: info
config: classpath:logback-spring.xml
\ No newline at end of file
server:
port: 8091
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:13306/road_map?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&serverTimezone=UTC&useSSL=true
driver-class-name: com.mysql.cj.jdbc.Driver
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat
mybatis:
mapper-locations: classpath:/mybatis/mapper/*.xml
config-location: classpath:/mybatis/config/mybatis-config.xml
logging:
level:
root: info
config: classpath:logback-spring.xml
\ No newline at end of file
server:
port: 8091
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:13306/road_map?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&serverTimezone=UTC&useSSL=true
driver-class-name: com.mysql.cj.jdbc.Driver
# SpringBoot 默认配置的连接池 Hikari 可以不用指定,这里指定只是想告诉它是通过哪个数据源实例化的
type: com.zaxxer.hikari.HikariDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat
mybatis:
mapper-locations: classpath:/mybatis/mapper/*.xml
config-location: classpath:/mybatis/config/mybatis-config.xml
logging:
level:
root: info
config: classpath:logback-spring.xml
\ No newline at end of file
spring:
config:
name: xfg-dev-tech
profiles:
active: dev
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<configuration scan="true" scanPeriod="10 seconds">
<contextName>logback</contextName>
<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
<springProperty scope="context" name="log.path" source="logging.path"/>
<!-- 日志格式 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- 输出到控制台 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- 此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>info</level>
</filter>
<encoder>
<pattern>%d{yy-MM-dd.HH:mm:ss.SSS} [%-16t] %-5p %-22c{0}%X{ServiceId} -%X{trace-id} %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!--输出到文件-->
<!-- 时间滚动输出 level为 INFO 日志 -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>./data/log/log_info.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yy-MM-dd.HH:mm:ss.SSS} [%-16t] %-5p %-22c{0}%X{ServiceId} -%X{trace-id} %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志归档路径以及格式 -->
<fileNamePattern>./data/log/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
</appender>
<!-- 时间滚动输出 level为 ERROR 日志 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>./data/log/log_error.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yy-MM-dd.HH:mm:ss.SSS} [%-16t] %-5p %-22c{0}%X{ServiceId} -%X{trace-id} %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./data/log/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 日志文件保留天数【根据服务器预留,可自行调整】 -->
<maxHistory>7</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
<!-- WARN 级别及以上 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
</appender>
<!-- 异步输出 -->
<appender name="ASYNC_FILE_INFO" class="ch.qos.logback.classic.AsyncAppender">
<!-- 队列剩余容量小于discardingThreshold,则会丢弃TRACT、DEBUG、INFO级别的日志;默认值-1,为queueSize的20%;0不丢失日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>8192</queueSize>
<!-- neverBlock:true 会丢失日志,但业务性能不受影响 -->
<neverBlock>true</neverBlock>
<!--是否提取调用者数据-->
<includeCallerData>false</includeCallerData>
<appender-ref ref="INFO_FILE"/>
</appender>
<appender name="ASYNC_FILE_ERROR" class="ch.qos.logback.classic.AsyncAppender">
<!-- 队列剩余容量小于discardingThreshold,则会丢弃TRACT、DEBUG、INFO级别的日志;默认值-1,为queueSize的20%;0不丢失日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>1024</queueSize>
<!-- neverBlock:true 会丢失日志,但业务性能不受影响 -->
<neverBlock>true</neverBlock>
<!--是否提取调用者数据-->
<includeCallerData>false</includeCallerData>
<appender-ref ref="ERROR_FILE"/>
</appender>
<!-- 开发环境:控制台打印 -->
<springProfile name="dev">
<logger name="com.nmys.view" level="debug"/>
</springProfile>
<root level="info">
<appender-ref ref="CONSOLE"/>
<!-- 异步日志-INFO -->
<appender-ref ref="ASYNC_FILE_INFO"/>
<!-- 异步日志-ERROR -->
<appender-ref ref="ASYNC_FILE_ERROR"/>
</root>
</configuration>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 暂时未使用 文档:https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases -->
<typeAliases>
</typeAliases>
</configuration>
\ No newline at end of file
<?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="cn.bugstack.xfg.dev.tech.infrastructure.persistent.dao.IUserOrderDao">
<resultMap id="dataMap" type="cn.bugstack.xfg.dev.tech.infrastructure.persistent.po.UserOrderPO">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
<result column="user_id" property="userId"/>
<result column="user_mobile" property="userMobile"/>
<result column="sku" property="sku"/>
<result column="sku_name" property="skuName"/>
<result column="order_id" property="orderId"/>
<result column="quantity" property="quantity"/>
<result column="unit_price" property="unitPrice"/>
<result column="discount_amount" property="discountAmount"/>
<result column="tax" property="tax"/>
<result column="total_amount" property="totalAmount"/>
<result column="order_date" property="orderDate"/>
<result column="order_status" property="orderStatus"/>
<result column="is_delete" property="isDelete"/>
<result column="uuid" property="uuid"/>
<result column="ipv4" property="ipv4"/>
<result column="ipv6" property="ipv6"/>
<result column="ext_data" property="extData"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<select id="insert" parameterType="cn.bugstack.xfg.dev.tech.infrastructure.persistent.po.UserOrderPO">
INSERT INTO user_order (user_name, user_id, user_mobile, sku, sku_name,
order_id, quantity, unit_price, discount_amount, tax,
total_amount, order_date, order_status, is_delete, uuid,
ipv4, ipv6, ext_data, update_time, create_time)
VALUES (#{userName}, #{userId}, #{userMobile}, #{sku}, #{skuName},
#{orderId}, #{quantity}, #{unitPrice}, #{discountAmount}, #{tax},
#{totalAmount}, #{orderDate}, #{orderStatus}, #{isDelete}, #{uuid},
INET_ATON(#{ipv4}), INET6_ATON(#{ipv6}), #{extData}, now(), now())
</select>
<update id="updateOrderStatusByUserMobile" parameterType="java.lang.String">
UPDATE user_order SET order_status = 2 WHERE user_mobile = #{userMobile}
</update>
<update id="updateOrderStatusByUserId" parameterType="java.lang.String">
UPDATE user_order SET order_status = 1 WHERE user_id = #{userId}
</update>
<update id="updateOrderStatusByOrderId" parameterType="java.lang.String">
UPDATE user_order SET order_status = 1 WHERE order_id = #{orderId}
</update>
<select id="queryMaxId" resultType="java.lang.Long">
SELECT max(id) FROM user_order
</select>
<select id="selectById" parameterType="java.lang.Long" resultMap="dataMap">
SELECT id, user_name, user_id, user_mobile, sku, sku_name,
order_id, quantity, unit_price, discount_amount, tax,
total_amount, order_date, order_status, is_delete, uuid,
ipv4, ipv6, ext_data, update_time, create_time
FROM user_order
WHERE id = #{id}
</select>
<select id="selectByUserId" parameterType="java.lang.String" resultMap="dataMap">
SELECT id, user_name, user_id, user_mobile, sku, sku_name,
order_id, quantity, unit_price, discount_amount, tax,
total_amount, order_date, order_status, is_delete, uuid,
ipv4, ipv6, ext_data, update_time, create_time
FROM user_order
WHERE user_id = #{userId}
</select>
<select id="selectByUserMobile" parameterType="java.lang.String" resultMap="dataMap">
SELECT id, user_name, user_id, user_mobile, sku, sku_name,
order_id, quantity, unit_price, discount_amount, tax,
total_amount, order_date, order_status, is_delete, uuid,
ipv4, ipv6, ext_data, update_time, create_time
FROM user_order
WHERE user_mobile = #{userMobile}
</select>
<select id="selectByOrderId" parameterType="cn.bugstack.xfg.dev.tech.infrastructure.persistent.po.UserOrderPO"
resultMap="dataMap">
SELECT id, user_name, user_id, user_mobile, sku, sku_name,
order_id, quantity, unit_price, discount_amount, tax,
total_amount, order_date, order_status, is_delete, uuid,
ipv4, ipv6, ext_data, update_time, create_time
FROM user_order
WHERE order_id = #{orderId}
</select>
<select id="selectByOrderIdAndUserId" parameterType="cn.bugstack.xfg.dev.tech.infrastructure.persistent.po.UserOrderPO"
resultMap="dataMap">
SELECT id, user_name, user_id, user_mobile, sku, sku_name,
order_id, quantity, unit_price, discount_amount, tax,
total_amount, order_date, order_status, is_delete, uuid,
ipv4, ipv6, ext_data, update_time, create_time
FROM user_order
WHERE order_id = #{orderId} and user_id = #{userId}
</select>
<select id="selectByUserIdAndOrderId" parameterType="cn.bugstack.xfg.dev.tech.infrastructure.persistent.po.UserOrderPO"
resultMap="dataMap">
SELECT id, user_name, user_id, user_mobile, sku, sku_name,
order_id, quantity, unit_price, discount_amount, tax,
total_amount, order_date, order_status, is_delete, uuid,
ipv4, ipv6, ext_data, update_time, create_time
FROM user_order
WHERE user_id = #{userId} and order_id = #{orderId}
</select>
</mapper>
package cn.bugstack.xfg.dev.tech.test;
import cn.bugstack.xfg.dev.tech.infrastructure.persistent.dao.IUserOrderDao;
import cn.bugstack.xfg.dev.tech.infrastructure.persistent.po.UserOrderPO;
import cn.bugstack.xfg.dev.tech.infrastructure.redis.IRedisService;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.redisson.api.RedissonClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.UUID;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 单元测试
* @create 2023-09-03 14:08
*/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApiTest {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.print("\r\033[31m" + i + "\033[0m");
try {
Thread.sleep(1000); // 暂停1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Resource
private IUserOrderDao userOrderDao;
@Resource
private IRedisService redissonService;
@Test
public void test_redissonClient() {
redissonService.setValue("xfg", "test123");
String xfg = redissonService.getValue("xfg");
log.info("测试结果:{}", xfg);
}
@Test
public void test_select() {
UserOrderPO userOrderPO = userOrderDao.selectByOrderId("ORD001");
log.info("测试结果: {}", JSON.toJSONString(userOrderPO));
}
@Test
public void test_insert() {
UserOrderPO userOrderPO = UserOrderPO.builder()
.userName("小傅哥")
.userId("xfg".concat(RandomStringUtils.randomNumeric(3)))
.userMobile("+86 13521408***")
.sku("13811216")
.skuName("《手写MyBatis:渐进式源码实践》")
.orderId(RandomStringUtils.randomNumeric(11))
.quantity(1)
.unitPrice(BigDecimal.valueOf(128))
.discountAmount(BigDecimal.valueOf(50))
.tax(BigDecimal.ZERO)
.totalAmount(BigDecimal.valueOf(78))
.orderDate(new Date())
.orderStatus(0)
.isDelete(0)
.uuid(UUID.randomUUID().toString().replace("-", ""))
.ipv4("127.0.0.1")
.ipv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334".getBytes())
.extData("{\"device\": {\"machine\": \"IPhone 14 Pro\", \"location\": \"shanghai\"}}")
.build();
userOrderDao.insert(userOrderPO);
}
}
package cn.bugstack.xfg.dev.tech.test.domain;
import cn.bugstack.xfg.dev.tech.domain.order.model.aggregate.OrderAggregate;
import cn.bugstack.xfg.dev.tech.domain.order.model.entity.OrderEntity;
import cn.bugstack.xfg.dev.tech.domain.order.model.entity.SKUEntity;
import cn.bugstack.xfg.dev.tech.domain.order.model.entity.UserEntity;
import cn.bugstack.xfg.dev.tech.domain.order.model.valobj.DeviceVO;
import cn.bugstack.xfg.dev.tech.domain.order.service.IOrderService;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicLong;
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderServiceTest {
@Resource
private IOrderService orderService;
@Resource
private ThreadPoolExecutor threadPoolExecutor;
private final AtomicLong totalExecutionTime = new AtomicLong(); // 记录总耗时
@Test
public void test_createOrder() throws InterruptedException {
String sku = RandomStringUtils.randomNumeric(9);
int count = 1;
orderService.initSkuCount(sku, count);
for (int i = 0; i < count; i++) {
threadPoolExecutor.execute(() -> {
UserEntity userEntity = UserEntity.builder()
.userId("小傅哥")
.userName("xfg".concat(RandomStringUtils.randomNumeric(3)))
.userMobile("+86 13521408***")
.build();
SKUEntity skuEntity = SKUEntity.builder()
.sku(sku)
.skuName("《手写MyBatis:渐进式源码实践》")
.quantity(1)
.unitPrice(BigDecimal.valueOf(128))
.discountAmount(BigDecimal.valueOf(50))
.tax(BigDecimal.ZERO)
.totalAmount(BigDecimal.valueOf(78))
.build();
DeviceVO deviceVO = DeviceVO.builder()
.ipv4("127.0.0.1")
.ipv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334".getBytes())
.machine("IPhone 14 Pro")
.location("shanghai")
.build();
long threadBeginTime = System.currentTimeMillis(); // 记录线程开始时间
// 耗时:4毫秒
// String orderId = orderService.createOrder(new OrderAggregate(userEntity, skuEntity, deviceVO));
// 耗时:106毫秒
// String orderId = orderService.createOrderByLock(new OrderAggregate(userEntity, skuEntity, deviceVO));
// 耗时:4毫秒
String orderId = orderService.createOrderByNoLock(new OrderAggregate(userEntity, skuEntity, deviceVO));
long took = System.currentTimeMillis() - threadBeginTime;
totalExecutionTime.addAndGet(took); // 累加线程耗时
log.info("写入完成 {} 耗时 {} (ms)", orderId, took / 1000);
});
}
new Thread(() -> {
while (true) {
if (threadPoolExecutor.getActiveCount() == 0) {
log.info("执行完毕,总耗时:{} (ms)", (totalExecutionTime.get() / 1000));
// log.info("执行完毕,总耗时:{}", "\r\033[31m" + (totalExecutionTime.get() / 1000) + "\033[0m");
break;
}
try {
Thread.sleep(350);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
// 等待
new CountDownLatch(1).await();
}
@Test
public void test_queryOrder() {
OrderEntity orderEntity = orderService.queryOrder("60711088280");
log.info("测试结果:{}", JSON.toJSONString(orderEntity));
}
}
package cn.bugstack.xfg.dev.tech.test.infrastructure.persistent;
import cn.bugstack.xfg.dev.tech.infrastructure.persistent.dao.IUserOrderDao;
import cn.bugstack.xfg.dev.tech.infrastructure.persistent.po.UserOrderPO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.UUID;
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserOrderDaoTest {
@Resource
private IUserOrderDao userOrderDao;
@Test
public void test_insert() {
for (int i = 0; i < 1; i++) {
UserOrderPO userOrderPO = UserOrderPO.builder()
.userName("小傅哥")
.userId("xfg" .concat(RandomStringUtils.randomNumeric(3)))
.userMobile("+86 13521408***")
.sku("13811216")
.skuName("《手写MyBatis:渐进式源码实践》")
.orderId(RandomStringUtils.randomNumeric(11))
.quantity(1)
.unitPrice(BigDecimal.valueOf(128))
.discountAmount(BigDecimal.valueOf(50))
.tax(BigDecimal.ZERO)
.totalAmount(BigDecimal.valueOf(78))
.orderDate(new Date())
.orderStatus(0)
.isDelete(0)
.uuid(UUID.randomUUID().toString().replace("-", ""))
.ipv4("127.0.0.1")
.ipv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334" .getBytes())
.extData("{\"device\": {\"machine\": \"IPhone 14 Pro\", \"location\": \"shanghai\"}}")
.build();
userOrderDao.insert(userOrderPO);
}
}
}
package cn.bugstack.xfg.dev.tech.test.infrastructure.redis;
import cn.bugstack.xfg.dev.tech.domain.order.model.entity.OrderEntity;
import cn.bugstack.xfg.dev.tech.infrastructure.redis.IRedisService;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.redisson.RedissonMultiLock;
import org.redisson.api.*;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Redisson 测试
*/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTest {
@Resource
private IRedisService redissonService;
@Test
public void test_getValue() {
OrderEntity value = redissonService.getValue("60711088280");
log.info("测试结果:{}", JSON.toJSONString(value));
}
@Test
public void test_remove() {
redissonService.remove("60711088280");
}
/**
* 可重入锁,加锁和解锁。Redisson的分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口,同时还支持自动过期解锁。
* lock.lock();
* lock.lock(10, TimeUnit.SECONDS);
* lock.tryLock(3, 10, TimeUnit.SECONDS);
* lock.lockAsync();
* lock.lockAsync(10, TimeUnit.SECONDS);
* Future<Boolean> res = lock.tryLockAsync(3, 10, TimeUnit.SECONDS);
*/
@Test
public void test_ReentrantLock() throws Exception {
RLock lock = redissonService.getLock("");
try {
// 1. 最常见的使用方法
lock.lock();
} finally {
lock.unlock();
}
}
/**
* 公平锁;保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。
* fairLock.lock();
* fairLock.lock(10, TimeUnit.SECONDS); - 自动解锁,无需unlock方法手动解锁
* fairLock.tryLock(100, 10, TimeUnit.SECONDS); - 尝试加锁,waitTime = 等待时间、leaseTime = 过期自动解锁。根据拿到锁的结果做业务
* fairLock.lockAsync(); - 异步加锁
* fairLock.lockAsync(10, TimeUnit.SECONDS); - 异步加锁,自动解锁
* Future<Boolean> res = fairLock.tryLockAsync(3, 10, TimeUnit.SECONDS); - 异步加锁,尝试加锁,自动解锁
*/
@Test
public void test_fairLock() throws InterruptedException {
RLock fairLock = redissonService.getFairLock("");
try {
// 1. 普通加锁
fairLock.lock();
// 2. 自动解锁,无需unlock方法手动解锁
fairLock.lock(10, TimeUnit.SECONDS);
// 3. 尝试加锁,waitTime = 等待时间、leaseTime = 过期自动解锁
boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
} finally {
fairLock.unlock();
}
}
/**
* RedissonMultiLock、RedissonRedLock 都可以将不同实例的多个 RLock 对象关联为一个联锁
*/
@Test
public void test_multiLock() throws InterruptedException {
// redissonService,可以是3个不同的客户端实例,如;redissonService01、redissonService02、redissonService03
RLock lock1 = redissonService.getLock("lock1");
RLock lock2 = redissonService.getLock("lock2");
RLock lock3 = redissonService.getLock("lock3");
RedissonMultiLock multiLock = new RedissonMultiLock(lock1, lock2, lock3);
// 联锁
try {
multiLock.lock();
boolean res = multiLock.tryLock(100, 10, TimeUnit.SECONDS);
} finally {
multiLock.unlock();
}
// 红锁
RedissonMultiLock redLock = new RedissonMultiLock(lock1, lock2, lock3);
try {
redLock.lock();
boolean res = redLock.tryLock(100, 10, TimeUnit.SECONDS);
} finally {
redLock.unlock();
}
}
/**
* 读写锁
*/
@Test
public void test_readWriteLock() throws InterruptedException {
RReadWriteLock lock = redissonService.getReadWriteLock("");
lock.readLock().lock();
lock.writeLock().lock();
lock.readLock().lock(10, TimeUnit.SECONDS);
lock.writeLock().lock(10, TimeUnit.SECONDS);
lock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
lock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
lock.writeLock().unlock();
lock.readLock().unlock();
}
/**
* 信号量
*/
@Test
public void test_semaphore() throws InterruptedException {
RSemaphore rSemaphore = redissonService.getSemaphore("");
rSemaphore.acquire();
rSemaphore.acquire(10);
rSemaphore.tryAcquire();
rSemaphore.tryAcquire(10);
rSemaphore.tryAcquire(100, 10, TimeUnit.SECONDS);
rSemaphore.tryAcquireAsync();
rSemaphore.release();
rSemaphore.release(10);
rSemaphore.releaseAsync(10);
}
@Test
public void test_getPermitExpirableSemaphore() throws InterruptedException {
RPermitExpirableSemaphore semaphore = redissonService.getPermitExpirableSemaphore("");
String acquireId = semaphore.acquire();
semaphore.acquire(10);
semaphore.tryAcquire();
semaphore.tryAcquire(10);
semaphore.tryAcquire(100, 10, TimeUnit.SECONDS);
semaphore.tryAcquireAsync();
semaphore.release(acquireId);
}
@Test
public void test_getCountDownLatch() throws InterruptedException {
RCountDownLatch latch = redissonService.getCountDownLatch("");
latch.trySetCount(1);
latch.await();
}
@Test
public void test_getBloomFilter() {
// 创建一个布隆过滤器,使用默认的误判率和预计元素数量
RBloomFilter<String> bloomFilter = redissonService.getBloomFilter("xfg-dev-tech-bloom");
// 初始化布隆过滤器,设置预计元素数量为10000,误判率为0.03
bloomFilter.tryInit(10000, 0.03);
// 添加元素到布隆过滤器
bloomFilter.add("1");
bloomFilter.add("2");
// 验证元素是否存在
log.info("测试结果 {}", bloomFilter.contains("1"));
log.info("测试结果 {}", bloomFilter.contains("3"));
}
@Test
public void test_getQueue() throws InterruptedException {
RQueue<String> queue = redissonService.getQueue("xfg-dev-tech-queue");
new Thread(() -> {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
queue.add(RandomStringUtils.randomNumeric(4));
}
}).start();
new Thread(() -> {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("测试结果 {}", queue.poll());
}
}).start();
// 等待
new CountDownLatch(1).await();
}
/**
* 延迟队列场景应用;https://mp.weixin.qq.com/s/jJ0vxdeKXHiYZLrwDEBOcQ
*/
@Test
public void test_getDelayedQueue() throws InterruptedException {
RBlockingQueue<Object> blockingQueue = redissonService.getBlockingQueue("xfg-dev-tech-task");
RDelayedQueue<Object> delayedQueue = redissonService.getDelayedQueue(blockingQueue);
new Thread(() -> {
try {
while (true){
Object take = blockingQueue.take();
log.info("测试结果 {}", take);
Thread.sleep(10);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
int i = 0;
while (true){
delayedQueue.offerAsync("测试" + ++i, 100L, TimeUnit.MILLISECONDS);
Thread.sleep(1000L);
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bugstack</groupId>
<artifactId>xfg-dev-tech-redis</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>xfg-dev-tech-domain</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<finalName>xfg-dev-tech-domain</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<compilerVersion>${java.version}</compilerVersion>
</configuration>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package cn.bugstack.xfg.dev.tech.domain.order.model.aggregate;
import cn.bugstack.xfg.dev.tech.domain.order.model.entity.SKUEntity;
import cn.bugstack.xfg.dev.tech.domain.order.model.entity.UserEntity;
import cn.bugstack.xfg.dev.tech.domain.order.model.valobj.DeviceVO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 订单聚合对象
* @create 2023-09-03 14:49
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class OrderAggregate {
/** 用户实体 */
private UserEntity userEntity;
/** 商品实体 */
private SKUEntity skuEntity;
/** 设备实体 */
private DeviceVO deviceVO;
}
package cn.bugstack.xfg.dev.tech.domain.order.model.entity;
import cn.bugstack.xfg.dev.tech.domain.order.model.valobj.DeviceVO;
import cn.bugstack.xfg.dev.tech.domain.order.model.valobj.OrderStatusVO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.Date;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 订单实体
* @create 2023-09-03 14:43
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OrderEntity {
/** 用户姓名 */
private String userName;
/** 用户编号 */
private String userId;
/** 用户电话 */
private String userMobile;
/** 商品编号 */
private String sku;
/** 商品名称 */
private String skuName;
/** 订单ID */
private String orderId;
/** 商品数量 */
private int quantity;
/** 商品价格 */
private BigDecimal unitPrice;
/** 折扣金额 */
private BigDecimal discountAmount;
/** 费率金额 */
private BigDecimal tax;
/** 支付金额 */
private BigDecimal totalAmount;
/** 订单日期 */
private Date orderDate;
/** 订单状态 */
private int orderStatus;
/** 唯一索引 */
private String uuid;
/** 下单设备 */
private DeviceVO deviceVO;
}
package cn.bugstack.xfg.dev.tech.domain.order.model.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description sku 实体对象
* @create 2023-09-03 14:53
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SKUEntity {
/** 商品编号 */
private String sku;
/** 商品名称 */
private String skuName;
/** 商品数量 */
private int quantity;
/** 商品价格 */
private BigDecimal unitPrice;
/** 折扣金额 */
private BigDecimal discountAmount;
/** 费率金额 */
private BigDecimal tax;
/** 支付金额 */
private BigDecimal totalAmount;
}
package cn.bugstack.xfg.dev.tech.domain.order.model.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 用户实体
* @create 2023-09-03 14:50
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity {
/** 用户编号 */
private String userId;
/** 用户姓名 */
private String userName;
/** 用户电话 */
private String userMobile;
}
package cn.bugstack.xfg.dev.tech.domain.order.model.valobj;
import lombok.*;
import java.util.Arrays;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 下单设备值对象
* @create 2023-09-03 14:45
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DeviceVO {
/** 设备 */
private String machine;
/** 地址;北京、杭州 */
private String location;
/** 设备地址 */
private String ipv4;
/** 设备地址 */
private byte[] ipv6;
}
package cn.bugstack.xfg.dev.tech.domain.order.model.valobj;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 订单状态;0 创建、1完成、2掉单、3关单
* @create 2023-08-12 08:50
*/
@Getter
@AllArgsConstructor
public enum OrderStatusVO {
CREATE(0, "创建"),
COMPLETE(1,"完成"),
DISPATCH(2,"调单"),
CLOSE(3,"关单"),
;
private final Integer code;
private final String desc;
}
package cn.bugstack.xfg.dev.tech.domain.order.repository;
import cn.bugstack.xfg.dev.tech.domain.order.model.aggregate.OrderAggregate;
import cn.bugstack.xfg.dev.tech.domain.order.model.entity.OrderEntity;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 订单仓储接口
* @create 2023-09-03 14:41
*/
public interface IOrderRepository {
long initSkuCount(String sku, long count);
String createOrderByNoLock(OrderAggregate orderAggregate);
String createOrderByLock(OrderAggregate orderAggregate);
String createOrder(OrderAggregate orderAggregate);
OrderEntity queryOrder(String orderId);
String payOrder(String orderId);
}
package cn.bugstack.xfg.dev.tech.domain.order.service;
import cn.bugstack.xfg.dev.tech.domain.order.model.aggregate.OrderAggregate;
import cn.bugstack.xfg.dev.tech.domain.order.model.entity.OrderEntity;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 订单服务接口
* @create 2023-09-03 14:39
*/
public interface IOrderService {
long initSkuCount(String sku, long count);
String createOrderByNoLock(OrderAggregate orderAggregate);
String createOrderByLock(OrderAggregate orderAggregate);
String createOrder(OrderAggregate orderAggregate);
OrderEntity queryOrder(String orderId);
String payOrder(String orderId);
}
package cn.bugstack.xfg.dev.tech.domain.order.service;
import cn.bugstack.xfg.dev.tech.domain.order.model.aggregate.OrderAggregate;
import cn.bugstack.xfg.dev.tech.domain.order.model.entity.OrderEntity;
import cn.bugstack.xfg.dev.tech.domain.order.repository.IOrderRepository;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 订单服务
* @create 2023-09-03 15:01
*/
@Service
public class OrderService implements IOrderService{
@Resource
private IOrderRepository orderRepository;
@Override
public long initSkuCount(String sku, long count) {
return orderRepository.initSkuCount(sku, count);
}
@Override
public String createOrderByNoLock(OrderAggregate orderAggregate) {
return orderRepository.createOrderByNoLock(orderAggregate);
}
@Override
public String createOrderByLock(OrderAggregate orderAggregate) {
return orderRepository.createOrderByLock(orderAggregate);
}
@Override
public String createOrder(OrderAggregate orderAggregate) {
return orderRepository.createOrder(orderAggregate);
}
@Override
public OrderEntity queryOrder(String orderId) {
return orderRepository.queryOrder(orderId);
}
@Override
public String payOrder(String orderId) {
return orderRepository.payOrder(orderId);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bugstack</groupId>
<artifactId>xfg-dev-tech-redis</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>xfg-dev-tech-infrastructure</artifactId>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.bugstack</groupId>
<artifactId>xfg-dev-tech-domain</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.23.4</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>xfg-dev-tech-infrastructure</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<compilerVersion>${java.version}</compilerVersion>
</configuration>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package cn.bugstack.xfg.dev.tech.infrastructure.persistent.dao;
import cn.bugstack.xfg.dev.tech.infrastructure.persistent.po.UserOrderPO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface IUserOrderDao {
void insert(UserOrderPO userOrderPO);
void updateOrderStatusByUserId(String userId);
void updateOrderStatusByUserMobile(String userMobile);
void updateOrderStatusByOrderId(String orderId);
UserOrderPO selectById(Long id);
List<UserOrderPO> selectByUserId(String userId);
List<UserOrderPO> selectByUserMobile(String userMobile);
UserOrderPO selectByOrderId(String orderId);
UserOrderPO selectByOrderIdAndUserId(UserOrderPO userOrderPO);
UserOrderPO selectByUserIdAndOrderId(UserOrderPO userOrderPO);
Long queryMaxId();
}
package cn.bugstack.xfg.dev.tech.infrastructure.persistent.po;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.Date;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserOrderPO {
/** 自增ID */
private Long id;
/** 用户姓名 */
private String userName;
/** 用户编号 */
private String userId;
/** 用户电话 */
private String userMobile;
/** 商品编号 */
private String sku;
/** 商品名称 */
private String skuName;
/** 订单ID */
private String orderId;
/** 商品数量 */
private int quantity;
/** 商品价格 */
private BigDecimal unitPrice;
/** 折扣金额 */
private BigDecimal discountAmount;
/** 费率金额 */
private BigDecimal tax;
/** 支付金额 */
private BigDecimal totalAmount;
/** 订单日期 */
private Date orderDate;
/** 订单状态 */
private int orderStatus;
/** 逻辑删单 */
private int isDelete;
/** 唯一索引 */
private String uuid;
/** 设备地址 */
private String ipv4;
/** 设备地址 */
private byte[] ipv6;
/** 扩展数据 */
private String extData;
/** 更新时间 */
private Date updateTime;
/** 创建时间 */
private Date createTime;
}
package cn.bugstack.xfg.dev.tech.infrastructure.persistent.repository;
import cn.bugstack.xfg.dev.tech.domain.order.model.aggregate.OrderAggregate;
import cn.bugstack.xfg.dev.tech.domain.order.model.entity.OrderEntity;
import cn.bugstack.xfg.dev.tech.domain.order.model.entity.SKUEntity;
import cn.bugstack.xfg.dev.tech.domain.order.model.entity.UserEntity;
import cn.bugstack.xfg.dev.tech.domain.order.model.valobj.DeviceVO;
import cn.bugstack.xfg.dev.tech.domain.order.model.valobj.OrderStatusVO;
import cn.bugstack.xfg.dev.tech.domain.order.repository.IOrderRepository;
import cn.bugstack.xfg.dev.tech.infrastructure.persistent.dao.IUserOrderDao;
import cn.bugstack.xfg.dev.tech.infrastructure.persistent.po.UserOrderPO;
import cn.bugstack.xfg.dev.tech.infrastructure.redis.IRedisService;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RTopic;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.UUID;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 订单仓储实现
* @create 2023-09-03 14:42
*/
@Slf4j
@Repository
public class OrderRepository implements IOrderRepository {
@Resource
private IRedisService redissonService;
@Resource
private IUserOrderDao userOrderDao;
@Resource
private RTopic testRedisTopic;
@Resource(name = "testRedisTopic02")
private RTopic testRedisTopic02;
@Resource(name = "testRedisTopic03")
private RTopic testRedisTopic03;
@Override
public long initSkuCount(String sku, long count) {
return redissonService.incrBy(sku, count);
}
@Override
public String createOrderByNoLock(OrderAggregate orderAggregate) {
UserEntity userEntity = orderAggregate.getUserEntity();
SKUEntity skuEntity = orderAggregate.getSkuEntity();
// 模拟锁商品库存
long decrCount = redissonService.decr(skuEntity.getSku());
if (decrCount < 0) return "已无库存[初始化的库存和使用库存,保持一致。orderService.initSkuCount(\"13811216\", 10000);]";
String lockKey = userEntity.getUserId().concat("_").concat(String.valueOf(decrCount));
RLock lock = redissonService.getLock(lockKey);
try {
lock.lock();
return createOrder(orderAggregate);
} finally {
lock.unlock();
}
}
@Override
public String createOrderByLock(OrderAggregate orderAggregate) {
RLock lock = redissonService.getLock("create_order_lock_".concat(orderAggregate.getSkuEntity().getSku()));
try {
lock.lock();
long decrCount = redissonService.decr(orderAggregate.getSkuEntity().getSku());
if (decrCount < 0) return "已无库存[初始化的库存和使用库存,保持一致。orderService.initSkuCount(\"13811216\", 10000);]";
return createOrder(orderAggregate);
} finally {
lock.unlock();
}
}
@Override
public String createOrder(OrderAggregate orderAggregate) {
try {
// 模拟数据库基础耗时在10毫秒以上
Thread.sleep(120);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
UserEntity userEntity = orderAggregate.getUserEntity();
SKUEntity skuEntity = orderAggregate.getSkuEntity();
DeviceVO deviceVO = orderAggregate.getDeviceVO();
String orderId = RandomStringUtils.randomNumeric(11);
UserOrderPO userOrderPO = UserOrderPO.builder()
.userName(userEntity.getUserName())
.userId(userEntity.getUserId())
.userMobile(userEntity.getUserMobile())
.sku(skuEntity.getSku())
.skuName(skuEntity.getSkuName())
.orderId(orderId)
.quantity(skuEntity.getQuantity())
.unitPrice(skuEntity.getUnitPrice())
.discountAmount(skuEntity.getDiscountAmount())
.tax(skuEntity.getTax())
.totalAmount(skuEntity.getTotalAmount())
.orderDate(new Date())
.orderStatus(OrderStatusVO.CREATE.getCode())
.isDelete(0)
.uuid(UUID.randomUUID().toString().replace("-", ""))
.ipv4(deviceVO.getIpv4())
.ipv6(deviceVO.getIpv6())
.extData(JSON.toJSONString(deviceVO))
.build();
// 插入数据库
userOrderDao.insert(userOrderPO);
OrderEntity orderEntity = new OrderEntity();
orderEntity.setUserName(userOrderPO.getUserName());
orderEntity.setUserId(userOrderPO.getUserId());
orderEntity.setUserMobile(userOrderPO.getUserMobile());
orderEntity.setSku(userOrderPO.getSku());
orderEntity.setSkuName(userOrderPO.getSkuName());
orderEntity.setOrderId(userOrderPO.getOrderId());
orderEntity.setQuantity(userOrderPO.getQuantity());
orderEntity.setUnitPrice(userOrderPO.getUnitPrice());
orderEntity.setDiscountAmount(userOrderPO.getDiscountAmount());
orderEntity.setTax(userOrderPO.getTax());
orderEntity.setTotalAmount(userOrderPO.getTotalAmount());
orderEntity.setOrderDate(userOrderPO.getOrderDate());
orderEntity.setOrderStatus(userOrderPO.getOrderStatus());
orderEntity.setUuid(userOrderPO.getUuid());
orderEntity.setDeviceVO(JSON.parseObject(userOrderPO.getExtData(), DeviceVO.class));
// 设置到缓存
redissonService.setValue(orderId, orderEntity);
testRedisTopic.publish(JSON.toJSONString(orderEntity));
testRedisTopic02.publish(JSON.toJSONString(orderEntity));
testRedisTopic03.publish(JSON.toJSONString(orderEntity));
return orderId;
}
@Override
public OrderEntity queryOrder(String orderId) {
OrderEntity orderEntity = redissonService.getValue(orderId);
if (null == orderEntity) {
UserOrderPO userOrderPO = userOrderDao.selectByOrderId(orderId);
orderEntity = new OrderEntity();
orderEntity.setUserName(userOrderPO.getUserName());
orderEntity.setUserId(userOrderPO.getUserId());
orderEntity.setUserMobile(userOrderPO.getUserMobile());
orderEntity.setSku(userOrderPO.getSku());
orderEntity.setSkuName(userOrderPO.getSkuName());
orderEntity.setOrderId(userOrderPO.getOrderId());
orderEntity.setQuantity(userOrderPO.getQuantity());
orderEntity.setUnitPrice(userOrderPO.getUnitPrice());
orderEntity.setDiscountAmount(userOrderPO.getDiscountAmount());
orderEntity.setTax(userOrderPO.getTax());
orderEntity.setTotalAmount(userOrderPO.getTotalAmount());
orderEntity.setOrderDate(userOrderPO.getOrderDate());
orderEntity.setOrderStatus(userOrderPO.getOrderStatus());
orderEntity.setUuid(userOrderPO.getUuid());
orderEntity.setDeviceVO(JSON.parseObject(userOrderPO.getExtData(), DeviceVO.class));
// 设置到缓存
redissonService.setValue(orderId, orderEntity);
}
return orderEntity;
}
/**
* 一般我们可以开户、下单、支付等个人场景做分布式加锁处理。虽然我们有数据库幂等的仿重,但对于个人用户非竞争下,为了避免重复的操作。可以使用加锁来降低对数据库的资源占用。
*/
@Override
public String payOrder(String orderId) {
RLock lock = redissonService.getLock("pay_order_lock_".concat(orderId));
try {
lock.lock();
userOrderDao.updateOrderStatusByOrderId(orderId);
} finally {
lock.unlock();
}
return orderId;
}
}
package cn.bugstack.xfg.dev.tech.infrastructure.redis;
import org.redisson.api.*;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description Redis 服务
* @create 2023-09-04 07:46
*/
public interface IRedisService {
/**
* 设置指定 key 的值
*
* @param key 键
* @param value 值
*/
<T> void setValue(String key, T value);
/**
* 设置指定 key 的值
*
* @param key 键
* @param value 值
* @param expired 过期时间
*/
<T> void setValue(String key, T value, long expired);
/**
* 获取指定 key 的值
*
* @param key 键
* @return 值
*/
<T> T getValue(String key);
/**
* 获取队列
*
* @param key 键
* @param <T> 泛型
* @return 队列
*/
<T> RQueue<T> getQueue(String key);
/**
* 加锁队列
*
* @param key 键
* @param <T> 泛型
* @return 队列
*/
<T> RBlockingQueue<T> getBlockingQueue(String key);
/**
* 延迟队列
*
* @param rBlockingQueue 加锁队列
* @param <T> 泛型
* @return 队列
*/
<T> RDelayedQueue<T> getDelayedQueue(RBlockingQueue<T> rBlockingQueue);
/**
* 自增 Key 的值;1、2、3、4
*
* @param key 键
* @return 自增后的值
*/
long incr(String key);
/**
* 指定值,自增 Key 的值;1、2、3、4
*
* @param key 键
* @return 自增后的值
*/
long incrBy(String key, long delta);
/**
* 自减 Key 的值;1、2、3、4
*
* @param key 键
* @return 自增后的值
*/
long decr(String key);
/**
* 指定值,自增 Key 的值;1、2、3、4
*
* @param key 键
* @return 自增后的值
*/
long decrBy(String key, long delta);
/**
* 移除指定 key 的值
*
* @param key 键
*/
void remove(String key);
/**
* 判断指定 key 的值是否存在
*
* @param key 键
* @return true/false
*/
boolean isExists(String key);
/**
* 将指定的值添加到集合中
*
* @param key 键
* @param value 值
*/
void addToSet(String key, String value);
/**
* 判断指定的值是否是集合的成员
*
* @param key 键
* @param value 值
* @return 如果是集合的成员返回 true,否则返回 false
*/
boolean isSetMember(String key, String value);
/**
* 将指定的值添加到列表中
*
* @param key 键
* @param value 值
*/
void addToList(String key, String value);
/**
* 获取列表中指定索引的值
*
* @param key 键
* @param index 索引
* @return 值
*/
String getFromList(String key, int index);
/**
* 将指定的键值对添加到哈希表中
*
* @param key 键
* @param field 字段
* @param value 值
*/
void addToMap(String key, String field, String value);
/**
* 获取哈希表中指定字段的值
*
* @param key 键
* @param field 字段
* @return 值
*/
String getFromMap(String key, String field);
/**
* 将指定的值添加到有序集合中
*
* @param key 键
* @param value 值
*/
void addToSortedSet(String key, String value);
/**
* 获取 Redis 锁(可重入锁)
*
* @param key 键
* @return Lock
*/
RLock getLock(String key);
/**
* 获取 Redis 锁(公平锁)
*
* @param key 键
* @return Lock
*/
RLock getFairLock(String key);
/**
* 获取 Redis 锁(读写锁)
*
* @param key 键
* @return RReadWriteLock
*/
RReadWriteLock getReadWriteLock(String key);
/**
* 获取 Redis 信号量
*
* @param key 键
* @return RSemaphore
*/
RSemaphore getSemaphore(String key);
/**
* 获取 Redis 过期信号量
* <p>
* 基于Redis的Redisson的分布式信号量(Semaphore)Java对象RSemaphore采用了与java.util.concurrent.Semaphore相似的接口和用法。
* 同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。
*
* @param key 键
* @return RPermitExpirableSemaphore
*/
RPermitExpirableSemaphore getPermitExpirableSemaphore(String key);
/**
* 闭锁
*
* @param key 键
* @return RCountDownLatch
*/
RCountDownLatch getCountDownLatch(String key);
/**
* 布隆过滤器
*
* @param key 键
* @param <T> 存放对象
* @return 返回结果
*/
<T> RBloomFilter<T> getBloomFilter(String key);
}
package cn.bugstack.xfg.dev.tech.infrastructure.redis;
import org.redisson.api.*;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.Duration;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description Redisson
* @create 2023-09-04 07:47
*/
@Service("redissonService")
public class RedissonService implements IRedisService {
@Resource
private RedissonClient redissonClient;
public <T> void setValue(String key, T value) {
redissonClient.<T>getBucket(key).set(value);
}
@Override
public <T> void setValue(String key, T value, long expired) {
RBucket<T> bucket = redissonClient.getBucket(key);
bucket.set(value, Duration.ofMillis(expired));
}
public <T> T getValue(String key) {
return redissonClient.<T>getBucket(key).get();
}
@Override
public <T> RQueue<T> getQueue(String key) {
return redissonClient.getQueue(key);
}
@Override
public <T> RBlockingQueue<T> getBlockingQueue(String key) {
return redissonClient.getBlockingQueue(key);
}
@Override
public <T> RDelayedQueue<T> getDelayedQueue(RBlockingQueue<T> rBlockingQueue) {
return redissonClient.getDelayedQueue(rBlockingQueue);
}
@Override
public long incr(String key) {
return redissonClient.getAtomicLong(key).incrementAndGet();
}
@Override
public long incrBy(String key, long delta) {
return redissonClient.getAtomicLong(key).addAndGet(delta);
}
@Override
public long decr(String key) {
return redissonClient.getAtomicLong(key).decrementAndGet();
}
@Override
public long decrBy(String key, long delta) {
return redissonClient.getAtomicLong(key).addAndGet(-delta);
}
@Override
public void remove(String key) {
redissonClient.getBucket(key).delete();
}
@Override
public boolean isExists(String key) {
return redissonClient.getBucket(key).isExists();
}
public void addToSet(String key, String value) {
RSet<String> set = redissonClient.getSet(key);
set.add(value);
}
public boolean isSetMember(String key, String value) {
RSet<String> set = redissonClient.getSet(key);
return set.contains(value);
}
public void addToList(String key, String value) {
RList<String> list = redissonClient.getList(key);
list.add(value);
}
public String getFromList(String key, int index) {
RList<String> list = redissonClient.getList(key);
return list.get(index);
}
public void addToMap(String key, String field, String value) {
RMap<String, String> map = redissonClient.getMap(key);
map.put(field, value);
}
public String getFromMap(String key, String field) {
RMap<String, String> map = redissonClient.getMap(key);
return map.get(field);
}
public void addToSortedSet(String key, String value) {
RSortedSet<String> sortedSet = redissonClient.getSortedSet(key);
sortedSet.add(value);
}
@Override
public RLock getLock(String key) {
return redissonClient.getLock(key);
}
@Override
public RLock getFairLock(String key) {
return redissonClient.getFairLock(key);
}
@Override
public RReadWriteLock getReadWriteLock(String key) {
return redissonClient.getReadWriteLock(key);
}
@Override
public RSemaphore getSemaphore(String key) {
return redissonClient.getSemaphore(key);
}
@Override
public RPermitExpirableSemaphore getPermitExpirableSemaphore(String key) {
return redissonClient.getPermitExpirableSemaphore(key);
}
@Override
public RCountDownLatch getCountDownLatch(String key) {
return redissonClient.getCountDownLatch(key);
}
@Override
public <T> RBloomFilter<T> getBloomFilter(String key) {
return redissonClient.getBloomFilter(key);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bugstack</groupId>
<artifactId>xfg-dev-tech-redis</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>xfg-dev-tech-trigger</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<!-- 自身模块 begin -->
<dependency>
<groupId>cn.bugstack</groupId>
<artifactId>xfg-dev-tech-domain</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.bugstack</groupId>
<artifactId>xfg-dev-tech-types</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!-- 自身模块 end -->
</dependencies>
<build>
<finalName>xfg-dev-tech-trigger</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<compilerVersion>${java.version}</compilerVersion>
</configuration>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package cn.bugstack.xfg.dev.tech.infrastructure.trigger.mq;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.listener.MessageListener;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class RedisTopicListener01 implements MessageListener<String> {
@Override
public void onMessage(CharSequence channel, String msg) {
log.info("01-监听消息(Redis 发布/订阅): {}", msg);
}
}
package cn.bugstack.xfg.dev.tech.infrastructure.trigger.mq;
import cn.bugstack.xfg.dev.tech.types.RedisTopic;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.listener.MessageListener;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RedisTopic(topic = "testRedisTopic02")
public class RedisTopicListener02 implements MessageListener<String> {
@Override
public void onMessage(CharSequence channel, String msg) {
log.info("02-监听消息(Redis 发布/订阅): {}", msg);
}
}
package cn.bugstack.xfg.dev.tech.infrastructure.trigger.mq;
import cn.bugstack.xfg.dev.tech.types.RedisTopic;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.listener.MessageListener;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RedisTopic(topic = "testRedisTopic03")
public class RedisTopicListener03 implements MessageListener<String> {
@Override
public void onMessage(CharSequence channel, String msg) {
log.info("03-监听消息(Redis 发布/订阅): {}", msg);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bugstack</groupId>
<artifactId>xfg-dev-tech-redis</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>xfg-dev-tech-types</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.2</version>
</dependency>
</dependencies>
<build>
<finalName>xfg-frame-types</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package cn.bugstack.xfg.dev.tech.types;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface RedisTopic {
String topic() default "";
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册