RealNameAuthorizeServiceImpl.java 14.4 KB
package com.yohoufo.user.service.impl;

import com.yoho.core.config.ConfigReader;
import com.yoho.error.exception.ServiceException;
import com.yoho.tools.common.beans.ApiResponse;
import com.yohoufo.dal.user.IUserAuthorizeHistoryDao;
import com.yohoufo.dal.user.IUserAuthorizeInfoDao;
import com.yohoufo.dal.user.model.UserAuthorizeHistory;
import com.yohoufo.dal.user.model.UserAuthorizeInfo;
import com.yohoufo.user.cache.CacheService;
import com.yohoufo.user.common.EnumBankBackCode;
import com.yohoufo.user.requestVO.RealNameAuthorizeReqVO;
import com.yohoufo.user.responseVO.AuthorizeResultRespVO;
import com.yohoufo.user.service.IRealNameAuthorizeService;
import com.yohoufo.user.service.risk.GraphVerifyService;
import lombok.Data;
import net.sf.json.JSONObject;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.digest.HmacUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 用户身份实名认证
 */
@Service("realNameAuthorizeServiceImpl")
public class RealNameAuthorizeServiceImpl implements IRealNameAuthorizeService {

    private Logger logger = LoggerFactory.getLogger(RealNameAuthorizeServiceImpl.class);

    private final static DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    //请求实名认证银联接口url 测试环境
    private final  String requestUrl="http://58.247.0.18:29015/v1/datacenter/smartverification/bankcard/verify";
    //请求实名认证银联接口url 生产环境
    //private final  String requestUrl="https://api-mop.chinaums.com/v1/datacenter/smartverification/bankcard/verify";

    ExecutorService executeService = Executors.newFixedThreadPool(10);

    @Autowired
    private IUserAuthorizeInfoDao userAuthorizeInfoDao;

    @Autowired
    private IUserAuthorizeHistoryDao userAuthorizeHistoryDao;

    @Resource(name="authorizeBankRestTemplate")
    private RestTemplate restTemplate;

    @Resource(name = "core-config-reader")
    private ConfigReader configReader;

    @Autowired
    private CacheService cacheService;

    @Autowired
    private GraphVerifyService graphVerifyService;


    public UserAuthorizeInfo getValidAuthorizeInfo(int uid){
        // 从redis缓存中获取
        UserAuthorizeInfo authorizeInfo = cacheService.getUserAuthorizeInfo(uid);
        if(null != authorizeInfo){
            return authorizeInfo;
        }

        //如果不存在,则从数据库获取
        authorizeInfo= userAuthorizeInfoDao.selectValidAuthorizeInfoByUid(uid);
        if(authorizeInfo!=null){
            //保存到redis
            try{
                cacheService.setUserAuthorizeInfo( authorizeInfo);
            }catch(Exception e){
                logger.warn("set valid authorize info to redis error. uid={}", uid);
            }
        }
        return authorizeInfo;
    }

    /**
     * 实名身份认证
     */
    public JSONObject authorizeRealNameWithBank(RealNameAuthorizeReqVO reqVO)  throws ServiceException {
        int uid=reqVO.getUid();
        String cardNo=reqVO.getCardNo();
        String certNo=reqVO.getCertNo();
        String name=reqVO.getName();

        //组织报文
        JSONObject msgContentParams=constructMsg(cardNo,certNo,name);
        //根据报文体,生成HTTP报文头的认证内容 (open-body-sig 方式)
        String authorizationContentByOpenBodySig=generateAuthorizationByOpenBodySig(msgContentParams);
        //请求返回
        PostBankResult responseResult = postRequest(msgContentParams,authorizationContentByOpenBodySig);

        //返回结果处理:请求成功入认证信息库,请求失败记录的redis,后续超过一定次数则开启验证码
        long ts=getLocalDateTime().toEpochSecond(ZoneOffset.of("+8"));
        if(responseResult.isSucFlag()){
            UserAuthorizeInfo userAuthorizeInfo =new UserAuthorizeInfo();
            userAuthorizeInfo.setUid(uid);
            userAuthorizeInfo.setValidStatus(1);
            userAuthorizeInfo.setCardNo(cardNo);
            userAuthorizeInfo.setCertNo(certNo);
            userAuthorizeInfo.setCertName(name);
            userAuthorizeInfo.setCreateTime(ts);
            userAuthorizeInfo.setUpdateTime(ts);
            userAuthorizeInfoDao.insert(userAuthorizeInfo);
        }else{
            //访问失败记录redis,保存半小时,失败超过一定次数打开验证码开关
            recordFailTimesAndOpenGraphVerify(reqVO);
        }

        //访问银联接口记录日志
        recordHistory(uid,cardNo,certNo,name,responseResult,ts);

        JSONObject jo=new JSONObject();
        jo.put("sucFlag",responseResult.isSucFlag());
        jo.put("errInfo",responseResult.getErrInfo());

        return jo;
    }

