package com.fdkanfang.web.backend; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.io.FileUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.fdkanfang.common.constant.ConstantFilePath; import com.fdkanfang.common.constant.MsgCode; import com.fdkanfang.common.constant.TypeCode; import com.fdkanfang.common.enums.ImageResolutionRate; import com.fdkanfang.common.enums.ResultCodeEnum; import com.fdkanfang.common.exception.BaseRuntimeException; import com.fdkanfang.common.exception.CommonBaseException; import com.fdkanfang.common.model.PageDto; import com.fdkanfang.common.util.*; import com.fdkanfang.domain.backend.*; import com.fdkanfang.domain.dto.*; import com.fdkanfang.domain.response.HouseResponse; import com.fdkanfang.domain.response.UserResponse; import com.fdkanfang.service.backend.*; import com.fdkanfang.web.backend.utils.UserUtils; import com.fdkanfang.web.mq.config.RabbitConfig; import com.github.pagehelper.PageInfo; import io.swagger.annotations.*; import lombok.extern.log4j.Log4j2; import net.coobird.thumbnailator.Thumbnails; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.RequiresRoles; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.commons.CommonsMultipartFile; import javax.mail.MessagingException; import javax.validation.Valid; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.*; /** * Created by owen on 2020/2/18 0018 12:17 */ @Api(tags = "房源管理") @RestController @RequestMapping("manage/house") @Transactional @Log4j2 public class HouseController extends BaseController { // 服务器文件保存位置 @Value("${output.file.path}") private String OUTPATH; @Value("${server.domain}") private String domain; @Value("${4dkankan.host}") private String KankanHost; @Value("${admin.email.account}") private String adminEmailAccount; @Value("${oss.image.file.path}") private String ossPath; @Value("${oss.query.url}") private String ossQueryUrl; @Autowired private HouseService2 houseService2; @Autowired private ImageService2 imageService2; @Autowired private RabbitTemplate rabbitTemplate; @Autowired private ISceneProService sceneProService; @Autowired private ISceneProEditService sceneProEditService; @Autowired private RoleService2 roleService; @Autowired private UserUtils userUtils; @Autowired private UserService2 userService2; @Autowired private OssCheckPointUploadUtil ossCheckPointUploadUtil; @ApiOperation("房源列表") @PostMapping("list") public R list(@RequestBody PageDto param){ String token = getToken(); // 获取用户角色 List userRoles = userUtils.getRolesByToken(token); UserEntity user = userUtils.getUserByToken(token); Long userId = user.getId(); PageInfo page = null; if(CollectionUtils.isEmpty(userRoles)){ log.info("用户权限为空,无法获取房源列表"); throw new CommonBaseException(ResultCodeEnum.D101 , "权限不足"); } if(userRoles.size() == 3){ //管理员获取全部房源列表 page = new PageInfo<>(houseService2.findBySearchKeyForEditOrUploader(param, null, null)); }else if(userRoles.size() == 2){ if(userRoles.contains("edit") && userRoles.contains("upload")){ //同时拥有编辑和上传权限 page = new PageInfo<>(houseService2.findBySearchKeyForEditOrUploader(param, userId ,userId)); }else if(userRoles.contains("edit") && !userRoles.contains("upload")){ //只拥有编辑权限 page = new PageInfo<>(houseService2.findBySearchKeyForEditOrUploader(param, userId ,null)); }else if (!userRoles.contains("edit") && userRoles.contains("upload")){ //只拥有上传权限 page = new PageInfo<>(houseService2.findBySearchKeyForEditOrUploader(param, null ,userId)); }else{ //理论上不存在此分支情况 log.error("上传的权限超出了当前有效权限范围"); throw new CommonBaseException(ResultCodeEnum.D101 , "存在无效权限"); } }else if(userRoles.size() == 1){ if(userRoles.contains("edit")){ //只拥有编辑权限 page = new PageInfo<>(houseService2.findBySearchKeyForEditOrUploader(param, userId ,null)); }else if (userRoles.contains("upload")){ //只拥有上传权限 page = new PageInfo<>(houseService2.findBySearchKeyForEditOrUploader(param, null ,userId)); }else{ //只有管理用户的权限,理论不应该单独给这种角色 log.warn("用户[{}]只单独给了用户管理权限" ,userId); throw new CommonBaseException(ResultCodeEnum.D101 , "当前权限不能查看数据"); } }else{ log.info("用户[{}]的权限超过3个,默认给拉取所有房源"); //默认获取全部 page = new PageInfo<>(houseService2.findBySearchKeyForEditOrUploader(param, null, null)); } return new R(2000, page); } /** * 服务器版本+垂直校验 * * 使用mq队列 * * 上传文件,不能使用json传参,只能用表单 * * 如果图片审核不通过,就直接把这条记录禁用or删除,从新添加一条。 * 修改只能修改房源信息,不能修改图片 * * 上传的图片必须是以xxx_xxx.jpg 格式命名,xxx_:图片所属的户型 * * * 这里要用手写的表单验证验证方式 * * 提前把垂直校验,模型图片地址写入数据库,需要配合状态来判断算法计算是否完成; */ @RequiresRoles(value = {"admin", "edit", "upload"}, logical = Logical.OR) @ApiOperation("新增或修改房源信息 + 垂直校验") @PostMapping(value = "save", consumes = {"multipart/form-data"}) @Transactional(rollbackFor = Exception.class) public R save(HouseDto param){ if(StringUtils.isBlank(param.getDistrictName())){ log.error("小区名称不能为空"); return new R(MsgCode.e_COMMON_3001, "小区名称不能为空"); } if(null == param.getFloor() || !StringUtils.isNoneBlank(param.getUnitType() , param.getArea() , param.getOrientation())){ return new R(MsgCode.e_COMMON_3001, "楼层/户型/面积/朝向不能为空"); } List fileNames = param.getFileNames(); if(CollectionUtils.isEmpty(fileNames)){ return new R(MsgCode.e_COMMON_3001, "照片名称列表不能为空"); } boolean isNewAdd = false; HouseEntity house = null; if (null != param.getIsCreate() && param.getIsCreate().compareTo(0) == 0) { isNewAdd = true; } house = updateOrInsertHouse(param); // 只有新增是才保存图片,修改时另外处理 if (isNewAdd) { //场景码 String sceneCode = house.getSceneCode(); boolean needSendMqMsg = false; String directoryName = OUTPATH + sceneCode + File.separator + "input_img"+ File.separator; FileUtil.mkdir(directoryName); HashMap ossVerticalImageHighMap = new HashMap<>(); HashMap ossVerticalImageLowMap = new HashMap<>(); String imageHighPath = null; String imageLowPath = null; for (String filename : fileNames) { JSONObject jsonObject = JSON.parseObject(filename); if(null == jsonObject){ log.error("转换json对象失败:{}" , filename); continue; } //原始照片在oss的路径 String origin = jsonObject.getString("origin"); String newFileName = jsonObject.getString("name"); if(!StringUtils.isNoneEmpty(origin , newFileName)){ continue; } checkAndChangeFileName(newFileName); //从oss获取照片,然后用新的名字下载到本地 Map ossInfo = getOssBucketInfo(origin); if(CollectionUtils.isEmpty(ossInfo)){ log.info("oss数据解析失败,跳过"); continue; } String bucketName = (String) ossInfo.get("ossBucket"); String ossObjKey = (String) ossInfo.get("ossKey"); if(!StringUtils.isNoneBlank(bucketName , ossObjKey)){ log.info("oss的bucketName数据解析失败,跳过"); continue; } String oldImageName = ""; int index = ossObjKey.lastIndexOf("/"); if(index == -1){ log.error("[{}]获取不到改名前的名称" , ossObjKey); continue; } oldImageName = ossObjKey.substring(index + 1); log.info("照片改名前的名称为:{}" , oldImageName); //这里改名 String newFilePath = directoryName + newFileName; String oldFilePath = directoryName + oldImageName; File oldImageFile = new File(oldFilePath); File newImageFile = new File(newFilePath); try { org.apache.commons.io.FileUtils.copyFile(oldImageFile, newImageFile); } catch (IOException e) { log.error("改名操作出现异常,跳过"); e.printStackTrace(); continue; } boolean del = FileUtils.deleteFile(oldFilePath); log.info("删除的文件目录为:{},删除结果为:{}" , oldFilePath , del); //用于四维看看的 imageHighPath = OUTPATH + sceneCode + "/output_img/" + newFileName; //用于sketch,sketch的image表需要存储这个路径,给前端使用 imageLowPath = OUTPATH + sceneCode + "/output_img_low/" + newFileName; // 提前把算法的图片路径存到数据库,但不代表算法运行成功,最后好是要看house的状态; String ossVerticalHighPath = ConstantFilePath.OSS_IMAGE_PATH+ sceneCode+"/pan/high/"+ newFileName; String ossVerticalLowPath = ConstantFilePath.OSS_IMAGE_PATH+ sceneCode+"/pan/low/"+ newFileName; //TODO:这里需要计算分辨率 //照片分辨率 int totalResolutinRate = 0; try { totalResolutinRate = getImageResolutinoRate(newImageFile); } catch (IOException e) { log.error("计算照片[{}]分辨率出异常" , newFilePath); e.printStackTrace(); } ImageResolutionRate maxResolutionRate = ImageResolutionRate.getResolutionRateByRate(totalResolutinRate); log.info("照片{}的像素为:{}" , newFilePath , totalResolutinRate); addNewImage(param.getId() , newFileName , newFilePath , ossVerticalLowPath, maxResolutionRate); // 封装垂直校验后的图片到信息到oss,key为照片在本地的路径,value为照片在oss中的目录路径 //给四维看看的前端加载使用 ossVerticalImageHighMap.put(imageHighPath, ossVerticalHighPath); //四维看看用的,文件太大 //给sketch的前端使用 ossVerticalImageLowMap.put(imageLowPath, ossVerticalLowPath); //sketch用的,文件适中 needSendMqMsg = true; } insertScene(param.getImageMaxRate() , house); if (needSendMqMsg) { HashMap mqMap = new HashMap<>(); mqMap.put("ossImageHigh", ossVerticalImageHighMap); mqMap.put("ossImageLow", ossVerticalImageLowMap); mqMap.put("id", house.getId()); mqMap.put("basePath", OUTPATH); mqMap.put("dataDescribe", ""); log.info("houseId: {}", house.getId()); //发消息到mq rabbitTemplate.convertAndSend(RabbitConfig.VERTICAL_EXCHANGE, RabbitConfig.VERTICAL_QUEUE_ROUTING, mqMap); } //新增这里,给管理员发送邮件 String emailContent = "【四维看看 Sketch】温馨提示:用户上传了新的房源,请尽快完成制作。点击立即登录查看:https://sk.4dkankan.com"; String subject = "四维看看sketch通知"; try { MailUtils.sendHtmlMail(adminEmailAccount,subject,emailContent); } catch (UnsupportedEncodingException e) { log.error("发送邮件给管理员出错"); e.printStackTrace(); } catch (MessagingException e) { log.error("发送邮件给管理员出错"); e.printStackTrace(); } } return new R(MsgCode.SUCCESS_CODE, house); } public Map getOssBucketInfo(String ossUrl){ Map result = new HashMap<>(); if(StringUtils.isBlank(ossUrl)){ return result; } log.info("oss资源的路径为:{}" , ossUrl); int bucketStar = ossUrl.indexOf("://"); if(bucketStar == -1){ log.error("oss路径没有://"); return result; } bucketStar += 3; int buckeEnd = ossUrl.indexOf("."); if(buckeEnd == -1){ log.error("oss路径没有./"); return result; } String buckeName = ossUrl.substring(bucketStar , buckeEnd); log.info("解析的buckeName={}" , buckeName); int keyStart = ossUrl.indexOf("com/"); if(keyStart == -1){ log.error("oss路径没有com/"); return result; } keyStart += 5; String key = ossUrl.substring(keyStart , ossUrl.length()); log.info("解析出来的oss key={}" , key); result.put("ossBucket" , buckeName); result.put("ossKey" , key); return result; } // @RequiresRoles(value = {"admin", "edit", "upload"}, logical = Logical.OR) @ApiOperation("生成houseId和场景码") @PostMapping(value = "addPosition") @Transactional(rollbackFor = Exception.class) public R addPosition(){ return new R(MsgCode.SUCCESS_CODE, generateHouseIdAndSceneNum()); } @ApiOperation("从OSS上下载原始照片并生成缩略图") @PostMapping(value = "uploadImages") @Transactional(rollbackFor = Exception.class) @ApiImplicitParams({ @ApiImplicitParam(name = "file", value = "文件", paramType = "query", required = true, dataType = "List"), @ApiImplicitParam(name = "sceneCode", value = "场景码", paramType = "query", required = true, dataType = "String"), @ApiImplicitParam(name = "houseId", value = "房间ID", paramType = "query", required = true, dataType = "Long") }) public Result uploadImages(@RequestParam(name = "sceneCode") String sceneCode, @RequestParam(name = "ossBucketName") String ossBucketName, @RequestParam(name = "ossObjectKey") String ossObjectKey){ return Result.success("成功" , genScaleImage(ossBucketName , ossObjectKey , sceneCode)); } @Transactional(rollbackFor = Exception.class) public void insertScene(String maxResolutionRate , HouseEntity house){ SceneProEntity sceneProEntity = new SceneProEntity(); SceneProEditEntity sceneProEditEntity = new SceneProEditEntity(); if(ImageResolutionRate.TWO_K.name().equals(maxResolutionRate)){ //2k sceneProEntity.setSceneScheme(11); }else{ //4k sceneProEntity.setSceneScheme(10); } sceneProEntity.setNum(house.getSceneCode()); //TODO:不知道为何数据库设置的默认不生效 sceneProEditEntity.setM2dVisi(1); sceneProEditEntity.setM3dVisi(1); sceneProEditEntity.setPanoVisi(1); sceneProEditEntity.setMapVisi(1); int insertOne = sceneProService.save(sceneProEntity); if(insertOne != 1){ throw new CommonBaseException(ResultCodeEnum.D101, "新增scenePro记录失败"); } sceneProEditEntity.setProId(sceneProEntity.getId()); int insertTwo = sceneProEditService.save(sceneProEditEntity); if(insertTwo != 1){ throw new CommonBaseException(ResultCodeEnum.D101, "新增sceneProEdit记录失败"); } } public String genScaleImage(String ossBucketName , String ossObjectKey , String sceneCode){ String scalImageOssUrl = ""; if(!StringUtils.isNoneEmpty(ossBucketName)){ log.error("oss的bucketName 和ossKey不能为空"); return scalImageOssUrl; } try { String fileName = ""; int index = ossObjectKey.lastIndexOf("/"); if(index != -1){ fileName = ossObjectKey.substring(index + 1 , ossObjectKey.length()); }else{ log.error("ossObjectKey[{}]的格式不正确,无法提取文件名" , ossObjectKey); } String directoryName = OUTPATH + sceneCode + File.separator + "input_img"+ File.separator; String localFile = ossCheckPointUploadUtil.downLoadOssFileWithSdk(directoryName , "", ossBucketName , ossObjectKey); if(StringUtils.isNotBlank(localFile)){ //生成缩略图,并上传到oss String scalImageFullPath = directoryName + "scal_" + fileName; log.info("要转换的照片的路径:{},转换的目标路径:{}" , localFile , scalImageFullPath); Thumbnails.of(localFile).size(980, 490).toFile(scalImageFullPath); String imageOssPath = ossPath + "scal_" + fileName; log.info("生成的照片上传oss的路径:{}" , imageOssPath); ossCheckPointUploadUtil.upload2(scalImageFullPath, imageOssPath); scalImageOssUrl = ossQueryUrl + imageOssPath; //删除缩略图的本地文件 boolean delScalResult = FileUtils.deleteFile(scalImageFullPath); if(!delScalResult){ log.error("删除本地缩略图[{}]失败" , scalImageFullPath); } } } catch (IOException e) { log.error("从oss上下载照片到本地出现异常"); e.printStackTrace(); } return scalImageOssUrl; } @Transactional(rollbackFor = Exception.class) // public ImageResolutionRate addAndGetResolutionRate(MultipartFile file , String filename,String directoryName , public Map addAndGetResolutionRate(CommonsMultipartFile file , String filename, String directoryName , String ossVerticalHighPath , Long houseId) throws IOException { if(null == file || null == houseId){ log.error("房源id或者文件为空,无法生成image记录"); return new HashMap<>(); } File dir = new File(directoryName); if(!dir.exists()){ dir.mkdirs(); } // 本地图片保存位置: /root/data/kanfang/场景码/input_img/xxxx.jpg String newFileName = generalRandFileName(filename); log.info("修改后的文件名称为:{}" , newFileName); String fileFullPath = directoryName + newFileName; String oldFileFullPath = directoryName + filename; ossVerticalHighPath = ossVerticalHighPath + newFileName; ImageEntity image = new ImageEntity(); image.setVerticalPath(ossVerticalHighPath); image.setFileName(newFileName); // 这个参数给前端用,只要有文件名就可以了 image.setPath(fileFullPath); image.setLocalPath(fileFullPath); image.setHouseId(houseId); // 默认所有图片都是一楼 image.setFloor(1); // image.setType(imageType); //将图片保存到本地指定目录filePath eg: /root/data/kanfang/d_9iRDUgn3l/input_img/xxx.jpg saveFileToLocal(file , fileFullPath); boolean delResult = FileUtils.deleteFile(oldFileFullPath); if(!delResult){ log.error("删除旧文件[{}]失败" , oldFileFullPath); } File localFile = new File(fileFullPath); //生成缩略图,并上传到oss String scalImageFullPath = directoryName + "scal_" + newFileName; Thumbnails.of(localFile).size(128 , 256).outputQuality(0.8f).toFile(scalImageFullPath); String imageOssPath = ossPath + "scal_" + newFileName; log.info("生成的照片上传oss的路径:{}" , imageOssPath); ossCheckPointUploadUtil.upload2(scalImageFullPath, imageOssPath); String imageTotalOssUrl = ossQueryUrl + imageOssPath; //删除缩略图的本地文件 boolean delScalResult = FileUtils.deleteFile(scalImageFullPath); if(!delScalResult){ log.error("删除本地缩略图[{}]失败" , scalImageFullPath); } //照片分辨率 int totalResolutinRate = getImageResolutinoRate(localFile); ImageResolutionRate maxResolutionRate = ImageResolutionRate.getResolutionRateByRate(totalResolutinRate); log.info("照片{}的像素为:{}" , fileFullPath , totalResolutinRate); image.setResolutionRate(null != maxResolutionRate ? maxResolutionRate.name() : "TWO_K"); int insert = imageService2.save(image); if(insert != 1){ throw new CommonBaseException(ResultCodeEnum.D101, "新增image记录失败"); } Map result = new HashMap<>(); result.put("rate" , maxResolutionRate.name()); result.put("fileName" , newFileName); result.put("ossUrl" , imageTotalOssUrl); return result; } private void addNewImage(long houseId, String imageFileName , String imageLocalPath , String ossVerticalHighPath, ImageResolutionRate maxResolutionRate){ ImageEntity image = new ImageEntity(); // 获取图片房间类型 String imageType = StringUtils.substringBefore(imageFileName, "_"); image.setType(imageType); image.setVerticalPath(ossVerticalHighPath); image.setFileName(imageFileName); // 这个参数给前端用,只要有文件名就可以了 image.setPath(imageLocalPath); image.setLocalPath(imageLocalPath); image.setHouseId(houseId); // 默认所有图片都是一楼 image.setFloor(1); image.setResolutionRate(null != maxResolutionRate ? maxResolutionRate.name() : "TWO_K"); int insert = imageService2.save(image); if(insert != 1){ throw new CommonBaseException(ResultCodeEnum.D101, "新增image记录失败"); } } private String generalRandFileName(String fileName){ if(StringUtils.isBlank(fileName)){ return ""; } int index = fileName.lastIndexOf("."); if(index != -1){ String name = fileName.substring(0 , index); String form = fileName.substring(index , fileName.length()); log.info("照片存名字部分为:{}" , name); if(StringUtils.isNotBlank(name)){ Random random = new Random(10); name += System.currentTimeMillis() + random.nextInt(10); name += form; return name; } } return ""; } private int getImageResolutinoRate(File localFile) throws IOException { if(null != localFile){ int totalResolutinRate = -1; SimpleImageInfo simpleImageInfo = new SimpleImageInfo(localFile); if(null != simpleImageInfo){ if(simpleImageInfo.getWidth() < simpleImageInfo.getHeight()){ totalResolutinRate = simpleImageInfo.getHeight(); }else{ totalResolutinRate = simpleImageInfo.getWidth(); } } return totalResolutinRate; } return 0; } private void saveFileToLocal(CommonsMultipartFile file , String localFullPath) throws IOException { if(StringUtils.isBlank(localFullPath) || null == file){ return; } File newFile = new File(localFullPath); file.transferTo(newFile); } private String checkAndChangeFileName(String filename){ if(StringUtils.isBlank(filename)){ throw new CommonBaseException(ResultCodeEnum.D100 , "文件名不能为空"); } if(filename.contains(".JPG")){ int index = filename.lastIndexOf("."); String fileNameWithoutPostfix = filename.substring(0 , index); filename = fileNameWithoutPostfix + ".jpg"; log.info("照片的格式写成了大写,需要转成小写:{}" , filename); } // 检查一下,生成模型的照片名称不能有"-",否则生成模型会出现问题 if (filename.contains("-")) { log.error("filename: {}", filename); throw new BaseRuntimeException(MsgCode.e_COMMON_3003, "图片命名不符合要求,不能包含符号-,请使用_"); } if (RegexpUtils.isContainChinese(filename)) { log.error("图片名称不允许中文字符: {}", filename); throw new BaseRuntimeException(MsgCode.e_COMMON_3003, "图片名称不允许中文字符"); } return filename; } private Map generateHouseIdAndSceneNum(){ Map result = new HashMap<>(); Integer byMaxNum = houseService2.findByMaxNum(); // 用于第一条数据 byMaxNum = (byMaxNum == null)? 10000:byMaxNum; byMaxNum += 1; String sceneCode = getSceneCode(); HouseEntity house = new HouseEntity(); UserEntity user = userUtils.getUserByToken(getToken()); house.setUserId(user.getId()); house.setNum(byMaxNum + 1); house.setSceneCode(sceneCode); house.setWebSite(domain+"?m="+sceneCode); house.setFilePath(OUTPATH + sceneCode); int insert = houseService2.save(house); if(insert != 1){ log.error("新增房源失败"); return result; } HouseEntity dbHouse = houseService2.findBySceneCode(sceneCode); if(null != dbHouse){ result.put("id" , dbHouse.getId()); result.put("sceneCode" , sceneCode); } return result; } @Transactional(rollbackFor = Exception.class) public HouseEntity updateOrInsertHouse(HouseDto param){ if(null == param){ return null; } HouseEntity house = null; if (param.getId() == null) { house = new HouseEntity(); UserEntity user = userUtils.getUserByToken(getToken()); house.setUserId(user.getId()); param.setId(null); BeanUtils.copyProperties(param, house); Integer byMaxNum = houseService2.findByMaxNum(); // 用于第一条数据 byMaxNum = (byMaxNum == null)? 10000:byMaxNum; String sceneCode = getSceneCode(); house.setNum(byMaxNum + 1); house.setSceneCode(sceneCode); house.setWebSite(domain+"?m="+sceneCode); house.setFilePath(OUTPATH + sceneCode); houseService2.save(house); } else { house = houseService2.findById(param.getId()); if (house == null) { log.error("房源不存在: {}", param.getId()); throw new CommonBaseException(ResultCodeEnum.D101 , "房源不存在"); } BeanUtils.copyProperties(param, house); if(StringUtils.isBlank(house.getSceneCode())){ house.setSceneCode(param.getSceneCode()); } if(StringUtils.isBlank(house.getWebSite())){ house.setWebSite(domain+"?m=" + param.getSceneCode()); } if(StringUtils.isBlank(house.getFilePath())){ house.setFilePath(OUTPATH + param.getSceneCode()); } house.setUpdateTime(new Date()); house.setStatus(0); houseService2.update(house); } return house; } @ApiOperation("预审") @ResponseBody @PostMapping(value = "/auditHouse") @Transactional(rollbackFor = Exception.class) public Result auditHouse(@RequestBody @ApiParam(name = "用户登录注册实体", value = "传入json格式", required = true) HouseAuditDto houseAuditDto) { if(StringUtils.isBlank(houseAuditDto.getDesc()) || null == houseAuditDto.getHouseId() || null == houseAuditDto.getResult()){ throw new CommonBaseException(ResultCodeEnum.D3001); } HouseEntity houseEntity = houseService2.findById(houseAuditDto.getHouseId()); if(null == houseEntity){ throw new CommonBaseException(ResultCodeEnum.D101 , "房源不存在"); } if(houseAuditDto.getResult().compareTo(1) != 0 && houseAuditDto.getResult().compareTo(0) != 0){ throw new CommonBaseException(ResultCodeEnum.D101 , "审批结果格式不正确"); } houseEntity.setAuditResult(houseAuditDto.getResult()); houseEntity.setAuditDesc(houseAuditDto.getDesc()); houseEntity.setUpdateTime(new Date()); int update = houseService2.update(houseEntity); if(update != 1){ throw new CommonBaseException(ResultCodeEnum.D101 , "更新房源预审信息失败"); } return Result.success(); } @ApiOperation("标记不能编辑") @ResponseBody @PostMapping(value = "/markFail") public Result markCannotEditHouse(@RequestBody @ApiParam(name = "用户登录注册实体", value = "传入json格式", required = true) HouseAuditDto houseAuditDto) { if(StringUtils.isBlank(houseAuditDto.getDesc()) || null == houseAuditDto.getHouseId()){ throw new CommonBaseException(ResultCodeEnum.D3001); } HouseEntity houseEntity = houseService2.findById(houseAuditDto.getHouseId()); if(null == houseEntity){ throw new CommonBaseException(ResultCodeEnum.D101 , "房源不存在"); } houseEntity.setCanNotEdit(0); houseEntity.setCanNotEditDesc(houseAuditDto.getDesc()); houseEntity.setUpdateTime(new Date()); int update = houseService2.update(houseEntity); if(update != 1){ throw new CommonBaseException(ResultCodeEnum.D101 , "更新房源无法制作信息失败"); } return Result.success(); } /** * * @param houseId * @param floor 可选参数 */ @ApiOperation("查询房源信息") @GetMapping("detail") @ApiImplicitParams({ @ApiImplicitParam(name = "houseId", value = "房源id", required = true), @ApiImplicitParam(name = "floor", value = "楼层", required = false) }) public R detail(Long houseId, Integer floor){ HouseEntity house = houseService2.findById(houseId); List images = imageService2.findByHouseIdAndFloor(houseId, floor); if(null != house){ house.setImages(images); HouseResponse houseResponse = new HouseResponse(); BeanUtil.copyProperties(house ,houseResponse , CopyOptions.create().setIgnoreNullValue(true).setIgnoreError(true)); if(null != house.getUserId()){ UserEntity uploader = userService2.findById(house.getUserId()); if(null != uploader){ houseResponse.setUploadName(uploader.getRealName()); } } if(null != house.getHandler()){ UserEntity handler = userService2.findById(house.getHandler()); if(null != handler){ houseResponse.setHandlerName(handler.getRealName()); } } return new R(MsgCode.SUCCESS_CODE, houseResponse); }else{ return new R(MsgCode.ERROR_CODE, "房源不存在"); } } @RequiresRoles(value = {"admin", "edit", "upload"}, logical = Logical.OR) @ApiOperation("删除房源(软删除)") @GetMapping("removes/{ids}") @Transactional(rollbackFor = Exception.class) public R removes(@PathVariable String ids){ List userRoles = userUtils.getRolesByToken(getToken()); String roleKey = null; if (userRoles.size() <= 1) { // 只有一个权限时 roleKey = (String)userRoles.get(0); if (!"upload".equals(roleKey)) { log.warn("只有一个权限的非上传者角色,不能删除房源"); throw new CommonBaseException(ResultCodeEnum.D101 , "权限不足,不能删除房源"); } }else if(userRoles.size() == 2){ if(userRoles.contains("edit") && userRoles.contains("admin")){ //理论上不会有这种权限组合 log.warn("多于一个权限的角色,只有用户管理和编辑房源权限不能删除房源"); throw new CommonBaseException(ResultCodeEnum.D101 , "权限不足(缺少上传房源权限),不能删除房源"); } } String[] split = ids.split(","); for (String s : split) { HouseEntity houseEntity = houseService2.findById(Long.valueOf(s)); if (houseEntity == null) { log.error("房源id有误,没有存在的房源"); return new R(MsgCode.e_COMMON_3002,"没有存在的房源"); } if(null != houseEntity.getStatus()){ if((houseEntity.getStatus().compareTo(0) == 0) && (null != houseEntity.getStatus() && houseEntity.getStatus().compareTo(1) == 0)){ //预审通过了的,还未垂直校验的,不能删除 log.error("房源垂直校验计算中不能删除"); throw new CommonBaseException(ResultCodeEnum.D101 , "房源垂直校验计算中不能删除"); } if(houseEntity.getStatus().compareTo(3) == 0){ log.error("房源生成模型计算中不能删除"); throw new CommonBaseException(ResultCodeEnum.D101 , "房源生成模型计算中不能删除"); } } houseEntity.setRecStatus("I"); houseEntity.setUpdateTime(new Date()); int update = houseService2.update(houseEntity); if(update != 1){ log.error( "删除房源失败[{}]" , houseEntity.getId()); throw new CommonBaseException(ResultCodeEnum.D101 , "删除房源失败"); } } return new R(MsgCode.SUCCESS_CODE, MsgCode.msg_SUCCESS); } /** * 更新sceneJson(写到另一个字段), 保留原始sceneJson数据 * */ @RequiresRoles(value = {"admin", "edit"}, logical = Logical.OR) @ApiOperation("更新sceneJson") @PostMapping("update/sceneJson") public R updateSceneJson(@Valid @RequestBody SceneJsonDto param){ HouseEntity house = houseService2.findById(param.getId()); if (house == null) { log.error("房源id不存在"); return new R(MsgCode.e_COMMON_3001, "房源id不存在"); } house.setNewSceneJson(param.getSceneJson()); house.setUpdateTime(new Date()); houseService2.update(house); return new R(MsgCode.SUCCESS_CODE, MsgCode.msg_SUCCESS); } @ApiOperation("获取楼层户型图") @GetMapping("image/find/floor/{houseId}/{floor}") public R findImageByFloor(@PathVariable Long houseId, @PathVariable Integer floor){ List images = imageService2.findByHouseIdAndFloor(houseId, floor); return new R(MsgCode.SUCCESS_CODE, images); } /** * 只有管理员才能分配制作人 * 制作人是edit角色 */ @RequiresRoles(value = {"admin"}, logical = Logical.OR) @ApiOperation("获取制作人") @GetMapping("getHandler") public R getHandler(){ // 根据edit权限查询用户 List usre = roleService.findUserByRoleKey("edit"); return new R(MsgCode.SUCCESS_CODE, usre); } @RequiresRoles(value = {"admin"}, logical = Logical.OR) @ApiOperation("分配制作人") @GetMapping("setHandler/{houseId}/{userId}") public R setHandler(@PathVariable Long houseId, @PathVariable Long userId){ // 根据edit权限查询用户 HouseEntity houseEntity = houseService2.findById(houseId); if (houseEntity == null) { log.error("房源不存在: {}", houseId); return new R(MsgCode.e_COMMON_3001, "房源不存在"); } houseEntity.setHandler(userId); houseEntity.setUpdateTime(new Date()); if(null != houseEntity.getAuditResult() && houseEntity.getAuditResult().compareTo(0) == 0){ //重新分配制作人,前面不通过的审批,需要擦除,TODO:需要添加一个上一状态,用于追踪 houseEntity.setAuditResult(null); houseEntity.setAuditDesc(null); houseEntity.setCanNotEdit(1); houseEntity.setCanNotEditDesc(null); } if(null != houseEntity.getCanNotEdit() && houseEntity.getCanNotEdit().compareTo(0) == 0){ //重新分配制作人,前面不通过的审批,需要擦除,TODO:需要添加一个上一状态,用于追踪 houseEntity.setAuditResult(null); houseEntity.setAuditDesc(null); houseEntity.setCanNotEdit(1); houseEntity.setCanNotEditDesc(null); } int update = houseService2.updateAll(houseEntity); if(update != 1){ throw new CommonBaseException(ResultCodeEnum.D101 , "更新房源数据失败"); } return new R(MsgCode.SUCCESS_CODE, MsgCode.msg_SUCCESS); } @ApiOperation("查询房源图片") @GetMapping("image/detail/{id}") public R imageDetail(@PathVariable Long id){ return new R(MsgCode.SUCCESS_CODE, imageService2.findById(id)); } @RequiresRoles(value = {"admin", "edit"}, logical = Logical.OR) @ApiOperation("编辑图片信息") @PostMapping("image/edit") public R imageEdit(@RequestBody ImageDto param){ ImageEntity image = imageService2.findById(param.getId()); if (image == null){ log.error("图片id不存在: {}" , param.getId()); throw new BaseRuntimeException(MsgCode.e_COMMON_3002, MsgCode.msg_COMMON_3002); } BeanUtils.copyProperties(param, image); image.setUpdateTime(new Date()); imageService2.update(image); return new R(MsgCode.SUCCESS_CODE, MsgCode.msg_SUCCESS); } @RequiresRoles(value = {"admin", "edit"}, logical = Logical.OR) @ApiOperation("批量编辑图片信息") @PostMapping("image/edit/all") public R imageEdit(@RequestBody Map param){ for (Map.Entry a : param.entrySet()) { ImageEntity image = imageService2.findById(a.getKey()); image.setUpdateTime(new Date()); image.setIssue(a.getValue()); imageService2.update(image); } return new R(MsgCode.SUCCESS_CODE, MsgCode.msg_SUCCESS); } /** * 陈世超的算法 * 调用算法计算场景,生产全景图 * * 图片是用垂直校验后的图片 * * Content传到后台会生成两个文件:floorplan.json、vision.txt */ @RequiresRoles(value = {"admin", "edit"}, logical = Logical.OR) @ApiOperation("生成模型") @PostMapping("rsa/pano") @Transactional(rollbackFor = Exception.class) public R rsaPano(@Valid @RequestBody PanoDto param) throws Exception { log.info("run rsaPano"); if (param == null) { log.error("参数为空"); return new R(50001, "error"); } HouseEntity house = houseService2.findById(param.getHouseId()); if (house == null) { log.error("房源不存在: {}", param.getHouseId()); return new R(MsgCode.e_COMMON_3002, MsgCode.msg_COMMON_3002); } String sceneCode = house.getSceneCode(); boolean flagQ = false; // data/kanfang/10001/pano String savePath = OUTPATH + sceneCode + "/" + TypeCode.SCENE_PANO; // 检查场景码对应的pano文件夹是否存在,存在则删除里面的内容(用户从新编辑场景) if (FileUtil.isDirectory(savePath)) { FileUtil.del(savePath); log.info("删除旧目录重新计算: {}", sceneCode); } log.warn("savePath: {}", savePath); String sourcePath = savePath + "/extras/"; String resultSourcePath = savePath + "/results/"; try { FileUtils.createDir(sourcePath); FileUtils.createDirIfExistThenDel(resultSourcePath); /** * 以下字段为给算法部传递的参数(data.json),用于生成模型和 * 照片,提供前端使用,完整的参数如下: * { * "split_type": "SPLIT_V5", * "skybox_type": "SKYBOX_V5", * "extras": { * "has_vision_txt": true, * "has_floorplan_json": true, * "has_source_images": true * } * } * */ String content = ""; //生成data.json供算法部使用 JSONObject dataJson = new JSONObject(); dataJson.put("split_type", "SPLIT_V5"); String imageResolution = checkImageResolutionRate(house); if(ImageResolutionRate.TWO_K.name().equals(imageResolution)){ //2 K 照片 dataJson.put("skybox_type", "SKYBOX_V7"); }else{ //4 k照片 dataJson.put("skybox_type", "SKYBOX_V6"); } dataJson.put("extras", null); JSONObject extrasJson = new JSONObject(); extrasJson.put("has_vision_txt" , true); extrasJson.put("has_floorplan_json" , true); extrasJson.put("has_source_images" , true); dataJson.put("extras" , extrasJson); FileUtils.fileWriter(dataJson.toJSONString(), savePath + "/data.json"); log.warn("dataJson的生成路径: {}", savePath + "/data.json"); // 解析json JSONObject original = JSON.parseObject(param.getContent()); // 获取modelData, cameraData String floorPlan = original.getString("floorplan"); String vision = original.getString("vision"); if (floorPlan == null) { log.error("floorPlan不能为空"); return new R(50002, "floorPlan不能为空"); } if (vision == null) { log.error("vision不能为空"); return new R(50002, "vision不能为空"); } // json写入服务器 FileUtils.fileWriter(floorPlan, sourcePath + "floorplan.json"); FileUtils.fileWriter(vision, sourcePath + "vision.txt"); FileUtils.fileWriter(vision, resultSourcePath + "vision.txt"); log.info("floorplan.json生成完成"); log.info("/extras/vision.txt,/results/vision.txt生成完成"); // 复制垂直校验图片到指定目录 String verticalImagePath = OUTPATH + sceneCode+"/output_img"; String panoImagePath = sourcePath + "images/"; // srcPath: /data/kanfang/10002/output_img log.info("srcPath: {}", verticalImagePath); // target: /data/kanfang/10002/pano/extras/images/ log.info("target: {}", panoImagePath); FileUtil.copyFilesFromDir(new File(verticalImagePath), new File(panoImagePath), true); flagQ = true; } catch (Exception e) { e.printStackTrace(); log.error("出错了……"); return new R(50002, e.getMessage()); } if (flagQ) { // 上传floorplan.json到oss, 并命名为floor.json HashMap uploadJson = new HashMap<>(); // osspath : images/images+sceneCode/floor.json uploadJson.put(sourcePath + "floorplan.json", ConstantFilePath.OSS_FLOOR_PATH+sceneCode+"/floor.json"); AliyunOssUtil.uploadMulFiles(uploadJson); // savePath:/data/kanfang/房源编号/ // basePath: /data/kanfang/10002/pano log.info("basePath: {}", savePath); log.info("houseId: {}", house.getId()); // 每点击生成模型一次,就修改SceneProEditEntity的部分值 SceneProEntity sceneProEntity = sceneProService.findBySceneNum(house.getSceneCode()); if (sceneProEntity == null) { log.error("sceneProEntity对象不存在:{}", house.getSceneCode()); return new R(MsgCode.ERROR_CODE, "sceneProEntity对象不存在"); } // 修改房源状态, 3:模型计算中 house.setStatus(3); house.setUpdateTime(new Date()); int updateHouse = houseService2.update(house); if(updateHouse != 1){ throw new CommonBaseException(ResultCodeEnum.D101 , "更新房源状态失败"); } SceneProEditEntity proEditEntity = sceneProEditService.findByProId(sceneProEntity.getId()); proEditEntity.setVersion(proEditEntity.getVersion() + 1); proEditEntity.setFloorEditVer(proEditEntity.getFloorEditVer() + 1); proEditEntity.setFloorPublishVer(proEditEntity.getFloorPublishVer() + 1); proEditEntity.setUpdateTime(new Date()); int updateSceneProEdit = sceneProEditService.update(proEditEntity); if(updateSceneProEdit != 1){ throw new CommonBaseException(ResultCodeEnum.D101 , "更新sceneProEdit状态失败"); } log.info("更新SceneProEditEntity数据完成"); //发消息到mq Map mqMap = formatMqReq(house , sceneProEntity , savePath , RabbitConfig.PANO_CALLBACK_QUEUE); String mqMapStr = JSON.toJSONString(mqMap); log.info("发送mq消息给四维看看建模:{}", mqMapStr); rabbitTemplate.convertAndSend(RabbitConfig.PANO_EXCHANGE, RabbitConfig.PANO_QUEUE_ROUTING, mqMapStr); log.info("Mq入队成功"); } return new R(MsgCode.SUCCESS_CODE, house); } private Map formatMqReq(HouseEntity house , SceneProEntity sceneProEntity , String path, String callbackQueue){ Map result = new HashMap<>(); if(null == house){ return result; } result.put("sceneName" , house.getDistrictName()); result.put("sceneNum" , house.getSceneCode()); result.put("sceneScheme" , sceneProEntity.getSceneScheme()); if(null != house.getUserId()){ UserEntity user = userService2.findById(house.getUserId()); int kankanUserId = -1; if(null != user && null != user.getKankanUserId()){ kankanUserId = user.getKankanUserId(); } result.put("userId" , kankanUserId); } result.put("dataSource" , path); result.put("callbackQueue" , callbackQueue); return result; } private String checkImageResolutionRate(HouseEntity houseEntity){ if(null == houseEntity || null == houseEntity.getId()){ return null; } List imageEntities = imageService2.findByHouseId(houseEntity.getId()); if(CollectionUtils.isEmpty(imageEntities)){ log.error("房源[{}]下面无照片,默认返回2k的分辨率"); return ImageResolutionRate.TWO_K.name(); } int sort = 0; ImageResolutionRate maxResolution = null; for (ImageEntity imageEntity : imageEntities){ if(StringUtils.isBlank(imageEntity.getResolutionRate())){ log.warn("房源[{}]的照片[{}]没有设置照片分辨率,跳过" , houseEntity.getId() , imageEntity.getId()); break; } ImageResolutionRate curResolution = ImageResolutionRate.getResolutionByName(imageEntity.getResolutionRate()); if(null != curResolution){ if(curResolution.getOrder() > sort){ sort = curResolution.getOrder(); maxResolution = curResolution; } } } log.info("房源[{}]的照片最大的分辨率为:{}" , houseEntity.getId() , null != maxResolution ? maxResolution.name() :"空值"); return null != maxResolution ? maxResolution.name() : ImageResolutionRate.TWO_K.name(); } /*** * 从四维看看获取唯一场景码 */ private String getSceneCode(){ String url = KankanHost + "api/scene/finSkSceneNum"; String kankanResult = HttpClientUtil.doPost(url); //解析返回结果 JSONObject res = JSONObject.parseObject(kankanResult); log.info("四维看看返回登录数据:{}" , null != res ? res.toJSONString() : "返回结果为null"); Integer code = DataUtils.getInteger(res.get("code")); if(null == code || code.compareTo(0) != 0){ throw new CommonBaseException(ResultCodeEnum.D101 , "从四维看看获取场景码失败"); } String sceneCode = res.getString("msg"); if(StringUtils.isBlank(sceneCode)){ throw new CommonBaseException(ResultCodeEnum.D101 , "四维看看返回的场景码为空"); } return sceneCode; } }