1. 添加ZXing依赖到Spring Boot项目场景描述: 在需要快速、高效地生成二维码或条形码的应用中，如电子票务、产品追踪、个人身份验证等，集成ZXing库至Spring Boot项目是至关重要的一步。在项目的pom.xml文件中添加以下依赖以引入ZXing库：xmlCopy code<dependencies> <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.5.2</version> </dependency> </dependencies>使用最新版本的ZXing库可以确保你的应用具备最新的二维码和条形码生成能力，包括更好的性能和支持更多格式。2. 创建二维码生成服务场景描述: 此服务类适用于需要将URL、文本信息或其他数据快速转换为二维码图像的场合，如商业广告、社交媒体共享、安全验证等。代码示例与注释:import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; import com.google.zxing.common.BitMatrix; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.util.HashMap; import java.util.Map; /** * 生成二维码图像类 * * @author songzixian */ public class QRCodeService { /** * 生成二维码图像 * * @param text 要编码的文本 * @param width 二维码图像宽度 * @param height 二维码图像高度 * @param filePath 文件存储路径 * @return 生成的二维码图像的字节数组 */ public void generateQRCode(String text, int width, int height, String filePath) { try { // 定义二维码参数 Map<EncodeHintType, Object> hints = new HashMap<>(); // 设置字符编码 hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // 错误纠正级别 hints.put(EncodeHintType.ERROR_CORRECTION, com.google.zxing.qrcode.decoder.ErrorCorrectionLevel.H); // 二维码边距 hints.put(EncodeHintType.MARGIN, 1); // 使用QRCodeWriter生成二维码矩阵信息 MultiFormatWriter writer = new MultiFormatWriter(); BitMatrix bitMatrix = writer.encode(text, BarcodeFormat.QR_CODE, width, height, hints); // 将二维码矩阵信息转换为图像 BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { image.setRGB(x, y, bitMatrix.get(x, y) ? Color.BLACK.getRGB() : Color.WHITE.getRGB()); } } // 江二维码保存成文件 File qrCodeFile = new File(filePath); ImageIO.write(image, "png", qrCodeFile); System.out.println("二维码生成成功: " + filePath); } catch (Exception e) { e.printStackTrace(); } } /** * 测试 */ public static void main(String[] args) { QRCodeService qrCodeGenerator = new QRCodeService(); // 要存储在二维码中的文本数据 String text = "https://songzixian.com"; // 二维码的宽度 int width = 300; // 二维码的高度 int height = 300; // 二维码存储路径 String filePath = "D:\\data\\qrcode.png"; qrCodeGenerator.generateQRCode(text, width, height, filePath); } }此服务类提供了一个generateQRCode方法，该方法可以将任意文本（如URL或文本信息）转换为指定大小的二维码图像，并保存至指定路径。3. 创建条形码生成服务场景描述: 适用于零售、库存管理、物流追踪等需要快速生成条形码的业务场景。代码示例与注释:import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; import com.google.zxing.common.BitMatrix; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * 生成条形图像类 * * @author songzixian */ public class barCodeService { /** * 生成条形码图像 * * @param text 要编码的数据 * @param width 条形码图像宽度 * @param height 条形码图像高度 * @param filePath 文件存储路径 * @return 生成的条形码图像的字节数组 * @throws IOException 当图像写入过程中出现问题时抛出 */ public void generateBarcode(String text, int width, int height, String filePath) { try { // 定义二维码参数 Map<EncodeHintType, Object> hints = new HashMap<>(); // 设置字符编码 hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // 使用QRCodeWriter生成条形码码矩阵信息 MultiFormatWriter writer = new MultiFormatWriter(); BitMatrix bitMatrix = writer.encode(text, BarcodeFormat.CODE_128, width, height, hints); // 创建BufferedImage对象来表示条形码 BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { // 生成黑色条和白色背景的条形码 image.setRGB(x, y, bitMatrix.get(x, y) ? 0 : 0xFFFFFF); } } // 将条形码保存到文件 File barcodeFile = new File(filePath); ImageIO.write(image, "png", barcodeFile); System.out.println("条形码已生成并保存到: " + filePath); } catch (Exception e) { e.printStackTrace(); } } /** * 测试 */ public static void main(String[] args) { barCodeService barcodeGenerator = new barCodeService(); // 要存储在条形码中的数据 String text = "1234567890"; // 条形码的宽度 int width = 200; // 条形码的高度 int height = 30; String filePath = "D:\\data\\barCode.png"; barcodeGenerator.generateBarcode(text, width, height, filePath); } }此服务类通过generateBarcode方法生成条形码，支持将数字或文本信息转换为条形码，非常适合产品编码和库存管理系统。把上面的代码复制到你的Spring Boot的Service代码中,就成功地在Spring Boot应用中整合了ZXing库，可以生成二维码和条形码了。
文详细解析在Spring Boot应用中优雅地脱敏敏感信息，如手机号、邮箱、身份证等，使用的是Jackson库中的JsonSerializer。1. 创建自定义脱敏序列化器首先，创建一个继承自JsonSerializer<String>的序列化器。以MobileSerializer为例，代码如下：import cn.hutool.core.util.DesensitizedUtil; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; /** * * 自定义手机序列号器 * * * @author snogzixian * @since 2023-09-24 */ public class MobileSerializer extends JsonSerializer<String> { /** * 自定义序列化方法，用于对手机号进行脱敏处理并写入JSON生成器。 * * @param mobile 要序列化的手机号 * @param jsonGenerator JSON生成器，用于写入序列化后的数据 * @param serializerProvider 序列化提供者，提供序列化所需的服务 * @throws IOException 如果在序列化过程中发生I/O错误 */ @Override public void serialize(String mobile, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { // 检查手机号是否为null或者是否是有效的手机号 if (mobile == null || !isValidMobile(mobile)) { // 如果手机号无效，直接输出原始手机号 jsonGenerator.writeString(mobile); return; } // 调用脱敏工具类对手机号进行脱敏处理 String mobileDesensitized = DesensitizedUtil.mobilePhone(mobile); // 将脱敏后的手机号字符串写入到JSON生成器jsonGenerator中 jsonGenerator.writeString(mobileDesensitized); } /** * 检查手机号的合法性 * * @param mobile 手机号 * @return 如果手机号合法则返回true，否则返回false */ private boolean isValidMobile(String mobile) { // 在此处添加对手机号合法性的检查逻辑 return true; } }其中，serialize方法用于处理数据的脱敏逻辑，isValidMobile用于检查数据的合法性。2. 使用自定义序列化器在实体类中，使用@JsonSerialize注解指定字段使用自定义序列化器。例如，对于一个用户的手机号，我们在实体类中如下使用：/** * * 实体类 * * * @author snogzixian * @since 2023-09-24 */ public class User { @JsonSerialize(using = MobileSerializer.class) private String mobile; // 其他字段和getter、setter }3. 扩展性 - 支持多类型数据脱敏为支持多类型数据（如邮箱、身份证等）脱敏，你可以为每种数据类型创建对应的序列化器类，类似于MobileSerializer。例如，创建一个EmailSerializer处理邮箱脱敏：public class EmailSerializer extends JsonSerializer<String> { // 实现邮箱的脱敏逻辑 }在实体类中，对应字段使用@JsonSerialize注解指定使用EmailSerializer。public class User { @JsonSerialize(using = EmailSerializer.class) private String email; // 其他字段和getter、setter }
请帮我完善这篇文档,并且里面的代码添加注释实现注解切换多数据源需要以下步骤：1.首先添加了AOP、MyBatis、Druid和MySQL的依赖。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency>2.配置两个数据源，一个是master，一个是slave，同时设置了Druid连接池的参数，以及MyBatis的相关配置。spring: datasource: druid: defaultAutoCommit: true initialSize: 5 maxActive: 20 maxWait: 60000 minIdle: 5 testOnBorrow: false testOnReturn: false testWhileIdle: true timeBetweenEvictionRunsMillis: 60000 validationQuery: SELECT 1 FROM DUAL filters: stat,wall,log4j2 connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 master: url: jdbc:mysql://localhost:3306/master?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: root driver-class-name: com.mysql.jdbc.Driver slave: url: jdbc:mysql://localhost:3306/slave?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: root driver-class-name: com.mysql.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.demo.entity configuration: map-underscore-to-camel-case: true3.创建一个枚举类，用于区分不同的数据源。/** * * 枚举类，用于区分不同的数据源 * * * @author snogzixian * @Url 宋子宪博客 www.songzixian.com * @since 2023-03-15 */ public enum DataSourceType { /** * 主数据源 */ MASTER("master"), /** * 从数据源 */ SLAVE("slave"); private final String value; DataSourceType(String value) { this.value = value; } public String getValue() { return value; } }4.创建一个注解类，用于标识需要使用哪个数据源。import com.songzixian.enums.DataSourceType; import java.lang.annotation.*; /** * * 注解类，用于标识需要使用哪个数据源 * * * @author snogzixian * @Url 宋子宪博客 www.songzixian.com * @since 2023-03-15 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { DataSourceType value() default DataSourceType.MASTER; }5.创建一个AOP切面类，用于在方法执行前将数据源切换到指定的数据源上，方法执行后再切回原来的数据源。import com.songzixian.annotation.DataSource; import com.songzixian.enums.DataSourceType; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; /** * * 数据源切面类，用于在方法执行前将数据源切换到指定的数据源上，方法执行后再切回原来的数据源 * * * @author snogzixian * @Url 宋子宪博客 www.songzixian.com * @since 2023-03-15 */ @Aspect @Component public class DataSourceAspect { @Around("@annotation(com.songzixian.annotation.DataSource) || @within(com.songzixian.annotation.DataSource)") public Object switchDataSource(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); DataSource dataSource = signature.getMethod().getAnnotation(DataSource.class); if (dataSource == null) { Class<?> declaringType = signature.getDeclaringType(); dataSource = declaringType.getAnnotation(DataSource.class); } if (dataSource == null) { DynamicDataSource.setDataSource(DataSourceType.MASTER.getValue()); } else { DynamicDataSource.setDataSource(dataSource.value().getValue()); } try { return point.proceed(); } finally { DynamicDataSource.clearDataSource(); } } }6.创建一个配置类，用于配置多数据源和动态数据源。配置多数据源后，将其放入动态数据源中，通过设置默认数据源和目标数据源来实现动态切换数据源的功能。import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * * 数据源配置类，用于配置多数据源和动态数据源 * * * @author snogzixian * @Url 宋子宪博客 www.songzixian.com * @since 2023-03-15 */ @Configuration public class DataSourceConfig { /** * 配置多数据源 */ @Bean @ConfigurationProperties(prefix = "spring.datasource.druid.master") public DataSource masterDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.druid.slave") public DataSource slave1DataSource() { return DruidDataSourceBuilder.create().build(); } /** * 配置动态数据源 */ @Bean public DynamicDataSource dynamicDataSource() { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("master", masterDataSource()); targetDataSources.put("slave", slave1DataSource()); DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setTargetDataSources(targetDataSources); dataSource.setDefaultTargetDataSource(masterDataSource()); return dataSource; } }7.创建一个动态数据源类，继承了AbstractRoutingDataSource类，并实现了determineCurrentLookupKey()方法，用于获取当前的数据源。同时，这里还提供了setDataSource()和clearDataSource()方法，用于设置和清除当前数据源。import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * * 动态数据源类，继承了AbstractRoutingDataSource类，并实现了determineCurrentLookupKey()方法，用于获取当前的数据源 * * * @author snogzixian * @Url 宋子宪博客 www.songzixian.com * @since 2023-03-15 */ public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> dataSourceHolder = new InheritableThreadLocal<>(); /** * 设置数据源 */ public static void setDataSource(String dataSource) { dataSourceHolder.set(dataSource); } /** * 清除数据源 */ public static void clearDataSource() { dataSourceHolder.remove(); } /** * 获取当前数据源 */ @Override protected Object determineCurrentLookupKey() { return dataSourceHolder.get(); } }8.创建一个MyBatis配置类，用于配置SqlSessionFactory和SqlSessionTemplate。配置SqlSessionFactory时，需要注入DynamicDataSource类，同时设置mapper文件的位置。配置SqlSessionTemplate时，需要注入SqlSessionFactory。import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; /** * * MyBatis配置类，用于配置SqlSessionFactory和SqlSessionTemplate * * * @author snogzixian * @Url 宋子宪博客 www.songzixian.com * @since 2023-03-15 */ @Configuration public class MyBatisConfig { @Autowired private DataSourceConfig dataSourceConfig; /** * 配置MyBatis */ @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSourceConfig.dynamicDataSource()); ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); factoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml")); return factoryBean.getObject(); } @Bean public SqlSessionTemplate sqlSessionTemplate() throws Exception { return new SqlSessionTemplate(sqlSessionFactory()); } }9.最后直接在显示类中使用注解方式即可自由切换不同数据源注解使用示例:@DataSource(枚举值),不填写注解则表示使用MASTER库@Service public class BlogUserServiceImpl implements BlogUserService{ @Resource private BlogUserMapper blogUserMapper; /** * 数据源一用法 不用注解默认MASTER,可以省略 * @return */ //@DataSource(DataSourceType.MASTER) @Override public List<BlogUser> getAll(){ return blogUserMapper.selectAll(); } /** * 数据源二用法 * @return */ @DataSource(DataSourceType.SLAVE) @Override public List<BlogUser> getUserSlave(){ return blogUserMapper.selectAll(); } }10.启动Spring Boot项目.然后进行调用2个不同数据源的接口就可以测试了,本篇文字由博主编写和测试,完全能用
Java整合Jsch实现SFTP连接对服务器文件增删改查操作
我们在开发中，有时候需要操作服务器上的文件，Spring Boot可以使用JSch库来连接SFTP并进行操作服务器上的文件读写。创建一个SFTP连接需要以下步骤：1.创建JSch对象2.使用JSch对象创建Session对象3.使用Session对象连接到SFTP服务器4.打开SFTP通道5.使用SFTP通道打开SFTP连接1.添加依赖在pom.xml文件中添加以下依赖：<dependency> <groupId>com.jcraft</groupId> <artifactId>jsch</artifactId> <version>0.1.55</version> </dependency>2.创建SftpConfig配置类import lombok.Getter; import lombok.Setter; /** * * SFTP连接参数配置类 * description: 如果后续只需要操作一台固定不变的服务器,可以从配置文件中读取 * * * @author snogzixian * @Url 宋子宪博客 www.songzixian.com * @since 2023-03-13 */ @Setter @Getter public class SftpConfig { /** * SFTP服务器地址 */ private String host; /** * SFTP服务器端口 */ private int port; /** * SFTP服务器用户名 */ private String username; /** * SFTP服务器密码 如果不使用密码方式则留空 */ private String password; /** * 密钥文件路径，如果不使用密钥连接则留空 */ private String privateKeyPath; /** * 密钥文件口令，如果不使用密钥连接则留空 */ private String passphrase; /** * 连接SFTP服务器超时时间 2秒 */ private Integer sessionConnectTimeoutTime = 2000; /** * 打开通道超时时间 2秒 */ private Integer channelSftpConnectTimeoutTime = 2000; /** * 密码方式连接方法 * * @param host SFTP服务器地址 * @param port SFTP服务器端口 * @param username SFTP服务器用户名 * @param password SFTP服务器密码 如果不使用连接则留空 */ public SftpConfig(String host, int port, String username, String password) { this.host = host; this.port = port; this.username = username; this.password = password; } /** * 秘钥方式连接方法 * * @param host SFTP服务器地址 * @param port SFTP服务器端口 * @param username SFTP服务器用户名 * @param privateKeyPath 密钥文件路径，如果不使用密钥连接则留空 * @param passphrase 密钥文件口令，如果不使用密钥连接则留空 */ public SftpConfig(String host, int port, String username,String privateKeyPath, String passphrase) { this.host = host; this.port = port; this.username = username; this.privateKeyPath = privateKeyPath; this.passphrase = passphrase; } }3.创建SFTP操作工具类import com.jcraft.jsch.*; import com.risk.riskControl.config.SftpConfig; import java.io.*; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; /** * * SFTP操作工具类 * * * @author snogzixian * @Url 宋子宪博客 www.songzixian.com * @since 2023-03-13 */ public class SftpService { /** * SFTP连接参数 */ private SftpConfig config; /** * JSch对象，用于创建SFTP连接 */ private JSch jsch; /** * Session对象，用于连接到SFTP服务器 */ private Session session; /** * SFTP通道，用于打开SFTP连接 */ private ChannelSftp channelSftp; /** * 构造函数，初始化SFTP连接参数 * * @param config SFTP连接参数 */ public SftpService(SftpConfig config) { this.config = config; // 创建JSch对象 this.jsch = new JSch(); } /** * 连接到SFTP服务器 * * @throws JSchException */ public void connect() throws JSchException { try { // 创建Session对象 session = jsch.getSession(config.getUsername(), config.getHost(), config.getPort()); if (config.getPassword() != null) { // 使用密码连接 session.setPassword(config.getPassword()); } else if (config.getPrivateKeyPath() != null) { // 使用私钥连接 jsch.addIdentity(config.getPrivateKeyPath(), config.getPassphrase()); } // 不进行主机密钥检查 session.setConfig("StrictHostKeyChecking", "no"); // 设置SFTP连接超时时间 session.connect(config.getSessionConnectTimeoutTime()); // 打开SFTP通道 channelSftp = (ChannelSftp) session.openChannel("sftp"); // 设置通道连接超时时间 channelSftp.connect(config.getChannelSftpConnectTimeoutTime()); } catch (JSchException e) { // 连接异常处理 throw new JSchException("Failed to connect to SFTP server: " + e.getMessage()); } } /** * 断开SFTP连接 */ public void disconnect() { if (channelSftp != null) { // 断开SFTP连接 channelSftp.disconnect(); } if (session != null) { // 断开Session连接 session.disconnect(); } } /** * 上传文件到SFTP服务器 * * @param localFilePath 本地文件路径 * @param remoteFilePath SFTP服务器文件路径 * @throws SftpException * @throws FileNotFoundException */ public void uploadFile(String localFilePath, String remoteFilePath) throws SftpException, IOException { // 使用try-with-resources语句,可以自动关闭资源 try (InputStream inputStream = new FileInputStream(localFilePath)) { // 上传文件到SFTP服务器 channelSftp.put(inputStream, remoteFilePath); } } /** * 从SFTP服务器下载文件 * * @param remoteFilePath SFTP服务器文件路径 * @param localFilePath 本地文件路径 * @throws SftpException * @throws FileNotFoundException */ public void downloadFile(String remoteFilePath, String localFilePath) throws SftpException, IOException { // 使用try-with-resources语句,可以自动关闭资源 try (OutputStream outputStream = new FileOutputStream(localFilePath)) { // 从SFTP服务器下载文件 channelSftp.get(remoteFilePath, outputStream); } } /** * 删除SFTP服务器上的文件 * * @param remoteFilePath SFTP服务器文件路径 * @throws SftpException */ public void deleteFile(String remoteFilePath) throws SftpException { // 删除SFTP服务器上的文件 channelSftp.rm(remoteFilePath); } /** * 列出SFTP服务器上的文件 * 使用这段代码会有什么问题,是否还可以优化 * * @param remoteDirectory SFTP服务器目录 * @return 文件列表 * @throws SftpException */ public List<String> listFiles(String remoteDirectory) throws SftpException { List<String> fileList; // 列出SFTP服务器上的文件 try (Stream<ChannelSftp.LsEntry> stream = channelSftp.ls(remoteDirectory).stream()) { fileList = stream // 过滤掉目录 .filter(entry -> !entry.getAttrs().isDir()) // 获取文件名 .map(ChannelSftp.LsEntry::getFilename) // 将文件名收集到List中 .collect(Collectors.toList()); } return fileList; } }4.创建SftpTest测试类import com.risk.riskControl.config.SftpConfig; import java.util.List; /** * /** * * SftpTest测试类 * * * @author snogzixian * @Url 宋子宪博客 www.songzixian.com * @since 2023-03-13 */ public class SftpTest { /** * 测试SFTP连接及文件上传、下载、删除、列出操作 * * @param args * @throws Exception */ public static void main(String[] args) throws Exception { // 构建SFTP参数,如果是参数固定一个不变改为用配置文件读取 SftpConfig config = new SftpConfig("sftp.songzixian.com", 22, "szxBlog", "12345678"); // 秘钥方式连接 //SftpConfig config = new SftpConfig("sftp.songzixian.com", 22, "root", "D:\\id_rsa_szx_blog.ppk","blog123"); // 连接到SFTP服务器 SftpService sftpService = new SftpService(config); sftpService.connect(); // 本地路径 String localFilePath = ""; // 服务器路径 String remoteFilePath = ""; // 上传文件使用 // 本地文件路径 localFilePath = "D:\\code\\a.txt"; // SFTP服务器文件路径 remoteFilePath = "/usr/local/sftp-test/a.txt"; // 上传文件到SFTP服务器 sftpService.uploadFile(localFilePath, remoteFilePath); // 下载文件使用 // SFTP服务器文件路径 remoteFilePath = "/usr/local/sftp-test/a.txt"; // 本地文件路径 localFilePath = "D:\\code\\aaaa.txt"; // 从SFTP服务器下载文件 sftpService.downloadFile(remoteFilePath, localFilePath); // 删除文件使用 remoteFilePath = "/usr/local/sftp-test/a.txt"; // 删除SFTP服务器上的文件 sftpService.deleteFile(remoteFilePath); // 列出文件 // SFTP服务器目录 String remoteDirectory = "/usr/local/sftp-test/"; // 列出SFTP服务器上的文件 List<String> files = sftpService.listFiles(remoteDirectory); for (String file : files) { System.out.println(file); } System.out.println("操作成功"); // 断开SFTP连接 sftpService.disconnect(); } }
Spring Boot项目动态启动设置参数的好处有以下几点：灵活性：通过动态设置参数，可以根据不同的环境和需求来灵活地配置应用程序，适应不同的场景。可维护性：动态设置参数可以避免硬编码，使得应用程序更易于维护和升级。安全性：通过动态设置参数，可以避免敏感信息（如数据库密码、密钥等）被硬编码到代码中，提高应用程序的安全性。可扩展性：通过动态设置参数，可以方便地扩展应用程序的功能，增加新的配置项，而不需要修改应用程序的代码。自动化部署：通过动态设置参数，可以方便地实现自动化部署，使得部署过程更加简洁、高效。以下可以设置的参数大概有这些--server.port：指定服务端口号。 --spring.profiles.active：指定激活的Spring配置文件。 --logging.level.root：指定日志级别。 --management.server.port：指定管理端口号。 --spring.config.name：指定配置文件的名称。 --spring.config.location：指定配置文件的路径。 --spring.application.name：指定应用程序名称。 --spring.datasource.url：指定数据源URL。 --spring.datasource.username：指定数据源用户名。 --spring.datasource.password：指定数据源密码。 --spring.jpa.show-sql：指定是否显示SQL语句。 --spring.jpa.hibernate.ddl-auto：指定Hibernate的DDL模式。 --spring.main.banner-mode：指定启动Banner模式。 --spring.main.web-application-type：指定应用程序类型。 --spring.main.allow-bean-definition-overriding：指定是否允许Bean覆盖。 --spring.main.lazy-initialization：指定是否启用懒加载。 --spring.main.sources：指定应用程序的源。 --spring.main.web-environment：指定Web环境类型。 --spring.profiles.include：指定需要包含的Spring配置文件。 --spring.profiles.exclude：指定需要排除的Spring配置文件。使用示例:例如启动szxBlog.jar包指定服务端口号为8080：java -jar szxblog.jar --server.port=8080指定激活的Spring配置文件为dev：java -jar szxblog.jar --spring.profiles.active=dev指定日志级别为DEBUG：java -jar szxblog.jar --logging.level.root=DEBUG指定管理端口号为8081：java -jar szxblog.jar --management.server.port=8081指定配置文件的名称为application：java -jar szxblog.jar --spring.config.name=application指定配置文件的路径为/etc/szxblog/：java -jar szxblog.jar --spring.config.location=file:/etc/szxblog/指定应用程序名称为szxblog：java -jar szxblog.jar --spring.application.name=szxblog指定数据源URL为jdbc:mysql://localhost:3306/szxblog：java -jar szxblog.jar --spring.datasource.url=jdbc:mysql://localhost:3306/szxblog指定数据源用户名为myuser：java -jar szxblog.jar --spring.datasource.username=myuser指定数据源密码为mypassword：java -jar szxblog.jar --spring.datasource.password=mypassword注意：以上命令中的参数值需要根据实际情况进行修改。
首先获取消息的内容，即payload。将payload转换为JSONObject对象，并从中获取消息类型type。如果消息类型是"join"，则从消息中获取群组ID，并将当前WebSocketSession的ID加入到该群组对应的Set中。如果消息类型是"leave"，则从消息中获取群组ID，并从该群组对应的Set中移除当前WebSocketSession的ID。如果消息类型是"message"，则从消息中获取群组ID，并获取该群组对应的所有WebSocketSession，逐一向这些Session发送消息。其中，groups和sessions是两个Map，分别用来存储群组与其对应的WebSocketSessions。computeIfAbsent和computeIfPresent方法分别用来向Map中添加元素和更新元素。sendMessage方法用来向客户端发送消息。导入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>创建MyWebSocketHandler类import com.songzixain.service.MyWebSocketHandler; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * * Description: WebSocketConfig配置类 * * * @author songzixian * @version v2.0.0 * @create 2023-03-11 15:57 * @see com.songzixain.config */ @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new MyWebSocketHandler(), "/ws").setAllowedOrigins("*"); 创建MyWebSocketHandler类这段代码是一个WebSocket处理器中的消息处理方法，用来处理WebSocket客户端发送的消息/** * * Description: MyWebSocketHandler用 来处理WebSocket客户端发送的消息 * * * @author songzixian * @version v2.0.0 * @create 2023-03-11 22:44 * @see com.songzixain.service */ @Slf4j public class MyWebSocketHandler implements WebSocketHandler { private static final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>(); private static final Map<String, Set<String>> groups = new ConcurrentHashMap<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessions.put(session.getId(), session); } @Override public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { String payload = message.getPayload().toString(); log.info("接收到的用户消息 {}", payload); JSONObject json = JSON.parseObject(payload); String type = json.getString("type"); // 用户加入了群组 if ("join".equals(type)) { String groupId = json.getString("groupId"); groups.computeIfAbsent(groupId, k -> new HashSet<>()).add(session.getId()); } else if ("leave".equals(type)) { String groupId = json.getString("groupId"); groups.computeIfPresent(groupId, (k, v) -> { v.remove(session.getId()); return v; }); // 接收到了用户消息 } else if ("message".equals(type)) { String groupId = json.getString("groupId"); Set<String> groupSessions = groups.get(groupId); for (String sessionId : groupSessions) { WebSocketSession s = sessions.get(sessionId); if (s != null && s.isOpen()) { s.sendMessage(new TextMessage(payload)); } } } } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { sessions.remove(session.getId()); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { sessions.remove(session.getId()); groups.values().forEach(v -> v.remove(session.getId())); } @Override public boolean supportsPartialMessages() { return false; } }前端的html测试页面 WebSocket Chat Send Join Group Leave Group 这个WebSocket处理器实现了群聊功能，并支持用户加入和离开群组时向群组中的其他用户发送通知。启动Spring Boot项目.点击加入群主,然后就可以发送消息了
导包 <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-spring-boot-starter</artifactId> <version>2.0.3</version> </dependency> 添加配置信息application配置文件# rocketMq地址 rocketmq.name-server=106.52.60.215:9876 # 生产者分组 rocketmq.producer.group=myGroup rocketmq.producer.topics=topic1 # 消费者分组 rocketmq.consumer.group=myGroup # topic rocketmq.consumer.topics=topic1 # 表示顺序消费模式 rocketmq.consumer.consume-mode=ORDERLY # 消费者的最大线程数，即消费消息的线程池大小。默认值为20，如果不需要处理大量的消息，可以将其调小。 rocketmq.consumer.consume-thread-max=1 # 表示每次消费消息的最大数量，即一次性消费的最大消息数。默认值为1，即每次只消费一条消息。如果需要批量消费消息，可以将其调大。但是需要注意的是，批量消费消息可能会影响消费的效率和消息的顺序性。 rocketmq.consumer.consume-message-batch-max-size=1yml配置文件rocketmq: consumer: consume-message-batch-max-size: 1 consume-mode: ORDERLY consume-thread-max: 1 group: myGroup topics: topic1 name-server: 106.52.60.215:9876 producer: group: myGroup topics: topic1 生产者发送消息同步发现消息在Spring Boot中，可以使用RocketMQTemplate来发送消息。设置消息的延迟级别，可以使用RocketMQTemplate的send(Message message, long timeout, int delayLevel)方法，其中delayLevel为延迟级别，单位为秒RocketMQ支持18个级别的延迟时间，分别为1s、5s、10s、30s、1m、2m、3m、4m、5m、6m、7m、8m、9m、10m、20m、30m、1h、2himport com.songzixain.Blog; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; /** * * Description: 消息生产者 * * * @author songzixian * @version v2.0.0 * @create 2023-03-11 21:10 * @see com.songzixain.controller */ @Slf4j @RestController public class MyProducer1 { @Value("${rocketmq.producer.topics}") private String topic; @Autowired private RocketMQTemplate rocketMQTemplate; /** * description 同步发送延迟消息 * * @param: [] * @return * @Date 2023/3/11 */ @GetMapping("syncSendTest") public void sendDelayMsg() { Blog blog = new Blog(); blog.setBlogName("宋子宪博客"); blog.setUrl("www.songzixian.com"); // delayTimeLevel代表延迟级别 messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h int delayTimeLevel = 3; Message<Blog> message = MessageBuilder.withPayload(blog) .setHeader(MessageConst.PROPERTY_DELAY_TIME_LEVEL, delayTimeLevel) .build(); SendResult sendResult = rocketMQTemplate.syncSend(topic, message, 3000, delayTimeLevel); log.info("消息发送成功,时间:{} 发送内容:{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), blog.toString()); log.info("发送结果:{}", sendResult); } }异步发送消息(推荐)import com.songzixain.Blog; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; /** * * Description: 消息生产者 * * * @author songzixian * @version v2.0.0 * @create 2023-03-11 11:34 * @see com.songzixain.controller */ @Slf4j @RestController public class DemoProducers { @Autowired private RocketMQTemplate rocketMQTemplate; /** * description 延迟消息发送 * * @param: [user] * @return * @Date 2023/3/11 */ @RequestMapping("/asyncSendTest") public String asyncSendTest(){ Blog blog = new Blog(); blog.setBlogName("宋子宪博客"); blog.setUrl("www.songzixian.com"); // 构建消息体 Message<Blog> msg = MessageBuilder.withPayload(blog).build(); rocketMQTemplate.asyncSend("topic1", msg, new SendCallback() { // 发送成功 @Override public void onSuccess(SendResult sendResult) { log.info("消息发送成功,时间:{} 发送内容:{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), blog.toString()); } // 发送失败 @Override public void onException(Throwable throwable) { log.info("消息发送失败,时间:{} 发送内容:{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), blog.toString()); } // ps:3 代表第三个延迟10s 延迟级别:"1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h" },3000,3); return "发送成功"; } }消息消费者import com.songzixain.Blog; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; /** * * Description: 消息生产者 * * * @author songzixian * @version v2.0.0 * @create 2023-03-11 11:34 * @see com.songzixain.controller */ @Slf4j @RestController public class MyProducer2 { @Autowired private RocketMQTemplate rocketMQTemplate; @Value("${rocketmq.producer.topics}") private String topic; /** * description 延迟消息发送 * 在上面的代码中，我们使用了RocketMQTemplate的syncSend方法来发送消息。 * 其中，第一个参数是消息的主题，第二个参数是消息内容，第三个参数是延迟时间（单位为毫秒） * ，第四个参数是发送消息的重试次数。 * @param: [user] * @return * @Date 2023/3/11 */ @RequestMapping("/asyncSendTest") public String asyncSendTest(){ Blog blog = new Blog(); blog.setBlogName("宋子宪博客"); blog.setUrl("www.songzixian.com"); // 构建消息体 Message<Blog> msg = MessageBuilder.withPayload(blog).build(); rocketMQTemplate.asyncSend(topic, msg, new SendCallback() { // 发送成功 @Override public void onSuccess(SendResult sendResult) { log.info("消息发送成功,时间:{} 发送内容:{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), blog.toString()); } // 发送失败 @Override public void onException(Throwable throwable) { log.info("消息发送失败,时间:{} 发送内容:{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), blog.toString()); } // ps:3 代表第三个延迟10s 延迟级别:"messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h" },3000,4); return "发送成功"; } }启动测试启动请求:http://localhost:8081/asyncSendTest控制台打印可以看到,消息生产者设置的延迟级别是3,对应延迟了10秒钟延迟级别:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h需要注意的是，顺序消费模式下，同一个消费者组内的消费者只会有一个线程消费同一个队列中的消息，这样才能保证消息的顺序性。通过以上步骤，就可以使用RocketMQ实现消息延迟功能了。
spring boot项目使用maven插件将项目和jar包依赖以及静态资源分离打包
将spring boot的pom.xml文件中的build替换为以下,然后刷新maven,打包即可 <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <excludes> <include>*.*</include> </excludes> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <useUniqueVersions>false</useUniqueVersions> <mainClass>com.songzixian.SzxBlogApplication </mainClass> </manifest> <manifestEntries> <Class-Path>config/</Class-Path> </manifestEntries> </archive> <outputDirectory>${project.build.directory}</outputDirectory> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/lib</outputDirectory> </configuration> </execution> </executions> </plugin> <!-- 这个插件是用来复制项目的静态资源 windows命令 java -jar -Dloader.path=config,lib rocket-web-1.0-SNAPSHOT.jar Linux 命令 nohup java -jar -Dloader.path=config,lib rocket-web-1.0-SNAPSHOT.jar >> dispatche.log & --> <plugin> <artifactId>maven-resources-plugin</artifactId> <executions> <execution> <id>copy-resources</id> <phase>package</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>*.*</include> </includes> </resource> </resources> <outputDirectory>${project.build.directory}/config</outputDirectory> </configuration> </execution> </executions> </plugin> </plugins> </build> 使用maven命令,或者IDEA插件打包mvn clean package打包后target目录会生成包含以下三个文件,config(配置) ,lib(jar包依赖),*.jar是项目包,将这3个目录文件拷贝到服务器,即可启动,这样打包的好处就是讲项目jar包和依赖分开,一般小项目jar包几百kb左右前台启动java -jar -Dloader.path=config,lib szxblog-1.0-SNAPSHOT.jar后台启动nohup java -jar -Dloader.path=config,lib szxblog-1.0-SNAPSHOT.jar > szxblog.log 2>&1 &带JVM参数的后台启动nohup java -Xms2048m -Xmx4096m -jar -Dloader.path=config,lib szxblog-1.0-SNAPSHOT.jar > loanjob.log 2>&1 &
首先创建以下4个spring boot配置文件application.yml bootstrap.yml // 默认公共文件 dev和prod可以重写配置 bootstrap-dev.yml //开发环境文件 bootstrap-prod.yml //生成环境在启动类里新增加载配置文件代码 import cn.hutool.core.net.NetUtil; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Spirng Boot启动类 */ @SpringBootApplication public class BlogApplication { public static void main(String[] args) { // 自动加载是开发环境还是生产环境 String environment = "--spring.profiles.active="; if (!String.join("", args).contains(environment)) { List<String> argList = new ArrayList<String>(Arrays.asList(args)); // 生成环境内网ip String prodIntranetIp = ""; // ping生产服务器的内网IP,如果能ping通代表是生成环境 boolean ping = NetUtil.ping(prodIntranetIp); if (ping == true) { // 生成环境配置文件 environment = "--spring.profiles.active=prod"; } else { // 测试环境配置文件 environment = "--spring.profiles.active=dev"; } // 添加环境参数到集合中 argList.add(environment); // list转数组 args = argList.toArray(args); } SpringApplication.run(BlogApplication.class, args); } }
1.需要先搭建Spring Boot工程2.引入hutool工具类依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.4</version> </dependency>文档:Spring MVC的MultipartFile接口常用API方法功能描述String getOriginalFilename()获取上传文件的原始文件名，即该文件在客户端中的文件名boolean isEmpty()判断上传的文件是否为空，当没有选择文件就直接上传，或者选中的文件是0字节的空文件时，返回true，否则返回falselong getSize()获取上传的文件大小，以字节为单位String getContentType()根据所上传的文件的扩展名决定该文件的MIME类型，例如上传.jpg格式的图片，将返回image/jpegInputStream getInputStream()获取上传文件的输入字节流，通常用于自定义读取所上传的文件的过程，该方法与transferTo()方法不可以同时使用void transferTo(File dest)保存上传的文件，该方法与getInputStream()方法不可以同时使用核心代码/** * description 文件上传 * * @return {@link AjaxResult} * @param: [multipartFile] * @Date 2022/7/16 */ @PostMapping("fileUpload") public String fileUpload(MultipartFile multipartFile) throws IOException { String PATH = "D:\\file\\"; log.info("multipartFile:{}", multipartFile); //判断上传的文件是否为空 if (multipartFile ==null || multipartFile.isEmpty()){ return "请选选择文件再上传，参数名multipartFile"; } String contentType = multipartFile.getContentType(); log.info("文件类型:{}",contentType); //获取文件名 String fileName = multipartFile.getOriginalFilename(); //获取文件后缀名 没有点符号 String fileSuffix = FileUtil.extName(fileName); //限制文件类型 if (!(fileSuffix.equals("jpg") || fileSuffix.equals("png") || fileSuffix.equals("xls") || fileSuffix.equals("xlsx"))) { return "当前只支持上传:jpg png xls xlsx类型"; } //获取文件大小 long size = multipartFile.getSize(); //使用时间戳+3位随机数作为新的文件名 long milli = Instant.now().toEpochMilli(); String randomNumbers = RandomUtil.randomNumbers(3); //生成新的文件名 String newFileName = milli + randomNumbers + "." + fileSuffix; //创建文件夹,如果存在则不创建 FileUtil.mkdir(PATH); //文件路径 String filePath = PATH + newFileName; //生成文件唯一has256值 String shaValue = DigestUtil.sha256Hex(multipartFile.getBytes()); //相同的文件每次上传has256值都是一样的,可以根据该值判断文件是否存在 if (shaValue.equals("数据库中的has256值")) { return "改文件已存在"; } log.info("has256:{}", shaValue); try { //实现文件存储 FileUtil.writeBytes(multipartFile.getBytes(), filePath); return "文件上传成功"; } catch (IOException e) { log.error("文件上传失败 报错信息:{}", e); return "文件上传失败，错误信息:" + e; } }yml参数作用如下：spring.servlet.multipart.enabled：表示是否开启文件上传支持，默认为 truespring.servlet.multipart.file-size-threshold：表示文件写入磁盘的阀值，默认为 0spring.servlet.multipart.location：表示上传文件的临时保存位置spring.servlet.multipart.max-file-size：表示上传的单个文件的最大大小，默认为 1MBspring.servlet.multipart.max-request-size：表示多文件上传时文件的总大小，默认为 10MBspring.servlet.multipart.resolve-lazily：表示文件是否延迟解析，默认为 falsespring: servlet: multipart: max-file-size: 20MB max-request-size: 200MB文件上传到这里就结束了ps:如果后面需要物理删除文件可以调用以下Api//删除单个文件 FileUtil.del("D:\\file\\test.txt"); //删除整个文件夹(慎用) FileUtil.del(D:\\file\\)