瀏覽代碼

2024年3月5日18:52:44

纪新园 2 年之前
父節點
當前提交
33abd45914
共有 28 個文件被更改,包括 2594 次插入43 次删除
  1. 5 0
      blade-service-api/blade-los-api/src/main/java/org/springblade/los/business/sea/entity/Containers.java
  2. 30 0
      blade-service/blade-los/pom.xml
  3. 17 36
      blade-service/blade-los/src/main/java/org/springblade/los/business/sea/service/impl/BillsServiceImpl.java
  4. 2 0
      blade-service/blade-los/src/main/java/org/springblade/los/business/sea/service/impl/ContainersServiceImpl.java
  5. 35 6
      blade-service/blade-los/src/main/java/org/springblade/los/business/sea/service/impl/PreContainersServiceImpl.java
  6. 80 0
      blade-service/blade-los/src/main/java/org/springblade/los/email/controller/EmailController.java
  7. 54 0
      blade-service/blade-los/src/main/java/org/springblade/los/email/service/EMailService.java
  8. 83 0
      blade-service/blade-los/src/main/java/org/springblade/los/email/service/impl/EmailServiceImpl.java
  9. 22 0
      blade-service/blade-los/src/main/java/org/springblade/los/email/utils/EMailConfig.java
  10. 27 0
      blade-service/blade-los/src/main/java/org/springblade/los/email/utils/EMailPool.java
  11. 1 1
      blade-service/blade-los/src/main/java/org/springblade/los/external/Luhaitong/ApiController.java
  12. 211 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/controller/SFTPController.java
  13. 49 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/controller/TestController.java
  14. 18 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/dto/FtpDto.java
  15. 29 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/service/FTPPoolService.java
  16. 17 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/service/FtpService.java
  17. 28 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/service/SFTPPoolService.java
  18. 73 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/service/impl/FTPPoolServiceImpl.java
  19. 63 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/service/impl/FtpServiceImpl.java
  20. 76 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/service/impl/SFTPPoolServiceImpl.java
  21. 108 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/utils/Sftp/ChannelSftpFactory.java
  22. 150 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/utils/Sftp/HutoolSFTPUtil.java
  23. 501 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/utils/Sftp/SFTPUtil.java
  24. 79 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/utils/Sftp/SFtpConfig.java
  25. 140 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/utils/ftp/FTPClientFactory.java
  26. 81 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/utils/ftp/FTPPoolConfig.java
  27. 559 0
      blade-service/blade-los/src/main/java/org/springblade/los/ftp/utils/ftp/FTPUtil.java
  28. 56 0
      blade-service/blade-los/src/main/resources/application-dev.yml

+ 5 - 0
blade-service-api/blade-los-api/src/main/java/org/springblade/los/business/sea/entity/Containers.java

@@ -405,6 +405,11 @@ public class Containers implements Serializable {
 	 */
 	@ApiModelProperty(value = "票数")
 	private String ticketNumber;
+	/**
+	 * 场站箱型代码
+	 */
+	@ApiModelProperty(value = "场站箱型代码")
+	private String cyCntrCode;
 
 	/**
 	 * 箱号装货明细

+ 30 - 0
blade-service/blade-los/pom.xml

@@ -12,6 +12,36 @@
     <artifactId>blade-los</artifactId>
 
     <dependencies>
+        <!-- 邮件依赖,配置hutool工具包发送邮件  -->
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>javax.mail</artifactId>
+            <version>1.6.2</version>
+        </dependency>
+        <!-- 模板引擎,制作邮件模板 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-thymeleaf</artifactId>
+        </dependency>
+        <!--ftp文件上传-->
+        <dependency>
+            <groupId>commons-net</groupId>
+            <artifactId>commons-net</artifactId>
+            <version>3.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.jcraft</groupId>
+            <artifactId>jsch</artifactId>
+            <version>0.1.54</version>
+        </dependency>
+
+        <!--自定义连接池-->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+            <version>2.4.2</version>
+        </dependency>
+
         <dependency>
             <groupId>org.springblade</groupId>
             <artifactId>blade-los-api</artifactId>

+ 17 - 36
blade-service/blade-los/src/main/java/org/springblade/los/business/sea/service/impl/BillsServiceImpl.java

@@ -676,6 +676,9 @@ public class BillsServiceImpl extends ServiceImpl<BillsMapper, Bills> implements
 				baseMapper.updateById(details);
 			}
 		}
+		if (!bills.getContainersList().isEmpty()) {
+			containersService.submitList(bills.getContainersList());
+		}
 		//利润 = 收 - 付
 		amountProfit = amountDr.subtract(amountCr);
 		amountProfitUsd = amountDrUsd.subtract(amountCrUsd);
@@ -704,8 +707,7 @@ public class BillsServiceImpl extends ServiceImpl<BillsMapper, Bills> implements
 		LocalDateTime now2 = LocalDateTime.now();
 		String formatted2 = now2.format(formatter);
 		System.out.println("止2 " + formatted2);
-
-		/**-------------费用计算---------*/
+		/*-------------费用计算---------*/
 		return R.data(bills);
 	}
 
@@ -769,43 +771,22 @@ public class BillsServiceImpl extends ServiceImpl<BillsMapper, Bills> implements
 	}
 
 	@Override
