e.g: private String checkXXX(String value,Integer index), the return value can be empty or
- * “true”, or other error message which will be added to errorMessage
- *
- */
- protected void initContext() {
- bindClass = this.getGenericClass();
- val declaredFields = bindClass.getDeclaredFields();
- val fieldNameArray = new String[declaredFields.length];
- for (var index = 0; index < declaredFields.length; index++) {
- val declaredField = declaredFields[index];
- fieldNameArray[index] = declaredField.getName();
- }
- log.info("Generated {} field name array by reflection, fieldNameArray: {}", bindClass.getSimpleName(),
- fieldNameArray);
- this.setFieldNameArray(fieldNameArray);
- }
-
- /**
- * Gets generic class.
- *
- * @return the generic class
- */
- private Class getGenericClass() {
- val type = getClass().getGenericSuperclass();
- log.info("Got type by reflection, typeName: {}", type.getTypeName());
- if (type instanceof ParameterizedType) {
- val parameterizedType = (ParameterizedType) type;
- val typeName = parameterizedType.getActualTypeArguments()[0].getTypeName();
- Class aClass;
- try {
- //noinspection unchecked
- aClass = (Class) Class.forName(typeName);
- } catch (ClassNotFoundException e) {
- log.error("Exception occurred when looking for class!", e);
- throw new RuntimeException(
- "Exception occurred when looking for class! Exception message:" + e.getMessage());
- }
- return aClass;
- }
- throw new RuntimeException("Cannot find the type from the generic class!");
- }
-
- /**
- * Execute database operation.
- *
- * @param beanList the bean list that can be used for DB operations
- * @throws Exception the exception
- */
- protected abstract void executeDatabaseOperation(List beanList) throws Exception;
-
- /**
- * Validate before adding to bean list boolean.
- *
- * @param beanList the bean list that contains validated bean
- * @param bean the bean that needs to be validated
- * @param index the index that is the reference to the row number of the Excel file
- * @return the boolean
- * @throws IllegalArgumentException the illegal argument exception
- */
- protected abstract boolean validateBeforeAddToBeanList(List beanList,
- ExcelImportBeanType bean,
- int index) throws IllegalArgumentException;
-
- /**
- * Sets field name array.
- *
- * @param fieldNameArray the field name array
- */
- public void setFieldNameArray(String[] fieldNameArray) {
- this.fieldNameArray = fieldNameArray;
- }
-
- /**
- * 上传文件到本地(暂时)
- *
- * TODO: check the method if is necessary or not
- *
- * @param itemBytes the item bytes
- * @param fileName the file name
- * @return File file
- */
- private File uploadTempFile(byte[] itemBytes, String fileName) {
- val fileFullPath = TEMP_FILE_PATH + fileName;
- log.info("上传文件到本地(暂时)。文件绝对路径:{}", fileFullPath);
- // 新建文件
- val file = new File(fileFullPath);
- if (!file.getParentFile().exists()) {
- //noinspection ResultOfMethodCallIgnored
- file.getParentFile().mkdir();
- }
- // 上传
- // FileCopyUtils.copy(itemBytes, file);
- return file;
- }
-
- /**
- * Upload file.
- *
- * @param request the request
- * @return File file
- * @throws IOException the io exception
- * @see
- * Upload large files : Spring Boot
- */
- @SuppressWarnings("AlibabaRemoveCommentedCode")
- private File uploadFile(MultipartHttpServletRequest request) throws IOException {
- val multipartFile = request.getFileMap().get(FILE_KEY);
- // Don't do this.
- // it loads all of the bytes in java heap memory that leads to OutOfMemoryError. We'll use stream instead.
- // byte[] fileBytes = multipartFile.getBytes();
- @Cleanup val fileStream = new BufferedInputStream(multipartFile.getInputStream());
- LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
- val fileName = String.format("%s%s",
- LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")),
- multipartFile.getOriginalFilename());
- val targetFile = new File(TEMP_FILE_PATH + fileName);
- FileUtils.copyInputStreamToFile(fileStream, targetFile);
- uploadFileToSftp(targetFile);
- return targetFile;
- }
-
- /**
- * Read the file into workbook.
- *
- * @param file the file
- * @return the workbook
- * @throws IOException the io exception
- */
- private Workbook readFile(@NonNull File file) throws IOException {
- Workbook workbook = null;
- val extension = FilenameUtils.getExtension(file.getName());
- val bufferedInputStream = new BufferedInputStream(new FileInputStream(file));
- if (XLS.equals(extension)) {
- POIFSFileSystem poifsFileSystem = new POIFSFileSystem(bufferedInputStream);
- workbook = new HSSFWorkbook(poifsFileSystem);
- bufferedInputStream.close();
- } else if (XLSX.equals(extension)) {
- workbook = new XSSFWorkbook(bufferedInputStream);
- }
- return workbook;
- }
-
- /**
- * Upload file to SFTP
- *
- * @param file the file
- */
- private void uploadFileToSftp(File file) {
- val sftpUploadFile = SftpUploadFile.builder()
- .fileToBeUploaded(file)
- .fileExistsMode(FileExistsMode.REPLACE)
- .subDirectory(SFTP_DIR).build();
- try {
- sftpHelper.upload(sftpUploadFile);
- } catch (Exception e) {
- log.error("Exception occurred when uploading file to SFTP! Exception message:{}", e.getMessage());
- }
- }
-
- /**
- * Filter sheet. In default, will proceed all sheets.
- *
- * @param sheet the sheet
- * @return the boolean
- */
- protected boolean filterSheet(Sheet sheet) {
- return true;
- }
-
- /**
- *
Bind data from the workbook. Processes below:
- *
- *
Bind data to the instance of bindClass, according to the fieldNameArray.
- *
- *
Validate the field by callback method
- *
- *
- *
- * @param workbook the workbook
- * @throws Exception the exception
- */
- @SuppressWarnings("RedundantThrows")
- private void bindData(Workbook workbook) throws Exception {
- for (int index = 0; index < workbook.getNumberOfSheets(); index++) {
- val sheet = workbook.getSheetAt(index);
- if (!filterSheet(sheet)) {
- continue;
- }
- // Specify current sheet location
- sheetLocation.set(index + 1);
- sheet.getSheetName();
- // Set Reading row count
- readingRowCount.set(readingRowCount.get() + sheet.getLastRowNum() - sheet.getFirstRowNum());
- // Check if exceeding the MAXIMUM_ROW_COUNT
- if (readingRowCount.get() > excelImportConfiguration.getMaximumRowCount()) {
- setErrorMessage(String.format(
- "The amount of importing data cannot be greater than %d (Table head included)! " +
- "Current reading row count: %d", excelImportConfiguration.getMaximumRowCount(),
- readingRowCount.get()));
- continue;
- }
- // Check if the readingRowCount is equal to zero
- if (readingRowCount.get() == 0) {
- setErrorMessage("Not available data to import. Check Excel again.");
- continue;
- }
- // Check if the first row is equal to null
- if (sheet.getRow(0) == null) {
- setErrorMessage(String.format("Sheet [%s] (No. %d) format is invalid: no title! ", sheet.getSheetName(),
- sheetLocation.get()));
- // If the sheet is not valid, then skip it
- continue;
- }
- val startIndex = sheet.getRow(0).getFirstCellNum();
- val endIndex = sheet.getRow(0).getLastCellNum();
- // Parse from the second row
- for (var i = sheet.getFirstRowNum() + 1; i <= sheet.getLastRowNum(); i++) {
- // Specify current row location
- rowLocation.set(i + 1);
- val row = sheet.getRow(i);
- if (isBlankRow(row)) {
- // Blank row will not be considered as a effective row, not be included in `readingRowCount`
- readingRowCount.set(readingRowCount.get() - 1);
- continue;
- }
- // Bind row to bean
- bindRowToBean(row, startIndex, endIndex);
- }
- }
- }
-
- /**
- * Check if the row is blank. If there is one cell of the row is not blank, then the row is not blank.
- *
- * @param row the row
- * @return true if the row is blank; or vice versa.
- */
- private boolean isBlankRow(Row row) {
- if (row == null) {
- return true;
- }
- Cell cell;
- var value = "";
- for (var i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) {
- cell = row.getCell(i, Row.MissingCellPolicy.RETURN_BLANK_AS_NULL);
- if (cell != null) {
- switch (cell.getCellType()) {
- case STRING:
- value = cell.getStringCellValue().trim();
- break;
- case NUMERIC:
- value = String.valueOf((int) cell.getNumericCellValue());
- break;
- case BOOLEAN:
- value = String.valueOf(cell.getBooleanCellValue());
- break;
- case FORMULA:
- value = String.valueOf(cell.getCellFormula());
- break;
- default:
- break;
- }
- if (StrUtil.isNotBlank(value)) {
- return false;
- }
- }
- }
- return true;
- }
-
- /**
- * Bind row to bean.
- *
- * @param row the row
- * @param startIndex the start index
- * @param endIndex the end index
- */
- private void bindRowToBean(Row row, int startIndex, int endIndex) {
- Assert.notNull(this.bindClass, "bindClass must not be null!");
- Assert.notNull(this.fieldNameArray, "fieldNameArray must not be null!");
- var bindingResult = false;
- try {
- // New bean instance
- val bean = this.bindClass.getDeclaredConstructor().newInstance();
- val beanInfo = Introspector.getBeanInfo(bean.getClass());
- val propertyDescriptors = beanInfo.getPropertyDescriptors();
- for (var index = startIndex; index < endIndex; index++) {
- // If found more data, then binding failed
- if (index >= fieldNameArray.length) {
- bindingResult = false;
- setErrorMessage(
- String.format("Found redundant data on row number %d. Check again.", rowLocation.get()));
- break;
- }
- // Get the field that needs binding
- val fieldName = fieldNameArray[index];
- PropertyDescriptor propertyDescriptor = null;
- for (val pd : propertyDescriptors) {
- if (pd.getName().equals(fieldName)) {
- propertyDescriptor = pd;
- break;
- }
- }
- if (ObjectUtil.isNull(propertyDescriptor)) {
- throw new RuntimeException("Cannot find the field in the specify class!");
- }
- val value = getCellValue2String(row.getCell(index));
- // Start to bind
- // Specify current column location
- columnLocation.set(index + 1);
- // Execute custom validation method
- val customValidationMethod = this.checkMethodMap.get(fieldName);
- Object returnObj = null;
- if (ObjectUtil.isNotNull(customValidationMethod)) {
- returnObj = customValidationMethod.invoke(this, value, rowLocation.get(), bean);
- }
- val validationResult = returnObj == null ? null : returnObj.toString();
- // If `validationResult` is blank or equal to "true"
- if (StrUtil.isBlank(validationResult) || Boolean.TRUE.toString().equals(validationResult)) {
- bindingResult = bind(value, propertyDescriptor, bean);
- } else {
- bindingResult = false;
- // If `validationResult` is not equal to "false" then add to error message list
- if (!Boolean.FALSE.toString().equals(validationResult)) {
- setErrorMessage(validationResult);
- }
- }
- // Finished binding
- if (!bindingResult) {
- break;
- }
- }
- if (bindingResult) {
- bindingResult = validateBeforeAddToBeanList(this.beanList.get(), bean, rowLocation.get());
- }
- if (bindingResult) {
- this.beanList.get().add(bean);
- }
- } catch (IntrospectionException
- | InvocationTargetException
- | InstantiationException
- | IllegalAccessException
- | NoSuchMethodException e) {
- log.error("bindRowToBean method has encountered a problem!", e);
- exceptionOccurred.set(true);
- val errorMessage = String.format(
- "Exception occurred when binding and validating the data of row number %d. " +
- "Exception message: %s", rowLocation.get(), e.getMessage());
- setErrorMessage(errorMessage);
- val lastCellNum = row.getLastCellNum();
- val errorInformationCell = row.createCell(fieldNameArray.length);
- errorInformationCell.setCellValue(errorMessage);
- val firstSheet = workbookWithErrorMessage.get().getSheetAt(0);
- val row1 = firstSheet.createRow(firstSheet.getLastRowNum() + 1);
- PoiUtil.copyRow(true, workbookWithErrorMessage.get(), row, row1);
- }
- }
-
- /**
- * Gets cell value 2 string.
- *
- * @param cell the cell
- * @return the string
- */
- private String getCellValue2String(Cell cell) {
- var returnString = "";
- if (ObjectUtil.isNull(cell)) {
- return returnString;
- }
- switch (cell.getCellType()) {
- case BLANK:
- return "";
- case NUMERIC:
- // If it's date
- if (DateUtil.isCellDateFormatted(cell)) {
- val date = cell.getDateCellValue();
- val dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- returnString = dateFormat.format(date);
- return returnString;
- }
- val bigDecimal = new BigDecimal(String.valueOf(cell.getNumericCellValue()).trim());
- // Keep decimal fraction parts which are not zero
- val tempStr = bigDecimal.toPlainString();
- val dotIndex = tempStr.indexOf(".");
- if ((bigDecimal.doubleValue() - bigDecimal.longValue()) == 0 && dotIndex > 0) {
- returnString = tempStr.substring(0, dotIndex);
- } else {
- returnString = tempStr;
- }
- return returnString;
- case STRING:
- if (cell.getStringCellValue() != null) {
- returnString = cell.getStringCellValue().trim();
- }
- return returnString;
- default:
- return returnString;
- }
- }
-
- /**
- * Register bind handler methods.
- */
- private void registerBindHandlerMethods() {
- try {
- val bindStringField = ReflectionUtils.findMethod(this.getClass(),
- "bindStringField",
- String.class,
- PropertyDescriptor.class,
- Object.class);
- val bindIntField = ReflectionUtils.findMethod(this.getClass(),
- "bindIntField",
- String.class,
- PropertyDescriptor.class,
- Object.class);
- val bindLongField = ReflectionUtils.findMethod(this.getClass(),
- "bindLongField",
- String.class,
- PropertyDescriptor.class,
- Object.class);
- val bindFloatField = ReflectionUtils.findMethod(this.getClass(),
- "bindFloatField",
- String.class,
- PropertyDescriptor.class,
- Object.class);
- val bindDoubleField = ReflectionUtils.findMethod(this.getClass(),
- "bindDoubleField",
- String.class,
- PropertyDescriptor.class,
- Object.class);
- val bindLocalDateTimeField = ReflectionUtils.findMethod(this.getClass(),
- "bindLocalDateTimeField",
- String.class,
- PropertyDescriptor.class,
- Object.class);
- this.bindMethodMap.put(String.class, bindStringField);
- this.bindMethodMap.put(Integer.class, bindIntField);
- this.bindMethodMap.put(Long.class, bindLongField);
- this.bindMethodMap.put(Float.class, bindFloatField);
- this.bindMethodMap.put(Double.class, bindDoubleField);
- this.bindMethodMap.put(LocalDateTime.class, bindLocalDateTimeField);
- } catch (Exception e) {
- log.error("The bindMethod required was not found in this class!", e);
- }
- }
-
- /**
- * Real binding value to the bean's field.
- *
- *
- * @param value the string value of the excel cell
- * @param propertyDescriptor the the field of the bean
- * @param bean the bean
- * @return true if the binding succeeded, or vice versa.
- * @throws RuntimeException the runtime exception
- */
- private Boolean bind(String value, PropertyDescriptor propertyDescriptor, Object bean) throws RuntimeException {
- boolean result;
- val fieldType = propertyDescriptor.getPropertyType();
- if (!this.bindMethodMap.containsKey(fieldType)) {
- throw new RuntimeException(
- String.format("The bindMethod required was not found in bindMethodMap! Field type: %s",
- fieldType.getSimpleName()));
- }
- val bindMethod = this.bindMethodMap.get(fieldType);
- value = StrUtil.isBlank(value) ? null : value;
- try {
- // Why is the parameter 'this' required? Because it's this class calling the 'bindMethod',
- // and the 'bingMethod' belongs to this class object.
- result = (Boolean) bindMethod.invoke(this, value, propertyDescriptor, bean);
- } catch (IllegalAccessException
- | IllegalArgumentException
- | InvocationTargetException e) {
- val exceptionMessage = new StringBuilder("Exception occurred when binding! ");
- if (!StrUtil.isBlank(e.getMessage())) {
- log.error("Exception occurred when invoking method!", e);
- exceptionMessage.append(e.getMessage()).append(" ");
- }
- if (ObjectUtil.isNotNull(e.getCause()) && !StrUtil.isBlank(e.getCause().getMessage())) {
- log.error("Exception occurred when invoking method!", e.getCause());
- exceptionMessage.append(e.getCause().getMessage());
- }
- throw new RuntimeException(exceptionMessage.toString());
- }
- return result;
- }
-
- /**
- * Bind int field boolean.
- *
- * @param value the value
- * @param propertyDescriptor the property descriptor
- * @param bean the bean
- * @return the boolean
- * @throws IllegalAccessException the illegal access exception
- */
- private Boolean bindIntField(String value, PropertyDescriptor propertyDescriptor, Object bean) throws IllegalAccessException {
- try {
- var realValue = value == null ? null : Integer.parseInt(value);
- if (ObjectUtil.isNull(realValue) && propertyDescriptor.getPropertyType() == int.class) {
- realValue = 0;
- }
- propertyDescriptor.getWriteMethod().invoke(bean, realValue);
- } catch (NumberFormatException | InvocationTargetException e) {
- log.error("Exception occurred when binding int/Integer field! Exception message: {}, value: {}, field: {}",
- e.getMessage(), value, propertyDescriptor.getName());
- val formattedMessage = String.format("Invalid data of the row %d, col %d, must be integer",
- rowLocation.get(), columnLocation.get());
- setErrorMessage(formattedMessage);
- throw new IllegalArgumentException(formattedMessage);
- }
- return true;
- }
-
- /**
- * Bind long field boolean.
- *
- * @param value the value
- * @param propertyDescriptor the property descriptor
- * @param bean the bean
- * @return the boolean
- * @throws IllegalAccessException the illegal access exception
- */
- private Boolean bindLongField(String value, PropertyDescriptor propertyDescriptor, Object bean) throws IllegalAccessException {
- try {
- var realValue = value == null ? null : (long) Double.parseDouble(value);
- if (ObjectUtil.isNull(realValue) && propertyDescriptor.getPropertyType() == long.class) {
- realValue = 0L;
- }
- propertyDescriptor.getWriteMethod().invoke(bean, realValue);
- } catch (NumberFormatException | InvocationTargetException e) {
- log.error("Exception occurred when binding long/Long field! Exception message: {}, value: {}, field: {}",
- e.getMessage(), value, propertyDescriptor.getName());
- val formattedMessage = String.format("Invalid data of the row %d, col %d, must be long integer",
- rowLocation.get(), columnLocation.get());
- setErrorMessage(formattedMessage);
- throw new IllegalArgumentException(formattedMessage);
- }
- return true;
- }
-
- /**
- * Bind float field boolean.
- *
- * @param value the value
- * @param propertyDescriptor the property descriptor
- * @param bean the bean
- * @return the boolean
- * @throws IllegalAccessException the illegal access exception
- */
- private Boolean bindFloatField(String value, PropertyDescriptor propertyDescriptor, Object bean) throws IllegalAccessException {
- try {
- var realValue = value == null ? null : Float.parseFloat(value);
- if (ObjectUtil.isNull(realValue) && propertyDescriptor.getPropertyType() == float.class) {
- realValue = 0F;
- }
- propertyDescriptor.getWriteMethod().invoke(bean, realValue);
- } catch (NumberFormatException | InvocationTargetException e) {
- log.error("Exception occurred when binding float/Float field! Exception message: {}, value: {}, field: {}",
- e.getMessage(), value, propertyDescriptor.getName());
- val formattedMessage = String.format("Invalid data of the row %d, col %d, must be float",
- rowLocation.get(), columnLocation.get());
- setErrorMessage(formattedMessage);
- throw new IllegalArgumentException(formattedMessage);
- }
- return true;
- }
-
- /**
- * Bind double field boolean.
- *
- * @param value the value
- * @param propertyDescriptor the property descriptor
- * @param bean the bean
- * @return the boolean
- * @throws IllegalAccessException the illegal access exception
- */
- private Boolean bindDoubleField(String value, PropertyDescriptor propertyDescriptor, Object bean) throws IllegalAccessException {
- try {
- var realValue = value == null ? null : Double.parseDouble(value);
- if (ObjectUtil.isNull(realValue) && propertyDescriptor.getPropertyType() == double.class) {
- realValue = 0D;
- }
- propertyDescriptor.getWriteMethod().invoke(bean, realValue);
- } catch (NumberFormatException | InvocationTargetException e) {
- log.error(
- "Exception occurred when binding double/Double field! Exception message: {}, value: {}, field: {}",
- e.getMessage(), value, propertyDescriptor.getName());
- val formattedMessage = String.format("Invalid data of the row %d, col %d, must be double",
- rowLocation.get(), columnLocation.get());
- setErrorMessage(formattedMessage);
- throw new IllegalArgumentException(formattedMessage);
- }
- return true;
- }
-
- /**
- * Bind string field boolean.
- *
- * @param value the value
- * @param propertyDescriptor the property descriptor
- * @param bean the bean
- * @return the boolean
- * @throws IllegalAccessException the illegal access exception
- */
- private Boolean bindStringField(String value, PropertyDescriptor propertyDescriptor, Object bean) throws IllegalAccessException {
- try {
- propertyDescriptor.getWriteMethod().invoke(bean, value);
- } catch (InvocationTargetException e) {
- log.error("Exception occurred when binding String field! Exception message: {}, value: {}, field: {}",
- e.getMessage(), value, propertyDescriptor.getName());
- val formattedMessage = String.format("Invalid data of the row %d, col %d, must be string",
- rowLocation.get(), columnLocation.get());
- setErrorMessage(formattedMessage);
- throw new IllegalArgumentException(formattedMessage);
- }
- return true;
- }
-
- /**
- * Bind local date time field boolean.
- *
- * @param value the value
- * @param propertyDescriptor the property descriptor
- * @param bean the bean
- * @return the boolean
- * @throws IllegalAccessException the illegal access exception
- */
- private Boolean bindLocalDateTimeField(String value, PropertyDescriptor propertyDescriptor, Object bean) throws IllegalAccessException {
- try {
- val date = value == null ? null : LocalDateTime.parse(value,
- DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
- propertyDescriptor.getWriteMethod().invoke(bean, date);
- } catch (DateTimeParseException | InvocationTargetException e) {
- log.error(
- "Exception occurred when binding LocalDateTime field! Exception message: {}, value: {}, field: {}",
- e.getMessage(), value, propertyDescriptor.getName());
- val formattedMessage = String.format("Invalid data of the row %d, col %d, must be date",
- rowLocation.get(), columnLocation.get());
- setErrorMessage(formattedMessage);
- throw new IllegalArgumentException(formattedMessage);
- }
- return true;
- }
-
- /**
- * Sets deny all. When exception occurred, deny all data or not
- *
- * @param denyAll the deny all
- */
- public void setDenyAll(Boolean denyAll) {
- this.denyAll = denyAll;
- }
-
- /**
- * Sets error message.
- *
- * @param errorInfo the error info
- */
- protected void setErrorMessage(String errorInfo) {
- this.errorMessageList.get().add(errorInfo);
- setReturnMessageList(errorInfo);
- }
-
- /**
- * Sets return message list.
- *
- * @param message the message
- */
- protected void setReturnMessageList(String message) {
- this.returnMessageList.get().add(message);
- }
-}
diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/MyBatisPlusMetaObjectHandler.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/CommonMetaObjectHandler.java
similarity index 52%
rename from spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/MyBatisPlusMetaObjectHandler.java
rename to spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/CommonMetaObjectHandler.java
index dfccb609..75aae1a9 100644
--- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/MyBatisPlusMetaObjectHandler.java
+++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/CommonMetaObjectHandler.java
@@ -1,24 +1,35 @@
package com.jmsoftware.maf.springcloudstarter.database;
+import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.jmsoftware.maf.common.domain.DeletedField;
-import com.jmsoftware.maf.springcloudstarter.util.UsernameUtil;
+import com.jmsoftware.maf.springcloudstarter.util.UserUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import java.time.LocalDateTime;
/**
- *
MyBatisPlusConfiguration
- *
- * Change description here.
- *
+ *
CommonMetaObjectHandler
+ *
CommonMetaObjectHandler will inject these fields automatically when executing INSERT or
+ * UPDATE.
+ *
This class is cooperating with the annotation @TableField. So the persistence Java class must be
+ * annotated by @TableField(value = COL_CREATED_BY, fill = INSERT) or @TableField(value =
+ * COL_MODIFIED_BY, fill = UPDATE).
+ *
+ *
+ *
MySQL Field Name
Field Name Java
+ *
created_by
createdBy
created_time
createdTime
modified_by
modifiedBy
modified_time
modifiedTime
deleted
deleted
+ *
+ *
*
- * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com
- * @date 2019-05-02 11:57
+ * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 8/23/2021 10:43 AM
+ * @see TableField
**/
@Slf4j
-public class MyBatisPlusMetaObjectHandler implements MetaObjectHandler {
+public class CommonMetaObjectHandler implements MetaObjectHandler {
/**
* The Java persistence object field name: createdBy
*/
@@ -46,18 +57,26 @@ public class MyBatisPlusMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 严格填充,只针对非主键的字段,只有该表注解了fill 并且 字段名和字段属性 能匹配到才会进行填充(null 值不填充)
- log.info("Starting to insert fill metaObject: {}", metaObject.getOriginalObject());
- this.strictInsertFill(metaObject, CREATED_BY_FIELD_NAME, String.class, UsernameUtil.getCurrentUsername())
+ if (log.isDebugEnabled()) {
+ log.debug("Starting to insert fill metaObject: {}", metaObject.getOriginalObject());
+ }
+ this.strictInsertFill(metaObject, CREATED_BY_FIELD_NAME, Long.class, UserUtil.getCurrentId())
.strictInsertFill(metaObject, CREATED_TIME_FIELD_NAME, LocalDateTime.class, LocalDateTime.now())
.strictInsertFill(metaObject, DELETED_FIELD_NAME, Byte.class, DeletedField.NOT_DELETED.getValue());
- log.info("Finished to insert fill metaObject: {}", metaObject.getOriginalObject());
+ if (log.isDebugEnabled()) {
+ log.debug("Finished to insert fill metaObject: {}", metaObject.getOriginalObject());
+ }
}
@Override
public void updateFill(MetaObject metaObject) {
- log.info("Starting to update fill metaObject: {}", metaObject.getOriginalObject());
- this.strictUpdateFill(metaObject, MODIFIED_BY_FIELD_NAME, String.class, UsernameUtil.getCurrentUsername())
+ if (log.isDebugEnabled()) {
+ log.debug("Starting to update fill metaObject: {}", metaObject.getOriginalObject());
+ }
+ this.strictUpdateFill(metaObject, MODIFIED_BY_FIELD_NAME, Long.class, UserUtil.getCurrentId())
.strictUpdateFill(metaObject, MODIFIED_TIME_FIELD_NAME, LocalDateTime.class, LocalDateTime.now());
- log.info("Finished to update fill metaObject: {}", metaObject.getOriginalObject());
+ if (log.isDebugEnabled()) {
+ log.debug("Finished to update fill metaObject: {}", metaObject.getOriginalObject());
+ }
}
}
diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceConfiguration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceConfiguration.java
index 8d87f23d..6e01a1a5 100644
--- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceConfiguration.java
+++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceConfiguration.java
@@ -29,6 +29,12 @@
@Slf4j
@ConditionalOnClass({MybatisPlusAutoConfiguration.class})
public class DataSourceConfiguration {
+ @Bean
+ public DruidDataSourceCreatorPostProcessor druidDataSourceCreatorPostProcessor() {
+ log.warn("Initial bean: '{}'", DruidDataSourceCreatorPostProcessor.class.getSimpleName());
+ return new DruidDataSourceCreatorPostProcessor();
+ }
+
/**
* Primary data source. Had to configure DynamicRoutingDataSource as primary. Otherwise
* MasterSlaveAutoRoutingPlugin will not be able to be injected datasource correctly.
diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DruidDataSourceCreatorPostProcessor.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DruidDataSourceCreatorPostProcessor.java
new file mode 100644
index 00000000..44d8b243
--- /dev/null
+++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DruidDataSourceCreatorPostProcessor.java
@@ -0,0 +1,65 @@
+package com.jmsoftware.maf.springcloudstarter.database;
+
+import com.baomidou.dynamic.datasource.creator.DruidDataSourceCreator;
+import lombok.extern.slf4j.Slf4j;
+import lombok.val;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+
+/**
+ * Description: DruidDataSourceCreatorPostProcessor, change description here.
+ *
+ * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 8/25/2021 11:27 AM
+ **/
+@Slf4j
+public class DruidDataSourceCreatorPostProcessor implements BeanPostProcessor {
+ @Nullable
+ @Override
+ public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
+ if (bean instanceof DruidDataSourceCreator) {
+ this.postProcessDynamicDataSourceProperties((DruidDataSourceCreator) bean);
+ }
+ return bean;
+ }
+
+ /**
+ * Post process dynamic data source properties. Enhance connection pool size by available processor count
+ * (logical processor count)
+ *
+ *
A formula which has held up pretty well across a lot of benchmarks for years is that for optimal throughput
+ * the number of active connections should be somewhere near ((core_count * 2) +
+ * effective_spindle_count). Core count should not include HT threads, even if hyperthreading is enabled
+ * . Effective spindle count is zero if the active data set is fully cached, and approaches the actual number of
+ * spindles as the cache hit rate falls. Benchmarks of WIP for version 9.2 suggest that this formula will need
+ * adjustment on that release. There hasn't been any analysis so far regarding how well the formula works
+ * with SSDs.
+ *
However you choose a starting point for a connection pool size, you should probably try incremental
+ * adjustments with your production system to find the actual "sweet spot" for your hardware and
+ * workload.
+ *
Remember that this "sweet spot" is for the number of connections that are actively doing
+ * work. Ignore mostly-idle connections used for system monitoring and control when working out an
+ * appropriate pool size. You should always make max_connections a bit bigger than the number of
+ * connections you enable in your connection pool. That way there are always a few slots available for direct
+ * connections for system maintenance and monitoring.
+ *
+ * @param bean the bean
+ * @see
+ * How to Find the Optimal Database Connection Pool Size
+ * @see
+ * Sizing the Connection Pool
+ */
+ private void postProcessDynamicDataSourceProperties(DruidDataSourceCreator bean) {
+ val cpuCoreCount = Runtime.getRuntime().availableProcessors();
+ val minConnectionPoolSize = cpuCoreCount * 2 + 1;
+ val maxConnectionPoolSize = cpuCoreCount * 3;
+ bean.getGConfig()
+ .setInitialSize(minConnectionPoolSize)
+ .setMinIdle(minConnectionPoolSize)
+ .setMaxActive(maxConnectionPoolSize);
+ log.warn("Druid connection pool enhanced by current cpuCoreCount: {}, initial size: {}, min idle: {}" +
+ ", max active: {}",
+ cpuCoreCount, minConnectionPoolSize, minConnectionPoolSize, maxConnectionPoolSize);
+ }
+}
diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/MyBatisPlusConfiguration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/MyBatisPlusConfiguration.java
index 09c4bfb5..2bef11fc 100644
--- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/MyBatisPlusConfiguration.java
+++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/MyBatisPlusConfiguration.java
@@ -70,8 +70,8 @@ public Interceptor masterSlaveAutoRoutingPlugin() {
}
@Bean
- public MyBatisPlusMetaObjectHandler myBatisPlusConfiguration() {
- log.warn("Initial bean: '{}'", MyBatisPlusMetaObjectHandler.class.getSimpleName());
- return new MyBatisPlusMetaObjectHandler();
+ public CommonMetaObjectHandler myBatisPlusConfiguration() {
+ log.warn("Initial bean: '{}'", CommonMetaObjectHandler.class.getSimpleName());
+ return new CommonMetaObjectHandler();
}
}
diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/helper/IpHelper.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/helper/IpHelper.java
index cfa208d0..d6623b3b 100644
--- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/helper/IpHelper.java
+++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/helper/IpHelper.java
@@ -43,7 +43,7 @@ public void onApplicationEvent(WebServerInitializedEvent event) {
public String getPublicIp() {
val jointProfiles = String.join(",", this.environment.getActiveProfiles());
if (StrUtil.containsIgnoreCase(jointProfiles, DEVELOPMENT_ENVIRONMENT)) {
- log.debug("Current active profiles for environment contains: {}. U", DEVELOPMENT_ENVIRONMENT);
+ log.debug("Current active profiles for environment contains: {}", DEVELOPMENT_ENVIRONMENT);
return this.getInternetIp();
}
// An API provided by https://whatismyipaddress.com/api
diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/util/PoiUtil.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/util/PoiUtil.java
deleted file mode 100644
index 136583ec..00000000
--- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/util/PoiUtil.java
+++ /dev/null
@@ -1,214 +0,0 @@
-package com.jmsoftware.maf.springcloudstarter.util;
-
-import lombok.val;
-import org.apache.poi.hssf.usermodel.*;
-import org.apache.poi.ss.usermodel.*;
-import org.apache.poi.ss.util.CellRangeAddress;
-
-/**
- *
PoiUtil
- *
- * Change description here.
- *
- * @author @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 2/18/2021 5:37 PM
- */
-@SuppressWarnings({"AlibabaRemoveCommentedCode", "unused"})
-public class PoiUtil {
- private PoiUtil() {
- }
-
- /**
- * Copy cell style.
- *
- * @param sourceCellStyle the source cell style
- * @param targetCellStyle the target cell style
- */
- public static void copyCellStyle(HSSFCellStyle sourceCellStyle, HSSFCellStyle targetCellStyle) {
- targetCellStyle.setAlignment(sourceCellStyle.getAlignment());
- // Boarder style
- targetCellStyle.setBorderBottom(sourceCellStyle.getBorderBottom());
- targetCellStyle.setBorderLeft(sourceCellStyle.getBorderLeft());
- targetCellStyle.setBorderRight(sourceCellStyle.getBorderRight());
- targetCellStyle.setBorderTop(sourceCellStyle.getBorderTop());
- targetCellStyle.setTopBorderColor(sourceCellStyle.getTopBorderColor());
- targetCellStyle.setBottomBorderColor(sourceCellStyle.getBottomBorderColor());
- targetCellStyle.setRightBorderColor(sourceCellStyle.getRightBorderColor());
- targetCellStyle.setLeftBorderColor(sourceCellStyle.getLeftBorderColor());
- // Background and foreground
- targetCellStyle.setFillBackgroundColor(sourceCellStyle.getFillBackgroundColor());
- targetCellStyle.setFillForegroundColor(sourceCellStyle.getFillForegroundColor());
- // Data format
- targetCellStyle.setDataFormat(sourceCellStyle.getDataFormat());
- targetCellStyle.setFillPattern(sourceCellStyle.getFillPattern());
- // toStyle.setFont(fromStyle.getFont(null));
- targetCellStyle.setHidden(sourceCellStyle.getHidden());
- targetCellStyle.setIndention(sourceCellStyle.getIndention());
- targetCellStyle.setLocked(sourceCellStyle.getLocked());
- targetCellStyle.setRotation(sourceCellStyle.getRotation());
- targetCellStyle.setVerticalAlignment(sourceCellStyle.getVerticalAlignment());
- targetCellStyle.setWrapText(sourceCellStyle.getWrapText());
- }
-
- /**
- * Copy sheet.
- *
- * @param workbook the workbook
- * @param sourceSheet the source sheet
- * @param targetSheet the target sheet
- * @param copyValue the copy value
- */
- public static void copySheet(HSSFWorkbook workbook, HSSFSheet sourceSheet, HSSFSheet targetSheet,
- boolean copyValue) {
- mergerRegion(sourceSheet, targetSheet);
- sourceSheet.rowIterator().forEachRemaining(oldRow -> {
- val newRow = targetSheet.createRow(oldRow.getRowNum());
- copyRow(workbook, (HSSFRow) oldRow, newRow, copyValue);
- });
- }
-
- /**
- * Copy row.
- *
- * @param workbook the workbook
- * @param sourceRow the source row
- * @param targetRow the target row
- * @param copyValue the copy value
- */
- public static void copyRow(HSSFWorkbook workbook, HSSFRow sourceRow, HSSFRow targetRow, boolean copyValue) {
- sourceRow.cellIterator().forEachRemaining(oldCell -> {
- val newCell = targetRow.createCell(oldCell.getColumnIndex());
- copyCell(workbook, oldCell, newCell, copyValue);
- });
- }
-
- /**
- * Merger region.
- *
- * @param sourceSheet the source sheet
- * @param targetSheet the target sheet
- */
- public static void mergerRegion(HSSFSheet sourceSheet, HSSFSheet targetSheet) {
- val sheetMergerCount = sourceSheet.getNumMergedRegions();
- for (var i = 0; i < sheetMergerCount; i++) {
- val mergedRegionAt = sourceSheet.getMergedRegion(i);
- targetSheet.addMergedRegion(mergedRegionAt);
- }
- }
-
- /**
- * Copy cell.
- *
- * @param workbook the workbook
- * @param sourceCell the source cell
- * @param targetCell the target cell
- * @param copyValue the copy value
- */
- public static void copyCell(HSSFWorkbook workbook, HSSFCell sourceCell, HSSFCell targetCell, boolean copyValue) {
- val newStyle = workbook.createCellStyle();
- copyCellStyle(sourceCell.getCellStyle(), newStyle);
- targetCell.setCellStyle(newStyle);
- if (sourceCell.getCellComment() != null) {
- targetCell.setCellComment(sourceCell.getCellComment());
- }
- val srcCellType = sourceCell.getCellType();
- targetCell.setCellType(srcCellType);
- if (copyValue) {
- switch (srcCellType) {
- case NUMERIC:
- if (DateUtil.isCellDateFormatted(sourceCell)) {
- targetCell.setCellValue(sourceCell.getDateCellValue());
- } else {
- targetCell.setCellValue(sourceCell.getNumericCellValue());
- }
- break;
- case STRING:
- targetCell.setCellValue(sourceCell.getRichStringCellValue());
- break;
- case BLANK:
- break;
- case BOOLEAN:
- targetCell.setCellValue(sourceCell.getBooleanCellValue());
- break;
- case ERROR:
- targetCell.setCellErrorValue(FormulaError.forInt(sourceCell.getErrorCellValue()));
- break;
- case FORMULA:
- targetCell.setCellFormula(sourceCell.getCellFormula());
- break;
- default:
- }
- }
- }
-
- /**
- * Copy row.
- *
- * @param copyValue the copy value
- * @param workbook the workbook
- * @param sourceRow the source row
- * @param targetRow the target row
- */
- public static void copyRow(boolean copyValue, Workbook workbook, Row sourceRow, Row targetRow) {
- targetRow.setHeight(sourceRow.getHeight());
-
- sourceRow.cellIterator().forEachRemaining(oldCell -> {
- Cell newCell = targetRow.createCell(oldCell.getColumnIndex());
- copyCell(workbook, oldCell, newCell, copyValue);
- });
-
- val worksheet = sourceRow.getSheet();
-
- for (var i = 0; i < worksheet.getNumMergedRegions(); i++) {
- val cellRangeAddress = worksheet.getMergedRegion(i);
- if (cellRangeAddress.getFirstRow() == sourceRow.getRowNum()) {
- val newCellRangeAddress =
- new CellRangeAddress(targetRow.getRowNum(),
- (targetRow.getRowNum() + (cellRangeAddress.getLastRow() - cellRangeAddress.getFirstRow())),
- cellRangeAddress.getFirstColumn(),
- cellRangeAddress.getLastColumn());
- worksheet.addMergedRegion(newCellRangeAddress);
- }
- }
- }
-
- /**
- * Copy cell.
- *
- * @param workbook the workbook
- * @param sourceCell the source cell
- * @param targetCell the target cell
- * @param copyValue the copy value
- */
- public static void copyCell(Workbook workbook, Cell sourceCell, Cell targetCell, boolean copyValue) {
- if (sourceCell.getCellComment() != null) {
- targetCell.setCellComment(sourceCell.getCellComment());
- }
- val srcCellType = sourceCell.getCellType();
- if (copyValue) {
- switch (srcCellType) {
- case NUMERIC:
- if (DateUtil.isCellDateFormatted(sourceCell)) {
- targetCell.setCellValue(sourceCell.getDateCellValue());
- } else {
- targetCell.setCellValue(sourceCell.getNumericCellValue());
- }
- break;
- case STRING:
- targetCell.setCellValue(sourceCell.getRichStringCellValue());
- break;
- case BLANK:
- break;
- case BOOLEAN:
- targetCell.setCellValue(sourceCell.getBooleanCellValue());
- break;
- case ERROR:
- targetCell.setCellErrorValue(sourceCell.getErrorCellValue());
- break;
- case FORMULA:
- targetCell.setCellFormula(sourceCell.getCellFormula());
- break;
- default:
- }
- }
- }
-}
diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/util/UserUtil.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/util/UserUtil.java
new file mode 100644
index 00000000..a0b8a6a8
--- /dev/null
+++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/util/UserUtil.java
@@ -0,0 +1,39 @@
+package com.jmsoftware.maf.springcloudstarter.util;
+
+import cn.hutool.core.util.StrUtil;
+import com.jmsoftware.maf.common.constant.MafHttpHeader;
+import lombok.extern.slf4j.Slf4j;
+import lombok.val;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+/**
+ * Description: UserUtil, change description here.
+ *
+ * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 6/28/2021 1:40 PM
+ **/
+@Slf4j
+public class UserUtil {
+ private UserUtil() {
+ }
+
+ public static String getCurrentUsername() {
+ val servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
+ return servletRequestAttributes.getRequest().getHeader(MafHttpHeader.X_USERNAME.getHeader());
+ }
+
+ /**
+ * Gets current id.
+ *
+ * @return the current id
+ * @throws IllegalArgumentException if X-ID is blank in HTTP header
+ */
+ public static Long getCurrentId() {
+ val servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
+ val xid = servletRequestAttributes.getRequest().getHeader(MafHttpHeader.X_ID.getHeader());
+ if (StrUtil.isBlank(xid)) {
+ throw new IllegalArgumentException("Invalid X-ID");
+ }
+ return Long.valueOf(xid);
+ }
+}
diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/util/UsernameUtil.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/util/UsernameUtil.java
deleted file mode 100644
index f85c94db..00000000
--- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/util/UsernameUtil.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.jmsoftware.maf.springcloudstarter.util;
-
-import com.jmsoftware.maf.common.constant.MafHttpHeader;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
-
-/**
- * Description: UsernameUtil, change description here.
- *
- * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 6/28/2021 1:40 PM
- **/
-public class UsernameUtil {
- private UsernameUtil() {
- }
-
- public static String getCurrentUsername() {
- final ServletRequestAttributes servletRequestAttributes =
- (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
- return servletRequestAttributes.getRequest().getHeader(MafHttpHeader.X_USERNAME.getHeader());
- }
-}
diff --git a/universal-ui/pom.xml b/universal-ui/pom.xml
index 58ccdf8b..7058e2f3 100644
--- a/universal-ui/pom.xml
+++ b/universal-ui/pom.xml
@@ -11,7 +11,7 @@
muscle-and-fitness-servercom.jmsoftware.maf
- 0.0.4
+ 0.0.5