    /**
     * 把失败写入redis,过期时间为30分钟
     * 超过一定次数需要通知uic开启图像验证码
     */
    public void recordFailTimesAndOpenGraphVerify(RealNameAuthorizeReqVO reqVO){
        Long failedNum=cacheService.incrementAuthorizeFailNum(reqVO.getUid());
        if(failedNum.intValue()>=getAuthorizeGraphVerifyLimit()){
            graphVerifyService.triggerUfoGraphVerifySwitch(reqVO.getApp_type(),reqVO.getClient_type(), reqVO.getApp_version(), reqVO.getFromPage(),
                    reqVO.getUdid(),  reqVO.getDegrees());
        }
    }


    /**
     * 获取图像验证码开启验证次数
     */
    public int getAuthorizeGraphVerifyLimit() {
        int time = configReader.getInt("ufo.passport.authorize.graph.verify.count", 3);
        logger.info("RealNameAuthorizeServiceImpl getGraphVerifyLimit result is {}", time);
        return time;
    }

    /**
     * 组织银联实名认证的请求报文
     */
    private JSONObject constructMsg(String cardNo, String certNo, String name){
        JSONObject msgContentParams = new JSONObject();
        msgContentParams.put("cardNo",cardNo);//卡号
        msgContentParams.put("certNo",certNo);//证件号
        msgContentParams.put("certType","01");  //证件类型:01居民身份证,只支持身份证
        msgContentParams.put("name",name);//证件姓名
        msgContentParams.put("personalMandate","1");  //个人是否授权,1表示授权,0表示未授权,只能是授权用户
        return msgContentParams;
    }

    /**
     * 请求银联接口,获取返回信息
     * 捕获所有异常
     */
    private PostBankResult postRequest(JSONObject msgContentParams,String authorizationContentByOpenBodySig){
        PostBankResult result=new PostBankResult();
        try{
            //组成post的请求参数
            JSONObject dataParams=new JSONObject();
            dataParams.put("data",msgContentParams);

            //header
            MultiValueMap<String, String> headers = new LinkedMultiValueMap();
            headers.set("Content-Type", "application/json; charset=UTF-8");
            headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
            headers.set("Authorization",authorizationContentByOpenBodySig);
            //body
            HttpEntity<JSONObject> bodyEntity = new HttpEntity<>(dataParams, headers);
            logger.info("RealNameAuthorizeServiceImpl authorizeRealNameWithBank request message {} ,headers {} ,body {}",msgContentParams,headers,bodyEntity);
            //post
            ResponseEntity<JSONObject> responseEntity = restTemplate.postForEntity(requestUrl, bodyEntity, JSONObject.class);
            logger.info("RealNameAuthorizeServiceImpl authorizeRealNameWithBank response entity {} ",responseEntity);
            //处理结果
            JSONObject jo=responseEntity.getBody();
            if(jo==null){
                result.setSucFlag(false);
                result.setBackMsg("请求银联返回内容为空");
                return result;
            }
            result.setBackMsg(jo.toString());
            result.setStatusCode(responseEntity.getStatusCodeValue());
            //实名认证成功的应答码:"0000"
            if(responseEntity.getStatusCodeValue()==200&&jo!=null&&"0000".equals(jo.getString("errCode"))){
                result.setSucFlag(true);
                result.setErrCode("0000");
                result.setErrInfo("请求银联实名认证成功");
            }else{
                result.setSucFlag(false);
                result.setErrCode(jo==null?"":jo.getString("errCode"));
                result.setErrInfo(jo==null?"":jo.getString("errInfo"));
            }
        }catch (Exception e){
            logger.error("RealNameAuthorizeServiceImpl authorizeRealNameWithBank response msgContentParams {} ,error",msgContentParams,e);

            //记录错误码
            result.setSucFlag(false);
            if(e instanceof HttpClientErrorException){
                HttpClientErrorException errorException=(HttpClientErrorException)e;
                result.setStatusCode(errorException.getStatusCode().value());
                result.setBackMsg(errorException.getResponseBodyAsString());
                try{
                    JSONObject jo=JSONObject.fromObject(result.getBackMsg());
                    result.setErrCode(jo==null?"":jo.getString("errCode"));
                    result.setErrInfo(jo==null?"":jo.getString("errInfo"));
                }catch (Exception jError){
                    logger.error("change to json error {} ",result.getBackMsg(),jError);
                }
            }else if(e instanceof HttpServerErrorException){
                HttpServerErrorException errorException=(HttpServerErrorException)e;
                result.setStatusCode(errorException.getStatusCode().value());
                result.setBackMsg(errorException.getResponseBodyAsString());
                try{
                    JSONObject jo=JSONObject.fromObject(errorException.getResponseBodyAsString());
                    result.setErrCode(jo==null?"":jo.getString("errCode"));
                    result.setErrInfo(jo==null?"":jo.getString("errInfo"));
                }catch (Exception jError){
                    logger.error("change to json error {} ",result.getBackMsg(),jError);
                }
            }else if(e instanceof RestClientException){
                RestClientException errorException = (RestClientException)e;
                result.setBackMsg(errorException.getMessage());
            }else{
                result.setBackMsg("error happen unknown reason");
            }
        }

        return result;
    }

