慢慢成长 - 代码小计
type: channel
link: https://azuoa.com/index.php/category/%E4%BB%A3%E7%A0%81%E5%B0%8F%E8%AE%A1/
items: SpringBoot 实现全局统一请求日志处理
items: SpringBoot 常用功能和封装代码小计
items: Java 调用打印机打印PDF文件
items: Java 实现录音并写出到文件
items: SpringSession会话管理
items: SpringBoot 基于 SSE(Server-Send-Event)实现日志文件实时监控
items: HTML中使用 Monaco Editor 编辑器
items: SpringBoot读取多次RequestBody
items: SpringBoot 多数据源配置
items: SpringCloud Gateway提示端口占用

SpringBoot 实现全局统一请求日志处理
type: item
date: 2024-06-20T08:32:22+00:00
description: SpringBoot 实现全局统一请求日志处理相关工具类内容类型判断工具类import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 内容类型工具类 * @author TheEnd */ public class ContentTypeUtil { public static boolean isBinaryContent(HttpServletRequest request) { String contentType = request.getContentType(); return isBinaryContent(contentType); } public static boolean isBinaryContent(HttpServletResponse response) { String contentType = response.getContentType(); return isBinaryContent(contentType); } public static boolean isBinaryContent(String contentType) { if (contentType == null || contentType.isEmpty()) { return false; } return contentType.startsWith("image/") || contentType.startsWith("multipart/form-data") || contentType.startsWith("audio/") || contentType.startsWith("video/") || contentType.startsWith("application/octet-stream") || contentType.startsWith("application/vnd.ms-excel") || contentType.startsWith("application/vnd.ms-powerpoint") || contentType.startsWith("application/msword") || contentType.startsWith("application/vnd.openxmlformats-officedocument") || contentType.startsWith("application/zip") || contentType.startsWith("application/x-"); } }客户端IP获取工具类import javax.servlet.http.HttpServletRequest; import java.util.*; /** * 用户获取客户端IP的工具类 * @author TheEnd */ public class IpUtil { /** * 获取客户端IP * @param request 请求对象 * @return 客户端IP */ public static String getIp(HttpServletRequest request) { if (request == null) { return ""; } ArrayList<String> headerNames = Collections.list(request.getHeaderNames()); List<String> ipHeaderNames = Arrays.asList("X-Forwarded-For", "Proxy-Client-IP", "X-Real-IP"); for (String ipHeaderName : ipHeaderNames) { String ip = getHeaderValueIgnoreCase(ipHeaderName, headerNames, request); if (ip != null && !ip.isEmpty()) { return ip; } } return request.getRemoteAddr(); } private static String getHeaderValueIgnoreCase(String headerName, List<String> requestHeaderNames, HttpServletRequest request) { for (String requestHeaderName : re…

SpringBoot 常用功能和封装代码小计
type: item
date: 2024-06-19T09:01:00+00:00
description: SpringBoot 常用功能和封装代码小计一. Spring框架通过静态方式获取上下文对象(ApplicationContext)创建一个名为ApplicationContextProvider的类，实现ApplicationContextAware接口。 实现这个接口后，Spring会在ApplicationContext初始化时调用setApplicationContext方法，在这个方法中将ApplicationContext赋值给静态变量context。然后即可在任何地方通过ApplicationContextProvider类的静态方法getApplicationContext()来获取ApplicationContext对象import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class ApplicationContextProvider implements ApplicationContextAware { private static ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) { context = applicationContext; } public static ApplicationContext getApplicationContext() { return context; } }获取示例:ApplicationContext context = ApplicationContextProvider.getApplicationContext();通过静态方式获取请求对象(HttpServletRequest)SpringBoot 项目中, 可以在非Controller类中使用RequestContextHolder来获取当前请求的HttpServletRequest对象。但是请注意，这种方式不是推荐的做法，因为它会使代码与Spring框架耦合度较高。示例import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; public class MyService { public void doSomething() { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 使用request对象获取请求信息 String userAgent = request.getHeader("User-Agent"); System.out.println("User-Agent: " + userAgent); } }SpringBoot 项目打包为可执行 Jar 包添加POM依赖<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <!-- <version>2.5.2</version> --> <configuration> <executable>true</executable> </configuration> </plugin> </plugins> </build>执行打包命令mvn clean package测试是否正常运行java -jar app.jarSpringBoot 项目加载外部配置文件通过命令行参数指定配置文件路径在启动应用程序时，可以使用--spring.config.location参数来指定配置文件的路径，例如：java -jar app.jar --spring.config.location=fil…

Java 调用打印机打印PDF文件
type: item
date: 2024-06-01T00:53:00+00:00
description: Java 调用打印机打印PDF文件添加依赖<dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>3.0.2</version> </dependency>编写代码package cn.zzzykj.demo; import cn.hutool.cache.CacheUtil; import cn.hutool.cache.impl.TimedCache; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.printing.PDFPageable; import sun.print.PageableDoc; import javax.print.*; import javax.print.attribute.standard.PrinterIsAcceptingJobs; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.file.Files; import java.util.Arrays; import java.util.Optional; import java.util.concurrent.TimeUnit; /** * @author Lenovo */ @Slf4j public class PrintHelper { public static final String PRINT_NAME = "xxx"; public static final String HTTP_PREFIX = "http"; private static final TimedCache<String, PrintJobState> JOB_STATE_CACHE = CacheUtil.newTimedCache(TimeUnit.HOURS.toMillis(1)); public static int status() { PrintService service = findPrint(); if (service == null) { return 6; } PrinterIsAcceptingJobs attribute = service.getAttribute(PrinterIsAcceptingJobs.class); return attribute.getValue() == 1 ? 0 : 7; } public static boolean printFile(String path, String fileName) { PrintService service = findPrint(); try ( PDDocument pdDocument = Loader.loadPDF(getBytes(path)); ) { PDFPageable pageable = new PDFPageable(pdDocument); int pages = pageable.getNumberOfPages(); PrintJobState jobState = new PrintJobState(pages, 1, path, fileName); JOB_STATE_CACHE.put(StrUtil.isNotEmpty(fileName) ? fileName : "", jobState); PageableDoc doc = new PageableDoc(pageable); DocPrintJob job = service.createPrintJob(); job.print(doc, null); return true…

Java 实现录音并写出到文件
type: item
date: 2024-05-31T06:05:00+00:00
description: Java 实现录音并写出到文件Java 实现录音package cn.zzzykj.demo; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.file.PathUtil; import cn.hutool.core.util.RandomUtil; import lombok.AllArgsConstructor; import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import javax.sound.sampled.*; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Date; import java.util.concurrent.*; /** * @author TheEnd */ @Slf4j public class AudioHelper { private static final TargetDataLine MICROPHONE; private static final ExecutorService executorService = Executors.newSingleThreadExecutor(); private static Future<AudioTaskInfo> future; static { try { MICROPHONE = AudioSystem.getTargetDataLine(new AudioFormat(44100.0F, 16, 2, true, false)); } catch (LineUnavailableException e) { log.error("Audio Init: ", e); throw new RuntimeException(e); } } public static void open() { try { log.info("Audio Open..."); MICROPHONE.open(new AudioFormat(44100.0F, 16, 2, true, false)); } catch (LineUnavailableException e) { log.error("Audio Open: ", e); } } public static synchronized void start() { if (future == null) { future = executorService.submit(new AudioTask()); } } public static synchronized String stop() { if (future == null) { throw new RuntimeException(); } log.info("Audio Stop..."); MICROPHONE.stop(); try { AudioTaskInfo info = future.get(); return info.getFile().getAbsoluteFile().getAbsolutePath(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } } public static void close() { MICROPHONE.close(); } @Data @Getter public static class AudioTask implements Callable<AudioTaskInfo> { private AudioTaskInfo info; private static File newWav() { Path audioTmpDir = Paths.get("tmp", "audio", "wav"); PathUtil.mkdir(audioTmpDir); String audioTmpName = DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomNumbers(3) + ".wav"; Path audioTmpPath = P…

SpringSession会话管理
type: item
date: 2024-01-05T07:29:00+00:00
description: SpringSession会话管理一. Session详情实体类package cn.zzzykj.session.vo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import java.util.Date; /** * @author TheEnd */ @Data @Accessors(chain = true) @NoArgsConstructor @AllArgsConstructor public class SessionInfoVO { private String id; private String sessionId; private String name; private Date creationTime; private Date lastAccessedTime; } 二. 会话管理器1. 创建会话管理器接口package cn.zzzykj.session; import java.util.List; /** * @author TheEnd */ public interface ISessionManager { /** * 获取所有会话信息 * @return 会话信息列表 */ List<SessionInfoVO> getAllSessionInfo(); /** * 使指定会话失效 * @param sessionId 会话id */ void invalidateSession(String sessionId); } 2.编写实现类并注入到Spring容器中 package cn.zzzykj.session; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ScanOptions; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.session.Session; import org.springframework.session.SessionRepository; import org.springframework.session.data.redis.RedisIndexedSessionRepository; import org.springframework.session.jdbc.JdbcIndexedSessionRepository; import org.springframework.stereotype.Component; import java.io.IOException; import java.time.Instant; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * @author Lenovo */ @Slf4j @Component public class DefaultSessionManager implements SessionManager { /** * 会话仓库 */ private SessionRepository<?> sessionRepository; @Autowired public DefaultSessionMan…

SpringBoot 基于 SSE(Server-Send-Event)实现日志文件实时监控
type: item
date: 2023-12-27T07:40:00+00:00
description: SpringBoot 基于 SSE(Server-Send-Event)实现日志文件实时监控一. 后端接口实现:@GetMapping(value = "/log") @ResponseBody public SseEmitter realtimeLog() { SseEmitter emitter = new SseEmitter(0L); String file = "./log.log"; Tailer tailer = new Tailer(FileUtil.newFile(file), StandardCharsets.UTF_8, line -> { try { emitter.send(line); } catch (IOException e) { log.error("SSE响应日志异常", e); throw new RuntimeException(e); } }, 10, DateUnit.SECOND.getMillis()); tailer.start(true); emitter.onCompletion(tailer::stop); return emitter; }二. 前端实现 实时日志监控

HTML中使用 Monaco Editor 编辑器
type: item
date: 2023-12-27T07:10:00+00:00
description: HTML中使用 Monaco Editor 编辑器Monaco Editor 是一个由 Microsoft 开发的基于 Web 的代码编辑器，它最初是为 Visual Studio Code 编辑器开发的。Monaco Editor 提供了许多强大的功能，包括语法高亮、智能代码补全、代码折叠、错误提示、代码片段等。作为一个基于 Web 的编辑器，Monaco Editor 可以很容易地嵌入到任何支持 Web 技术的应用程序中，使得开发人员可以在他们的应用程序中使用类似 Visual Studio Code 的编辑体验。Monaco Editor 也是 Visual Studio Code 的核心编辑器，因此它与 Visual Studio Code 共享了许多功能和特性，包括相似的外观和行为。如果需要在 Web 应用程序中集成一个功能强大的代码编辑器，Monaco Editor 可能是一个很好的选择。一. 下载 Monaco Editor下载链接: monaco-editor-0.45.0.tgzGitHubAPI文档项目首页下载后解压至项目静态资源目录中二. 使用 Monaco Editor1. 引入 Monaco Editor 2. 创建编辑器容器 3. 初始化编辑器const options = { value: [ 'function x() {', '\tconsole.log("Hello, world!");', '}' ].join('\n'), // 设置编辑器初始内容 language: 'javascript', // 设置编辑器语言 automaticLayout: true, // 自动调整布局 readOnly: false, // 是否只读 theme: 'vs-dark', // 设置编辑器主题 fontSize: 14, // 设置字体大小 fontFamily: 'Courier New', // 设置字体 wordWrap: 'on', // 设置自动换行 minimap: { enabled: true // 是否显示迷你地图 }, scrollbar: { useShadows: false, // 是否使用阴影 verticalHasArrows: true, // 垂直滚动条是否显示箭头 horizontal: 'auto', // 设置水平滚动条 vertical: 'auto' // 设置垂直滚动条 }, lineNumbers: 'on', // 是否显示行号 lineDecorationsWidth: 2, // 设置行号装饰宽度 renderWhitespace: 'all', // 显示空格和制表符 renderControlCharacters: true, // 显示控制字符 rulers: [80, 120], // 设置标尺 glyphMargin: true, // 是否显示字形边距 lightbulb: { enabled: true // 是否显示灯泡建议 }, contextmenu: true, // 是否启用右键菜单 folding: true, // 是否启用代码折叠 suggest: { showIcons: true, // 是否显示建议的图标 filteredTypes: { keyword: false, snippet: true, color: true, reference: true } // 过滤建议类型 }, quickSuggestions: { other: true, comments: false, strings: true } }; // 创建 Monaco Editor 实例 monaco.editor.create(document.getElementById('editor'), options);三. 常用功能// 创建编辑器实例：使用 monaco.editor.create 方法可以创建一个新的编辑器实例。 var editor = monaco.editor.create(document.getElementById('container'), { value: "function hello() {\n\talert('Hello, world!');\n}", language: "javascript" }); // 获取和设置文本内容：使用 editor.getValue() 方法可以获取编辑器中的文本内容，使用 editor.setValue(newValue) 方法可以设置编辑器的文本内容。 var currentText = editor.getValue(); editor.setV…

SpringBoot读取多次RequestBody
type: item
date: 2023-12-04T06:30:00+00:00
description: SpringBoot读取多次RequestBody一. 编写相关类1. Request包装器import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringReader; /** * @author TheEnd */ public class RequestWrapper extends HttpServletRequestWrapper { private String requestBody; public RequestWrapper(HttpServletRequest request) { super(request); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new StringReader(getRequestBody())); } @Override public ServletInputStream getInputStream() throws IOException { return new ServletInputStreamWrapper(requestBody.getBytes()); } private static class ServletInputStreamWrapper extends ServletInputStream { private final ByteArrayInputStream inputStream; public ServletInputStreamWrapper(byte[] body) { inputStream = new ByteArrayInputStream(body); } @Override public int read() throws IOException { return inputStream.read(); } @Override public boolean isFinished() { return inputStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener listener) { } } private String getRequestBody() throws IOException { if (requestBody == null) { StringBuilder requestBodyBuilder = new StringBuilder(); BufferedReader reader = super.getReader(); String line; while ((line = reader.readLine()) != null) { requestBodyBuilder.append(line); } requestBody = requestBodyBuilder.toString(); } return requestBody; } }2. 编写工具类 import cn.hutool.core.util.StrUtil; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author TheEnd */ public class ContentTypeUtil { public static boolean isBinaryContent(HttpServletRequest request) { String contentType = request.getContentType(); return isBinaryContent(contentType); } public static b…

SpringBoot 多数据源配置
type: item
date: 2023-11-28T05:50:00+00:00
description: SpringBoot 多数据源配置一. 创建动态数据源和数据源切换器创建一个继承自AbstractRoutingDataSource的动态数据源类，用于根据当前线程的上下文选择数据源：import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSourceKey(); } }创建一个DataSourceContextHolder类，用于保存当前线程使用的数据源标识：public class DataSourceContextHolder { private static final ThreadLocal<String> dataSourceKey = new ThreadLocal<>(); public static void setDataSourceKey(String key) { dataSourceKey.set(key); } public static String getDataSourceKey() { return dataSourceKey.get(); } public static void clearDataSourceKey() { dataSourceKey.remove(); } }二. 数据源注入在Spring Boot的配置类中配置多个数据源，并将它们添加到DynamicDataSource中：import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; @Configuration public class DataSourceConfig { @Bean(name = "dataSource1") @ConfigurationProperties("spring.datasource.dataSource1") public DataSource dataSource1() { // 配置第一个数据源 // ... } @Bean(name = "dataSource2") @ConfigurationProperties("spring.datasource.dataSource2") public DataSource dataSource2() { // 配置第二个数据源 // ... } @Primary @Bean(name = "datasource") public AbstractRoutingDataSource routingDataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put("dataSource1", dataSource1()); dataSourceMap.put("dataSource2", dataSource2()); dynamicDataSource.setTargetDataSources(dataSourceMap); dynamicDataSource.setDefaultTargetDataSource(dataSource1()); return dynamicDataSource; } }移除SpringBoot中关于DataSource的自动配置, 解决循环依赖问题@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }三. 在业务中切换数据源在需要切换数…

SpringCloud Gateway提示端口占用
type: item
date: 2023-02-01T06:56:00+00:00
description: SpringCloud Gateway提示端口占用检查端口是否已使用检查 server.address 是否正确