Java对日志文件进行加密

news2025/1/22 9:23:58

最近碰到了一个新的需求,生产环境中Java程序部署的服务器会定期清理数据,需要将保存在程序所在服务器上的日志文件挂载到网盘上,但又不想让用户看到日志文件中的信息,因此需要对日志文件中的内容进行加密。
这里,并不是对日志文件中的敏感信息进行加密,而是对所有数据都进行加密。上网查了一圈资料之后,最终到了解决方案:自定义Appender,使用AES进行加密。下面贴出具体代码。
AES加密解密工具类

package com.lg.coding.util;

import java.io.*;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AESUtil {
    private static final String ALGORITHM = "AES";
    private static int offset = 16;
    private static final String transformation = "AES/CBC/PKCS5Padding";

    /**
     * AES加密字符串
     * @param password  密钥
     * @param value 待加密字符串
     */
    public static String encrypt(String password, String value) {
        try {
            Key key = generateKey(password);
            //创建初始向量iv用于指定密钥偏移量
            IvParameterSpec iv = new IvParameterSpec(password.getBytes(), 0, offset);
            Cipher cipher = Cipher.getInstance(transformation);
            cipher.init(Cipher.ENCRYPT_MODE, key, iv);
            byte[] encryptedByteValue = cipher.doFinal(value.getBytes("utf-8"));
            String encryptedValue64 = Base64.getEncoder().encodeToString(encryptedByteValue);
            return encryptedValue64;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * AES解密字符串
     * @param password  密钥
     * @param value 待解密字符串
     * @return
     */
    public static String decrypt(String password, String value) {
        try {
            Key key = generateKey(password);
            //创建初始向量iv用于指定密钥偏移量
            IvParameterSpec iv = new IvParameterSpec(password.getBytes(), 0, offset);
            Cipher cipher = Cipher.getInstance(transformation);
            cipher.init(Cipher.DECRYPT_MODE, key, iv);
            byte[] decryptedValue64 = Base64.getDecoder().decode(value);
            byte[] decryptedByteValue = cipher.doFinal(decryptedValue64);
            String decryptedValue = new String(decryptedByteValue,"utf-8");
            return decryptedValue;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * AES解密文件
     * @param password 密钥
     * @param inputFilePath 待解密文件路径
     * @param outputFilePath 输出文件路径
     */
    public static void decryptFile(String password, String inputFilePath, String outputFilePath) {
        InputStream inputStream = null;
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        try {
            inputStream = new FileInputStream(inputFilePath);
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
            bufferedWriter = new BufferedWriter(new FileWriter(outputFilePath));
            String s;
            while ((s = bufferedReader.readLine()) != null) {
                bufferedWriter.write(decrypt(password, s));
                bufferedWriter.newLine();
                bufferedWriter.flush();
            }
        } catch (FileNotFoundException e) {
            System.out.println("找不到指定文件!");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("文件读取错误!");
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
                bufferedReader.close();
                bufferedWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 生成key
     * @param password
     * @return
     * @throws Exception
     */
    private static Key generateKey(String password) {
        Key key = new SecretKeySpec(password.getBytes(),ALGORITHM);
        return key;
    }
}

在这里,加密操作和解密操作都是针对字符串进行的,在自定义Appender类中,重写subAppend方法,在执行输出文件操作之前对内容进行字符串加密;解密时,逐行读取文件内容后再进行字符串解密。
自定义Appender类

package com.lg.coding.util;

import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.Layout;
import ch.qos.logback.core.rolling.RollingPolicy;
import ch.qos.logback.core.rolling.RollingPolicyBase;
import ch.qos.logback.core.rolling.RolloverFailure;
import ch.qos.logback.core.rolling.TriggeringPolicy;
import ch.qos.logback.core.rolling.helper.CompressionMode;
import ch.qos.logback.core.rolling.helper.FileNamePattern;
import ch.qos.logback.core.spi.DeferredProcessingAware;
import ch.qos.logback.core.status.ErrorStatus;
import ch.qos.logback.core.util.ContextUtil;
import org.slf4j.event.LoggingEvent;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;

public class CustomRollingFileAppender<E> extends FileAppender<E> {
    File currentlyActiveFile;
    TriggeringPolicy<E> triggeringPolicy;
    RollingPolicy rollingPolicy;
    private static String RFA_NO_TP_URL = "http://logback.qos.ch/codes.html#rfa_no_tp";
    private static String RFA_NO_RP_URL = "http://logback.qos.ch/codes.html#rfa_no_rp";
    private static String COLLISION_URL = "http://logback.qos.ch/codes.html#rfa_collision";
    private static String RFA_LATE_FILE_URL = "http://logback.qos.ch/codes.html#rfa_file_after";

    public CustomRollingFileAppender() {
    }

    public void start() {
        if (this.triggeringPolicy == null) {
            this.addWarn("No TriggeringPolicy was set for the RollingFileAppender named " + this.getName());
            this.addWarn("For more information, please visit " + RFA_NO_TP_URL);
        } else if (!this.triggeringPolicy.isStarted()) {
            this.addWarn("TriggeringPolicy has not started. RollingFileAppender will not start");
        } /*else if (this.checkForCollisionsInPreviousRollingFileAppenders()) {
            this.addError("Collisions detected with FileAppender/RollingAppender instances defined earlier. Aborting.");
            this.addError("For more information, please visit " + COLLISION_WITH_EARLIER_APPENDER_URL);
        }*/ else {
            if (!this.append) {
                this.addWarn("Append mode is mandatory for RollingFileAppender. Defaulting to append=true.");
                this.append = true;
            }

            if (this.rollingPolicy == null) {
                this.addError("No RollingPolicy was set for the RollingFileAppender named " + this.getName());
                this.addError("For more information, please visit " + RFA_NO_RP_URL);
            } /*else if (this.checkForFileAndPatternCollisions()) {
                this.addError("File property collides with fileNamePattern. Aborting.");
                this.addError("For more information, please visit " + COLLISION_URL);
            }*/ else {
                if (this.isPrudent()) {
                    if (this.rawFileProperty() != null) {
                        this.addWarn("Setting \"File\" property to null on account of prudent mode");
                        this.setFile((String)null);
                    }

                    if (this.rollingPolicy.getCompressionMode() != CompressionMode.NONE) {
                        this.addError("Compression is not supported in prudent mode. Aborting");
                        return;
                    }
                }

                this.currentlyActiveFile = new File(this.getFile());
                this.addInfo("Active log file name: " + this.getFile());
                super.start();
            }
        }
    }

    /*private boolean checkForFileAndPatternCollisions() {
        if (this.triggeringPolicy instanceof RollingPolicyBase) {
            RollingPolicyBase base = (RollingPolicyBase)this.triggeringPolicy;
            FileNamePattern fileNamePattern = base.fileNamePattern;
            if (fileNamePattern != null && this.fileName != null) {
                String regex = fileNamePattern.toRegex();
                return this.fileName.matches(regex);
            }
        }

        return false;
    }

    private boolean checkForCollisionsInPreviousRollingFileAppenders() {
        boolean collisionResult = false;
        if (this.triggeringPolicy instanceof RollingPolicyBase) {
            RollingPolicyBase base = (RollingPolicyBase)this.triggeringPolicy;
            FileNamePattern fileNamePattern = base.fileNamePattern;
            boolean collisionsDetected = this.innerCheckForFileNamePatternCollisionInPreviousRFA(fileNamePattern);
            if (collisionsDetected) {
                collisionResult = true;
            }
        }

        return collisionResult;
    }*/

    private boolean innerCheckForFileNamePatternCollisionInPreviousRFA(FileNamePattern fileNamePattern) {
        boolean collisionsDetected = false;
        Map<String, FileNamePattern> map = (Map)this.context.getObject("RFA_FILENAME_PATTERN_COLLISION_MAP");
        if (map == null) {
            return collisionsDetected;
        } else {
            Iterator var4 = map.entrySet().iterator();

            while(var4.hasNext()) {
                Map.Entry<String, FileNamePattern> entry = (Map.Entry)var4.next();
                if (fileNamePattern.equals(entry.getValue())) {
                    this.addErrorForCollision("FileNamePattern", ((FileNamePattern)entry.getValue()).toString(), (String)entry.getKey());
                    collisionsDetected = true;
                }
            }

            if (this.name != null) {
                map.put(this.getName(), fileNamePattern);
            }

            return collisionsDetected;
        }
    }

    public void stop() {
        super.stop();
        if (this.rollingPolicy != null) {
            this.rollingPolicy.stop();
        }

        if (this.triggeringPolicy != null) {
            this.triggeringPolicy.stop();
        }

        Map<String, FileNamePattern> map = ContextUtil.getFilenamePatternCollisionMap(this.context);
        if (map != null && this.getName() != null) {
            map.remove(this.getName());
        }

    }

    public void setFile(String file) {
        if (file != null && (this.triggeringPolicy != null || this.rollingPolicy != null)) {
            this.addError("File property must be set before any triggeringPolicy or rollingPolicy properties");
            this.addError("For more information, please visit " + RFA_LATE_FILE_URL);
        }

        super.setFile(file);
    }

    public String getFile() {
        return this.rollingPolicy.getActiveFileName();
    }

    public void rollover() {
        this.lock.lock();

        try {
            this.closeOutputStream();
            this.attemptRollover();
            this.attemptOpenFile();
        } finally {
            this.lock.unlock();
        }

    }

    private void attemptOpenFile() {
        try {
            this.currentlyActiveFile = new File(this.rollingPolicy.getActiveFileName());
            this.openFile(this.rollingPolicy.getActiveFileName());
        } catch (IOException var2) {
            this.addError("setFile(" + this.fileName + ", false) call failed.", var2);
        }

    }

    private void attemptRollover() {
        try {
            this.rollingPolicy.rollover();
        } catch (RolloverFailure var2) {
            this.addWarn("RolloverFailure occurred. Deferring roll-over.");
            this.append = true;
        }

    }

    protected void subAppend(E event) {
        if (this.isStarted()) {
            try {
                if (event instanceof DeferredProcessingAware) {
                    ((DeferredProcessingAware)event).prepareForDeferredProcessing();
                }
                byte[] byteArray = this.encoder.encode(event);
                //加密前数据
                String originalString = new String(byteArray, "UTF-8");
                //加密后数据
                String encryptedString = AESUtil.encrypt("Sanyuan123456789", originalString);
                this.writeBytes((encryptedString + "\n").getBytes());
            } catch (IOException var3) {
                this.started = false;
                this.addStatus(new ErrorStatus("IO failure in appender", this, var3));
            }

        }
    }

    private void writeBytes(byte[] byteArray) throws IOException {
        if (byteArray != null && byteArray.length != 0) {
            this.lock.lock();

            try {
                this.getOutputStream().write(byteArray);
                if (this.isImmediateFlush()) {
                    this.getOutputStream().flush();
                }
            } finally {
                this.lock.unlock();
            }

        }
    }

    public RollingPolicy getRollingPolicy() {
        return this.rollingPolicy;
    }

    public TriggeringPolicy<E> getTriggeringPolicy() {
        return this.triggeringPolicy;
    }

    public void setRollingPolicy(RollingPolicy policy) {
        this.rollingPolicy = policy;
        if (this.rollingPolicy instanceof TriggeringPolicy) {
            this.triggeringPolicy = (TriggeringPolicy)policy;
        }

    }

    public void setTriggeringPolicy(TriggeringPolicy<E> policy) {
        this.triggeringPolicy = policy;
        if (policy instanceof RollingPolicy) {
            this.rollingPolicy = (RollingPolicy)policy;
        }

    }
}

这里是直接复制RollingFileAppender类的代码,对subAppend方法进行重写,在调用writeBytes()方法之前进行加密操作,将加密后的数据输出到本地。
本系统用的日志框架为SpringBoot内置的日志处理框架Logback。将logback-spring.xml文件中系统日志输出对应的Appender标签,class属性改为自定义Appender类的全路径:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <springProperty scope="context" name="logDir" source="logging.path"/>
    <!-- 日志存放路径 -->
    <property name="log.path" value="${logDir}" />
    <!-- 日志输出格式 -->
<!--    <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />-->
    <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />

    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>utf8</charset>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>${log.pattern}</pattern>
        </layout>
    </appender>

    <!-- 系统日志输出 -->
    <appender name="file_info" class="com.lg.coding.util.CustomRollingFileAppender">
        <file>${log.path}/coding-info.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>${log.path}/coding-info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 日志最大的历史 60-->
            <maxHistory>60</maxHistory>
            <maxFileSize>10MB</maxFileSize>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>utf8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 只会打印debug不会有info日志-->
            <!--            <level>DEBUG</level>-->
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
            <level>INFO</level>
        </filter>
    </appender>

    <appender name="file_error" class="com.lg.coding.util.CustomRollingFileAppender">
        <file>${log.path}/coding-error.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>coding/.%d{yyyy-MM-dd-HH:mm:ss}.gz</fileNamePattern>
            <fileNamePattern>${log.path}/coding-error.%d{yyyy-MM-dd-HH}.log</fileNamePattern>
            <!-- 日志最大的历史 60-->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>utf8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 -->
            <level>ERROR</level>
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="file_debug" class="com.lg.coding.util.CustomRollingFileAppender">
        <file>${log.path}/coding-debug.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>${log.path}/coding-debug.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 日志最大的历史 60-->
            <maxHistory>60</maxHistory>
            <maxFileSize>10MB</maxFileSize>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>utf8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 只会打印debug不会有info日志-->
            <!--            <level>DEBUG</level>-->
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
            <level>DEBUG</level>
        </filter>
    </appender>

    <!-- 用户访问日志输出  -->
    <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/sys-user.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天回滚 daily -->
            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日志最大的历史 60-->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>

    <!-- 系统模块日志级别控制  -->
    <logger name="com.example" level="debug" />
    <!-- Spring日志级别控制  -->
    <logger name="org.springframework" level="warn" />

    <root level="info">
        <appender-ref ref="console" />
    </root>

    <!--系统操作日志-->
    <root level="info">
        <appender-ref ref="console" />
        <appender-ref ref="file_info" />
        <appender-ref ref="file_error" />
        <appender-ref ref="file_debug" />
    </root>

    <!--系统用户操作日志-->
    <logger name="sys-user" level="info">
        <appender-ref ref="sys-user"/>
    </logger>
</configuration>

此时,启动项目,查看系统本地日志文件:
在这里插入图片描述
可以看到,日志文件的内容已成功加密。
再写一个文件解密接口:

	@ApiOperation(value = "aes文件解密测试")
    @PostMapping("/aesFileDecrypteTest")
    public void aesFileDecrypteTest(String fileInputPath, String fileOutputPath) {
        String key = "Sanyuan123456789";
        AESUtil.decryptFile(key, fileInputPath, fileOutputPath);
    }

其中,fileInputPath和fileOutputPath两个参数分别为待解密文件所在路径和解密后的文件所在路径,接口调用中输入对应参数:
在这里插入图片描述
调用接口,可以看到,指定路径下生成了一个名为“解密日志.log”的文件, 打开文件,查看内容:
在这里插入图片描述
可以看到,内容已被成功解密。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/768075.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

详细介绍MATLAB中的图论算法

MATLAB是一种功能强大的编程语言和环境,提供了许多用于图论算法的工具和函数。图论是研究图及其属性和关系的数学分支,广泛应用于计算机科学、网络分析、社交网络分析等领域。在MATLAB中,我们可以使用图论算法来解决各种问题,如最短路径问题、最小生成树问题、最大流问题等…

华为无线ac+ap旁挂二层组网常用配置案例

AC控制器理解配置步骤&#xff1a; capwap source interface Vlanif 100 //源IP回包地址 wlan ssid-profile name test //新建个模版名称为test ssid test //wifi名称 wlan security-profile name test //建立安全模版也叫test security wpa-wpa2 psk pass-phrase admin123 a…

Python 算法基础篇:冒泡排序和选择排序

Python 算法基础篇&#xff1a;冒泡排序和选择排序 引言 1. 冒泡排序算法概述2. 冒泡排序算法实现实例1&#xff1a;冒泡排序 3. 选择排序算法概述4. 选择排序算法实现实例2&#xff1a;选择排序 5. 冒泡排序与选择排序的对比总结 引言 冒泡排序和选择排序是两种常用的排序算法…

使用java创建图片文字模版

场景:公司员工生日到了,给过生日的同事发送邮件附带图片祝福 以下是生成图片模版的代码 package com.yujie;import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import javax.imageio.ImageIO;public class ImageMergeExample {public static vo…

第十五 rabbitmq及数据同步

0.学习目标 了解常见的MQ产品 了解RabbitMQ的5种消息模型 会使用Spring AMQP 利用MQ实现搜索和静态页的数据同步 1.RabbitMQ 1.1.搜索与商品服务的问题 目前我们已经完成了商品详情和搜索系统的开发。我们思考一下&#xff0c;是否存在问题&#xff1f; 商品的原始数据保…

C++ 第三讲

1 >手动封装一个顺序栈类(数据元素为整形)&#xff0c;要求私有成员属性:堆区空间的指针&#xff0c;用于存放数据&#xff0c;和一个指向栈顶元素的变量 main.cpp #include "zuoye.h"int main() {//实例化对象My_stack Stck;My_stack &st Stck;//入栈Stck…

【数据结构】手撕排序NO.1----排序初识

目录 一. 前言 二. 排序的概念及运用 2.1 排序的概念 2.2 排序的运用 2.3 常见的排序算法 三. 冒泡and选择排序 3.1 冒泡排序 3.2 选择排序 四. 各大排序算法的复杂度和稳定性 一. 前言 从本期开始&#xff0c;我们的数据结构将迎来一个新的篇章&#xff1a;排序篇&#xff…

【7天学GO】第1章 开发环境

1.1 开篇介绍(必看) A. Why choose the go language B. 学语言阶段 1.2 环境搭建前戏 A. 学习一门语言步骤 B. 编译型与解释型 1.3 mac系统Go开发环境搭建 (略) 1.4 linux系统Go开发环境搭建 (略) 1.5 windows系统Go开发环境搭建 A. 开发环境搭建 Stage 1&#xff1a…

Appium+python自动化(十一)- 元素定位- 下卷超详解)

1、 List定位 List故名思义就是一个列表&#xff0c;在python里面也有list这一个说法&#xff0c;如果你不是很理解什么是list&#xff0c;这里暂且理解为一个数组或者说一个集合。首先一个list是一个集合&#xff0c;那么他的个数也就成了不确定性&#xff0c;所以这里需要用复…

和为 K 的子数组——前缀和+哈希

题目链接&#xff1a;力扣 注意&#xff1a;此题不能使用滑动窗口&#xff0c;因为数组中可能会出现负数。也就是说右指针向后移1位不能保证区间会增大&#xff0c;左指针向后移1位也不能保证区间和会减小。给定左右指针的位置没有二段性 已知sum[i]是从nums[0~i]的和&#x…

STM32之按键驱动的使用和自定义(MultiButton)

原始Github地址 Github地址 修改后 调整内容 将宏定义转换成配置结构体 头文件 #ifndef _MULTI_BUTTON_H_ #define _MULTI_BUTTON_H_#include "stdint.h" #include "string.h"//According to your need to modify the constants. //#define TICKS_IN…

数据结构--图的存储邻接表法

数据结构–图的存储邻接表法 邻接矩阵&#xff1a; 数组实现的顺序存储&#xff0c;空间复杂度高&#xff0c;不适合存储稀疏图 邻接表&#xff1a; 顺序链式存储 邻接表法&#xff08;顺序链式存储&#xff09; //边/弧 typedef struct ArcNode {int adjvex; //边/弧指向哪个…

MVX-Net Multimodal VoxelNet for 3D Object Detection 论文学习

论文链接&#xff1a;MVX-Net Multimodal VoxelNet for 3D Object Detection 1. 解决了什么问题&#xff1f; 2D 目标检测取得了显著成效&#xff0c;但由于输入模态的本质区别&#xff0c;CNN 无法直接应用在 3D 检测任务。LiDAR 能准确地定位到 3D 空间的物体&#xff0c;基…

根据对象数组的key进行分组

简单版&#xff1a; const arr [{key: aaa,tableName: bbbbb},{key: aaa,tableName: bbbbb},{key: www,tableName: bbbbb},{key: www,tableName: bbbbb},{key: mysql_ytr,tableName: bbbbb} ]// 把arr按key進行分組&#xff0c; 輸出結果是對象&#xff0c;對象裡面用key做鍵…

logback自定义调用以及文件输出

1、logback 首先五大日志等级是不可更改的&#xff0c;我们所定义的日志输出和调用也是用的这五大等级&#xff0c;这个就不多说了&#xff0c;没啥用。 2、效果 调用 String msg "测试日志"; MyLoger.myloger(msg); 输出 2023-07-18 10:55:05 [main] INFO m…

[element-ui] el-select,虚拟滚动(vue-virtual-scroll-list)

一、问题描述 表单中某下拉框&#xff0c;由于数据过多&#xff0c;选择的时候会因为数据量过大导致页面卡顿&#xff0c;于是对于el-select进行二次封装&#xff0c;实现虚拟滚动。 二、实现如下&#xff1a; 看起来是加载了全部数据&#xff0c;实际上只加载了自己设定的1…

单例模式类设计|什么是饿汉模式和懒汉模式

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量干货博客汇总https://blog.csdn.net/yu_cblog/c…

汇编实现1-100累加(ARMv7)

汇编实现1-100累加 代码.text .globl _start _start:mov r0,#0 summationmov r1,#0 autoIncrementbl funadd funadd:cmp r1,#100addccs r0,r1,r0addccs r1,r1,#1mov pc,lr stop:b stop .end运行效果

请问如何用oracle触发器实现不允许新增/删除表/增加/减少/修改字段类型

请问如何用oracle触发器实现不允许新增/删除表/增加/减少/修改字段类型 给本帖投票 56211打赏收藏 分享 转发到动态举报 写回复 性能测试中发现oracle11g数据库每天22点,oralce进程CPU占用率突增>> 11 条回复 切换为时间正序 请发表友善的回复… 发表回复 microsof…

UWB天线设计之一种优化扁平超宽带单极的新方法

文章亮点 一种新颖的方法提出了扁平超宽带单极天线。 通过应用收缩,可以设计具有相同性能的更小的天线作为平面单极天线。 优化结果表明该方法能够设计非常小的天线。 在这项研究中,提出了一种优化扁平超宽带单极天线的新方法。在该方法中,一般性地描述了天线的形状。这种一…