38 changed files with 602 additions and 24 deletions
@ -0,0 +1,30 @@
|
||||
-- 创建 order库、业务表、undo_log表 |
||||
create database seata_order; |
||||
use seata_order; |
||||
|
||||
DROP TABLE IF EXISTS `tb_order`; |
||||
CREATE TABLE `tb_order` ( |
||||
`id` int(11) NOT NULL AUTO_INCREMENT, |
||||
`user_id` varchar(255) DEFAULT NULL, |
||||
`commodity_code` varchar(255) DEFAULT NULL, |
||||
`count` int(11) DEFAULT 0, |
||||
`money` int(11) DEFAULT 0, |
||||
PRIMARY KEY (`id`) |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
||||
|
||||
CREATE TABLE `undo_log` |
||||
( |
||||
`id` BIGINT(20) NOT NULL AUTO_INCREMENT, |
||||
`branch_id` BIGINT(20) NOT NULL, |
||||
`xid` VARCHAR(100) NOT NULL, |
||||
`context` VARCHAR(128) NOT NULL, |
||||
`rollback_info` LONGBLOB NOT NULL, |
||||
`log_status` INT(11) NOT NULL, |
||||
`log_created` DATETIME NOT NULL, |
||||
`log_modified` DATETIME NOT NULL, |
||||
`ext` VARCHAR(100) DEFAULT NULL, |
||||
PRIMARY KEY (`id`), |
||||
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) |
||||
) ENGINE = InnoDB |
||||
AUTO_INCREMENT = 1 |
||||
DEFAULT CHARSET = utf8; |
@ -0,0 +1,30 @@
|
||||
<?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"> |
||||
<parent> |
||||
<artifactId>blade-example</artifactId> |
||||
<groupId>org.springblade</groupId> |
||||
<version>2.1.0.RELEASE</version> |
||||
</parent> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<artifactId>blade-seata-order</artifactId> |
||||
<name>${project.artifactId}</name> |
||||
<version>${bladex.project.version}</version> |
||||
<packaging>jar</packaging> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.springblade</groupId> |
||||
<artifactId>blade-core-boot</artifactId> |
||||
<version>${bladex.tool.version}</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springblade</groupId> |
||||
<artifactId>blade-starter-transaction</artifactId> |
||||
<version>${bladex.tool.version}</version> |
||||
</dependency> |
||||
</dependencies> |
||||
|
||||
</project> |
@ -0,0 +1,36 @@
|
||||
/** |
||||
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com). |
||||
* <p> |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* <p> |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p> |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springblade.seata.order; |
||||
|
||||
import org.springblade.core.cloud.feign.EnableBladeFeign; |
||||
import org.springblade.core.launch.BladeApplication; |
||||
import org.springblade.core.transaction.annotation.SeataCloudApplication; |
||||
|
||||
/** |
||||
* Order启动器 |
||||
* |
||||
* @author Chill |
||||
*/ |
||||
@EnableBladeFeign |
||||
@SeataCloudApplication |
||||
public class SeataOrderApplication { |
||||
|
||||
public static void main(String[] args) { |
||||
BladeApplication.run("blade-seata-order", SeataOrderApplication.class, args); |
||||
} |
||||
|
||||
} |
||||
|
@ -0,0 +1,34 @@
|
||||
package org.springblade.seata.order.controller; |
||||
|
||||
import lombok.AllArgsConstructor; |
||||
import org.springblade.core.tool.api.R; |
||||
import org.springblade.seata.order.service.IOrderService; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
|
||||
/** |
||||
* OrderController |
||||
* |
||||
* @author Chill |
||||
*/ |
||||
@RestController |
||||
@RequestMapping("order") |
||||
@AllArgsConstructor |
||||
public class OrderController { |
||||
|
||||
private IOrderService orderService; |
||||
|
||||
/** |
||||
* 创建订单 |
||||
* |
||||
* @param userId 用户id |
||||
* @param commodityCode 商品代码 |
||||
* @param count 数量 |
||||
* @return boolean |
||||
*/ |
||||
@RequestMapping("/create") |
||||
public R createOrder(String userId, String commodityCode, Integer count) { |
||||
return R.status(orderService.createOrder(userId, commodityCode, count)); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,31 @@
|
||||
package org.springblade.seata.order.entity; |
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType; |
||||
import com.baomidou.mybatisplus.annotation.TableId; |
||||
import com.baomidou.mybatisplus.annotation.TableName; |
||||
import lombok.Data; |
||||
import lombok.experimental.Accessors; |
||||
|
||||
import java.io.Serializable; |
||||
import java.math.BigDecimal; |
||||
|
||||
/** |
||||
* Order |
||||
* |
||||
* @author Chill |
||||
*/ |
||||
@Data |
||||
@Accessors(chain = true) |
||||
@TableName("tb_order") |
||||
public class Order implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
@TableId(value = "id", type = IdType.AUTO) |
||||
private Integer id; |
||||
private String userId; |
||||
private String commodityCode; |
||||
private Integer count; |
||||
private BigDecimal money; |
||||
|
||||
} |
@ -0,0 +1,25 @@
|
||||
package org.springblade.seata.order.feign; |
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient; |
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.bind.annotation.RequestParam; |
||||
|
||||
/** |
||||
* StorageClient |
||||
* |
||||
* @author Chill |
||||
*/ |
||||
@FeignClient(name = "blade-seata-storage", fallback = StorageClientFallback.class) |
||||
public interface IStorageClient { |
||||
|
||||
/** |
||||
* 减库存 |
||||
* |
||||
* @param commodityCode 商品代码 |
||||
* @param count 数量 |
||||
* @return boolean |
||||
*/ |
||||
@GetMapping("/deduct") |
||||
int deduct(@RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count); |
||||
|
||||
} |
@ -0,0 +1,18 @@
|
||||
package org.springblade.seata.order.feign; |
||||
|
||||
import org.springframework.stereotype.Component; |
||||
|
||||
/** |
||||
* StorageClientFallback |
||||
* |
||||
* @author Chill |
||||
*/ |
||||
@Component |
||||
public class StorageClientFallback implements IStorageClient { |
||||
|
||||
@Override |
||||
public int deduct(String commodityCode, Integer count) { |
||||
return -1; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,12 @@
|
||||
package org.springblade.seata.order.mapper; |
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
import org.springblade.seata.order.entity.Order; |
||||
|
||||
/** |
||||
* OrderMapper |
||||
* |
||||
* @author Chill |
||||
*/ |
||||
public interface OrderMapper extends BaseMapper<Order> { |
||||
} |
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||
<mapper namespace="org.springblade.seata.order.mapper.OrderMapper"> |
||||
|
||||
</mapper> |
@ -0,0 +1,23 @@
|
||||
package org.springblade.seata.order.service; |
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService; |
||||
import org.springblade.seata.order.entity.Order; |
||||
|
||||
/** |
||||
* IOrderService |
||||
* |
||||
* @author Chill |
||||
*/ |
||||
public interface IOrderService extends IService<Order> { |
||||
|
||||
/** |
||||
* 创建订单 |
||||
* |
||||
* @param userId 用户id |
||||
* @param commodityCode 商品代码 |
||||
* @param count 数量 |
||||
* @return boolean |
||||
*/ |
||||
boolean createOrder(String userId, String commodityCode, Integer count); |
||||
|
||||
} |
@ -0,0 +1,48 @@
|
||||
package org.springblade.seata.order.service.impl; |
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
||||
import io.seata.spring.annotation.GlobalTransactional; |
||||
import lombok.AllArgsConstructor; |
||||
import org.springblade.core.log.exception.ServiceException; |
||||
import org.springblade.seata.order.entity.Order; |
||||
import org.springblade.seata.order.feign.IStorageClient; |
||||
import org.springblade.seata.order.mapper.OrderMapper; |
||||
import org.springblade.seata.order.service.IOrderService; |
||||
import org.springframework.stereotype.Service; |
||||
import org.springframework.transaction.annotation.Transactional; |
||||
|
||||
import java.math.BigDecimal; |
||||
|
||||
/** |
||||
* OrderServiceImpl |
||||
* |
||||
* @author Chill |
||||
*/ |
||||
@Service |
||||
@AllArgsConstructor |
||||
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService { |
||||
|
||||
private IStorageClient storageClient; |
||||
|
||||
@Override |
||||
@GlobalTransactional |
||||
@Transactional(rollbackFor = Exception.class) |
||||
public boolean createOrder(String userId, String commodityCode, Integer count) { |
||||
int maxCount = 100; |
||||
BigDecimal orderMoney = new BigDecimal(count).multiply(new BigDecimal(5)); |
||||
Order order = new Order() |
||||
.setUserId(userId) |
||||
.setCommodityCode(commodityCode) |
||||
.setCount(count) |
||||
.setMoney(orderMoney); |
||||
int cnt1 = baseMapper.insert(order); |
||||
int cnt2 = storageClient.deduct(commodityCode, count); |
||||
if (cnt2 < 0) { |
||||
throw new ServiceException("创建订单失败"); |
||||
} else if (count > maxCount) { |
||||
throw new ServiceException("超过订单最大值,创建订单失败"); |
||||
} |
||||
return cnt1 > 0 && cnt2 > 0; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,10 @@
|
||||
#服务器端口 |
||||
server: |
||||
port: 8501 |
||||
|
||||
#数据源配置 |
||||
spring: |
||||
datasource: |
||||
url: jdbc:mysql://localhost:3306/seata_order?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true |
||||
username: root |
||||
password: root |
@ -0,0 +1,20 @@
|
||||
registry { |
||||
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa |
||||
type = "nacos" |
||||
|
||||
nacos { |
||||
serverAddr = "localhost" |
||||
namespace = "" |
||||
cluster = "default" |
||||
} |
||||
} |
||||
|
||||
config { |
||||
# file、nacos 、apollo、zk、consul、etcd3 |
||||
type = "nacos" |
||||
|
||||
nacos { |
||||
serverAddr = "localhost" |
||||
namespace = "" |
||||
} |
||||
} |
@ -0,0 +1,33 @@
|
||||
-- 创建 storage库、业务表、undo_log表 |
||||
create database seata_storage; |
||||
use seata_storage; |
||||
|
||||
DROP TABLE IF EXISTS `tb_storage`; |
||||
CREATE TABLE `tb_storage` ( |
||||
`id` int(11) NOT NULL AUTO_INCREMENT, |
||||
`commodity_code` varchar(255) DEFAULT NULL, |
||||
`count` int(11) DEFAULT 0, |
||||
PRIMARY KEY (`id`), |
||||
UNIQUE KEY (`commodity_code`) |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
||||
|
||||
CREATE TABLE `undo_log` |
||||
( |
||||
`id` BIGINT(20) NOT NULL AUTO_INCREMENT, |
||||
`branch_id` BIGINT(20) NOT NULL, |
||||
`xid` VARCHAR(100) NOT NULL, |
||||
`context` VARCHAR(128) NOT NULL, |
||||
`rollback_info` LONGBLOB NOT NULL, |
||||
`log_status` INT(11) NOT NULL, |
||||
`log_created` DATETIME NOT NULL, |
||||
`log_modified` DATETIME NOT NULL, |
||||
`ext` VARCHAR(100) DEFAULT NULL, |
||||
PRIMARY KEY (`id`), |
||||
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) |
||||
) ENGINE = InnoDB |
||||
AUTO_INCREMENT = 1 |
||||
DEFAULT CHARSET = utf8; |
||||
|
||||
-- 初始化库存模拟数据 |
||||
INSERT INTO seata_storage.tb_storage (id, commodity_code, count) VALUES (1, 'product-1', 9999999); |
||||
INSERT INTO seata_storage.tb_storage (id, commodity_code, count) VALUES (2, 'product-2', 0); |
@ -0,0 +1,31 @@
|
||||
<?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"> |
||||
<parent> |
||||
<artifactId>blade-example</artifactId> |
||||
<groupId>org.springblade</groupId> |
||||
<version>2.1.0.RELEASE</version> |
||||
</parent> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<artifactId>blade-seata-storage</artifactId> |
||||
<name>${project.artifactId}</name> |
||||
<version>${bladex.project.version}</version> |
||||
<packaging>jar</packaging> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.springblade</groupId> |
||||
<artifactId>blade-core-boot</artifactId> |
||||
<version>${bladex.tool.version}</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springblade</groupId> |
||||
<artifactId>blade-starter-transaction</artifactId> |
||||
<version>${bladex.tool.version}</version> |
||||
</dependency> |
||||
</dependencies> |
||||
|
||||
|
||||
</project> |
@ -0,0 +1,36 @@
|
||||
/** |
||||
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com). |
||||
* <p> |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* <p> |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p> |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springblade.seata.storage; |
||||
|
||||
import org.springblade.core.cloud.feign.EnableBladeFeign; |
||||
import org.springblade.core.launch.BladeApplication; |
||||
import org.springblade.core.transaction.annotation.SeataCloudApplication; |
||||
|
||||
/** |
||||
* Storage启动器 |
||||
* |
||||
* @author Chill |
||||
*/ |
||||
@EnableBladeFeign |
||||
@SeataCloudApplication |
||||
public class SeataStorageApplication { |
||||
|
||||
public static void main(String[] args) { |
||||
BladeApplication.run("blade-seata-storage", SeataStorageApplication.class, args); |
||||
} |
||||
|
||||
} |
||||
|
@ -0,0 +1,30 @@
|
||||
package org.springblade.seata.storage.controller; |
||||
|
||||
import lombok.AllArgsConstructor; |
||||
import org.springblade.seata.storage.service.IStorageService; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
|
||||
/** |
||||
* StorageController |
||||
* |
||||
* @author Chill |
||||
*/ |
||||
@RestController |
||||
@AllArgsConstructor |
||||
public class StorageController { |
||||
|
||||
private IStorageService storageService; |
||||
|
||||
/** |
||||
* 减库存 |
||||
* |
||||
* @param commodityCode 商品代码 |
||||
* @param count 数量 |
||||
*/ |
||||
@RequestMapping(path = "/deduct") |
||||
public int deduct(String commodityCode, Integer count) { |
||||
return storageService.deduct(commodityCode, count); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,24 @@
|
||||
package org.springblade.seata.storage.entity; |
||||
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName; |
||||
import lombok.Data; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
/** |
||||
* storage |
||||
* |
||||
* @author Chill |
||||
*/ |
||||
@Data |
||||
@TableName("tb_storage") |
||||
public class Storage implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
private Long id; |
||||
private String commodityCode; |
||||
private Long count; |
||||
|
||||
} |
@ -0,0 +1,12 @@
|
||||
package org.springblade.seata.storage.mapper; |
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
import org.springblade.seata.storage.entity.Storage; |
||||
|
||||
/** |
||||
* StorageMapper |
||||
* |
||||
* @author Chill |
||||
*/ |
||||
public interface StorageMapper extends BaseMapper<Storage> { |
||||
} |
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||
<mapper namespace="org.springblade.seata.storage.mapper.StorageMapper"> |
||||
|
||||
</mapper> |
@ -0,0 +1,22 @@
|
||||
package org.springblade.seata.storage.service; |
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService; |
||||
import org.springblade.seata.storage.entity.Storage; |
||||
|
||||
/** |
||||
* IStorageService |
||||
* |
||||
* @author Chill |
||||
*/ |
||||
public interface IStorageService extends IService<Storage> { |
||||
|
||||
/** |
||||
* 减库存 |
||||
* |
||||
* @param commodityCode 商品代码 |
||||
* @param count 数量 |
||||
* @return boolean |
||||
*/ |
||||
int deduct(String commodityCode, int count); |
||||
|
||||
} |
@ -0,0 +1,30 @@
|
||||
package org.springblade.seata.storage.service.impl; |
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
||||
import org.springblade.seata.storage.entity.Storage; |
||||
import org.springblade.seata.storage.mapper.StorageMapper; |
||||
import org.springblade.seata.storage.service.IStorageService; |
||||
import org.springframework.stereotype.Service; |
||||
import org.springframework.transaction.annotation.Transactional; |
||||
|
||||
/** |
||||
* StorageServiceImpl |
||||
* |
||||
* @author Chill |
||||
*/ |
||||
@Service |
||||
public class StorageServiceImpl extends ServiceImpl<StorageMapper, Storage> implements IStorageService { |
||||
|
||||
@Override |
||||
@Transactional(rollbackFor = Exception.class) |
||||
public int deduct(String commodityCode, int count) { |
||||
Storage storage = baseMapper.selectOne(Wrappers.<Storage>query().lambda().eq(Storage::getCommodityCode, commodityCode)); |
||||
if (storage.getCount() < count) { |
||||
throw new RuntimeException("超过库存数,扣除失败!"); |
||||
} |
||||
storage.setCount(storage.getCount() - count); |
||||
return baseMapper.updateById(storage); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,10 @@
|
||||
#服务器端口 |
||||
server: |
||||
port: 8502 |
||||
|
||||
#数据源配置 |
||||
spring: |
||||
datasource: |
||||
url: jdbc:mysql://localhost:3306/seata_storage?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true |
||||
username: root |
||||
password: root |
@ -0,0 +1,20 @@
|
||||
registry { |
||||
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa |
||||
type = "nacos" |
||||
|
||||
nacos { |
||||
serverAddr = "localhost" |
||||
namespace = "" |
||||
cluster = "default" |
||||
} |
||||
} |
||||
|
||||
config { |
||||
# file、nacos 、apollo、zk、consul、etcd3 |
||||
type = "nacos" |
||||
|
||||
nacos { |
||||
serverAddr = "localhost" |
||||
namespace = "" |
||||
} |
||||
} |
Loading…
Reference in new issue