NjsSceneBoxModelServiceImpl.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. package com.fdkankan.scene.service.impl;
  2. import cn.hutool.core.bean.BeanUtil;
  3. import cn.hutool.core.collection.CollUtil;
  4. import cn.hutool.core.exceptions.ExceptionUtil;
  5. import cn.hutool.core.io.FileUtil;
  6. import cn.hutool.core.lang.UUID;
  7. import cn.hutool.core.util.CharsetUtil;
  8. import cn.hutool.core.util.StrUtil;
  9. import cn.hutool.core.util.ZipUtil;
  10. import com.alibaba.fastjson.JSON;
  11. import com.alibaba.fastjson.JSONArray;
  12. import com.alibaba.fastjson.JSONObject;
  13. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  14. import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  15. import com.fdkankan.common.constant.CommonStatus;
  16. import com.fdkankan.common.constant.CommonSuccessStatus;
  17. import com.fdkankan.common.constant.ErrorCode;
  18. import com.fdkankan.common.constant.ServerCode;
  19. import com.fdkankan.common.exception.BusinessException;
  20. import com.fdkankan.common.util.DateExtUtil;
  21. import com.fdkankan.common.util.FileUtils;
  22. import com.fdkankan.common.util.OBJToGLBUtil;
  23. import com.fdkankan.fyun.face.FYunFileServiceInterface;
  24. import com.fdkankan.model.constants.ConstantFilePath;
  25. import com.fdkankan.model.constants.UploadFilePath;
  26. import com.fdkankan.model.utils.ComputerUtil;
  27. import com.fdkankan.rabbitmq.util.RabbitMqProducer;
  28. import com.fdkankan.scene.constant.BoxModelType;
  29. import com.fdkankan.scene.constant.NjsErrorCode;
  30. import com.fdkankan.scene.dto.ExportBoxModelDTO;
  31. import com.fdkankan.scene.dto.NjsBoxModelDTO;
  32. import com.fdkankan.scene.dto.SaveBoxModelDTO;
  33. import com.fdkankan.scene.entity.NjsSceneBoxModel;
  34. import com.fdkankan.scene.entity.SceneEditInfo;
  35. import com.fdkankan.scene.entity.ScenePlus;
  36. import com.fdkankan.scene.entity.ScenePlusExt;
  37. import com.fdkankan.scene.mapper.INjsSceneBoxModelMapper;
  38. import com.fdkankan.scene.service.INjsSceneBoxModelService;
  39. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  40. import com.fdkankan.scene.service.ISceneEditInfoService;
  41. import com.fdkankan.scene.service.IScenePlusExtService;
  42. import com.fdkankan.scene.service.IScenePlusService;
  43. import com.fdkankan.scene.vo.BaseJsonDataParamVO;
  44. import com.fdkankan.scene.vo.BaseSceneParamVO;
  45. import com.fdkankan.scene.vo.BathUpdateNjsBoxModelParamVO;
  46. import com.fdkankan.scene.vo.DeleteSidListParamVO;
  47. import com.fdkankan.web.response.ResultData;
  48. import lombok.extern.slf4j.Slf4j;
  49. import org.springframework.beans.factory.annotation.Autowired;
  50. import org.springframework.stereotype.Service;
  51. import org.springframework.web.multipart.MultipartFile;
  52. import javax.annotation.Resource;
  53. import java.io.File;
  54. import java.io.IOException;
  55. import java.nio.charset.StandardCharsets;
  56. import java.time.Instant;
  57. import java.util.*;
  58. import java.util.stream.Collectors;
  59. /**
  60. * <p>
  61. * NJS-模型编辑器数据表 服务实现类
  62. * </p>
  63. *
  64. * @author
  65. * @since 2026-01-09
  66. */
  67. @Slf4j
  68. @Service
  69. public class NjsSceneBoxModelServiceImpl extends ServiceImpl<INjsSceneBoxModelMapper, NjsSceneBoxModel> implements INjsSceneBoxModelService {
  70. @Resource
  71. private RabbitMqProducer mqProducer;
  72. @Resource
  73. private FYunFileServiceInterface fYunFileService;
  74. @Autowired
  75. private IScenePlusService scenePlusService;
  76. @Autowired
  77. private ISceneEditInfoService sceneEditInfoService;
  78. @Autowired
  79. private IScenePlusExtService scenePlusExtService;
  80. @Override
  81. public NjsSceneBoxModel getBySid(String num, String sid) {
  82. return lambdaQuery().eq(NjsSceneBoxModel::getNum, num).eq(NjsSceneBoxModel::getSid, sid).one();
  83. }
  84. @Override
  85. public void saveBoxModel(BaseJsonDataParamVO dto) {
  86. JSONObject data = dto.getData();
  87. String sid = data.getString("sid");
  88. if(StrUtil.isEmpty(sid)){
  89. throw new BusinessException(ServerCode.PARAM_REQUIRED, "sid");
  90. }
  91. NjsSceneBoxModel bean = this.getBySid(dto.getNum(), sid);
  92. if(bean == null){
  93. bean = BeanUtil.toBean(dto, NjsSceneBoxModel.class);
  94. bean.setSid(sid);
  95. }else{
  96. bean.setData(dto.getData());
  97. bean.setUpdateTime(null);
  98. }
  99. this.saveOrUpdate(bean);
  100. }
  101. @Override
  102. public List<JSONObject> listNjsBoxModel(BaseSceneParamVO param) {
  103. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  104. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  105. List<JSONObject> results = new ArrayList<>();
  106. if(StrUtil.isNotEmpty(sceneEditInfo.getBoxModels())){
  107. List<JSONObject> boxModels = JSON.parseArray(
  108. sceneEditInfo.getBoxModels(),
  109. JSONObject.class
  110. );
  111. boxModels.stream().forEach(v->v.put("sourceType", "mesh"));
  112. results.addAll(boxModels);
  113. }
  114. List<NjsSceneBoxModel> list = lambdaQuery().eq(NjsSceneBoxModel::getNum, param.getNum()).list();
  115. if(CollUtil.isNotEmpty(list)){
  116. List<JSONObject> njsBoxModels = list.stream().map(v->{
  117. JSONObject data = v.getData();
  118. data.put("createTime", v.getCreateTime().getTime());
  119. data.put("sourceType", "model");
  120. return data;
  121. }).collect(Collectors.toList());
  122. results.addAll(njsBoxModels);
  123. }
  124. if(CollUtil.isEmpty(results)){
  125. return null;
  126. }
  127. results.sort(Comparator.comparing(
  128. o -> ((JSONObject)o).getLong("createTime"),
  129. Comparator.nullsLast(Long::compareTo)
  130. ).reversed());
  131. return results;
  132. }
  133. @Override
  134. public void batchVisible(BathUpdateNjsBoxModelParamVO param) {
  135. LambdaQueryWrapper<NjsSceneBoxModel> wrapper = new LambdaQueryWrapper();
  136. wrapper.eq(NjsSceneBoxModel::getNum, param.getNum());
  137. wrapper.in(NjsSceneBoxModel::getSid, param.getSidList());
  138. List<NjsSceneBoxModel> list = this.list(wrapper);
  139. if(list.isEmpty()){
  140. return;
  141. }
  142. list.stream().forEach(v->{
  143. JSONObject data = v.getData();
  144. if(param.getVisible() != null){
  145. data.put("visible", param.getVisible());
  146. }
  147. if(param.getVisibleSize() != null){
  148. data.put("visibleSize", param.getVisibleSize());
  149. }
  150. v.setUpdateTime(null);
  151. });
  152. this.updateBatchById(list);
  153. }
  154. @Override
  155. public void batchDelete(BathUpdateNjsBoxModelParamVO param) throws IOException {
  156. List<NjsSceneBoxModel> list = lambdaQuery().eq(NjsSceneBoxModel::getNum, param.getNum()).in(NjsSceneBoxModel::getSid, param.getSidList()).list();
  157. if(list.isEmpty()){
  158. return;
  159. }
  160. lambdaUpdate().eq(NjsSceneBoxModel::getNum, param.getNum()).in(NjsSceneBoxModel::getSid, param.getSidList()).remove();
  161. //删除文件
  162. for (NjsSceneBoxModel njsSceneBoxModel : list) {
  163. String ossDir = String.format(UploadFilePath.USER_EDIT_PATH, njsSceneBoxModel.getNum()) + "njsBoxModel" + File.separator + njsSceneBoxModel.getSid();
  164. String key = ossDir + ".glb";
  165. if(fYunFileService.fileExist(key)){
  166. fYunFileService.deleteFile(key);
  167. }
  168. if(fYunFileService.fileExist(ossDir)){
  169. fYunFileService.deleteFolder(ossDir);
  170. }
  171. }
  172. }
  173. // @Override
  174. // public void objToGlb(JSONObject param) throws Exception {
  175. // Long id = param.getLong("id");
  176. // NjsSceneBoxModel njsSceneBoxModel = this.getById(id);
  177. // if(njsSceneBoxModel == null){
  178. // return;
  179. // }
  180. // String uuid = UUID.randomUUID().toString();
  181. // String origKey = njsSceneBoxModel.getOrigKey();
  182. // String workDir = String.format(ConstantFilePath.SCENE_USER_PATH_V4, njsSceneBoxModel.getNum()) + "objToGlb" + File.separator + uuid + File.separator;
  183. // String srcPath = workDir + "src" + File.separator;
  184. // try {
  185. // FileUtil.mkdir(srcPath);
  186. // String zipPath = workDir + FileUtil.getName(origKey);
  187. // fYunFileService.downloadFile(origKey, zipPath);
  188. // //解压
  189. // ZipUtil.unzip(zipPath, srcPath, CharsetUtil.CHARSET_GBK);
  190. // //转glb
  191. // String glbPath = workDir + uuid + ".glb";
  192. // OBJToGLBUtil.objToGlb(srcPath, glbPath);
  193. // //上传glb
  194. // if(!ComputerUtil.checkComputeCompleted(glbPath, 5, 200)){
  195. // throw new RuntimeException("转glb失败");
  196. // }
  197. // String ossDir = String.format(UploadFilePath.USER_EDIT_PATH, njsSceneBoxModel.getNum()) + "njsBoxModel" + File.separator + njsSceneBoxModel.getSid();
  198. // String key = ossDir + ".glb";
  199. // //上传结果文件
  200. // fYunFileService.uploadFile(glbPath, key);
  201. // njsSceneBoxModel.setGlbKey(key);
  202. // //上传源文件
  203. // List<File> files = FileUtil.loopFiles(srcPath);
  204. // files.stream().forEach(v->{
  205. // String ossKey = v.getAbsolutePath().replace(srcPath, ossDir + File.separator);
  206. // fYunFileService.uploadFile(v.getAbsolutePath(), ossKey);
  207. // });
  208. // njsSceneBoxModel.setStatus(CommonSuccessStatus.SUCCESS.code());
  209. // }catch (Exception e){
  210. // log.error("模型编辑器-模型转换失败, num:{}, sid:{}", njsSceneBoxModel.getNum(), njsSceneBoxModel.getSid(), e);
  211. // njsSceneBoxModel.setReason(ExceptionUtil.stacktraceToString(e, 3000));
  212. // njsSceneBoxModel.setStatus(CommonSuccessStatus.FAIL.code());
  213. // }finally{
  214. // this.updateById(njsSceneBoxModel);
  215. // //删除文件
  216. // fYunFileService.deleteFile(origKey);
  217. // FileUtil.del(workDir);
  218. // }
  219. // }
  220. @Override
  221. public void publicNjsSceneBoxModel(String num) throws IOException {
  222. List<NjsSceneBoxModel> list = lambdaQuery().eq(NjsSceneBoxModel::getNum, num).list();
  223. String njsBoxModelKey = String.format(UploadFilePath.USER_EDIT_PATH, num) + "njsBoxModels.json";
  224. String njsBoxModelDir = String.format(UploadFilePath.USER_EDIT_PATH, num) + "njsBoxModel";
  225. if(list.isEmpty()){
  226. if(fYunFileService.fileExist(njsBoxModelKey)){
  227. fYunFileService.deleteFile(njsBoxModelKey);
  228. }
  229. if(fYunFileService.fileExist(njsBoxModelDir)){
  230. fYunFileService.deleteFile(njsBoxModelDir);
  231. }
  232. return;
  233. }
  234. //删除无效文件
  235. Set<String> prefixList = list.stream().map(v -> njsBoxModelDir + "/" + v.getData().getString("sid")).collect(Collectors.toSet());
  236. List<String> keyList = fYunFileService.listRemoteFiles(njsBoxModelDir);
  237. if(!keyList.isEmpty()){
  238. keyList.removeIf(file ->
  239. prefixList.stream().anyMatch(file::startsWith)
  240. );
  241. if(!keyList.isEmpty()){
  242. keyList.stream().forEach(key -> {
  243. try {
  244. fYunFileService.deleteFile(key);
  245. } catch (IOException e) {
  246. throw new RuntimeException(e);
  247. }
  248. });
  249. }
  250. }
  251. List<JSONObject> collect = list.stream().map(v -> v.getData()).collect(Collectors.toList());
  252. fYunFileService.uploadFile(JSON.toJSONString(collect).getBytes(StandardCharsets.UTF_8), njsBoxModelKey);
  253. }
  254. public static void main(String[] args) {
  255. List<String> prefixes = Arrays.asList(
  256. "img/",
  257. "video/2025/",
  258. "model/mesh/"
  259. );
  260. List<String> files = new ArrayList<>(Arrays.asList(
  261. "img/a.png",
  262. "img/b.jpg",
  263. "tmp/c.txt",
  264. "video/2024/d.mp4",
  265. "model/mesh/x.obj"
  266. ));
  267. files.removeIf(file ->
  268. prefixes.stream().anyMatch(file::startsWith)
  269. );
  270. System.out.println(files);
  271. }
  272. @Override
  273. public ResultData uploadBoxModel(String num, String sid, MultipartFile file) throws Exception {
  274. if(!file.getOriginalFilename().endsWith(".zip")){
  275. throw new BusinessException(ErrorCode.FAILURE_CODE_7015);
  276. }
  277. if(!FileUtils.checkFileSizeIsLimit(file.getSize(), 100, "M")){
  278. throw new BusinessException(ErrorCode.FAILURE_CODE_7023, "100M");
  279. }
  280. String path = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num) + "njsBoxModel/" + sid + "/";
  281. try {
  282. String zipPath = path + file.getOriginalFilename();
  283. String srcPath = path + "data/";
  284. String glbPath = path + sid + ".glb";
  285. FileUtil.del(path);
  286. FileUtil.mkParentDirs(zipPath);
  287. file.transferTo(new File(zipPath));
  288. //解压
  289. ZipUtil.unzip(zipPath,srcPath, CharsetUtil.CHARSET_GBK);
  290. //校验是否包含目录,如果包含目录提示错误
  291. File srcFile = new File(srcPath);
  292. Arrays.stream(srcFile.listFiles()).forEach(subFile->{
  293. if(subFile.isDirectory()){
  294. throw new BusinessException(ErrorCode.FAILURE_CODE_5065);
  295. }
  296. });
  297. //转glb
  298. OBJToGLBUtil.objToGlb(srcPath, glbPath);
  299. if(!ComputerUtil.checkComputeCompleted(glbPath, 10, 2000)){
  300. throw new BusinessException(ErrorCode.FAILURE_CODE_7013);
  301. }
  302. //上传glb
  303. fYunFileService.uploadFile(glbPath, String.format(UploadFilePath.USER_EDIT_PATH, num) + "njsBoxModel/" + sid + ".glb");
  304. //上传原始obj相关文件
  305. String objPath = String.format(UploadFilePath.USER_EDIT_PATH, num) + "njsBoxModel/" + sid + "/";
  306. //先删除旧的,只需要保留最新的
  307. fYunFileService.deleteFolder(objPath);
  308. List<File> files = FileUtil.loopFiles(srcPath);
  309. files.stream().forEach(v->{
  310. String ossKey = String.format(UploadFilePath.USER_EDIT_PATH, num) + "njsBoxModel/" + sid + "/" + v.getAbsolutePath().replace(srcPath, "");
  311. fYunFileService.uploadFile(v.getAbsolutePath(), ossKey);
  312. });
  313. return ResultData.ok(sid + ".glb");
  314. }catch (Exception e){
  315. log.error("上传三维模型失败", e);
  316. throw e;
  317. }finally {
  318. FileUtil.del(path);
  319. }
  320. }
  321. }