SceneEditInfoExtServiceImpl.java 17 KB

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