    @Data
    private static class PostBankResult {
        private boolean sucFlag;
        private int statusCode;
        private String errCode;
        private String errInfo;
        private String backMsg;
    }

    /**
     * 无论成功还是失败,都把访问记录日志表
     */
    private void recordHistory(int uid,String cardNo,String certNo,String name,PostBankResult responseResult ,long ts){
        UserAuthorizeHistory history=new UserAuthorizeHistory();
        history.setUid(uid);
        history.setCardNo(cardNo);
        history.setCertNo(certNo);
        history.setCertName(name);
        history.setResponseCode(responseResult.getStatusCode());
        history.setResponseContent(responseResult.getBackMsg());
        history.setBackErrorCode(responseResult.getErrCode());
        history.setBackErrorInfo(responseResult.getErrInfo());
        history.setFirstErrorCode(EnumBankBackCode.getFirstCodeBySecondCode(responseResult.getErrCode()));
        history.setCreateTime(ts);
        history.setUpdateTime(ts);
       //最后记录日志 ,异步
        executeService.execute(()->{
            try{
                userAuthorizeHistoryDao.insert(history);
            }catch (Exception e){
                logger.error("RealNameAuthorizeServiceImpl authorizeRealNameWithBank userAuthorizeHistoryDao insert history {} error {}",history,e);
            }
        });
    }


    /**
     * 生成授权内容
     * 内容生成算法请参考接口文档
     * @param msgContentParams 请求报文
     */
    public String generateAuthorizationByOpenBodySig(JSONObject msgContentParams){
        //产品ID,由商务提供
        String appId="2c909a515499b76b01549a01d2730000";
        String appKey="keyqinchao";
        //当前时间戳 ,要求格式 yyyyMMddHHmmss
        String timestamp=getLocalDateTime().format(df);
        //随机的正整数,不超过128位
        int nonce = (new Random()).nextInt(100000);

        //timestamp="20180911154812";
        //nonce=26713;
        /******************** 根据报文体,生成签名 **********/
        //1.报文内容转字节数组 ,sha256加密转16进制后再转小写
        String sha256_hex_lower= DigestUtils.sha256Hex(msgContentParams.toString()).toLowerCase();
        //2.生成代签名字符串
        String prepare_sig_str=appId+timestamp+nonce+sha256_hex_lower;
        //3.HmacSHA256签名 ,并base64编码
        String signature= Base64.encodeBase64String(HmacUtils.hmacSha256(appKey, prepare_sig_str));
        /****************************************************/

        //得到授权内容
        StringBuilder sb=new StringBuilder("OPEN-BODY-SIG ");
        sb.append("AppId=\"").append(appId).append("\", ");
        sb.append("Timestamp=\"").append(timestamp).append("\", ");
        sb.append("Nonce=\"").append(nonce).append("\", ");
        sb.append("Signature=\"").append(signature).append("\"");
        return sb.toString();
    }

    private LocalDateTime getLocalDateTime(){
        LocalDateTime now=LocalDateTime.now();
        return now;
    }
}