- 后端:引入分布式事务框架 seata 框架。

- 后端:目前订单提交时,如果发生异常,会回滚优惠劵的使用。
This commit is contained in:
YunaiV
2019-05-12 18:47:45 +08:00
parent 36018c84da
commit 4888dfaf2a
20 changed files with 275 additions and 146 deletions

View File

@@ -22,7 +22,6 @@
<artifactId>mall-spring-boot</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>product-service-api</artifactId>

View File

@@ -1,36 +0,0 @@
package cn.iocoder.mall.promotion.application.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2 // TODO 生产环境时,禁用掉。
public class SwaggerConfiguration {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("cn.iocoder.mall.promotion.application.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("营销子系统")
.description("营销子系统")
.termsOfServiceUrl("http://www.iocoder.cn")
.version("1.0.0")
.build();
}
}

View File

@@ -80,7 +80,7 @@ public interface CouponService {
* @return 是否成功
*/
Boolean useCouponCard(Integer userId,
@NotNull(message = "优惠劵编号不能为空") Integer couponCardId);
@NotNull(message = "优惠劵编号不能为空") Integer couponCardId);
/**
* 取消优惠劵的使用

View File

@@ -28,6 +28,35 @@
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-jdbc</artifactId>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <artifactId>logback-classic</artifactId>-->
<!-- <groupId>ch.qos.logback</groupId>-->
<!-- </exclusion>-->
<!-- <exclusion>-->
<!-- <artifactId>spring-boot-starter-logging</artifactId>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!-- </dependency>-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
@@ -46,50 +75,53 @@
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<exclusions>
<exclusion>
<artifactId>logback-classic</artifactId>
<groupId>ch.qos.logback</groupId>
</exclusion>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring</artifactId>
</dependency>
<!--dependency for Apache Dubbo-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-dubbo</artifactId>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework</groupId>-->
<!-- <artifactId>spring-tx</artifactId>-->
<!-- <version>5.0.12.RELEASE</version>-->
<!-- <scope>compile</scope>-->
<!-- </dependency>-->
</dependencies>

View File

@@ -1,14 +1,78 @@
package cn.iocoder.mall.promotion.biz.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import io.seata.spring.annotation.GlobalTransactionScanner;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@MapperScan("cn.iocoder.mall.promotion.biz.dao") // 扫描对应的 Mapper 接口
@EnableTransactionManagement(proxyTargetClass = true) // 启动事务管理。为什么使用 proxyTargetClass 参数,参见 https://blog.csdn.net/huang_550/article/details/76492600
@EnableConfigurationProperties(DataSourceProperties.class)
public class DatabaseConfiguration {
// 数据源,使用 HikariCP
}
@Value("${spring.application.name}")
private String applicationId;
@Autowired
private DataSourceProperties dataSourceProperties;
// @Bean // TODO 芋艿,加了就一直报错,后面在找原因。
// @Primary
public DruidDataSource druidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(dataSourceProperties.getUrl());
druidDataSource.setUsername(dataSourceProperties.getUsername());
druidDataSource.setPassword(dataSourceProperties.getPassword());
druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
druidDataSource.setInitialSize(0);
druidDataSource.setMaxActive(180);
druidDataSource.setMaxWait(60000);
druidDataSource.setMinIdle(0);
druidDataSource.setValidationQuery("Select 1 from DUAL");
druidDataSource.setTestOnBorrow(false);
druidDataSource.setTestOnReturn(false);
druidDataSource.setTestWhileIdle(true);
druidDataSource.setTimeBetweenEvictionRunsMillis(60000);
druidDataSource.setMinEvictableIdleTimeMillis(25200000);
druidDataSource.setRemoveAbandoned(true);
druidDataSource.setRemoveAbandonedTimeout(1800);
druidDataSource.setLogAbandoned(true);
return druidDataSource;
}
@ConfigurationProperties(prefix = "spring.datasource")
@Primary
@Bean("dataSource")
// @Bean
public DataSource dataSource() {
DruidDataSource dataSource = druidDataSource();
return new DataSourceProxy(dataSource);
}
/**
* 注册一个StatViewServlet
*
* @return global transaction scanner
*/
@Bean
public GlobalTransactionScanner globalTransactionScanner() {
return new GlobalTransactionScanner(applicationId, "my_test_tx_group");
// TODO 芋艿txServiceGroup 后续要编辑下
}
}

