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);
}
}
}