1,模擬支付寶轉賬到餘額寶的整個操作流程圖如下
2,直接上代碼
Alipay---支付寶
xml配置
AccountMapper.xml
<mapper namespace="com.reyco.core.dao.AccountDao">
<select id="getAccountById" resultType="Account">
select id,name,amount from account where id = #{id}
</select>
<update id="updateAccountById">
update account set amount = amount-#{amount},gmtModified=now() where id= #{id}
</update>
</mapper>
MessageMapper.xml
<mapper namespace="com.reyco.core.dao.MessageDao">
<!-- 新增 -->
<insert id="insertMessage">
insert into message(accountId,price,status,gmtCreate,gmtModified) values(#{accountId},#{price},#{status},now(),now())
<selectKey resultType="int" keyProperty="id" order="AFTER">
select @@identity
</selectKey>
</insert>
<select id="getMessageById" resultType="MessageMQ">
select id,price,accountId,status from message where id = #{id}
</select>
<update id="updateStatusById">
update message set status = #{status},gmtModified=now() where id = #{id}
</update>
<!-- 掃描所有未確認的消息 -->
<select id="listMessageByStatus" resultType="MessageMQ">
select id,price,accountId,status from message where status = 1
</select>
</mapper>
Mapper代碼
AccountDao.java
public interface AccountDao {
/**
* 查詢餘額
* @param accountId
* @return
*/
Account getAccountById(Integer accountId);
/**
* 轉賬-->出錢
* @param accountId 轉賬人id
* @param amount 轉賬金額
* @param messageId 消息id
*/
void updateAccountById(Account account);
}
MessageDao.java
public interface MessageDao {
/**
* 新增message
* @param message 實體參數
*/
void insertMessage(MessageMQ message);
/**
* 查詢
* @param id
* @return
*/
MessageMQ getMessageById(Integer id);
/**
* 掃描未確認的消息
* @return
*/
List<MessageMQ> listMessageByStatus();
/**
* 修改狀態
* @param id
* @param status
*/
void updateStatusById(MessageMQ message);
}
Service代碼
AccountService.java
public interface AccountService {
Account getAccountById(Integer accountId);
/**
* 轉賬
* @param accountId 轉賬人id
* @param amount 轉賬金額
* @param messageId 消息id
* @param messageStatus 消息狀態
*/
void updateAccountById(Account account);
/**
*
* @param accountId
* @param amount
* @return
*/
boolean transfer(Integer accountId, Integer amount);
void testUpdateAccountById(Account account);
}
messageService.java
public interface MessageService {
/**
* 新增message
* @param message 實體參數
*/
void insertMessage(MessageMQ message);
/**
* 查詢
* @param id
* @return
*/
MessageMQ getMessageById(Integer id);
/**
* 掃描未確認的消息
* @return
*/
List<MessageMQ> listMessageByStatus();
/**
* 修改狀態
* @param id
* @param status
*/
void updateStatusById(String param);
}
QueueProducerService.java
public interface QueueProducerService {
/**
* 發送Object queue消息
* @param message 消息內容
* @return
*/
public void sendObjectMessage(MessageMQ message);
/**
* 發送text queue消息
* @param message 消息內容
* @return
*/
public void sendTextMessage(String message);
}
ServiceImpl代碼
AccountServiceImpl.java
@Service("accountService")
public class AccountServiceImpl implements AccountService {
protected Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private AccountDao accountDao;
@Autowired
private MessageService messageService;
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private QueueProducerService queueProducerService;
/**
* 轉賬流程:
* 1,修改金額;
* 2,修改金額成功,新增一條未確認消息,修改金額失敗,不新增消息;
* 3,新增消息成功,向MQ發送一條消息。
* 如果向MQ發送消息失敗(消息丟失),用一個定時任務,定時掃描未確認的消息用於消息(發送MQ消息).
*/
public boolean transfer(Integer id, Integer amount) {
// 未提交狀態
Integer stats = 1;
MessageMQ message = new MessageMQ(id,amount,stats);
Boolean flag = transactionTemplate.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
try {
logger.info("###########支付寶方查詢餘額操作###########id==" + id);
Account account = accountDao.getAccountById(id);
Integer oldAmount = account.getAmount();
logger.info("###########支付寶方查詢當前賬號餘額###########OldAmount==" + oldAmount);
if (oldAmount < amount) {
logger.info("###########支付寶方餘額不足###########" + account);
return false;
}
// 設置餘額
account.setAmount(amount);
logger.info("###########支付寶方扣款操作###########" + account);
accountDao.updateAccountById(account);
logger.info("###########支付寶方添加消息操作###########" + message);
// 新增一條消息
messageService.insertMessage(message);
return true;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
}
});
// 失敗
if (!flag) {
return false;
}
// 獲取新增message的id
Integer messageId = message.getId();
// 向MQ發送一條消息
logger.info("###########支付寶方向MQ發送消息###########" + message);
queueProducerService.sendObjectMessage(message);
return true;
}
/**
* 轉賬流程:
* 1,修改金額;
* 2,修改金額成功,新增一條未確認消息,修改金額失敗,不新增消息;
* 3,新增消息成功,向MQ發送一條消息。
* 如果向MQ發送消息失敗(消息丟失),用一個定時任務,定時掃描未確認的消息用於消息(發送MQ消息).
*/
@Transactional(rollbackFor = Throwable.class)
public boolean transfer1(Integer id, Integer amount) {
logger.info("###########支付寶方查詢餘額操作###########id==" + id);
Account account = accountDao.getAccountById(id);
Integer oldAmount = account.getAmount();
logger.info("###########支付寶方查詢當前賬號餘額###########OldAmount==" + oldAmount);
if (oldAmount < amount) {
logger.info("###########支付寶方餘額不足###########" + account);
return false;
}
// 設置餘額
account.setAmount(amount);
logger.info("###########支付寶方扣款操作###########" + account);
accountDao.updateAccountById(account);
// 未確認狀態
Integer stats = 1;
MessageMQ message = new MessageMQ(id, amount, stats);
logger.info("###########支付寶方添加消息操作###########" + message);
// 新增一條消息
messageService.insertMessage(message);
// 獲取新增message的id
Integer messageId = message.getId();
// 向MQ發送一條消息
if (null != messageId) {
logger.info("###########支付寶方向MQ發送消息###########" + message);
queueProducerService.sendObjectMessage(message);
}
return true;
}
@Override
public Account getAccountById(Integer accountId) {
return accountDao.getAccountById(accountId);
}
@Override
public void updateAccountById(Account account) {
accountDao.updateAccountById(account);
}
@Override
public void testUpdateAccountById(Account account) {
Boolean flag = (Boolean) transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
try {
account.setAmount(100);
System.out.println("第一次" + account);
accountDao.updateAccountById(account);
account.setAmount(10000);
System.out.println("第一次" + account);
accountDao.updateAccountById(account);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
});
System.out.println("###############flag###########" + flag);
}
}
MessageServiceImpl.java
@Service("messageService")
public class MessageServiceImpl implements MessageService {
protected Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private MessageDao messageDao;
@Override
public void insertMessage(MessageMQ message) {
messageDao.insertMessage(message);
}
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void updateStatusById(String param) {
try {
// JSONObject jsonObject = JSONObject.parseObject(param);
// String messageId = jsonObject.getString("messageId");
// String respCode = jsonObject.getString("respCode");
logger.info("############支付寶收到回調參數##########"+param);
Integer id = Integer.parseInt(param);
MessageMQ message = this.getMessageById(id);
logger.info("############支付寶根據根據回調參數查詢消息##########"+message);
Integer search_id = message.getId();
if (id.equals(search_id)) {
Integer search_status = message.getStatus();
if(search_status==1) {
Integer status = 0;
message.setStatus(status);
logger.info("############支付寶根據根據回調修改消息狀態##########"+message);
messageDao.updateStatusById(message);
logger.info("############支付寶消息已更新確認狀態##########"+message);
}
}
} catch (Exception e) {
logger.error("############消息更新失敗############。。。"+param);
throw e;
}
}
@Override
public MessageMQ getMessageById(Integer id) {
return messageDao.getMessageById(id);
}
@Override
public List<MessageMQ> listMessageByStatus() {
return messageDao.listMessageByStatus();
}
}
QueueProducerSerivceImpl.java
@Service("queueProducerService")
public class QueueProducerSerivceImpl implements QueueProducerService {
@Autowired
private JmsTemplate jmsTemplate;
@Override
public void sendObjectMessage(MessageMQ message) {
//設置發送地址
this.jmsTemplate.setDefaultDestinationName("distributedTransaction-message");
jmsTemplate.send(new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
return session.createObjectMessage(message);
}
});
}
@Override
public void sendTextMessage(String message) {
//設置發送地址
this.jmsTemplate.setDefaultDestinationName("text-message");
jmsTemplate.send(new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(message);
}
});
}
}
MessageScheduledJob.java
/**
* 定時掃描消息發送失敗的消息
* 重發失敗消息
* @author reyco
*
*/
public class MessageScheduledJob extends QuartzJobBean {
protected Logger logger = LoggerFactory.getLogger(getClass());
/**
* messageService
*/
private MessageService messageService;
/**
* MQ queueService
*/
private QueueProducerService queueProducerService;
public void setMessageService(MessageService messageService) {
this.messageService = messageService;
}
public void setQueueProducerService(QueueProducerService queueProducerService) {
this.queueProducerService = queueProducerService;
}
@Override
protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException {
logger.info("--------------------------------------------------");
logger.info("###################定時任務被執行啦################");
List<MessageMQ> messages = messageService.listMessageByStatus();
logger.info("###################發送失敗的消息################"+messages);
if(messages.size() < 1) {
logger.info("###################沒有發送失敗的消息################");
return;
}
//
MessageTask task = new MessageTask();
task.setMessages(messages);
task.setQueueProducerService(queueProducerService);
task.run();
}
}
messageTask.java
/**
* 消費信息任務
*
* @author reyco
*
*/
public class MessageTask {
protected Logger logger = LoggerFactory.getLogger(getClass());
/**
* 信息集合
*/
private List<MessageMQ> messages;
private QueueProducerService queueProducerService;
public void setMessages(List<MessageMQ> messages) {
this.messages = messages;
}
public void setQueueProducerService(QueueProducerService queueProducerService) {
this.queueProducerService = queueProducerService;
}
public void run() {
Integer size = messages.size();
Integer countSize = this.getCountSize(size);
Integer threalPoolSize = size / countSize + 1;
List<MessageMQ> newMessages = new ArrayList<>();
for (int i = 0; i < threalPoolSize; i++) {
if((i+1) == threalPoolSize) {
int startIndex = i * countSize;
int endIndex = size;
newMessages = messages.subList(startIndex, endIndex);
}else {
int startIndex = i * countSize;
int endIndex = (i+1) * countSize;
newMessages = messages.subList(startIndex, endIndex);
}
logger.info("################newMessages size################"+newMessages.size());
new Thread(new Runnable() {
@Override
public void run() {
for(MessageMQ message : messages) {
logger.info("################定時任務向MQ發送消息################"+message);
queueProducerService.sendObjectMessage(message);
}
}
}).start();
}
}
/**
* 獲取每個線程池執行任務數
*
* @param messages
* @return
*/
private Integer getCountSize(int size) {
Integer countSize = 10;
if (null == messages) {
return 0;
}
if (size < 5) {
countSize = 5;
} else if (size >= 5 && size < 33) {
countSize = 5;
} else if (size >= 33 && size < 133) {
countSize = 10;
} else if (size >= 133 && size < 533) {
countSize = 20;
} else if (size >= 533 && size < 1333) {
countSize = 30;
} else if (size >= 1333 && size < 5000) {
countSize = 60;
} else {
countSize = 200;
}
return countSize;
}
}
AccountController.java
@RequestMapping("account")
@Controller
public class AccountController {
protected Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private AccountService accountService;
@Autowired
private MessageService messageService;
@ResponseBody
@RequestMapping("transfer")
public String transfer(Integer accountId,Integer amount) {
if(null == accountId || null == amount) {
return "轉賬失敗。。。";
}
logger.info("Parameter:\taccountId==="+accountId+"\tamount==="+amount);
boolean flag = accountService.transfer(accountId, amount);
if(flag) {
logger.info("##############支付寶方轉賬成功,預計24小時之內到賬,請注意查收!##############");
return "轉賬成功,預計24小時之內到賬,請注意查收!";
}
return "轉賬失敗。。。";
}
@ResponseBody
@RequestMapping("callback")
public String updateStatus(String param) {
if(null == param || "".equals(param)) {
return "fail";
}
logger.info("################Parameter:\t#################param==="+param.toString());
try {
messageService.updateStatusById(param);
return "success";
} catch (Exception e) {
return "fail";
}
}
@ResponseBody
@RequestMapping("test")
public String testUpdateStatus() {
Account account = new Account();
account.setId(1);
accountService.testUpdateAccountById(account);
return "ok";
}
}
balance----餘額寶
xml配置
accountMapper.xml
<mapper namespace="com.reyco.core.dao.AccountDao">
<select id="getAccountById" resultType="Account">
select id,name,amount from account where id = #{id}
</select>
<update id="updateAccountById">
update account set amount = amount + #{amount},gmtModified=now() where id=#{id}
</update>
</mapper>
messageMapper.xml
<mapper namespace="com.reyco.core.dao.MessageDao">
<select id="countMessageById" resultType="int">
select count(*) from message where id = #{id}
</select>
<!-- 新增 -->
<insert id="insertMessage">
insert into message(id,accountId,price,status,gmtCreate,gmtModified) values(#{id},#{accountId},#{price},#{status},now(),now())
</insert>
</mapper>
Mapper代碼
accountDao.java
public interface AccountDao {
/**
* 查詢餘額
* @param accountId
* @return
*/
Account getAccountById(Integer id);
/**
* 轉賬-->出錢
* @param accountId 轉賬人id
* @param amount 轉賬金額
* @param messageId 消息id
*/
void updateAccountById(Account account);
}
messageDao.java
public interface MessageDao {
/**
* 查詢
* @param id
* @return
*/
Integer countMessageById(Integer id);
/**
* 新增message
* @param message 實體參數
*/
void insertMessage(MessageMQ message);
}
Service代碼
AccountService.java
public interface AccountService {
/**
* 查詢餘額
* @param accountId
* @return
*/
Account getAccountById(Integer id);
/**
* 轉賬---收錢
* @param accountId 轉賬人id
* @param amount 轉賬金額
*/
void updateAccountById(Account account);
}
messageService.java
public interface MessageService {
/**
* 查詢
* @param id
* @return
*/
Integer countMessageById(Integer id);
/**
* 新增message
* @param message 實體參數
*/
void insertMessage(MessageMQ message);
}
ServiceImpl代碼
AccountServiceImpl.java
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void updateAccountById(Account account) {
Integer accountId = account.getId();
Account ac = getAccountById(accountId);
if(null == ac) {
return;
}
// 修改金額
accountDao.updateAccountById(account);
}
@Override
public Account getAccountById(Integer accountId) {
return accountDao.getAccountById(accountId);
}
}
MessageServiceImpl.java
@Service("messageService")
public class MessageServiceImpl implements MessageService {
@Autowired
private MessageDao messageDao;
@Override
public Integer countMessageById(Integer id) {
return messageDao.countMessageById(id);
}
@Override
public void insertMessage(MessageMQ message) {
messageDao.insertMessage(message);
}
}
QueueMessageListener.java
@Component("messageListener")
public class QueueMessageListener implements MessageListener {
protected Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private AccountService accountService;
@Autowired
private MessageService messageService;
/**
* 收款流程:
* 1,監聽消息,拿到MQ消息;
* 2,到消息表查詢該消息是否消費過;
* 3,該消息如未消費(沒有查到該消息),執行本地事務;
* 4,執行本地事務1,首先修改賬號金額;
* 5,執行本地事務2,向消息表新增消息;
* 6,回調
*/
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void onMessage(Message message) {
try {
if (message instanceof ObjectMessage) {
ObjectMessage om = (ObjectMessage) message;
Object data = om.getObject();
if (data instanceof MessageMQ) {
MessageMQ mcMQ = (MessageMQ) data;
Integer mcMQId = mcMQ.getId();
// 先去查詢是否消費過
Integer count = messageService.countMessageById(mcMQId);
// 沒有消費過
if (0 == count) {
Integer accountId = mcMQ.getAccountId();
Integer amount = mcMQ.getPrice();
Account account = new Account();
account.setId(accountId);
account.setAmount(amount);
// 收錢
logger.info("###########餘額寶方收款操作###########"+amount);
accountService.updateAccountById(account);
logger.info("###########餘額寶方收款成功###########"+amount);
mcMQ.setStatus(0);
logger.info("###########餘額寶方新增消息操作###########"+mcMQ);
// 新增消息
messageService.insertMessage(mcMQ);
logger.info("###########餘額寶方新增消息成功###########"+mcMQ);
RestTemplate restTemplate = this.getRestTemplate();
// 請求參數
//JSONObject jsonObject = new JSONObject();
//jsonObject.put("messageId", mc.getId());
//jsonObject.put("respCode", 200);
//String url = "http://localhost:80/alipay/account/callback.do?param=" + jsonObject.toString();
//ResponseEntity<Object> response = restTemplate.getForEntity(url, null);
String url = "http://localhost:80/alipay/account/callback.do?param=" + mcMQ.getId();
logger.info("###########餘額寶方回調支付寶操作###########"+url);
String response = restTemplate.getForObject(url, String.class);
if("fail".equals(response)) {
logger.error("###########餘額寶方回調支付寶失敗###########"+message);
throw new RuntimeException("###########餘額寶方回調支付寶失敗###########。。。" + message);
}else {
logger.info("###########餘額寶方回調支付寶成功###########" + message);
}
} else {
logger.info("#################異常轉賬###########");
}
}
}
} catch (Exception e) {
logger.error("###########餘額寶方消息消費失敗###########"+message);
throw new RuntimeException("###########餘額寶方消息消費失敗###########" + message);
}
}
/**
* 請求工具
* @return
*/
public RestTemplate getRestTemplate() {
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
simpleClientHttpRequestFactory.setConnectTimeout(3000);
simpleClientHttpRequestFactory.setReadTimeout(3000);
return new RestTemplate(simpleClientHttpRequestFactory);
}
}