mirror of https://github.com/jeecgboot/jeecg-boot
commit
63b61d3f24
|
@ -156,28 +156,49 @@ public abstract class AbstractInvoice<E, F, G, H, I> {
|
||||||
// if the number of rows of data is greater than the available space in the default template
|
// if the number of rows of data is greater than the available space in the default template
|
||||||
// we shift down the rows after the table and we clone the row in the table
|
// we shift down the rows after the table and we clone the row in the table
|
||||||
int dataRowNumber = LAST_ROW - FIRST_ROW;
|
int dataRowNumber = LAST_ROW - FIRST_ROW;
|
||||||
int additionalRowNum = data.size() - dataRowNumber - 1;
|
int additionalRowNum = Math.max(data.size() - dataRowNumber - 1, 0);
|
||||||
TOTAL_ROW = LAST_ROW+additionalRowNum;
|
TOTAL_ROW = LAST_ROW + additionalRowNum;
|
||||||
|
|
||||||
Sheet sheet = factory.getWorkbook().getSheetAt(0);
|
Sheet sheet = factory.getWorkbook().getSheetAt(0);
|
||||||
org.apache.poi.ss.usermodel.Row sourceRow = sheet.getRow(FIRST_ROW);
|
org.apache.poi.ss.usermodel.Row sourceRow = sheet.getRow(FIRST_ROW);
|
||||||
if(data.size() > dataRowNumber)
|
int footerRow = TOTAL_ROW + 1; // la ligne à laquelle le footer commence (1 ligne avant le total)
|
||||||
|
int imgShift = additionalRowNum; // le nombre de ligne qu'on va décaler les images (signatures et tampon)
|
||||||
|
if(data.size() > dataRowNumber + 1)
|
||||||
{
|
{
|
||||||
int startRow = LAST_ROW+1;
|
int startRow = LAST_ROW+1;
|
||||||
int fileLastRow = sheet.getLastRowNum();
|
int fileLastRow = sheet.getLastRowNum();
|
||||||
// shifting the footer of the file, to X rows below
|
// shifting the footer of the file, to X rows below
|
||||||
// making sure the whole footer is in the same page (13 lines) and we fill the end of page with blank data lines
|
// making sure the whole footer is in the same page (13 lines) and we fill the end of page with blank data lines
|
||||||
if(additionalRowNum%PAGE_ROW_MAX <= 13) {
|
|
||||||
TOTAL_ROW = PAGE_ROW_MAX-2;
|
// si le nombre de lignes de data rentre dans 1 page A4
|
||||||
sheet.shiftRows(startRow, fileLastRow, PAGE_ROW_MAX - LAST_ROW, true, false);
|
if(data.size() < 44) {
|
||||||
|
if(TOTAL_ROW > LAST_ROW + 3) { // s'il ne reste pas assez de place pour le footer
|
||||||
|
// on shift le footer à la page suivante (total + signature etc..)
|
||||||
|
sheet.shiftRows(startRow, fileLastRow, PAGE_ROW_MAX - LAST_ROW - 1, true, false);
|
||||||
|
footerRow = PAGE_ROW_MAX - LAST_ROW + 1;
|
||||||
|
imgShift = PAGE_ROW_MAX - LAST_ROW + 1;
|
||||||
|
TOTAL_ROW = PAGE_ROW_MAX;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// +6 because if we use US template there's one more row
|
|
||||||
sheet.shiftRows(startRow, fileLastRow, additionalRowNum, true, false);
|
sheet.shiftRows(startRow, fileLastRow, additionalRowNum, true, false);
|
||||||
|
footerRow = additionalRowNum + 1;
|
||||||
|
TOTAL_ROW = LAST_ROW + additionalRowNum+1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {// on dépasse forcément le format A4 d'un PDF
|
||||||
|
if(((TOTAL_ROW - 44) % 63) < 13) {
|
||||||
|
sheet.shiftRows(startRow, fileLastRow, TOTAL_ROW - LAST_ROW + ((TOTAL_ROW-44)%63), true, false);
|
||||||
|
footerRow = additionalRowNum + ((TOTAL_ROW-44)%63) + 1;
|
||||||
|
imgShift = TOTAL_ROW-44 + ((TOTAL_ROW-44)%63) -1;
|
||||||
|
TOTAL_ROW += ((TOTAL_ROW-44)%63) + 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sheet.shiftRows(startRow, fileLastRow, TOTAL_ROW - LAST_ROW, true, false);
|
||||||
|
footerRow = TOTAL_ROW - LAST_ROW +1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// inserting new rows after row 42
|
// inserting new rows after row 42
|
||||||
for(int i = 0; i < (data.size() - dataRowNumber - 1 <= 13 ? PAGE_ROW_MAX-LAST_ROW+1 : additionalRowNum+1); i++) {
|
for(int i = 0; i < footerRow; i++) {
|
||||||
sheet.createRow(startRow-1 + i);
|
sheet.createRow(startRow-1 + i);
|
||||||
org.apache.poi.ss.usermodel.Row newRow = sheet.getRow(startRow-1 + i);
|
org.apache.poi.ss.usermodel.Row newRow = sheet.getRow(startRow-1 + i);
|
||||||
newRow.setHeight(sourceRow.getHeight());
|
newRow.setHeight(sourceRow.getHeight());
|
||||||
|
@ -192,7 +213,7 @@ public abstract class AbstractInvoice<E, F, G, H, I> {
|
||||||
cellStyle.setBorderLeft(BorderStyle.DOUBLE);
|
cellStyle.setBorderLeft(BorderStyle.DOUBLE);
|
||||||
cell.setCellStyle(cellStyle);
|
cell.setCellStyle(cellStyle);
|
||||||
}
|
}
|
||||||
if(startRow + i < PAGE_ROW_MAX-2) {
|
if((startRow + i <= TOTAL_ROW && data.size() >= 44) || (startRow + i <= PAGE_ROW_MAX - 1 && data.size() < 44)) {
|
||||||
if (j >= 2 && j <= 7) {
|
if (j >= 2 && j <= 7) {
|
||||||
middleCellStyle.setBorderLeft(BorderStyle.THIN);
|
middleCellStyle.setBorderLeft(BorderStyle.THIN);
|
||||||
middleCellStyle.setBorderRight(BorderStyle.THIN);
|
middleCellStyle.setBorderRight(BorderStyle.THIN);
|
||||||
|
@ -209,11 +230,14 @@ public abstract class AbstractInvoice<E, F, G, H, I> {
|
||||||
XSSFPicture picture = (XSSFPicture)shape;
|
XSSFPicture picture = (XSSFPicture)shape;
|
||||||
XSSFClientAnchor anchor = picture.getClientAnchor();
|
XSSFClientAnchor anchor = picture.getClientAnchor();
|
||||||
|
|
||||||
anchor.setRow1(data.size() - dataRowNumber - 1 <= 13 ? anchor.getRow1() + PAGE_ROW_MAX-LAST_ROW : anchor.getRow1() + additionalRowNum);
|
anchor.setRow1(anchor.getRow1() + imgShift);
|
||||||
anchor.setRow2(data.size() - dataRowNumber - 1 <= 13 ? anchor.getRow2() + PAGE_ROW_MAX-LAST_ROW : anchor.getRow2() + additionalRowNum);
|
anchor.setRow2(anchor.getRow2() + imgShift);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
TOTAL_ROW = LAST_ROW + additionalRowNum + 1;
|
||||||
|
}
|
||||||
// table section
|
// table section
|
||||||
for (int i = 0; i < data.size(); i++) {
|
for (int i = 0; i < data.size(); i++) {
|
||||||
lineNum = i + FIRST_ROW;
|
lineNum = i + FIRST_ROW;
|
||||||
|
@ -298,16 +322,16 @@ public abstract class AbstractInvoice<E, F, G, H, I> {
|
||||||
totalDueCellStyle.setBorderTop(BorderStyle.THIN);
|
totalDueCellStyle.setBorderTop(BorderStyle.THIN);
|
||||||
totalDueCellStyle.setFont(arialBold);
|
totalDueCellStyle.setFont(arialBold);
|
||||||
|
|
||||||
if(additionalRowNum%PAGE_ROW_MAX <= 13) {
|
if(((LAST_ROW+additionalRowNum - 44) % 63) < 13 && ((LAST_ROW+additionalRowNum - 44) % 63) > 0) {
|
||||||
totalDueRow = sheet.getRow(PAGE_ROW_MAX + 2);
|
totalDueRow = sheet.getRow( data.size() < 44 ? PAGE_ROW_MAX + 1 : TOTAL_ROW + 1);
|
||||||
Cell totalDueCell = totalDueRow.createCell(7);
|
Cell totalDueCell = totalDueRow.createCell(7);
|
||||||
totalDueCell.setCellFormula("H" + (PAGE_ROW_MAX - 2) + "-G" + (PAGE_ROW_MAX - 2));
|
totalDueCell.setCellFormula("H" + (TOTAL_ROW) + "-G" + (TOTAL_ROW));
|
||||||
totalDueCell.setCellStyle(totalDueCellStyle);
|
totalDueCell.setCellStyle(totalDueCellStyle);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
totalDueRow = sheet.getRow(TOTAL_ROW + 2);
|
totalDueRow = sheet.getRow(data.size() < 44 ? TOTAL_ROW + 1 : TOTAL_ROW + 2);
|
||||||
Cell totalDueCell = totalDueRow.createCell(7);
|
Cell totalDueCell = totalDueRow.createCell(7);
|
||||||
totalDueCell.setCellFormula("H" + (TOTAL_ROW+1) + "-G" + (TOTAL_ROW+1));
|
totalDueCell.setCellFormula("H" + (data.size() < 44 ? TOTAL_ROW : TOTAL_ROW + 1) + "-G" + (data.size() < 44 ? TOTAL_ROW : TOTAL_ROW + 1));
|
||||||
totalDueCell.setCellStyle(totalDueCellStyle);
|
totalDueCell.setCellStyle(totalDueCellStyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,13 +339,13 @@ public abstract class AbstractInvoice<E, F, G, H, I> {
|
||||||
if (targetClient.getCurrency().equals("USD")) {
|
if (targetClient.getCurrency().equals("USD")) {
|
||||||
org.apache.poi.ss.usermodel.Row dollarRow;
|
org.apache.poi.ss.usermodel.Row dollarRow;
|
||||||
String formula;
|
String formula;
|
||||||
if (additionalRowNum % PAGE_ROW_MAX <= 13) {
|
if ((((LAST_ROW + additionalRowNum - 44) % 63) < 13) && ((LAST_ROW + additionalRowNum - 44) % 63) > 0) {
|
||||||
dollarRow = sheet.getRow(TOTAL_ROW + 5);
|
dollarRow = sheet.getRow(TOTAL_ROW + 2);
|
||||||
formula = "H"+ (TOTAL_ROW+5) +" *" + exchangeRate;
|
formula = "H"+ (TOTAL_ROW + 2) +" *" + exchangeRate;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
dollarRow = sheet.getRow(TOTAL_ROW + 3);
|
dollarRow = sheet.getRow(data.size() >= 44 ? TOTAL_ROW + 3 : TOTAL_ROW + 2);
|
||||||
formula = "H" + (TOTAL_ROW + 3) + " *" + exchangeRate;
|
formula = "H" + (data.size() >= 44 ? TOTAL_ROW + 3 : TOTAL_ROW + 2) + " *" + exchangeRate;
|
||||||
}
|
}
|
||||||
Cell dollarCell = dollarRow.createCell(7); // column H
|
Cell dollarCell = dollarRow.createCell(7); // column H
|
||||||
CellStyle cellStyle = factory.getWorkbook().createCellStyle();
|
CellStyle cellStyle = factory.getWorkbook().createCellStyle();
|
||||||
|
|
|
@ -92,7 +92,7 @@ public class InvoiceStyleFactory {
|
||||||
rightSideDecimalStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
rightSideDecimalStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||||
// decimal
|
// decimal
|
||||||
// DataFormat format =workbook.createDataFormat();
|
// DataFormat format =workbook.createDataFormat();
|
||||||
leftSideStyle.setDataFormat(creationHelper.createDataFormat().getFormat("#,##0.00"));
|
rightSideDecimalStyle.setDataFormat(creationHelper.createDataFormat().getFormat("#,##0.00"));
|
||||||
// font
|
// font
|
||||||
Font font = workbook.createFont();
|
Font font = workbook.createFont();
|
||||||
font.setFontName("Arial");
|
font.setFontName("Arial");
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -31,6 +32,7 @@ public class ArchiveOrderJob implements Job {
|
||||||
|
|
||||||
private static final List<String> DEFAULT_EXCLUDED_SHOPS = Arrays.asList("JCH3", "JCH4", "JCH5", "FB2");
|
private static final List<String> DEFAULT_EXCLUDED_SHOPS = Arrays.asList("JCH3", "JCH4", "JCH5", "FB2");
|
||||||
private static final Integer DEFAULT_NUMBER_OF_THREADS = 10;
|
private static final Integer DEFAULT_NUMBER_OF_THREADS = 10;
|
||||||
|
private static final Integer MABANG_API_RATE_LIMIT_PER_MINUTE = 300;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IPlatformOrderService platformOrderService;
|
private IPlatformOrderService platformOrderService;
|
||||||
|
@ -76,7 +78,8 @@ public class ArchiveOrderJob implements Job {
|
||||||
|
|
||||||
List<String> platformOrderIds = platformOrderService.fetchInvoicedShippedOrdersNotInShops(startDateTime, endDateTime, shops, excludedTrackingNumbersRegex);
|
List<String> platformOrderIds = platformOrderService.fetchInvoicedShippedOrdersNotInShops(startDateTime, endDateTime, shops, excludedTrackingNumbersRegex);
|
||||||
|
|
||||||
ExecutorService executor = Executors.newFixedThreadPool(DEFAULT_NUMBER_OF_THREADS);
|
ExecutorService throttlingExecutorService = ThrottlingExecutorService.createExecutorService(DEFAULT_NUMBER_OF_THREADS,
|
||||||
|
MABANG_API_RATE_LIMIT_PER_MINUTE, TimeUnit.MINUTES);
|
||||||
|
|
||||||
log.info("Constructing order archiving requests");
|
log.info("Constructing order archiving requests");
|
||||||
List<ArchiveOrderRequestBody> archiveOrderRequestBodies = new ArrayList<>();
|
List<ArchiveOrderRequestBody> archiveOrderRequestBodies = new ArrayList<>();
|
||||||
|
@ -95,7 +98,7 @@ public class ArchiveOrderJob implements Job {
|
||||||
log.error("Error communicating with MabangAPI", e);
|
log.error("Error communicating with MabangAPI", e);
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}, executor))
|
}, throttlingExecutorService))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
List<Boolean> results = changeOrderFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());
|
List<Boolean> results = changeOrderFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());
|
||||||
long nbSuccesses = results.stream().filter(b -> b).count();
|
long nbSuccesses = results.stream().filter(b -> b).count();
|
||||||
|
|
|
@ -0,0 +1,205 @@
|
||||||
|
package org.jeecg.modules.business.domain.job;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
/**
|
||||||
|
* Extends {@code LinkedHashMap} into a fixed sized ordered cache
|
||||||
|
* for allocating and tracking limited resources in <em>intervals</em>. It also
|
||||||
|
* tracks the allocation rate as a moving average.
|
||||||
|
* <p>
|
||||||
|
* The {@code IntervalWindow} is created with a cache that consists of the
|
||||||
|
* present <em>interval</em> and at least one past <em>interval</em>.
|
||||||
|
* As the number of cached <em>intervals</em> exceed the windows size, they are
|
||||||
|
* removed and the moving average updated. The allocation method is
|
||||||
|
* thread-safe to ensure over allocation is avoided.
|
||||||
|
*
|
||||||
|
* @author martinb
|
||||||
|
* @since 2015-08-17
|
||||||
|
*/
|
||||||
|
public class IntervalWindow extends LinkedHashMap<Long, Integer>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Serial
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 201508171315L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The upper execution limit per interval
|
||||||
|
*/
|
||||||
|
private final int INTERVAL_LIMIT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of intervals to track
|
||||||
|
*/
|
||||||
|
private final int INTERVAL_WINDOW_SIZE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current interval being filled.
|
||||||
|
*/
|
||||||
|
private long currentInterval = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The moving total of slots used in the window
|
||||||
|
*/
|
||||||
|
private int slotsUsedInWindow = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum interval index that can be considered.
|
||||||
|
*/
|
||||||
|
private long minimumInterval = 0;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value in the map, or a default.
|
||||||
|
* Implemented in JSE8
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param defaultValue
|
||||||
|
* @return the value
|
||||||
|
*/
|
||||||
|
private final int getOrDefault(Long key,
|
||||||
|
Integer defaultValue)
|
||||||
|
{
|
||||||
|
if (get(key) != null)
|
||||||
|
{
|
||||||
|
return get(key);
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decreases the running total by the number of slots used in the
|
||||||
|
* interval leaving the moving window.
|
||||||
|
* <p>
|
||||||
|
* The value in map is the number of free slots left in the interval.
|
||||||
|
*
|
||||||
|
* @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)
|
||||||
|
*/
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<Long, Integer> eldest)
|
||||||
|
{
|
||||||
|
if (INTERVAL_WINDOW_SIZE < size())
|
||||||
|
{
|
||||||
|
slotsUsedInWindow -= (INTERVAL_LIMIT - eldest.getValue());
|
||||||
|
minimumInterval = eldest.getKey();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to allocate a slot in the given interval within the rate limit.
|
||||||
|
*
|
||||||
|
* @param interval
|
||||||
|
* the interval
|
||||||
|
* @return true is a slot was allocated in the interval
|
||||||
|
*/
|
||||||
|
public boolean allocateSlot(long interval)
|
||||||
|
{
|
||||||
|
boolean isSlotAllocated = false;
|
||||||
|
int freeSlots = 0; // Free slots in the interval
|
||||||
|
|
||||||
|
if (interval > minimumInterval)
|
||||||
|
/*
|
||||||
|
* Cheap range check is OK
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
synchronized (this)
|
||||||
|
/*
|
||||||
|
* Synchronize allocate on this object to ensure that cache is consistent
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
if ((freeSlots = getOrDefault(interval,
|
||||||
|
INTERVAL_LIMIT)) > 0)
|
||||||
|
/*
|
||||||
|
* There are free slots in this interval to execute this thread
|
||||||
|
* Break out of the loop and return.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
if (currentInterval > 0 && currentInterval != interval)
|
||||||
|
/*
|
||||||
|
* Update the running total of slots used in window
|
||||||
|
* with past values only once past the first interval.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
slotsUsedInWindow +=
|
||||||
|
INTERVAL_LIMIT
|
||||||
|
- getOrDefault(currentInterval,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
put(currentInterval = interval, freeSlots - 1); // Maximum is RATE_LIMIT - 1
|
||||||
|
isSlotAllocated = true;
|
||||||
|
} // if
|
||||||
|
} // synchronized
|
||||||
|
} // if
|
||||||
|
|
||||||
|
return isSlotAllocated;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the moving average number of slots allocated for work during
|
||||||
|
* the present window but excluding the currently filling interval
|
||||||
|
*
|
||||||
|
* @return the average number of slots used
|
||||||
|
*/
|
||||||
|
public float getAverageSlotUsed()
|
||||||
|
{
|
||||||
|
return slotsUsedInWindow / (INTERVAL_WINDOW_SIZE - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check window size parameters for range.
|
||||||
|
*
|
||||||
|
* @param intervalWindowSize
|
||||||
|
* the proposed window size
|
||||||
|
* @return the window size
|
||||||
|
*/
|
||||||
|
private static int checkWindowSize(int intervalWindowSize)
|
||||||
|
{
|
||||||
|
if (intervalWindowSize < 2)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Interval Window Size cannot be smaller than 2");
|
||||||
|
}
|
||||||
|
return intervalWindowSize;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an {@code IntervalWindow} of a window size of two that limits the
|
||||||
|
* number of successful allocations in each interval.
|
||||||
|
*
|
||||||
|
* @param intervalLimit
|
||||||
|
* the maximum number of allocations per interval.
|
||||||
|
*/
|
||||||
|
public IntervalWindow(int intervalLimit)
|
||||||
|
{
|
||||||
|
super(2, 1);
|
||||||
|
INTERVAL_WINDOW_SIZE = 2;
|
||||||
|
INTERVAL_LIMIT = intervalLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an {@code IntervalWindow} of a given window size that limits the
|
||||||
|
* number of successful allocations in each interval.
|
||||||
|
*
|
||||||
|
* @param intervalLimit
|
||||||
|
* the maximum number of allocations per interval.
|
||||||
|
* @param intervalWindow
|
||||||
|
* the number if intervals to track, must be at least two
|
||||||
|
*/
|
||||||
|
public IntervalWindow(int intervalLimit, int intervalWindow)
|
||||||
|
{
|
||||||
|
super(checkWindowSize(intervalWindow),
|
||||||
|
1);
|
||||||
|
INTERVAL_WINDOW_SIZE = intervalWindow;
|
||||||
|
INTERVAL_LIMIT = intervalLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
package org.jeecg.modules.business.domain.job;
|
||||||
|
|
||||||
|
import static java.lang.Integer.MAX_VALUE;
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.SynchronousQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@code ExecutorService} that throttles the amount of work released
|
||||||
|
* for execution per time period.
|
||||||
|
*
|
||||||
|
* @author martinb
|
||||||
|
* @since 2015-08-17
|
||||||
|
*/
|
||||||
|
public class ThrottlingExecutorService extends ThreadPoolExecutor {
|
||||||
|
/**
|
||||||
|
* The interval window cache
|
||||||
|
*/
|
||||||
|
private final IntervalWindow INTERVAL_WINDOW;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rate limit interval in milliseconds
|
||||||
|
*/
|
||||||
|
private final long RATE_INTERVAL_MILLISECONDS;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caching, dynamic rate limiting {@code ExecutorService}
|
||||||
|
*
|
||||||
|
* @param rateLimit
|
||||||
|
* the rate limit
|
||||||
|
* @param unit
|
||||||
|
* the rate limit time unit
|
||||||
|
*/
|
||||||
|
private ThrottlingExecutorService(
|
||||||
|
int rateLimit,
|
||||||
|
TimeUnit unit)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Create a CACHING ExecutorService
|
||||||
|
*/
|
||||||
|
super(0, MAX_VALUE,
|
||||||
|
60L, SECONDS,
|
||||||
|
new SynchronousQueue<Runnable>());
|
||||||
|
|
||||||
|
INTERVAL_WINDOW = new IntervalWindow(rateLimit);
|
||||||
|
RATE_INTERVAL_MILLISECONDS = unit.toMillis(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixed size rate limiting {@code ExecutorService}
|
||||||
|
*
|
||||||
|
* @param parallelism
|
||||||
|
* @param rateLimit
|
||||||
|
* @param unit
|
||||||
|
*/
|
||||||
|
private ThrottlingExecutorService(int parallelism,
|
||||||
|
int rateLimit,
|
||||||
|
TimeUnit unit)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Create a FIXED ExecutorService
|
||||||
|
*/
|
||||||
|
super(parallelism, parallelism, 0, MILLISECONDS,
|
||||||
|
new LinkedBlockingQueue<Runnable>());
|
||||||
|
|
||||||
|
INTERVAL_WINDOW = new IntervalWindow(rateLimit);
|
||||||
|
RATE_INTERVAL_MILLISECONDS = unit.toMillis(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces a throttling ExecutorService
|
||||||
|
* <p>
|
||||||
|
* Evaluates the parameters and generates an appropriate ExecutorService
|
||||||
|
*
|
||||||
|
* @param parallelism
|
||||||
|
* how many threads
|
||||||
|
* @param rateLimit
|
||||||
|
* work per time unit
|
||||||
|
* @param unit
|
||||||
|
* the time unit
|
||||||
|
* @return the ExecutorService
|
||||||
|
*/
|
||||||
|
public static ExecutorService createExecutorService(int parallelism,
|
||||||
|
int rateLimit,
|
||||||
|
TimeUnit unit)
|
||||||
|
{
|
||||||
|
if (parallelism > 0)
|
||||||
|
/*
|
||||||
|
* Fixed ExecutorService
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
return new ThrottlingExecutorService(parallelism,
|
||||||
|
rateLimit > 0 ? rateLimit : MAX_VALUE,
|
||||||
|
unit);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
/*
|
||||||
|
* Caching ExecutorService
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
return new ThrottlingExecutorService(
|
||||||
|
rateLimit > 0 ? rateLimit : MAX_VALUE,
|
||||||
|
unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throttles the execution before executing the task to achieve the desired
|
||||||
|
* rate.
|
||||||
|
*
|
||||||
|
* @see java.util.concurrent.ThreadPoolExecutor#execute(java.lang.Runnable)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void execute(final Runnable task)
|
||||||
|
{
|
||||||
|
throttle();
|
||||||
|
super.execute(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throttles if the thread can not be allocated in the current time
|
||||||
|
* interval,
|
||||||
|
* forcing it to wait to the next interval.
|
||||||
|
*/
|
||||||
|
private void throttle()
|
||||||
|
{
|
||||||
|
long interval = 0; // The interval index
|
||||||
|
long milliTime = System.currentTimeMillis(); // The current time
|
||||||
|
long offset = milliTime % RATE_INTERVAL_MILLISECONDS; // Interval offset
|
||||||
|
|
||||||
|
while (!INTERVAL_WINDOW.allocateSlot(
|
||||||
|
(interval = (milliTime + offset) / RATE_INTERVAL_MILLISECONDS)))
|
||||||
|
/*
|
||||||
|
* Cannot allocate free slots in this interval.
|
||||||
|
* Calculate the required pause to get to the next interval and sleep
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
int pause = (int) (((interval + 1)
|
||||||
|
* RATE_INTERVAL_MILLISECONDS)
|
||||||
|
- milliTime + offset);
|
||||||
|
try
|
||||||
|
/*
|
||||||
|
* Try to sleep the thread for a pause of nanoseconds
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
Thread.sleep(pause);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
milliTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
} // while
|
||||||
|
}
|
||||||
|
}
|
2
pom.xml
2
pom.xml
|
@ -2,7 +2,7 @@
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.jeecgframework.boot</groupId>
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
<artifactId>jeecg-boot-parent</artifactId>
|
<artifactId>jeecg-boot-parent</artifactId>
|
||||||
<version>1.6.0</version>
|
<version>1.6.1</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>WIA APP ${project.version} </name>
|
<name>WIA APP ${project.version} </name>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue