SceneEditInfoExtServiceImpl.java 17 KB

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