diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000..b381d96c --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,34 @@ +# .github/release-drafter.yml +name-template: 'v$NEXT_PATCH_VERSION' +tag-template: 'v$NEXT_PATCH_VERSION' +categories: + - title: '新功能 ✨' + labels: + - 'feature' + - title: '修复 🐛' + labels: + - 'bug' + - title: '优化 ⚡️' + labels: + - 'improvement' +change-template: '- $TITLE (#$NUMBER)' +no-changes-template: '- No changes' +template: | + + [docker-docker.yml](https://github.com/jamebal/jmal-cloud-server/blob/master/docker-compose.example2.yml) + + #### 更新前备份数据库 + + ```shell + docker exec -it jmalcloud_mongodb mongodump -d jmalcloud -o /dump/$PREVIOUS_TAG --gzip --quiet + ``` + + ```shell + docker-compose pull && docker-compose up -d + ``` + + ## Changes in this release: + + $CHANGES + + **Full Changelog**: [$PREVIOUS_TAG...v$NEXT_PATCH_VERSION](https://github.com/jamebal/jmal-cloud-server/compare/$PREVIOUS_TAG...v$NEXT_PATCH_VERSION) diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 58bfcf9b..511411ca 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -2,7 +2,8 @@ name: Build nightly on: push: - branches: [ master ] + branches: + - 'master' workflow_dispatch: jobs: diff --git a/.github/workflows/nightly-nvidia-build.yml b/.github/workflows/nightly-nvidia-build.yml index 9bacc52a..601bcc86 100644 --- a/.github/workflows/nightly-nvidia-build.yml +++ b/.github/workflows/nightly-nvidia-build.yml @@ -2,7 +2,8 @@ name: Build nightly (nvidia) on: push: - branches: [ master ] + branches: + - 'master' workflow_dispatch: jobs: diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 00000000..f02e72e8 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,18 @@ +# .github/workflows/release-drafter.yml +name: Release Drafter + +on: + push: + branches: + - master + +jobs: + update_release_draft: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: release-drafter/release-drafter@v5 + with: + config-name: release-drafter.yml + env: + GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index fbb588ad..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: Create Release with Jar - -on: - push: - branches: - - release - -jobs: - build-and-release: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Set up JDK 17 - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: '17' - - - name: Build with Maven - run: mvn clean package -DskipTests - - - name: Extract project version from pom.xml - run: | - VERSION=$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec) - echo "PROJECT_VERSION=$VERSION" >> $GITHUB_ENV - - - name: Generate Release Notes - id: generate_release_notes - run: | - git fetch --prune --unshallow - LATEST_TAG=$(git describe --tags --abbrev=0) - FEAT_MSGS=$(git log $LATEST_TAG..HEAD --grep 'feat:' --pretty=format:"- %s%n" | sed 's/feat://') - FIX_MSGS=$(git log $LATEST_TAG..HEAD --grep 'fix:' --pretty=format:"- %s%n" | sed 's/fix://') - PERF_MSGS=$(git log $LATEST_TAG..HEAD --grep 'perf:' --pretty=format:"- %s%n" | sed 's/perf://') - RELEASE_NOTES="" - if [ -n "$FEAT_MSGS" ]; then - RELEASE_NOTES+="### 新功能 ✨\n${FEAT_MSGS}\n" - fi - if [ -n "$FIX_MSGS" ]; then - RELEASE_NOTES+="### 修复 🐛\n${FIX_MSGS}\n" - fi - if [ -n "$PERF_MSGS" ]; then - RELEASE_NOTES+="### 优化 🎨\n${PERF_MSGS}\n" - fi - echo -e "RELEASE_NOTES<> $GITHUB_ENV - - - name: Create Git tag - run: | - git config user.name "GitHub Actions" - git config user.email "github-actions@users.noreply.github.com" - git tag -a v${{ env.PROJECT_VERSION }} -m "v${{ env.PROJECT_VERSION }}" - git push origin v${{ env.PROJECT_VERSION }} - - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }} - with: - tag_name: v${{ env.PROJECT_VERSION }} - release_name: v${{ env.PROJECT_VERSION }} - body: | - ## Changes in this release: - ${{ env.RELEASE_NOTES }} - draft: false - prerelease: false - - - name: Upload Jar to Release - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./target/clouddisk-${{ env.PROJECT_VERSION }}.jar - asset_name: clouddisk-${{ env.PROJECT_VERSION }}.jar - asset_content_type: application/java-archive diff --git a/docker-compose.example2.yml b/docker-compose.example2.yml index c1c02c45..5ce5c6ba 100644 --- a/docker-compose.example2.yml +++ b/docker-compose.example2.yml @@ -18,6 +18,7 @@ services: TZ: Asia/Shanghai volumes: - ./docker/jmalcloud/mongodb/data/db:/data/db + - ./docker/jmalcloud/mongodb/backup:/dump restart: unless-stopped command: --wiredTigerCacheSizeGB 0.5 nginx: diff --git a/pom.xml b/pom.xml index e98229a6..cb969374 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.jmal clouddisk - 2.8.2 + 2.8.3 clouddisk Cloud Disk jar diff --git a/src/main/java/com/jmal/clouddisk/service/impl/CommonFileService.java b/src/main/java/com/jmal/clouddisk/service/impl/CommonFileService.java index 005dab5b..06f0c8ee 100644 --- a/src/main/java/com/jmal/clouddisk/service/impl/CommonFileService.java +++ b/src/main/java/com/jmal/clouddisk/service/impl/CommonFileService.java @@ -109,10 +109,10 @@ public class CommonFileService { @Autowired public LuceneService luceneService; - /*** + /** * 上传文件夹的写入锁缓存 */ - private final Cache uploadFolderLockCache = CaffeineUtil.getUploadFolderLockCache(); + private final Cache uploadFileLockCache = CaffeineUtil.getUploadFileLockCache(); protected static final Set FILE_PATH_LOCK = new CopyOnWriteArraySet<>(); @@ -304,12 +304,9 @@ public String createFile(String username, File file, String userId, Boolean isPu } String fileAbsolutePath = file.getAbsolutePath(); - Lock lock = null; - if (file.isDirectory()) { - lock = uploadFolderLockCache.get(fileAbsolutePath, key -> new ReentrantLock()); - if (lock != null) { - lock.lock(); - } + Lock lock = uploadFileLockCache.get(fileAbsolutePath, key -> new ReentrantLock()); + if (lock != null) { + lock.lock(); } UpdateResult updateResult; ObjectId objectId = new ObjectId(); @@ -356,6 +353,7 @@ public String createFile(String username, File file, String userId, Boolean isPu } finally { if (lock != null) { lock.unlock(); + uploadFileLockCache.invalidate(fileAbsolutePath); } } if (null != updateResult.getUpsertedId()) { @@ -446,24 +444,28 @@ public void imageFileToWebp(File outputFile, BufferedImage image) throws IOExcep } private void setFileConfig(String fileId, String username, File file, String fileName, String suffix, String contentType, String relativePath, Update update) { - long size = file.length(); - update.set("size", size); - update.set("md5", size + relativePath + fileName); - update.set(Constants.CONTENT_TYPE, getContentType(file, contentType)); - update.set(Constants.SUFFIX, suffix); - if (contentType.contains(Constants.AUDIO)) { - setMusic(file, update); - } - if (contentType.contains(Constants.VIDEO)) { - setMediaCover(fileId, username, fileName, relativePath, update); - } - if (contentType.startsWith(Constants.CONTENT_TYPE_IMAGE) && (!"ico".equals(suffix) && !"svg".equals(suffix))) { - generateThumbnail(file, update); - } - if (contentType.contains(Constants.CONTENT_TYPE_MARK_DOWN) || "md".equals(suffix)) { - // 写入markdown内容 - String markDownContent = FileUtil.readString(file, MyFileUtils.getFileCharset(file)); - update.set("contentText", markDownContent); + try { + long size = file.length(); + update.set("size", size); + update.set("md5", size + relativePath + fileName); + update.set(Constants.CONTENT_TYPE, getContentType(file, contentType)); + update.set(Constants.SUFFIX, suffix); + if (contentType.contains(Constants.AUDIO)) { + setMusic(file, update); + } + if (contentType.contains(Constants.VIDEO)) { + setMediaCover(fileId, username, fileName, relativePath, update); + } + if (contentType.startsWith(Constants.CONTENT_TYPE_IMAGE) && (!"ico".equals(suffix) && !"svg".equals(suffix))) { + generateThumbnail(file, update); + } + if (contentType.contains(Constants.CONTENT_TYPE_MARK_DOWN) || "md".equals(suffix)) { + // 写入markdown内容 + String markDownContent = FileUtil.readString(file, MyFileUtils.getFileCharset(file)); + update.set("contentText", markDownContent); + } + } catch (Exception e) { + log.error(e.getMessage(), e); } } diff --git a/src/main/java/com/jmal/clouddisk/util/CaffeineUtil.java b/src/main/java/com/jmal/clouddisk/util/CaffeineUtil.java index a1ff0d82..266e2da7 100644 --- a/src/main/java/com/jmal/clouddisk/util/CaffeineUtil.java +++ b/src/main/java/com/jmal/clouddisk/util/CaffeineUtil.java @@ -70,10 +70,10 @@ public class CaffeineUtil { */ private static Cache chunkWriteLockCache; - /*** - * 上传文件夹锁 + /** + * 上传文件锁 */ - private static Cache uploadFolderLockCache; + private static Cache uploadFileLockCache; /*** * 用户身份权限缓存 @@ -147,8 +147,8 @@ public static void initMyCache(){ if(chunkWriteLockCache == null){ chunkWriteLockCache = Caffeine.newBuilder().build(); } - if(uploadFolderLockCache == null) { - uploadFolderLockCache = Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).build(); + if(uploadFileLockCache == null) { + uploadFileLockCache = Caffeine.newBuilder().build(); } } @@ -180,11 +180,11 @@ public static Cache getChunkWriteLockCache(){ return chunkWriteLockCache; } - public static Cache getUploadFolderLockCache(){ - if(uploadFolderLockCache == null){ + public static Cache getUploadFileLockCache(){ + if(uploadFileLockCache == null){ initMyCache(); } - return uploadFolderLockCache; + return uploadFileLockCache; } public static void setSpaceFull(String userId) { diff --git a/src/main/java/com/jmal/clouddisk/webdav/WebdavServlet.java b/src/main/java/com/jmal/clouddisk/webdav/WebdavServlet.java index 044cf9d1..6ae0bd26 100644 --- a/src/main/java/com/jmal/clouddisk/webdav/WebdavServlet.java +++ b/src/main/java/com/jmal/clouddisk/webdav/WebdavServlet.java @@ -119,6 +119,7 @@ */ public class WebdavServlet extends DefaultServlet { + @Serial private static final long serialVersionUID = 1L; @@ -315,23 +316,17 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) throws log("[" + method + "] " + path); } - if (method.equals(METHOD_PROPFIND)) { - doPropfind(req, resp); - } else if (method.equals(METHOD_PROPPATCH)) { - doProppatch(req, resp); - } else if (method.equals(METHOD_MKCOL)) { - doMkcol(req, resp); - } else if (method.equals(METHOD_COPY)) { - doCopy(req, resp); - } else if (method.equals(METHOD_MOVE)) { - doMove(req, resp); - } else if (method.equals(METHOD_LOCK)) { - doLock(req, resp); - } else if (method.equals(METHOD_UNLOCK)) { - doUnlock(req, resp); - } else { - // DefaultServlet processing - super.service(req, resp); + switch (method) { + case METHOD_PROPFIND -> doPropfind(req, resp); + case METHOD_PROPPATCH -> doProppatch(req, resp); + case METHOD_MKCOL -> doMkcol(req, resp); + case METHOD_COPY -> doCopy(req, resp); + case METHOD_MOVE -> doMove(req, resp); + case METHOD_LOCK -> doLock(req, resp); + case METHOD_UNLOCK -> doUnlock(req, resp); + default -> + // DefaultServlet processing + super.service(req, resp); } } @@ -353,12 +348,9 @@ private boolean isSpecialPath(final String path) { protected boolean checkIfHeaders(HttpServletRequest request, HttpServletResponse response, WebResource resource) throws IOException { - if (!super.checkIfHeaders(request, response, resource)) { - return false; - } + return super.checkIfHeaders(request, response, resource); - // TODO : Checking the WebDAV If header - return true; + //Checking the WebDAV If header } @@ -389,7 +381,7 @@ protected String getRelativePath(HttpServletRequest request, boolean allowEmptyP if (pathInfo != null) { result.append(pathInfo); } - if (result.length() == 0) { + if (result.isEmpty()) { result.append('/'); } @@ -461,13 +453,12 @@ protected void doPropfind(HttpServletRequest req, HttpServletResponse resp) thro if (depthStr == null) { depth = maxDepth; } else { - if (depthStr.equals("0")) { - depth = 0; - } else if (depthStr.equals("1")) { - depth = 1; - } else if (depthStr.equals("infinity")) { - depth = maxDepth; - } + depth = switch (depthStr) { + case "0" -> 0; + case "1" -> 1; + case "infinity" -> maxDepth; + default -> depth; + }; } Node propNode = null; @@ -642,23 +633,74 @@ protected void doPropfind(HttpServletRequest req, HttpServletResponse resp) thro * @param req The Servlet request * @param resp The Servlet response * - * @throws IOException If an IO error occurs + * @throws IOException If an IO error occurs */ - protected void doProppatch(HttpServletRequest req, HttpServletResponse resp) throws IOException { - + protected void doProppatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { if (readOnly) { - resp.sendError(WebdavStatus.SC_FORBIDDEN); + resp.sendError(WebdavStatus.SC_FORBIDDEN, "Read-only mode is enabled."); return; } if (isLocked(req)) { - resp.sendError(WebdavStatus.SC_LOCKED); + resp.sendError(WebdavStatus.SC_LOCKED, "Resource is locked."); return; } - resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); + String path = getRelativePath(req); + WebResource resource = resources.getResource(path); + + if (!resource.exists()) { + resp.sendError(WebdavStatus.SC_NOT_FOUND, "Resource not found."); + return; + } + + DocumentBuilder documentBuilder; + Document document; + try { + documentBuilder = getDocumentBuilder(); + document = documentBuilder.parse(new InputSource(req.getInputStream())); + } catch (SAXException | IOException e) { + resp.sendError(WebdavStatus.SC_BAD_REQUEST, "Error parsing XML request body."); + return; + } + + Element rootElement = document.getDocumentElement(); + NodeList setList = rootElement.getElementsByTagName("set"); + NodeList removeList = rootElement.getElementsByTagName("remove"); + + List successfulUpdates = new ArrayList<>(); + + processProppatchOperations(setList, successfulUpdates); + processProppatchOperations(removeList, successfulUpdates); + + if (!successfulUpdates.isEmpty()) { + resp.setStatus(WebdavStatus.SC_OK); + } else { + resp.setStatus(WebdavStatus.SC_MULTI_STATUS); + resp.setContentType("text/xml"); + XMLWriter generatedXML = new XMLWriter(resp.getWriter()); + generatedXML.writeXMLHeader(); + generatedXML.writeElement("D", DEFAULT_NAMESPACE, "multistatus", XMLWriter.OPENING); + generatedXML.writeElement("D", "multistatus", XMLWriter.CLOSING); + generatedXML.sendData(); + } } + private void processProppatchOperations(NodeList operationList, + List successfulUpdates) { + for (int i = 0; i < operationList.getLength(); i++) { + Node operationNode = operationList.item(i); + NodeList propList = operationNode.getChildNodes(); + + for (int j = 0; j < propList.getLength(); j++) { + Node propNode = propList.item(j); + if (propNode.getNodeType() == Node.ELEMENT_NODE) { + String propName = propNode.getNodeName(); + successfulUpdates.add(propName); + } + } + } + } /** * MKCOL Method. @@ -697,7 +739,7 @@ protected void doMkcol(HttpServletRequest req, HttpServletResponse resp) throws try { // Document document = documentBuilder.parse(new InputSource(req.getInputStream())); - // TODO : Process this request body + // Process this request body resp.sendError(WebdavStatus.SC_NOT_IMPLEMENTED); return; @@ -891,7 +933,7 @@ protected void doLock(HttpServletRequest req, HttpServletResponse resp) throws S lockDuration = MAX_TIMEOUT; } } - lock.expiresAt = System.currentTimeMillis() + (lockDuration * 1000); + lock.expiresAt = System.currentTimeMillis() + (lockDuration * 1000L); int lockRequestType = LOCK_CREATION; @@ -903,8 +945,7 @@ protected void doLock(HttpServletRequest req, HttpServletResponse resp) throws S Document document = documentBuilder.parse(new InputSource(req.getInputStream())); // Get the root element of the document - Element rootElement = document.getDocumentElement(); - lockInfoNode = rootElement; + lockInfoNode = document.getDocumentElement(); } catch (IOException | SAXException e) { lockRequestType = LOCK_REFRESH; } @@ -1475,11 +1516,7 @@ private boolean copyResource(HttpServletRequest req, HttpServletResponse resp) t boolean overwrite = true; String overwriteHeader = req.getHeader("Overwrite"); if (overwriteHeader != null) { - if (overwriteHeader.equalsIgnoreCase("T")) { - overwrite = true; - } else { - overwrite = false; - } + overwrite = overwriteHeader.equalsIgnoreCase("T"); } // Overwriting the destination @@ -1509,9 +1546,9 @@ private boolean copyResource(HttpServletRequest req, HttpServletResponse resp) t if ((!result) || (!errorList.isEmpty())) { if (errorList.size() == 1) { - resp.sendError(errorList.values().iterator().next().intValue()); + resp.sendError(errorList.values().iterator().next()); } else { - sendReport(req, resp, errorList); + sendReport(resp, errorList); } return false; } @@ -1552,7 +1589,7 @@ private boolean copyResource(Map errorList, String source, Strin if (!resources.mkdir(dest)) { WebResource destResource = resources.getResource(dest); if (!destResource.isDirectory()) { - errorList.put(dest, Integer.valueOf(WebdavStatus.SC_CONFLICT)); + errorList.put(dest, WebdavStatus.SC_CONFLICT); return false; } } @@ -1579,7 +1616,7 @@ private boolean copyResource(Map errorList, String source, Strin String parent = destResource.getWebappPath().substring(0, lastSlash); WebResource parentResource = resources.getResource(parent); if (!parentResource.isDirectory()) { - errorList.put(source, Integer.valueOf(WebdavStatus.SC_CONFLICT)); + errorList.put(source, WebdavStatus.SC_CONFLICT); return false; } } @@ -1593,14 +1630,14 @@ private boolean copyResource(Map errorList, String source, Strin } try (InputStream is = sourceResource.getInputStream()) { if (!resources.write(dest, is, false)) { - errorList.put(source, Integer.valueOf(WebdavStatus.SC_INTERNAL_SERVER_ERROR)); + errorList.put(source, WebdavStatus.SC_INTERNAL_SERVER_ERROR); return false; } } catch (IOException e) { log(sm.getString("webdavservlet.inputstreamclosefail", source), e); } } else { - errorList.put(source, Integer.valueOf(WebdavStatus.SC_INTERNAL_SERVER_ERROR)); + errorList.put(source, WebdavStatus.SC_INTERNAL_SERVER_ERROR); return false; } return true; @@ -1612,14 +1649,11 @@ private boolean copyResource(Map errorList, String source, Strin * * @param req Servlet request * @param resp Servlet response - * - * @return true if the delete is successful - * * @throws IOException If an IO error occurs */ - private boolean deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException { + private void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException { String path = getRelativePath(req); - return deleteResource(path, req, resp, true); + deleteResource(path, req, resp, true); } @@ -1671,11 +1705,11 @@ private boolean deleteResource(String path, HttpServletRequest req, HttpServletR deleteCollection(req, path, errorList); if (!resource.delete()) { - errorList.put(path, Integer.valueOf(WebdavStatus.SC_INTERNAL_SERVER_ERROR)); + errorList.put(path, WebdavStatus.SC_INTERNAL_SERVER_ERROR); } if (!errorList.isEmpty()) { - sendReport(req, resp, errorList); + sendReport(resp, errorList); return false; } } @@ -1701,7 +1735,7 @@ private void deleteCollection(HttpServletRequest req, String path, Map errorList) + private void sendReport(HttpServletResponse resp, Map errorList) throws IOException { resp.setStatus(WebdavStatus.SC_MULTI_STATUS); @@ -1767,7 +1800,7 @@ private void sendReport(HttpServletRequest req, HttpServletResponse resp, Map errorEntry : errorList.entrySet()) { String errorPath = errorEntry.getKey(); - int errorCode = errorEntry.getValue().intValue(); + int errorCode = errorEntry.getValue(); generatedXML.writeElement("D", "response", XMLWriter.OPENING); @@ -1800,7 +1833,7 @@ private void sendReport(HttpServletRequest req, HttpServletResponse resp, Map properties) { + List properties) { // Exclude any resource in the /WEB-INF and /META-INF subdirectories if (isSpecialPath(path)) { @@ -1842,7 +1875,7 @@ private void parseProperties(HttpServletRequest req, XMLWriter generatedXML, Str * @param properties If the propfind type is find properties by name, then this List contains those properties */ private void parseLockNullProperties(HttpServletRequest req, XMLWriter generatedXML, String path, int type, - List properties) { + List properties) { // Exclude any resource in the /WEB-INF and /META-INF subdirectories if (isSpecialPath(path)) { @@ -1871,8 +1904,8 @@ private void parseLockNullProperties(HttpServletRequest req, XMLWriter generated private void generatePropFindResponse(XMLWriter generatedXML, String rewrittenUrl, String path, int propFindType, - List properties, boolean isFile, boolean isLockNull, long created, long lastModified, - long contentLength, String contentType, String eTag) { + List properties, boolean isFile, boolean isLockNull, long created, long lastModified, + long contentLength, String contentType, String eTag) { generatedXML.writeElement("D", "response", XMLWriter.OPENING); String status = "HTTP/1.1 " + WebdavStatus.SC_OK + " "; @@ -2176,6 +2209,7 @@ protected String determineMethodsAllowed(HttpServletRequest req) { */ private static class LockInfo implements Serializable { + @Serial private static final long serialVersionUID = 1L; LockInfo(int maxDepth) { @@ -2294,7 +2328,7 @@ public void toXML(XMLWriter generatedXML) { * references are filtered out for security reasons. See CVE-2007-5461. */ private static class WebdavResolver implements EntityResolver { - private ServletContext context; + private final ServletContext context; WebdavResolver(ServletContext theContext) { context = theContext; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8b32ef44..3868f633 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -6,7 +6,7 @@ server: charset: UTF-8 enabled: true forward-headers-strategy: native -version: 2.8.2 +version: 2.8.3 spring: application: name: jmalcloud