+	@Transactional(rollbackFor = Exception.class)
+	@GlobalTransactional(rollbackFor = Exception.class, timeoutMills = 12000000)
 	public R add(Bills bills) {
-		String deptId = "";
-		String deptName = "";
-		String branchId = deptUtils.getDeptPid() + "";
-		//获取部门ids对应中文名
-		if (ObjectUtils.isNotNull(AuthUtil.getDeptId())) {
-			deptId = AuthUtil.getDeptId();
-			R<List<String>> res = sysClient.getDeptNames(AuthUtil.getDeptId());
-			if (res.isSuccess() && ObjectUtils.isNotNull(res.getData())) {
-				deptName = String.join(",", res.getData());
-			}
+		if (bills.getId() == null){
+			throw new RuntimeException("缺少必要参数");
 		}
-		long billNo = baseMapper.selectCount(new LambdaQueryWrapper<Bills>()
-			.eq(Bills::getBillNo, bills.getBillNo())
-			.eq(Bills::getTenantId, AuthUtil.getTenantId())
-			.eq(Bills::getIsDeleted, 0));
-		if (bills.getId() == null) {
-			if (billNo > 0) {
-				throw new RuntimeException("单据编号不允许重复");
-			}
-			bills.setCreateTime(new Date());
-			bills.setCreateUser(AuthUtil.getUserId());
-			bills.setCreateUserName(AuthUtil.getUserName());
-			if (ObjectUtils.isNotNull(AuthUtil.getDeptId())) {
-				bills.setBranchId(branchId);
-				bills.setCreateDept(deptId);
-				bills.setCreateDeptName(deptName);
-			}
-		} else {
-			if (billNo > 0) {
-				throw new RuntimeException("单据编号不允许重复");
-			}
-			bills.setUpdateUser(AuthUtil.getUserId());
-			bills.setUpdateTime(new Date());
-			bills.setUpdateUserName(AuthUtil.getUserName());
+		bills.setUpdateUser(AuthUtil.getUserId());
+		bills.setUpdateTime(new Date());
+		bills.setUpdateUserName(AuthUtil.getUserName());
+		if (!bills.getPreContainersList().isEmpty()) {
+			preContainersService.saveOrUpdateBatch(bills.getPreContainersList());
 		}
-		this.saveOrUpdate(bills);
+		if (!bills.getContainersList().isEmpty()) {
+			containersService.saveOrUpdateBatch(bills.getContainersList());
+		}
+		baseMapper.updateById(bills);
 		return R.data(bills);
 	}
 

+ 2 - 0
blade-service/blade-los/src/main/java/org/springblade/los/business/sea/service/impl/ContainersServiceImpl.java

@@ -404,6 +404,8 @@ public class ContainersServiceImpl extends ServiceImpl<ContainersMapper, Contain
 	}
 
 	@Override
+	@Transactional(rollbackFor = Exception.class)
+	@GlobalTransactional(rollbackFor = Exception.class, timeoutMills = 12000000)
 	public R submitList(List<Containers> list) {
 		String deptId = "";
 		String deptName = "";

+ 35 - 6
blade-service/blade-los/src/main/java/org/springblade/los/business/sea/service/impl/PreContainersServiceImpl.java

@@ -25,6 +25,9 @@ import lombok.AllArgsConstructor;
 import org.springblade.core.secure.utils.AuthUtil;
 import org.springblade.core.tool.api.R;
 import org.springblade.los.Util.IDeptUtils;
+import org.springblade.los.Util.RegularUtils;
+import org.springblade.los.basic.cntr.entity.BCntrTypes;
+import org.springblade.los.basic.cntr.service.IBCntrTypesService;
 import org.springblade.los.business.sea.entity.Bills;
 import org.springblade.los.business.sea.entity.Containers;
 import org.springblade.los.business.sea.entity.ContainersBills;
@@ -65,6 +68,8 @@ public class PreContainersServiceImpl extends ServiceImpl<PreContainersMapper, P
 
 	private final BillsMapper billsMapper;
 
+	private final IBCntrTypesService bCntrTypesService;
+
 	@Override
 	public IPage<PreContainersVO> selectPreContainersPage(IPage<PreContainersVO> page, PreContainersVO preContainers) {
 		return page.setRecords(baseMapper.selectPreContainersPage(page, preContainers));
@@ -108,13 +113,13 @@ public class PreContainersServiceImpl extends ServiceImpl<PreContainersMapper, P
 				preContainers.setCreateDeptName(deptName);
 			}
 			teu += preContainers.getTeu().intValue();
-			if (measurementSum.add(preContainers.getMeasurement()).compareTo(new BigDecimal("0")) != 0){
+			if (measurementSum.add(preContainers.getMeasurement()).compareTo(new BigDecimal("0")) != 0) {
 				bills.setMeasurement(measurementSum.add(preContainers.getMeasurement()));
 			}
-			if (grossWeightSum.add(preContainers.getGrossWeight()).compareTo(new BigDecimal("0")) != 0){
+			if (grossWeightSum.add(preContainers.getGrossWeight()).compareTo(new BigDecimal("0")) != 0) {
 				bills.setGrossWeight(grossWeightSum.add(preContainers.getGrossWeight()));
 			}
-			if (quantitySum.add(new BigDecimal(preContainers.getQuantity())).compareTo(new BigDecimal("0")) != 0){
+			if (quantitySum.add(new BigDecimal(preContainers.getQuantity())).compareTo(new BigDecimal("0")) != 0) {
 				bills.setQuantity(quantitySum.add(new BigDecimal(preContainers.getQuantity())));
 			}
 		} else {
@@ -123,13 +128,13 @@ public class PreContainersServiceImpl extends ServiceImpl<PreContainersMapper, P
 				BigDecimal measurement = ObjectUtils.isNotNull(preContainers1.getMeasurement()) ? preContainers1.getMeasurement() : new BigDecimal("0.00");
 				BigDecimal grossWeight = ObjectUtils.isNotNull(preContainers1.getGrossWeight()) ? preContainers1.getGrossWeight() : new BigDecimal("0.00");
 				BigDecimal quantity = ObjectUtils.isNotNull(preContainers1.getNumber()) ? preContainers1.getNumber() : new BigDecimal("0.00");
-				if (measurementSum.add(preContainers.getMeasurement()).subtract(measurement).compareTo(new BigDecimal("0")) != 0){
+				if (measurementSum.add(preContainers.getMeasurement()).subtract(measurement).compareTo(new BigDecimal("0")) != 0) {
 					bills.setMeasurement(measurementSum.add(preContainers.getMeasurement()).subtract(measurement));
 				}
-				if (grossWeightSum.add(preContainers.getGrossWeight()).subtract(grossWeight).compareTo(new BigDecimal("0")) != 0){
+				if (grossWeightSum.add(preContainers.getGrossWeight()).subtract(grossWeight).compareTo(new BigDecimal("0")) != 0) {
 					bills.setGrossWeight(grossWeightSum.add(preContainers.getGrossWeight()).subtract(grossWeight));
 				}
-				if (quantitySum.add(preContainers.getNumber()).subtract(quantity).compareTo(new BigDecimal("0")) != 0){
+				if (quantitySum.add(preContainers.getNumber()).subtract(quantity).compareTo(new BigDecimal("0")) != 0) {
 					bills.setQuantity(quantitySum.add(preContainers.getNumber()).subtract(quantity));
 				}
 				if (ObjectUtils.isNotNull(preContainers1.getTeu()) && ObjectUtils.isNotNull(preContainers.getTeu())) {
@@ -162,6 +167,17 @@ public class PreContainersServiceImpl extends ServiceImpl<PreContainersMapper, P
 			List<Long> ids = removeContainersList.stream().map(Containers::getId).collect(Collectors.toList());
 			containersBillsService.removeByPid(ids);
 		}
+		BCntrTypes bCntrTypes = bCntrTypesService.getOne(new LambdaQueryWrapper<BCntrTypes>()
+			.eq(BCntrTypes::getCnName, preContainers.getCntrTypeCode())
+			.eq(BCntrTypes::getStatus, 0)
+		);
+		String ediCode = "";
+		if (bCntrTypes != null) {
+			ediCode = RegularUtils.getEdiCode(bills.getCyCode(), bCntrTypes.getExtendedData());
+		}
+		if (ObjectUtils.isNull(ediCode)) {
+			throw new RuntimeException("请先配置场站对应箱型代码");
+		}
 		//生成海运进出口配箱-箱号装箱
 		List<Containers> containersList = new ArrayList<>();
 		for (int i = 0; i < preContainers.getQuantity(); i++) {
@@ -169,6 +185,7 @@ public class PreContainersServiceImpl extends ServiceImpl<PreContainersMapper, P
 			containers.setCreateTime(new Date());
 			containers.setCreateUser(AuthUtil.getUserId());
 			containers.setCreateUserName(AuthUtil.getUserName());
+			containers.setCyCntrCode(ediCode);
 			if (ObjectUtils.isNotNull(AuthUtil.getDeptId())) {
 				containers.setBranchId(branchId);
 				containers.setCreateDept(deptId);
@@ -300,12 +317,24 @@ public class PreContainersServiceImpl extends ServiceImpl<PreContainersMapper, P
 				List<Long> ids = removeContainersList.stream().map(Containers::getId).collect(Collectors.toList());
 				containersBillsService.removeByPid(ids);
 			}
+			BCntrTypes bCntrTypes = bCntrTypesService.getOne(new LambdaQueryWrapper<BCntrTypes>()
+				.eq(BCntrTypes::getCnName, preContainers.getCntrTypeCode())
+				.eq(BCntrTypes::getStatus, 0)
+			);
+			String ediCode = "";
+			if (bCntrTypes != null && bills != null) {
+				ediCode = RegularUtils.getEdiCode(bills.getCyCode(), bCntrTypes.getExtendedData());
+			}
+			if (ObjectUtils.isNull(ediCode)) {
+				throw new RuntimeException("请先配置场站对应箱型代码");
+			}
 			//生成海运进出口配箱-箱号装箱
 			for (int i = 0; i < preContainers.getQuantity(); i++) {
 				Containers containers = new Containers();
 				containers.setCreateTime(new Date());
 				containers.setCreateUser(AuthUtil.getUserId());
 				containers.setCreateUserName(AuthUtil.getUserName());
+				containers.setCyCntrCode(ediCode);
 				if (ObjectUtils.isNotNull(AuthUtil.getDeptId())) {
 					containers.setBranchId(branchId);
 					containers.setCreateDept(deptId);

+ 80 - 0
blade-service/blade-los/src/main/java/org/springblade/los/email/controller/EmailController.java

@@ -0,0 +1,80 @@
+package org.springblade.los.email.controller;
+
+
+import io.swagger.annotations.Api;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.springblade.los.email.service.EMailService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.thymeleaf.context.Context;
+
+import java.io.File;
+
+/**
+ * @author ding
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/e-mail")
+@Api(value = "e-mail接口", tags = "e-mail接口")
+
+public class EmailController {
+
+	private final EMailService emailService;
+
+	/**
+	 * 基础邮件
+	 *
+	 * @param to 目标的邮箱地址
+	 */
+	@SneakyThrows
+	@GetMapping("/send")
+	public String send(String to) {
+		emailService.send(to, "测试邮件", "欢迎使用springboot-cli开发脚手架");
+		return "发送成功!!";
+	}
+
+	/**
+	 * 发送附件
+	 *
+	 * @param to 目标的邮箱地址
+	 */
+	@SneakyThrows
+	@GetMapping("/send/file")
+	public String sendFile(String to) {
+		File file = new File("C:\\Users\\10599\\Desktop\\test.txt");
+		emailService.send(to, "测试邮件", "欢迎使用springboot-cli开发脚手架", file);
+		return "发送成功!!";
+	}
+
+	/**
+	 * 发送模板邮件
+	 *
+	 * @param to 目标的邮箱地址
+	 */
+	@SneakyThrows
+	@GetMapping("/sendByTem")
+	public String sendByTem(String to) {
+		Context context = new Context();
+		context.setVariable("code", 6666);
+		emailService.send(to, "激活码模板邮件", context);
+		return "发送成功!!";
+	}
+
+	/**
+	 * 发送模板邮件(带附件)
+	 *
+	 * @param to 目标的邮箱地址
+	 */
+	@SneakyThrows
+	@GetMapping("/sendByTem/file")
+	public String sendByTemFile(String to) {
+		File file = new File("D:\\test\\test.txt");
+		Context context = new Context();
+		context.setVariable("code", 6666);
+		emailService.send(to, "激活码模板邮件", context, file);
+		return "发送成功!!";
+	}
+}

+ 54 - 0
blade-service/blade-los/src/main/java/org/springblade/los/email/service/EMailService.java

@@ -0,0 +1,54 @@
+package org.springblade.los.email.service;
+
+import org.thymeleaf.context.Context;
+
+import java.io.File;
+
+/**
+ * @author :jixinyuan
+ * @date : 2024/3/5
+ */
+public interface EMailService {
+
+
+	/**
+	 * 发送邮件
+	 *
+	 * @param to      目标邮箱
+	 * @param subject 标题
+	 * @param content 内容
+	 */
+	public void send(String to, String subject, String content);
+
+	/**
+	 * 发送邮件(带附件)
+	 *
+	 * @param to      目标邮箱
+	 * @param subject 标题
+	 * @param content 内容
+	 * @param files   附件(可选)
+	 */
+	public void send(String to, String subject, String content, File... files);
+
+	/**
+	 * 发送邮件-读取自定义模板
+	 *
+	 * @param to      目标邮箱
+	 * @param subject 标题
+	 * @param context 内容
+	 */
+	public void send(String to, String subject, Context context);
+
+
+	/**
+	 * 发送邮件-读取自定义模板(带附件)
+	 *
+	 * @param to      目标邮箱
+	 * @param subject 标题
+	 * @param context 内容
+	 * @param files   附件
+	 */
+	public void send(String to, String subject, Context context, File... files);
+
+
+}

+ 83 - 0
blade-service/blade-los/src/main/java/org/springblade/los/email/service/impl/EmailServiceImpl.java

@@ -0,0 +1,83 @@
+package org.springblade.los.email.service.impl;
+
+import cn.hutool.extra.mail.MailUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.los.email.service.EMailService;
+import org.springblade.los.email.utils.EMailPool;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.thymeleaf.TemplateEngine;
+import org.thymeleaf.context.Context;
+
+import java.io.File;
+
+/**
+ * @author :jixinyuan
+ * @date : 2024/3/5
+ */
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class EmailServiceImpl implements EMailService {
+	@Autowired
+	private final EMailPool eMailPool;
+
+	private final TemplateEngine templateEngine;
+
+
+	/**
+	 * 发送邮件
+	 *
+	 * @param to      目标邮箱
+	 * @param subject 标题
+	 * @param content 内容
+	 */
+	@Override
+	public void send(String to, String subject, String content) {
+		MailUtil.send(eMailPool.getAccount(), to, subject, content, false);
+	}
+
+	/**
+	 * 发送邮件(带附件)
+	 *
+	 * @param to      目标邮箱
+	 * @param subject 标题
+	 * @param content 内容
+	 * @param files   附件(可选)
+	 */
+	@Override
+	public void send(String to, String subject, String content, File... files) {
+		MailUtil.send(eMailPool.getAccount(), to, subject, content, false, files);
+	}
+
+	/**
+	 * 发送邮件-读取自定义模板
+	 *
+	 * @param to      目标邮箱
+	 * @param subject 标题
+	 * @param context 内容
+	 */
+	@Override
+	public void send(String to, String subject, Context context) {
+		String template = templateEngine.process("emailTemplate", context);
+		MailUtil.send(eMailPool.getAccount(), to, subject, template, true);
+	}
+
+
+	/**
+	 * 发送邮件-读取自定义模板(带附件)
+	 *
+	 * @param to      目标邮箱
+	 * @param subject 标题
+	 * @param context 内容
+	 * @param files   附件
+	 */
+	@Override
+	public void send(String to, String subject, Context context, File... files) {
+		String template = templateEngine.process("emailTemplate", context);
+		MailUtil.send(eMailPool.getAccount(), to, subject, template, true, files);
+	}
+
+
+}

+ 22 - 0
blade-service/blade-los/src/main/java/org/springblade/los/email/utils/EMailConfig.java

@@ -0,0 +1,22 @@
+package org.springblade.los.email.utils;
+
+import cn.hutool.extra.mail.MailAccount;
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+
+@Configuration
+@Data
+public class EMailConfig {
+
+	@Value("${email.host}")
+    private String host;
+	@Value("${email.port}")
+    private Integer port;
+	@Value("${email.from}")
+    private String from ;
+	@Value("${email.pass}")
+    private String pass;
+}

+ 27 - 0
blade-service/blade-los/src/main/java/org/springblade/los/email/utils/EMailPool.java

@@ -0,0 +1,27 @@
+package org.springblade.los.email.utils;
+
+import cn.hutool.extra.mail.MailAccount;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author :jixinyuan
+ * @date : 2024/3/5
+ */
+@Configuration
+public class EMailPool {
+
+	@Autowired
+	EMailConfig eMailConfig;
+
+	public MailAccount getAccount() {
+		MailAccount account = new MailAccount();
+		account.setAuth(true);
+		account.setHost(eMailConfig.getHost());
+		account.setPort(eMailConfig.getPort());
+		account.setFrom(eMailConfig.getFrom());
+		account.setUser(eMailConfig.getFrom());
+		account.setPass(eMailConfig.getPass());
+		return account;
+	}
+}

+ 1 - 1
blade-service/blade-los/src/main/java/org/springblade/los/external/Luhaitong/ApiController.java

@@ -50,7 +50,7 @@ public class ApiController {
 		connection.setRequestMethod("GET");
 		// 添加自定义的Header信息
 		connection.addRequestProperty("Host", "www.sdland-sea.com");
-		connection.addRequestProperty("Lh-Auth", "14658740-0996-412e-a6c2-0c3cc5aace4b");
+		connection.addRequestProperty("Lh-Auth", auth);
 		connection.addRequestProperty("Referer", "https://www.sdland-sea.com/service/station/billSearch");
 		connection.connect();// 连接会话
 		// 获取输入流

+ 211 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/controller/SFTPController.java

@@ -0,0 +1,211 @@
+package org.springblade.los.ftp.controller;
+
+import io.swagger.annotations.Api;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.los.ftp.utils.Sftp.HutoolSFTPUtil;
+import org.springblade.los.ftp.utils.Sftp.SFTPUtil;
+import org.springblade.los.ftp.utils.ftp.FTPUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author :jixinyuan
+ * @date : 2024/3/5
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/ftp")
+@Api(value = "edi发送日志", tags = "edi发送日志接口")
+@Slf4j
+public class SFTPController {
+
+	/**
+	 * 构造方法注入
+	 */
+	@Autowired
+	FTPUtil ftpUtil;
+
+	@Autowired
+	SFTPUtil sftpUtil;
+
+	@Autowired
+	HutoolSFTPUtil hutoolSFTPUtil;
+
+
+	/**
+	 * 保存数据
+	 *
+	 * @return
+	 */
+	@GetMapping("/ftpCreate")
+	public String ftpCreate() {
+		boolean result =  false;
+		try {
+			result = ftpUtil.createDirectory("test03");
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return String.valueOf(result);
+	}
+
+	/**
+	 * 保存数据
+	 *
+	 * @return
+	 */
+	@GetMapping("/upload")
+	public String upload() {
+		try {
+			String remotePath = "/test01/test-20220718 -测试.txt";
+			ftpUtil.createDirectory(remotePath);
+			String localPath = "D:\\logs\\log\\test-20220718 -测试.txt";
+			FTPUtil.UploadStatus uploadStatus = ftpUtil.upload(localPath,remotePath);
+			log.info(String.valueOf(uploadStatus));
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return "success";
+	}
+
+	/**
+	 * 保存数据
+	 *
+	 * @return
+	 */
+	@GetMapping("/sftpCreate")
+	public String sftpCreate() {
+		boolean result =  false;
+		try {
+			result = sftpUtil.createFolders("/home/hadoop/sftpdata/test01");
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return String.valueOf(result);
+	}
+
+	/**
+	 * 保存数据
+	 *
+	 * @return
+	 */
+	@GetMapping("/sftpUpload")
+	public String sftpUpload() {
+		String result =  "";
+		try {
+			String remotePath = "/test01";
+			String remoteFileName = "test-20220718 -测试.txt";
+			String localFileFullPath = "D:\\logs\\log\\test-20220718 -测试.txt";
+			result = sftpUtil.uploadLocalToRemote(remotePath,remoteFileName,localFileFullPath);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return result;
+	}
+
+
+	/**
+	 * 保存数据
+	 *
+	 * @return
+	 */
+	@GetMapping("/hutoolMakeDir")
+	public String hutoolMakeDir() {
+		boolean result = false;
+		try {
+			result = hutoolSFTPUtil.mkdir("/home/hadoop/sftpdata/hutool");
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return String.valueOf(result);
+	}
+
+	/**
+	 * 保存数据
+	 *
+	 * @return
+	 */
+	@GetMapping("/hutoolMakeDirs")
+	public String hutoolMakeDirs() {
+		boolean result = false;
+		try {
+			result = hutoolSFTPUtil.mkdirs("/home/hadoop/sftpdata/hutool/test01/test02");
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return String.valueOf(result);
+	}
+
+
+	/**
+	 * 保存数据
+	 *
+	 * @return
+	 */
+	@GetMapping("/hutoolUpload")
+	public String hutoolUpload() {
+		boolean result = false;
+		try {
+			String remoteFile = "/home/hadoop/sftpdata/hutool/test01/test02/test01.txt";
+			String localFile = "D:\\logs\\log\\test-20220718 -测试.txt";
+			String remotePath = remoteFile.substring(0,remoteFile.lastIndexOf("/"));
+			if(hutoolSFTPUtil.mkdirs(remotePath)){
+				result = hutoolSFTPUtil.upload(remoteFile,localFile);
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return String.valueOf(result);
+	}
+
+	/**
+	 * 保存数据
+	 *
+	 * @return
+	 */
+	@GetMapping("/hutoolDownload")
+	public String hutoolDownload() {
+		boolean result = false;
+		try {
+			String remoteFile = "/home/hadoop/sftpdata/hutool/01.txt";
+			String localFile = "D:\\logs\\log\\test-20220718-02.txt";
+			result = hutoolSFTPUtil.download(remoteFile,localFile);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return String.valueOf(result);
+	}
+
+	/**
+	 * 保存数据
+	 *
+	 * @return
+	 */
+	@GetMapping("/hutoolDelDir")
+	public String hutoolDelDir() {
+		boolean result = false;
+		try {
+			result = hutoolSFTPUtil.delDir("/home/hadoop/sftpdata/hutool");
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return String.valueOf(result);
+	}
+	/**
+	 * 保存数据
+	 *
+	 * @return
+	 */
+	@GetMapping("/exec")
+	public String exec() {
+		String result = "";
+		try {
+			result = hutoolSFTPUtil.exec("ls /home/hadoop");
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return String.valueOf(result);
+	}
+}

+ 49 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/controller/TestController.java

@@ -0,0 +1,49 @@
+package org.springblade.los.ftp.controller;
+
+import io.swagger.annotations.Api;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.springblade.core.tool.api.R;
+import org.springblade.los.ftp.dto.FtpDto;
+import org.springblade.los.ftp.service.FtpService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * @author :jixinyuan
+ * @date : 2024/3/5
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/ftp")
+@Api(value = "edi发送日志", tags = "edi发送日志接口")
+public class TestController {
+
+	@Autowired
+	private final FtpService ftpService;
+
+	/**
+	 * 测试ftp文件上传
+	 *
+	 * @param file
+	 * @return
+	 */
+	@SneakyThrows
+	@PostMapping("/put-file")
+	public R<String> putFile(@RequestParam("file") MultipartFile file) {
+		return R.data(ftpService.upload(file));
+	}
+
+	/**
+	 * 检测ftp通道是否正常
+	 *
+	 * @param ftpDto
+	 * @return
+	 */
+	@SneakyThrows
+	@GetMapping("/detectionChannel")
+	public R detectionChannel(FtpDto ftpDto) {
+		return ftpService.detectionChannel(ftpDto);
+	}
+}

+ 18 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/dto/FtpDto.java

@@ -0,0 +1,18 @@
+package org.springblade.los.ftp.dto;
+
+import lombok.Data;
+
+/**
+ * @author :jixinyuan
+ * @date : 2024/3/5
+ */
+@Data
+public class FtpDto {
+
+	private String host;
+	private String username;
+	private String password;
+	private String path;
+	private String address;
+
+}

+ 29 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/service/FTPPoolService.java

@@ -0,0 +1,29 @@
+package org.springblade.los.ftp.service;
+
+import org.apache.commons.net.ftp.FTPClient;
+import org.springblade.los.ftp.utils.ftp.FTPPoolConfig;
+
+/**
+ * @author :jixinyuan
+ * @date : 2024/3/5
+ */
+public interface  FTPPoolService {
+
+	/**
+	 * 获取ftpClient
+	 */
+	FTPClient borrowObject();
+
+	/**
+	 * 归还ftpClient
+	 */
+	void returnObject(FTPClient ftpClient);
+
+	/**
+	 * 获取 ftp 配置信息
+	 * @return
+	 */
+	FTPPoolConfig getFtpPoolConfig();
+
+
+}

+ 17 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/service/FtpService.java

@@ -0,0 +1,17 @@
+package org.springblade.los.ftp.service;
+
+import org.springblade.core.tool.api.R;
+import org.springblade.los.ftp.dto.FtpDto;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * @author :jixinyuan
+ * @date : 2024/3/5
+ */
+public interface FtpService {
+
+
+	R detectionChannel(FtpDto ftpDto) throws Exception;
+
+	String upload(MultipartFile file);
+}

+ 28 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/service/SFTPPoolService.java

@@ -0,0 +1,28 @@
+package org.springblade.los.ftp.service;
+
+import com.jcraft.jsch.ChannelSftp;
+import org.springblade.los.ftp.utils.Sftp.SFtpConfig;
+
+/**
+ * @author :jixinyuan
+ * @date : 2024/3/5
+ */
+public interface SFTPPoolService {
+
+	/**
+	 * 获取 sftp
+	 */
+	ChannelSftp borrowObject() ;
+
+	/**
+	 * 归还 sftp
+	 */
+	void returnObject(ChannelSftp channelSftp);
+
+	/**
+	 * 获取 ftp 配置信息
+	 * @return
+	 */
+	SFtpConfig getFtpPoolConfig();
+
+}

+ 73 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/service/impl/FTPPoolServiceImpl.java

@@ -0,0 +1,73 @@
+package org.springblade.los.ftp.service.impl;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.net.ftp.FTPClient;
+import org.apache.commons.pool2.impl.GenericObjectPool;
+import org.springblade.los.ftp.service.FTPPoolService;
+import org.springblade.los.ftp.utils.ftp.FTPClientFactory;
+import org.springblade.los.ftp.utils.ftp.FTPPoolConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+
+
+@Component
+@Slf4j
+public class FTPPoolServiceImpl implements FTPPoolService {
+
+	/**
+	 * ftp 连接池生成
+	 */
+	private GenericObjectPool<FTPClient> pool;
+
+	/**
+	 * ftp 客户端配置文件
+	 */
+	@Autowired
+	private FTPPoolConfig config;
+
+	/**
+	 * ftp 客户端工厂
+	 */
+	@Autowired
+	private FTPClientFactory factory;
+
+	/**
+	 * 初始化pool
+	 */
+	@PostConstruct
+	private void initPool() {
+		this.pool = new GenericObjectPool<FTPClient>(this.factory, this.config);
+	}
+
+	/**
+	 * 获取ftpClient
+	 */
+	@Override
+	public FTPClient borrowObject() {
+		if (this.pool != null) {
+			try {
+				return this.pool.borrowObject();
+			} catch (Exception e) {
+				log.error("获取 FTPClient 失败 ", e);
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * 归还 ftpClient
+	 */
+	@Override
+	public void returnObject(FTPClient ftpClient) {
+		if (this.pool != null && ftpClient != null) {
+			this.pool.returnObject(ftpClient);
+		}
+	}
+
+	@Override
+	public FTPPoolConfig getFtpPoolConfig() {
+		return config;
+	}
+}

+ 63 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/service/impl/FtpServiceImpl.java

@@ -0,0 +1,63 @@
+package org.springblade.los.ftp.service.impl;
+
+import com.jcraft.jsch.Channel;
+import com.jcraft.jsch.ChannelSftp;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.Session;
+import org.springblade.core.tool.api.R;
+import org.springblade.los.ftp.dto.FtpDto;
+import org.springblade.los.ftp.service.FtpService;
+import org.springblade.los.ftp.service.SFTPPoolService;
+import org.springblade.los.ftp.utils.Sftp.ChannelSftpFactory;
+import org.springblade.los.ftp.utils.Sftp.SFtpConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * @author :jixinyuan
+ * @date : 2024/3/5
+ */
+@Service
+public class FtpServiceImpl implements FtpService {
+
+	@Autowired
+	SFtpConfig sftpPoolConfig;
+
+	@Autowired
+	SFTPPoolService sFtpPoolService;
+	@Autowired
+	private ChannelSftpFactory factory;
+
+	@Override
+	public R detectionChannel(FtpDto ftpDto) throws Exception {
+		JSch jsch = new JSch();
+		Session session = null;
+		Channel channel = null;
+		boolean status = false;
+		session = jsch.getSession(ftpDto.getUsername(), ftpDto.getHost(), sftpPoolConfig.getPort());
+		session.setPassword(ftpDto.getPassword());
+		// 设置不需要交互式输入密码
+		session.setConfig("StrictHostKeyChecking", "no");
+		// 开始会话
+		session.connect();
+		// 打开SFTP通道
+		channel = session.openChannel("sftp");
+		channel.connect();
+		if (channel instanceof ChannelSftp && ((ChannelSftp) channel).isConnected()) {
+			status = true;
+		}
+		if (status) {
+			channel.disconnect();
+			session.disconnect();
+			return R.data("连接成功");
+		} else {
+			return R.fail("连接失败");
+		}
+	}
+
+	@Override
+	public String upload(MultipartFile file) {
+		return null;
+	}
+}

+ 76 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/service/impl/SFTPPoolServiceImpl.java

@@ -0,0 +1,76 @@
+package org.springblade.los.ftp.service.impl;
+
+import com.jcraft.jsch.ChannelSftp;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.pool2.impl.GenericObjectPool;
+import org.springblade.los.ftp.service.SFTPPoolService;
+import org.springblade.los.ftp.utils.Sftp.ChannelSftpFactory;
+import org.springblade.los.ftp.utils.Sftp.SFtpConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * @author :jixinyuan
+ * @date : 2024/3/5
+ */
+@Component
+@Slf4j
+public class SFTPPoolServiceImpl implements SFTPPoolService {
+	/**
+	 * ftp 连接池生成
+	 */
+	private GenericObjectPool<ChannelSftp> pool;
+
+	/**
+	 * ftp 客户端配置文件
+	 */
+	@Autowired
+	private SFtpConfig config;
+
+	/**
+	 * ftp 客户端工厂
+	 */
+	@Autowired
+	private ChannelSftpFactory factory;
+
+	/**
+	 * 初始化pool
+	 */
+	@PostConstruct
+	private void initPool() {
+		this.pool = new GenericObjectPool<ChannelSftp>(this.factory, this.config);
+	}
+
+	/**
+	 * 获取sftp
+	 */
+	@Override
+	public ChannelSftp borrowObject() {
+		if (this.pool != null) {
+			try {
+				return this.pool.borrowObject();
+			} catch (Exception e) {
+				log.error("获取 ChannelSftp 失败", e);
+				e.printStackTrace();
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * 归还 sftp
+	 */
+	@Override
+	public void returnObject(ChannelSftp channelSftp) {
+		if (this.pool != null && channelSftp != null) {
+			this.pool.returnObject(channelSftp);
+		}
+	}
+
+	@Override
+	public SFtpConfig getFtpPoolConfig() {
+		return config;
+	}
+}

+ 108 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/utils/Sftp/ChannelSftpFactory.java

@@ -0,0 +1,108 @@
+package org.springblade.los.ftp.utils.Sftp;
+
+import com.jcraft.jsch.ChannelSftp;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.Session;
+import lombok.EqualsAndHashCode;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.pool2.BasePooledObjectFactory;
+import org.apache.commons.pool2.PooledObject;
+import org.apache.commons.pool2.impl.DefaultPooledObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Properties;
+
+/**
+ * SFTP 工厂声场连接对象
+ */
+@EqualsAndHashCode(callSuper = true)
+@Component
+@Slf4j
+public class ChannelSftpFactory extends BasePooledObjectFactory<ChannelSftp>{
+	/**
+	 * 注入 sftp 连接配置
+	 */
+	@Autowired
+	SFtpConfig config;
+
+	/**
+	 * 新建对象
+	 */
+	@Override
+	public ChannelSftp create() {
+		ChannelSftp channel = null;
+		try {
+			// 用户名密码不能为空
+			if (StringUtils.isBlank(config.getUsername()) || StringUtils.isBlank(config.getPassword())) {
+				log.error("username or password is needed !!!");
+				return null;
+			}
+
+			JSch jsch = new JSch();
+			// 设置私钥
+			if (StringUtils.isNotBlank(config.getPrivateKey())) {
+				jsch.addIdentity(config.getPrivateKey());
+			}
+			// jsch的session需要补充设置sshConfig.put("PreferredAuthentications", "publickey,keyboard-interactive,password")来跳过Kerberos认证,同样的HutoolSFTPUtil工具类里面也有这个问题
+			Session sshSession = jsch.getSession(config.getUsername(), config.getHost(), config.getPort());
+			sshSession.setPassword(config.getPassword());
+			Properties sshConfig = new Properties();
+			// “StrictHostKeyChecking”如果设置成“yes”,ssh就不会自动把计算机的密匙加入“$HOME/.ssh/known_hosts”文件,并且一旦计算机的密匙发生了变化,就拒绝连接。
+			sshConfig.put("StrictHostKeyChecking", "no");
+			sshSession.setConfig(sshConfig);
+			sshSession.connect();
+			channel = (ChannelSftp) sshSession.openChannel("sftp");
+			channel.connect();
+		} catch (Exception e) {
+			log.error("连接 sftp 失败,请检查配置", e);
+		}
+		return channel;
+	}
+
+	/**
+	 * 创建一个连接
+	 *
+	 * @param channelSftp
+	 * @return
+	 */
+	@Override
+	public PooledObject<ChannelSftp> wrap(ChannelSftp channelSftp) {
+		return new DefaultPooledObject<>(channelSftp);
+	}
+
+	/**
+	 * 销毁一个连接
+	 *
+	 * @param p
+	 */
+	@Override
+	public void destroyObject(PooledObject<ChannelSftp> p) {
+		ChannelSftp channelSftp = p.getObject();
+		channelSftp.disconnect();
+	}
+
+	@Override
+	public boolean validateObject(final PooledObject<ChannelSftp> p) {
+		final ChannelSftp channelSftp = p.getObject();
+		try {
+			if (channelSftp.isClosed()) {
+				return false;
+			}
+			channelSftp.cd("/");
+		} catch (Exception e) {
+			log.error("channelSftp 不可用 ", e);
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 * 获取 FTP 连接配置
+	 * @return
+	 */
+	public SFtpConfig getConfig(){
+		return config;
+	}
+}

+ 150 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/utils/Sftp/HutoolSFTPUtil.java

@@ -0,0 +1,150 @@
+package org.springblade.los.ftp.utils.Sftp;
+
+
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.extra.ssh.JschUtil;
+import cn.hutool.extra.ssh.Sftp;
+import com.jcraft.jsch.Session;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+
+@Component
+@Slf4j
+public class HutoolSFTPUtil {
+
+	@Autowired
+	SFtpConfig sftpPoolConfig;
+
+	/**
+	 * 创建目录
+	 *
+	 * @param newDir
+	 */
+	public boolean mkdir(String newDir) {
+		// jsch的session需要补充设置sshConfig.put("PreferredAuthentications", "publickey,keyboard-interactive,password")来跳过Kerberos认证,同样的HutoolSFTPUtil工具类里面也有这个问题
+		Session session = JschUtil.createSession(sftpPoolConfig.getHost(), sftpPoolConfig.getPort(), sftpPoolConfig.getUsername(), sftpPoolConfig.getPassword());
+		Sftp sftp = JschUtil.createSftp(session);
+		boolean result = false;
+		try {
+			result = sftp.mkdir(newDir);
+		} catch (Exception e) {
+			log.error("mkdir error ", e);
+		} finally {
+			JschUtil.close(session);
+		}
+		return result;
+	}
+
+	/**
+	 * 删除目录
+	 *
+	 * @param delDir
+	 */
+	public boolean delDir(String delDir) {
+		Session session = JschUtil.createSession(sftpPoolConfig.getHost(), sftpPoolConfig.getPort(), sftpPoolConfig.getUsername(), sftpPoolConfig.getPassword());
+		Sftp sftp = JschUtil.createSftp(session);
+		boolean result = false;
+		try {
+			result = sftp.delDir(delDir);
+		} catch (Exception e) {
+			log.error("mkdir error ", e);
+		} finally {
+			JschUtil.close(session);
+		}
+		return result;
+	}
+
+	/**
+	 * 递归创建目录
+	 *
+	 * @param newDir
+	 * @return
+	 */
+	public boolean mkdirs(String newDir) {
+		Session session = JschUtil.createSession(sftpPoolConfig.getHost(), sftpPoolConfig.getPort(), sftpPoolConfig.getUsername(), sftpPoolConfig.getPassword());
+		Sftp sftp = JschUtil.createSftp(session);
+		boolean result = false;
+		try {
+			sftp.mkDirs(newDir);
+			result = true;
+		} catch (Exception e) {
+			log.error("mkdir error ", e);
+		} finally {
+			JschUtil.close(session);
+		}
+		return result;
+	}
+
+	/**
+	 * 上传文件
+	 *
+	 * @param destPath
+	 * @param srcFileFullPath
+	 * @return
+	 */
+	public boolean upload(String destPath, String srcFileFullPath) {
+		Session session = JschUtil.createSession(sftpPoolConfig.getHost(), sftpPoolConfig.getPort(), sftpPoolConfig.getUsername(), sftpPoolConfig.getPassword());
+		Sftp sftp = JschUtil.createSftp(session);
+		boolean result = false;
+		try {
+			File file = new File(srcFileFullPath);
+			result = sftp.upload(destPath, file);
+		} catch (Exception e) {
+			log.error("mkdir error ", e);
+		} finally {
+			JschUtil.close(session);
+		}
+		return result;
+	}
+
+	/**
+	 * 下载文件
+	 *
+	 * @param src
+	 * @param destFileFullPath
+	 * @return
+	 */
+	public boolean download(String src, String destFileFullPath) {
+		Session session = JschUtil.createSession(sftpPoolConfig.getHost(), sftpPoolConfig.getPort(), sftpPoolConfig.getUsername(), sftpPoolConfig.getPassword());
+		Sftp sftp = JschUtil.createSftp(session);
+		boolean result = false;
+		try {
+			File destFile = new File(destFileFullPath);
+			if (!destFile.exists()) {
+				destFile.createNewFile();
+			}
+			sftp.download(src, destFile);
+			result = true;
+		} catch (Exception e) {
+			log.error("mkdir error ", e);
+		} finally {
+			JschUtil.close(session);
+		}
+		return result;
+	}
+
+	/**
+	 * 远程执行 shell 命令
+	 *
+	 * @param command
+	 * @return
+	 * @throws Exception
+	 */
+	public String exec(String command) {
+		Session session = JschUtil.createSession(sftpPoolConfig.getHost(), sftpPoolConfig.getPort(), sftpPoolConfig.getUsername(), sftpPoolConfig.getPassword());
+		String result = "";
+		try {
+			log.info("执行命令:{}", command);
+			result = JschUtil.exec(session, command, CharsetUtil.CHARSET_UTF_8);
+			log.info("执行结果:{}", result);
+		} catch (Exception e) {
+			log.error("exec error ", e);
+		} finally {
+			JschUtil.close(session);
+		}
+		return result;
+	}
+}

+ 501 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/utils/Sftp/SFTPUtil.java

@@ -0,0 +1,501 @@
+package org.springblade.los.ftp.utils.Sftp;
+
+import cn.hutool.core.io.file.FileNameUtil;
+import cn.hutool.core.util.ArrayUtil;
+import com.jcraft.jsch.ChannelSftp;
+import com.jcraft.jsch.SftpException;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.los.ftp.service.SFTPPoolService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Vector;
+import java.util.stream.Collectors;
+
+/**
+ * Sftp工具类
+ */
+@Component
+@Slf4j
+public class SFTPUtil {
+
+	/**
+	 * sftp连接池服务
+	 */
+	@Autowired
+	SFTPPoolService sFtpPoolService;
+
+	/**
+	 * 文件分隔符,linux下就是 /
+	 */
+	private String NODE_SEPARATOR = "/";
+
+	/**
+	 * 权限数字
+	 */
+	private String permission = "755";
+
+	/**
+	 * 每个目录下最大子文件(夹)数量
+	 */
+	private int MAX_CHILD_FILE_NUMBER = 1000;
+
+
+	/**
+	 * 本地文件上传到远程 目录
+	 *
+	 * @param relativePath 远程文件最后一级目录
+	 * @param fileName 远程文件名
+	 * @param localFileFullPath 本地文件全路径
+	 * @return
+	 */
+	public String uploadLocalToRemote(String relativePath, String fileName, String localFileFullPath) {
+		String remoteFullPath = "";
+		File localFile = new File(localFileFullPath);
+		if (localFile.exists() && localFile.isFile()) {
+			try (InputStream inputStream = new FileInputStream(localFile)) {
+				if (!relativePath.endsWith(NODE_SEPARATOR)) {
+					relativePath = relativePath.concat(NODE_SEPARATOR);
+				}
+				remoteFullPath = upload(relativePath, fileName, inputStream);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		}
+		return remoteFullPath;
+	}
+
+
+	/**
+	 * 附件上传
+	 *
+	 * @param fileName    文件名
+	 * @param inputStream 文件流
+	 * @return 上传后的文件完整路径
+	 */
+	public String upload(String fileName, InputStream inputStream) {
+		return upload(null, fileName, inputStream);
+	}
+
+	/**
+	 * 文件上传
+	 *
+	 * @param relativePath 文件保存的相对路径(最后一级目录)
+	 * @param fileName     文件名
+	 * @param inputStream  文件流
+	 * @return 上传后的文件完整路径
+	 */
+	public String upload(String relativePath, String fileName, InputStream inputStream) {
+		ChannelSftp sftp = sFtpPoolService.borrowObject();
+		String filePath = sFtpPoolService.getFtpPoolConfig().getWorkingDirectory();
+		try {
+			if (relativePath != null && !relativePath.trim().isEmpty()) {
+				filePath = filePath + relativePath;
+			}
+			if (!dirIsExist(filePath)) {
+				filePath = generateValidPath(filePath, sftp);
+			}
+			filePath = filePath.concat(fileName);
+			sftp.put(inputStream, filePath);
+			sftp.chmod(Integer.parseInt(permission, 8), filePath);
+			return filePath;
+		} catch (SftpException e) {
+			log.error("SFTP上传文件出错", e);
+		} finally {
+			sFtpPoolService.returnObject(sftp);
+		}
+		return filePath;
+	}
+
+	/**
+	 * 文件下载
+	 *
+	 * @param fileUrl 文件路径
+	 * @return 文件字节数组
+	 */
+	public byte[] download(String fileUrl) {
+		ChannelSftp sftp = sFtpPoolService.borrowObject();
+		try {
+			InputStream inputStream = sftp.get(fileUrl);
+			ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+			int n;
+			byte[] data = new byte[sFtpPoolService.getFtpPoolConfig().getBufferSize()];
+			while ((n = inputStream.read(data, 0, data.length)) != -1) {
+				buffer.write(data, 0, n);
+			}
+			buffer.flush();
+			return buffer.toByteArray();
+		} catch (IOException | SftpException e) {
+			log.error("SFTP下载文件出错", e);
+		} finally {
+			sFtpPoolService.returnObject(sftp);
+		}
+		return new byte[0];
+	}
+
+	/**
+	 * 创建目录(只能创建一级目录,如果需要创建多级目录,需要调用mkdirs方法)
+	 *
+	 * @param path 目录路径
+	 */
+	public void createFolder(String path) {
+		ChannelSftp sftp = sFtpPoolService.borrowObject();
+		try {
+			sftp.mkdir(path);
+		} catch (SftpException e) {
+			log.error("SFTP创建文件夹出错", e);
+		} finally {
+			sFtpPoolService.returnObject(sftp);
+		}
+	}
+
+	/**
+	 * 如果目录不存在,则创建多级目录
+	 *
+	 * @param path
+	 * @return
+	 */
+	public boolean createFolders(String path){
+		ChannelSftp sftp = sFtpPoolService.borrowObject();
+		boolean result = false;
+		try {
+			if (validatePathValid(path, sftp)) {
+				result = true;
+			} else {
+				String newPath = path + String.valueOf(System.currentTimeMillis()).substring(9);
+				mkdirs(sftp, newPath.split("/"), "", newPath.split("/").length, 0);
+				result = true;
+			}
+		} catch (Exception e) {
+			log.error("SFTP创建文件夹出错", e);
+		} finally {
+			sFtpPoolService.returnObject(sftp);
+		}
+		return result;
+	}
+
+	/**
+	 * 文件读取
+	 *
+	 * @param fileUrl 文件路径
+	 * @return 文件字节数组
+	 */
+	public String read(String fileUrl) {
+		ChannelSftp sftp = sFtpPoolService.borrowObject();
+		try {
+			InputStream inputStream = sftp.get(fileUrl);
+			BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
+			String str, resultStr = "";
+			while ((str = in.readLine()) != null) {
+				resultStr = resultStr.concat(str);
+			}
+			return resultStr;
+		} catch (SftpException | IOException e) {
+			log.error("SFTP读取文件出错", e);
+		} finally {
+			sFtpPoolService.returnObject(sftp);
+		}
+		return "";
+	}
+
+	/**
+	 * 判断目录是否存在
+	 *
+	 * @param url 文件夹目录
+	 * @return ture:存在;false:不存在
+	 */
+	public boolean dirIsExist(String url) {
+		ChannelSftp sftp = sFtpPoolService.borrowObject();
+		try {
+			if (isDirectory(url)) {
+				sftp.cd(url);
+				String pwd = sftp.pwd();
+				return pwd.equals(url) || pwd.concat("/").equals(url);
+			}
+			return false;
+		} catch (SftpException e) {
+			log.error("SFTP读取文件夹出错", e);
+		} finally {
+			sFtpPoolService.returnObject(sftp);
+		}
+		return false;
+	}
+
+	/**
+	 * 删除文件 或 删除文件夹
+	 * 注: 如果是文件夹, 不论该文件夹中有无内容,都能删除, 因此:此方法慎用
+	 *
+	 * @param remoteDirOrRemoteFile 要删除的文件  或 文件夹
+	 */
+	public void delete(String remoteDirOrRemoteFile) {
+		ChannelSftp sftp = sFtpPoolService.borrowObject();
+		try {
+			List<String> targetFileOrDirContainer = new ArrayList<>(8);
+			targetFileOrDirContainer.add(remoteDirOrRemoteFile);
+			List<String> toBeDeletedEmptyDirContainer = new ArrayList<>(8);
+			if (isDirectory(remoteDirOrRemoteFile)) {
+				toBeDeletedEmptyDirContainer.add(remoteDirOrRemoteFile);
+			}
+			collectToBeDeletedEmptyDir(toBeDeletedEmptyDirContainer, targetFileOrDirContainer);
+			if (!toBeDeletedEmptyDirContainer.isEmpty()) {
+				String targetDir;
+				for (int i = toBeDeletedEmptyDirContainer.size() - 1; i >= 0; i--) {
+					targetDir = toBeDeletedEmptyDirContainer.get(i);
+					sftp.rmdir(targetDir);
+				}
+			}
+		} catch (SftpException e) {
+			log.error("SFTP删除文件或者文件夹出错", e);
+		} finally {
+			sFtpPoolService.returnObject(sftp);
+		}
+	}
+
+	/**
+	 * 删除相关文件 并 采集所有 需要被删除的 文件夹
+	 * <p>
+	 * 注: 如果是文件夹, 不论该文件夹中有无内容,都能删除, 因此:此方法慎用
+	 *
+	 * @param toBeDeletedEmptyDirContainer 所有待删除的空文件夹集合
+	 * @param targetFileOrDirContainer     本次, 要删除的文件的集合   或   本次, 要删除的文件所在文件夹的集合
+	 */
+	public void collectToBeDeletedEmptyDir(List<String> toBeDeletedEmptyDirContainer, List<String> targetFileOrDirContainer) {
+		List<String> todoCallDirContainer = new ArrayList<>(8);
+		List<String> subfolderList;
+		for (String remoteDirOrRemoteFile : targetFileOrDirContainer) {
+			subfolderList = fileDeleteExecutor(remoteDirOrRemoteFile);
+			toBeDeletedEmptyDirContainer.addAll(subfolderList);
+			todoCallDirContainer.addAll(subfolderList);
+		}
+		if (!todoCallDirContainer.isEmpty()) {
+			collectToBeDeletedEmptyDir(toBeDeletedEmptyDirContainer, todoCallDirContainer);
+		}
+	}
+
+	/**
+	 * 删除remoteDirOrRemoteFile指向的文件 或 删除remoteDirOrRemoteFile指向的文件夹下的所有子级文件
+	 * 注: 如果是文件夹, 只会删除该文件夹下的子级文件;不会删除该文件夹下的孙子级文件(如果有孙子级文件的话)
+	 *
+	 * @param remoteDirOrRemoteFile 要删除的文件 或 要 文件夹   【绝对路径】
+	 * @return remoteDirOrRemoteFile指向的文件夹 下的 文件夹集合
+	 * 注: 如果remoteDirOrRemoteFile指向的是文件的话,返回空的集合
+	 * 注: 只会包含子级文件夹,不包含孙子级文件夹(如果有孙子级文件夹的话)
+	 */
+	public List<String> fileDeleteExecutor(String remoteDirOrRemoteFile) {
+		ChannelSftp sftp = sFtpPoolService.borrowObject();
+		try {
+			List<String> subfolderList = new ArrayList<>(8);
+			// 如果是文件,直接删除
+			if (!isDirectory(remoteDirOrRemoteFile)) {
+				sftp.rm(remoteDirOrRemoteFile);
+				return subfolderList;
+			}
+			// 保证 remoteDirOrRemoteFile 以 “/” 开头,以 “/” 结尾
+			remoteDirOrRemoteFile = handlePath(remoteDirOrRemoteFile, true, true);
+			Vector<?> vector = sftp.ls(remoteDirOrRemoteFile);
+			String fileName;
+			String sftpAbsoluteFilename;
+			// 列出文件名
+			for (Object item : vector) {
+				ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) item;
+				fileName = entry.getFilename();
+				if (invalidFileName(fileName)) {
+					continue;
+				}
+				sftpAbsoluteFilename = remoteDirOrRemoteFile + fileName;
+				// 如果是文件,直接删除
+				if (!isDirectory(sftpAbsoluteFilename)) {
+					sftp.rm(sftpAbsoluteFilename);
+					continue;
+				}
+				subfolderList.add(sftpAbsoluteFilename);
+			}
+			return subfolderList;
+		} catch (SftpException e) {
+			log.error("SFTP删除文件或者文件夹出错", e);
+		} finally {
+			sFtpPoolService.returnObject(sftp);
+		}
+		return new ArrayList<>(0);
+	}
+
+	/**
+	 * 从给定路径中截取文件名
+	 *
+	 * @param path 路径,  如: /files/abc/info.yml
+	 * @return 文件名, 如: info.yml
+	 */
+	private String getFilenameFromPath(String path) {
+		return path.substring(path.lastIndexOf(NODE_SEPARATOR) + 1);
+	}
+
+	/**
+	 * 路径处理器
+	 * <p>
+	 * 根据参数控制处理类型,如:
+	 * 当: originPath 为【var/apps】时,
+	 * 当: handleHead 为 true, 处理结果为【/var/apps】
+	 * 当: handleTail 为 true, 处理结果为【var/apps/】
+	 * 当: handleHead 和 handleTail 均为 true, 处理结果为【/var/apps/】
+	 *
+	 * @param originPath 要处理的路径
+	 * @param handleHead 处理 起始处
+	 * @param handleTail 处理 结尾处
+	 * @return 处理后的路径
+	 */
+	private String handlePath(String originPath, boolean handleHead, boolean handleTail) {
+		if (originPath == null || "".equals(originPath.trim())) {
+			return NODE_SEPARATOR;
+		}
+		if (handleHead && !originPath.startsWith(NODE_SEPARATOR)) {
+			originPath = NODE_SEPARATOR.concat(originPath);
+		}
+		if (handleTail && !originPath.endsWith(NODE_SEPARATOR)) {
+			originPath = originPath.concat(NODE_SEPARATOR);
+		}
+		return originPath;
+	}
+
+	/**
+	 * 判断是否为无效的文件名
+	 * 注:文件名(夹)名为【.】或【..】时,是无效的
+	 *
+	 * @param fileName 文件名
+	 * @return 是有无效
+	 */
+	public boolean invalidFileName(String fileName) {
+		return ".".equals(fileName) || "..".equals(fileName);
+	}
+
+	/**
+	 * 判断SFTP上的path是否为文件夹
+	 * 注:如果该路径不存在,那么会返回false
+	 *
+	 * @param path SFTP上的路径
+	 * @return 判断结果
+	 */
+	public boolean isDirectory(String path) {
+		ChannelSftp sftp = sFtpPoolService.borrowObject();
+		// 合法的错误id
+		// int legalErrorId = 4;
+		try {
+			sftp.cd(path);
+			return true;
+		} catch (SftpException e) {
+			// 如果 path不存在,那么报错信息为【No such file】,错误id为【2】
+			// 如果 path存在,但是不能cd进去,那么报错信息形如【Can't change directory: /files/sqljdbc4-3.0.jar】,错误id为【4】
+			return false;
+		} finally {
+			sFtpPoolService.returnObject(sftp);
+		}
+	}
+
+	/**
+	 * 获取某个文件夹下的所有文件名称
+	 *
+	 * @param path      文件夹路径
+	 * @param fileTypes 文件类型,如果为null或者长度为0,则获取所有文件名称,如果已指定,则获取指定类型的文件类型
+	 * @return 文件名称
+	 */
+	public List<String> queryFileName(String path, String... fileTypes) {
+		ChannelSftp sftp = sFtpPoolService.borrowObject();
+		try {
+			Vector<ChannelSftp.LsEntry> ls = sftp.ls(path);
+			return ls.stream().map(ChannelSftp.LsEntry::getFilename).filter(
+				name -> {
+					if (ArrayUtil.isNotEmpty(fileTypes)) {
+						return FileNameUtil.isType(name, fileTypes);
+					}
+					return true;
+				}).collect(Collectors.toList());
+		} catch (SftpException e) {
+			log.error("SFTP获取某个文件夹下的所有文件名称出错", e);
+		} finally {
+			sFtpPoolService.returnObject(sftp);
+		}
+		return new ArrayList<>(0);
+	}
+
+	/**
+	 * 创建多级文件目录
+	 *
+	 * @param dirs     每个目录的名称数组
+	 * @param tempPath 临时路径,传入""空字符串,主要为了递归调用方便
+	 * @param length   数组长度
+	 * @param index    当前索引,为了递归调用
+	 */
+	private void mkdirs(ChannelSftp sftp, String[] dirs, String tempPath, int length, int index) {
+		// 以"/a/b/c/d"为例按"/"分隔后,第0位是"";顾下标从1开始
+		index++;
+		if (index < length) {
+			// 目录不存在,则创建文件夹
+			tempPath += "/" + dirs[index];
+		}
+		try {
+			sftp.cd(tempPath);
+			if (index < length) {
+				mkdirs(sftp, dirs, tempPath, length, index);
+			}
+		} catch (SftpException ex) {
+			try {
+				sftp.mkdir(tempPath);
+				sftp.chmod(Integer.parseInt(permission, 8), tempPath);
+				sftp.cd(tempPath);
+			} catch (SftpException e) {
+				return;
+			}
+			mkdirs(sftp, dirs, tempPath, length, index);
+		}
+	}
+
+	/**
+	 * 统计目录下文件(夹)数量
+	 *
+	 * @param path 目录路径
+	 * @return 文件数量
+	 */
+	private int countFiles(String path) throws SftpException {
+		ChannelSftp sftp = sFtpPoolService.borrowObject();
+		try {
+			sftp.cd(path);
+			return sftp.ls(path).size();
+		} finally {
+			sFtpPoolService.returnObject(sftp);
+		}
+	}
+
+	/**
+	 * 校验路径是否可用
+	 *
+	 * @param path 路径
+	 * @return 是否可用
+	 */
+	private boolean validatePathValid(String path, ChannelSftp sftp) {
+		int countFiles = 0;
+		try {
+			countFiles = countFiles(path);
+		} catch (SftpException e) {
+			mkdirs(sftp, path.split("/"), "", path.split("/").length, 0);
+		}
+		return countFiles <= MAX_CHILD_FILE_NUMBER;
+	}
+
+	/**
+	 * 生成有效路径
+	 *
+	 * @param path 参数路径
+	 * @return 解析后的有效路径
+	 */
+	private String generateValidPath(String path, ChannelSftp sftp) {
+		if (validatePathValid(path, sftp)) {
+			return path;
+		} else {
+			String newPath = path + String.valueOf(System.currentTimeMillis()).substring(9);
+			mkdirs(sftp, newPath.split("/"), "", newPath.split("/").length, 0);
+			return newPath;
+		}
+	}
+}

+ 79 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/utils/Sftp/SFtpConfig.java

@@ -0,0 +1,79 @@
+package org.springblade.los.ftp.utils.Sftp;
+
+import lombok.Data;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * fileName:FtpConfig
+ * description:
+ * author:hcq
+ * createTime:2019-03-15 15:04
+ */
+
+@Configuration
+@Data
+public class SFtpConfig extends GenericObjectPoolConfig {
+	@Value("${sftp.client.workingDirectory}")
+	// 默认进入的路径
+	private String workingDirectory;
+	// 主机地址
+	@Value("${sftp.client.host}")
+	private String host;
+	// 主机端口
+	@Value("${sftp.client.port}")
+	private int port;
+	// 主机用户名
+	@Value("${sftp.client.username}")
+	private String username;
+	// 主机密码
+	@Value("${sftp.client.password}")
+	private String password;
+	// 主机密码
+	@Value("${sftp.client.privateKey}")
+	private String privateKey;
+	// 传输编码
+	@Value("${sftp.client.encoding}")
+	private String encoding;
+	// 连接超时时间
+	@Value("${sftp.client.clientTimeout}")
+	private int clientTimeout;
+
+	// 重新连接时间
+	@Value("${sftp.client.retryTimes}")
+	private int retryTimes;
+	// 缓存大小
+	@Value("${sftp.client.bufferSize}")
+	private int bufferSize;
+	// 最大数
+	@Value("${sftp.client.maxTotal}")
+	private int maxTotal;
+	// 最小空闲
+	@Value("${sftp.client.minldle}")
+	private int minldle;
+	// 最大空闲
+	@Value("${sftp.client.maxldle}")
+	private int maxldle;
+	// 最大等待时间
+	@Value("${sftp.client.maxWait}")
+	private int maxWait;
+	// 池对象耗尽之后是否阻塞,maxWait < 0 时一直等待
+	@Value("${sftp.client.blockWhenExhausted}")
+	private boolean blockWhenExhausted;
+	// 取对象时验证
+	@Value("${sftp.client.testOnBorrow}")
+	private boolean testOnBorrow;
+	// 回收验证
+	@Value("${sftp.client.testOnReturn}")
+	private boolean testOnReturn;
+	// 创建时验证
+	@Value("${sftp.client.testOnCreate}")
+	private boolean testOnCreate;
+	// 空闲验证
+	@Value("${sftp.client.testWhileldle}")
+	private boolean testWhileldle;
+	// 后进先出
+	@Value("${sftp.client.lifo}")
+	private boolean lifo;
+}

+ 140 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/utils/ftp/FTPClientFactory.java

@@ -0,0 +1,140 @@
+package org.springblade.los.ftp.utils.ftp;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.net.ftp.FTP;
+import org.apache.commons.net.ftp.FTPClient;
+import org.apache.commons.net.ftp.FTPReply;
+import org.apache.commons.pool2.PooledObject;
+import org.apache.commons.pool2.PooledObjectFactory;
+import org.apache.commons.pool2.impl.DefaultPooledObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * FtpClient 工厂声场连接对象
+ */
+@Component
+@Slf4j
+public class FTPClientFactory implements PooledObjectFactory<FTPClient> {
+	/**
+	 * 注入 ftp 连接配置
+	 */
+	@Autowired
+	FTPPoolConfig config;
+
+	/**
+	 * 创建连接到池中
+	 *
+	 * @return
+	 * @throws Exception
+	 */
+	@Override
+	public PooledObject<FTPClient> makeObject() throws Exception {
+		FTPClient ftpClient = new FTPClient();
+		ftpClient.setConnectTimeout(config.getClientTimeout());
+		ftpClient.connect(config.getHost(), config.getPort());
+		int reply = ftpClient.getReplyCode();
+		if (!FTPReply.isPositiveCompletion(reply)) {
+			ftpClient.disconnect();
+			return null;
+		}
+		boolean success;
+		if (StringUtils.isBlank(config.getUsername())) {
+			success = ftpClient.login("anonymous", "anonymous");
+		} else {
+			success = ftpClient.login(config.getUsername(), config.getPassword());
+		}
+		if (!success) {
+			return null;
+		}
+		ftpClient.setFileType(config.getTransferFileType());
+		ftpClient.setBufferSize(1024);
+		ftpClient.setControlEncoding(config.getEncoding());
+		if (config.isPassiveMode()) {
+			ftpClient.enterLocalPassiveMode();
+		}
+		log.debug("创建ftp连接");
+		return new DefaultPooledObject<>(ftpClient);
+	}
+
+	/**
+	 * 链接状态检查
+	 *
+	 * @param pool
+	 * @return
+	 */
+	@Override
+	public boolean validateObject(PooledObject<FTPClient> pool) {
+		FTPClient ftpClient = pool.getObject();
+		try {
+			return ftpClient != null && ftpClient.sendNoOp();
+		} catch (Exception e) {
+			return false;
+		}
+	}
+
+	/**
+	 * 销毁连接,当连接池空闲数量达到上限时,调用此方法销毁连接
+	 *
+	 * @param pool
+	 * @throws Exception
+	 */
+	@Override
+	public void destroyObject(PooledObject<FTPClient> pool) throws Exception {
+		FTPClient ftpClient = pool.getObject();
+		if (ftpClient != null) {
+			try {
+				ftpClient.disconnect();
+				log.debug("销毁ftp连接");
+			} catch (Exception e) {
+				log.error("销毁ftpClient异常,error:", e.getMessage());
+			}
+		}
+	}
+
+	/**
+	 * 钝化连接,是连接变为可用状态
+	 *
+	 * @param p
+	 * @throws Exception
+	 */
+	@Override
+	public void passivateObject(PooledObject<FTPClient> p) throws Exception{
+		FTPClient ftpClient = p.getObject();
+		try {
+			ftpClient.changeWorkingDirectory(config.getWorkingDirectory());
+			ftpClient.logout();
+			if (ftpClient.isConnected()) {
+				ftpClient.disconnect();
+			}
+		} catch (Exception e) {
+			throw new RuntimeException("Could not disconnect from server.", e);
+		}
+	}
+
+	/**
+	 * 初始化连接
+	 *
+	 * @param pool
+	 * @throws Exception
+	 */
+	@Override
+	public void activateObject(PooledObject<FTPClient> pool) throws Exception {
+		FTPClient ftpClient = pool.getObject();
+		ftpClient.connect(config.getHost(),config.getPort());
+		ftpClient.login(config.getUsername(), config.getPassword());
+		ftpClient.setControlEncoding(config.getEncoding());
+		ftpClient.changeWorkingDirectory(config.getWorkingDirectory());
+		//设置上传文件类型为二进制,否则将无法打开文件
+		ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
+	}
+
+	/**
+	 * 获取 FTP 连接配置
+	 * @return
+	 */
+	public FTPPoolConfig getConfig(){
+		return config;
+	}
+}

+ 81 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/utils/ftp/FTPPoolConfig.java

@@ -0,0 +1,81 @@
+package org.springblade.los.ftp.utils.ftp;
+
+import lombok.Data;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.beans.factory.annotation.Value;
+
+@Configuration
+@Data
+public class FTPPoolConfig extends GenericObjectPoolConfig {
+
+	// 默认进入的路径
+	@Value("${ftp.client.workingDirectory}")
+	private String workingDirectory;
+	// 主机地址
+	@Value("${ftp.client.host}")
+	private String host;
+	// 主机端口
+	@Value("${ftp.client.port}")
+	private int port;
+	// 主机用户名
+	@Value("${ftp.client.username}")
+	private String username;
+	// 主机密码
+	@Value("${ftp.client.password}")
+	private String password;
+	// 传输编码
+	@Value("${ftp.client.encoding}")
+	private String encoding;
+	// 被动模式:在这种模式下,数据连接是由客户程序发起的
+	@Value("${ftp.client.passiveMode}")
+	private boolean passiveMode;
+	// 连接超时时间
+	@Value("${ftp.client.clientTimeout}")
+	private int clientTimeout;
+	// 线程数
+	@Value("${ftp.client.threaNum}")
+	private int threaNum;
+	// 0=ASCII_FILE_TYPE(ASCII格式),1=EBCDIC_FILE_TYPE,2=LOCAL_FILE_TYPE(二进制文件)
+	@Value("${ftp.client.transferFileType}")
+	private int transferFileType;
+	// 是否重命名
+	@Value("${ftp.client.renameUploaded}")
+	private boolean renameUploaded;
+	// 重新连接时间
+	@Value("${ftp.client.retryTimes}")
+	private int retryTimes;
+	// 缓存大小
+	@Value("${ftp.client.bufferSize}")
+	private int bufferSize;
+	// 最大数
+	@Value("${ftp.client.maxTotal}")
+	private int maxTotal;
+	// 最小空闲
+	@Value("${ftp.client.minldle}")
+	private int minldle;
+	// 最大空闲
+	@Value("${ftp.client.maxldle}")
+	private int maxldle;
+	// 最大等待时间
+	@Value("${ftp.client.maxWait}")
+	private int maxWait;
+	// 池对象耗尽之后是否阻塞,maxWait < 0 时一直等待
+	@Value("${ftp.client.blockWhenExhausted}")
+	private boolean blockWhenExhausted;
+	// 取对象时验证
+	@Value("${ftp.client.testOnBorrow}")
+	private boolean testOnBorrow;
+	// 回收验证
+	@Value("${ftp.client.testOnReturn}")
+	private boolean testOnReturn;
+	// 创建时验证
+	@Value("${ftp.client.testOnCreate}")
+	private boolean testOnCreate;
+	// 空闲验证
+	@Value("${ftp.client.testWhileldle}")
+	private boolean testWhileldle;
+	// 后进先出
+	@Value("${ftp.client.lifo}")
+	private boolean lifo;
+}

+ 559 - 0
blade-service/blade-los/src/main/java/org/springblade/los/ftp/utils/ftp/FTPUtil.java

@@ -0,0 +1,559 @@
+package org.springblade.los.ftp.utils.ftp;
+
+import cn.hutool.core.util.CharsetUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.net.ftp.FTP;
+import org.apache.commons.net.ftp.FTPClient;
+import org.apache.commons.net.ftp.FTPFile;
+import org.springblade.los.ftp.service.FTPPoolService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * FTP工具类
+ *
+ * @Scope设置Bean的作用域 作用域类型            使用范围	            作用域描述
+ * singleton	    所有Spring应用	    默认值,IoC容器只存在单例
+ * prototype	    所有Spring应用	    每次从IoC容器中取出都是一个bean,都创建一个新的bean
+ * session	        Spring web应用	    Http会话
+ * application	    Spring web应用	    web工程生命周期
+ * request	        Spring web应用	    web工程单次请求
+ * globalSession	Spring web应用	    在一个全局的 httpSession中,一个bean对应一个实例。实践中基本不使用
+ */
+@Component
+@Slf4j
+public class FTPUtil {
+
+	/**
+	 * ftp 连接池
+	 */
+	@Autowired
+	FTPPoolService ftpPoolService;
+
+	/**
+	 * 从FTP服务器上下载文件,支持断点续传,下载百分比汇报
+	 *
+	 * @param remote 远程文件路径及名称
+	 * @param local  本地文件完整绝对路径
+	 * @return 下载的状态
+	 * @throws IOException
+	 */
+	public DownloadStatus download(String remote, String local) throws IOException {
+		FTPClient ftpClient = ftpPoolService.borrowObject();
+		// 设置被动模式,由于Linux安全性考虑,端口没有全部放开,所有被动模式不能用
+		ftpClient.enterLocalPassiveMode();
+		// 设置以二进制方式传输
+		ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
+		DownloadStatus result;
+		try {
+			// 检查远程文件是否存在
+			FTPFile[] files = ftpClient.listFiles(new String(remote.getBytes(CharsetUtil.UTF_8), CharsetUtil.ISO_8859_1));
+			if (files.length != 1) {
+				log.info("远程文件不存在");
+				return DownloadStatus.RemoteFileNotExist;
+			}
+			long lRemoteSize = files[0].getSize();
+			File f = new File(local);
+			// 本地存在文件,进行断点下载
+			if (f.exists()) {
+				long localSize = f.length();
+				// 判断本地文件大小是否大于远程文件大小
+				if (localSize >= lRemoteSize) {
+					log.info("本地文件大于远程文件,下载中止");
+					return DownloadStatus.LocalFileBiggerThanRemoteFile;
+				}
+				// 进行断点续传,并记录状态
+				FileOutputStream out = new FileOutputStream(f, true);
+				ftpClient.setRestartOffset(localSize);
+				InputStream in = ftpClient.retrieveFileStream(new String(remote.getBytes(CharsetUtil.UTF_8), CharsetUtil.ISO_8859_1));
+				byte[] bytes = new byte[1024];
+				long step = lRemoteSize / 100;
+				step = step == 0 ? 1 : step;// 文件过小,step可能为0
+				long process = localSize / step;
+				int c;
+				while ((c = in.read(bytes)) != -1) {
+					out.write(bytes, 0, c);
+					localSize += c;
+					long nowProcess = localSize / step;
+					if (nowProcess > process) {
+						process = nowProcess;
+						if (process % 10 == 0) {
+							log.info("下载进度:" + process);
+						}
+					}
+				}
+				in.close();
+				out.close();
+				boolean isDo = ftpClient.completePendingCommand();
+				if (isDo) {
+					result = DownloadStatus.DownloadFromBreakSuccess;
+				} else {
+					result = DownloadStatus.DownloadFromBreakFailed;
+				}
+			} else {
+				OutputStream out = new FileOutputStream(f);
+				InputStream in = ftpClient.retrieveFileStream(new String(remote.getBytes(CharsetUtil.UTF_8), CharsetUtil.ISO_8859_1));
+				byte[] bytes = new byte[1024];
+				long step = lRemoteSize / 100;
+				step = step == 0 ? 1 : step;// 文件过小,step可能为0
+				long process = 0;
+				long localSize = 0L;
+				int c;
+				while ((c = in.read(bytes)) != -1) {
+					out.write(bytes, 0, c);
+					localSize += c;
+					long nowProcess = localSize / step;
+					if (nowProcess > process) {
+						process = nowProcess;
+						if (process % 10 == 0) {
+							log.info("下载进度:" + process);
+						}
+					}
+				}
+				in.close();
+				out.close();
+				boolean upNewStatus = ftpClient.completePendingCommand();
+				if (upNewStatus) {
+					result = DownloadStatus.DownloadNewSuccess;
+				} else {
+					result = DownloadStatus.DownloadNewFailed;
+				}
+			}
+		} catch (Exception e) {
+			log.error("download error", e);
+		} finally {
+			ftpPoolService.returnObject(ftpClient);
+		}
+		return DownloadStatus.DownloadNewFailed;
+	}
+
+	/**
+	 * 切换远程目录
+	 *
+	 * @param path
+	 * @return
+	 * @throws IOException
+	 */
+	public boolean changeDirectory(String path) {
+		FTPClient ftpClient = ftpPoolService.borrowObject();
+		boolean result = false;
+		try {
+			result = ftpClient.changeWorkingDirectory(path);
+		} catch (Exception e) {
+			log.error("changeDirectory error ", e);
+		} finally {
+			ftpPoolService.returnObject(ftpClient);
+		}
+		return result;
+	}
+
+	/**
+	 * 创建远程目录
+	 *
+	 * @param pathName
+	 * @return
+	 * @throws IOException
+	 */
+	public boolean createDirectory(String pathName) {
+		FTPClient ftpClient = ftpPoolService.borrowObject();
+		boolean result = false;
+		try {
+			result = ftpClient.makeDirectory(pathName);
+		} catch (Exception e) {
+			log.error("createDirectory error ", e);
+		} finally {
+			ftpPoolService.returnObject(ftpClient);
+		}
+		return result;
+	}
+
+	/**
+	 * 删除远程目录
+	 *
+	 * @param path
+	 * @return
+	 * @throws IOException
+	 */
+	public boolean removeDirectory(String path) {
+		FTPClient ftpClient = ftpPoolService.borrowObject();
+		boolean result = false;
+		try {
+			result = ftpClient.removeDirectory(path);
+		} catch (Exception e) {
+			log.error("removeDirectory error ", e);
+		} finally {
+			ftpPoolService.returnObject(ftpClient);
+		}
+		return result;
+	}
+
+	/**
+	 * 删除远程目录,可递归删除所有
+	 *
+	 * @param path
+	 * @param isAll
+	 * @return
+	 * @throws IOException
+	 */
+	public boolean removeDirectory(String path, boolean isAll) {
+		FTPClient ftpClient = ftpPoolService.borrowObject();
+		boolean result = false;
+		try {
+			if (!isAll) {
+				return removeDirectory(path);
+			}
+			FTPFile[] ftpFileArr = ftpClient.listFiles(path);
+			if (ftpFileArr == null || ftpFileArr.length == 0) {
+				return removeDirectory(path);
+			}
+			//
+			for (FTPFile ftpFile : ftpFileArr) {
+				String name = ftpFile.getName();
+				if (ftpFile.isDirectory()) {
+					log.info("* [sD]Delete subPath [" + path + "/" + name + "]");
+					if (!ftpFile.getName().equals(".") && (!ftpFile.getName().equals(".."))) {
+						removeDirectory(path + "/" + name, true);
+					}
+				} else if (ftpFile.isFile()) {
+					log.info("* [sF]Delete file [" + path + "/" + name + "]");
+					deleteFile(path + "/" + name);
+				} else if (ftpFile.isSymbolicLink()) {
+				} else if (ftpFile.isUnknown()) {
+				}
+			}
+			result = ftpClient.removeDirectory(path);
+		} catch (Exception e) {
+			log.error("removeDirectory error ", e);
+		} finally {
+			ftpPoolService.returnObject(ftpClient);
+		}
+		return result;
+	}
+
+	/**
+	 * 查看目录是否存在
+	 *
+	 * @param path
+	 * @return
+	 * @throws IOException
+	 */
+
+	public boolean isDirectoryExists(String path) {
+		FTPClient ftpClient = ftpPoolService.borrowObject();
+		boolean flag = false;
+		try {
+			FTPFile[] ftpFileArr = ftpClient.listFiles(path);
+			for (FTPFile ftpFile : ftpFileArr) {
+				if (ftpFile.isDirectory() && ftpFile.getName().equalsIgnoreCase(path)) {
+					flag = true;
+					break;
+				}
+			}
+		} catch (Exception e) {
+			log.error("isDirectoryExists error ", e);
+		} finally {
+			ftpPoolService.returnObject(ftpClient);
+		}
+		return flag;
+	}
+
+	/**
+	 * 得到某个目录下的文件名列表
+	 *
+	 * @param path
+	 * @return
+	 * @throws IOException
+	 */
+
+	public List<String> getFileList(String path) {
+		FTPClient ftpClient = ftpPoolService.borrowObject();
+		List<String> retList = new ArrayList<String>();
+		try {
+			FTPFile[] ftpFiles = ftpClient.listFiles(path);
+			if (ftpFiles == null || ftpFiles.length == 0) {
+				return retList;
+			}
+			for (FTPFile ftpFile : ftpFiles) {
+				if (ftpFile.isFile()) {
+					retList.add(ftpFile.getName());
+				}
+			}
+		} catch (Exception e) {
+			log.error("getFileList error ", e);
+		} finally {
+			ftpPoolService.returnObject(ftpClient);
+		}
+		return retList;
+	}
+
+	/**
+	 * 删除文件
+	 *
+	 * @param pathName
+	 * @return
+	 * @throws IOException
+	 */
+	public boolean deleteFile(String pathName) {
+		FTPClient ftpClient = ftpPoolService.borrowObject();
+		boolean result = false;
+		try {
+			result = ftpClient.deleteFile(pathName);
+		} catch (Exception e) {
+			log.error("deleteFile error ", e);
+		} finally {
+			ftpPoolService.returnObject(ftpClient);
+		}
+		return result;
+	}
+
+	/**
+	 * 上传文件到FTP服务器,支持断点续传
+	 *
+	 * @param local  本地文件名称,绝对路径
+	 * @param remote 远程文件路径,按照Linux上的路径指定方式,支持多级目录嵌套,支持递归创建不存在的目录结构
+	 * @return 上传结果
+	 * @throws IOException
+	 */
+
+	public UploadStatus upload(String local, String remote) {
+		FTPClient ftpClient = ftpPoolService.borrowObject();
+		UploadStatus result = UploadStatus.UploadNewFileFailed;
+		try {
+			ftpClient.changeWorkingDirectory(ftpPoolService.getFtpPoolConfig().getWorkingDirectory());
+			// 设置PassiveMode传输
+			ftpClient.enterLocalPassiveMode();
+			// 设置以二进制流的方式传输
+			ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
+			ftpClient.setControlEncoding(CharsetUtil.UTF_8);
+			// 对远程目录的处理
+			String remoteFileName = remote;
+			if (remote.contains("/")) {
+				remoteFileName = remote.substring(remote.lastIndexOf("/") + 1);
+				// 创建服务器远程目录结构,创建失败直接返回
+				if (createDirecroty(remote, ftpClient) == UploadStatus.CreateDirectoryFail) {
+					return UploadStatus.CreateDirectoryFail;
+				}
+			}
+			// 检查远程是否存在文件
+			FTPFile[] files = ftpClient.listFiles(new String(remoteFileName.getBytes(CharsetUtil.UTF_8), CharsetUtil.ISO_8859_1));
+			if (files.length == 1) {
+				long remoteSize = files[0].getSize();
+				File f = new File(local);
+				long localSize = f.length();
+				if (remoteSize == localSize) { // 文件存在
+					return UploadStatus.FileExits;
+				} else if (remoteSize > localSize) {
+					return UploadStatus.RemoteFileBiggerThanLocalFile;
+				}
+				// 尝试移动文件内读取指针,实现断点续传
+				result = uploadFile(remoteFileName, f, ftpClient, remoteSize);
+				// 如果断点续传没有成功,则删除服务器上文件,重新上传
+				if (result == UploadStatus.UploadFromBreakFailed) {
+					if (!ftpClient.deleteFile(remoteFileName)) {
+						return UploadStatus.DeleteRemoteFaild;
+					}
+					result = uploadFile(remoteFileName, f, ftpClient, 0);
+				}
+			} else {
+				result = uploadFile(remoteFileName, new File(local), ftpClient, 0);
+			}
+		} catch (Exception e) {
+			log.error("upload error ", e);
+		} finally {
+			ftpPoolService.returnObject(ftpClient);
+		}
+
+		return result;
+	}
+
+	/**
+	 * 递归创建远程服务器目录
+	 *
+	 * @param remote    远程服务器文件绝对路径
+	 * @param ftpClient FTPClient对象
+	 * @return 目录创建是否成功
+	 * @throws IOException
+	 */
+
+	public UploadStatus createDirecroty(String remote, FTPClient ftpClient) throws IOException {
+		UploadStatus status = UploadStatus.CreateDirectorySuccess;
+		String directory = remote.substring(0, remote.lastIndexOf("/") + 1);
+		if (!directory.equalsIgnoreCase("/") && !ftpClient.changeWorkingDirectory(new String(directory.getBytes(CharsetUtil.UTF_8), CharsetUtil.ISO_8859_1))) {
+			// 如果远程目录不存在,则递归创建远程服务器目录
+			int start = 0;
+			int end = 0;
+			if (directory.startsWith("/")) {
+				start = 1;
+			} else {
+				start = 0;
+			}
+			end = directory.indexOf("/", start);
+			while (true) {
+				String subDirectory = new String(remote.substring(start, end).getBytes(CharsetUtil.UTF_8), CharsetUtil.ISO_8859_1);
+				if (!ftpClient.changeWorkingDirectory(subDirectory)) {
+					if (ftpClient.makeDirectory(subDirectory)) {
+						ftpClient.changeWorkingDirectory(subDirectory);
+					} else {
+						log.info("创建目录失败");
+						return UploadStatus.CreateDirectoryFail;
+					}
+				}
+				start = end + 1;
+				end = directory.indexOf("/", start);
+				// 检查所有目录是否创建完毕
+				if (end <= start) {
+					break;
+				}
+			}
+		}
+		return status;
+	}
+
+	/**
+	 * 上传文件到服务器,新上传和断点续传
+	 *
+	 * @param remoteFile 远程文件名,在上传之前已经将服务器工作目录做了改变,一定要注意这里的 remoteFile 已经别被编码 ISO-8859-1
+	 * @param localFile  本地文件File句柄,绝对路径
+	 * @param ftpClient  FTPClient引用
+	 * @return
+	 * @throws IOException
+	 */
+
+	public UploadStatus uploadFile(String remoteFile, File localFile, FTPClient ftpClient, long remoteSize) {
+		if (null == ftpClient) {
+			ftpClient = ftpPoolService.borrowObject();
+			;
+		}
+		if (null == ftpClient) {
+			return null;
+		}
+		UploadStatus status = UploadStatus.UploadNewFileFailed;
+
+		try (RandomAccessFile raf = new RandomAccessFile(localFile, "r");
+			 // 一定要注意这里的 remoteFile 已经别被编码 ISO-8859-1
+			 OutputStream out = ftpClient.appendFileStream(remoteFile);) {
+			// 显示进度的上传
+			log.info("localFile.length():" + localFile.length());
+			long step = localFile.length() / 100;
+			step = step == 0 ? 1 : step;// 文件过小,step可能为0
+			long process = 0;
+			long localreadbytes = 0L;
+
+			// 断点续传
+			if (remoteSize > 0) {
+				ftpClient.setRestartOffset(remoteSize);
+				process = remoteSize / step;
+				raf.seek(remoteSize);
+				localreadbytes = remoteSize;
+			}
+			byte[] bytes = new byte[1024];
+			int c;
+			while ((c = raf.read(bytes)) != -1) {
+				out.write(bytes, 0, c);
+				localreadbytes += c;
+				if (localreadbytes / step != process) {
+					process = localreadbytes / step;
+					if (process % 10 == 0) {
+						log.info("上传进度:" + process);
+					}
+				}
+			}
+			out.flush();
+			raf.close();
+			out.close();
+			// FTPUtil的upload方法在执行ftpClient.completePendingCommand()之前应该先关闭OutputStream,否则主线程会在这里卡死执行不下去。
+			// 原因是completePendingCommand()会一直在等FTP Server返回226 Transfer complete,但是FTP Server只有在接受到OutputStream执行close方法时,才会返回。
+			boolean result = ftpClient.completePendingCommand();
+			if (remoteSize > 0) {
+				status = result ? UploadStatus.UploadFromBreakSuccess : UploadStatus.UploadFromBreakFailed;
+			} else {
+				status = result ? UploadStatus.UploadNewFileSuccess : UploadStatus.UploadNewFileFailed;
+			}
+		} catch (Exception e) {
+			log.error("uploadFile error ", e);
+		}
+		return status;
+	}
+
+	/**
+	 * 下载资源文件
+	 *
+	 * @param sourceFileName
+	 * @return
+	 * @throws IOException
+	 */
+	public InputStream downFile(String sourceFileName) {
+		FTPClient ftpClient = ftpPoolService.borrowObject();
+		InputStream result = null;
+		try {
+			result = ftpClient.retrieveFileStream(sourceFileName);
+		} catch (Exception e) {
+			log.error("deleteFile error ", e);
+		} finally {
+			ftpPoolService.returnObject(ftpClient);
+		}
+		return result;
+	}
+
+	/**
+	 * 查看用户目录
+	 *
+	 * @param path
+	 * @throws IOException
+	 */
+	public void listFilesDir(String path) {
+		FTPClient ftpClient = ftpPoolService.borrowObject();
+		try {
+			String ftpPath = path;
+			ftpClient.changeWorkingDirectory(ftpPath);
+			FTPFile[] files = ftpClient.listFiles();
+			for (FTPFile ff : files) {
+				if (!ff.isDirectory()) {
+				} else {
+					if (!ff.getName().startsWith(".")) {
+						ftpPath = ff.getName() + "/";
+						ftpClient.changeWorkingDirectory(ftpPath);
+						listFilesDir(ftpPath);
+						ftpClient.changeWorkingDirectory(path);
+					}
+				}
+			}
+		} catch (Exception e) {
+			log.error("deleteFile error ", e);
+		} finally {
+			ftpPoolService.returnObject(ftpClient);
+		}
+	}
+
+
+	/**
+	 * 上传状态枚举
+	 */
+	public enum UploadStatus {
+		CreateDirectoryFail, // 远程服务器相应目录创建失败
+		CreateDirectorySuccess, // 远程服务器闯将目录成功
+		UploadNewFileSuccess, // 上传新文件成功
+		UploadNewFileFailed, // 上传新文件失败
+		FileExits, // 文件已经存在
+		RemoteFileBiggerThanLocalFile, // 远程文件大于本地文件
+		UploadFromBreakSuccess, // 断点续传成功
+		UploadFromBreakFailed, // 断点续传失败
+		DeleteRemoteFaild; // 删除远程文件失败
+	}
+
+	/**
+	 * 下载状态枚举
+	 */
+	public enum DownloadStatus {
+		RemoteFileNotExist, // 远程文件不存在
+		DownloadNewSuccess, // 下载文件成功
+		DownloadNewFailed, // 下载文件失败
+		LocalFileBiggerThanRemoteFile, // 本地文件大于远程文件
+		DownloadFromBreakSuccess, // 断点续传成功
+		DownloadFromBreakFailed; // 断点续传失败
+	}
+}

+ 56 - 0
blade-service/blade-los/src/main/resources/application-dev.yml

@@ -11,3 +11,59 @@ spring:
 #  mapper-locations: classpath:com/yh/project/mapper/*Mapper.xml
 #  type-aliases-package: com.yh.project.*.entity
 #swagger文档
+ftp:
+  client:
+    workingDirectory: /
+    host: sftp.north.inttra.cn
+    port: 22
+    username: j1229351
+    password: Yg5YwPCe
+    privateKey:
+    encoding: utf-8
+    passiveMode: true
+    clientTimeout: 30000
+    threaNum: 1
+    transferFileType: 2
+    retryTimes: 1200
+    renameUploaded: true
+    bufferSize: 8192
+    maxTotal: 50
+    minldle: 10
+    maxldle: 50
+    maxWait: 30000
+    blockWhenExhausted: true
+    testOnBorrow: true
+    testOnReturn: true
+    testOnCreate: true
+    testWhileldle: false
+    lifo: false
+
+sftp:
+  client:
+    workingDirectory: /
+    host: sftp.north.inttra.cn
+    port: 22
+    username: j1229351
+    password: Yg5YwPCe
+    privateKey:
+    encoding: utf-8
+    clientTimeout: 30000
+    retryTimes: 1200
+    bufferSize: 8192
+    maxTotal: 50
+    minldle: 10
+    maxldle: 50
+    maxWait: 30000
+    blockWhenExhausted: true
+    testOnBorrow: true
+    testOnReturn: true
+    testOnCreate: true
+    testWhileldle: false
+    lifo: false
+
+email:
+  host: smtp.qq.com
+  from: 2377121191@qq.com
+  pass: qwertyuiop000
+  port: 25
+