package com.fdkankan.scene.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.UUID; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.ZipUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.fdkankan.common.constant.CommonStatus; import com.fdkankan.common.constant.CommonSuccessStatus; import com.fdkankan.common.constant.ErrorCode; import com.fdkankan.common.constant.ServerCode; import com.fdkankan.common.exception.BusinessException; import com.fdkankan.common.util.DateExtUtil; import com.fdkankan.common.util.FileUtils; import com.fdkankan.common.util.OBJToGLBUtil; import com.fdkankan.fyun.face.FYunFileServiceInterface; import com.fdkankan.model.constants.ConstantFilePath; import com.fdkankan.model.constants.UploadFilePath; import com.fdkankan.model.utils.ComputerUtil; import com.fdkankan.rabbitmq.util.RabbitMqProducer; import com.fdkankan.scene.constant.BoxModelType; import com.fdkankan.scene.constant.NjsErrorCode; import com.fdkankan.scene.dto.ExportBoxModelDTO; import com.fdkankan.scene.dto.NjsBoxModelDTO; import com.fdkankan.scene.dto.SaveBoxModelDTO; import com.fdkankan.scene.entity.NjsSceneBoxModel; import com.fdkankan.scene.entity.SceneEditInfo; import com.fdkankan.scene.entity.ScenePlus; import com.fdkankan.scene.entity.ScenePlusExt; import com.fdkankan.scene.mapper.INjsSceneBoxModelMapper; import com.fdkankan.scene.service.INjsSceneBoxModelService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.fdkankan.scene.service.ISceneEditInfoService; import com.fdkankan.scene.service.IScenePlusExtService; import com.fdkankan.scene.service.IScenePlusService; import com.fdkankan.scene.vo.BaseJsonDataParamVO; import com.fdkankan.scene.vo.BaseSceneParamVO; import com.fdkankan.scene.vo.BathUpdateNjsBoxModelParamVO; import com.fdkankan.scene.vo.DeleteSidListParamVO; import com.fdkankan.web.response.ResultData; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.*; import java.util.stream.Collectors; /** *

* NJS-模型编辑器数据表 服务实现类 *

* * @author * @since 2026-01-09 */ @Slf4j @Service public class NjsSceneBoxModelServiceImpl extends ServiceImpl implements INjsSceneBoxModelService { @Resource private RabbitMqProducer mqProducer; @Resource private FYunFileServiceInterface fYunFileService; @Autowired private IScenePlusService scenePlusService; @Autowired private ISceneEditInfoService sceneEditInfoService; @Autowired private IScenePlusExtService scenePlusExtService; @Override public NjsSceneBoxModel getBySid(String num, String sid) { return lambdaQuery().eq(NjsSceneBoxModel::getNum, num).eq(NjsSceneBoxModel::getSid, sid).one(); } @Override public void saveBoxModel(BaseJsonDataParamVO dto) { JSONObject data = dto.getData(); String sid = data.getString("sid"); if(StrUtil.isEmpty(sid)){ throw new BusinessException(ServerCode.PARAM_REQUIRED, "sid"); } NjsSceneBoxModel bean = this.getBySid(dto.getNum(), sid); if(bean == null){ bean = BeanUtil.toBean(dto, NjsSceneBoxModel.class); bean.setSid(sid); }else{ bean.setData(dto.getData()); bean.setUpdateTime(null); } this.saveOrUpdate(bean); } @Override public List listNjsBoxModel(BaseSceneParamVO param) { ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum()); SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId()); List results = new ArrayList<>(); if(StrUtil.isNotEmpty(sceneEditInfo.getBoxModels())){ List boxModels = JSON.parseArray( sceneEditInfo.getBoxModels(), JSONObject.class ); boxModels.stream().forEach(v->v.put("sourceType", "mesh")); results.addAll(boxModels); } List list = lambdaQuery().eq(NjsSceneBoxModel::getNum, param.getNum()).list(); if(CollUtil.isNotEmpty(list)){ List njsBoxModels = list.stream().map(v->{ JSONObject data = v.getData(); data.put("createTime", v.getCreateTime().getTime()); data.put("sourceType", "model"); return data; }).collect(Collectors.toList()); results.addAll(njsBoxModels); } if(CollUtil.isEmpty(results)){ return null; } results.sort(Comparator.comparing( o -> ((JSONObject)o).getLong("createTime"), Comparator.nullsLast(Long::compareTo) ).reversed()); return results; } @Override public void batchVisible(BathUpdateNjsBoxModelParamVO param) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper(); wrapper.eq(NjsSceneBoxModel::getNum, param.getNum()); wrapper.in(NjsSceneBoxModel::getSid, param.getSidList()); List list = this.list(wrapper); if(list.isEmpty()){ return; } list.stream().forEach(v->{ JSONObject data = v.getData(); if(param.getVisible() != null){ data.put("visible", param.getVisible()); } if(param.getVisibleSize() != null){ data.put("visibleSize", param.getVisibleSize()); } v.setUpdateTime(null); }); this.updateBatchById(list); } @Override public void batchDelete(BathUpdateNjsBoxModelParamVO param) throws IOException { List list = lambdaQuery().eq(NjsSceneBoxModel::getNum, param.getNum()).in(NjsSceneBoxModel::getSid, param.getSidList()).list(); if(list.isEmpty()){ return; } lambdaUpdate().eq(NjsSceneBoxModel::getNum, param.getNum()).in(NjsSceneBoxModel::getSid, param.getSidList()).remove(); //删除文件 for (NjsSceneBoxModel njsSceneBoxModel : list) { String ossDir = String.format(UploadFilePath.USER_EDIT_PATH, njsSceneBoxModel.getNum()) + "njsBoxModel" + File.separator + njsSceneBoxModel.getSid(); String key = ossDir + ".glb"; if(fYunFileService.fileExist(key)){ fYunFileService.deleteFile(key); } if(fYunFileService.fileExist(ossDir)){ fYunFileService.deleteFolder(ossDir); } } } // @Override // public void objToGlb(JSONObject param) throws Exception { // Long id = param.getLong("id"); // NjsSceneBoxModel njsSceneBoxModel = this.getById(id); // if(njsSceneBoxModel == null){ // return; // } // String uuid = UUID.randomUUID().toString(); // String origKey = njsSceneBoxModel.getOrigKey(); // String workDir = String.format(ConstantFilePath.SCENE_USER_PATH_V4, njsSceneBoxModel.getNum()) + "objToGlb" + File.separator + uuid + File.separator; // String srcPath = workDir + "src" + File.separator; // try { // FileUtil.mkdir(srcPath); // String zipPath = workDir + FileUtil.getName(origKey); // fYunFileService.downloadFile(origKey, zipPath); // //解压 // ZipUtil.unzip(zipPath, srcPath, CharsetUtil.CHARSET_GBK); // //转glb // String glbPath = workDir + uuid + ".glb"; // OBJToGLBUtil.objToGlb(srcPath, glbPath); // //上传glb // if(!ComputerUtil.checkComputeCompleted(glbPath, 5, 200)){ // throw new RuntimeException("转glb失败"); // } // String ossDir = String.format(UploadFilePath.USER_EDIT_PATH, njsSceneBoxModel.getNum()) + "njsBoxModel" + File.separator + njsSceneBoxModel.getSid(); // String key = ossDir + ".glb"; // //上传结果文件 // fYunFileService.uploadFile(glbPath, key); // njsSceneBoxModel.setGlbKey(key); // //上传源文件 // List files = FileUtil.loopFiles(srcPath); // files.stream().forEach(v->{ // String ossKey = v.getAbsolutePath().replace(srcPath, ossDir + File.separator); // fYunFileService.uploadFile(v.getAbsolutePath(), ossKey); // }); // njsSceneBoxModel.setStatus(CommonSuccessStatus.SUCCESS.code()); // }catch (Exception e){ // log.error("模型编辑器-模型转换失败, num:{}, sid:{}", njsSceneBoxModel.getNum(), njsSceneBoxModel.getSid(), e); // njsSceneBoxModel.setReason(ExceptionUtil.stacktraceToString(e, 3000)); // njsSceneBoxModel.setStatus(CommonSuccessStatus.FAIL.code()); // }finally{ // this.updateById(njsSceneBoxModel); // //删除文件 // fYunFileService.deleteFile(origKey); // FileUtil.del(workDir); // } // } @Override public void publicNjsSceneBoxModel(String num) throws IOException { List list = lambdaQuery().eq(NjsSceneBoxModel::getNum, num).list(); String njsBoxModelKey = String.format(UploadFilePath.USER_EDIT_PATH, num) + "njsBoxModels.json"; String njsBoxModelDir = String.format(UploadFilePath.USER_EDIT_PATH, num) + "njsBoxModel"; if(list.isEmpty()){ if(fYunFileService.fileExist(njsBoxModelKey)){ fYunFileService.deleteFile(njsBoxModelKey); } if(fYunFileService.fileExist(njsBoxModelDir)){ fYunFileService.deleteFile(njsBoxModelDir); } return; } //删除无效文件 Set prefixList = list.stream().map(v -> njsBoxModelDir + "/" + v.getData().getString("sid")).collect(Collectors.toSet()); List keyList = fYunFileService.listRemoteFiles(njsBoxModelDir); if(!keyList.isEmpty()){ keyList.removeIf(file -> prefixList.stream().anyMatch(file::startsWith) ); if(!keyList.isEmpty()){ keyList.stream().forEach(key -> { try { fYunFileService.deleteFile(key); } catch (IOException e) { throw new RuntimeException(e); } }); } } List collect = list.stream().map(v -> v.getData()).collect(Collectors.toList()); fYunFileService.uploadFile(JSON.toJSONString(collect).getBytes(StandardCharsets.UTF_8), njsBoxModelKey); } public static void main(String[] args) { List prefixes = Arrays.asList( "img/", "video/2025/", "model/mesh/" ); List files = new ArrayList<>(Arrays.asList( "img/a.png", "img/b.jpg", "tmp/c.txt", "video/2024/d.mp4", "model/mesh/x.obj" )); files.removeIf(file -> prefixes.stream().anyMatch(file::startsWith) ); System.out.println(files); } @Override public ResultData uploadBoxModel(String num, String sid, MultipartFile file) throws Exception { if(!file.getOriginalFilename().endsWith(".zip")){ throw new BusinessException(ErrorCode.FAILURE_CODE_7015); } if(!FileUtils.checkFileSizeIsLimit(file.getSize(), 100, "M")){ throw new BusinessException(ErrorCode.FAILURE_CODE_7023, "100M"); } String path = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num) + "njsBoxModel/" + sid + "/"; try { String zipPath = path + file.getOriginalFilename(); String srcPath = path + "data/"; String glbPath = path + sid + ".glb"; FileUtil.del(path); FileUtil.mkParentDirs(zipPath); file.transferTo(new File(zipPath)); //解压 ZipUtil.unzip(zipPath,srcPath, CharsetUtil.CHARSET_GBK); //校验是否包含目录,如果包含目录提示错误 File srcFile = new File(srcPath); Arrays.stream(srcFile.listFiles()).forEach(subFile->{ if(subFile.isDirectory()){ throw new BusinessException(ErrorCode.FAILURE_CODE_5065); } }); //转glb OBJToGLBUtil.objToGlb(srcPath, glbPath); if(!ComputerUtil.checkComputeCompleted(glbPath, 10, 2000)){ throw new BusinessException(ErrorCode.FAILURE_CODE_7013); } //上传glb fYunFileService.uploadFile(glbPath, String.format(UploadFilePath.USER_EDIT_PATH, num) + "njsBoxModel/" + sid + ".glb"); //上传原始obj相关文件 String objPath = String.format(UploadFilePath.USER_EDIT_PATH, num) + "njsBoxModel/" + sid + "/"; //先删除旧的,只需要保留最新的 fYunFileService.deleteFolder(objPath); List files = FileUtil.loopFiles(srcPath); files.stream().forEach(v->{ String ossKey = String.format(UploadFilePath.USER_EDIT_PATH, num) + "njsBoxModel/" + sid + "/" + v.getAbsolutePath().replace(srcPath, ""); fYunFileService.uploadFile(v.getAbsolutePath(), ossKey); }); return ResultData.ok(sid + ".glb"); }catch (Exception e){ log.error("上传三维模型失败", e); throw e; }finally { FileUtil.del(path); } } }