場景
假設支付寶轉賬1000元到餘額寶, 通過RabbitMQ對轉賬過程進行解耦,
支付寶將轉賬的消息投遞到RabbitMQ, 餘額寶通過監聽RibbitMQ的消息隊列獲得消息, 然後通過應答隊列告訴支付寶消息已經消費
遇到的問題
1. 當餘額寶獲取到消息之後, 可能轉賬失敗, 消息隊列不會關心餘額寶是否操作成功, 這就是消息丟失的問題
2. 如果餘額寶成功轉賬, 但響應隊列遲遲沒有將消費成功的消息告訴支付寶, 導致支付寶重複發送消息, 這就是消息重複發送的問題.
解決方案
1. 第一種是引入ZK, 順序消費
2. 本地消息表
在支付寶端和餘額寶端同時建立消息表
1. 當在支付寶端扣款成功的同時, 在消息表中建立一條記錄, 狀態標識爲unconfirm, 將消息投遞到消息隊列
2. 餘額寶從消息隊列中獲取消息後, 在餘額寶中扣款成功後, 同時在消息表中建立一條消息, 狀態標識爲confirmed.
3. 餘額寶將響應消息投遞到響應隊列, 支付寶獲得響應後, 查詢餘額寶的消息表, 如果其中沒有消費記錄, 則插入新的消息。如果查詢有消費的消息, 就停止插入, 並返回已經消費的消息。這樣可以避免消息重複消費的問題。
4. 支付寶端會有一個定時任務, 相隔一段時間就從消息表中將unconfirm的消息拉取並重新發送, 這樣可以避免消息丟失的問題
代碼
數據庫腳本
DROP DATABASE IF EXISTS `rabbit_taobao_consumer`;
CREATE DATABASE IF NOT EXISTS `rabbit_taobao_consumer` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `rabbit_taobao_consumer`;
DROP TABLE IF EXISTS `tb_account`;
CREATE TABLE IF NOT EXISTS `tb_account` (
`user_id` varchar(10) NOT NULL,
`amount` int(11) NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `tb_account` (`user_id`, `amount`, `update_time`) VALUES
('SAM0000001', 0, '2019-09-22 14:30:49'),
('SAM0000002', 0, '2019-09-22 14:02:59'),
('SAM0000003', 0, '2019-09-22 14:03:09');
DROP TABLE IF EXISTS `tb_message`;
CREATE TABLE IF NOT EXISTS `tb_message` (
`message_id` varchar(100) NOT NULL,
`user_id` varchar(10) NOT NULL,
`amount` int(11) NOT NULL,
`state` varchar(10) NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP DATABASE IF EXISTS `rabbit_taobao_provider`;
CREATE DATABASE IF NOT EXISTS `rabbit_taobao_provider` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `rabbit_taobao_provider`;
DROP TABLE IF EXISTS `tb_account`;
CREATE TABLE IF NOT EXISTS `tb_account` (
`user_id` varchar(10) NOT NULL,
`amount` int(11) NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `tb_account` (`user_id`, `amount`, `update_time`) VALUES
('SAM0000001', 64000, '2019-09-22 14:30:48'),
('SAM0000002', 80000, '2019-09-21 18:10:44'),
('SAM0000003', 70000, '2019-09-21 18:10:57');
DROP TABLE IF EXISTS `tb_message`;
CREATE TABLE IF NOT EXISTS `tb_message` (
`message_id` varchar(100) NOT NULL,
`user_id` varchar(10) NOT NULL,
`amount` int(11) NOT NULL,
`state` varchar(10) NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
建立alipay-server工程, 下面是pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.teddy</groupId>
<artifactId>alipay-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>alipay-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web
</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
支付寶端的Dao文件
package com.teddy.alipayserver.dao;
import com.teddy.alipayserver.bean.Account;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Update;
public interface AccountMapper {
@Update("update tb_account set amount=amount-#{amount}, update_time=now() where user_id=#{userId}")
int updateAccount(Account account);
@Insert("insert tb_account(user_id, amount, update_time) values(#{userId}, #{amount}, now())")
int addAccount(Account account);
}
package com.teddy.alipayserver.dao;
import com.teddy.alipayserver.bean.Message;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.apache.ibatis.annotations.Insert;
import java.util.List;
public interface MessageMapper {
@Update("update tb_message set state=#{state} where message_id=#{message_id}")
int updateMessage(Message message);
@Insert("insert into tb_message(user_id, message_id, amount, state, update_time) values (#{user_id}, #{message_id}, #{amount}, 'unconfirm', now())")
int addMessage(Message message);
@Select("select * from tb_message where state=#{state}")
List<Message> queryMessageByState(String state);
}
service接口
package com.teddy.alipayserver.service;
public interface AlipayService {
//修改支付寶賬戶餘額的接口
public void updateAmount(int amount, String userId);
//回調接口 修改本地消息表消息的狀態
public void updateMessage(String param);
}
service接口實現
package com.teddy.alipayserver.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.teddy.alipayserver.bean.Account;
import com.teddy.alipayserver.bean.Message;
import com.teddy.alipayserver.config.RabbitmqSender;
import com.teddy.alipayserver.dao.AccountMapper;
import com.teddy.alipayserver.dao.MessageMapper;
import com.teddy.alipayserver.service.AlipayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.Random;
@Service
public class AlipayServiceImpl implements AlipayService{
@Autowired
TransactionTemplate transactionTemplate;
@Autowired
AccountMapper accountMapper;
@Autowired
MessageMapper messageMapper;
@Autowired
RabbitmqSender rabbitmqSender;
private static String SUCCESS="OK";
/**
* 1. 修改支付寶賬戶餘額信息
* 2. 插入本地消息表
* 3. 往mq中插入消息, 供餘額寶業務消息
* @param amount
* @param userId
*/
@Override
public void updateAmount(int amount, String userId) {
String messageId=(String)transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
//1. 修改餘額寶賬戶餘額信息
Account account = new Account();
account.setUserId(userId);
account.setAmount(amount);
int count=accountMapper.updateAccount(account);
if(count==1){
//構建一個本地消息對賬表
String messageId="Sam"+System.currentTimeMillis()+new Random().nextInt(Integer.MAX_VALUE);
Message message = new Message();
message.setMessage_id(messageId);
message.setUser_id(userId);
message.setAmount(amount);
//dao
int result = 0;
result = messageMapper.addMessage(message);
if(result==1){
return messageId;
}else{
System.out.println("update local message table failure");
return null;
}
}
//2. 插入本地消息表
return null;
}
});
if(messageId!=null && messageId.trim().length()>1){
// 構建待確認消息給MQ
Message message = new Message();
message.setMessage_id(messageId);
message.setUser_id(userId);
message.setAmount(amount);
message.setState("unconfirm");
// 消息發送模板發送這個消息
rabbitmqSender.sendMessage("exchange.message", "teddy.message.routeKey", message);
}
}
@Override
public void updateMessage(String param) {
JSONObject jsonObject= JSONObject.parseObject(param);
String respCode=jsonObject.getString("respCode");
String messageId=jsonObject.getString("messageId");
if(SUCCESS.equals(respCode)){
Message message=new Message();
message.setState("confirm");
message.setMessage_id(messageId);
messageMapper.updateMessage(message);
}
}
}
這裏通過Spring的TransactionTemplate引入了編程式事務, 因爲本地操作要成爲一個事務, 遠程的操作不可能和本地一個事務, 但是所有操作需要在一個方法裏, 所以引入了編程式的事務。
實體
package com.teddy.alipayserver.bean;
import java.util.Date;
public class Account {
private String userId;
private Integer amount;
private Date updateTime;
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
}
package com.teddy.alipayserver.bean;
import java.io.Serializable;
public class Message implements Serializable{
private static final long serialVersionUID=1L;
private String message_id;
private String user_id;
private Integer amount;
private String state;
public String getMessage_id() {
return message_id;
}
public void setMessage_id(String message_id) {
this.message_id = message_id;
}
public String getUser_id() {
return user_id;
}
public void setUser_id(String user_id) {
this.user_id = user_id;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
RabbitMQ的配置文件
package com.teddy.alipayserver.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitmqConfig {
@Bean(name="message")
public Queue queueMessage(){
return new Queue("teddy.message");
}
@Bean
public TopicExchange exchange(){
return new TopicExchange("exchange.message");
}
@Bean
Binding bindingExchangeMessage(@Qualifier("message") Queue queueMessage, TopicExchange exchange){
return BindingBuilder.bind(queueMessage).to(exchange).with("teddy.message.routeKey");
}
}
RabbitMQ的服務
package com.teddy.alipayserver.config;
import com.alibaba.fastjson.JSONObject;
import com.teddy.alipayserver.bean.Message;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class RabbitmqSender {
@Autowired
private AmqpTemplate amqpTemplate;
public void sendMessage(String exchange, String routeKey, Message content){
String message= JSONObject.toJSONString(content);
System.out.println("send message to MQ, waiting for alipay consuming:"+message);
amqpTemplate.convertAndSend(exchange, routeKey, message);
}
}
Controller
package com.teddy.alipayserver.controller;
import com.teddy.alipayserver.service.AlipayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AlipayController {
@Autowired
AlipayService alipayService;
@RequestMapping("/transfer")
@ResponseBody
public String transferAmount(String userId, int amount){
try {
alipayService.updateAmount(amount, userId);
} catch (Exception e) {
e.printStackTrace();
return "fail";
}
return"OK";
}
}
配置文件
spring:
datasource:
druid:
url: jdbc:mysql://192.168.25.132:3306/rabbit_taobao_provider?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
initial-size: 1
min-idle: 1
max-active: 20
test-on-borrow: true
driver-class-name: com.mysql.jdbc.Driver
application:
name: aplipay-server
rabbitmq:
host: 192.168.25.137
port: 5672
username: rabbit
password: 123456
server:
port: 8090
定時器, 用來重發消息
package com.teddy.alipayserver.timer;
import com.alibaba.fastjson.JSONObject;
import com.teddy.alipayserver.bean.Message;
import com.teddy.alipayserver.config.RabbitmqSender;
import com.teddy.alipayserver.dao.MessageMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
@Component
public class ScheduledService {
private static SimpleDateFormat sdf=new SimpleDateFormat("YYYY-MM-dd");
@Autowired
MessageMapper messageMapper;
@Autowired
RabbitmqSender rabbitmqSender;
@Scheduled(cron="0/60 0/1 * * * ?")
public void scheduledProcess(){
System.out.println("============>>>>>>>>>use cron "+sdf.format(new Date())+" start scan ......");
List<Message> unconfirmMessages=messageMapper.queryMessageByState("unconfirm");
if(unconfirmMessages!=null&&unconfirmMessages.size()>0){
System.out.println("query unconfirmed message:"+JSONObject.toJSONString(unconfirmMessages));
for(Message message:unconfirmMessages){
System.out.println("============timer send unconfirm message to mq"+ JSONObject.toJSONString(message));
rabbitmqSender.sendMessage("exchange.message", "teddy.message.routeKey", message);
}
}
}
}
監聽器, 用來監聽響應隊列中的消息
package com.teddy.alipayserver.listener;
import com.teddy.alipayserver.service.AlipayService;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class listener {
@Autowired
AlipayService alipayService;
@RabbitListener(queues="teddy.message.response")
public void process(final String result){
System.out.println("=====================receive balance transaction successul response message========"+result);
alipayService.updateMessage(result);
}
}
啓動文件
package com.teddy.alipayserver;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* http://localhost:8090/transfer?userId=SAM0000001&amount=3000
*/
@SpringBootApplication(scanBasePackages = "com.teddy.alipayserver")
@MapperScan(basePackages = {"com.teddy.alipayserver.dao"})
@EnableScheduling
public class AlipayServerApplication {
public static void main(String[] args) {
SpringApplication.run(AlipayServerApplication.class, args);
}
}
下面是餘額寶的工程, pom.xml和支付寶是一樣的
dao
package com.teddy.balanceserver.dao;
import com.teddy.balanceserver.bean.Account;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
public interface AccountMapper {
@Update("update tb_account set amount=amount-#{amount}, update_time=now() where user_id=#{userId}")
int updateAccount(@Param("amount")int amount, @Param("userId") String userId);
@Insert("insert tb_account(user_id, amount, update_time) values(#{userId}, #{amount}, now())")
int addAccount(Account account);
}
package com.teddy.balanceserver.dao;
import com.teddy.balanceserver.bean.Message;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface MessageMapper {
@Update("update tb_message set state=#{state} where message_id=#{message_id}")
int updateMessage(Message message);
@Insert("insert into tb_message(user_id, message_id, amount, state, update_time) values (#{userId}, #{messageId}, #{amount}, 'confirm', now())")
int addMessage(@Param("userId")String userId, @Param("messageId")String messageId, @Param("amount")int amount);
@Select("select * from tb_message where state=#{state}")
List<Message> queryMessageByState(@Param("state") String state);
@Select("select * from tb_message where message_id=#{messageId}")
List<Message> queryMessaegCountByMessageId(@Param("messageId") String messageId);
}
service
package com.teddy.balanceserver.service;
import com.teddy.balanceserver.bean.Account;
public interface BalanceService {
public int queryMessaegCountByMessageId(String messageId);
public void updateAmount(int amount, String userId);
public void addMessage(String userId, String messageId, int amount);
}
service的實現
package com.teddy.balanceserver.service.impl;
import com.teddy.balanceserver.bean.Account;
import com.teddy.balanceserver.bean.Message;
import com.teddy.balanceserver.dao.AccountMapper;
import com.teddy.balanceserver.dao.MessageMapper;
import com.teddy.balanceserver.service.BalanceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BalanceServiceImpl implements BalanceService{
@Autowired
AccountMapper accountMapper;
@Autowired
MessageMapper messageMapper;
@Override
public int queryMessaegCountByMessageId(String messageId) {
List<Message> messages = messageMapper.queryMessaegCountByMessageId(messageId);
return messages.size();
}
@Override
public void updateAmount(int amount, String userId) {
accountMapper.updateAccount(amount, userId);
}
@Override
public void addMessage(String userId, String messageId, int amount) {
messageMapper.addMessage(userId, messageId, amount);
}
}
隊列的配置
package com.teddy.balanceserver.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitmqConfig {
@Bean(name="message")
public Queue queueMessage(){
return new Queue("teddy.message.response");
}
@Bean
public TopicExchange exchange(){
return new TopicExchange("exchange.message.response");
}
@Bean
Binding bindingExchangeMessage(@Qualifier("message") Queue queueMessage, TopicExchange exchange){
return BindingBuilder.bind(queueMessage).to(exchange).with("teddy.message.routeKey.response");
}
}
發送服務
package com.teddy.balanceserver.config;
import com.alibaba.fastjson.JSONObject;
import com.teddy.balanceserver.bean.Message;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class RabbitmqSender {
@Autowired
private AmqpTemplate amqpTemplate;
public void sendMessage(String exchange, String routeKey, String content){
System.out.println("send message to MQ, waiting for alipay consuming:"+content);
amqpTemplate.convertAndSend(exchange, routeKey, content);
}
}
監聽器
package com.teddy.balanceserver.listener;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.teddy.balanceserver.bean.Message;
import com.teddy.balanceserver.config.RabbitmqSender;
import com.teddy.balanceserver.service.BalanceService;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
@Component
public class ReceiveListener {
private static String SUCCESS="OK";
@Autowired
BalanceService balanceService;
@Autowired
TransactionTemplate transactionTemplate;
@Autowired
RabbitmqSender rabbitmqSender;
@RabbitListener(queues="teddy.message")
public void process(String jsonStr){
final Message message=JSONObject.parseObject(jsonStr, Message.class);
System.out.println("========balance start to consume MQ's message, message is: "+jsonStr);
boolean isSuccess = (Boolean) transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
//1. 修改本地餘額寶的賬戶餘額
//2. 修改本地餘額成功消息插入本地消息對賬表
//編程式事務
String message_id=message.getMessage_id();
int count=balanceService.queryMessaegCountByMessageId(message_id);
// count==0代表餘額寶沒有處理過該流水號的記錄
if(count==0){
balanceService.updateAmount(message.getAmount(), message.getUser_id());
balanceService.addMessage(message.getUser_id(), message_id, message.getAmount());
return true;
}else{
System.out.println("this message id:"+message_id+"already consumed. transaction stopped");
return false;
}
}
});
//3. 修改本地餘額成功消息通過MQ的應答隊列發送給支付寶進行確認
if(isSuccess){
JSONObject jsonObject = new JSONObject();
jsonObject.put("messageId", message.getMessage_id());
jsonObject.put("respCode", SUCCESS);
System.out.println("===========send transaction successful response message to alipay======"+jsonObject.toJSONString());
rabbitmqSender.sendMessage("exchange.message.response", "teddy.message.routeKey.response", jsonObject.toJSONString());
}else{
}
}
}
配置文件
spring:
datasource:
druid:
url: jdbc:mysql://192.168.25.132:3306/rabbit_taobao_consumer?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
initial-size: 1
min-idle: 1
max-active: 20
test-on-borrow: true
driver-class-name: com.mysql.jdbc.Driver
application:
name: balance-server
rabbitmq:
host: 192.168.25.137
port: 5672
username: rabbit
password: 123456
server:
port: 8080
啓動類
package com.teddy.balanceserver;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication(scanBasePackages = "com.teddy.balanceserver")
@MapperScan(basePackages = {"com.teddy.balanceserver.dao"})
@EnableScheduling
public class BalanceServerApplication {
public static void main(String[] args) {
SpringApplication.run(BalanceServerApplication.class, args);
}
}