補充說明:生鮮電商平臺-提現模塊的設計與架構,提現功能指的賣家把在平臺掙的錢提現到本身的支付寶或者銀行卡的一個過程。後端
功能相對而言不算複雜,有如下幾個功能須要處理。安全
業務邏輯以下;架構
1. 賣家登錄本身的B2B系統提交提現功能。app
2. 若是沒有綁定銀行卡或者支付寶,則須要先綁定銀行卡或者支付寶帳戶,以及填寫提現密碼post
3. 支付寶或者銀行卡須要跟用戶的姓名所填一致,防止錯誤轉帳。ui
4. 後端須要記錄所提現的記錄,實際狀況是支付寶提現須要收取手續費,這個也須要記錄在內。加密
5. 須要造成一個審覈機制,用戶提現的狀態有申請提現,審覈成功,提現成功,提現失敗四種可能狀態。設計
6,每一個提現的過程須要記錄時間軸,若是有拒絕,用戶須要查看拒絕的緣由。3d
7.全部的提現到帳後,須要平臺短信通知用戶申請了提現,提現成功,包括提現拒絕等等,都須要短信通知,給用戶一個信任感。日誌
8,天天晚上5:30以前提現當日到達,以後的第二天早上10點鐘到達。
9,系統自動審覈提現的金額數據量的正確與否,來源於用戶的訂單以及帳單數據。
相關的系統設計表以下:
1.提現信息表,爲了便於你們理解,我詳細的註釋都寫上了。
CREATE TABLE `withdrawal` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自動增長ID', `uid` bigint(20) NOT NULL COMMENT '提現申請人', `withdraw_order` varchar(64) NOT NULL COMMENT '提現訂單號,系統自動生成的.', `withdraw_bank_id` bigint(20) NOT NULL COMMENT '用戶對應的卡的編號', `withdraw_charge` decimal(12,2) NOT NULL COMMENT '提現手續費', `withdraw_reality_total` decimal(12,2) NOT NULL COMMENT '實際提現金額', `withdraw_apply_total` decimal(12,2) NOT NULL COMMENT '申請提現的金額', `withdraw_apply_time` datetime NOT NULL COMMENT '申請提現時間', `status` int(11) NOT NULL COMMENT '提現狀態,1表示申請提現,2表示審批經過,3,交易完成,-1審批不經過.', `create_by` bigint(20) DEFAULT NULL COMMENT '建立人', `create_time` datetime DEFAULT NULL COMMENT '建立時間', `update_by` bigint(20) DEFAULT NULL COMMENT '修改人', `last_update_time` datetime DEFAULT NULL COMMENT '最後修改時間', PRIMARY KEY (`id`), KEY `unique_order` (`withdraw_order`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='提現信息表';
2. 賣家綁定卡的記錄表
CREATE TABLE `withdrawal_bank` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自動增長ID', `uid` bigint(20) NOT NULL COMMENT '用戶ID', `cnname` varchar(8) NOT NULL COMMENT '中文姓名', `bank_code` varchar(32) NOT NULL COMMENT '卡的縮寫,例如:ICBC', `bank_name` varchar(32) NOT NULL COMMENT '卡的名字', `bank_number` varchar(64) NOT NULL COMMENT '卡號', `sequence` tinyint(11) DEFAULT NULL COMMENT '排序用。按照小到大排序。', `create_time` datetime NOT NULL COMMENT '建立時間', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 COMMENT='用戶綁定的銀行';
補充說明:若是是支付寶,那麼bank_code填寫alipay,bank_name爲支付寶,bank_number爲支付寶卡號,cnname爲提現的姓名
3. 賣家提現日誌表。(會根據賣家的提現時間,造成時間軸)
CREATE TABLE `withdrawal_logs` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自動增長ID', `uid` bigint(20) NOT NULL COMMENT '提現申請人', `withdraw_order` varchar(64) NOT NULL COMMENT '提現訂單號,系統自動生成的.', `remark` varchar(64) NOT NULL COMMENT '備註', `status` int(11) DEFAULT NULL COMMENT '提現的狀態', `create_by` bigint(20) DEFAULT NULL COMMENT '建立人', `create_time` datetime DEFAULT NULL COMMENT '建立時間', PRIMARY KEY (`id`), KEY `unique_order` (`withdraw_order`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COMMENT='用戶提現日誌表';
補充說明:造成時間軸來顯示。
4. 賣家提現密碼:
CREATE TABLE `withdrawal_password` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自動增長ID', `uid` bigint(20) NOT NULL COMMENT '用戶ID', `password` varchar(32) NOT NULL COMMENT '密碼,md5加密', `create_time` datetime NOT NULL COMMENT '記錄建立時間', `last_update_time` datetime DEFAULT NULL COMMENT '最後一次更新時間', PRIMARY KEY (`id`), UNIQUE KEY `unique_key_uid` (`uid`) ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COMMENT='用戶提現密碼';
補充說明:因爲設計到資金安全問題,提現須要設置提現密碼,這個有別於用戶的登錄密碼。
整個業務比較簡單,只是步驟比較多而已。
相關的業務核心代碼以下:
2.1 賣家綁定本身的銀行卡或者支付寶
/** * 添加用戶銀行信息 * @param request * @param response * @return */ @RequestMapping(value = "/withdrawalBank/add", method = { RequestMethod.GET, RequestMethod.POST }) public JsonResult addWithdrawalBank(HttpServletRequest request, HttpServletResponse response,@RequestBody WithdrawalBank withdrawalBank) { try { if(withdrawalBank==null) { return new JsonResult(JsonResultCode.FAILURE, "傳入對象有誤", ""); } Long uid = withdrawalBank.getUid(); String bankCode = withdrawalBank.getBankCode(); if(uid == null) { return new JsonResult(JsonResultCode.FAILURE, "參數有誤", ""); } //拿到當前銀行卡的惟一編號 WithdrawalBank dbWithdrawalBank = withdrawalBankService.getWithdrawalBankByUidAndBankCode(uid, bankCode); if(dbWithdrawalBank != null){ return new JsonResult(JsonResultCode.FAILURE, "卡已存在,請重試",dbWithdrawalBank); } int result = withdrawalBankService.insertWithdrawalBank(withdrawalBank); if (result>0) { return new JsonResult(JsonResultCode.SUCCESS, "添加用戶銀行成功", result); } return new JsonResult(JsonResultCode.FAILURE, "添加用戶銀行失敗", ""); }catch(Exception e){ logger.error("[WithdrawalBankController][addWithdrawalBank] exception :",e); return new JsonResult(JsonResultCode.FAILURE, "系統錯誤,請稍後重試",""); } }
2.2 修改與管理本身的提現密碼:
/** * 提現金額計算 */ @RequestMapping(value = "/withdrawal/count", method = { RequestMethod.GET, RequestMethod.POST }) public JsonResult countWithdraw(HttpServletRequest request, HttpServletResponse response, BigDecimal withdrawApplyTotal, Long userId) { logger.info("WithdrawalController.countWithdraw.start"); try { Money m = new Money(withdrawApplyTotal); Map<String, BigDecimal> result = new HashMap<String, BigDecimal>(); BigDecimal withdrawCharge = null; BigDecimal withdrawRealityTotal = null; if (m.compareTo(new Money(1500)) < 0) { // 提現手續費 withdrawCharge = (new BigDecimal(2).add(withdrawApplyTotal.multiply(new BigDecimal(0.0055)))) .setScale(2, BigDecimal.ROUND_HALF_UP); // 實際提現金額 withdrawRealityTotal = withdrawApplyTotal.subtract(withdrawCharge); } else { // 提現手續費 withdrawCharge = withdrawApplyTotal.multiply(new BigDecimal(0.007)).setScale(2, BigDecimal.ROUND_HALF_UP); // 實際提現金額 withdrawRealityTotal = withdrawApplyTotal.subtract(withdrawCharge); } result.put("withdrawCharge", withdrawCharge); result.put("withdrawRealityTotal", withdrawRealityTotal); result.put("withdrawApplyTotal", withdrawApplyTotal); return new JsonResult(JsonResultCode.SUCCESS, "計算成功", result); } catch (Exception ex) { logger.error("[WithdrawalController][countWithdraw]exception ", ex); return new JsonResult(JsonResultCode.FAILURE, "系統異常,請稍後再試", ""); } } /** * 提現申請 */ @RequestMapping(value = "/withdrawal/apply", method = { RequestMethod.GET, RequestMethod.POST }) public JsonResult applyWithDrawal(HttpServletRequest request, HttpServletResponse response, @RequestBody Withdrawal withdrawal) { logger.info("WithdrawalController.applyWithDrawal.start"); try { Long userId = withdrawal.getUid(); if (userId==null) { return new JsonResult(JsonResultCode.FAILURE, "參數異常!", ""); } // 查詢提現表中是否存在當前用戶正在審覈的提現,若是存在就不容許繼續申請 Withdrawal withdrawalByUserId = withdrawalService.getWithdrawalByUserId(userId); if (withdrawalByUserId != null) { return new JsonResult(JsonResultCode.FAILURE, "已有提現記錄,正在審覈中!", withdrawalByUserId); } //所屬賣家 Seller seller = sellerService.getSellerById(userId); // 賣家可提現金額 BigDecimal billMoney = seller.getBillMoney(); logger.info("[WithdrawalController][applyWithDrawal]當前用戶userId:" + userId + " 可提現金額:" + billMoney); // 賣家申請提現金額; BigDecimal withdrawApplyTotal = withdrawal.getWithdrawApplyTotal(); logger.info("[WithdrawalController][applyWithDrawal]當前用戶userId:" + userId + " 申請的提現金額:" + withdrawApplyTotal); // 若是申請的金額大於系統的金額,則返回1,同時不符合邏輯,直接返回error if (withdrawApplyTotal.compareTo(billMoney) == 1) { return new JsonResult(JsonResultCode.FAILURE, "申請金額錯誤,返回重試!", ""); } String orderNumber = OrderIDGenerator.getOrderNumber(); withdrawal.setWithdrawApplyTime(new Date()); withdrawal.setCreateTime(new Date()); withdrawal.setWithdrawOrder(orderNumber); // 申請提現 withdrawal.setStatus(WithdrawalConstant.APPLY_WITHDRAWAL); WithdrawalLogs withdrawalLogs = new WithdrawalLogs(); withdrawalLogs.setWithdrawOrder(orderNumber); withdrawalLogs.setCreateTime(new Date()); withdrawalLogs.setStatus(WithdrawalConstant.APPLY_WITHDRAWAL); withdrawalLogs.setCreateBy(userId); withdrawalLogs.setUid(userId); withdrawalLogs.setRemark("提現已提交,審覈中!"); withdrawalService.applyWithdrawal(withdrawal, withdrawalLogs); sendSmsNotice(withdrawal, userId, seller, billMoney, withdrawApplyTotal); return new JsonResult(JsonResultCode.SUCCESS, "申請提現成功", ""); } catch (Exception ex) { logger.error("[WithdrawalController][applyWithDrawal]exception ", ex); return new JsonResult(JsonResultCode.FAILURE, "申請失敗,系統異常,請稍後再試", ""); } } /** * 發送短信通知給賣家 * @param withdrawal * @param userId * @param seller * @param billMoney * @param withdrawApplyTotal */ private void sendSmsNotice(Withdrawal withdrawal, Long userId, Seller seller, BigDecimal billMoney,BigDecimal withdrawApplyTotal) { try { // 發送短信給賣家 SmsMessage smsMessage = new SmsMessage(); smsMessage.setAccount(seller.getSellerAccount()); smsMessage.setAmount(withdrawal.getWithdrawApplyTotal()); smsMessage.setName(seller.getRealName() == null ? "" : seller.getRealName()); SmsMessageService smsMessageService = new SmsMessageServiceImpl(); smsMessageService.applicationWithdrawal(smsMessage, environment); // 保存信息到短信日誌中 Sms sms = new Sms(); String msg = "當前用戶userId:" + userId + ",申請的提現金額:" + withdrawApplyTotal + ",可提現金額:" + billMoney; sms.setPhone(seller.getSellerAccount()); sms.setMessage(msg); sms.setRemark("提現申請"); sms.setCreateTime(new Date()); smsService.saveSms(sms); }catch(Exception ex) { logger.error("[WithdrawalController][sendSmsNotice]exception",ex); } } /** * 提現申請 列表 * @param withdrawal * 傳遞查詢條件 * @param request * @param response * @return */ @RequestMapping(value = "/withdrawal/applyList", method = { RequestMethod.GET, RequestMethod.POST }) public JsonResult applyWithDrawalList(HttpServletRequest request, HttpServletResponse response, Long userId, int currentPageNum, int currentPageSize) { logger.info("WithdrawalController.applyWithDrawalList.start"); try { if (null == userId) { return new JsonResult(JsonResultCode.FAILURE, "參數有誤,userId不能爲空", ""); } PageUtil pageUtil = withdrawalService.getPageResult(userId, currentPageNum, currentPageSize); return new JsonResult(JsonResultCode.SUCCESS, "查詢成功", pageUtil); } catch (Exception ex) { logger.error("[WithdrawalController][applyWithDrawalList]exception ", ex); return new JsonResult(JsonResultCode.FAILURE, "申請失敗,系統異常,請稍後再試", ""); } } /** * 根據提現訂單號獲取訂單的詳細狀況 * * @param request * @param response * @return */ @RequestMapping(value = "/withdrawal/order", method = { RequestMethod.GET, RequestMethod.POST }) public JsonResult withdrawalOrder(HttpServletRequest request, HttpServletResponse response, String withdrawalOrder) { logger.info("WithdrawalController.withdrawalOrder.start"); try { if (StringUtils.isBlank(withdrawalOrder)) { return new JsonResult(JsonResultCode.FAILURE, "提現訂單號有誤,請從新輸入", ""); } WithdrawalQuery withdrawal = withdrawalService.getWithdrawalByWithdrawalOrder(withdrawalOrder); if (withdrawal == null) { return new JsonResult(JsonResultCode.FAILURE, "提現訂單號不存在,請從新填寫", ""); } return new JsonResult(JsonResultCode.SUCCESS, "查詢成功", withdrawal); } catch (Exception ex) { logger.error("[WithdrawalController][applyWithDrawalList]exception ", ex); return new JsonResult(JsonResultCode.FAILURE, "系統異常,請稍後再試", ""); } }
3. 提現記錄表核心代碼
/** * 賣家提現功能---提現銀行設置 */ @RestController @RequestMapping("/seller") public class WithdrawalBankController extends BaseController { private static final Logger logger = LoggerFactory.getLogger(WithdrawalBankController.class); @Autowired private WithdrawalBankService withdrawalBankService; /** * 根據用戶Uid查詢用戶綁定的銀行卡信息; * @param request * @param response * @param withdrawal 條件查詢 * @return */ @RequestMapping(value = "/withdrawalBank/list", method = { RequestMethod.GET, RequestMethod.POST }) public JsonResult withdrawalBankList(HttpServletRequest request, HttpServletResponse response,Long userId,Model model) { try { List<WithdrawalBank> withdrawalBankList = withdrawalBankService.getWithdrawalBankByUid(userId); if(CollectionUtils.isEmpty(withdrawalBankList)) { return new JsonResult(JsonResultCode.SUCCESS, "用戶未綁定銀行卡", withdrawalBankList); } return new JsonResult(JsonResultCode.SUCCESS, "查詢用戶銀行卡信息", withdrawalBankList); }catch(Exception ex){ logger.error("[WithdrawalBankController][withdrawalBankList] exception :",ex); return new JsonResult(JsonResultCode.FAILURE, "系統錯誤,請稍後重試",""); } } /** * 添加用戶銀行信息 * @param request * @param response * @return */ @RequestMapping(value = "/withdrawalBank/add", method = { RequestMethod.GET, RequestMethod.POST }) public JsonResult addWithdrawalBank(HttpServletRequest request, HttpServletResponse response,@RequestBody WithdrawalBank withdrawalBank) { try { if(withdrawalBank==null) { return new JsonResult(JsonResultCode.FAILURE, "傳入對象有誤", ""); } Long uid = withdrawalBank.getUid(); String bankCode = withdrawalBank.getBankCode(); if(uid == null) { return new JsonResult(JsonResultCode.FAILURE, "參數有誤", ""); } //拿到當前銀行卡的惟一編號 WithdrawalBank dbWithdrawalBank = withdrawalBankService.getWithdrawalBankByUidAndBankCode(uid, bankCode); if(dbWithdrawalBank != null){ return new JsonResult(JsonResultCode.FAILURE, "卡已存在,請重試",dbWithdrawalBank); } int result = withdrawalBankService.insertWithdrawalBank(withdrawalBank); if (result>0) { return new JsonResult(JsonResultCode.SUCCESS, "添加用戶銀行成功", result); } return new JsonResult(JsonResultCode.FAILURE, "添加用戶銀行失敗", ""); }catch(Exception e){ logger.error("[WithdrawalBankController][addWithdrawalBank] exception :",e); return new JsonResult(JsonResultCode.FAILURE, "系統錯誤,請稍後重試",""); } } }
4. 賣家提現日誌表:
/** * 賣家提現功能---提現日誌記錄 */ @RestController @RequestMapping("/seller") public class WithdrawalLogsController extends BaseController { private static final Logger logger = LoggerFactory.getLogger(WithdrawalLogsController.class); @Autowired private WithdrawalLogsService withdrawalLogsService; /** * 根據Uid和withdrawOrder查詢單個提現詳情 * * @param userId * @param withdrawOrder * @return */ @RequestMapping(value = "/withdrawalLogs/getLogsByWithdrawOrder", method = { RequestMethod.GET,RequestMethod.POST }) public JsonResult getWithdrawalLogsByUidAndWithdrawOrder(HttpServletRequest request, HttpServletResponse response, Long userId, String withdrawOrder) { try { if (StringUtils.isBlank(withdrawOrder)) { return new JsonResult(JsonResultCode.FAILURE, "請求參數異常", ""); } List<WithdrawalLogs> withdrawalLogs = withdrawalLogsService.getWithdrawalLogsByWithdrawOrder(withdrawOrder); return new JsonResult(JsonResultCode.SUCCESS, "訂單詳情", withdrawalLogs); } catch (Exception ex) { logger.error("[WithdrawalLogsController][getWithdrawalLogsByUidAndWithdrawOrder]", ex); return new JsonResult(JsonResultCode.FAILURE, "系統錯誤,請稍後重試", ""); } } }
相關的運營截圖以下: