Commit b0bbbc88 authored by ZWT's avatar ZWT

feat(零碳): 长庆

1.修改极短期间开预测定时任务逻辑,解决优化后部分优化结果时间段过短问题;
2.修改心知天气气象数据获取及接收定时任务,解决天气数据通过邮件下载后,部分数据精度丢失问题;

BREAKING CHANGE: 无

Closes 无

[skip ci]
parent 19c5899c
......@@ -190,7 +190,40 @@ public class SpaceOptimizeShortPeriodService extends SpaceOptimizeBaseService {
//取待优化井口并遍历
wellheadList = collect.get(true);
//todo 防冻堵策略(一口井就全开不走优化)
SpaceInstitutionWellheadView tundraStrategyWellhead = null;
List<SpaceOptimizeDurationDTO> tundraStrategyList = new ArrayList<>(32);
Integer tundraStrategy = detail.getTundraStrategy();
boolean isTundraStrategy = CollUtil.isNotEmpty(wellheadList) && ObjectUtil.isNotNull(tundraStrategy) && tundraStrategy.equals(0);
if (isTundraStrategy) {
int removeIndex = 0;
BigDecimal serviceRating = wellheadList.get(0).getServiceRating();
if (wellheadList.size() == 1) {
SpaceInstitutionWellheadView wellhead = wellheadList.get(0);
String wellheadId = wellhead.getWellheadId();
String recordId = this.createOptimizeWellhead(wellheadDTOList, periodId, wellheadId, wellhead.getWellNumber(), wellhead.getStartSeq(), startDate);
//保存间开原始记录
for (SpaceInstitutionDurationEnt durationEnt : durationMap.get(detail.getId()).get(wellheadId)) {
this.createUnOptimizeDuration(unOptimizeDurationList, durationEnt, periodId, recordId, wellheadId, startDate);
}
//设置间开优化24小时全开
this.createOptimizeDuration(durationDTOList, periodId, recordId, wellheadId, null,
BusinessConstant.START_OF_DAY_TIME,
BusinessConstant.END_OF_DAY_TIME,
BusinessConstant.ZERO, startDate
);
} else {
for (int i = 1; i < wellheadList.size(); i++) {
//找运行功率最小的井口(功率都一样取启动顺序最晚的)
if (serviceRating.compareTo(wellheadList.get(i).getServiceRating()) >= 0) {
removeIndex = i;
}
}
//取出防冻井
tundraStrategyWellhead = wellheadList.get(removeIndex);
}
//排除防冻井
wellheadList.remove(removeIndex);
}
BigDecimal serviceRating = BigDecimal.ZERO;
SpaceInstitutionWellheadView wellhead;
//记录第一次开井时间
......@@ -198,120 +231,121 @@ public class SpaceOptimizeShortPeriodService extends SpaceOptimizeBaseService {
/* [第一次开井时间优化]
int offset = detail.getStartInterval();*/
startInterval = detail.getStartInterval();
for (int i = 0; i < wellheadList.size(); i++) {
wellhead = wellheadList.get(i);
String wellheadId = wellhead.getWellheadId();
String recordId = this.createOptimizeWellhead(wellheadDTOList, periodId, wellheadId, wellhead.getWellNumber(), wellhead.getStartSeq(), startDate);
//保存间开原始记录
for (SpaceInstitutionDurationEnt durationEnt : durationMap.get(detail.getId()).get(wellheadId)) {
this.createUnOptimizeDuration(unOptimizeDurationList, durationEnt, periodId, recordId, wellheadId, startDate);
}
//累加运行功率
serviceRating = serviceRating.add(wellhead.getServiceRating());
//计算权重
int rangeListSize = rangeToList.size();
int rangeIndex = 0;
int powerListSize = powerList.size();
int powerIndex = 0;
int strategyListSize = strategyList.size();
int strategyIndex = 0;
List<SpaceOptimizeWeight> weightList = new ArrayList<>(rangeListSize);
DynamicQueryPlantPredictedPowerOutput firstPower;
DynamicQueryPlantPredictedPowerOutput lastPower;
GetBasePriceStrategyDetailOutput firstStrategy;
GetBasePriceStrategyDetailOutput lastStrategy;
while (rangeListSize > 0 && rangeListSize > rangeIndex && powerListSize > powerIndex) {
boolean powerFlag = false;
SpaceOptimizeWeight firstWeight = new SpaceOptimizeWeight();
DateTime firstTime = rangeToList.get(rangeIndex);
firstWeight.setTimestamp(firstTime);
firstWeight.setSort(rangeIndex);
firstPower = powerList.get(powerIndex);
if (0 == firstTime.compareTo(firstPower.getCreateTime())) {
firstWeight.setPower(firstPower.getPower());
//判断发电量是否满足运行功率
powerFlag = firstPower.getPower().compareTo(serviceRating) >= 0;
if (powerFlag) {
firstWeight.setWeight(10);
}
powerIndex++;
if (CollUtil.isNotEmpty(wellheadList)) {
for (int i = 0; i < wellheadList.size(); i++) {
wellhead = wellheadList.get(i);
String wellheadId = wellhead.getWellheadId();
String recordId = this.createOptimizeWellhead(wellheadDTOList, periodId, wellheadId, wellhead.getWellNumber(), wellhead.getStartSeq(), startDate);
//保存间开原始记录
for (SpaceInstitutionDurationEnt durationEnt : durationMap.get(detail.getId()).get(wellheadId)) {
this.createUnOptimizeDuration(unOptimizeDurationList, durationEnt, periodId, recordId, wellheadId, startDate);
}
firstStrategy = strategyList.get(strategyIndex);
Date closeTime = ObjectUtil.isEmpty(firstStrategy.getCloseTime()) && CharSequenceUtil.equals(BusinessConstant.END_OF_DAY_TIME, firstStrategy.getStartTime()) ? BusinessConstant.DATE_FLAG : firstStrategy.getCloseTime();
//在市电峰谷时段内,且未满足运行功率,设置权重
if (DateUtil.isIn(firstTime, firstStrategy.getOpenTime(), closeTime)) {
if (CharSequenceUtil.equals(firstStrategy.getPeriodTypeKey(), "RUSH")) {
//尖峰时段要停井
firstWeight.setWeight(0);
} else if (!powerFlag) {
firstWeight.setWeight(this.getWeightByPeriodTypeKey(firstStrategy.getPeriodTypeKey()));
//累加运行功率
serviceRating = serviceRating.add(wellhead.getServiceRating());
//计算权重
int rangeListSize = rangeToList.size();
int rangeIndex = 0;
int powerListSize = powerList.size();
int powerIndex = 0;
int strategyListSize = strategyList.size();
int strategyIndex = 0;
List<SpaceOptimizeWeight> weightList = new ArrayList<>(rangeListSize);
DynamicQueryPlantPredictedPowerOutput firstPower;
DynamicQueryPlantPredictedPowerOutput lastPower;
GetBasePriceStrategyDetailOutput firstStrategy;
GetBasePriceStrategyDetailOutput lastStrategy;
while (rangeListSize > 0 && rangeListSize > rangeIndex && powerListSize > powerIndex) {
boolean powerFlag = false;
SpaceOptimizeWeight firstWeight = new SpaceOptimizeWeight();
DateTime firstTime = rangeToList.get(rangeIndex);
firstWeight.setTimestamp(firstTime);
firstWeight.setSort(rangeIndex);
firstPower = powerList.get(powerIndex);
if (0 == firstTime.compareTo(firstPower.getCreateTime())) {
firstWeight.setPower(firstPower.getPower());
//判断发电量是否满足运行功率
powerFlag = firstPower.getPower().compareTo(serviceRating) >= 0;
if (powerFlag) {
firstWeight.setWeight(10);
}
powerIndex++;
}
}
if (firstTime.compareTo(firstStrategy.getCloseTime()) >= 0) {
strategyIndex++;
}
weightList.add(firstWeight);
rangeIndex++;
rangeListSize--;
//防止重复插入
if (rangeIndex >= rangeListSize) {
continue;
}
/*----------------------------------------------------*/
powerFlag = false;
SpaceOptimizeWeight lastWeight = new SpaceOptimizeWeight();
DateTime lastTime = rangeToList.get(rangeListSize);
lastWeight.setTimestamp(lastTime);
lastWeight.setSort(rangeListSize);
lastPower = powerList.get(powerListSize - 1);
if (0 == lastTime.compareTo(lastPower.getCreateTime())) {
lastWeight.setPower(lastPower.getPower());
//判断发电量是否满足运行功率
powerFlag = lastPower.getPower().compareTo(serviceRating) >= 0;
if (powerFlag) {
lastWeight.setWeight(10);
firstStrategy = strategyList.get(strategyIndex);
Date closeTime = ObjectUtil.isEmpty(firstStrategy.getCloseTime()) && CharSequenceUtil.equals(BusinessConstant.END_OF_DAY_TIME, firstStrategy.getStartTime()) ? BusinessConstant.DATE_FLAG : firstStrategy.getCloseTime();
//在市电峰谷时段内,且未满足运行功率,设置权重
if (DateUtil.isIn(firstTime, firstStrategy.getOpenTime(), closeTime)) {
if (CharSequenceUtil.equals(firstStrategy.getPeriodTypeKey(), "RUSH")) {
//尖峰时段要停井
firstWeight.setWeight(0);
} else if (!powerFlag) {
firstWeight.setWeight(this.getWeightByPeriodTypeKey(firstStrategy.getPeriodTypeKey()));
}
}
powerListSize--;
}
lastStrategy = strategyList.get(strategyListSize - 1);
closeTime = ObjectUtil.isEmpty(lastStrategy.getCloseTime()) && CharSequenceUtil.equals(BusinessConstant.END_OF_DAY_TIME, lastStrategy.getEndTime()) ? BusinessConstant.DATE_FLAG : lastStrategy.getCloseTime();
//在市电峰谷时段内,且未满足运行功率,设置权重
if (DateUtil.isIn(lastTime, lastStrategy.getOpenTime(), closeTime)) {
if (CharSequenceUtil.equals(lastStrategy.getPeriodTypeKey(), "RUSH")) {
//尖峰时段要停井
lastWeight.setWeight(0);
} else if (!powerFlag) {
lastWeight.setWeight(this.getWeightByPeriodTypeKey(lastStrategy.getPeriodTypeKey()));
if (firstTime.compareTo(firstStrategy.getCloseTime()) >= 0) {
strategyIndex++;
}
weightList.add(firstWeight);
rangeIndex++;
rangeListSize--;
//防止重复插入
if (rangeIndex >= rangeListSize) {
continue;
}
/*----------------------------------------------------*/
powerFlag = false;
SpaceOptimizeWeight lastWeight = new SpaceOptimizeWeight();
DateTime lastTime = rangeToList.get(rangeListSize);
lastWeight.setTimestamp(lastTime);
lastWeight.setSort(rangeListSize);
lastPower = powerList.get(powerListSize - 1);
if (0 == lastTime.compareTo(lastPower.getCreateTime())) {
lastWeight.setPower(lastPower.getPower());
//判断发电量是否满足运行功率
powerFlag = lastPower.getPower().compareTo(serviceRating) >= 0;
if (powerFlag) {
lastWeight.setWeight(10);
}
powerListSize--;
}
lastStrategy = strategyList.get(strategyListSize - 1);
closeTime = ObjectUtil.isEmpty(lastStrategy.getCloseTime()) && CharSequenceUtil.equals(BusinessConstant.END_OF_DAY_TIME, lastStrategy.getEndTime()) ? BusinessConstant.DATE_FLAG : lastStrategy.getCloseTime();
//在市电峰谷时段内,且未满足运行功率,设置权重
if (DateUtil.isIn(lastTime, lastStrategy.getOpenTime(), closeTime)) {
if (CharSequenceUtil.equals(lastStrategy.getPeriodTypeKey(), "RUSH")) {
//尖峰时段要停井
lastWeight.setWeight(0);
} else if (!powerFlag) {
lastWeight.setWeight(this.getWeightByPeriodTypeKey(lastStrategy.getPeriodTypeKey()));
}
}
if (lastTime.compareTo(lastStrategy.getOpenTime()) <= 0) {
strategyListSize--;
}
weightList.add(lastWeight);
}
if (lastTime.compareTo(lastStrategy.getOpenTime()) <= 0) {
strategyListSize--;
}
weightList.add(lastWeight);
}
//排序
weightList.sort(Comparator.comparing(SpaceOptimizeWeight::getSort));
//每日开井时长
int dayOpenMinute = wellhead.getRunDuration()
.multiply(BusinessConstant.SIXTY)
.intValue();
//每日最小开井时长(分钟)
int minOpenMinute = wellhead.getMinOpen()
.multiply(BusinessConstant.SIXTY)
.intValue();
//每日最大开井时长(分钟)
int maxOpenMinute = wellhead.getMaxOpen()
.multiply(BusinessConstant.SIXTY)
.intValue();
//每日最小关井时长(分钟)
int minCloseMinute = wellhead.getMinClose()
.multiply(BusinessConstant.SIXTY)
.intValue();
//每日最大关井时长(分钟)
int maxCloseMinute = wellhead.getMaxClose()
.multiply(BusinessConstant.SIXTY)
.intValue();
SpaceOptimizeWeight weight;
//排序
weightList.sort(Comparator.comparing(SpaceOptimizeWeight::getSort));
//每日开井时长
int dayOpenMinute = wellhead.getRunDuration()
.multiply(BusinessConstant.SIXTY)
.intValue();
//每日最小开井时长(分钟)
int minOpenMinute = wellhead.getMinOpen()
.multiply(BusinessConstant.SIXTY)
.intValue();
//每日最大开井时长(分钟)
int maxOpenMinute = wellhead.getMaxOpen()
.multiply(BusinessConstant.SIXTY)
.intValue();
//每日最小关井时长(分钟)
int minCloseMinute = wellhead.getMinClose()
.multiply(BusinessConstant.SIXTY)
.intValue();
//每日最大关井时长(分钟)
int maxCloseMinute = wellhead.getMaxClose()
.multiply(BusinessConstant.SIXTY)
.intValue();
SpaceOptimizeWeight weight;
// //取权重最高的开始位置
// int maxIndex = 0;
// int weightNum = 0;
......@@ -341,114 +375,80 @@ public class SpaceOptimizeShortPeriodService extends SpaceOptimizeBaseService {
// }
// //按索引排序
// weightList.sort(Comparator.comparing(SpaceOptimizeWeight::getSort));
//分级取时间段
List<SpaceOptimizeWeightDuration> weightDurationList = new ArrayList<>(32);
long between;
for (int i1 = 0; i1 < ladder.length; i1++) {
int begin = -1;
//取每级时间
for (int i2 = 0; i2 < weightList.size(); i2++) {
weight = weightList.get(i2);
//过滤条件:权重相同
if (ladder[i1] == weight.getWeight()) {
//确定开始时间位置
if (begin == -1) {
begin = i2;
}
} else if (begin != -1) {
//如果开井时间不满足最小开井时间则舍弃
between = DateUtil.between(weightList.get(begin).getTimestamp(), weightList.get(i2).getTimestamp(), DateUnit.MINUTE);
if (minOpenMinute <= between) {
//创建区间
weightDurationList.add(
SpaceOptimizeWeightDuration.builder()
.openTime(weightList.get(begin).getTimestamp())
.closeTime(weightList.get(i2).getTimestamp())
.openIndex(begin)
.closeIndex(i2)
.duration(between)
.weight(ladder[i1])
.build()
);
//分级取时间段
List<SpaceOptimizeWeightDuration> weightDurationList = new ArrayList<>(32);
long between;
for (int i1 = 0; i1 < ladder.length; i1++) {
int begin = -1;
//取每级时间
for (int i2 = 0; i2 < weightList.size(); i2++) {
weight = weightList.get(i2);
//过滤条件:权重相同
if (ladder[i1] == weight.getWeight()) {
//确定开始时间位置
if (begin == -1) {
begin = i2;
}
} else if (begin != -1) {
//如果开井时间不满足最小开井时间则舍弃
between = DateUtil.between(weightList.get(begin).getTimestamp(), weightList.get(i2).getTimestamp(), DateUnit.MINUTE);
if (minOpenMinute <= between) {
//创建区间
weightDurationList.add(
SpaceOptimizeWeightDuration.builder()
.openTime(weightList.get(begin).getTimestamp())
.closeTime(weightList.get(i2).getTimestamp())
.openIndex(begin)
.closeIndex(i2)
.duration(between)
.weight(ladder[i1])
.build()
);
}
begin = -1;
}
begin = -1;
}
}
}
//时间处理并排序(处理后第一次开井时间的索引位置为0)
weightDurationList.sort((o1, o2) -> o2.getWeight() - o1.getWeight());
List<SpaceOptimizeDurationDTO> optimizeDurationDTOList = new ArrayList<>(12);
//总开井时间
long sumOpenTime = 0;
//时间处理并排序(处理后第一次开井时间的索引位置为0)
weightDurationList.sort((o1, o2) -> o2.getWeight() - o1.getWeight());
List<SpaceOptimizeDurationDTO> optimizeDurationDTOList = new ArrayList<>(12);
//总开井时间
long sumOpenTime = 0;
/* //中断标识 [第一次开井时间优化]
boolean breakFlag = false;
//重新计算标识
boolean againFlag = false;*/
for (int i1 = 0; i1 < weightDurationList.size(); i1++) {
SpaceOptimizeWeightDuration weightDuration = weightDurationList.get(i1);
long duration = weightDuration.getDuration();
DateTime openTime = weightDuration.getOpenTime();
//记录第一次开井时间
if (0 == i && 0 == i1) {
firstOpenWellTime = weightDuration.getOpenTime();
}
//偏移其他井口开井时间,并修改时间间隔
if (0 == openTime.compareTo(firstOpenWellTime) && i > 0 && 0 == i1) {
duration -= (long) startInterval * i;
openTime = DateUtil.offsetMinute(openTime, startInterval * i);
}
DateTime closeTime = weightDuration.getCloseTime();
//正向标识
boolean forwardFlag = true;
//偏移开/关井时间
if (CollUtil.isNotEmpty(optimizeDurationDTOList)) {
SpaceOptimizeDurationDTO durationDTO = optimizeDurationDTOList.get(optimizeDurationDTOList.size() - 1);
Date firstOpenTime = optimizeDurationDTOList.get(0).getOpenTime();
Date lastCloseTime = durationDTO.getCloseTime();
long l;
//如果关井时间在第一次开井时间之前,判断是否需要偏移关井时间
if (DateUtil.compare(firstOpenTime, closeTime) >= 0) {
forwardFlag = false;
l = closeTime.between(firstOpenTime, DateUnit.MINUTE);
long l1 = l - minCloseMinute;
//小于0,说明不满足最小停井时间,需要补
if (l1 < 0) {
//偏移关井时间
closeTime = closeTime.offsetNew(DateField.MINUTE, (int) l1);
duration += l1;
if (duration < minOpenMinute) {
continue;
}
} else if (l1 > 0) {
//判断时间间隔是否超过最大停井时间
if (l > maxCloseMinute) {
//调整当前权重
if (i1 > 0) {
//计算出本次应该的开/关井时间
closeTime = DateUtil.offsetMinute(closeTime, (int) l - maxCloseMinute);
openTime = DateUtil.offsetMinute(openTime, (int) l - maxCloseMinute);
}
}
}
} else {
//计算当前开井时间和上一次关井时间的时间间隔
l = openTime.between(lastCloseTime, DateUnit.MINUTE);
//比较时间
if (DateUtil.compare(lastCloseTime, openTime) > 0) {
//时间间隔-(超出部分时间+最小关井时长)
duration -= (l + minCloseMinute);
if (duration >= minOpenMinute) {
//如果剩余时长能满足最小开井时长,则计算开井时间(用关井时间往前推)
openTime = closeTime.offsetNew(DateField.MINUTE, (int) -duration);
} else {
continue;
}
} else {
for (int i1 = 0; i1 < weightDurationList.size(); i1++) {
SpaceOptimizeWeightDuration weightDuration = weightDurationList.get(i1);
long duration = weightDuration.getDuration();
DateTime openTime = weightDuration.getOpenTime();
//记录第一次开井时间
if (0 == i && 0 == i1) {
firstOpenWellTime = weightDuration.getOpenTime();
}
//偏移其他井口开井时间,并修改时间间隔
if (0 == openTime.compareTo(firstOpenWellTime) && i > 0 && 0 == i1) {
duration -= (long) startInterval * i;
openTime = DateUtil.offsetMinute(openTime, startInterval * i);
}
DateTime closeTime = weightDuration.getCloseTime();
//正向标识
boolean forwardFlag = true;
//偏移开/关井时间
if (CollUtil.isNotEmpty(optimizeDurationDTOList)) {
SpaceOptimizeDurationDTO durationDTO = optimizeDurationDTOList.get(optimizeDurationDTOList.size() - 1);
Date firstOpenTime = optimizeDurationDTOList.get(0).getOpenTime();
Date lastCloseTime = durationDTO.getCloseTime();
long l;
//如果关井时间在第一次开井时间之前,判断是否需要偏移关井时间
if (DateUtil.compare(firstOpenTime, closeTime) >= 0) {
forwardFlag = false;
l = closeTime.between(firstOpenTime, DateUnit.MINUTE);
long l1 = l - minCloseMinute;
//小于0,说明不满足最小停井时间,需要补
if (l1 < 0) {
openTime = openTime.offsetNew(DateField.MINUTE, (int) -l1);
//偏移启动时间,判断时间区间是否满足最小开井时长
//偏移关井时间
closeTime = closeTime.offsetNew(DateField.MINUTE, (int) l1);
duration += l1;
if (duration < minOpenMinute) {
continue;
......@@ -458,9 +458,43 @@ public class SpaceOptimizeShortPeriodService extends SpaceOptimizeBaseService {
if (l > maxCloseMinute) {
//调整当前权重
if (i1 > 0) {
//计算出本次应该的开井时间
openTime = DateUtil.offsetMinute(lastCloseTime, maxCloseMinute);
//取当前权重的开始索引作为范围结束
//计算出本次应该的开/关井时间
closeTime = DateUtil.offsetMinute(closeTime, (int) l - maxCloseMinute);
openTime = DateUtil.offsetMinute(openTime, (int) l - maxCloseMinute);
}
}
}
} else {
//计算当前开井时间和上一次关井时间的时间间隔
l = openTime.between(lastCloseTime, DateUnit.MINUTE);
//比较时间
if (DateUtil.compare(lastCloseTime, openTime) > 0) {
//时间间隔-(超出部分时间+最小关井时长)
duration -= (l + minCloseMinute);
if (duration >= minOpenMinute) {
//如果剩余时长能满足最小开井时长,则计算开井时间(用关井时间往前推)
openTime = closeTime.offsetNew(DateField.MINUTE, (int) -duration);
} else {
continue;
}
} else {
long l1 = l - minCloseMinute;
//小于0,说明不满足最小停井时间,需要补
if (l1 < 0) {
openTime = openTime.offsetNew(DateField.MINUTE, (int) -l1);
//偏移启动时间,判断时间区间是否满足最小开井时长
duration += l1;
if (duration < minOpenMinute) {
continue;
}
} else if (l1 > 0) {
//判断时间间隔是否超过最大停井时间
if (l > maxCloseMinute) {
//调整当前权重
if (i1 > 0) {
//计算出本次应该的开井时间
openTime = DateUtil.offsetMinute(lastCloseTime, maxCloseMinute);
//取当前权重的开始索引作为范围结束
// int endIndex = weightDuration.getOpenIndex();
// SpaceOptimizeWeight spaceOptimizeWeight;
// int beginIndex = 0;
......@@ -477,120 +511,120 @@ public class SpaceOptimizeShortPeriodService extends SpaceOptimizeBaseService {
// }
// //todo :判断时间区间是否连续(暂时不用,留着,不连续的处理会很麻烦)
// boolean b = endIndex - beginIndex == sub;
//修改本次区间的开始时间及时间间隔
duration = DateUtil.between(openTime, weightDuration.getCloseTime(), DateUnit.MINUTE);
//修改本次区间的开始时间及时间间隔
duration = DateUtil.between(openTime, weightDuration.getCloseTime(), DateUnit.MINUTE);
}
}
}
}
}
}
}
//判断时间间隔是否能满足最大开井时间
if (duration >= maxOpenMinute) {
//满足,判断能满足几次(最大开井时间)
//todo : 能力有限,只能用(最大开井时间+最小停井时间)固定范围,求不了最优排布
int div = (int) NumberUtil.div(duration, maxOpenMinute, 0, RoundingMode.UP);
if (forwardFlag) {
//正向拆分
for (int i2 = 1; i2 <= div; i2++) {
DateTime closeTimeNew;
if (duration >= maxOpenMinute) {
sumOpenTime += maxOpenMinute;
closeTimeNew = openTime.offsetNew(DateField.MINUTE, maxOpenMinute);
duration = duration - maxOpenMinute - minCloseMinute;
} else if (duration >= minOpenMinute) {
sumOpenTime += minOpenMinute;
closeTimeNew = openTime.offsetNew(DateField.MINUTE, minOpenMinute);
duration = duration - minOpenMinute - minCloseMinute;
} else {
//时间不够,舍弃
continue;
//判断时间间隔是否能满足最大开井时间
if (duration >= maxOpenMinute) {
//满足,判断能满足几次(最大开井时间)
//todo : 能力有限,只能用(最大开井时间+最小停井时间)固定范围,求不了最优排布
int div = (int) NumberUtil.div(duration, maxOpenMinute, 0, RoundingMode.UP);
if (forwardFlag) {
//正向拆分
for (int i2 = 1; i2 <= div; i2++) {
DateTime closeTimeNew;
if (duration >= maxOpenMinute) {
sumOpenTime += maxOpenMinute;
closeTimeNew = openTime.offsetNew(DateField.MINUTE, maxOpenMinute);
duration = duration - maxOpenMinute - minCloseMinute;
} else if (duration >= minOpenMinute) {
sumOpenTime += minOpenMinute;
closeTimeNew = openTime.offsetNew(DateField.MINUTE, minOpenMinute);
duration = duration - minOpenMinute - minCloseMinute;
} else {
//时间不够,舍弃
continue;
}
optimizeDurationDTOList.add(SpaceOptimizeDurationDTO.builder()
.openTime(openTime)
.closeTime(closeTimeNew)
.build());
//下次启动时间为本次关井时间向后移动最小关井时间
openTime = closeTimeNew.offsetNew(DateField.MINUTE, minCloseMinute);
}
optimizeDurationDTOList.add(SpaceOptimizeDurationDTO.builder()
.openTime(openTime)
.closeTime(closeTimeNew)
.build());
//下次启动时间为本次关井时间向后移动最小关井时间
openTime = closeTimeNew.offsetNew(DateField.MINUTE, minCloseMinute);
}
} else {
//反向拆分
for (int i2 = 1; i2 <= div; i2++) {
DateTime openTimeNew;
if (duration >= maxOpenMinute) {
sumOpenTime += maxOpenMinute;
openTimeNew = closeTime.offsetNew(DateField.MINUTE, -maxOpenMinute);
duration = duration - maxOpenMinute - minCloseMinute;
} else if (duration >= minOpenMinute) {
sumOpenTime += minOpenMinute;
openTimeNew = closeTime.offsetNew(DateField.MINUTE, -minOpenMinute);
duration = duration - minOpenMinute - minCloseMinute;
} else {
//时间不够,舍弃
continue;
} else {
//反向拆分
for (int i2 = 1; i2 <= div; i2++) {
DateTime openTimeNew;
if (duration >= maxOpenMinute) {
sumOpenTime += maxOpenMinute;
openTimeNew = closeTime.offsetNew(DateField.MINUTE, -maxOpenMinute);
duration = duration - maxOpenMinute - minCloseMinute;
} else if (duration >= minOpenMinute) {
sumOpenTime += minOpenMinute;
openTimeNew = closeTime.offsetNew(DateField.MINUTE, -minOpenMinute);
duration = duration - minOpenMinute - minCloseMinute;
} else {
//时间不够,舍弃
continue;
}
optimizeDurationDTOList.add(SpaceOptimizeDurationDTO.builder()
.openTime(openTimeNew)
.closeTime(closeTime)
.build());
//下次启动时间为本次关井时间向后移动最小关井时间
closeTime = openTimeNew.offsetNew(DateField.MINUTE, -minCloseMinute);
}
optimizeDurationDTOList.add(SpaceOptimizeDurationDTO.builder()
.openTime(openTimeNew)
.closeTime(closeTime)
.build());
//下次启动时间为本次关井时间向后移动最小关井时间
closeTime = openTimeNew.offsetNew(DateField.MINUTE, -minCloseMinute);
}
} else {
//不满足,取全部
optimizeDurationDTOList.add(SpaceOptimizeDurationDTO.builder()
.openTime(openTime)
.closeTime(closeTime)
.build());
sumOpenTime += duration;
}
} else {
//不满足,取全部
optimizeDurationDTOList.add(SpaceOptimizeDurationDTO.builder()
.openTime(openTime)
.closeTime(closeTime)
.build());
sumOpenTime += duration;
}
//判断开井总时间是否大于每日最大开井时间
if (sumOpenTime >= dayOpenMinute) {
long outdo = sumOpenTime - dayOpenMinute;
if (outdo > 0) {
//todo : 如果超过最大时间,缩短结束时间(只能缩短,无法重新优化)
SpaceOptimizeDurationDTO durationDTO = optimizeDurationDTOList.get(optimizeDurationDTOList.size() - 1);
if (forwardFlag) {
DateTime closeTimeNew = DateUtil.offsetMinute(durationDTO.getCloseTime(), (int) -outdo);
int compare = closeTimeNew.compareTo(durationDTO.getOpenTime());
if (compare <= 0) {
optimizeDurationDTOList.remove(optimizeDurationDTOList.size() - 1);
sumOpenTime -= outdo;
} else {
if (DateUtil.between(durationDTO.getOpenTime(), closeTimeNew, DateUnit.MINUTE) < minOpenMinute) {
//如果缩短后时间间隔不满足最小开井时长则删除
sumOpenTime -= DateUtil.between(durationDTO.getOpenTime(), durationDTO.getCloseTime(), DateUnit.MINUTE);
//判断开井总时间是否大于每日最大开井时间
if (sumOpenTime >= dayOpenMinute) {
long outdo = sumOpenTime - dayOpenMinute;
if (outdo > 0) {
//todo : 如果超过最大时间,缩短结束时间(只能缩短,无法重新优化)
SpaceOptimizeDurationDTO durationDTO = optimizeDurationDTOList.get(optimizeDurationDTOList.size() - 1);
if (forwardFlag) {
DateTime closeTimeNew = DateUtil.offsetMinute(durationDTO.getCloseTime(), (int) -outdo);
int compare = closeTimeNew.compareTo(durationDTO.getOpenTime());
if (compare <= 0) {
optimizeDurationDTOList.remove(optimizeDurationDTOList.size() - 1);
sumOpenTime -= outdo;
} else {
//需要补时间,下面统一补
durationDTO.setCloseTime(closeTimeNew);
if (DateUtil.between(durationDTO.getOpenTime(), closeTimeNew, DateUnit.MINUTE) < minOpenMinute) {
//如果缩短后时间间隔不满足最小开井时长则删除
sumOpenTime -= DateUtil.between(durationDTO.getOpenTime(), durationDTO.getCloseTime(), DateUnit.MINUTE);
optimizeDurationDTOList.remove(optimizeDurationDTOList.size() - 1);
} else {
//需要补时间,下面统一补
durationDTO.setCloseTime(closeTimeNew);
}
}
}
} else {
DateTime openTimeNew = DateUtil.offsetMinute(durationDTO.getOpenTime(), (int) outdo);
int compare = openTimeNew.compareTo(durationDTO.getCloseTime());
if (compare >= 0) {
optimizeDurationDTOList.remove(optimizeDurationDTOList.size() - 1);
sumOpenTime -= outdo;
} else {
if (DateUtil.between(openTimeNew, durationDTO.getCloseTime(), DateUnit.MINUTE) < minOpenMinute) {
//如果缩短后时间间隔不满足最小开井时长则删除
sumOpenTime -= DateUtil.between(durationDTO.getOpenTime(), durationDTO.getCloseTime(), DateUnit.MINUTE);
DateTime openTimeNew = DateUtil.offsetMinute(durationDTO.getOpenTime(), (int) outdo);
int compare = openTimeNew.compareTo(durationDTO.getCloseTime());
if (compare >= 0) {
optimizeDurationDTOList.remove(optimizeDurationDTOList.size() - 1);
sumOpenTime -= outdo;
} else {
//需要补时间,下面统一补
durationDTO.setOpenTime(openTimeNew);
if (DateUtil.between(openTimeNew, durationDTO.getCloseTime(), DateUnit.MINUTE) < minOpenMinute) {
//如果缩短后时间间隔不满足最小开井时长则删除
sumOpenTime -= DateUtil.between(durationDTO.getOpenTime(), durationDTO.getCloseTime(), DateUnit.MINUTE);
optimizeDurationDTOList.remove(optimizeDurationDTOList.size() - 1);
} else {
//需要补时间,下面统一补
durationDTO.setOpenTime(openTimeNew);
}
}
}
}
}
break;
break;
/* //结束循环 [第一次开井时间优化]
breakFlag = true;*/
}
//重新按开井时间排序
optimizeDurationDTOList.sort(Comparator.comparing(SpaceOptimizeDurationDTO::getOpenTime));
}
//重新按开井时间排序
optimizeDurationDTOList.sort(Comparator.comparing(SpaceOptimizeDurationDTO::getOpenTime));
/* //判断第一次开井时间 [第一次开井时间优化]
if (breakFlag || i1 == (weightDurationList.size() - 1)) {
Date checkOpenTime = optimizeDurationDTOList.get(0).getOpenTime();
......@@ -628,143 +662,143 @@ public class SpaceOptimizeShortPeriodService extends SpaceOptimizeBaseService {
break;
}
}*/
}
//判断是否需要补时间
if (sumOpenTime < dayOpenMinute) {
//创建需要补时间的时间范围
List<SpaceOptimizeWeightDuration> replenishList = new ArrayList<>(12);
//得到开始时间时间戳
DateTime beginTime = weightList.get(0).getTimestamp();
for (int i1 = 0; i1 < optimizeDurationDTOList.size() - 1; i1++) {
//取相邻两段时间
SpaceOptimizeDurationDTO first = optimizeDurationDTOList.get(i1);
SpaceOptimizeDurationDTO second = optimizeDurationDTOList.get(i1 + 1);
Date firstOpenTime = first.getOpenTime();
Date firstCloseTime = first.getCloseTime();
Date secondOpenTime = second.getOpenTime();
Date secondCloseTime = second.getCloseTime();
long closeMinute = DateUtil.between(firstCloseTime, secondOpenTime, DateUnit.MINUTE);
//判断关井时长是否满足条件
if (closeMinute == minCloseMinute) {
continue;
}
//计算可优化时长
int optimizeMinute = (int) (closeMinute - minCloseMinute);
//判断是否小于最大开井时长
long firstOpenMinute = DateUtil.between(firstOpenTime, firstCloseTime, DateUnit.MINUTE);
if (firstOpenMinute < maxOpenMinute) {
//找索引开始/结束位置
int firstBeginIndex = (int) DateUtil.between(beginTime, firstCloseTime, DateUnit.MINUTE) / 30;
int firstEndIndex = (int) DateUtil.between(beginTime, DateUtil.offsetMinute(firstCloseTime, optimizeMinute), DateUnit.MINUTE) / 30;
//累加权重
int weightSum = 0;
int endIndex = firstBeginIndex;
//取时间段
SpaceOptimizeWeight optimizeWeight;
//从前往后推
for (int i2 = firstBeginIndex; i2 <= firstEndIndex; i2++) {
optimizeWeight = weightList.get(i2);
if (0 == optimizeWeight.getWeight()) {
break;
}
weightSum += optimizeWeight.getWeight();
endIndex = i2;
}
//添加优化区间
if (endIndex > firstBeginIndex) {
replenishList.add(
SpaceOptimizeWeightDuration.builder()
.openIndex(firstBeginIndex)
.closeIndex(endIndex)
.weight(weightSum)
.optimizeIndex(i1)
.build()
);
}
//判断是否需要补时间
if (sumOpenTime < dayOpenMinute) {
//创建需要补时间的时间范围
List<SpaceOptimizeWeightDuration> replenishList = new ArrayList<>(12);
//得到开始时间时间戳
DateTime beginTime = weightList.get(0).getTimestamp();
for (int i1 = 0; i1 < optimizeDurationDTOList.size() - 1; i1++) {
//取相邻两段时间
SpaceOptimizeDurationDTO first = optimizeDurationDTOList.get(i1);
SpaceOptimizeDurationDTO second = optimizeDurationDTOList.get(i1 + 1);
Date firstOpenTime = first.getOpenTime();
Date firstCloseTime = first.getCloseTime();
Date secondOpenTime = second.getOpenTime();
Date secondCloseTime = second.getCloseTime();
long closeMinute = DateUtil.between(firstCloseTime, secondOpenTime, DateUnit.MINUTE);
//判断关井时长是否满足条件
if (closeMinute == minCloseMinute) {
continue;
}
}
/*---------------------------- 计算下一段 -------------------------------*/
long secondOpenMinute = DateUtil.between(secondOpenTime, secondCloseTime, DateUnit.MINUTE);
if (secondOpenMinute < maxOpenMinute) {
//找索引开始/结束位置
int secondBeginIndex = (int) DateUtil.between(beginTime, DateUtil.offsetMinute(secondOpenTime, -optimizeMinute), DateUnit.MINUTE) / 30;
int secondEndIndex = (int) DateUtil.between(beginTime, secondOpenTime, DateUnit.MINUTE) / 30;
//累加权重
int weightSum = 0;
int beginIndex = secondEndIndex;
//取时间段
SpaceOptimizeWeight optimizeWeight;
//从后往前推
for (int i2 = secondEndIndex; i2 > secondBeginIndex; i2--) {
optimizeWeight = weightList.get(i2);
if (0 == optimizeWeight.getWeight()) {
break;
//计算可优化时长
int optimizeMinute = (int) (closeMinute - minCloseMinute);
//判断是否小于最大开井时长
long firstOpenMinute = DateUtil.between(firstOpenTime, firstCloseTime, DateUnit.MINUTE);
if (firstOpenMinute < maxOpenMinute) {
//找索引开始/结束位置
int firstBeginIndex = (int) DateUtil.between(beginTime, firstCloseTime, DateUnit.MINUTE) / 30;
int firstEndIndex = (int) DateUtil.between(beginTime, DateUtil.offsetMinute(firstCloseTime, optimizeMinute), DateUnit.MINUTE) / 30;
//累加权重
int weightSum = 0;
int endIndex = firstBeginIndex;
//取时间段
SpaceOptimizeWeight optimizeWeight;
//从前往后推
for (int i2 = firstBeginIndex; i2 <= firstEndIndex; i2++) {
optimizeWeight = weightList.get(i2);
if (0 == optimizeWeight.getWeight()) {
break;
}
weightSum += optimizeWeight.getWeight();
endIndex = i2;
}
//添加优化区间
if (endIndex > firstBeginIndex) {
replenishList.add(
SpaceOptimizeWeightDuration.builder()
.openIndex(firstBeginIndex)
.closeIndex(endIndex)
.weight(weightSum)
.optimizeIndex(i1)
.build()
);
}
weightSum += optimizeWeight.getWeight();
beginIndex = i2;
}
//添加优化区间
if (beginIndex < secondEndIndex) {
replenishList.add(
SpaceOptimizeWeightDuration.builder()
.openIndex(beginIndex)
.closeIndex(secondEndIndex)
.weight(weightSum)
.optimizeIndex(i1 + 1)
.build()
);
/*---------------------------- 计算下一段 -------------------------------*/
long secondOpenMinute = DateUtil.between(secondOpenTime, secondCloseTime, DateUnit.MINUTE);
if (secondOpenMinute < maxOpenMinute) {
//找索引开始/结束位置
int secondBeginIndex = (int) DateUtil.between(beginTime, DateUtil.offsetMinute(secondOpenTime, -optimizeMinute), DateUnit.MINUTE) / 30;
int secondEndIndex = (int) DateUtil.between(beginTime, secondOpenTime, DateUnit.MINUTE) / 30;
//累加权重
int weightSum = 0;
int beginIndex = secondEndIndex;
//取时间段
SpaceOptimizeWeight optimizeWeight;
//从后往前推
for (int i2 = secondEndIndex; i2 > secondBeginIndex; i2--) {
optimizeWeight = weightList.get(i2);
if (0 == optimizeWeight.getWeight()) {
break;
}
weightSum += optimizeWeight.getWeight();
beginIndex = i2;
}
//添加优化区间
if (beginIndex < secondEndIndex) {
replenishList.add(
SpaceOptimizeWeightDuration.builder()
.openIndex(beginIndex)
.closeIndex(secondEndIndex)
.weight(weightSum)
.optimizeIndex(i1 + 1)
.build()
);
}
}
}
}
//按权重优先级排序补时间
if (CollUtil.isNotEmpty(replenishList)) {
//计算需要补的时长
long subMinute = dayOpenMinute - sumOpenTime;
//按照权重降序排序
replenishList.sort((o1, o2) -> o2.getWeight() - o1.getWeight());
//遍历,补时间
for (SpaceOptimizeWeightDuration replenish : replenishList) {
if (subMinute <= 0) {
break;
}
//取需要优化的时间段
SpaceOptimizeDurationDTO durationDTO = optimizeDurationDTOList.get(replenish.getOptimizeIndex());
//计算剩余可开井时长
long remainOpenMinute = maxOpenMinute - DateUtil.between(durationDTO.getOpenTime(), durationDTO.getCloseTime(), DateUnit.MINUTE);
//取可优化时间段
DateTime startTime = weightList.get(replenish.getOpenIndex()).getTimestamp();
DateTime endTime = weightList.get(replenish.getCloseIndex()).getTimestamp();
//计算可优化时间
long replenishDuration = DateUtil.between(startTime, endTime, DateUnit.MINUTE);
//判断是否可以满足全部优化
if (replenishDuration >= subMinute) {
//可开井时间大于等于需要优化时长
if (remainOpenMinute >= subMinute) {
this.overtime(durationDTO, endTime, (int) subMinute);
//按权重优先级排序补时间
if (CollUtil.isNotEmpty(replenishList)) {
//计算需要补的时长
long subMinute = dayOpenMinute - sumOpenTime;
//按照权重降序排序
replenishList.sort((o1, o2) -> o2.getWeight() - o1.getWeight());
//遍历,补时间
for (SpaceOptimizeWeightDuration replenish : replenishList) {
if (subMinute <= 0) {
break;
} else {
this.overtime(durationDTO, endTime, (int) remainOpenMinute);
subMinute -= remainOpenMinute;
}
} else {
//可开井时间大于等于可优化时长
if (remainOpenMinute >= replenishDuration) {
this.overtime(durationDTO, endTime, (int) replenishDuration);
subMinute -= replenishDuration;
//取需要优化的时间段
SpaceOptimizeDurationDTO durationDTO = optimizeDurationDTOList.get(replenish.getOptimizeIndex());
//计算剩余可开井时长
long remainOpenMinute = maxOpenMinute - DateUtil.between(durationDTO.getOpenTime(), durationDTO.getCloseTime(), DateUnit.MINUTE);
//取可优化时间段
DateTime startTime = weightList.get(replenish.getOpenIndex()).getTimestamp();
DateTime endTime = weightList.get(replenish.getCloseIndex()).getTimestamp();
//计算可优化时间
long replenishDuration = DateUtil.between(startTime, endTime, DateUnit.MINUTE);
//判断是否可以满足全部优化
if (replenishDuration >= subMinute) {
//可开井时间大于等于需要优化时长
if (remainOpenMinute >= subMinute) {
this.overtime(durationDTO, endTime, (int) subMinute);
break;
} else {
this.overtime(durationDTO, endTime, (int) remainOpenMinute);
subMinute -= remainOpenMinute;
}
} else {
this.overtime(durationDTO, endTime, (int) remainOpenMinute);
subMinute -= remainOpenMinute;
//可开井时间大于等于可优化时长
if (remainOpenMinute >= replenishDuration) {
this.overtime(durationDTO, endTime, (int) replenishDuration);
subMinute -= replenishDuration;
} else {
this.overtime(durationDTO, endTime, (int) remainOpenMinute);
subMinute -= remainOpenMinute;
}
}
}
} else {
//todo : 没法补时间
}
} else {
//todo : 没法补时间
}
}
//创建优化后的间开区间
if (CollUtil.isNotEmpty(optimizeDurationDTOList)) {
//重新按开井时间排序
optimizeDurationDTOList.sort(Comparator.comparing(SpaceOptimizeDurationDTO::getOpenTime));
//时间段优化
//创建优化后的间开区间
if (CollUtil.isNotEmpty(optimizeDurationDTOList)) {
//重新按开井时间排序
optimizeDurationDTOList.sort(Comparator.comparing(SpaceOptimizeDurationDTO::getOpenTime));
//时间段优化
// List<SpaceOptimizeDurationDTO> optimizeDurationList = new ArrayList<>(optimizeDurationDTOList.size());
// if (optimizeDurationDTOList.size() > 1) {
// int begin = 0;
......@@ -791,39 +825,68 @@ public class SpaceOptimizeShortPeriodService extends SpaceOptimizeBaseService {
// //重构
// optimizeDurationDTOList = optimizeDurationList;
// }
SpaceOptimizeDurationDTO durationDTO;
for (int i1 = 0; i1 < optimizeDurationDTOList.size(); i1++) {
durationDTO = optimizeDurationDTOList.get(i1);
DateTime startOffset = DateUtil.date(durationDTO.getOpenTime());
DateTime endOffset = DateUtil.date(durationDTO.getCloseTime());
if (startOffset.compareTo(BusinessConstant.DATE_FLAG) < 0 && endOffset.compareTo(BusinessConstant.DATE_FLAG) > 0) {
//如果时间超过当天,舍弃
this.createOptimizeDuration(durationDTOList, periodId, recordId, wellheadId, null,
startOffset.toString(BusinessConstant.MINUTES_FORMAT),
BusinessConstant.END_OF_DAY_TIME,
BusinessConstant.ONE, startDate
);
this.createOptimizeDuration(durationDTOList, periodId, recordId, wellheadId, null,
BusinessConstant.START_OF_DAY_TIME,
endOffset.toString(BusinessConstant.MINUTES_FORMAT),
BusinessConstant.ONE, startDate
);
} else if (endOffset.compareTo(BusinessConstant.DATE_FLAG) == 0) {
this.createOptimizeDuration(durationDTOList, periodId, recordId, wellheadId, null,
startOffset.toString(BusinessConstant.MINUTES_FORMAT),
BusinessConstant.END_OF_DAY_TIME,
BusinessConstant.ONE, startDate
);
} else {
//计算偏移
this.createOptimizeDuration(durationDTOList, periodId, recordId, wellheadId, null,
startOffset.toString(BusinessConstant.MINUTES_FORMAT),
endOffset.toString(BusinessConstant.MINUTES_FORMAT),
BusinessConstant.ONE, startDate
);
SpaceOptimizeDurationDTO durationDTO;
for (int i1 = 0; i1 < optimizeDurationDTOList.size(); i1++) {
durationDTO = optimizeDurationDTOList.get(i1);
DateTime startOffset = DateUtil.date(durationDTO.getOpenTime());
DateTime endOffset = DateUtil.date(durationDTO.getCloseTime());
if (startOffset.compareTo(BusinessConstant.DATE_FLAG) < 0 && endOffset.compareTo(BusinessConstant.DATE_FLAG) > 0) {
//如果时间超过当天,舍弃
this.createOptimizeDuration(durationDTOList, periodId, recordId, wellheadId, null,
startOffset.toString(BusinessConstant.MINUTES_FORMAT),
BusinessConstant.END_OF_DAY_TIME,
BusinessConstant.ONE, startDate
);
this.createOptimizeDuration(durationDTOList, periodId, recordId, wellheadId, null,
BusinessConstant.START_OF_DAY_TIME,
endOffset.toString(BusinessConstant.MINUTES_FORMAT),
BusinessConstant.ONE, startDate
);
tundraStrategyList.add(
SpaceOptimizeDurationDTO.builder()
.openTime(startOffset)
.closeTime(BusinessConstant.DATE_FLAG)
.build()
);
tundraStrategyList.add(
SpaceOptimizeDurationDTO.builder()
.openTime(BusinessConstant.DATE_FLAG_BEGIN)
.closeTime(endOffset)
.build()
);
} else if (endOffset.compareTo(BusinessConstant.DATE_FLAG) == 0) {
this.createOptimizeDuration(durationDTOList, periodId, recordId, wellheadId, null,
startOffset.toString(BusinessConstant.MINUTES_FORMAT),
BusinessConstant.END_OF_DAY_TIME,
BusinessConstant.ONE, startDate
);
tundraStrategyList.add(
SpaceOptimizeDurationDTO.builder()
.openTime(startOffset)
.closeTime(BusinessConstant.DATE_FLAG)
.build()
);
} else {
//计算偏移
this.createOptimizeDuration(durationDTOList, periodId, recordId, wellheadId, null,
startOffset.toString(BusinessConstant.MINUTES_FORMAT),
endOffset.toString(BusinessConstant.MINUTES_FORMAT),
BusinessConstant.ONE, startDate
);
tundraStrategyList.add(
SpaceOptimizeDurationDTO.builder()
.openTime(startOffset)
.closeTime(endOffset)
.build()
);
}
}
}
}
//防冻井
if (ObjectUtil.isNotNull(tundraStrategyWellhead)) {
}
}
}
//开启事务
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment