package com.baidu.disconf.web.service.config.service.impl;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baidu.disconf.core.common.constants.DisConfigTypeEnum;
import com.baidu.disconf.web.common.Constants;
import com.baidu.disconf.web.config.ApplicationPropertyConfig;
import com.baidu.disconf.web.innerapi.zookeeper.ZooKeeperDriver;
import com.baidu.disconf.web.service.app.bo.App;
import com.baidu.disconf.web.service.app.service.AppMgr;
import com.baidu.disconf.web.service.config.bo.Config;
import com.baidu.disconf.web.service.config.dao.ConfigDao;
import com.baidu.disconf.web.service.config.form.ConfListForm;
import com.baidu.disconf.web.service.config.form.ConfNewItemForm;
import com.baidu.disconf.web.service.config.service.ConfigHistoryMgr;
import com.baidu.disconf.web.service.config.service.ConfigMgr;
import com.baidu.disconf.web.service.config.vo.ConfListVo;
import com.baidu.disconf.web.service.config.vo.MachineListVo;
import com.baidu.disconf.web.service.env.bo.Env;
import com.baidu.disconf.web.service.env.service.EnvMgr;
import com.baidu.disconf.web.service.zookeeper.dto.ZkDisconfData;
import com.baidu.disconf.web.service.zookeeper.dto.ZkDisconfData.ZkDisconfDataItem;
import com.baidu.disconf.web.service.zookeeper.service.ZkDeployMgr;
import com.baidu.disconf.web.utils.CodeUtils;
import com.baidu.disconf.web.utils.DiffUtils;
import com.baidu.disconf.web.utils.MyStringUtils;
import com.baidu.dsp.common.constant.DataFormatConstants;
import com.baidu.dsp.common.utils.DataTransfer;
import com.baidu.dsp.common.utils.ServiceUtil;
import com.baidu.dsp.common.utils.email.LogMailBean;
import com.baidu.ub.common.db.DaoPageResult;
import com.github.knightliao.apollo.utils.data.GsonUtils;
import com.github.knightliao.apollo.utils.io.OsUtil;
import com.github.knightliao.apollo.utils.time.DateUtils;
/**
* @author liaoqiqi
* @version 2014-6-16
*/
@Service
public class ConfigMgrImpl implements ConfigMgr {
protected static final Logger LOG = LoggerFactory.getLogger(ConfigMgrImpl.class);
@Autowired
private ConfigDao configDao;
@Autowired
private AppMgr appMgr;
@Autowired
private EnvMgr envMgr;
@Autowired
private ZooKeeperDriver zooKeeperDriver;
@Autowired
private ZkDeployMgr zkDeployMgr;
@Autowired
private LogMailBean logMailBean;
@Autowired
private ApplicationPropertyConfig applicationPropertyConfig;
@Autowired
private ConfigHistoryMgr configHistoryMgr;
/**
* 根据APPid获取其版本列表
*/
@Override
public List<String> getVersionListByAppEnv(Long appId, Long envId) {
List<String> versionList = new ArrayList<String>();
List<Config> configs = configDao.getConfByAppEnv(appId, envId);
for (Config config : configs) {
if (!versionList.contains(config.getVersion())) {
versionList.add(config.getVersion());
}
}
return versionList;
}
/**
* 配置文件的整合
*
* @param confListForm
*
* @return
*/
public List<File> getDisconfFileList(ConfListForm confListForm) {
List<Config> configList =
configDao.getConfigList(confListForm.getAppId(), confListForm.getEnvId(), confListForm.getVersion(),
true);
// 时间作为当前文件夹
String curTime = DateUtils.format(new Date(), DataFormatConstants.COMMON_TIME_FORMAT);
curTime = "tmp" + File.separator + curTime;
OsUtil.makeDirs(curTime);
List<File> files = new ArrayList<File>();
for (Config config : configList) {
if (config.getType().equals(DisConfigTypeEnum.FILE.getType())) {
File file = new File(curTime, config.getName());
try {
FileUtils.writeByteArrayToFile(file, config.getValue().getBytes());
} catch (IOException e) {
LOG.warn(e.toString());
}
files.add(file);
}
}
return files;
}
/**
* 配置列表
*/
@Override
public DaoPageResult<ConfListVo> getConfigList(ConfListForm confListForm, boolean fetchZk,
final boolean getErrorMessage) {
//
// 数据据结果
//
DaoPageResult<Config> configList = configDao.getConfigList(confListForm.getAppId(), confListForm.getEnvId(),
confListForm.getVersion(),
confListForm.getPage());
//
//
//
final App app = appMgr.getById(confListForm.getAppId());
final Env env = envMgr.getById(confListForm.getEnvId());
//
//
//
final boolean myFetchZk = fetchZk;
Map<String, ZkDisconfData> zkDataMap = new HashMap<String, ZkDisconfData>();
if (myFetchZk) {
zkDataMap = zkDeployMgr.getZkDisconfDataMap(app.getName(), env.getName(), confListForm.getVersion());
}
final Map<String, ZkDisconfData> myzkDataMap = zkDataMap;
//
// 进行转换
//
DaoPageResult<ConfListVo> configListVo =
ServiceUtil.getResult(configList, new DataTransfer<Config, ConfListVo>() {
@Override
public ConfListVo transfer(Config input) {
String appNameString = app.getName();
String envName = env.getName();
ZkDisconfData zkDisconfData = null;
if (myzkDataMap != null && myzkDataMap.keySet().contains(input.getName())) {
zkDisconfData = myzkDataMap.get(input.getName());
}
ConfListVo configListVo = convert(input, appNameString, envName, zkDisconfData);
// 列表操作不要显示值, 为了前端显示快速(只是内存里操作)
if (!myFetchZk && !getErrorMessage) {
// 列表 value 设置为 ""
configListVo.setValue("");
configListVo.setMachineList(new ArrayList<ZkDisconfData.ZkDisconfDataItem>());
}
return configListVo;
}
});
return configListVo;
}
/**
* 根据 配置ID获取配置返回
*/
@Override
public ConfListVo getConfVo(Long configId) {
Config config = configDao.get(configId);
App app = appMgr.getById(config.getAppId());
Env env = envMgr.getById(config.getEnvId());
return convert(config, app.getName(), env.getName(), null);
}
/**
* 根据 配置ID获取ZK对比数据
*/
@Override
public MachineListVo getConfVoWithZk(Long configId) {
Config config = configDao.get(configId);
App app = appMgr.getById(config.getAppId());
Env env = envMgr.getById(config.getEnvId());
//
//
//
DisConfigTypeEnum disConfigTypeEnum = DisConfigTypeEnum.FILE;
if (config.getType().equals(DisConfigTypeEnum.ITEM.getType())) {
disConfigTypeEnum = DisConfigTypeEnum.ITEM;
}
ZkDisconfData zkDisconfData = zkDeployMgr.getZkDisconfData(app.getName(), env.getName(), config.getVersion(),
disConfigTypeEnum, config.getName());
if (zkDisconfData == null) {
return new MachineListVo();
}
MachineListVo machineListVo = getZkData(zkDisconfData.getData(), config);
return machineListVo;
}
/**
* 根据配置ID获取配置
*/
@Override
public Config getConfigById(Long configId) {
return configDao.get(configId);
}
/**
* 更新 配置项/配置文件 的值
*/
@Override
public String updateItemValue(Long configId, String value) {
Config config = getConfigById(configId);
String oldValue = config.getValue();
//
// 配置数据库的值 encode to db
//
configDao.updateValue(configId, CodeUtils.utf8ToUnicode(value));
configHistoryMgr.createOne(configId, oldValue, CodeUtils.utf8ToUnicode(value));
//
// 发送邮件通知
//
String toEmails = appMgr.getEmails(config.getAppId());
if (applicationPropertyConfig.isEmailMonitorOn()) {
boolean isSendSuccess = logMailBean.sendHtmlEmail(toEmails,
" config update", DiffUtils.getDiff(CodeUtils.unicodeToUtf8(oldValue),
value,
config.toString(),
getConfigUrlHtml(config)));
if (isSendSuccess) {
return "修改成功,邮件通知成功";
} else {
return "修改成功,邮件发送失败,请检查邮箱配置";
}
}
return "修改成功";
}
/**
* 通知Zookeeper, 失败时不回滚数据库,通过监控来解决分布式不一致问题
*/
@Override
public void notifyZookeeper(Long configId) {
ConfListVo confListVo = getConfVo(configId);
if (confListVo.getTypeId().equals(DisConfigTypeEnum.FILE.getType())) {
zooKeeperDriver.notifyNodeUpdate(confListVo.getAppName(), confListVo.getEnvName(), confListVo.getVersion(),
confListVo.getKey(), GsonUtils.toJson(confListVo.getValue()),
DisConfigTypeEnum.FILE);
} else {
zooKeeperDriver.notifyNodeUpdate(confListVo.getAppName(), confListVo.getEnvName(), confListVo.getVersion(),
confListVo.getKey(), confListVo.getValue(), DisConfigTypeEnum.ITEM);
}
}
/**
* 获取配置值
*/
@Override
public String getValue(Long configId) {
return configDao.getValue(configId);
}
/**
* 新建配置
*/
@Override
public void newConfig(ConfNewItemForm confNewForm, DisConfigTypeEnum disConfigTypeEnum) {
Config config = new Config();
config.setAppId(confNewForm.getAppId());
config.setEnvId(confNewForm.getEnvId());
config.setName(confNewForm.getKey());
config.setType(disConfigTypeEnum.getType());
config.setVersion(confNewForm.getVersion());
config.setValue(CodeUtils.utf8ToUnicode(confNewForm.getValue()));
config.setStatus(Constants.STATUS_NORMAL);
// 时间
String curTime = DateUtils.format(new Date(), DataFormatConstants.COMMON_TIME_FORMAT);
config.setCreateTime(curTime);
config.setUpdateTime(curTime);
configDao.create(config);
configHistoryMgr.createOne(config.getId(), "", config.getValue());
// 发送邮件通知
//
String toEmails = appMgr.getEmails(config.getAppId());
if (applicationPropertyConfig.isEmailMonitorOn() == true) {
logMailBean.sendHtmlEmail(toEmails, " config new", getNewValue(confNewForm.getValue(), config.toString(),
getConfigUrlHtml(config)));
}
}
/**
* 删除配置
*
* @param configId
*/
@Override
public void delete(Long configId) {
Config config = configDao.get(configId);
configHistoryMgr.createOne(configId, config.getValue(), "");
configDao.deleteItem(configId);
}
/**
* 主要用于邮箱发送
*
* @return
*/
private String getConfigUrlHtml(Config config) {
return "<br/>点击<a href='http://" + applicationPropertyConfig.getDomain() + "/modifyFile.html?configId=" +
config.getId() + "'> 这里 </a> 进入查看<br/>";
}
/**
* 主要用于邮箱发送
*
* @param newValue
* @param identify
*
* @return
*/
private String getNewValue(String newValue, String identify, String htmlClick) {
String contentString = StringEscapeUtils.escapeHtml4(identify) + "<br/>" + htmlClick + "<br/><br/> ";
String data = "<br/><br/><br/><span style='color:#FF0000'>New value:</span><br/>";
contentString = contentString + data + StringEscapeUtils.escapeHtml4(newValue);
return contentString;
}
/**
*
*/
private List<String> compareConfig(String zkData, String dbData) {
List<String> errorKeyList = new ArrayList<String>();
Properties prop = new Properties();
try {
prop.load(IOUtils.toInputStream(dbData));
} catch (Exception e) {
LOG.error(e.toString());
errorKeyList.add(zkData);
return errorKeyList;
}
Map<String, String> zkMap = GsonUtils.parse2Map(zkData);
for (String keyInZk : zkMap.keySet()) {
Object valueInDb = prop.get(keyInZk);
String zkDataStr = zkMap.get(keyInZk);
// convert zk data to utf-8
//zkMap.put(keyInZk, CodeUtils.unicodeToUtf8(zkDataStr));
try {
if ((zkDataStr == null && valueInDb != null) || (zkDataStr != null && valueInDb == null)) {
errorKeyList.add(keyInZk);
} else {
zkDataStr = zkDataStr.trim();
boolean isEqual = true;
if (MyStringUtils.isDouble(zkDataStr) && MyStringUtils.isDouble(valueInDb.toString())) {
if (Math.abs(Double.parseDouble(zkDataStr) - Double.parseDouble(valueInDb.toString())) >
0.001d) {
isEqual = false;
}
} else {
if (!zkDataStr.equals(valueInDb.toString().trim())) {
isEqual = false;
}
}
if (!isEqual) {
errorKeyList
.add(keyInZk + "\t" + DiffUtils.getDiffSimple(zkDataStr, valueInDb.toString().trim()));
}
}
} catch (Exception e) {
LOG.warn(e.toString() + " ; " + keyInZk + " ; " + zkMap.get(keyInZk) + " ; " + valueInDb);
}
}
return errorKeyList;
}
/**
* 转换成配置返回
*
* @param config
*
* @return
*/
private ConfListVo convert(Config config, String appNameString, String envName, ZkDisconfData zkDisconfData) {
ConfListVo confListVo = new ConfListVo();
confListVo.setConfigId(config.getId());
confListVo.setAppId(config.getAppId());
confListVo.setAppName(appNameString);
confListVo.setEnvName(envName);
confListVo.setEnvId(config.getEnvId());
confListVo.setCreateTime(config.getCreateTime());
confListVo.setModifyTime(config.getUpdateTime().substring(0, 12));
confListVo.setKey(config.getName());
// StringEscapeUtils.escapeHtml escape
confListVo.setValue(CodeUtils.unicodeToUtf8(config.getValue()));
confListVo.setVersion(config.getVersion());
confListVo.setType(DisConfigTypeEnum.getByType(config.getType()).getModelName());
confListVo.setTypeId(config.getType());
//
//
//
if (zkDisconfData != null) {
confListVo.setMachineSize(zkDisconfData.getData().size());
List<ZkDisconfDataItem> datalist = zkDisconfData.getData();
MachineListVo machineListVo = getZkData(datalist, config);
confListVo.setErrorNum(machineListVo.getErrorNum());
confListVo.setMachineList(machineListVo.getDatalist());
confListVo.setMachineSize(machineListVo.getMachineSize());
}
return confListVo;
}
/**
* 获取ZK data
*/
private MachineListVo getZkData(List<ZkDisconfDataItem> datalist, Config config) {
int errorNum = 0;
for (ZkDisconfDataItem zkDisconfDataItem : datalist) {
if (config.getType().equals(DisConfigTypeEnum.FILE.getType())) {
List<String> errorKeyList = compareConfig(zkDisconfDataItem.getValue(), config.getValue());
if (errorKeyList.size() != 0) {
zkDisconfDataItem.setErrorList(errorKeyList);
errorNum++;
}
} else {
//
// 配置项
//
if (zkDisconfDataItem.getValue().trim().equals(config.getValue().trim())) {
} else {
List<String> errorKeyList = new ArrayList<String>();
errorKeyList.add(config.getValue().trim());
zkDisconfDataItem.setErrorList(errorKeyList);
errorNum++;
}
}
}
MachineListVo machineListVo = new MachineListVo();
machineListVo.setDatalist(datalist);
machineListVo.setErrorNum(errorNum);
machineListVo.setMachineSize(datalist.size());
return machineListVo;
}
}