Authored by qinchao

Merge branch 'dev_qc691_上传身份证' into test6.9.1

... ... @@ -20,6 +20,9 @@ public enum CacheKeyEnum {
ZHI_MA_CERT_KEY("ufo:user:zhiMaCert:","芝麻认证信息", 2, TimeUnit.HOURS),
//身份证上传验证,一天只允许一定的次数
PHOTO_CHECK_COUNT_KEY("ufo:user:photoCheckCount:","芝麻认证信息", 24, TimeUnit.HOURS),
STORED_SELLER_KEY("ufo:user:storedSeller:","商家入驻信息", 2, TimeUnit.HOURS),
USER_PRODUCT_FAVORITE_ZSET_KEY("ufo:user:product:favorite:","用户商品收藏集合", 180, TimeUnit.DAYS),
... ...
... ... @@ -15,6 +15,8 @@ public interface IZhiMaCertDao {
int updateBizNoByByPrimaryKey(@Param("id")int id, @Param("bizNo")String bizNo);
int updatePhotoToValidByPK(@Param("id")int id, @Param("imageUrl")String imageUrl);
//获取生效的认证信息
ZhiMaCert selectByPrimaryKey(int id);
... ...
... ... @@ -14,6 +14,11 @@ public class ZhiMaCert {
//生效状态:1 生效 ; 0 不生效
private Integer validStatus;
//身份证图片已传:1 生效 ; 0 不生效
private Integer validPhoto;
private String imageUrl;
//证件姓名
private String certName;
... ...
... ... @@ -10,10 +10,12 @@
<result column="biz_no" property="bizNo" jdbcType="VARCHAR" />
<result column="create_time" property="createTime" jdbcType="INTEGER" />
<result column="update_time" property="updateTime" jdbcType="INTEGER" />
<result column="valid_photo" property="validPhoto" jdbcType="INTEGER" />
<result column="image_url" property="imageUrl" jdbcType="INTEGER" />
</resultMap>
<sql id="Base_Column_List" >
id, uid, valid_status,cert_no, cert_name, biz_no,create_time, update_time
id, uid, valid_status,cert_no, cert_name, biz_no,create_time, update_time,valid_photo,image_url
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
... ... @@ -57,10 +59,10 @@
<selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id">
SELECT LAST_INSERT_ID()
</selectKey>
insert into zhima_cert (uid, valid_status, cert_no, cert_name, biz_no,create_time, update_time)
insert into zhima_cert (uid, valid_status, cert_no, cert_name, biz_no,create_time, update_time,valid_photo,image_url)
values (#{uid},#{validStatus},
#{certNo},#{certName},#{bizNo},
#{createTime},#{updateTime})
#{createTime},#{updateTime},#{validPhoto},#{imageUrl})
</insert>
<update id="updateValidStatusByPrimaryKey" parameterType="java.lang.Integer">
... ... @@ -74,6 +76,13 @@
set biz_no = #{bizNo}
where id = #{id}
</update>
<update id="updatePhotoToValidByPK">
update zhima_cert
set image_url = #{imageUrl} , valid_photo = 1
where id = #{id}
</update>
<delete id="deleteByUid" parameterType="java.lang.Integer">
update zhima_cert
set valid_status = 0
... ...
... ... @@ -15,6 +15,11 @@
<dependencies>
<dependency>
<groupId>com.yoho.service.model</groupId>
<artifactId>reviewed-service-model</artifactId>
</dependency>
<dependency>
<groupId>com.yohoufo.fore</groupId>
<artifactId>yohoufo-fore-dal</artifactId>
</dependency>
... ...
... ... @@ -51,6 +51,10 @@ public class CacheService {
return RedisKeyBuilder.newInstance().appendFixed(CacheKeyEnum.ZHI_MA_CERT_KEY.getCacheKey()).appendVar(uid);
}
private static RedisKeyBuilder getPhotoCheckRedisKeyBuilder(String dayStr,Integer uid){
return RedisKeyBuilder.newInstance().appendFixed(CacheKeyEnum.PHOTO_CHECK_COUNT_KEY.getCacheKey()).appendVarWithMH(dayStr).appendVar(uid);
}
/**************************************************************************
* 商品收藏相关
*************************************************************************/
... ... @@ -198,6 +202,17 @@ public class CacheService {
yhRedisTemplate.delete(getZhiMaCertRedisKeyBuilder(uid));
}
public Long getPhotoCheckCount(String dayStr,Integer uid){
RedisKeyBuilder cacheKey = getPhotoCheckRedisKeyBuilder(dayStr,uid);
Long count = get(cacheKey,Long.class);
return count;
}
public Long incrementPhotoCheckCount(String dayStr,Integer uid) {
RedisKeyBuilder cacheKey = getPhotoCheckRedisKeyBuilder(dayStr,uid);
return this.incrementWithExpire(cacheKey, 1L , CacheKeyEnum.PHOTO_CHECK_COUNT_KEY.getDefaultExpireTime(), CacheKeyEnum.PHOTO_CHECK_COUNT_KEY.getTimeUnit()) ;
}
/**************************************************************************
* 通用
... ...
... ... @@ -17,6 +17,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 用户身份通过银行卡实名认证
... ... @@ -122,6 +125,83 @@ public class RealNameAuthorizeController {
}
/**
* 6.9.1版本 ,上传身份证图片并校验
* 由于调用百度云ocr验证接口,需求file格式
* 因此要求正反面一起上传,两张
* bucket :yohocard
*/
@RequestMapping(params = "method=ufo.user.zhiMaCertUploadPhotoAndCheck")
public ApiResponse zhiMaCertUploadPhotoAndCheck(@RequestParam("files") MultipartFile[] files,
@RequestParam(value = "bucket") String bucket,
@RequestParam(value = "limitType", required = false) Integer limitType,
RealNameAuthorizeReqVO reqVO) throws Exception {
logger.info("zhiMaCertUploadPhotoAndCheck , reqVO [{}], files by files [{}] , and bucket [{}] , limitType [{}]",reqVO , files, bucket, limitType);
try {
//(1) 优先校验请求的参数
if (reqVO == null || reqVO.getUid()<=0){
throw new GatewayException(400, "uid不能为空!");
}
if ( StringUtils.isEmpty(reqVO.getCertNo())|| StringUtils.isEmpty(reqVO.getCertName())){
throw new GatewayException(400, "身份证号、姓名不能为空!");
}
//身份证格式校验IDCardUtils
if(!IDCardUtils.validate(reqVO.getCertNo())){
throw new GatewayException(400, "身份证号格式不正确!");
}
List<String> imgUrls = realNameAuthorizeService.zhiMaCertUploadPhotoAndCheck(files, bucket, limitType,reqVO);
return new ApiResponse(200, "上传成功", imgUrls);
} catch (Exception e) {
e.printStackTrace();
logger.info("upload files fail");
return new ApiResponse(400, "上传失败" + e.getMessage(), null);
}
}
/**
* 6.9.1版本 ,芝麻认证要求上传图片
* 原接口保持不变,芝麻认证用新的接口
* 芝麻认证(初始化并购置返回url)
*/
@RequestMapping(params = "method=ufo.user.zhiMaCertWithPhotoInit")
public ApiResponse zhiMaCertWithPhotoInit(RealNameAuthorizeReqVO reqVO) throws GatewayException {
logger.info("enter realNameAuthorize.zhiMaCertWithPhotoInit param reqVO is {}", reqVO);
//(1) 优先校验请求的参数
if (reqVO == null || reqVO.getUid()<=0){
throw new GatewayException(400, "uid不能为空!");
}
if ( StringUtils.isEmpty(reqVO.getCertNo())|| StringUtils.isEmpty(reqVO.getCertName())){
throw new GatewayException(400, "身份证号、姓名不能为空!");
}
//身份证格式校验IDCardUtils
if(!IDCardUtils.validate(reqVO.getCertNo())){
throw new GatewayException(400, "身份证号格式不正确!");
}
//身份证图片,必须
if(StringUtils.isBlank(reqVO.getFrontImageUrl())||StringUtils.isBlank(reqVO.getBackImageUrl())){
throw new GatewayException(400, "身份证正反面图片不能为空!");
}
//调用芝麻,得到回调url
AuthorizeResultRespVO resultVo=realNameAuthorizeService.zhiMaCertWithPhotoInit(reqVO);
logger.info("realNameAuthorize.zhiMaCertInit result vo {} ",resultVo);
ApiResponse apiResponse=new ApiResponse();
if(resultVo==null){
apiResponse.setCode(201);
apiResponse.setMessage("异常结果为空");
}
apiResponse.setData(resultVo);
return apiResponse;
}
/**
* 芝麻认证(初始化并购置返回url)
*/
@RequestMapping(params = "method=ufo.user.zhiMaCertInit")
... ...
... ... @@ -31,5 +31,11 @@ public class RealNameAuthorizeReqVO extends BaseBO {
private String auth_code;
//身份证图片:正面
private String frontImageUrl;
//身份证图片:反面
private String backImageUrl;
}
... ...
... ... @@ -8,6 +8,9 @@ import com.yohoufo.common.exception.GatewayException;
import com.yohoufo.dal.user.model.UserAuthorizeInfo;
import com.yohoufo.dal.user.model.ZhiMaCert;
import com.yohoufo.user.requestVO.RealNameAuthorizeReqVO;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 用户身份实名认证
... ... @@ -24,6 +27,10 @@ public interface IRealNameAuthorizeService {
ZhiMaCert getValidZhiMaCert(int uid);
List<String> zhiMaCertUploadPhotoAndCheck(MultipartFile[] multipartFile, String bucket, Integer limitType, RealNameAuthorizeReqVO reqVO) throws Exception;
AuthorizeResultRespVO zhiMaCertWithPhotoInit(RealNameAuthorizeReqVO reqVO);
AuthorizeResultRespVO zhiMaCertInit(RealNameAuthorizeReqVO reqVO);
ApiResponse zhiMaCertResultQuery(RealNameAuthorizeReqVO reqVO);
... ...
package com.yohoufo.user.service.impl;
import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
... ... @@ -9,8 +10,14 @@ import com.alipay.api.request.AlipayUserInfoShareRequest;
import com.alipay.api.response.AlipaySystemOauthTokenResponse;
import com.alipay.api.response.AlipayUserInfoShareResponse;
import com.alipay.api.response.ZhimaCustomerCertificationQueryResponse;
import com.google.common.collect.Lists;
import com.yoho.core.redis.cluster.operations.serializer.RedisKeyBuilder;
import com.yoho.error.exception.ServiceException;
import com.yoho.service.model.reviewed.request.ImageBO;
import com.yoho.service.model.reviewed.request.ImageReviewedReq;
import com.yoho.service.model.reviewed.response.ImageReviewResp;
import com.yoho.tools.common.beans.ApiResponse;
import com.yoho.tools.common.service.IQNUploadService;
import com.yohobuy.ufo.model.user.resp.AuthorizeResultRespVO;
import com.yohoufo.common.exception.GatewayException;
import com.yohoufo.common.exception.UfoServiceException;
... ... @@ -25,16 +32,26 @@ import com.yohoufo.user.common.AlipayConfigInfo;
import com.yohoufo.user.helper.HideDataUtil;
import com.yohoufo.user.requestVO.RealNameAuthorizeReqVO;
import com.yohoufo.user.service.IRealNameAuthorizeService;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
... ... @@ -58,27 +75,22 @@ public class RealNameAuthorizeServiceImpl implements IRealNameAuthorizeService {
@Autowired
private HttpClient httpClient;
@Value("${web.context}")
private String contextPath;
@Value("${zhimacert.switch:true}")
private boolean zhiMaCertSwitch;
/* @Autowired
private GraphVerifyService graphVerifyService;*/
/* @Resource(name="authorizeBankRestTemplate")
private RestTemplate restTemplate;*/
// 需要放到zk上
private Integer maxPhotoCheckCount = 10;
/* @Resource(name = "core-config-reader")
private ConfigReader configReader;*/
//@Value("${file.saveDir}")
private String saveDir="D:/pic";
/* @Autowired
private IUserAuthorizeHistoryDao userAuthorizeHistoryDao;*/
//private final static DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
//@Autowired
private IQNUploadService iqnUploadService;
//请求实名认证银联接口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);
public UserAuthorizeInfo getValidAuthorizeInfo(int uid){
... ... @@ -352,6 +364,201 @@ public class RealNameAuthorizeServiceImpl implements IRealNameAuthorizeService {
System.out.println(getStringDate());
}
@Override
public List<String> zhiMaCertUploadPhotoAndCheck(MultipartFile[] multipartFiles, String bucket, Integer limitType, RealNameAuthorizeReqVO reqVO) throws Exception{
List<String> imgUrls = new ArrayList<>();
//判断file数组不能为空并且长度大于0
if(multipartFiles==null||multipartFiles.length!=2){
throw new GatewayException(400, "请上传身份证正反面!");
}
//每天:记录一个累加的次数 ,超过一定的数量不允许继续调
Date currentTime = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");
String dayStr = formatter.format(currentTime);
Long count = cacheService.getPhotoCheckCount(dayStr,reqVO.getUid());
logger.info("zhiMaCertUploadPhotoAndCheck getPhotoCheckCount count {},reqVO {} ",count,reqVO);
if(count!=null&&count>=maxPhotoCheckCount){
throw new GatewayException(400, "超过当日最大调用次数!");
}
//次数加1
cacheService.incrementPhotoCheckCount(dayStr,reqVO.getUid());
//循环获取file数组中得文件
ImageBO frontUploadModel=null;
ImageBO backUploadModel=null;
for (int i = 0; i < multipartFiles.length; i++) {
MultipartFile multipartFile = multipartFiles[i];
//保存文件
ImageBO tmpBO = getUploadImagePath(multipartFile, bucket, String.valueOf(reqVO.getUid()), limitType);
imgUrls.add(tmpBO.getImageUrl());
if(i==0){
tmpBO.setImageSide("front");
frontUploadModel = tmpBO;
}else{
tmpBO.setImageSide("back");
backUploadModel = tmpBO;
}
}
//调用接口,ocr验证
boolean pass = checkCertPhoto(Lists.newArrayList(frontUploadModel ,backUploadModel) ,reqVO);
if(!pass){
throw new GatewayException(400, "身份证图片校验不通过!");
}
return imgUrls;
}
/**
* 调用通用的图片验证ocr
* @param imageBOList
* @param reqVO
* @return
*/
private boolean checkCertPhoto(List<ImageBO> imageBOList,RealNameAuthorizeReqVO reqVO){
try{
ImageReviewedReq req=new ImageReviewedReq();
req.setUid(reqVO.getUid());
req.setImageBOList(imageBOList);
req.setBusinessLine("ufo");
req.setSceneName("SELLER_IDCARD_RECOGNIZE");
req.setContext(contextPath);
//调用校验,然后直接返回
logger.info("checkCertPhoto call begin reqVO {} ,req {}",reqVO, JSON.toJSONString(req));
ImageReviewResp resp =null;
logger.info("checkCertPhoto call end reqVO {} ,req {} ,resp {} ",reqVO,JSON.toJSONString(req),JSON.toJSONString(resp));
//比较信息
}catch (Exception e){
logger.warn("checkCertPhoto error reqVO {}",reqVO,e);
throw new UfoServiceException(400,"图片检查异常");
}
return true;
}
public ImageBO getUploadImagePath(MultipartFile multipartFile, String bucket, String uid, Integer limitType) throws Exception {
//文件为空
if (multipartFile.isEmpty()) {
logger.warn("upload file is empty");
throw new FileNotFoundException("上传文件为空");
}
// bucket必须为系统已定义常量
if (!"yohocard".equals(bucket)) {
logger.warn("can't find upload bucket {}", bucket);
throw new Exception("上传Bucket:" + bucket + "无效");
}
String imageFileName = multipartFile.getOriginalFilename();
long imageFileSize = multipartFile.getSize();
//产品需求:百度云要求图片大小4M
boolean flag = StringUtils.contains(imageFileName, ".jpeg") ||
StringUtils.contains(imageFileName, ".jpg") || StringUtils.contains(imageFileName, ".png");
if(!flag){
logger.warn("upload file type not correct {}",imageFileName);
throw new FileNotFoundException("上传文件为空");
}
if (imageFileSize >= 4*1024*1024) {
logger.warn("upload images exceeded imageSize {}", imageFileSize);
DecimalFormat decimalFormat = new DecimalFormat("#.00");
throw new Exception("上传图片实际大小:" + decimalFormat.format(imageFileSize / 1024.0) + "KB,超过了图片默认值" );
}
String fileMode = "01";
logger.info("upload image bucket is {}, fileMode is {}", bucket, fileMode);
String saveName = new SimpleDateFormat("/yyyy/MM/dd/HH/").format(new Date());
String fileName = fileMode
+ DigestUtils.md5Hex(uid + "_" + System.currentTimeMillis() + multipartFile.getOriginalFilename())
+ multipartFile.getOriginalFilename().substring(multipartFile.getOriginalFilename().indexOf("."));
// 文件保存路径
String filePath = saveDir + File.separator + fileName;
File uploadFile = new File(filePath);
multipartFile.transferTo(uploadFile);
checkFilePixels(uploadFile, limitType);
String imgUrl = iqnUploadService.upload(uploadFile, bucket, saveName + fileName);
ImageBO imageBO = new ImageBO();
imageBO.setFileByte(FileUtils.readFileToByteArray(uploadFile));
imageBO.setImageUrl(imgUrl);
//删除文件
uploadFile.delete();
return imageBO;
}
/**
* 根据配置,校验每种场景的图片是否符合规范
*
* @param uploadFile
* @param limitType
*/
private void checkFilePixels(File uploadFile, Integer limitType) throws Exception {
if (null == limitType) {
return;
}
logger.info("method checkFilePixels exceute.");
BufferedImage sourceImg = null;
try {
sourceImg = ImageIO.read(java.nio.file.Files.newInputStream(uploadFile.toPath()));
} catch (IOException e) {
logger.warn("ImageIO read find wrong uploadFile {}",uploadFile , e);
throw new GatewayException( 400,"上传文件失败");
}
}
@Override
public AuthorizeResultRespVO zhiMaCertWithPhotoInit(RealNameAuthorizeReqVO reqVO){
logger.info("real name zhiMaCertWithPhotoInit reqVO {}", reqVO);
boolean justCheckPhoto = false;
int uid = reqVO.getUid();
ZhiMaCert zhiMaCert = this.getValidZhiMaCert(uid);
if(zhiMaCert!=null){
if(1==zhiMaCert.getValidPhoto()){
//已经有照片了
throw new UfoServiceException(400, "已实名认证!");
}else{
//只是缺少照片,只是验证照片即可
justCheckPhoto = true;
}
}else{
//检查身份证号认证信息是否已经存在,存在则不允许再次使用
if (null != zhiMaCertDao.selectValidByCertNo(reqVO.getCertNo())) {
throw new UfoServiceException(400, "身份证号已被占用!");
}
}
logger.info("real name zhiMaCertWithPhotoInit reqVO {} ,justCheckPhoto {}", reqVO,justCheckPhoto);
String imgUrl = reqVO.getFrontImageUrl()+","+reqVO.getBackImageUrl();
if(justCheckPhoto){
//TODO 兼容原来老数据,只需要更新照片信息
zhiMaCertDao.updatePhotoToValidByPK(zhiMaCert.getId(),imgUrl);
AuthorizeResultRespVO vo = new AuthorizeResultRespVO();
vo.setCallZhiMa("0");
return vo;
}
//新的芝麻认证
AuthorizeResultRespVO vo = callZhiMaForUrl(reqVO,imgUrl);
vo.setCallZhiMa("1");
return vo;
}
@Override
public AuthorizeResultRespVO zhiMaCertInit(RealNameAuthorizeReqVO reqVO) {
logger.info("real name zhiMaCertInit reqVO {}", reqVO);
... ... @@ -364,6 +571,11 @@ public class RealNameAuthorizeServiceImpl implements IRealNameAuthorizeService {
throw new UfoServiceException(400, "身份证号已被占用!");
}
AuthorizeResultRespVO vo = callZhiMaForUrl(reqVO,"");
return vo;
}
private AuthorizeResultRespVO callZhiMaForUrl(RealNameAuthorizeReqVO reqVO,String imgUrl){
//调用芝麻接口,返回biz_no
//构建唯一的 transaction_id : ufoCert+id
Long nowSecond = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
... ... @@ -377,6 +589,14 @@ public class RealNameAuthorizeServiceImpl implements IRealNameAuthorizeService {
zhiMaCert.setCreateTime(nowSecond);
zhiMaCert.setUpdateTime(nowSecond);
//图片信息
if(StringUtils.isNotBlank(imgUrl)){
zhiMaCert.setValidPhoto(1);
zhiMaCert.setImageUrl(imgUrl);
}else{
zhiMaCert.setValidPhoto(0);
}
zhiMaCertDao.insertAndGetID(zhiMaCert);
if(zhiMaCert.getId()==null||zhiMaCert.getId()<1){
... ...