精华内容
下载资源
问答
  • Java生鲜电商平台-提现模块的设计与架构 补充说明:生鲜电商平台-提现模块的设计与架构,提现功能指的卖家把在平台挣的钱提现到自己的支付宝或者银行卡的一个过程。 功能相对而言不算复杂,有以下几个功能...

    Java生鲜电商平台-提现模块的设计与架构

    补充说明:生鲜电商平台-提现模块的设计与架构,提现功能指的卖家把在平台挣的钱提现到自己的支付宝或者银行卡的一个过程。

     

    功能相对而言不算复杂,有以下几个功能需要处理。

     

    业务逻辑如下;

                           1. 卖家登陆自己的B2B系统提交提现功能。

                           2. 如果没有绑定银行卡或者支付宝,则需要先绑定银行卡或者支付宝账户,以及填写提现密码

                           3. 支付宝或者银行卡需要跟用户的姓名所填一致,防止错误转账。

                           4. 后端需要记录所提现的记录,实际情况是支付宝提现需要收取手续费,这个也需要记录在内。

                           5. 需要形成一个审核机制,用户提现的状态有申请提现,审核成功,提现成功,提现失败四种可能状态。

                           6,每个提现的过程需要记录时间轴,如果有拒绝,用户需要查看拒绝的原因。

                           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, "系统错误,请稍后重试", "");
            }
        }
    }

     

    相关的运营截图如下:

     

     

     

     

     

     

     

     

     

    转载于:https://www.cnblogs.com/jurendage/p/9053523.html

    展开全文
  • 充说明:Java开源生鲜电商平台-提现模块的设计与架构(源码可下载),提现功能指的卖家把在平台挣的钱提现到自己的支付宝或者银行卡的一个过程。   功能相对而言不算复杂,有以下几个功能需要处理。   业务逻辑...

    https://blog.csdn.net/hongjuntu/article/details/80893860

    充说明:Java开源生鲜电商平台-提现模块的设计与架构(源码可下载),提现功能指的卖家把在平台挣的钱提现到自己的支付宝或者银行卡的一个过程。

     

    功能相对而言不算复杂,有以下几个功能需要处理。

     

    业务逻辑如下;

                           1. 卖家登陆自己的B2B系统提交提现功能。

                           2. 如果没有绑定银行卡或者支付宝,则需要先绑定银行卡或者支付宝账户,以及填写提现密码

                           3. 支付宝或者银行卡需要跟用户的姓名所填一致,防止错误转账。

                           4. 后端需要记录所提现的记录,实际情况是支付宝提现需要收取手续费,这个也需要记录在内。

                           5. 需要形成一个审核机制,用户提现的状态有申请提现,审核成功,提现成功,提现失败四种可能状态。

                           6,每个提现的过程需要记录时间轴,如果有拒绝,用户需要查看拒绝的原因。

                           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, "系统错误,请稍后重试", "");
            }
        }
    }

    复制代码

     

    相关的运营截图如下:

     https://img2.mukewang.com/5b042c900001c11203630607.jpg

     

     

     

     

     https://img2.mukewang.com/5b042cad000196d703610523.jpghttps://img.mukewang.com/5b042cb7000166ef03490586.jpghttps://img4.mukewang.com/5b042cca0001416a03530646.jpg

     

     

     https://img.mukewang.com/5b042cdb0001da7b03600641.jpg

     

    原文出处

    JAVA


    作者:
    链接:https://www.imooc.com/article/30477
    来源:慕课网

    展开全文
  • Java生鲜电商平台-账单模块的设计与架构 补充说明:生鲜电商平台-账单模块的设计与架构,即用户的账单形成过程。 由于系统存在一个押账功能的需求,(何为押账,就是形成公司的资金池,类似摩拜单车,ofo单车...

    Java生鲜电商平台-账单模块的设计与架构

     

    补充说明:生鲜电商平台-账单模块的设计与架构,即用户的账单形成过程。

     

    由于系统存在一个押账功能的需求,(何为押账,就是形成公司的资金池,类似摩拜单车,ofo单车等等)。目前B2B平台也是采用押账的这种功能策略。

     

    这里有个特别说明的押账方式:就是比如有个卖家张三,他是5月1日跟我们平台签约开始入住平台卖菜,我们约定好押账7天,那么他5月1日的金额会在5月2日存入

    他自己的余额里面,但是这个钱不能马上提取出来,需要等一个星期,也就是5月8日可以提现5月1日的金额,5月9日可以提现5月2日以前的所有金额。

     

    这个算法的最大好处就是永远的压住客户7天的金额。

     

    这个算法采用的是Spring quartz定时器每天晚上23:00点处理的。

    相关核心的代码如下:

    /**
     * 任务工作
     * @author wangfucai
     */
    @Component
    public class TasksQuartz{
        
        private static final Logger logger=LoggerFactory.getLogger(TasksQuartz.class);
        
        @Autowired
        private BillService billService;
        @Autowired
        private SellerService sellerService;
        @Autowired
        private DeliveryIncomeService deliveryIncomeService;
        @Autowired
        private BuyerService buyerService;
        @Autowired
        private OrderInfoService orderInfoService;
        @Autowired
        private GroupsBuyerService groupsBuyerService;
        
        /**
         * 计算每天账单
         * 每天23点执行
         */
        @Scheduled(cron="0 0 23 * * ?")
        protected void makeBill(){
            try
            {
                logger.info("TasksQuartz.execute.start");
                //统计当天的交易完成的订单生成账单
                billService.addBills();
                logger.info("账单数据更新完成");
                //根据卖家抽点金额更新账单实际金额
                billService.updateRealAmountByPercentage();
                logger.info("根据卖家抽点金额更新账单实际金额完成");
                //更新卖家余额
                sellerService.updateBalanceByBill();
                logger.info("卖家余额数据更新完成");
                logger.info("TasksQuartz.execute.end");
            }catch(Exception ex)
            {
                logger.error("TasksQuartz.execute.exception",ex);
            }
        }

     

    补充说明:1.需要统计每个卖家今天的收入。

                      2.并行的需要把订单的数据存入账单表。

                      3.余额来源于账单表。形成一个数据的流转体现。

     

    账单表的表结构如下:

    补充说明:每天定时器会根据卖家的账期形成账单,最终更新到用卖家的余额里面。

                      实际运营情况来讲是每个卖家的账期是不一样的,有的两天,有的三天,有的一周,有的是一个月。

     

    相关核心算法与代码如下:

           

     
    /**
         * 统计10天前的账单更新卖家余额和账单金额
         */
        @Override
        public void updateMoney() {
            // 获取10天前的日期d
            String day = DateUtil.dateToString(DateUtil.addDay(new Date(), -9), DateUtil.FMT_DATE);
            // 查询十天前的所有帐单信息
            List<Map<String, Object>> list = billDao.getBillsByDay(day);
            if (CollectionUtils.isEmpty(list)) {
                logger.info("TasksQuartz.updateMoney.isEmpty-->day:" + day);
                return;
            }
            for (Map<String, Object> map : list) {
                // 卖家ID
                Long sellerId = (Long) map.get("sellerId");
                if (sellerId == null) {
                    continue;
                }
                // 获取提现的金额即最终账单的金额
                BigDecimal realityMoney = (BigDecimal) map.get("realIncome");
                if (realityMoney == null) {
                    continue;
                }
                // 获取卖家的余额
                BigDecimal balanceMoney = (BigDecimal) map.get("balanceMoney");
                if (balanceMoney == null) {
                    balanceMoney = BigDecimal.ZERO;
                }
                // 获取卖家的账单金额
                BigDecimal billMoney = (BigDecimal) map.get("billMoney");
                if (billMoney == null) {
                    billMoney = BigDecimal.ZERO;
                }
                // 金额相加
                BigDecimal resultBalanceMoney = realityMoney.add(balanceMoney);
    
                BigDecimal resultBillMoney = realityMoney.add(billMoney);
    
                logger.info("当前用户sellerId:" + sellerId + " 当前的余额为:balanceMoney=" + balanceMoney
                        + " 最终金额:resultBalanceMoney=" + resultBalanceMoney);
    
                logger.info("当前的余额为:billMoney=" + billMoney + " 最终金额:resultBillMoney=" + resultBillMoney);
                // 更新卖家余额和账单金额
                int result = sellerDao.updateMoney(sellerId, resultBalanceMoney, resultBillMoney);
                logger.info("当前用户sellerId:" + sellerId + " 更新结果为:" + (result > 0));
            }
            // 更新十天前的所有账单的状态
            int count = billDao.updateStatus(day);
            logger.info(" 更新" + count + "条账单,状态变为已结算");
        }

     

    业务说明:

                      1. 无外乎每天需要统计卖家的今日收益情况。

                      2. 更新卖家的最终余额。

                      3.  根据卖家的所设置的账单周期,形成用户的账单金额。

                      4. 最终根据账单金额,形成用户的可提现余额的过程。

                业务有点绕口,但是整体是非常地清晰的,思路就是押用户所配置的账期金额。配置10天就压10天,配置15天就压15天。

     

    以下是账单跟卖家的核心关联表,就是配置所属的卖家对应的所属账期时间。

     

     

    总结:整个技术方面其实都不算复杂,主要是业务逻辑以及统计的一些概念,希望这些定时器计算,账单思路形成,架构方面能给大家一些帮助。

    转载于:https://www.cnblogs.com/jurendage/p/9053417.html

    展开全文
  • java

    千次阅读 2020-07-17 16:35:33
    jvm:jre的一部分,他是整个java实现跨平台的最核心部分,负责解释执行Java字节码文件,是运行java字节码文件的虚拟计算机,java跨平台的原因:Java源文件被虚拟机编译成字节码文件后,生成的是与平台无关的字节码,...

    毕业一年后,趁工作闲暇之余整理一些Java面试题,上传到网上纯粹当做笔记,有错欢迎指出 --(2)

    java基础篇

    一、JDK 和 JRE 有什么区别?
    jdk:java开发工具包,包含jre,jre中包含jvm
    jre:java运行环境
    jvm:jre的一部分,他是整个java实现跨平台的最核心部分,负责解释执行Java字节码文件,是运行java字节码文件的虚拟计算机,java跨平台的原因:Java源文件被虚拟机编译成字节码文件后,生成的是与平台无关的字节码,这些字节码只面向于jvm,不同平台的jvm都是不同的,但是他们都提供了相同的接口
    jvm执行过程:1.加载.class文件 2.运行.class文件
    二、java中 == 与equals区别 ?
    == 如果比较的两个操作数是数值类型,即使他们的数据类型不相等,只要他们的值相等也将返回true,如果比较的是引用类型的话则比较的是地址是否相等。
    equals比较的是object类的方法如果不重写equals的话那么比较的就是直接调用的==进行比较,一般equals都是要被重写的,所以关注的是有没有重写equals方法
    三、两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?
    不一定,Java开发中有几个约定:两个对象调用equals返回true,那么对象的hashcode必须返回相同的结果,二两个对象用equals返回false,但是不要求hashcode返回false,但是为了哈希表的性能最好返回不同值,重写equals方法必须重写hashcode,从而保证equals相同的两个对象返回相同的hashcode
    四、final 在 java 中有什么作用?
    1.修饰类。类不可以被继承
    2.修饰方法,方法不能被重写,但是可以被重载
    3.修饰基本数据类型的变量,变量只能一次赋值后不能修改,修饰引用类型变量后,也就是修饰一个对象,引用初始化以后永远指向一个内存地址不可修改,但是内存中保存的对象信息是可以修改的。
    五、java 中操作字符串都有哪些类?它们之间有什么区别?
    String,StringBuffer线程安全,StringBulider线程不安全
    六、如何将字符串反转?
    1.调用字符串的reserve方法
    2.new一个char数组,然后拿到每一个字符,调用字符串的chatAt方法拿到每一个索引的字符,存放到char数组中
    七、String类的常用方法?
    length,charAt,valueOf,构造方法
    八、抽象类必须要有抽象方法吗?
    抽象类不一定非要有抽象方法,有抽象方法的一定要是抽象类
    九、普通类和抽象类有哪些区别?
    普通类可以实例化,抽象类要想实例化必须指向实现所有方法的子类对象
    抽象类的抽象方法必须全部被子类实现,如果不能全部实现那么子类也必须是抽象类
    十、抽象类可以使用final修饰吗?
    不能,因为抽象类是要被继承的,final修饰后不可继承是矛盾的
    十一、.java 中 IO 流分为几种?
    按照流向分可以分为输入流、输出流
    按照操作单元划分为字节流、字符流
    InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流
    OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流
    十二、字节流字符流区别?
    字节流是程序的计量单位,字符是应用程序中的字符表示
    字节流是直接操作文件的,而字符流操作文件是使用到了缓冲区,通过缓冲区在操作文件,所以在程序中如果没有关闭字符流的操作,缓冲区中的内容是没发输出的,关闭字符流会强制将缓冲区中的内容进行输出。或者使用Writer类中的flush()方法完成内容输出
    总结:字节流没有使用缓冲区,而字符流使用到了缓冲区
    缓冲区可以理解为一块内存区域,如果程序频繁的操作某一个资源比如数据库,那么性能将会降低,为了提高性能可以将一部分数据暂时存放在内存的一块区域中,以后直接从内存中操作数据即可
    由字节流转化为字符流实际上就是操作byte[]转化为String
    十三、Files的常用方法有哪些?
    Files.exists() 检测文件路径是否存在
    Files.createFile()创建文件
    Files.read() 读取文件
    Files.write()写入文件

    java容器

    一、java 容器都有哪些?
    数组,和集合容器
    二、Collection 和 Collections 有什么区别?
    Collection是集合的顶层接口,其实现类有list和set
    Collections是一个操作集合的工具类,里面定义了很多操作集合的静态方法是一个帮助类
    三、如何决定使用 HashMap 还是 TreeMap?
    TreeMap的key是实现comparable所以迭代是默认按照key值迭代升序排列的,TreeMap底层是红黑树数据适用于自然排序
    HashMap的key值是实现散列hashcode,分布是随机的不支持排序,底层数据结构是数组或者链表,适合在map中插入,删除和定位元素
    结论:如果需要得到一个有序的结果的时候就使用TreeMap,其他情况使用HashMap,HashMap拥有比较好的性能
    四、说一下 HashMap 的实现原理?HashSet 的实现原理?
    结合HashMap的put/get方法进行研究(记住HashMap的几个默认值如,默认长度为16,动态扩容时负载因子为0.75,扩容方式为二倍扩容),hashSet底层是由HashMap实现的,只不过将HashMap的value值写死为定值
    五、.ArrayList 和 LinkedList 的区别是什么?
    1.ArrayList是动态数组的实现,而LinkedList是双向链表的实现
    随机访问效率中ArrayList效率要高于LinkedList,前者根据索引而后者需要移动指针
    综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。
    2.对ArrayList的源码分析

    
    public boolean add(E var1);//默认在末尾直接添加元素
    public boolean add(E var1) {
    	//先判断内部容量是否足够了,size是数组中数据的个数,因为要添加一个元素所以size+1
    	//判断size+1这个数,数组中是否放的下,若数组是个空的数组则分配默认空间为10的长度
    	//如果size+1大于10这个长度,那么就说明elementData数组的长度不够用,那么就要增加
    	//elementData的length,调用grow方法,进行自动扩展ArrayList的大小
    	this.ensureCapacityInternal(this.size + 1);
    	this.elementData[this.size++] = var1;
    	return true;
    }
    扩容
    
    private void grow(int minCapacity) {
        // overflow-conscious code
        //将扩充前的elementData大小给oldCapacity
        int oldCapacity = elementData.length;
        //newCapacity就是1.5倍的oldCapacity,因为向右移1位代表除以2
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            //这句话就是适应elementData空数组的时候,length=0,那么oldCapacity=0,newCapacity=0,所以这个判断成立,在这里就是真正的初始化elementData的大小了,前面的工作都是准备工作。
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            //如果newCapacity超过了最大的容量的限制,就调用hugeCapacity,也就是能给的最大值给newCapacity
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //新的容量大小已经确定好了,就copy数组,改变容量大小
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    

    六、.如何实现数组和 List 之间的转换?
    数组转List: Arrays.stream(str).collect(Collectors.toList());或者Arrays.asList,但是这种方法是不能对集合进行新增和删除操作的
    集合转数组:直接new一个数组,调用集合的toArray方法,构造方法里面传入集合大小
    七、.ArrayList 和 Vector 的区别是什么?
    Vector是线程安全的,扩容方式100%的扩容
    ArrayList线程不安全,扩容方式50%的扩容
    如过在集合中使用数据量比较大的数据或者多线程的话,用vector有一定的优势
    八、Array 和 ArrayList 有何区别?
    定义一个数组的时候必须指定数组的长度和数据类型
    ArrayList是动态数组,长度动态可变,会自动扩容,不使用泛型的时候可以添加不同类型元素
    九、哪些集合类是线程安全的?
    vector、hashtable、ConcurrentHashMap
    十、迭代器 Iterator 是什么?
    Iterator对集合类中的任何一个实现类,都可以返回这样一个Iterator对象。可以适用于任何一个类
    迭代器是可以遍历集合的对象,为各种容器提供了操作集合的接口,迭代器与集合类成对增加,用于顺序访问集合对象的元素,无需知道对象的底层实现
    十一、使用for循环还是迭代器Iterator对比
    采用ArrayList对随机访问比较快,而for循环中的get()方法,采用的即是随机访问的方法,因此在ArrayList里,for循环较快
    采用LinkedList则是顺序访问比较快,iterator中的next()方法,采用的即是顺序访问的方法,因此在LinkedList里,使用iterator较快
    for循环适合访问顺序结构,可以根据下标快速获取指定元素.而Iterator 适合访问链式的顺序结构,因为迭代器是通过next()和Pre()来定位的.可以访问没有顺序的集合
    十二、Iterator 怎么使用?有什么特点?
    Iterator配合集合使用,使用 hasNext 检查集合中是否还有元素 和 next 方法获得集合中的下一个元素,遍历
    特点:Iterator遍历元素的过程中不允许对元素进行修改,否则会抛出ConcurrentModificationEception并发修改的异常,因为在Iterator底层实现中存在两个变量modCount和expectedModCount 必须相等,所以不能使用list集合的remove方法,(借鉴网上说法)Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变。当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出java.util.ConcurrentModificationException 异常。所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。应该使用iterator自带的remove,移除后并没有return,而是重新维护了索引,使得两个常量有重新相等Iterator遍历集合元素的过程中可以通过remove方法来移除集合中的元素
    十三、Iterator和 ListIterator有什么区别?
    ListIterator 继承 Iterator,所以他比 Iterator方法多
    使用范围不同,Iterator可以迭代所有集合;ListIterator 只能用于List及其子类
    ListIterator 有 add 方法,可以向 List 中添加对象;Iterator 不能
    ListIterator 有 set()方法,可以实现对 List 的修改;Iterator 仅能遍历,不能修改
    十四、怎么确保一个集合不能被修改?
    使用Collections类的unmodifiable方法赋值原来的集合即可

    多线程

    一、并行和并发有什么区别?
    并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
    并发是交替做不同事情的能力,而并行是同时做不同事的能力
    二、线程和进程的区别?
    1.进程是一段正在执行的程序,是资源分配的基本单元,而线程是CPU调度的基本单元
    2.进程之间相互独立,不能共享资源
    3.线程是进程的一部分
    三、守护线程是什么?
    守护线程,是一个服务线程,准确的来说就是服务其他线程,其他线程就是只有一种就是用户线程,所以Java中线程分为两类
    1.守护线程,比如垃圾回收线程
    2.用户线程,应用程序里面的自定义线程
    换一种说法,就是当用户自定义线程还没有结束,jvm就不会退出,当然守护线程也不会退出,因为他要进行垃圾回收任务
    四、说一下 runnable 和 callable 有什么区别?
    1.实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
    2.Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛
    互斥锁是一种简单的加锁的方法来控制对共享资源的访问
    五、线程有哪些状态?
    新建-就绪-运行-阻塞-死亡
    1.新建状态:当用new操作符新建一个线程时,线程此时处于新建状态,此时程序还没有开始运行程序中的代码
    2.就绪状态:一个新建的线程不自动开始执行,要执行线程,必须调用线程的start方法,start方法返回后线程处于就绪状态,处于就绪状态的线程不一定立即开始运行run方法,此时还要获取cpu的执行权,当获取到CPU的执行权以后,才能开始真正的执行线程的run方法,因为在单CPU的计算机操作系统中,是不允许同时运行多个线程的,一个时刻只能有一个线程运行,因此此时会有许多线程处于就绪状态
    3.运行状态:当线程获得到CPU执行权后,他才进入运行状态,开始执行真正的run方法
    4.阻塞状态:线程在执行过程中,可能由于各种原因进入阻塞状态。如:调用sleep方法进入睡眠状态,试图得到一个锁,但是这个锁正在被其他线程锁持有,所谓阻塞状态就是正在运行的线程没有运行结束,暂时让出CPU的执行权,这时处于就绪状态的线程就可以获取CPU时间进入运行状态
    5.死亡状态:run方法正常退出死亡,未捕获的异常终止run方法导致线程猝死。可以使用isAlive方法。如果是可运行或被阻塞,这个方法返回true;如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false
    六、sleep() 和 wait() 有什么区别
    sleep方法就线程Thread类的静态方法,调用该方法使线程进入睡眠状态,让出执行机会给其他线程,等待休眠结束后进入就绪状态然后再和其他线程一起争夺cpu执行权,因为是线程类的静态方法,所以当在一个synchronized块中调用了sleep方法,线程虽然进入了休眠但是没有释放锁,其他线程仍然无法访问这个对象
    wait是Object类的一个方法,当一个线程执行到wait方法时,会释放一个对象的锁,使得其他线程能够访问,可以通过notify或者notifyAll方法唤醒等待中的线程
    综上所述:sleep() 和 wait() 的区别就是 调用sleep方法的线程不会释放对象锁,而调用wait() 方法会释放对象锁
    七、notify()和 notifyAll()有什么区别?
    1.先了解两个概念,锁池和等待池
    锁池:假设线程A已经拥有了某个对象的的锁,注意:不是类,而其他线程想要进入对象的synchronized方法之前必须先要获取对象的锁的拥有权,但是该对象的锁目前正被线程A持有,所以这些对象就进入了对象的锁池中
    等待池:假设线程A调用了对象的wait方法,线程A释放了对象的锁后,就进入了该对象的等待池中,等待池中的线程不会竞争该对象的锁
    2.当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
    八、线程的 run()和 start()有什么区别?
    start方法是用来启动线程执行的,当线程获取cpu执行权时,会自动调用run方法,所以start()调用结束并不表示相应线程已经开始运行,这个线程可能稍后运行,也可能永远也不会运行。
    而直接调用run方法无法达到启动多线程的目的,相当于主线程线性的执行多线程的run()方法。
    九、创建线程池的几种方式
    Executors提供了几种创建线程池的创建配置
    1.newFixedThreadPool:固定大小的线程池,可以指定线程池的大小,该线程池corePoolSize和maximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值
    2.newSingleThreadExecutor:单个线程线程池,只有一个线程的线程池,阻塞队列使用的是LinkedBlockingQueue,若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先入先出的顺序执行任务
    3.newCachedThreadPool:缓存线程池,缓存的线程默认存活60秒。线程的核心池corePoolSize大小为0,核心池最大为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。是一个直接提交的阻塞队列
    4.newScheduledThreadPool:定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据
    十、线程池都有哪些状态?
    1.RUNNING,这是最正常的状态,接受新的任务,处理等待队列中的任务。线程池的初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0
    2.SHUTDOWN,不接受新的任务提交,但是为继续处理等待中的任务,调用线程池的shutdown方法时,线程池由running状态转为shutdown
    3.STOP.不接受新的任务提交,不在处理等待中的任务,中断正在执行任务的线程,调用线程池的shutdownNow方法时,线程池变为stop
    4.TIDYING。所有任务都销毁,线程池的状态在转换为 TIDYING 状态
    5.TERMINATED。线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
    十一、线程池中 submit()和 execute()方法有什么区别?
    execute() 没有返回值;参数Runnable,没有返回值无法确定任务是否完成
    submit() 有返回值,参数runnable或者callable,submit() 的返回值 Future 调用get方法时,可以捕获处理异常
    十二、在 java 程序中怎么保证多线程的运行安全?加锁机制synchronized
    线程安全的问题体现在:
    原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性
    可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到
    有序性:程序执行的顺序按照代码的先后顺序执行
    解决:
    JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
    synchronized、volatile、LOCK,可以解决可见性问题
    Happens-Before 规则可以解决有序性问题
    十三、什么是死锁?
    死锁是指两个或者两个以上的进程在执行过程中,由于彼此竞争资源而产生的一种阻塞的现象
    死锁的产生的原因:
    1.互斥条件:一段时间内一个资源只能由一个进程所占有
    2.请求与保持条件:当进程由于请求资源而阻塞时,对以获取的资源保持不放
    3.不剥夺条件:进程在资源未使用完成之前,不可剥夺,只能由自己使用完之后由自己释放
    预防死锁:
    1.一次性分配:一次性分配所有的资源,这样就不会有请求了
    2.可剥夺资源:当某进程获得了部分资源,释放已经占有的资源
    3.破坏请求保持条件:当进程由于请求资源而阻塞时,释放获取的资源
    十四、ThreadLocal 是什么?有哪些使用场景?
    ThreadLocal是线程本地存储,每一个线程都创建了一个本地的ThreadLocalMap对象,每一个线程都可以访问自己Map对象里的value,通过这种方式避免资源在多线程之间的共享
    get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本
    1.经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection,
    2.session管理
    十五、synchronized 和 volatile 的区别是什么?
    1.volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
    2.volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
    3.volatile是变量之间的线程共享,synchronized则通过加锁的机制实现线程安全
    自定义线程池的实现:
    ThreadPoolExecutor 线程池中最核心的一个类

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory);
    

    参数分析:
    1.corePoolSize:线程池核心池大小,当创建线程池后,线程池默认没有任何线程,当有任务过来时才会有创建线程执行任务。精炼的说就是表示线程池中允许同时运行的最大线程数
    2.maximumPoolSize:线程池中允许的最大线程数
    3.keepAliveTime:表示线程没有任务时最多保持多久然后停止
    4.unit:keepAliveTime的单位
    5.workQueue:一个阻塞队列,用来存储等待执行的任务,当线程池中线程数超过他的corePoolSize时,线程进入阻塞队列进行等待
    6.threadFactory:线程工厂用来创建线程
    7.handler :表示当拒绝处理任务时的策略。
    流程:任务进来时,首先执行判断,判断核心线程是否处于空闲状态,如果不是,核心线程就先就执行任务,如果核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行,如果满了,在判断最大可容纳的线程数,如果没有超出这个数量,就开创非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。
    handler:表示当拒绝处理任务时的策略,有以下四种取值

    ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
    ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
    ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 ;
    

    JVM

    一、说一下 jvm 的主要组成部分?及其作用?
    1.类加载器class loader,加载类文件到内存,class loader只负责加载,只要符合文件结构就加载,至于能否运行他不负责,由执行引擎Execution Engine负责
    2.执行引擎Execution Engine,负责 解释命令,交由操作系统执行,执行class中的指令
    3.本地库接口,融合不同的语言为Java所用,与本地库交互
    4.运行时数据区:又叫做java内存模型,分为程序计数器,Java堆,虚拟机栈,本地方法栈,方法区包括运行时常量池
    运行时常量池是方法区的一部分,主要负责存放预编译时期生成的各种字面量与符号引用,运行时可以将新的常量放入池中,符号引用是指用一组符号来描述引用的目标,而不是地址描述引用的目标
    二、.说一下堆栈的区别?
    栈内存首先是一块内存区域存储着局部变量,方法的引用等,在方法中定义的都是局部变量,在方法外的都是全局变量,在for循环中也是局部变量,是先加载函数再加载局部变量,所以方法肯定是先进栈,然后再定义变量,变量都有自己的作用域,变量的作用域可以分为类级,方法级,块级,所以一旦离开作用域,变量就会被释放,栈内存的的更新速度都很快,所以局部变量的声明周期都很短。
    堆内存:存放的是数组和对象,凡是new出来的都是建立在堆中,堆中存放的是对象实体,实体中存放多个数据,就算数据消失,对象也不会消失,不像栈一样,函数走完,栈内存就会释放空间,堆中的存放的实体虽然不会释放但是会被jvm的垃圾回收机制进行回收
    三、队列和栈的区别?
    队列:先进先出,是限定只能在表的一端进行插入和另一端删除操作的线性表
    栈:先进后出,是限定之能在表的一端进行插入和删除操作的线性表
    四、什么是双亲委派模型?
    当需要加载一个类的时候,子类加载器并不会马上去加载,而是依次去请求父类加载器加载,一直往上请求到最高类加载器:启动类加载器。当启动类加载器加载不了的时候,依次往下让子类加载器进行加载。当达到最底下的时候,如果还是加载不到该类,就会出现ClassNotFound的情况。
    类加载器:启动类加载器,扩展类加载器,应用类加载器,自定义类加载器
    五、说一下类加载的执行过程?
    1.加载:获取类的二进制字节流,将其静态存储结构转化为方法区的运行时数据结构
    2.校验:文字格式验证,字节码的验证
    3.准备:在方法区对类的static变量分配内存并设置类变量数据类型默认的初始值,基本数据/引用类型数据
    4.解析:将常量池内的符号引用替换为直接引用的过程
    符号引用以一组符号(任何形式的字面量(定位代码的变量),只要在使用时能够无歧义的定位到目标即可)来描述所引用的目标。
    直接引用通过对符号引用进行解析,找到引用的实际内存地址
    5.初始化:为类的静态变量赋予正确的初始值,执行静态语句块、执行构造函数
    六、怎么判断对象是否可以被回收?
    引用计数器:引用计数器:为每个对象创建一个引用计数,当有对象引用时,计数器+1,当引用释放时,计数器-1,所以,当计数器为0时,就认为可以被回收。
    可达性分析:可达性分析 从GC Roots开始向下搜索,搜索所走过的路径称为引用链。 当一个对象到GC Roots没有任何引用链时,则认为此对象可以被回收
    七、java 中都有哪些引用类型?
    1.强引用(strongreference)就是指在程序代码之中普遍存在的,类似“Object obj=new Object()” 这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象实例。当内存不足时,宁愿抛出OutOfMemeryError异常也不会通过回收强引用的对象,因为JVM认为强引用的对象是用户正在使用的对象,它无法分辨出到底该回收哪个,强行回收有可能导致系统严重错误

    2.软引用(softreference)如果一个对象只有软引用,那么只有当内存不足时,JVM才会去回收该对象,其他情况不会回收。软引用可以结合ReferenceQueue来使用,当由于系统内存不足,导致软引用的对象被回收了,JVM会把这个软引用加入到与之相关联的ReferenceQueue中。

    3.弱引用(weakreference)也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱 引用关联的对象实例只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时, 无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。在 JDK 1.2 之 后,提供了 WeakReference 类来实现弱引用。

    4.虚引用(phantomreference)也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象 实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用 来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象 实例被收集器回收时收到一个系统通知。在 JDK 1.2 之后,提供了 PhantomReference 类来实现虚引用。

    八.说一下 jvm 有哪些垃圾回收算法?
    ··标记-清除算法:分为两个阶段:标记和清除
    在标记阶段从跟对象开始遍历,对跟对象可以访问到的对象都打上一个标识,一般都是在对象的堆中,被标记后的对象,将其记录为可达对象。
    在清除阶段,collector对从头到尾进行线性遍历,如果发现某个对象没有标记为可达对象-通过读取对象的堆信息,将其回收
    ··标记-整理算法
    标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

    九、说一下 jvm 有哪些垃圾回收器?
    CMS,G1
    ···CMS(Concurrent Mark Sweep) 收集器: 是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片,CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用
    ··G1收集器: 标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确地控制停顿。
    G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
    十、新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?
    新生代回收器:Serial、ParNew、Parallel Scavenge
    老年代回收器:Serial Old、Parallel Old、CMS
    整堆回收器:G1
    新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。
    十一、.简述分代垃圾回收器是怎么工作的?
    可以从新生代,老年代下手理解
    新生代分为1个Eden区和2个Survivor区(分别叫from和to)默认比例为8:1,一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。
    因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。

    在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

    十二、常用的 jvm 调优的参数都有哪些?idea中compiler中可配置
    -Xms s为strating,表示堆内存起始大小
    -Xmx x为max,表示最大的堆内存
    -Xss64m 设置每个线程的堆栈大小

    十三、说一下 jvm 调优的工具?
    Jconsole : jdk自带
    JProfiler:商业软件
    VisualVM:JDK自带

    Java web

    一、说一下 session 的工作原理?
    session的工作原理就是用户在登录成功以后创建session,session创建完成以后,会把sessionID发送给客户端,,客户端在存储在浏览器当中,这样在每次访问服务器的时候都会带着这个sessionID,服务器拿到sessionID后再内存中找到相应的session就可以工作了
    二。如果客户端禁止 cookie 能实现 session 还能用吗?
    一般情况下,服务器的session的sessionid是通过cookie储存在浏览器的,一旦浏览器禁用了cookie浏览器无法携带sessionid,这样服务器无法识别用户的身份session失效。
    可以通过一下方法,继续使用session
    1.URL重写,将sessionid追加到原URL参数中,这样访问服务器就携带sessionid了
    2.服务器返回的数据中携带sessionid,浏览器发送请求时携带sessionid参数
    三、如何避免 sql 注入?
    sql注入是一种用户通过一些非法的字符或途径获取到数据库的数据,操作数据库对数据造成危害
    1.对进入数据库的字符进行转义处理
    2.严格控制对数据库的操作权限,给用户仅仅能工作的最低权限,从而最大限度的减少注入攻击对数据库的危害
    四、.session生命周期
    session用于存放用户与web服务器之前的对话,即服务器为客户端开辟的存储空间,因为客户端与服务端是无状态的机制,所以session可用于串联服务端与客户端 session存储在服务器内存中(高速存取),当web服务器启动,用户第一次访问web服务器时session创建,访问静态资源不会创建session,如果session在web服务器上长时间没有活动,session将会失效,默认时间二十分钟,或者调用session的invalivate方法
    另外HTTP协议是无状态的(就是客户端服务端交互完成后断开连接一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。),Session不能依据HTTP连接来判断是否为同一客户,因此服务器向客户端浏览器发送一个名为JSESSIONID的Cookie,它的值为该Session的id(也就是HttpSession.getId()的返回值)。Session依据该Cookie来识别是否为同一用户,如果客户端将cookie功能禁用,或者不支持cookie,java web提供一种解决方案,URL地址重写,基本原理为将用户的sessionID重写到URL地址中服务器解析重写后的URL获取sessionID

    网络

    一、localhost和127.0.0.1的区别
    localhost本地服务器的意思不会受到网卡协议的限制,不会解析成ip也不会占用网卡,网络资源
    127.0.0.1是经过网卡传输的,依赖网卡协议,受到网卡相关协议的限制,使用IP进行访问时,等于本机通过网络再去访问本机,会涉及到网络用户的权限
    二、http 响应码 301 和 302 代表的是什么?有什么区别?
    301,302都表示重定向成功,由浏览器输入的URL由地址A到地址B的变化
    301表示永久性转移,A地址的资源永久性移出不能访问了
    302表示暂时性转移,旧地址A资源仍然可以访问
    浏览器默认302跳转
    三、讲一下http协议,http协议请求消息头里面都有什么参数?cookie里面缓存的有账户信息吗?
    超文本传输协议,是一种通信协议,它允许将超文本传输语言从web服务器传送到客户端浏览器
    HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。
    四、http协议请求消息头里面都有什么参数?
    通用信息有三个字段: 请求url, 请求方法, 状态码, 远程地址。
    Accept : 指定客户端能够接收的内容类型,是json还是其他
    Accept-Language:浏览器用来展示的语言
    Accept-Encoding:浏览器客户端用来支持服务器返回内容的编码类型
    Connec-Length : 请求头的长度
    cookie : 浏览器端cookie。

    设计模式

    一、说一下你熟悉的设计模式?
    单例模式,简单工厂模式,抽象工厂模式,工厂方法模式

    二、简单工厂和抽象工厂有什么区别?
    工厂模式就是现在有不同类型的几种产品也就是对象,但是最后可以由一个统一的类型进行接收,为了方便解除代码之间的耦合,就委托工厂进行制造这些对象,就是工厂模式
    简单工厂是一个工厂对象可以生产很多种产品对象,但是不利于扩展,抽象工厂一个工厂生产一种产品对象,可以横向新增产品品种,只要实现产品对象接口,扩展性强,

    框架篇

    一、为什么要使用 spring?
    1.方便解耦,简化开发。因为通过Spring提供的IOC容器,我们可以将对象之间的依赖关系交由Spring容器管理
    2.AOP编程的支持,通过aop的功能,方便进行面向切面的编程,具体体现在事物方面
    3.方便程序的测试,Spring对Junit4支持,可以通过注解方便的测试Spring程序
    二、spring 有哪些主要模块?
    1.Spring Core框架的最基础部分,提供IOC容器,对bean进行管理
    2.spring context Context模块提供框架式的Bean访问方式,其他程序可以通过Context访问Spring的Bean资源,相当于资源注入。
    3.DAO模块 Spring提供对jdbc的支持,对jdbc进行封装,允许jdbc使用资源,并统一管理jdbc事物
    4.Spring ORM 对象关系映射,对常用orm框架的管理和支持,本身不对orm实现,仅对常见的orm框架进行封装,并对其进行管理
    5.Spring AOP 提供了符合AOP Alliance规范的面向方面的编程实现。
    6.MVC模块 提供了 Web 应用的 Model-View-Controller 全功能实现
    三、spring 常用的注入方式有哪些?
    1.构造器注入 例如:在userImpl实现类里面 添加userDao入参为构造函数
    2.setter方法注入 在userImpl实现类里面 添加userDao入参为setter方法注入
    3.基于注解的注入 @Component @Repository @Controller @Service
    @Resource注解默认以ByName去找属性名形同的bean的id,如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean
    @Autowired:默认是以byType的方式去匹配类型相同的bean,@Autowire默认按照对象类型来注入,如果想按照名称注入可以添加@Qualifier属性实现。
    四、spring 中的 bean 是线程安全的吗?
    Spring容器中的bean本身不具备线程安全的特性,默认 spring 容器中的 bean 是单例的。当单例中存在竞态条件,即有线程安全问题
    五、spring 支持几种 bean 的作用域?
    五种
    singleton:单例模式,在整个Spring IoC容器中,使用 singleton 定义的 bean 只有一个实例
    prototype:原型模式,每次通过容器的getbean方法获取 prototype 定义的 bean 时,都产生一个新的 bean 实例
    request,session,global session
    六、spring 自动装配 bean 有哪些方式?
    1.xml配置
    2.自动化装配,用到两个注解,组件扫描(ComponentScan):自动发现应用上下文中所创建的bean,自动装配(Autowired):自动满足bean之间的依赖
    七、.spring 事务实现方式有哪些?
    1.编程式事物管理,在代码中显示的调用提交,回滚
    2.声明式事物管理,配置类的形式,AOP的体现,在方法执行前后加入事物管理,建立在AOP功能之上,本质是通过AOP功能对,方法前后进行拦截,将事物处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务
    八、说一下 spring 的事务隔离?
    读未提交,读已提交,可重复读,串行化
    九、说一下 spring mvc 运行流程?
    客户端向服务器发送请求,请求被Spring MVC的前端控制器DispatcherServlet拦截,DispatcherServlet对请求的URL进行解析得到请求资源标识符,然后根据uri调用handlerMapping,返回handler给前端控制器,DispatcherServlet 根据获得的 Handler选择一个合适的 HandlerAdapter,提取数据开始执行controller,执行完成以后返回一个modelAndView,这是一个逻辑视图给前端控制器,前端控制器调用视图解析器…
    十、spring mvc 有哪些组件?
    1.前端控制器(DispatcherServlet)
    2.处理器映射器(HandlerMapping)
    是用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事
    3.处理器适配器(HandlerAdapter)
    处理handler
    4.视图解析器(ViewResolver)
    解析逻辑视图
    5.拦截器(HandlerInterceptor)
    十一、@RequestMapping 的作用是什么?
    他是一个用来处理请求映射的注解,用在类或者方法上。
    该注解的属性,value:指定请求的实际地址,method:指定请求的method类型
    十二、@Autowired 的作用是什么?
    @Autowired是一个注解,让bean完成自动装配的工作,默认按类型去装配,配合 @Qualifier 指定按照名称去装配 bean

    Spring boot

    一、什么是 spring boot?
    spring boot 是基于Spring开发的项目的起点,让你尽快的跑起来并且尽可能的减少配置文件,Spring boot只需要非常少的几个配置就可以搭建一个web项目
    二、为什么要用 spring boot?
    1.简化XML配置,所有配置都是配置类的形式
    2.快速整合第三方框架
    3.内置web服务器,Tomcat
    三、spring boot 核心配置文件是什么?
    Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。
    application配置文件主要是用于Spring boot项目的自动化配置
    四、spring boot 有哪些方式可以实现热部署?
    1.模板热部署,在Spring boot项目中,模板引擎页面是默认开启缓存的,如果修改页面内容,刷新页面是得不到修改后的页面的,可以在application.properties中关闭模版引擎的缓存。例如 Thymeleaf的配置 spring.thymeleaf.cache=false
    2.使用调试模式Debug实现热部署

    mybatis

    一、mybatis分页方式
    1.mapper文件分页
    2.sql分页
    3.RowBounds分页
    二、mybatis 逻辑分页和物理分页的区别是什么?
    物理分页就是数据库本身提供了分页方式,如MySQL的limit,oracle的rownum ,好处是效率高,不好的地方就是不同数据库有不同的搞法
    逻辑分页利用游标RowBounds对象分页,ResultSet结果集执行的内存分页,好处是所有数据库都统一,坏处就是效率低。
    三、mybatis 有哪些执行器(Executor)?
    SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
    ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象
    作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
    四. MyBatis实现一对一有几种方式?具体怎么操作的? MyBatis实现一对多有几种方式,怎么操作的?
    一对一:resultMap里面配置association节点配置一对一的类就可以完成
    一对多:通过在resultMap里面的collection节点配置一对多的类就可以完成
    现在基本上不用使用配置节点标签这种,直接嵌套查询,查询单表的数据取出外键id然后去查询关联表就行

    mysql

    一、数据库的三大范式是什么?
    1.第一范式强调的列是具有原子性,即列不能分为其他几列
    2.首先要满足第一范式,第二范式包含两部分内容,第一表必须有主键,二是非主键字段必须依赖于主键
    3.首先是第二范式,非主键必须直接依赖于主键,不能传递依赖
    二、如何获取当前数据库版本?
    Mysql命令行 select version();
    三、说一下 ACID 是什么?
    ACID 一般是指数据库事务的ACID
    四、char 和 varchar 的区别是什么?
    1.char的长度是不可变的,而varchar的长度是可变的
    2.char的存取效率要比varchar快的多,因为长度固定,所以方便程序的存储于查找,但是char也是要付出空间代价,因为长度固定所以单独的空格也会暂用空间
    3.char的存储方式是,对英文字符(ASCII)占用1个字节,对一个汉字占用两个字节;而varchar的存储方式是,对每个英文字符占用2个字节,汉字也占用2个字节,两者的存储数据都非unicode的字符数据
    五、怎么验证 mysql 的索引是否满足需求
    使用EXPLAIN 分析表
    EXPLAIN列的解释:
    1.table:显示这一行的数据是关于哪张表的
    2.key: 实际使用的索引,是主键索引还是其他索引
    3.Extra:关于MySQL如何解析查询的额外信息
    4.ref:显示索引的哪一列被使用了,如果可能的话,是一个常数
    索引类型 创建索引:Create Index Xxx
    普通索引,这个是最基本的索引
    唯一索引,与普通索引类似,但是不同的是唯一索引要求所有的类的值是唯一的,这一点和主键索引一样.但是他允许有空值,
    主键索引,不允许有空值
    六、事物的隔离级别
    读未提交,读已提交,可重复读,串行化
    七、说一下 mysql 常用的引擎?
    1.MyISAM存储引擎:不支持事务、也不支持外键,优势是访问速度快,对事务完整性没有 要求或者以select,insert为主的应用基本上可以用这个引擎来创建表,查询快
    2.InnoDB存储引擎支持事物但是对比MyISAM引擎,写的处理效率会差一些,并且会占用更多的磁盘空间以保留数据和索引
    InnoDB存储引擎的特点:支持自动增长列,支持外键约束
    八、说一下 mysql 的行锁和表锁?
    表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
    行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高
    九、如何做 MySQL 的性能优化?
    为搜索字段创建索引。
    避免使用 select *,列出需要查询的字段。
    选择正确的存储引擎。
    十、如何做 mysql 的性能优化?
    1.EXPLAIN 你的 SELECT 查询
    2.当只要一行数据时使用 LIMIT 1
    加上 LIMIT 1 可以增加性能。这样一样,MySQL数据库引擎会在找到一条数据后停止搜索,而不是继续往后查少下一条符合记录的数据
    3.为搜索字段建索引
    4.避免 SELECT *
    5.垂直分割
    6.选择正确的存储引擎
    Sql的基本架构
    客户端
    server端:连接器(连接验证,资源分配),分析器(分析要查的东西是否语法正确),优化器(通过索引选择合适的查找方式),执行器(检查用户权限将结果放入结果集)

    redis

    一、redis 是什么?都有哪些使用场景?
    redis可以说是一种非关系型数据库,与传统的数据库不同的是,redis的数据是存储在内存中的,所以读写速度非常快。
    内存存储和持久化:redis支持异步将内存中的数据持久化到硬盘上,同时不影响业务,rdb和aof持久化
    海量数据的读写
    二、redis都有哪些功能?
    1.基于本机内存的缓存功能:频繁访问数据库会造成数据库压力太大,大大损耗性能,使用redis将数据缓存起来,这样下次访问数据是直接冲缓存中读取,效率大大提高。
    2.持久化功能。rdb和aof
    3.主从复制,提供了监控,提醒以及自动的故障转移的功能,一个Redis服务器可以配备多个备份的服务器。Redis也是利用这两个功能来保证Redis的高可用的
    4.集群单台服务器的资源是有限的CPU和IO资源我们可以通过主从复制,主从复制只是做到相同的数据备份,并不能横向扩充内存,单台机器的内存能加大但是始终有上线的,所以我们可以横向扩展,最终每台服务器都负责其中的一部分,这些服务器构成一个整体
    三、redis 为什么是单线程的?
    因为redis是存内存操作的,内存的IO读写速度是相当快的,redis的核心就是如果数据都存在内存中,那么单线程操作就是最快的,因为多线程的本质就是cpu模拟多线程的本质出现的,多线程操作内存无疑会存在频繁的IO切换,上下文的切换,这样肯定是要浪费时间的,但是单线程就不一样了,他没有上下文的切换就是效率最高的,redis用单个CPU绑定一块内存的数据,这样一个内存的读写操作都是在单个CPU上完成的,所以单线程处理这件事是最高的。
    四、什么是缓存穿透?缓存雪崩?怎么解决?
    1.缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透
    解决方法:布隆过滤器,就是将所有可能存在的数据都hash到一个足够大的map中,一个一定不存在的数据就被拦截掉,这样就可以避免缓存穿透去访问服务器造成服务器的压力
    缓存空对象将null也缓存一个值,这样即使不存在的数据也会存在在缓存,避免访问数据库
    2.如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。
    解决方案:限流
    五、怎么保证缓存和数据库数据的一致性?
    选择淘汰缓存,数据可能为简单数据或者复杂数据,复杂数据进行缓存的更新操作,成本较高,因为如果一个数据一分钟内更新了二十次,一百次,那么你就要更新缓存一百次二十次,如果删除缓存的话只需要一次就可以,更新缓存并不是简单的更新,假如数据库更新了以后,更新缓存的时候是先查找两个表中的数据是否一致,然后不一致的进行更新。
    先更新数据库在淘汰缓存;因为如果先更新数据库在淘汰缓存的话,若缓存淘汰失败那么后面的请求就会得到脏数据,因为缓存和数据库不一致了,先淘汰缓存在更新数据库,就算数据库更新失败,那也就只会产生一次缓存的失败,相比而言后面的对业务影响较小一点
    六、redis持久化的两种方式?
    1.rdb持久化是指在指定时间间隔内生成数据集的时间点快照,即将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化
    redis调用fock()方法,同时拥有父进程和子进,子进程将数据集写到一个临时的RDB文件中,当子进程对新的rdb文件写入完成时,用新的rdb文件替代旧的rdb文件,并且删除掉旧的文件
    持久化方式配置:
    save 900 1 #900秒时间,至少有一条数据更新,则保存到数据文件中
    save 300 10 #300秒时间,至少有10条数据更新,则保存到数据文件中
    save 60 10000 #60秒时间,至少有10000条数据更新,则保存到数据文件中
    rdb持久化优势:对文件备份比较完美,你可以每小时更新一次近24小时数据或者每天更新一次30天数据
    对于灾难恢复比较便捷,可以简单的将一个文件移到其他存储介质上
    劣势:因为rdb是通过fork子进程来进行持久化的如果数据较大可能会导致数据停滞几秒钟
    如果持久化之前出现宕机,那么此前没有来得及写入磁盘的数据将丢失
    2.aof持久化
    aof持久化是一种增量的持久化方式,可以在配置文件中配置,然后以追加日志的形式记录在redis的一个.aof文件中
    配置:
    appendfsync always always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
    appendfsync everysec everysec:表示每秒同步一次(折衷,默认值)
    appendfsync no no:表示等操作系统进行数据缓存同步到磁盘(快)
    优势:Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的
    由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容
    劣势:丢失一秒钟的数据
    rdb在恢复大数据的时候比aof要快
    七、redis 如何做内存优化?
    1.缩减键值对象,key长度,如在设计键时,在完整的业务情况下,键值越短越好
    2.控制key的数量
    3.字符串优化
    八。redis 淘汰策略有哪些?
    1.设置最大内存参数。在redis.windows.config文件中配置可以设置多少个字节,设置我们的最大内存。默认是关闭的
    2.内存淘汰策略。redis提供了八中内存置换策略在config配置文件中
    (1)volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。

    (2)volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。

    (3)volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。

    (4)volatile-lfu:从已设置过期时间的数据集挑选使用频率最低的数据淘汰。
    淘汰机制的实现:
    1.删除失效主键,既然是淘汰那就需要把数据删除,然后保存新的,消极方法就是在主键访问时发现他已经失效那就删除他
    积极方法:周期性的探测发现失效就删除
    主动删除:当内存超过maxMemory限定时,触发主动清理策略,该策略由启动参数配置决定

    zookeeper

    一、zookeeper 是什么?
    1.zookeeper是一个分布式的协调服务,分布式应用程序可以基于zookeeper实现数据的发布与订阅,分布式协调通知,负载均衡与集群管理
    二、.zookeeper 都有哪些功能?
    1.统一命名服务
    在分布式系统中,客户端能够根据指定名字来获取资源或者服务的地址,提供者等信息
    2.集群管理 监控节点存活状态,运行请求
    zookeeper能够很容易的实现集群管理的功能,如果有很多server组成一个服务集群,总要有一个leader知道当前权重每台机器的服务状态,一旦有机器不能服务,集群中的其他机器必须知道,同样增加集群的服务能力是增加一台或者多态server必须leader知道
    他们的实现方式都是在服务器上创建几个集群节点
    3.配置管理
    程序总是需要配置的,如果应用程序分散部署在多台机器上,那么要改变配置的话就变得非常困难,现在把这些配置都放在zookeeper的一个节点上,然后相关应用程序对这个节点进行监听,一旦配置发生变化,每个应用程序都会收到通知,然后zookeeper将新的配置信息应用到应用程序就行
    三、.zookeeper 有几种部署模式?
    单机,集群,伪集群
    伪集群的部署方式就是在一台PC中启动多个zookeeper实例
    集群部署就是多台服务器上面各单独部署一个zookeeper服务组成一个集群
    进入bin目录下9
    启动服务:./zkServer.sh start
    检查状态: ./zkServer.sh status
    Mode:standalone 表明当前是单机模式,也表明部署成功
    单机部署方式:
    1.服务器上创建工作目录zookeeper,
    2.zookeeper下面创建data用于存放节点ID,log下存放相关日志文件
    3.解压zookeeper到工作目录下
    4.创建zoo.cfg到zookeeper目录下的conf子目录
    在zoo.cfg中配置相关文件的参数
    tickTime=2000 zookeeper中使用的基本时间单位, 毫秒值
    initLimit=10 Follower在启动过程中,会从Leader同步所有最新数据,然后确定自己能够对外服务的起始状态。Leader允许Follower在initLimit时间内完成这个工作
    syncLimit=5
    dataDir=/home/midware/zookeeper/data 数据目录
    dataLogDir=/usr/lib/zookeeper/logs log目录
    clientPort=2181 监听client连接的端口号
    伪集群部署与集群部署的区别就是在zoo.cfg配置文件中最后面新增下面内容,增加接个server
    server.1=127.0.0.1:2888:3888每个
    server.2=127.0.0.1:2889:3889
    server.3=127.0.0.1:2890:3890
    server.服务号=服务地址:服务端口:通信端口
    1.本地创建三个虚拟节点,即三个文件夹存放三个不同节点的配置,例如server1/zookeeper,每一个节点下创建data,log文件夹,data下用于存放每一个的节点id,在每个zoo.cfg节点中都增加,server1,server2,server3配置,但是clientPort设置的端口号每个节点不同
    2.配置不同节点上的myid,就是在data文件下分别创建存放ID的myid文件,每个myid文件只写上该节点的id
    如server1/data/myid 里写1 ,server2/data/myid 里写2
    集群部署:跟伪集群部署一样,只不过配置需要变化一下,IP需要变化,端口可以每台机器使用同一个
    四、zookeeper 怎么保证主从节点的状态同步?
    zookeeper的核心是原子广播,这个机制保证了各个server之间的同步,实现这个机制的协议叫做zab协议
    zab协议有两种模式分别是恢复模式或者选主模式和广播模式,当服务启动或者领导者崩溃以后,zab就进入了选主模式,当领导者被选举出来,保证各server和leader之间同步以后,恢复模式就结束了
    五、集群中为什么要有主节点?
    在分布式环境中,有些业务只需要在集群中的某一台机器中执行,而其他机器可以共享这个结果,这样可以大大减少重复计算,提高性能,所以需要主节点。
    六、Zookeeper 和 Dubbo 的关系?
    zookeeper作为dubbo的注册中心,也就是将zookeeper的特性引入进来,首先是负载均衡,单注册中心的承载能力是非常有限的,在流量达到一定程度的时候需要分流,分布在多个节点减轻单一系统的压力,所以负载均衡就是这个时候提现出来的,zookeeper配合web应用程序很容易达到负载均衡
    二就是资源同步,单单是负载均衡还不够,各个节点之间还需要资源同步,这样zookeeper可以做到,此外zookeeper还管理者服务的注册于发布
    七、说一下 zookeeper 的通知机制?
    客户端注册监听他关心的目录节点,当目录节点发生变化(数据改变,被删除,子目录节点增加删除)时,zookeeper会通知客户端

    展开全文
  • 1. 涉及平台 平台管理、商家端(PC端、手机端)、买家平台(H5/公众号、小...分布式、微服务、云架构、模块化、原子化、持续集成、集群部署、前后端分离、支持阿里Docker 5. 开发模式 前后端分离、微服务开发 6. 社
  • Java

    2021-03-01 09:58:51
    文章目录目录JVMJava内存模型★Java内存区域类加载★类加载过程类的初始化垃圾回收★垃圾回收器★如何辨别一个对象是存是亡★垃圾回收的方式★堆的结构划分引用Stop-the-worldJVM是如何调用方法的静态绑定和动态绑定...
  • 一、系统整体模块设计图 简单介绍一下架构设计,随着大数据的广泛应用,在现如今的系统之中,尤其是作为流媒体视频播放网站,统计网站用户的行为,分析用户的行为,以及对用户行为的采集无疑是很重要的一个系统...
  • 补充说明:Java开源生鲜电商平台-账单模块的设计与架构,即用户的账单形成过程。   由于系统存在一个押账功能的需求,(何为押账,就是形成公司的资金池,类似摩拜单车,ofo单车等等)。目前B2B平台也是采用押账...
  • } } public static class ModuleA { public void execute() { System.out.println("子系统1的模块A的功能"); } } public static class ModuleB { public void execute() { System.out.println("子系统1的模块B的...
  • 我爱Java

    2021-02-25 08:41:02
    1、 Java 基础知识 1.1 重载和重写的区别 重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。重写: 发生在父子类中,方法名、参数...
  • 在小程序模板消息下线的日子记一下订阅消息的使用 博主最近做的小程序涉及到余额...在小程序管理后台订阅消息功能模块中添加如下模板 提现失败模板的详情 # 订阅消息发送云函数添加 # 参照前文mpvue框架下使用小程序...
  • 第一阶段:Java基础 掌握基本语法、面向对象、常用类、正则、集合、Io流、多线程、Nio、网络编程、JDK新特性、函数式编程。 可胜任工作(工作方向):数据采集与嵌入式交互,负责公司业务平台开发和维护工作,根据...
  • Java基础知识

    2017-03-15 16:27:01
    Java面向对象的特点:封装,继承,多态 封装特点: 1.变量的属性私有化,提高变量的安全性; 2.使方法或函数模块化,便于外部调用,使调用者不用知道实现方法,只用知道请求结果;继承
  • } if(res.equals("SUCCESS")){ //能到这里说明,已经体现成功了,接下来就是每个人的逻辑模块 } return res; } } 然后就成了 温馨提示:在工具类中,我把所有的错误报告全部打印出来了,大家可以根据错误报告来查看...
  • Java面经

    2019-11-20 15:55:18
    提现了实物的传递性,继承关系达到复用的目的。  区别:单继承,多实现。在接口中只能定义全局常量(static final),和无实现的方法。   3.java中为什么要单继承,多实现 a. 若为多继承,那么当多个...
  • Java - 支付宝支付

    万次阅读 多人点赞 2020-06-21 09:35:20
    商家APP调用支付宝提供的 SDK,SDK 再调用支付宝APP内的支付模块。如果用户已安装支付宝 APP,商家 APP 会跳转到支付宝中完成支付,支付完后跳回到商家APP内,最后展示支付结果。如果用户没有安装支付宝 APP,商家 ...
  • java笔记

    2017-04-18 14:30:49
    CallableStatement用来执行存储过程,存储过程是由数据库存储和提供的,存储过程可以接受输入参数,也可以有返回结果,非常鼓励使用存储过程,因为它提供了安全和模块化,准备一个CallableStatement的方法是: ...
  • Java程序员面试笔试宝典-Java基础知识(一)

    千次阅读 多人点赞 2019-05-21 15:01:42
    1.1 Java语言有哪些优点? 1.2 Java与C++有什么异同? 1.3 为什么需要public static void main(String[] args)这个方法? 1.4 如何实现在main方法执行前输出“Hello World”? 1.5 Java程序初始化的顺序是怎样的...
  • 中级Java知识点

    2021-02-07 15:30:29
    中级Java知识点 Java 基本类型哪些,所占字节 byte :1 个字节 short :2 个字节 char :2 个字节 int :4 个字节 long :8 个字节 float :4 个字节 double :8 个字节 java 集合以及底层原理 Java 集合框架的根...
  • java学习笔记

    2020-03-16 20:19:37
    Java中数据类型分为两大类,基本类型和对象类型。相应的,变量也有两种类型:基本类型和引用类型。 基本类型的变量保存原始值,即它代表的值就是数值本身;而引用类型的变量保存引用值,"引用值"指向内存空间的地址...
  • java面试突击

    千次阅读 2020-02-23 11:51:27
    举个 简单的例子:一般情况下你的简历上注明你会的东西才会被问到(Java、数据结构、网络、算法这些基础是每个人必 问的),比如写了你会 redis,那面试官就很大概率会问你 redis 的一些问题。比如:redis的常见数据...
  • 还有 Java 8 中新特性的介绍,比如时间和日期模块,让你使用更简洁和优化的方式写出更完美的代码;还有我们日常用的很多包装类不为人知的有趣现象和知识盲点介绍;还有数组以及算法的介绍,虽然基础但容易被面试者...
  • 不一致,还要提供提现的金额(闲钱币,换算:100闲钱币 = 1RMB),然后调用 /money 怎么跟充值一样的地址呢?因为可以通过传递的 json 对象的 money 字段的 正负判断是充值还是提现 从下图中还可以看到创建问卷时...
  • java最最最基础

    2019-05-30 16:44:31
    1.1 Java语言有哪些优点? 1.2 Java与C++有什么异同? 1.3 为什么需要public static void main(String[] args)这个方法? 1.4 如何实现在main方法执行前输出“Hello World”? 1.5 Java程序初始化的顺序是怎样的? ...
  • Java面向对象

    2021-07-24 09:29:01
    堆:存放的是类的对象Java是一个纯面向对象语言, 限制了对象的创建方式:所有类的对象都是通过new关键字创建, new关键字, 是指告诉JVM , 需要明确的去创建一个新的对象 , 去开辟一块新的堆内存空间 1. 类与对象 类...
  • 前言 Java一度被称为是应用最广泛的编程语言。尤其在Java web方面,Java作为后台服务器开发语言,尤其是它跨平台一次编译随处运行的特性,更是受到不少企业和工程师们的爱戴。作为应用开发的主要语言,Java也需要...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,668
精华内容 667
关键字:

java提现模块

java 订阅