View File

@@ -242,14 +242,14 @@ public class CouponServiceImpl implements CouponService {
if (!userId.equals(card.getUserId())) {
throw ServiceExceptionUtil.exception(PromotionErrorCodeEnum.COUPON_CARD_ERROR_USER.getCode());
}
if (CouponCardStatusEnum.UNUSED.getValue().equals(card.getStatus())) {
if (!CouponCardStatusEnum.UNUSED.getValue().equals(card.getStatus())) {
throw ServiceExceptionUtil.exception(PromotionErrorCodeEnum.COUPON_CARD_STATUS_NOT_UNUSED.getCode());
}
if (DateUtil.isBetween(card.getValidStartTime(), card.getValidEndTime())) { // 为避免定时器没跑,实际优惠劵已经过期
throw ServiceExceptionUtil.exception(PromotionErrorCodeEnum.COUPON_CARD_STATUS_NOT_UNUSED.getCode());
}
// 更新优惠劵已使用
int updateCount = couponCardMapper.updateByIdAndStatus(card.getId(), CouponCardStatusEnum.USED.getValue(),
int updateCount = couponCardMapper.updateByIdAndStatus(card.getId(), CouponCardStatusEnum.UNUSED.getValue(),
new CouponCardDO().setStatus(CouponCardStatusEnum.USED.getValue()).setUsedTime(new Date()));
if (updateCount == 0) {
throw ServiceExceptionUtil.exception(PromotionErrorCodeEnum.COUPON_CARD_STATUS_NOT_UNUSED.getCode());
@@ -267,12 +267,12 @@ public class CouponServiceImpl implements CouponService {
if (!userId.equals(card.getUserId())) {
throw ServiceExceptionUtil.exception(PromotionErrorCodeEnum.COUPON_CARD_ERROR_USER.getCode());
}
if (CouponCardStatusEnum.USED.getValue().equals(card.getStatus())) {
if (!CouponCardStatusEnum.USED.getValue().equals(card.getStatus())) {
throw ServiceExceptionUtil.exception(PromotionErrorCodeEnum.COUPON_CARD_STATUS_NOT_USED.getCode());
}
// 更新优惠劵已使用
int updateCount = couponCardMapper.updateByIdAndStatus(card.getId(), CouponCardStatusEnum.UNUSED.getValue(),
new CouponCardDO().setStatus(CouponCardStatusEnum.USED.getValue())); // TODO 芋艿usedTime 未设置空,后面处理。
int updateCount = couponCardMapper.updateByIdAndStatus(card.getId(), CouponCardStatusEnum.USED.getValue(),
new CouponCardDO().setStatus(CouponCardStatusEnum.UNUSED.getValue())); // TODO 芋艿usedTime 未设置空,后面处理。
if (updateCount == 0) {
throw ServiceExceptionUtil.exception(PromotionErrorCodeEnum.COUPON_CARD_STATUS_NOT_USED.getCode());
}

View File

@@ -7,9 +7,18 @@ spring:
password: ${MALL_MYSQL_PASSWORD}
# mybatis
mybatis:
config-location: classpath:mybatis-config.xml
mapper-locations: classpath:mapper/*.xml
#mybatis:
# config-location: classpath:mybatis-config.xml
# mapper-locations: classpath:mapper/*.xml
# type-aliases-package: cn.iocoder.mall.promotion.biz.dataobject
# mybatis-plus
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
global-config:
db-config:
id-type: auto
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: cn.iocoder.mall.promotion.biz.dataobject
# dubbo