操作_操作系统 - CSDN
  • char相关 判断是否是字母、数字 Character.isLetterOrDigit(c1)//字母或者字符串 Character.isDigit();//数字 Character.isLetter();//字母 ...char的大小写转换,单个字符 ...if(Character.isUpperCase(c1)){ ...

    char相关

    • 判断是否是字母、数字
    Character.isLetterOrDigit(c1)//字母或者字符串
    Character.isDigit();//数字
    Character.isLetter();//字母
    
    • char的大小写转换,单个字符
    c1 = Character.toLowerCase(c1);
    c2 = Character.isUpperCase(c1)
    //toLowerCase() 方法可以将字符串中的所有字符全部转换成小写,而非字母的字符不受影响
    或者:
    c = (char)(c - 32);//小写字母转大写字母
    

    String相关

    String low = s.toLowerCase()//全部转成小写
    String.length() //获取字符串的长度
    String.charAt(i) //获取第i个字符的内容
    String.subString(start) //获取[start,)的字符串
    String.subString(start,end) //获取[start,end)中的字符串,左闭右开
    char[] c = iniString.toCharArray() //将字符串转为char数
    String.equal() //判断两个字符串是否相等
    
    • String 拼接
    char firstletter = s.charAt(0);
    firstletter = (char)(firstletter - 32);//小写转大写
    String firstword = firstletter + c.substring(1);
    
    • 看String字符串是否为空字符串的方法
      去空格!然后和""相比,相同就是true
    str.trim().equals("")
    

    StringBuilder相关

    StringBuffer又称为可变字符序列,它是一个类似于 String 的字符串缓冲区,通过某些方法调用可以改变该序列的长度和内容。和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象

    • StringBuilder和StringBuffer的区别?
      基本相似,两个类的构造器和方法也基本相同。不同的是:StringBuffer是线程安全的,而StringBuilder则没有实现线程安全功能,所以性能略高。
    StringBuilder sb = new StringBuilder();
    sb.length()
    sb.append(s);//添加
    sb.append(s, i+1, j);//左闭右开
    sb.delete(0,1);  //删除[0,1)的数据
    sb.deleteCharAt(sb.length()-1);//删除某个位置的元素
    sb.setCharAt(0, 'c');//修改某个位置的元素,将第0个位置改成c
    sb.toString();//StringBuilder 转 String
    sb.insert(0,"we");//在0的位置加入后面的内容
    char c = stringBuilder.charAt(i);//查询某个位置上的字符
    new String(stringBuilder);//用stringBuilder来初始化String
    

    相互转换

    char[] c = iniString.toCharArray() //String转为char数组
    sb.toString();//StringBuilder 转 String
    String[] str = low.split(" ");//String转String[],根据空格切分
    //String[] to String,根据空格合并
    String ans = "";
    ans = String.join(" ", str);
    
    //List转String,这样会把逗号也加进String里
    String string1 = String.join(",",list1);
    //char转String
    String s = String.valueOf('c');
    
    展开全文
  • P、V操作经典问题

    千次阅读 2019-01-16 16:29:39
    1.设有一台计算机,有两条I/O通道,分别接一台卡片输入机和一台打印机。卡片机把一叠卡片逐一输入到缓冲区B1中,加工处理后再搬到缓冲区B2中,并在打印机上打印。 【解】 (0)分析题意,画出草图: ...

    1.设有一台计算机,有两条I/O通道,分别接一台卡片输入机和一台打印机。卡片机把一叠卡片逐一输入到缓冲区B1中,加工处理后再搬到缓冲区B2中,并在打印机上打印。

    【解】
    (0)分析题意,画出草图:
    在这里插入图片描述
    (1)设定三个进程:
    I进程:负责把卡片上的信息输入缓冲区B1
    P进程:负责从缓冲区B1中取到信息并加工处理,送到缓冲区B2
    C进程:负责从缓冲区B2中取到信息,在打印机上打印

    (2)假设B1缓冲区大小是M个单元,B2缓冲区大小是N个单元。

    (3)设定四个信号量:
    B1FULL:B1缓冲区已经使用的单元数,初始化为0
    B1EMPTY:B1缓冲区未使用的单元数,初始化为M
    B2FULL:B2缓冲区已经使用的单元数,初始化为0
    B2EMPTY:B2缓冲区未使用的单元数,初始化为N

    (4)根据题设书写算法:

    I进程:

    repeat
    	P(B1EMPTY)
    	把卡片信息写入缓冲区B1
    	V(B1FULL)
    util false
    

    P进程:

    repeat
    	P(B1FULL)
    	P(B2EMPTY)
    	从B1中取信息
    	加工处理
    	送到B2
    	V(B1EMPTY)
    	V(B2FULL)
    until false
    

    C进程:

    repeat
    	P(B2FULL)
    	从B2取信息
    	打印
    	V(B2EMPTY)
    util false
    

    2.生产者-消费者问题(缓冲区大小是1)
    在这里插入图片描述A进程:生产者生产产品,放入缓冲区
    B进程:消费者从缓冲区取产品,消费掉

    设定信号量full和empty,full表示缓冲区已经使用的空间,初始化为0,empty表示缓冲区尚未使用的空间,初始化为1

    A进程:

    repeat
    	生产产品
    	P(empty)
    	放入缓冲区
    	V(full)
    util false
    

    B进程:

    repeat 
    	P(full)
    	从缓冲区取产品
    	V(empty)
    	消费掉产品
    false
    

    3.生产者-消费者问题(缓冲区大小是k)
    在这里插入图片描述
    A进程:生产进程
    B进程:消费进程

    设定信号量full和empty,full表示缓冲区已经使用的空间,初始化为0,empty表示缓冲区尚未使用的空间,初始化为k。设定信号量mutex,初始值为1,用来互斥。

    A进程:

    i = 0;
    repeat 
    	生产产品
    	P(empty)
    	P(mutex)
    	往缓冲区的i位置放入产品
    	i = (i+1)%k;
    	V(mutex)
    	V(full)
    false
    

    B进程:

    j = 0;
    repeat
    	P(full)
    	P(mutex)
    	从缓冲区的j位置取产品
    	j = (j+1)%k;
    	V(mutex)
    	V(empty)
    	消费掉产品
    false
    

    笔者认为,既然i和j都初始化为0,两个进程对环形缓冲池这一资源不需要做互斥。上述的互斥代码可用去掉。

    4.读者-写者问题
    多个进程可用同时读文件,任意一个进程在写文件的时候其他进程就不得对文件进行写或读操作,有进程在读文件的时候不允许别的进程写文件。

    设定两个进程A(读进程)和B(写进程)。
    设定信号量mutex对临界区资源做互斥,初始值为1。
    设定信号量readerCount表示读者数量,初始值是0。
    设定信号量write表示可用做写操作的能力,初始值为1。

    A:

    repeat
    	P(mutex)
    	readerCount++;
    	if(readerCount == 1){//第一个读者来的时候,看看有没有写者在写文件
    		P(write)
    			//如果write是1,表明没有写者在写文件,把write减1,使得写者不能写文件,
    			//如果write<1,表明现在有写者在写文件,需要等待写者写完
    	}
    	V(mutex)
    	读文件
    	P(mutex)
    	readerCount--;
    	if(readerCount == 0){//如果读者数量为0,此时写者是可写的
    		V(write)
    	}
    	V(mutex)
    util false
    

    B:

    repeat
    	P(write)
    	写文件
    	V(write)
    util false
    

    5.哲学家就餐问题
    说的是一个桌子周围坐5个哲学家,每个哲学家左右都只有一个叉子,每个哲学家要想吃饭,就必须同时拿起两个叉子,怎么5个哲学家同时拿起一侧的叉子而发现另一侧没有叉子的尴尬局面(死锁)?

    笔者参考了《操作系统–精髓与设计原理》(美)William Stallings著的解决方案,书中说的真有趣,一种方案是教会哲学家用一个叉子吃饭就OK了!!哈哈哈哈,另一种方案就是增加一个服务生,每次只允许4个哲学家同时就坐,下面就是这个算法啦:

    规定:哲学家编号依次是0,1,2,3,4,第i个哲学家左侧叉子编号是i,右侧叉子编号是(i+1)%5

    int[] fork = new int[]{1,1,1,1,1};//5个叉子都能用,初始化为1
    
    void philosopher(int i){
    	while(true){
    		思考
    		P(fork[i])
    		P(fork[(i+1)%5])
    		吃饭
    		V(fork[(i+1)%5])
    		V(fork[i])
    	}
    }
    
    void main(){
    	cobegin
    		philosopher(0);
    		philosopher(1);
    		philosopher(2);
    		philosopher(3);
    		philosopher(4);
    	coend
    }
    
    展开全文
  • 操作系统内存详解

    千次阅读 2018-02-10 13:35:06
    在现代操作系统中,每个进程所能访问的内存是互相独立的(一些交换区除外)。而进程中的线程可以共享进程所分配的内存空间。 在操作系统的角度来看,进程=程序+数据+PCB(进程控制块) 没有内存抽象 在早些的操作...

    进程的简单介绍

    进程是占有资源的最小单位,这个资源当然包括内存。在现代操作系统中,每个进程所能访问的内存是互相独立的(一些交换区除外)。而进程中的线程可以共享进程所分配的内存空间。
    在操作系统的角度来看,进程=程序+数据+PCB(进程控制块)

    没有内存抽象

    在早些的操作系统中,并没有引入内存抽象的概念。程序直接访问和操作的都是物理内存。比如当执行如下指令时:
    mov reg1,1000

    这条指令会将物理地址1000中的内容赋值给寄存器。不难想象,这种内存操作方式使得操作系统中存在多进程变得完全不可能,比如MS-DOS,你必须执行完一条指令后才能接着执行下一条。如果是多进程的话,由于直接操作物理内存地址,当一个进程给内存地址1000赋值后,另一个进程也同样给内存地址赋值,那么第二个进程对内存的赋值会覆盖第一个进程所赋的值,这回造成两条进程同时崩溃。

    没有内存抽象对于内存的管理通常非常简单,除去操作系统所用的内存之外,全部给用户程序使用。或是在内存中多留一片区域给驱动程序使用,如图1所示。

    这里写图片描述

    第一种情况操作系统存于RAM中,放在内存的低地址第二种情况操作系统存在于ROM中,存在内存的高地址,一般老式的手机操作系统是这么设计的。

    如果这种情况下,想要操作系统可以执行多进程的话,唯一的解决方案就是和硬盘搞交换,当一个进程执行到一定程度时,整个存入硬盘,转而执行其它进程,到需要执行这个进程时,再从硬盘中取回内存,只要同一时间内存中只有一个进程就行,这也就是所谓的交换(Swapping)技术。但这种技术由于还是直接操作物理内存,依然有可能引起进程的崩溃。
    所以,通常来说,这种内存操作往往只存在于一些洗衣机,微波炉的芯片中,因为不可能有第二个进程去征用内存。

    内存抽象

    为了解决直接操作内存带来的各种问题,引入的地址空间(Address Space)这个概念,这允许每个进程拥有自己的地址。这还需要硬件上存在两个寄存器,基址寄存器(base register)和界址寄存器(limit register),第一个寄存器保存进程的开始地址,第二个寄存器保存上界,防止内存溢出。在内存抽象的情况下,当执行
    mov reg1,20
    这时,实际操作的物理地址并不是20,而是根据基址和偏移量算出实际的物理地址进程操作,此时操作的实际地址可能是:
    mov reg1,16245
    在这种情况下,任何操作虚拟地址的操作都会被转换为操作物理地址。而每一个进程所拥有的内存地址是完全不同的,因此也使得多进程成为可能。

    但此时还有一个问题,通常来说,内存大小不可能容纳下所有并发执行的进程。因此,交换(Swapping)技术应运而生。这个交换和前面所讲的交换大同小异,只是现在讲的交换在多进程条件下。交换的基本思想是,将闲置的进程交换出内存,暂存在硬盘中,待执行时再交换回内存,比如下面一个例子,当程序一开始时,只有进程A,逐渐有了进程B和C,此时来了进程D,但内存中没有足够的空间给进程D,因此将进程B交换出内存,分给进程D。如图2所示。

    这里写图片描述

    通过图2,我们还发现一个问题,进程D和C之间的空间由于太小无法令任何进程使用,这也就是所谓的外部碎片。一种方法是通过紧凑技术(Memory Compaction)解决,通过移动进程在内存中的地址,使得这些外部碎片空间被填满。使用紧凑技术会非常消耗CPU资源,一个2G的CPU每10ns可以处理4byte,因此多一个2G的内存进行一次紧凑可能需要好几秒的CPU时间。还有一些讨巧的方法,比如内存整理软件,原理是申请一块超大的内存,将所有进程置换出原内存,然后再释放这块内存,重新加载进程,使得外部碎片被消除。这也是为什么运行完内存整理会狂读硬盘的原因。

    进程内存是动态变化的

    实际情况下,进程往往会动态增长,因此创建进程时分配的内存就是个问题了,如果分配多了,会产生内部碎片,浪费了内存,而分配少了会造成内存溢出。一个解决方法是在进程创建的时候,比进程实际需要的多分配一点内存空间用于进程的增长。

    一种是直接多分配一点内存空间用于进程在内存中的增长,另一种是将增长区分为数据段和栈(用于存放返回地址和局部变量),如图3所示。

    这里写图片描述

    空间不足的解决方案

    当预留的空间不够满足增长时,操作系统首先会看相邻的内存是否空闲,如果空闲则自动分配,如果不空闲,就将整个进程移到足够容纳增长的空间内存中,如果不存在这样的内存空间,则会将闲置的进程置换出去。

    内存的管理策略

    当允许进程动态增长时,操作系统必须对内存进行更有效的管理,操作系统使用如下两种方法之一来得知内存的使用情况,分别为1位图(bitmap) 和链表
    这里写图片描述

    使用位图,将内存划为多个大小相等的块,比如一个32K的内存1K一块可以划为32块,则需要32位(4字节)来表示其使用情况,使用位图将已经使用的块标为1,未使用的标为0.

    而使用链表,则将内存按使用或未使用分为多个段进行链接。使用链表中的P表示从0-2是进程,H表示从3-4是空闲。

    使用位图表示内存简单明了,但一个问题是当分配内存时必须在内存中搜索大量的连续0的空间,这是十分消耗资源的操作。相比之下,使用链表进行此操作将会更胜一筹。还有一些操作系统会使用双向链表,因为当进程销毁时,邻接的往往是空内存或是另外的进程。使用双向链表使得链表之间的融合变得更加容易。还有,当利用链表管理内存的情况下,创建进程时分配什么样的空闲空间也是个问题。通常情况下有如下几种算法来对进程创建时的空间进行分配。

    • 临近适应算法(Next fit)—从当前位置开始,搜索第一个能满足进程要求的内存空间
    • 最佳- 适应算法(Best fit)—搜索整个链表,找到能满足进程要求最小内存的内存空间
    • 最大适应算法(Wrost fit)—找到当前内存中最大的空闲空间
    • 首次适应算法(First fit) —从链表的第一个开始,找到第一个能满足进程要求的内存空间

    虚拟内存(Virtual Memory)

    虚拟内存是现代操作系统普遍使用的一种技术。前面所讲的抽象满足了多进程的要求,但很多情况下,现有内存无法满足仅仅一个大进程的内存要求(比如很多游戏,都是10G+的级别)。在早期的操作系统曾使用覆盖(overlays)来解决这个问题,将一个程序分为多个块,基本思想是先将块0加入内存,块0执行完后,将块1加入内存。依次往复,这个解决方案最大的问题是需要程序员去程序进行分块,这是一个费时费力让人痛苦不堪的过程。后来这个解决方案的修正版就是虚拟内存。

    虚拟内存的基本思想是,每个进程有用独立的逻辑地址空间,内存被分为大小相等的多个块,称为页(Page).每个页都是一段连续的地址。对于进程来看,逻辑上貌似有很多内存空间,其中一部分对应物理内存上的一块(称为页框,通常页和页框大小相等),还有一些没加载在内存中的对应在硬盘上,如图5所示。

    这里写图片描述
    由图5可以看出,虚拟内存实际上可以比物理内存大。当访问虚拟内存时,会访问MMU(内存管理单元)去匹配对应的物理地址(比如图5的0,1,2),而如果虚拟内存的页并不存在于物理内存中(如图5的3,4),会产生缺页中断,从磁盘中取得缺的页放入内存,如果内存已满,还会根据某种算法将磁盘中的页换出。

    而虚拟内存和物理内存的匹配是通过页表实现,页表存在MMU中,页表中每个项通常为32位,既4byte,除了存储虚拟地址和页框地址之外,还会存储一些标志位,比如是否缺页,是否修改过,写保护等。可以把MMU想象成一个接收虚拟地址项返回物理地址的方法。

    因为页表中每个条目是4字节,现在的32位操作系统虚拟地址空间会是2的32次方,即使每页分为4K,也需要2的20次方*4字节=4M的空间,为每个进程建立一个4M的页表并不明智。因此在页表的概念上进行推广,产生二级页表,二级页表每个对应4M的虚拟地址,而一级页表去索引这些二级页表,因此32位的系统需要1024个二级页表,虽然页表条目没有减少,但内存中可以仅仅存放需要使用的二级页表和一级页表,大大减少了内存的使用。

    分页机制:

    为什么使用两级页表

    假设每个进程都占用了4G的线性地址空间,页表共含1M个表项,每个表项占4个字节,那么每个进程的页表要占据4M的内存空间。为了节省页表占用的空间,我们使用两级页表。每个进程都会被分配一个页目录,但是只有被实际使用页表才会被分配到内存里面。一级页表需要一次分配所有页表空间,两级页表则可以在需要的时候再分配页表空间。

    两级页表结构

    两级表结构的第一级称为页目录,存储在一个4K字节的页面中。页目录表共有1K个表项,每个表项为4个字节,并指向第二级表。线性地址的最高10位(即位31~位32)用来产生第一级的索引,由索引得到的表项中,指定并选择了1K个二级表中的一个表。
    这里写图片描述
    两级表结构的第二级称为页表,也刚好存储在一个4K字节的页面中,包含1K个字节的表项,每个表项包含一个页的物理基地址。第二级页表由线性地址的中间10位(即位21~位12)进行索引,以获得包含页的物理地址的页表项,这个物理地址的高20位与线性地址的低12位形成了最后的物理地址,也就是页转化过程输出的物理地址。

    线性地址到物理地址的转换:
    这里写图片描述
    扩展分页
    从奔腾处理器开始,Intel微处理器引进了扩展分页,它允许页的大小为4MB。
    这里写图片描述
    页面高速缓存:
    这里写图片描述

    页面替换算法

    因为在计算机系统中,读取少量数据硬盘通常需要几毫秒,而内存中仅仅需要几纳秒。一条CPU指令也通常是几纳秒,如果在执行CPU指令时,产生几次缺页中断,那性能可想而知,因此尽量减少从硬盘的读取无疑是大大的提升了性能。而前面知道,物理内存是极其有限的,当虚拟内存所求的页不在物理内存中时,将需要将物理内存中的页替换出去,选择哪些页替换出去就显得尤为重要,如果算法不好将未来需要使用的页替换出去,则以后使用时还需要替换进来,这无疑是降低效率的,让我们来看几种页面替换算法。

    1) 最佳置换算法(Optimal Page Replacement Algorithm)

    最佳置换算法是将未来最久不使用的页替换出去,这听起来很简单,但是无法实现。但是这种算法可以作为衡量其它算法的基准。

    2) 最近不常使用算法(Not Recently Used Replacement Algorithm)

    这种算法给每个页一个标志位,R表示最近被访问过,M表示被修改过。定期对R进行清零。这个算法的思路是首先淘汰那些未被访问过R=0的页,其次是被访问过R=1,未被修改过M=0的页,最后是R=1,M=1的页。

    3) 先进先出页面置换算法(First-In,First-Out Page Replacement Algorithm)

    这种算法的思想是淘汰在内存中最久的页,这种算法的性能接近于随机淘汰。并不好。
    改进型FIFO算法(Second Chance Page Replacement Algorithm)
    这种算法是在FIFO的基础上,为了避免置换出经常使用的页,增加一个标志位R,如果最近使用过将R置1,当页将会淘汰时,如果R为1,则不淘汰页,将R置0.而那些R=0的页将被淘汰时,直接淘汰。这种算法避免了经常被使用的页被淘汰。

    4) 时钟替换算法(Clock Page Replacement Algorithm)

    虽然改进型FIFO算法避免置换出常用的页,但由于需要经常移动页,效率并不高。因此在改进型FIFO算法的基础上,将队列首位相连形成一个环路,当缺页中断产生时,从当前位置开始找R=0的页,而所经过的R=1的页被置0,并不需要移动页。如图6所示。
    最久未使用算法(LRU Page Replacement Algorithm)
    LRU算法的思路是淘汰最近最长未使用的页。这种算法性能比较好,但实现起来比较困难。

    算法 描述:
    最佳置换算法 无法实现,作为测试基准使用
    最近不常使用算法 和LRU性能差不多
    先进先出算法 有可能会置换出经常使用的页
    改进型先进先出算法 和先进先出相比有很大提升
    最久未使用算法 性能非常好,但实现起来比较困难
    时钟置换算法 非常实用的算法

    上面几种算法或多或少有一些局部性原理的思想。局部性原理分为时间和空间上的局部性
    1.时间上,最近被访问的页在不久的将来还会被访问。
    2.空间上,内存中被访问的页周围的页也很可能被访问。

    展开全文
  • 如何实现操作操作日志记录

    万次阅读 2018-12-26 11:16:45
    如何实现操作操作日志记录 为什么要记录操作日志? 项目中的业务需求,需要针对用户的一些业务操作操作记录, 也就是标题中的操场日志记录,最近做的项目也有这个需求, 我也是第一次写,相信有很多开发者也有遇到这个...

    如何实现操作操作日志记录

    为什么要记录操作日志?

    项目中的业务需求,需要针对用户的一些业务操作做操作记录,
    也就是标题中的操场日志记录,最近做的项目也有这个需求,
    我也是第一次写,相信有很多开发者也有遇到这个需求的,所以
    在这里做一个简单的记录,只是提供一个思路参考,代码什么的
    其实是次要的!
    

    业务需求如下,记录用户的重要操作,记录除查询外,如增加,修改,和删除等操作

    在这里插入图片描述

    实现思路

    首先我肯定是用aop了,在后面的使用发现,apo的实现适合大部分
    的单表操作,但是多表更改,例如先加后改是没法实现的,所以我决定
    提供两种实现方式,另外一种使用service函数调用来解决了
    

    表设计

    CREATE TABLE operation_log (
    `id` INT(10) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键id',
    `name` VARCHAR(128) NULL DEFAULT NULL COMMENT '操作业务名',
    `table_name` VARCHAR(16) NULL DEFAULT NULL COMMENT '操作表名',
    `table_id` VARCHAR(16) NULL DEFAULT NULL COMMENT '操作表id',
    `type` VARCHAR(8) NULL DEFAULT NULL COMMENT '操作类型,(添加ADD,删除DELETE,修改UPDATE)' ,
    `operator_id` VARCHAR(16) NULL DEFAULT NULL COMMENT '操作人id',
    `operator_name` VARCHAR(16) NULL DEFAULT NULL COMMENT '操作人名',
    `operation_time` TIMESTAMP NULL DEFAULT NULL COMMENT '操作时间'
    )ENGINE INNODB CHARSET utf8 COMMENT '用户操作日志记录表';
    	
    CREATE TABLE operation_log_detail (
    `id` INT(10) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键id',
    `operation_log_id` INT(10) NULL DEFAULT NULL COMMENT '操作日志id',
    `clm_name` VARCHAR(16) NULL DEFAULT NULL COMMENT '字段名',
    `clm_comment` VARCHAR(128) NULL DEFAULT NULL COMMENT '字段描述',
    `old_string` VARCHAR(128) NULL DEFAULT NULL COMMENT '旧值',
    `new_string` VARCHAR(128) NULL DEFAULT NULL COMMENT '新值'
    )ENGINE INNODB CHARSET utf8 COMMENT '操作日志详情表';
    

    AOP实现

    0目标: 在业务代码函数上使用注解,通过注解实现执行时的环形切面,在切面前,切面后,做数据的变更记录操作
    开始:
    1创建注解
    package com.csp.operationlog.aspect.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import com.csp.operationlog.aspect.enums.OperationType;
    
    /**
     * 用来标注需要进行操作日志的服务函数上
     * @author taoken
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface OperationLog {
    	/** 业务名 */
    	String name();
    	/** 表名 */
    	String table();
    	/** id 在函数的字段名 */
    	int idRef() default -1; 
    	/** 需要记录的字段 */
    	String[] cloum() default {};
    	/** 操作类型 */
    	OperationType type();
    	/** 操作人 id 在函数的字段名*/
    	int operatorIdRef();
    	/** 操作人名称 在函数的字段名 */
    	int operatorNameRef();
    }
    由于使用了一个枚举下面提供一个枚举,作用是分辨操作类型
    package com.csp.operationlog.aspect.enums;
    public enum OperationType {
    	ADD,
    	UPDATE,
    	DELETE;
    
    	public String getType() {
    		if (this.equals(ADD)) {
    			return "ADD";
    		}
    		if (this.equals(UPDATE)) {
    			return "UPDATE";
    		}
    		if (this.equals(DELETE)) {
    			return "DELETE";
    		}
    		return null;
    	};
    }
    2使用注解,只是提前看看使用效果
        @OperationLog(name = "更新账户",type = OperationType.UPDATE,operatorIdRef = 0,operatorNameRef = 1,idRef = 2,table = "account")
        public void updateAccount(String operatorId,String operatorName,Integer accountId){
            Account account = new Account();
            account.setId(accountId);
            account.setAccount(1100);
            accountMapper.updateAccount(account);
        }
    3下面开始实现切面
    package com.csp.operationlog.aspect.aop;
    
    import java.sql.Timestamp;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.support.TransactionCallbackWithoutResult;
    import org.springframework.transaction.support.TransactionTemplate;
    
    import com.csp.operationlog.aspect.enums.OperationType;
    import com.csp.operationlog.dto.ColumnComment;
    import com.csp.operationlog.mapper.OperationLogDetailMapper;
    import com.csp.operationlog.mapper.OperationLogMapper;
    import com.csp.operationlog.model.OperationLog;
    import com.csp.operationlog.model.OperationLogDetail;
    
    @Aspect
    @Component
    public class OperationLogAop {
    	@Autowired
    	private OperationLogMapper operationLogMapper;
    	@Autowired
    	private OperationLogDetailMapper operationLogDetailMapper;
    	@Autowired
    	private TransactionTemplate txTemplate;
    
    	@Around(value = "@annotation(operationlog)")
    	public void logAround(final ProceedingJoinPoint p,final com.csp.operationlog.aspect.annotation.OperationLog operationlog) throws Throwable {
    		OperationType type = operationlog.type();
    		if (OperationType.UPDATE.equals(type)) {
    			update(p, operationlog);
    		}
    		if (OperationType.ADD.equals(type)) {
    			add(p, operationlog);
    		}
    		if (OperationType.DELETE.equals(type)) {
    			delete(p, operationlog);
    		}
    	}
    	
    	public void delete(final ProceedingJoinPoint p,final com.csp.operationlog.aspect.annotation.OperationLog operationlog) {
    		txTemplate.execute(new TransactionCallbackWithoutResult() {
    			@Override
    			protected void doInTransactionWithoutResult(TransactionStatus status) {
    				StringBuilder sql = new StringBuilder();
    				OperationType type = operationlog.type();
    				Object[] args = p.getArgs();
    				String logName = operationlog.name();
    				String logTable = operationlog.table();
    				if (operationlog.idRef()==-1) {
    					throw new RuntimeException();
    				}
    				String id = args[operationlog.idRef()].toString();
    				String[] cloum = operationlog.cloum();
    				String operatorId = args[operationlog.operatorIdRef()].toString();
    				String operatorName = args[operationlog.operatorNameRef()].toString();
    
    				Map<String, Object> columnCommentMap = new HashMap<String, Object>();
    				List<ColumnComment> columnCommentList = operationLogMapper.selectColumnCommentByTable(logTable);
    
    				for (ColumnComment cc : columnCommentList) {
    					columnCommentMap.put(cc.getColumn(), cc.getComment());
    				}
    				if (cloum.length == 0) {
    					Set<String> keySet = columnCommentMap.keySet();
    					List<String> list = new ArrayList<String>();
    					for (String o : keySet) {
    						list.add(o.toString());
    					}
    					cloum = list.toArray(new String[list.size()]);
    				}
    				sql.append("SELECT ");
    				for (int i = 0; i < cloum.length; i++) {
    					if (i == 0) {
    						sql.append("`" + cloum[i] + "` ");
    					} else {
    						sql.append(",`" + cloum[i] + "` ");
    					}
    				}
    				sql.append(" FROM " + logTable + " WHERE id=" + id);
    				Map<String, Object> oldMap = operationLogMapper.selectAnyTalbe(sql.toString());
    
    				try {
    					p.proceed();
    				} catch (Throwable e) {
    					throw new RuntimeException(e);
    				}
    
    				if (oldMap!=null) {
    					OperationLog op = new OperationLog();
    					op.setName(logName);
    					op.setTableName(logTable);
    					op.setTableId(id);
    					op.setType(type.getType());
    					op.setOperatorId(operatorId);
    					op.setOperatorName(operatorName);
    					op.setOperationTime(new Timestamp(System.currentTimeMillis()));
    					operationLogMapper.insertOperationLog(op);
    					List<OperationLogDetail> opds = new ArrayList<OperationLogDetail>();
    					for (String clm : cloum) {
    						Object oldclm = oldMap.get(clm);
    						OperationLogDetail opd = new OperationLogDetail();
    						opd.setOldString(oldclm.toString());
    						opd.setNewString("");
    						opd.setClmName(clm);
    						opd.setClmComment(columnCommentMap.get(clm).toString());
    						opd.setOperationLogId(op.getId());
    						opds.add(opd);
    					}
    					if (!opds.isEmpty()) {
    						operationLogDetailMapper.insertOperationLogDetail(opds);
    					}
    				}
    			}
    		});
    	}
    
    	private void add(final ProceedingJoinPoint p,final com.csp.operationlog.aspect.annotation.OperationLog operationlog) {
    		txTemplate.execute(new TransactionCallbackWithoutResult() {
    			@Override
    			protected void doInTransactionWithoutResult(TransactionStatus status) {
    				StringBuilder sql = new StringBuilder();
    				OperationType type = operationlog.type();
    				Object[] args = p.getArgs();
    				String logName = operationlog.name();
    				String logTable = operationlog.table();
    				String[] cloum = operationlog.cloum();
    				String operatorId = args[operationlog.operatorIdRef()].toString();
    				String operatorName = args[operationlog.operatorNameRef()].toString();
    
    				Map<String, Object> columnCommentMap = new HashMap<String, Object>();
    				List<ColumnComment> columnCommentList = operationLogMapper.selectColumnCommentByTable(logTable);
    				
    				for (ColumnComment cc : columnCommentList) {
    					columnCommentMap.put(cc.getColumn(), cc.getComment());
    				}
    				if (cloum.length == 0) {
    					Set<String> keySet = columnCommentMap.keySet();
    					List<String> list = new ArrayList<String>();
    					for (String o : keySet) {
    						list.add(o.toString());
    					}
    					cloum = list.toArray(new String[list.size()]);
    				}
    				sql.append("SELECT ");
    				for (int i = 0; i < cloum.length; i++) {
    					if (i == 0) {
    						sql.append("`" + cloum[i] + "` ");
    					} else {
    						sql.append(",`" + cloum[i] + "` ");
    					}
    				}
    				sql.append(" FROM " + logTable + " ORDER BY id DESC LIMIT 1");
    				Map<String, Object> oldMap = operationLogMapper.selectAnyTalbe(sql.toString());
    				try {
    					p.proceed();
    				} catch (Throwable e) {
    					throw new RuntimeException(e);
    				}
    				Map<String, Object> newMap = operationLogMapper.selectAnyTalbe(sql.toString());
    				if ((oldMap==null)||(!oldMap.get("id").toString().equals(newMap.get("id").toString()))) {
    					
    					OperationLog op = new OperationLog();
    					op.setName(logName);
    					op.setTableName(logTable);
    					op.setTableId("");
    					op.setType(type.getType());
    					op.setOperatorId(operatorId);
    					op.setOperatorName(operatorName);
    					op.setOperationTime(new Timestamp(System.currentTimeMillis()));
    					operationLogMapper.insertOperationLog(op);
    					List<OperationLogDetail> opds = new ArrayList<OperationLogDetail>();
    					for (String clm : cloum) {
    						Object oldclm = "";
    						Object newclm = newMap.get(clm);
    						OperationLogDetail opd = new OperationLogDetail();
    						opd.setOldString(oldclm.toString());
    						opd.setNewString(newclm.toString());
    						opd.setClmName(clm);
    						opd.setClmComment(columnCommentMap.get(clm).toString());
    						opd.setOperationLogId(op.getId());
    						opds.add(opd);
    					}
    					if (!opds.isEmpty()) {
    						operationLogDetailMapper.insertOperationLogDetail(opds);
    					}
    					
    				}
    			}
    		});
    	}
    
    	public void update(final ProceedingJoinPoint p,final com.csp.operationlog.aspect.annotation.OperationLog operationlog) {
    		txTemplate.execute(new TransactionCallbackWithoutResult() {
    			@Override
    			protected void doInTransactionWithoutResult(TransactionStatus status) {
    				StringBuilder sql = new StringBuilder();
    				OperationType type = operationlog.type();
    				Object[] args = p.getArgs();
    				String logName = operationlog.name();
    				String logTable = operationlog.table();
    				if (operationlog.idRef()==-1) {
    					throw new RuntimeException();
    				}
    				String id = args[operationlog.idRef()].toString();
    				String[] cloum = operationlog.cloum();
    				String operatorId = args[operationlog.operatorIdRef()].toString();
    				String operatorName = args[operationlog.operatorNameRef()].toString();
    
    				Map<String, Object> columnCommentMap = new HashMap<String, Object>();
    				List<ColumnComment> columnCommentList = operationLogMapper.selectColumnCommentByTable(logTable);
    				
    				for (ColumnComment cc : columnCommentList) {
    					columnCommentMap.put(cc.getColumn(), cc.getComment());
    				}
    				if (cloum.length == 0) {
    					Set<String> keySet = columnCommentMap.keySet();
    					List<String> list = new ArrayList<String>();
    					for (String o : keySet) {
    						list.add(o.toString());
    					}
    					cloum = list.toArray(new String[list.size()]);
    				}
    				sql.append("SELECT ");
    				for (int i = 0; i < cloum.length; i++) {
    					if (i == 0) {
    						sql.append("`" + cloum[i] + "` ");
    					} else {
    						sql.append(",`" + cloum[i] + "` ");
    					}
    				}
    				sql.append(" FROM " + logTable + " WHERE id=" + id);
    				Map<String, Object> oldMap = operationLogMapper.selectAnyTalbe(sql.toString());
    
    				try {
    					p.proceed();
    				} catch (Throwable e) {
    					throw new RuntimeException(e);
    				}
    
    				Map<String, Object> newMap = operationLogMapper.selectAnyTalbe(sql.toString());
    				if (oldMap!=null&&newMap!=null) {
    					OperationLog op = new OperationLog();
    					op.setName(logName);
    					op.setTableName(logTable);
    					op.setTableId(id);
    					op.setType(type.getType());
    					op.setOperatorId(operatorId);
    					op.setOperatorName(operatorName);
    					op.setOperationTime(new Timestamp(System.currentTimeMillis()));
    					operationLogMapper.insertOperationLog(op);
    					List<OperationLogDetail> opds = new ArrayList<OperationLogDetail>();
    					for (String clm : cloum) {
    						Object oldclm = oldMap.get(clm);
    						Object newclm = newMap.get(clm);
    						OperationLogDetail opd = new OperationLogDetail();
    						opd.setOldString(oldclm.toString());
    						opd.setNewString(newclm.toString());
    						opd.setClmName(clm);
    						opd.setClmComment(columnCommentMap.get(clm).toString());
    						opd.setOperationLogId(op.getId());
    						opds.add(opd);
    					}
    					if (!opds.isEmpty()) {
    						operationLogDetailMapper.insertOperationLogDetail(opds);
    					}
    				}
    			}
    		});
    	}
    }
    4 可以看出上面实现中用到了表对应的实体类,以及操作数据库的持久层mapper,还有一个数据对象
    我们提供一下,这里简单说明一下,我用的是mybatis,最后提供pom.xml
    package com.csp.operationlog.model;
    import java.sql.Timestamp;
    /**
     * 操作日志主信息模型
     * @author taoken
     */
    public class OperationLog {
    	/** 主键id */
    	private String id;
    	/** 操作业务名 */
    	private String name;
    	/** 操作表名 */
    	private String tableName;
    	/** 操作表id */
    	private String tableId;
    	/** 操作类型,(添加ADD,删除DELETE,修改UPDATE)' */
    	private String type;
    	/** 操作人id */
    	private String operatorId;
    	/** 操作人名 */
    	private String operatorName;
    	/** 操作时间 */
    	private Timestamp operationTime;
    	public String getId() {
    		return id;
    	}
    	public void setId(String id) {
    		this.id = id;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public String getTableName() {
    		return tableName;
    	}
    	public void setTableName(String tableName) {
    		this.tableName = tableName;
    	}
    	public String getTableId() {
    		return tableId;
    	}
    	public void setTableId(String tableId) {
    		this.tableId = tableId;
    	}
    	public String getType() {
    		return type;
    	}
    	public void setType(String type) {
    		this.type = type;
    	}
    	public String getOperatorId() {
    		return operatorId;
    	}
    	public void setOperatorId(String operatorId) {
    		this.operatorId = operatorId;
    	}
    	public String getOperatorName() {
    		return operatorName;
    	}
    	public void setOperatorName(String operatorName) {
    		this.operatorName = operatorName;
    	}
    	public Timestamp getOperationTime() {
    		return operationTime;
    	}
    	public void setOperationTime(Timestamp operationTime) {
    		this.operationTime = operationTime;
    	}
    }
    
    package com.csp.operationlog.model;
    /**
     * 操作日志详情模型
     * @author taoken
     */
    public class OperationLogDetail {
    	/** 主键id */
    	private String id;
    	/** 操作日志id */
    	private String operationLogId;
    	/** 字段名 */
    	private String clmName;
    	/** 字段描述 */
    	private String clmComment;
    	/** 旧值 */
    	private String oldString;
    	/** 新值 */
    	private String newString;
    	
    	public String getId() {
    		return id;
    	}
    	public void setId(String id) {
    		this.id = id;
    	}
    	public String getOperationLogId() {
    		return operationLogId;
    	}
    	public void setOperationLogId(String operationLogId) {
    		this.operationLogId = operationLogId;
    	}
    	public String getClmName() {
    		return clmName;
    	}
    	public void setClmName(String clmName) {
    		this.clmName = clmName;
    	}
    	public String getClmComment() {
    		return clmComment;
    	}
    	public void setClmComment(String clmComment) {
    		this.clmComment = clmComment;
    	}
    	public String getOldString() {
    		return oldString;
    	}
    	public void setOldString(String oldString) {
    		this.oldString = oldString;
    	}
    	public String getNewString() {
    		return newString;
    	}
    	public void setNewString(String newString) {
    		this.newString = newString;
    	}
    }
    
    package com.csp.operationlog.mapper;
    import java.util.List;
    import java.util.Map;
    import org.apache.ibatis.annotations.InsertProvider;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    import com.csp.operationlog.model.OperationLogDetail;
    /**
     * 操作日志详情持久层
     * @author taoken
     */
    @Mapper
    public interface OperationLogDetailMapper {
    	public static class OperationLogDetailMapperProvider{
    		public String insertOperationLogDetailSQL(Map<String,List<OperationLogDetail>> map) {
    			List<OperationLogDetail> ops = map.get("ops");
    			StringBuilder sqlBuid = new StringBuilder("INSERT INTO operation_log_detail (operation_log_id,clm_name,clm_comment,old_string,new_string) VALUES ");
    			for (int i = 0; i < ops.size(); i++) {
    				OperationLogDetail o = ops.get(i);
    				if (i==0) {
    					sqlBuid.append(" ('"+o.getOperationLogId()+"','"+o.getClmName()+"','"+o.getClmComment()+"','"+o.getOldString()+"','"+o.getNewString()+"') ");
    				}else {
    					sqlBuid.append(" ,('"+o.getOperationLogId()+"','"+o.getClmName()+"','"+o.getClmComment()+"','"+o.getOldString()+"','"+o.getNewString()+"') ");
    				}
    			}
    			return sqlBuid.toString();
    		}
    	}
    	//批量添加操作详情
    	@InsertProvider( type=OperationLogDetailMapperProvider.class, method="insertOperationLogDetailSQL" )
    	public void insertOperationLogDetail(@Param("ops")List<OperationLogDetail> operationLogDetails);
    }
    
    package com.csp.operationlog.mapper;
    import java.util.List;
    import java.util.Map;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Options;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    import org.apache.ibatis.annotations.SelectProvider;
    import com.csp.operationlog.dto.ColumnComment;
    import com.csp.operationlog.model.OperationLog;
    
    /**
     * 操作日志持久层
     * @author taoken
     */
    @Mapper
    public interface OperationLogMapper {
    	public static class OperationLogMapperProvider{
    		public String selectAnyTalbeSQL(Map<String,String> map) {
    			return map.get("sql");
    		}
    	}
    	//添加操作日志
    	@Insert("INSERT INTO operation_log (name,table_name,table_id,type,operator_id,operator_name,operation_time) VALUES (#{p.name},#{p.tableName},#{p.tableId},#{p.type},#{p.operatorId},#{p.operatorName},#{p.operationTime});")
    	@Options(useGeneratedKeys=true,keyColumn="id",keyProperty="p.id")
    	public void insertOperationLog(@Param("p")OperationLog operationLog);
    	
    	//查询任意sql
    	@SelectProvider(type=OperationLogMapperProvider.class,method="selectAnyTalbeSQL")
    	public Map<String,Object> selectAnyTalbe(@Param("sql")String sql);
    	
    	//查询任意表的字段与备注
    	@Select("SELECT COLUMN_NAME `column`,column_comment `comment` FROM INFORMATION_SCHEMA.Columns WHERE table_name=#{table}")
    	public List<ColumnComment> selectColumnCommentByTable(@Param("table")String tableName);
    }
    
    package com.csp.operationlog.dto;
    public class ColumnComment {
    	private String column;
    	private String comment;
    	public String getColumn() {
    		return column;
    	}
    	public void setColumn(String column) {
    		this.column = column;
    	}
    	public String getComment() {
    		return comment;
    	}
    	public void setComment(String comment) {
    		this.comment = comment;
    	}
    }
    
    package com.csp.operationlog.util;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    public class HumpUtil {
    	public static final char UNDERLINE = '_';
    	/**
    	 * (userId:user_id)
    	 * @param param
    	 * @return
    	 */
    	public static String camelToUnderline(String param) {
    		if (param == null || "".equals(param.trim())) {
    			return "";
    		}
    		int len = param.length();
    		StringBuilder sb = new StringBuilder(len);
    		for (int i = 0; i < len; i++) {
    			char c = param.charAt(i);
    			if (Character.isUpperCase(c)) {
    				sb.append(UNDERLINE);
    				sb.append(Character.toLowerCase(c));
    			} else {
    				sb.append(c);
    			}
    		}
    		return sb.toString();
    	}
    
    	/**
    	 * (user_id:userId)
    	 * @param param
    	 * @return
    	 */
    	public static String underlineToCamel(String param) {
    		if (param == null || "".equals(param.trim())) {
    			return "";
    		}
    		StringBuilder sb = new StringBuilder(param);
    		Matcher mc = Pattern.compile(UNDERLINE + "").matcher(param);
    		int i = 0;
    		while (mc.find()) {
    			int position = mc.end() - (i++);
    			String.valueOf(Character.toUpperCase(sb.charAt(position)));
    			sb.replace(position - 1, position + 1,
    					sb.substring(position, position + 1).toUpperCase());
    		}
    		return sb.toString();
    	}
    }
    
    package com.csp.operationlog.util;
    import java.beans.PropertyDescriptor;
    import java.util.HashMap;
    import java.util.Map;
    import org.springframework.beans.BeanWrapper;
    import org.springframework.beans.BeanWrapperImpl;
    public class ToMapUtil {
    	@SuppressWarnings({ "unchecked"})
    	public static <T> Map<String, Object> toMap(T bean) {
    		if (bean instanceof Map) {
    			return (Map<String, Object>)bean;
    		}
    		BeanWrapper beanWrapper = new BeanWrapperImpl(bean);
    		Map<String, Object> map = new HashMap<String, Object>();
    		PropertyDescriptor[] pds = beanWrapper.getPropertyDescriptors();
    		for (PropertyDescriptor pd : pds) {
    			if (!"class".equals(pd.getName())) {
    				map.put(pd.getName(),
    						beanWrapper.getPropertyValue(pd.getName()));
    			}
    		}
    		return map;
    	}
    }
    
    pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    
    	<groupId>com.csp.service</groupId>
    	<artifactId>service-operationlog</artifactId>
    	<version>1.0</version>
    	<packaging>jar</packaging>
    
    	<name>service-operationlog</name>
    	<description>service-operationlog project for Spring Boot</description>
    	
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>1.5.9.RELEASE</version>
    		<relativePath />
    	</parent>
    
    	<dependencies>
    		<!-- aop -->
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-aop</artifactId>
    		</dependency>
    		<!-- mybatis -->
    		<dependency>
    			<groupId>org.mybatis.spring.boot</groupId>
    			<artifactId>mybatis-spring-boot-starter</artifactId>
    			<version>1.3.2</version>
    		</dependency>
    		<!-- mysql -->
    		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
    		</dependency>
    		<!--long3-->
    		<dependency>
    			<groupId>org.apache.commons</groupId>
    			<artifactId>commons-lang3</artifactId>
    			<version>3.3.2</version>
    		</dependency>
    	</dependencies>
    </project>
    

    开始测试

    创建springboot的测试demo项目

    1启动相关配置与启动类,这里模拟我们的真实项目
    pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>1.5.9.RELEASE</version>
    		<relativePath/> <!-- lookup parent from repository -->
    	</parent>
    	<groupId>com.example</groupId>
    	<artifactId>demo</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<name>demo</name>
    	<description>Demo project for Spring Boot</description>
    
    	<properties>
    		<java.version>1.8</java.version>
    	</properties>
    
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-test</artifactId>
    			<scope>test</scope>
    		</dependency>
    		<dependency>
    			<groupId>com.csp.service</groupId>
    			<artifactId>service-operationlog</artifactId>
    			<version>1.0</version>
    		</dependency>
    		<!-- mybatis -->
    		<dependency>
    			<groupId>org.mybatis.spring.boot</groupId>
    			<artifactId>mybatis-spring-boot-starter</artifactId>
    			<version>1.3.2</version>
    		</dependency>
    		<!-- mysql -->
    		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
    		</dependency>
    	</dependencies>
    
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    			</plugin>
    		</plugins>
    	</build>
    </project>
    
    package com.example.demo;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ComponentScan;
    @SpringBootApplication
    @ComponentScan(basePackages = {"com.csp.**","com.example.**"})//这里是项目对SpringBean注入的扫描,前面是对operationlog项目中bean的扫描,后面是demo项目的bean的扫描
    @MapperScan({"com.csp.operationlog.mapper","com.example.demo.**.mapper"})//这里com.csp.**是扫描我的operationlog项目的mapper,而com.example.**扫描的是我的demo项目的mapper
    public class DemoApplication {
    	public static void main(String[] args) {
    		SpringApplication.run(DemoApplication.class, args);
    	}
    }
    
    2 我们使用一个账户表,用来测试操作账户,看看是否能够实现日志记录
    表创建:
    CREATE TABLE `account` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
      `account` int(10) DEFAULT NULL COMMENT '账户',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
    
    2 创建对应实体与mapper
    package com.example.demo.domain;
    import java.io.Serializable;
    public class Account implements Cloneable, Serializable {
    	private static final long serialVersionUID = 1L;
    	
    	private Integer id;
        private Integer account;
    
        public Account clone() {
            try {
                Account proto = (Account) super.clone();
                return proto;
            }catch (CloneNotSupportedException e){
                return null;
            }
        }
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public Integer getAccount() {
            return account;
        }
        public void setAccount(Integer account) {
            this.account = account;
        }
    }
    
    package com.example.demo.mapper;
    import com.example.demo.domain.Account;
    import org.apache.ibatis.annotations.*;
    @Mapper
    public interface AccountMapper {
        @Insert("INSERT INTO account (account) VALUES (#{a.account})")
        public void insertAccount(@Param("a") Account a);
        @Update("UPDATE account SET account=#{a.account} WHERE id=#{a.id}")
        public void updateAccount(@Param("a") Account a);
        @Select("DELETE FROM account WHERE id=#{id}")
        public void deleteAccountById(@Param("id") Integer id);
        @Select("SELECT id,account FROM account WHERE id=#{id}")
        public Account selectAccountById(@Param("id")Integer id);
    }
    
    3 后面是具体的业务代码
    package com.example.demo.service;
    import javax.annotation.Resource;
    import org.springframework.stereotype.Service;
    import com.csp.operationlog.aspect.annotation.OperationLog;
    import com.csp.operationlog.aspect.enums.OperationType;
    import com.csp.operationlog.service.OperationLogService;
    import com.example.demo.domain.Account;
    import com.example.demo.mapper.AccountMapper;
    @Service
    public class AccountService {
        @Resource
        AccountMapper accountMapper;
        @Resource
        OperationLogService operationLogService;
    
        //采用注解方式,实现操作日志的记录,适用于大多数简单服务,不涉及代码中多表更改的业务
        @OperationLog(name = "添加账户",type = OperationType.ADD,operatorIdRef = 0,operatorNameRef = 1,table = "account")
        public void addAccount(String operatorId,String operatorName){
            Account account = new Account();
            account.setAccount(181);
            accountMapper.insertAccount(account);
        }
    
        @OperationLog(name = "更新账户",type = OperationType.UPDATE,operatorIdRef = 0,operatorNameRef = 1,idRef = 2,table = "account")
        public void updateAccount(String operatorId,String operatorName,Integer accountId){
            Account account = new Account();
            account.setId(accountId);
            account.setAccount(1100);
            accountMapper.updateAccount(account);
        }
    
        @OperationLog(name = "删除账户",type = OperationType.DELETE,operatorIdRef = 0,operatorNameRef = 1,idRef = 2,table = "account")
        public void deleteAccount(String operatorId,String operatorName,Integer accountId){
            accountMapper.deleteAccountById(accountId);
        }
    
        //使用服务调用的方式,实现操作日志的记录,为了在注解无法解决业务代码中对多个表操作时的应对方法
        public void addAcccount2(){
            Account account=new Account();
            account.setAccount(181);
            accountMapper.insertAccount(account);
            operationLogService.logForAdd("添加账户","account","1","liutao",account);
        }
    
        public void updateAccount2(){
            Account account = accountMapper.selectAccountById(1);
            if (account!=null){
                Account accountOld = account.clone();
                account.setId(1);
                account.setAccount(0);
                accountMapper.updateAccount(account);
                operationLogService.logForUpd("更新账户","account",account.getId().toString(),"1","liutao",accountOld,account);
            }
        }
    
        public void deleteAccount2(){
            Account account = accountMapper.selectAccountById(1);
            if (account!=null){
                accountMapper.deleteAccountById(1);
                operationLogService.logForDel("删除账户","account",account.getId().toString(),"1","liutao",account);
            }
        }
    }
    4 为了方便测试,我们写几个controller进行测试,有页面调用,模拟实际业务操作
    package com.example.demo.controller;
    import javax.annotation.Resource;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import com.example.demo.service.AccountService;
    @Controller
    public class AccountControler {
        @Resource
        private AccountService accountService;
        //采用注解方式
        @RequestMapping("/t1")
        @ResponseBody
        public String addTest(){
            accountService.addAccount("1","liutao");
            return "ok";
        }
        @RequestMapping("/t2")
        @ResponseBody
        public String updateTest(){
            accountService.updateAccount("1","liutao",1);
            return "ok";
        }
        @RequestMapping("/t3")
        @ResponseBody
        public String deleteTest(){
            accountService.deleteAccount("1","liutao",1);
            return "ok";
        }
    
        //采用服务调用
        @RequestMapping("/s1")
        @ResponseBody
        public String addTest2(){
            accountService.addAcccount2();
            return "ok";
        }
        @RequestMapping("/s2")
        @ResponseBody
        public String updateTest2() {
            accountService.updateAccount2();
            return "ok";
        }
        @RequestMapping("/s3")
        @ResponseBody
        public String deleteTest2(){
            accountService.deleteAccount2();
            return "ok";
        }
    }
    
    配置文件application.yml
    spring:
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/demo?characterEncoding=utf-8&useSSL=false
        username: root
        password: root
    

    备注

    有了上面的基本实现,和测试demo,应该可以基本实现日志的记录,
    对于相关细节,我都放到备注中,
    1 apo实现,需要再建立在mysql事务级别在可重复读级别上(一般默认就是哈!)
    2 服务调用实现,可以异步处理啦,如果有为了效率可以再我备注的地方实现即可,自己选择mq实现就行了
    3 只能保证基本实现了,质量不保证,主要是提供思路和实现逻辑,有了思路,自己可以写的
    4 已经有了记录,具体的表查询的业务代码就可以自己查表了哈
    最后项目代码放到github: https://github.com/taopanwoaini/operation-log 自行下载吧,路过几点点个星,谢谢

    展开全文
  • Linux的基本操作——基本介绍

    万次阅读 2019-07-05 16:12:36
    打个广告,帮朋友卖点东西,东西超便宜的哟【衣服鞋子等】,厂家直接出货,绝对低于市场价!!!一般都比市场价便宜3—7折【都是牌子货】,如果您感...单机操作系统 : windows(dos 、ucdos、win95、win98、win20...
  • p/v操作

    千次阅读 2018-03-29 15:33:52
    PV操作原语和信号量sem是计算机操作系统进程和线程同步的核心手段,虽然说起来只有句话,但有几个点非常容易引起模糊。先把PV操作的说明如下: P操作原语: sem 减1 若sem 大于等于0,线程继续执行. 若sem &lt;...
  • 操作系统——PV操作

    万次阅读 2018-10-30 10:58:57
    PV操作概念:操作系统中的一种同步机制,实现对于并发进程中临界区的管理。 并发进程分为两种: ①无交互的并发进程:每个进程是相互独立的,谁也不影响谁,基本不会用到PV操作。 ②有交互的并发进程:多个进程...
  • 原子操作

    千次阅读 2019-08-12 10:13:28
    现代操作系统中,一般都提供了原子操作来实现一些同步操作,所谓原子操作,也就是一个独立而不可分割的操作。在单核环境中,一般的意义下原子操作中线程不会被切换,线程切换要么在原子操作之前,要么在原子操作完成...
  • (1)统计操作 mean() 用来排除缺失的数据 df.mean() (2)在其他轴的同样操作 df.mean(1) (3)带有对象的操作会有不同的维度要去对齐,除此之外,pandas会自动沿着指定维度广播 s = pd.Series([1, 3, 5...
  • 操作基础篇之位操作全面总结

    万次阅读 多人点赞 2013-04-28 14:39:09
    Title: 位操作基础篇之位操作全面总结Author: MoreWindowsE-mail: morewindows@126.comKeyWord: C/C++ 位操作操作技巧 判断奇偶 交换两数 变换符号 求绝对值 位操作压缩空间 筛素数 位操作趣味应用 位操作...
  • 构筑基于物联网操作系统的物联网生态环境

    万次阅读 多人点赞 2014-04-08 21:01:36
    最近跟物联网行业和移动互联网行业的一些资深从业人员做了深入交流,就物联网操作系统的概念和必要性、定位等进行了充分深入的沟通。首先说明的是,物联网操作系统的概念被广泛认同。同时,对物联网操作系统在整个...
  • 如何安装Windows操作系统

    万次阅读 多人点赞 2020-04-28 17:34:14
    博主喜欢以最原始最直接的方式安装系统,并且不喜欢安装Ghost、精简、修改等等各种操作系统,在这里分享一个一直在用,看起来麻烦博主却觉得最适合个人安装操作系统的方式,请往下看,欢迎指正交流分享 一、关于...
  • vim 撤销 回退操作

    万次阅读 多人点赞 2012-07-06 17:18:04
    在vi中按u可以撤销一次操作 u 撤销上一步的操作 Ctrl+r 恢复上一步被撤销的操作 注意: 如果你输入“u”两次,你的文本恢复原样,那应该是你的Vim被配置在Vi兼容模式了。 重做 如果你撤销得太多,你可以...
  • git所有操作命令大全

    万次阅读 2019-01-04 13:13:18
    git的操作流程如下所示: &nbsp;&nbsp; 1. 安装git &nbsp;&nbsp; &nbsp;sudo apt-get install git &nbsp; &nbsp;2. 查看git的安装结果 &nbsp; &nbsp; &nbsp; &nbsp; &...
  • 我看鸿蒙操作系统

    万次阅读 多人点赞 2019-06-19 02:14:26
    华为宣布推出鸿蒙操作系统。其实我觉得能理解,但也蛮无奈的,所谓不得已而为之,google不提供后续版本授权,不提供新的支持,怎么办,硬着头皮也要上。有些自媒体说什么安卓慌...
  • 操作系统与多核处理器

    万次阅读 2017-11-10 18:00:04
    这篇文章解答了我心中的疑问,那就是操作系统会自动调度cpu资源来处理多进程,多线程的并发。    早在上世纪90年代末,就有众多业界人士呼吁用CMP(单芯片多处理器)技术来替代复杂性较高的单线程CPU。IBM、惠普、...
  • 3. Git与TortoiseGit基本操作

    万次阅读 多人点赞 2017-01-15 18:18:55
    本节先简单介绍 git 的使用与操作, 然后再介绍 TortoiseGit 的使用与操作.
  • 操作系统引论 操作系统是配置在计算机硬件上的第一层软件,是对硬件系统的第一次扩充。其主要功能为管理计算机设备,提高他们的利用率和系统吞吐量,并为用户和应用程序提供简单的接口,便于用户使用。OS是现代...
  • 已终止操作”。而大多数情况下该页面甚至很可能看起来已经载入完毕,内容可以完全显示。只是为何还会出现此等提示呢?!更令人懊恼的是点完该提示的确定后,页面就跳转到错误信息显示页面,完全没法正常浏览原网页.....
  • Scala 强大的集合数据操作示例

    万次阅读 多人点赞 2015-05-23 18:43:05
    Scala是数据挖掘算法领域最有力的编程语言之一,语言本身是面向函数,这也符合了数据挖掘算法的常用场景:在原始数据集上应用一系列的变换,语言本身也对集合操作提供了众多强大的函数,本文将以List类型为例子,...
1 2 3 4 5 ... 20
收藏数 9,718,490
精华内容 3,887,396
关键字:

操作