package com.fdkankan.scene.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; 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.extension.service.impl.ServiceImpl; import com.fdkankan.common.constant.CommonStatus; import com.fdkankan.common.constant.ErrorCode; import com.fdkankan.common.constant.FileBizType; import com.fdkankan.common.exception.BusinessException; import com.fdkankan.common.util.FileUtils; import com.fdkankan.fyun.face.FYunFileServiceInterface; import com.fdkankan.model.constants.ConstantFilePath; import com.fdkankan.model.constants.UploadFilePath; import com.fdkankan.redis.constant.RedisKey; import com.fdkankan.redis.constant.RedisLockKey; import com.fdkankan.redis.util.RedisLockUtil; import com.fdkankan.redis.util.RedisUtil; import com.fdkankan.scene.bean.TagBean; import com.fdkankan.scene.entity.SceneEditInfo; import com.fdkankan.scene.entity.SceneEditInfoExt; import com.fdkankan.scene.entity.ScenePlus; import com.fdkankan.scene.entity.ScenePlusExt; import com.fdkankan.scene.mapper.ISceneEditInfoExtMapper; import com.fdkankan.scene.service.*; import com.fdkankan.scene.vo.*; import com.fdkankan.web.response.ResultData; import org.aspectj.apache.bcel.generic.RET; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; /** *

* 服务实现类 *

* * @author * @since 2022-03-07 */ @Service public class SceneEditInfoExtServiceImpl extends ServiceImpl implements ISceneEditInfoExtService { @Autowired private IScenePlusService scenePlusService; @Autowired private IScenePlusExtService scenePlusExtService; @Autowired private ISceneEditInfoService sceneEditInfoService; @Autowired private RedisUtil redisUtil; @Autowired private RedisLockUtil redisLockUtil; @Autowired private FYunFileServiceInterface fYunFileService; @Autowired private ISceneUploadService sceneUploadService; @Override public SceneEditInfoExt getByScenePlusId(long scenePlusId) { return this.getOne(new LambdaQueryWrapper().eq(SceneEditInfoExt::getScenePlusId, scenePlusId)); } @Override public SceneEditInfoExt getByEditInfoId(long editInfoId) { return this.getOne(new LambdaQueryWrapper().eq(SceneEditInfoExt::getEditInfoId, editInfoId)); } @Override public void updateToursByNum(String num, Integer tours) { ScenePlus scenePlus = scenePlusService.getScenePlusByNum(num); if(Objects.isNull(scenePlus)){ throw new BusinessException(ErrorCode.FAILURE_CODE_5005); } SceneEditInfo sceneEditInfo = sceneEditInfoService.getByScenePlusId(scenePlus.getId()); sceneEditInfoService.upgradeVersionById(sceneEditInfo.getId()); SceneEditInfoExt sceneEditInfoExt = this.getByEditInfoId(sceneEditInfo.getId()); sceneEditInfoExt.setTours(tours); this.updateById(sceneEditInfoExt); } @Override public ResultData saveBillboards(BaseJsonArrayParamVO param) throws Exception { ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum()); this.addOrUpdateBillboards(param.getNum(), param.getData()); this.addOrUpdateBillboardsStyles(param.getNum(), param.getStyles()); //保存数据库 SceneEditInfoExt sceneEditInfoExt = this.getByScenePlusId(scenePlus.getId()); this.updateBillboards(param.getNum(), sceneEditInfoExt); // this.updateById(sceneEditInfoExt); sceneEditInfoService.upgradeVersionById(sceneEditInfoExt.getEditInfoId()); return ResultData.ok(); } private void addOrUpdateBillboardsStyles(String num, List styles) throws Exception{ this.syncBillboardsStylesFromFileToRedis(num); if(CollUtil.isEmpty(styles)){ return; } long time = Calendar.getInstance().getTimeInMillis(); Map styleMap = new HashMap<>(); AtomicInteger index = new AtomicInteger(); styles.stream().forEach(style->{ String id = style.getString("sid"); style.put("createTime", time + index.getAndIncrement()); styleMap.put(id, style.toJSONString()); }); String key = String.format(RedisKey.SCENE_BILLBOARDS_STYLES, num); redisUtil.hmset(key, styleMap); //写入本地文件,作为备份 this.writeBillboardStylesJson(num); } private void syncBillboardsStylesFromFileToRedis(String num) throws Exception{ String key = String.format(RedisKey.SCENE_BILLBOARDS_STYLES, num); boolean exist = redisUtil.hasKey(key); if(exist){ return; } String lockKey = String.format(RedisLockKey.LOCK_BILLBOARDS_STYLES_SYNC, num); String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString(); boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE); if(!lock){ throw new BusinessException(ErrorCode.SYSTEM_BUSY); } try{ exist = redisUtil.hasKey(key); if(exist){ return; } String stylesPath = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num); String stylesData = FileUtils.readUtf8String(stylesPath + "billboards-styles.json"); if(StrUtil.isEmpty(stylesData)){ return; } JSONArray stylesArr = JSON.parseArray(stylesData); if(CollUtil.isEmpty(stylesArr)){ return; } Map styleMap = new HashMap<>(); for (Object style : stylesArr) { JSONObject styleObj = (JSONObject)style; String id = styleObj.getString("sid"); styleMap.put(id, styleObj.toJSONString()); } redisUtil.hmset(key, styleMap); }finally { redisLockUtil.unlockLua(lockKey, lockVal); } } @Override public ResultData deleteBillboards(DeleteSidListParamVO param) throws Exception { ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum()); ScenePlusExt scenePlusExt = scenePlusExtService.getScenePlusExtByPlusId(scenePlus.getId()); String bucket = scenePlusExt.getYunFileBucket(); List deleteSidList = param.getSidList(); this.syncBillboardsFromFileToRedis(param.getNum()); //处理删除状态数据 this.deleteBillboards(param.getNum(), deleteSidList, bucket); //写入本地文件,作为备份 this.writeBillboardJson(param.getNum()); //保存数据库 SceneEditInfoExt sceneEditInfoExt = this.getByScenePlusId(scenePlus.getId()); this.updateBillboards(param.getNum(), sceneEditInfoExt); sceneEditInfoService.upgradeVersionById(sceneEditInfoExt.getEditInfoId()); return ResultData.ok(); } @Override public JSONObject listBillboards(BaseSceneParamVO param) throws Exception { JSONObject result = new JSONObject(); List tags = new ArrayList<>(); List styles = new ArrayList<>(); ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum()); SceneEditInfoExt sceneEditInfoExt = this.getByScenePlusId(scenePlus.getId()); if(sceneEditInfoExt.getBillboards() == CommonStatus.NO.code().intValue()){ result.put("tags", tags); result.put("styles", styles); return result; } this.syncBillboardsFromFileToRedis(param.getNum()); //获取指示牌数据 String key = String.format(RedisKey.SCENE_BILLBOARDS, param.getNum()); List list = redisUtil.hgetValues(key); List sortList = list.stream().map(str -> { JSONObject jsonObject = JSON.parseObject(str); TagBean tagBean = new TagBean(); tagBean.setCreateTime(jsonObject.getLong("createTime")); jsonObject.remove("createTime"); tagBean.setTag(jsonObject); return tagBean; }).collect(Collectors.toList()); sortList.sort(Comparator.comparingLong(TagBean::getCreateTime).reversed()); tags = sortList.stream().map(item -> item.getTag()).collect(Collectors.toList()); result.put("tags", tags); //获取图标数据 this.syncBillboardsStylesFromFileToRedis(param.getNum()); key = String.format(RedisKey.SCENE_BILLBOARDS_STYLES, param.getNum()); list = redisUtil.hgetValues(key); sortList = list.stream().map(str -> { JSONObject jsonObject = JSON.parseObject(str); TagBean tagBean = new TagBean(); tagBean.setCreateTime(jsonObject.getLong("createTime")); jsonObject.remove("createTime"); tagBean.setTag(jsonObject); return tagBean; }).collect(Collectors.toList()); sortList.sort(Comparator.comparingLong(TagBean::getCreateTime).reversed()); styles = sortList.stream().map(item -> item.getTag()).collect(Collectors.toList()); result.put("styles", styles); return result; } private void deleteBillboards(String num, List deleteSidList, String bucket) throws Exception { if(CollUtil.isEmpty(deleteSidList)){ return; } //从redis中加载热点数据 String key = String.format(RedisKey.SCENE_BILLBOARDS, num); List deletDataList = redisUtil.hMultiGet(key, deleteSidList); if(CollUtil.isEmpty(deletDataList)) return; //从redis中移除热点数据 redisUtil.hdel(key, deleteSidList.toArray()); } private void addOrUpdateBillboards(String num, List data) throws Exception{ Map addOrUpdateMap = new HashMap<>(); int i = 0; for (JSONObject jsonObject : data) { jsonObject.put("createTime", Calendar.getInstance().getTimeInMillis() + i++); addOrUpdateMap.put(jsonObject.getString("sid"), JSON.toJSONString(jsonObject)); } this.syncBillboardsFromFileToRedis(num); //处理新增和修改数据 this.addOrUpdateBillboardsHandler(num, addOrUpdateMap); } /** *

保证指示牌数据安全性,当redis宕机导致热点数据丢失时,可以从文件中读取,恢复到redis *

* @author dengsixing * @date 2022/3/3 **/ private void syncBillboardsFromFileToRedis(String num) throws Exception{ String key = String.format(RedisKey.SCENE_BILLBOARDS, num); boolean exist = redisUtil.hasKey(key); if(exist){ return; } String lockKey = String.format(RedisLockKey.LOCK_BILLBOARDS_SYNC, num); String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString(); boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE); if(!lock){ throw new BusinessException(ErrorCode.SYSTEM_BUSY); } try{ exist = redisUtil.hasKey(key); if(exist){ return; } String billboardsFilePath = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num) + "billboards.json"; String billboardsData = FileUtils.readUtf8String(billboardsFilePath); if(StrUtil.isEmpty(billboardsData)){ return; } JSONArray tagsArr = JSON.parseArray(billboardsData); if(CollUtil.isEmpty(tagsArr)){ return; } Map map = new HashMap<>(); for (Object o : tagsArr) { JSONObject jo = (JSONObject)o; map.put(jo.getString("sid"), jo.toJSONString()); } redisUtil.hmset(key, map); }finally { redisLockUtil.unlockLua(lockKey, lockVal); } } private void addOrUpdateBillboardsHandler(String num, Map addOrUpdateMap){ if(CollUtil.isEmpty(addOrUpdateMap)) return; //批量写入缓存 String key = String.format(RedisKey.SCENE_BILLBOARDS, num); redisUtil.hmset(key, addOrUpdateMap); //写入本地文件,作为备份 this.writeBillboardJson(num); } /** *

热点数据保存 *

* @author dengsixing * @date 2022/3/3 * **/ private void writeBillboardJson(String num){ String lockKey = String.format(RedisLockKey.LOCK_BILLBOARDS_SYNC, num); String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString(); boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE); if(!lock){ return; } try{ String dataKey = String.format(RedisKey.SCENE_BILLBOARDS, num); String hotJsonPath = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num) + "billboards.json"; if(!redisUtil.hasKey(dataKey)){ FileUtil.del(hotJsonPath); return; } Map billboardMap = redisUtil.hmget(dataKey); List billboardList = billboardMap.entrySet().stream().map(entry->JSON.parseObject(entry.getValue())).collect(Collectors.toList()); FileUtil.writeUtf8String(JSON.toJSONString(billboardList), hotJsonPath); }finally { redisLockUtil.unlockLua(lockKey, lockVal); } } private void writeBillboardStylesJson(String num){ String lockKey = String.format(RedisLockKey.LOCK_BILLBOARDS_STYLES_SYNC, num); String lockVal = cn.hutool.core.lang.UUID.randomUUID().toString(); boolean lock = redisLockUtil.lock(lockKey, lockVal, RedisKey.EXPIRE_TIME_1_MINUTE); if(!lock){ return; } try{ String dataKey = String.format(RedisKey.SCENE_BILLBOARDS_STYLES, num); String stylesPath = String.format(ConstantFilePath.SCENE_USER_PATH_V4, num) + "billboards-styles.json"; if(!redisUtil.hasKey(dataKey)){ FileUtil.del(stylesPath); return; } Map billboardStylesMap = redisUtil.hmget(dataKey); List billboardStyleList = billboardStylesMap.entrySet().stream().map(entry->JSON.parseObject(entry.getValue())).collect(Collectors.toList()); FileUtil.writeUtf8String(JSON.toJSONString(billboardStyleList), stylesPath); }finally { redisLockUtil.unlockLua(lockKey, lockVal); } } private void updateBillboards(String num, SceneEditInfoExt sceneEditInfoExt){ //查询缓存是否包含热点数据 String key = String.format(RedisKey.SCENE_BILLBOARDS, num); Map billboardsMap = redisUtil.hmget(key); boolean hashBillboards= false; for (Map.Entry tagMap : billboardsMap.entrySet()) { if(StrUtil.isEmpty(tagMap.getValue())){ continue; } hashBillboards = true; break; } //更改热点状态 sceneEditInfoExt.setBillboards(hashBillboards ? CommonStatus.YES.code().intValue() : CommonStatus.NO.code().intValue()); this.updateById(sceneEditInfoExt); } @Override public ResultData deleteBillboardsStyles(DeleteStylesParamVO param) throws Exception { ScenePlus scenePlus = scenePlusService.getScenePlusByNum(param.getNum()); if (scenePlus == null) throw new BusinessException(ErrorCode.FAILURE_CODE_5005); List sidList = param.getSidList(); this.syncBillboardsStylesFromFileToRedis(param.getNum()); String key = String.format(RedisKey.SCENE_BILLBOARDS_STYLES, param.getNum()); List deleteList = redisUtil.hMultiGet(key, sidList); redisUtil.hdel(key, sidList.toArray()); //写入本地文件,作为备份 this.writeBillboardStylesJson(param.getNum()); //删除oss文件 List deleteFileList = deleteList.stream().map(str -> { JSONObject parse = JSON.parseObject(str); return parse.getString("url"); }).collect(Collectors.toList()); sceneUploadService.delete( DeleteFileParamVO.builder() .num(param.getNum()) .fileNames(deleteFileList) .bizType(FileBizType.BILLBOARD_ICON.code()).build()); return ResultData.ok(); } }