SceneEditInfoExtServiceImpl.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. package com.fdkankan.scene.service.impl;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.io.FileUtil;
  4. import cn.hutool.core.util.StrUtil;
  5. import com.alibaba.fastjson.JSON;
  6. import com.alibaba.fastjson.JSONArray;
  7. import com.alibaba.fastjson.JSONObject;
  8. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  9. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  10. import com.fdkankan.common.constant.CommonStatus;
  11. import com.fdkankan.common.constant.ErrorCode;
  12. import com.fdkankan.common.constant.FileBizType;
  13. import com.fdkankan.common.exception.BusinessException;
  14. import com.fdkankan.common.util.FileUtils;
  15. import com.fdkankan.model.constants.ConstantFilePath;
  16. import com.fdkankan.model.constants.UploadFilePath;
  17. import com.fdkankan.redis.constant.RedisKey;
  18. import com.fdkankan.redis.constant.RedisLockKey;
  19. import com.fdkankan.redis.util.RedisLockUtil;
  20. import com.fdkankan.redis.util.RedisUtil;
  21. import com.fdkankan.scene.bean.TagBean;
  22. import com.fdkankan.scene.entity.SceneEditInfo;
  23. import com.fdkankan.scene.entity.SceneEditInfoExt;
  24. import com.fdkankan.scene.entity.ScenePlus;
  25. import com.fdkankan.scene.entity.ScenePlusExt;
  26. import com.fdkankan.scene.mapper.ISceneEditInfoExtMapper;
  27. import com.fdkankan.scene.oss.OssUtil;
  28. import com.fdkankan.scene.service.ISceneEditInfoExtService;
  29. import com.fdkankan.scene.service.ISceneEditInfoService;
  30. import com.fdkankan.scene.service.IScenePlusExtService;
  31. import com.fdkankan.scene.service.IScenePlusService;
  32. import com.fdkankan.scene.vo.BaseJsonArrayParamVO;
  33. import com.fdkankan.scene.vo.BaseSceneParamVO;
  34. import com.fdkankan.scene.vo.DeleteSidListParamVO;
  35. import com.fdkankan.scene.service.*;
  36. import com.fdkankan.scene.vo.*;
  37. import com.fdkankan.web.response.ResultData;
  38. import org.aspectj.apache.bcel.generic.RET;
  39. import org.springframework.beans.factory.annotation.Autowired;
  40. import org.springframework.stereotype.Service;
  41. import java.util.*;
  42. import java.util.concurrent.atomic.AtomicInteger;
  43. import java.util.stream.Collectors;
  44. /**
  45. * <p>
  46. * 服务实现类
  47. * </p>
  48. *
  49. * @author
  50. * @since 2022-03-07
  51. */
  52. @Service
  53. public class SceneEditInfoExtServiceImpl extends ServiceImpl<ISceneEditInfoExtMapper, SceneEditInfoExt> implements ISceneEditInfoExtService {
  54. @Autowired
  55. private IScenePlusService scenePlusService;
  56. @Autowired
  57. private IScenePlusExtService scenePlusExtService;
  58. @Autowired
  59. private ISceneEditInfoService sceneEditInfoService;
  60. @Autowired
  61. private RedisUtil redisUtil;
  62. @Autowired
  63. private RedisLockUtil redisLockUtil;
  64. @Autowired
  65. private OssUtil ossUtil;
  66. @Autowired
  67. private ISceneUploadService sceneUploadService;
  68. @Override
  69. public SceneEditInfoExt getByScenePlusId(long scenePlusId) {
  70. return this.getOne(new LambdaQueryWrapper<SceneEditInfoExt>().eq(SceneEditInfoExt::getScenePlusId, scenePlusId));
  71. }
  72. @Override
  73. public SceneEditInfoExt getByEditInfoId(long editInfoId) {
  74. return this.getOne(new LambdaQueryWrapper<SceneEditInfoExt>().eq(SceneEditInfoExt::getEditInfoId, editInfoId));
  75. }
  76. @Override
  77. public void updateToursByNum(String num, Integer tours) {
  78. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num);
  79. if(Objects.isNull(scenePlus)){
  80. throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
  81. }
  82. SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId());
  83. sceneEditInfoService.upgradeVersionById(sceneEditInfo.getId());
  84. SceneEditInfoExt sceneEditInfoExt = this.getByEditInfoId(sceneEditInfo.getId());
  85. sceneEditInfoExt.setTours(tours);
  86. this.updateById(sceneEditInfoExt);
  87. }
  88. @Override
  89. public ResultData saveBillboards(BaseJsonArrayParamVO param) throws Exception {
  90. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  91. this.addOrUpdateBillboards(param.getNum(), param.getData());
  92. this.addOrUpdateBillboardsStyles(param.getNum(), param.getStyles());
  93. //保存数据库
  94. SceneEditInfoExt sceneEditInfoExt = this.getByScenePlusId(scenePlus.getId());
  95. this.updateBillboards(param.getNum(), sceneEditInfoExt);
  96. // this.updateById(sceneEditInfoExt);
  97. sceneEditInfoService.upgradeVersionById(sceneEditInfoExt.getEditInfoId());
  98. return ResultData.ok();
  99. }
  100. private void addOrUpdateBillboardsStyles(String num, List<JSONObject> styles) throws Exception{
  101. this.syncBillboardsStylesFromFileToRedis(num);
  102. if(CollUtil.isEmpty(styles)){
  103. return;
  104. }
  105. long time = Calendar.getInstance().getTimeInMillis();
  106. Map<String, String> styleMap = new HashMap<>();
  107. AtomicInteger index = new AtomicInteger();
  108. styles.stream().forEach(style->{
  109. String id = style.getString("sid");
  110. style.put("createTime", time + index.getAndIncrement());
  111. styleMap.put(id, style.toJSONString());
  112. });
  113. String key = String.format(RedisKey.SCENE_BILLBOARDS_STYLES, num);
  114. redisUtil.hmset(key, styleMap);
  115. //写入本地文件,作为备份
  116. this.writeBillboardStylesJson(num);
  117. }
  118. private void syncBillboardsStylesFromFileToRedis(String num) throws Exception{
  119. String key = String.format(RedisKey.SCENE_BILLBOARDS_STYLES, num);
  120. boolean exist = redisUtil.hasKey(key);
  121. if(exist){
  122. return;
  123. }
  124. String lockKey = String.format(RedisLockKey.LOCK_BILLBOARDS_STYLES_SYNC, num);
  125. String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString();
  126. boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE);
  127. if(!lock){
  128. throw new BusinessException(ErrorCode.SYSTEM_BUSY);
  129. }
  130. try{
  131. exist = redisUtil.hasKey(key);
  132. if(exist){
  133. return;
  134. }
  135. String stylesPath = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num);
  136. String stylesData = FileUtils.readUtf8String(stylesPath + "billboards-styles.json");
  137. if(StrUtil.isEmpty(stylesData)){
  138. return;
  139. }
  140. JSONArray stylesArr = JSON.parseArray(stylesData);
  141. if(CollUtil.isEmpty(stylesArr)){
  142. return;
  143. }
  144. Map<String, String> styleMap = new HashMap<>();
  145. for (Object style : stylesArr) {
  146. JSONObject styleObj = (JSONObject)style;
  147. String id = styleObj.getString("sid");
  148. styleMap.put(id, styleObj.toJSONString());
  149. }
  150. redisUtil.hmset(key, styleMap);
  151. }finally {
  152. redisLockUtil.unlockLua(lockKey, lockVal);
  153. }
  154. }
  155. @Override
  156. public ResultData deleteBillboards(DeleteSidListParamVO param) throws Exception {
  157. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  158. ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId());
  159. String bucket = scenePlusExt.getYunFileBucket();
  160. List<String> deleteSidList = param.getSidList();
  161. this.syncBillboardsFromFileToRedis(param.getNum());
  162. //处理删除状态数据
  163. this.deleteBillboards(param.getNum(), deleteSidList, bucket);
  164. //写入本地文件,作为备份
  165. this.writeBillboardJson(param.getNum());
  166. //保存数据库
  167. SceneEditInfoExt sceneEditInfoExt = this.getByScenePlusId(scenePlus.getId());
  168. this.updateBillboards(param.getNum(), sceneEditInfoExt);
  169. sceneEditInfoService.upgradeVersionById(sceneEditInfoExt.getEditInfoId());
  170. return ResultData.ok();
  171. }
  172. @Override
  173. public JSONObject listBillboards(BaseSceneParamVO param) throws Exception {
  174. JSONObject result = new JSONObject();
  175. List<JSONObject> tags = new ArrayList<>();
  176. List<JSONObject> styles = new ArrayList<>();
  177. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  178. SceneEditInfoExt sceneEditInfoExt = this.getByScenePlusId(scenePlus.getId());
  179. this.syncBillboardsFromFileToRedis(param.getNum());
  180. //获取指示牌数据
  181. String key = String.format(RedisKey.SCENE_BILLBOARDS, param.getNum());
  182. List<String> list = redisUtil.hgetValues(key);
  183. if(CollUtil.isNotEmpty(list)){
  184. List<TagBean> sortList = list.stream().map(str -> {
  185. JSONObject jsonObject = JSON.parseObject(str);
  186. TagBean tagBean = new TagBean();
  187. tagBean.setCreateTime(jsonObject.getLong("createTime"));
  188. jsonObject.remove("createTime");
  189. tagBean.setTag(jsonObject);
  190. return tagBean;
  191. }).collect(Collectors.toList());
  192. sortList.sort(Comparator.comparingLong(TagBean::getCreateTime).reversed());
  193. tags = sortList.stream().map(item -> item.getTag()).collect(Collectors.toList());
  194. }
  195. result.put("tags", tags);
  196. //获取图标数据
  197. this.syncBillboardsStylesFromFileToRedis(param.getNum());
  198. key = String.format(RedisKey.SCENE_BILLBOARDS_STYLES, param.getNum());
  199. List<String> sytlelist = redisUtil.hgetValues(key);
  200. if(CollUtil.isNotEmpty(sytlelist)){
  201. List<TagBean> stileSortList = sytlelist.stream().map(str -> {
  202. JSONObject jsonObject = JSON.parseObject(str);
  203. TagBean tagBean = new TagBean();
  204. tagBean.setCreateTime(jsonObject.getLong("createTime"));
  205. jsonObject.remove("createTime");
  206. tagBean.setTag(jsonObject);
  207. return tagBean;
  208. }).collect(Collectors.toList());
  209. stileSortList.sort(Comparator.comparingLong(TagBean::getCreateTime).reversed());
  210. styles = stileSortList.stream().map(item -> item.getTag()).collect(Collectors.toList());
  211. }
  212. result.put("styles", styles);
  213. return result;
  214. }
  215. private void deleteBillboards(String num, List<String> deleteSidList, String bucket) throws Exception {
  216. if(CollUtil.isEmpty(deleteSidList)){
  217. return;
  218. }
  219. //从redis中加载热点数据
  220. String key = String.format(RedisKey.SCENE_BILLBOARDS, num);
  221. List<String> deletDataList = redisUtil.hMultiGet(key, deleteSidList);
  222. if(CollUtil.isEmpty(deletDataList))
  223. return;
  224. //从redis中移除热点数据
  225. redisUtil.hdel(key, deleteSidList.toArray());
  226. }
  227. private void addOrUpdateBillboards(String num, List<JSONObject> data) throws Exception{
  228. Map<String, String> addOrUpdateMap = new HashMap<>();
  229. int i = 0;
  230. for (JSONObject jsonObject : data) {
  231. jsonObject.put("createTime", Calendar.getInstance().getTimeInMillis() + i++);
  232. addOrUpdateMap.put(jsonObject.getString("sid"), JSON.toJSONString(jsonObject));
  233. }
  234. this.syncBillboardsFromFileToRedis(num);
  235. //处理新增和修改数据
  236. this.addOrUpdateBillboardsHandler(num, addOrUpdateMap);
  237. }
  238. /**
  239. * <p>
  240. 保证指示牌数据安全性,当redis宕机导致热点数据丢失时,可以从文件中读取,恢复到redis
  241. * </p>
  242. * @author dengsixing
  243. * @date 2022/3/3
  244. **/
  245. private void syncBillboardsFromFileToRedis(String num) throws Exception{
  246. String key = String.format(RedisKey.SCENE_BILLBOARDS, num);
  247. boolean exist = redisUtil.hasKey(key);
  248. if(exist){
  249. return;
  250. }
  251. String lockKey = String.format(RedisLockKey.LOCK_BILLBOARDS_SYNC, num);
  252. String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString();
  253. boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE);
  254. if(!lock){
  255. throw new BusinessException(ErrorCode.SYSTEM_BUSY);
  256. }
  257. try{
  258. exist = redisUtil.hasKey(key);
  259. if(exist){
  260. return;
  261. }
  262. String billboardsFilePath = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num) + "billboards.json";
  263. String billboardsData = FileUtils.readUtf8String(billboardsFilePath);
  264. if(StrUtil.isEmpty(billboardsData)){
  265. return;
  266. }
  267. JSONArray tagsArr = JSON.parseArray(billboardsData);
  268. if(CollUtil.isEmpty(tagsArr)){
  269. return;
  270. }
  271. Map<String, String> map = new HashMap<>();
  272. for (Object o : tagsArr) {
  273. JSONObject jo = (JSONObject)o;
  274. map.put(jo.getString("sid"), jo.toJSONString());
  275. }
  276. redisUtil.hmset(key, map);
  277. }finally {
  278. redisLockUtil.unlockLua(lockKey, lockVal);
  279. }
  280. }
  281. private void addOrUpdateBillboardsHandler(String num, Map<String, String> addOrUpdateMap){
  282. if(CollUtil.isEmpty(addOrUpdateMap))
  283. return;
  284. //批量写入缓存
  285. String key = String.format(RedisKey.SCENE_BILLBOARDS, num);
  286. redisUtil.hmset(key, addOrUpdateMap);
  287. //写入本地文件,作为备份
  288. this.writeBillboardJson(num);
  289. }
  290. /**
  291. * <p>
  292. 热点数据保存
  293. * </p>
  294. * @author dengsixing
  295. * @date 2022/3/3
  296. *
  297. **/
  298. private void writeBillboardJson(String num){
  299. String lockKey = String.format(RedisLockKey.LOCK_BILLBOARDS_SYNC, num);
  300. String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString();
  301. boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE);
  302. if(!lock){
  303. return;
  304. }
  305. try{
  306. String dataKey = String.format(RedisKey.SCENE_BILLBOARDS, num);
  307. String hotJsonPath = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num) + "billboards.json";
  308. if(!redisUtil.hasKey(dataKey)){
  309. FileUtil.del(hotJsonPath);
  310. return;
  311. }
  312. Map<String, String> billboardMap = redisUtil.hmget(dataKey);
  313. List<JSONObject> billboardList = billboardMap.entrySet().stream().map(entry->JSON.parseObject(entry.getValue())).collect(Collectors.toList());
  314. FileUtil.writeUtf8String(JSON.toJSONString(billboardList), hotJsonPath);
  315. }finally {
  316. redisLockUtil.unlockLua(lockKey, lockVal);
  317. }
  318. }
  319. private void writeBillboardStylesJson(String num){
  320. String lockKey = String.format(RedisLockKey.LOCK_BILLBOARDS_STYLES_SYNC, num);
  321. String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString();
  322. boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE);
  323. if(!lock){
  324. return;
  325. }
  326. try{
  327. String dataKey = String.format(RedisKey.SCENE_BILLBOARDS_STYLES, num);
  328. String stylesPath = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num) + "billboards-styles.json";
  329. if(!redisUtil.hasKey(dataKey)){
  330. FileUtil.del(stylesPath);
  331. return;
  332. }
  333. Map<String, String> billboardStylesMap = redisUtil.hmget(dataKey);
  334. List<JSONObject> billboardStyleList = billboardStylesMap.entrySet().stream().map(entry->JSON.parseObject(entry.getValue())).collect(Collectors.toList());
  335. FileUtil.writeUtf8String(JSON.toJSONString(billboardStyleList), stylesPath);
  336. }finally {
  337. redisLockUtil.unlockLua(lockKey, lockVal);
  338. }
  339. }
  340. private void updateBillboards(String num, SceneEditInfoExt sceneEditInfoExt){
  341. //查询缓存是否包含热点数据
  342. String key = String.format(RedisKey.SCENE_BILLBOARDS, num);
  343. Map<String, String> billboardsMap = redisUtil.hmget(key);
  344. boolean hashBillboards= false;
  345. for (Map.Entry<String, String> tagMap : billboardsMap.entrySet()) {
  346. if(StrUtil.isEmpty(tagMap.getValue())){
  347. continue;
  348. }
  349. hashBillboards = true;
  350. break;
  351. }
  352. //更改热点状态
  353. sceneEditInfoExt.setBillboards(hashBillboards ? CommonStatus.YES.code().intValue() : CommonStatus.NO.code().intValue());
  354. this.updateById(sceneEditInfoExt);
  355. }
  356. @Override
  357. public ResultData deleteBillboardsStyles(DeleteStylesParamVO param) throws Exception {
  358. ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum());
  359. if (scenePlus == null)
  360. throw new BusinessException(ErrorCode.FAILURE_CODE_5005);
  361. List<String> sidList = param.getSidList();
  362. this.syncBillboardsStylesFromFileToRedis(param.getNum());
  363. String key = String.format(RedisKey.SCENE_BILLBOARDS_STYLES, param.getNum());
  364. List<String> deleteList = redisUtil.hMultiGet(key, sidList);
  365. redisUtil.hdel(key, sidList.toArray());
  366. //写入本地文件,作为备份
  367. this.writeBillboardStylesJson(param.getNum());
  368. //删除oss文件
  369. List<String> deleteFileList = deleteList.stream().map(str -> {
  370. JSONObject parse = JSON.parseObject(str);
  371. return parse.getString("url");
  372. }).collect(Collectors.toList());
  373. sceneUploadService.delete(
  374. DeleteFileParamVO.builder()
  375. .num(param.getNum())
  376. .fileNames(deleteFileList)
  377. .bizType(FileBizType.BILLBOARD_ICON.code()).build());
  378. return ResultData.ok();
  379. }
  380. }