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();
}
}