基於SpringBoot的後臺管理系統(資料來源配置、日誌記錄配置及實現(重點))(三)

Guo_1_9發表於2018-03-04

3、Spring Boot 資料來源配置、日誌記錄配置

說明

如果您有幸能看到,請認閱讀以下內容;

  • 1、本專案臨摹自abel533的Guns,他的專案 fork 自 stylefengGuns!開源的世界真好,可以學到很多知識。

  • 2、版權歸原作者所有,自己只是學習使用。跟著大佬的思路,希望自己也能變成大佬。gogogo》。。

  • 3、目前只是一個後臺模組,希望自己技能增強到一定時,可以把stylefeng 的 [Guns]融合進來。

  • 4、note裡面是自己的學習過程,菜鳥寫的,不是大佬寫的。內容都是大佬的。

本來想一步一步的來,但是工具類快把我看暈了。所以我們還是先來看配置類吧,這才是今天的主角。先從資料庫,日誌,快取開始。

想說明的是SpringBoot有四個重要的特性:

  • 起步依賴 :起步依賴其實就是特殊的Maven依賴和Gradle依賴,利用了傳遞依賴解析,把常用庫聚合在一起,組成一個特定功能而制定的依賴。
  • 自動配置 :針對很多Spring應用常見的應用功能,SpringBoot能夠提供相關配置,(底層幫我們做了很多事)
  • 命令列介面:無需傳統專案構建,
  • Actuator:讓你能夠深入執行的SpringBott應用程式,一探究竟。

目前重要的是理解前兩個,只要看見這個spring-boot-starter-Xxx它就屬於起步依賴。會自動匯入想依賴的庫。

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
        <relativePath/>
</parent>
-------------------------------------------------------------------------------
<dependencies>
    <!--spring boot依賴-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
複製程式碼

《SpringBoot實戰》小節 有機會一定要看《Spring實戰》是同一個作者。結合程式碼效果更佳。實戰練習筆記

SpringBoot為Spring應用程式的開發提供了一種激動人心的新方式,框架本身帶來的阻力很小,自動配置消除了傳統Spring應用程式裡很多的樣板配置,Spring的起步依賴讓你能通過庫所提供的功能而非名稱與版本號來指定構建依賴。

資料庫配置

2、接下來,回到我們專案中的配置吧,先從阿里的druid。WebConfig一般是配置的起點。帶有@Configuration註解的就意味著這是一個配置類。還有就是@Bean註解。bean的定義之前在XMl中形式為<bean id ="xx" class="xx.xx.xx" />

在spring boot中新增自己的Servlet、Filter、Listener有兩種方法

  • 通過程式碼註冊:ServletRegistrationBeanFilterRegistrationBeanServletListenerRegistrationBean獲得控制/
  • 註解註冊:在SpringBootApplication上使用@ServletCompanentScan註解後,Servlet、Filter、Listener可以通過@WebServlet、@WebFilter、@WebListener註解自動註冊,無需其他程式碼。
/**
 * web 配置類  還有很多
 */
@Configuration
public class WebConfig {

    /**
     * druidServlet註冊
     */
    @Bean
    public ServletRegistrationBean druidServletRegistration() {
        ServletRegistrationBean registration = new ServletRegistrationBean(new StatViewServlet());
        registration.addUrlMappings("/druid/*");
        return registration;
    }

    /**
     * druid監控 配置URI攔截策略
     */
    @Bean
    public FilterRegistrationBean druidStatFilter(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
        //新增過濾規則.
        filterRegistrationBean.addUrlPatterns("/*");
        //新增不需要忽略的格式資訊.
        filterRegistrationBean.addInitParameter(
                "exclusions","/static/*,*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid,/druid/*");
        //用於session監控頁面的使用者名稱顯示 需要登入後主動將username注入到session裡
        filterRegistrationBean.addInitParameter("principalSessionName","username");
        return filterRegistrationBean;
    }

    /**
     * druid資料庫連線池監控
     */
    @Bean
    public DruidStatInterceptor druidStatInterceptor() {
        return new DruidStatInterceptor();
    }

    /**
     * druid資料庫連線池監控
     */
    @Bean
    public BeanTypeAutoProxyCreator beanTypeAutoProxyCreator() {
        BeanTypeAutoProxyCreator beanTypeAutoProxyCreator = new BeanTypeAutoProxyCreator();
        beanTypeAutoProxyCreator.setTargetBeanType(DruidDataSource.class);
        beanTypeAutoProxyCreator.setInterceptorNames("druidStatInterceptor");
        return beanTypeAutoProxyCreator;
    }

    /**
     * druid 為druidStatPointcut新增攔截
     * @return
     */
    @Bean
    public Advisor druidStatAdvisor() {
        return new DefaultPointcutAdvisor(druidStatPointcut(), druidStatInterceptor());
    }

}
複製程式碼

3、接下來我們在看看資料來源的配置,先摘抄點我之前的筆記。配置H2資料庫和JDBC的。

H2是一個開源的嵌入式資料庫引擎,採用java語言編寫,不受平臺的限制,同時H2提供了一個十分方便的web控制檯用於操作和管理資料庫內容。H2還提供相容模式,可以相容一些主流的資料庫,因此採用H2作為開發期的資料庫非常方便。(資料儲存在記憶體中)。

還需要注意的是DataSource資料來源主要有兩種方式實現:

  • 1、直接資料庫連線,因為每次都要進行三次握手(遠端),所有效能較差。
  • 2、就是採用池化技術,比如上面說的阿里的druid(號稱效能最強大,安全措施,還可以監控),之前最常用的是C3PO,DBCP,需要的時候直接從池子中拿,用完直接還回去。DataSource實現原理是對連線進行快取,從而提高效率,資源的重複利用。
@Configuration
public class DataConfig {

  @Bean
  public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("schema.sql")
            .addScript("my-test-data.sql")
            .build();
  }
-----------------------------------------------------------------------------
  @Bean
  public JdbcOperations jdbcTemplate(DataSource dataSource) {
    return new JdbcTemplate(dataSource);
  }

}
複製程式碼

4、需要補充一點的是:老外很多都在用底層的JDBC技術,因為原生,效率高。jdbcTemplate是Spring對JDBC進一步封裝。命名引數的使用。這種思想理解了嗎?

其實還有一種更絕絕的那就是Spring Date。只要繼承了Repository介面,你就擁有了18個方法,不滿足你的話,還可以自己定義,還有一個就是JpaRepository建議瞭解下。

private static final String SELECT_SPITTLE = "select sp.id, s.id as spitterId, s.username, s.password, s.fullname, s.email, s.updateByEmail, sp.message, sp.postedTime from Spittle sp, Spitter s where sp.spitter = s.id";
private static final String SELECT_SPITTLE_BY_ID = SELECT_SPITTLE + " and sp.id=?";
private static final String SELECT_SPITTLES_BY_SPITTER_ID = SELECT_SPITTLE + " and s.id=? order by sp.postedTime desc";
private static final String SELECT_RECENT_SPITTLES = SELECT_SPITTLE + " order by sp.postedTime desc limit ?";

public List<Spittle> findBySpitterId(long spitterId) {
  return jdbcTemplate.query(SELECT_SPITTLES_BY_SPITTER_ID, new SpittleRowMapper(), spitterId);
}
public List<Spittle> findBySpitterId(long spitterId) {
  return jdbcTemplate.query(SELECT_SPITTLES_BY_SPITTER_ID, new SpittleRowMapper(), spitterId);
}
複製程式碼

5、接下來我們就是配置資料來源了,

本來想錄個Gif,但我軟體出BUG了,有什麼好推薦的麼?為了不佔地方,只放一張。關於日誌的,自行腦補。好想給大家分享我的書籤,太多有用的了。

基於SpringBoot的後臺管理系統(資料來源配置、日誌記錄配置及實現(重點))(三)

  • @Component spring初始化的時候,spring會把所有新增@Component註解的類作為使用自動掃描注入配置路徑下的備選物件,同時在初始化spring的@Autowired
  •  @Controller註解是一個特殊的Component,它允許了實現類可以通過掃描類配置路徑的方式完成自動注入,通常@Controller是結合@RequestMapping註解一起使用的。
  • @ConfigurationProperties 註解用於外部化(externalized)配置,提供 prefix 和 locations 兩個屬性指定屬性檔案的來源和屬性的字首
/**
 * <p>資料庫資料來源配置</p>
 * <p>說明:這個類中包含了許多預設配置,若這些配置符合您的情況,您可以不用管,若不符合,建議不要修改本類,建議直接在"application.yml"中配置即可</p>
 */
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {

    private String url = "jdbc:mysql://127.0.0.1:3306/guns?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull";

    private String username = "root";

    private String password = "632364";

    private String driverClassName = "com.mysql.jdbc.Driver";
    //為了節約地方就不都貼出來了。

    public void config(DruidDataSource dataSource) {

        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);

        dataSource.setDriverClassName(driverClassName);
        dataSource.setInitialSize(initialSize);     //定義初始連線數
        dataSource.setMinIdle(minIdle);             //最小空閒
        dataSource.setMaxActive(maxActive);         //定義最大連線數
        dataSource.setMaxWait(maxWait);             //最長等待時間

        // 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒
        dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);

        // 配置一個連線在池中最小生存的時間,單位是毫秒
        dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        dataSource.setValidationQuery(validationQuery);
        dataSource.setTestWhileIdle(testWhileIdle);
        dataSource.setTestOnBorrow(testOnBorrow);
        dataSource.setTestOnReturn(testOnReturn);

        // 開啟PSCache,並且指定每個連線上PSCache的大小
        dataSource.setPoolPreparedStatements(poolPreparedStatements);
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);

        try {
            dataSource.setFilters(filters);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
複製程式碼

複習AOP

6、還有就是多資料來源,採用切面織入。直接拿自己之前的筆記吧,

在軟體開發中,散佈於應用中多處的功能被稱為橫切關注點(crosscutting concern)。通常來講橫切關注點從概念上是與應用的業務邏輯分離的。但往往是耦合在一起的,把這些橫切關注點與業務邏輯相分離正是面向切面程式設計(AOP)所要解決的問題。

依賴注入(DI)管理我們的應用物件,DI有助於應用物件之間解耦。而AOP可以實現橫切關注點與它們所影響的物件之間的耦合。

橫切關注點可以被模組化為特殊的類,這些類被稱為切面(aspect). 這樣做帶來兩個好處:每個關注點都集中到一個地方,而不是分散到多處程式碼中:其次,服務模組更簡潔,因為它只包含了主要關注點(核心功能)的程式碼。而次要關注的程式碼被移到切面中了。

描述切面的常用術語有:通知(advice)、切點(pointcut)、(連線點)。

通知(advice)

通知定義了切面是什麼以及何時使用。除了描述切面要完成的工作外,通知還解決了何時執行這個工作問題。它應該在某個方法被呼叫之前?之後?之前和之後都呼叫?還是隻在方法丟擲異常時呼叫?

Spring切面可以應用5中型別的通知:

  • 前置通知(Before):在目標方法被呼叫之前呼叫通知功能。
  • 後置通知(After):在目標方法完成之後呼叫通知
  • 返回通知(After-returning):在目標方法成功執行之後呼叫通知
  • 異常通知(After-throwing):在目標方法丟擲異常後呼叫通知
  • 環繞通知(Around):在被通知方法呼叫之前和呼叫之後執行自定義的行為

連線點

我們的應用可能有數以千計的時機應用通知,這些時機被稱為連線點。連線點是在應用執行過程中能夠插入的一個點。這個點可以是呼叫方法時,丟擲異常時,甚至修改一個欄位時。切面可以利用這些點插入到應用的正常流程之中,並新增新的行為。

切點

如果說通知定義了切面的的“什麼”和“何時”,那麼切點定義了“何處”。切點的定義會匹配通知所要織入的一個或多個連線點。

切面

切面是通知和切點的結合。通知和切點通過定義了切面的全部 內容——他是什麼,在什麼時候和在哪裡完成其功能。

引入 引入允許我們向現有的類新增新的方法或者屬性。

織入

織入是把切面應用到目標物件並建立新的代理物件的過程。切面在指定的連線點被織入到目標物件。在目標物件的生命週期裡有多個點可以進行織入:

  • 編譯器:切面在目標類編譯時被織入。Aspect的織入編譯器就是以這種方式織入切面的。

  • 類載入器:切面在目標類載入到JVM時被織入。需要特殊的類載入(Classloader),它可以在目標類被引入之前增強該目標類的位元組碼(CGlib)

  • 執行期:切面在應用執行時的某個時刻被織入。AOP會為目標物件建立一個代理物件 Spring提供了4種型別的AOP支援:

  • 基於代理的經典Spring AOP

  • 純POJO切面

  • @AspectJ註解驅動的切面

  • 注入式AspectJ切面

7、帶著上面的概念,我們在來看下多資料來源的配置,先看一下測試效果:

首先所資料來源作為一個切面,用@Aspect註解,然後定義了切點,只要使用@DataSource註解的方法它就是一個切點,簡單說就是切面切在那個方法上。然後用@Around("cut()")定義了環繞通知,就是呼叫前和呼叫之後執行這個資料來源。還有就是這裡使用了日誌記錄功能,這個主題待會說。

/**
 * 多資料來源的列舉
 */
public interface DSEnum {

	String DATA_SOURCE_GUNS = "dataSourceGuns";		//guns資料來源

	String DATA_SOURCE_BIZ = "dataSourceBiz";			//其他業務的資料來源
}

--------------------------------------------------------------------------------
@Override
@DataSource(name = DSEnum.DATA_SOURCE_BIZ)
public void testBiz() {
    Test test = testMapper.selectByPrimaryKey(1);
    test.setId(22);
    testMapper.insert(test);
}

@Override
@DataSource(name = DSEnum.DATA_SOURCE_GUNS)
public void testGuns() {
    Test test = testMapper.selectByPrimaryKey(1);
    test.setId(33);
    testMapper.insert(test);
}
複製程式碼
/**
 *
 * 多資料來源切換的aop
 */
@Aspect
@Component
@ConditionalOnProperty(prefix = "guns", name = "muti-datasource-open", havingValue = "true")
public class MultiSourceExAop implements Ordered {

	private Logger log = LoggerFactory.getLogger(this.getClass());


	@Pointcut(value = "@annotation(com.guo.guns.common.annotion.DataSource)")
	private void cut() {

	}

	@Around("cut()")
	public Object around(ProceedingJoinPoint point) throws Throwable {

		Signature signature = point.getSignature();
        MethodSignature methodSignature = null;
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalArgumentException("該註解只能用於方法");
        }
        methodSignature = (MethodSignature) signature;

        Object target = point.getTarget();
        Method currentMethod = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());

        DataSource datasource = currentMethod.getAnnotation(DataSource.class);
        if(datasource != null){
			DataSourceContextHolder.setDataSourceType(datasource.name());
			log.debug("設定資料來源為:" + datasource.name());
        }else{
        	DataSourceContextHolder.setDataSourceType(DSEnum.DATA_SOURCE_GUNS);
			log.debug("設定資料來源為:dataSourceCurrent");
        }

        try {
        	return point.proceed();
		} finally {
			log.debug("清空資料來源資訊!");
			DataSourceContextHolder.clearDataSourceType();
		}
	}
}
複製程式碼

這個專案使用了Mybatis作為持久層框架,所以看看他是怎麼配置的。要使用當然要注入了,這裡使用了@Autowired註解。

在Spring中,物件無需自己查詢或建立與其所關聯的其他物件。相反,容器負責把需要相互協作的物件引用賦予各個物件。 一個訂單管理元件需要信用卡認證元件,但它不需要自己建立信用卡認證元件,容器會主動賦予它一個人在元件。Spirng自動滿足bean之間的依賴

@MapperScan:自動掃描mappers,將其關聯到SqlSessionTemplate,並將mappers註冊到spring容器中,以便注入到我們的beans中。

/**
 * MybatisPlus配置
 */
@Configuration
@EnableTransactionManagement(order = 2)//由於引入多資料來源,所以讓spring事務的aop要在多資料來源切換aop的後面
@MapperScan(basePackages = {"com.guo.guns.modular.*.dao", "com.guo.guns.common.persistence.dao"})
public class MybatisPlusConfig {

    @Autowired
    DruidProperties druidProperties;

    @Autowired
    MutiDataSourceProperties mutiDataSourceProperties;
    /**
     * 另一個資料來源
     */
    private DruidDataSource bizDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        druidProperties.config(dataSource);
        mutiDataSourceProperties.config(dataSource);
        return dataSource;
    }
    //省略單資料來源和guns資料來源
    /**
     * 多資料來源連線池配置
     */
    @Bean
    @ConditionalOnProperty(prefix = "guns", name = "muti-datasource-open", havingValue = "true")
    public DynamicDataSource mutiDataSource() {

        DruidDataSource dataSourceGuns = dataSourceGuns();
        DruidDataSource bizDataSource = bizDataSource();

        try {
            dataSourceGuns.init();    //重點
            bizDataSource.init();
        }catch (SQLException sql){
            sql.printStackTrace();
        }

        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        HashMap<Object, Object> hashMap = new HashMap();     //這裡使用了HashMap
        hashMap.put(DSEnum.DATA_SOURCE_GUNS, dataSourceGuns);
        hashMap.put(DSEnum.DATA_SOURCE_BIZ, bizDataSource);
        dynamicDataSource.setTargetDataSources(hashMap);
        dynamicDataSource.setDefaultTargetDataSource(dataSourceGuns);
        return dynamicDataSource;
    }
-----------------------------待會說--------------------------------------------
    /**
     * 資料範圍mybatis外掛
     */
    @Bean
    public DataScopeInterceptor dataScopeInterceptor() {
        return new DataScopeInterceptor();
    }
}
複製程式碼

看程式碼可以讓問題變得更簡單,

攔截器的一個作用就是我們可以攔截某些方法的呼叫,我們可以選擇在這些被攔截的方法執行前後加上某些邏輯,也可以在執行這些被攔截的方法時執行自己的邏輯而不再執行被攔截的方法。

  • @Intercepts用於表明當前的物件是一個Interceptor,
  • @Signature則表明要攔截的介面、方法以及對應的引數型別。

原諒我沒看懂。


/**
 * 資料範圍
 */
public class DataScope {
    /**
     * 限制範圍的欄位名稱
     */
    private String scopeName = "deptid";
    /**
     * 限制範圍的
     */
    private List<Integer> deptIds;
    //......
}
--------------------------------------------------------------------------------
/**
 * 資料範圍的攔截器
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DataScopeInterceptor implements Interceptor {

    /**
     * 獲得真正的處理物件,可能多層代理.
     */
    public static Object realTarget(Object target) {
        if (Proxy.isProxyClass(target.getClass())) {
            MetaObject metaObject = SystemMetaObject.forObject(target);
            return realTarget(metaObject.getValue("h.target"));
        }
        return target;
    }
  //省略一大堆,回來在縷縷。
}
複製程式碼

日誌記錄配置

資料部分就算配置完成了,接下來就是重要的日誌部分。這個很重要,可具體記錄哪個使用者,執行了哪些業務,修改了哪些資料,並且日誌記錄為非同步執行,也是基於JavaConfig.

老樣子,先看工廠

/**
 * 日誌物件建立工廠
 */
public class LogFactory {

    /**
     * 建立操作日誌
     */
    public static OperationLog createOperationLog(LogType logType, Integer userId, String bussinessName, String clazzName, String methodName, String msg, LogSucceed succeed) {
        OperationLog operationLog = new OperationLog();
        operationLog.setLogtype(logType.getMessage());
        operationLog.setLogname(bussinessName);
        operationLog.setUserid(userId);
        operationLog.setClassname(clazzName);
        operationLog.setMethod(methodName);
        operationLog.setCreatetime(new Date());
        operationLog.setSucceed(succeed.getMessage());
        operationLog.setMessage(msg);
        return operationLog;
    }
    //登入日誌省略
}
---------------------------------------------------------------------------------
Timer是一種定時器工具,用來在一個後臺執行緒計劃執行指定任務。它可以計劃執行一個任務一次或反覆多次。
TimerTask一個抽象類,它的子類代表一個可以被Timer計劃的任務。

/**
 * 日誌操作任務建立工廠
 *
 * @author fengshuonan
 * @date 2016年12月6日 下午9:18:27
 */
public class LogTaskFactory {

    private static Logger logger             = LoggerFactory.getLogger(LogManager.class);
    private static LoginLogMapper loginLogMapper     = SpringContextHolder.getBean(LoginLogMapper.class);
    private static OperationLogMapper operationLogMapper = SpringContextHolder.getBean(OperationLogMapper.class);

    public static TimerTask loginLog(final Integer userId, final String ip) {
        return new TimerTask() {
            @Override
            public void run() {
                try {
                    LoginLog loginLog = LogFactory.createLoginLog(LogType.LOGIN, userId, null, ip);
                    loginLogMapper.insert(loginLog);
                } catch (Exception e) {
                    logger.error("建立登入日誌異常!", e);
                }
            }
        };
    }
    //省略很多,慢慢研究程式碼。
}


複製程式碼

日誌管理器

public class LogManager {

    //日誌記錄操作延時
    private final int OPERATE_DELAY_TIME = 10;

    //非同步操作記錄日誌的執行緒池
    private ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(10);

    private LogManager() {
    }

    public static LogManager logManager = new LogManager();

    public static LogManager me() {
        return logManager;
    }

    public void executeLog(TimerTask task) {
        executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
    }
}
--------------------------------------------------------------------------------
/**
 * 被修改的bean臨時存放的地方
 */
@Component
@Scope(scopeName = WebApplicationContext.SCOPE_SESSION)
public class LogObjectHolder implements Serializable{

    private Object object = null;

    public void set(Object obj) {
        this.object = obj;
    }

    public Object get() {
        return object;
    }
    //這個方法是重點。
    public static LogObjectHolder me(){
        LogObjectHolder bean = SpringContextHolder.getBean(LogObjectHolder.class);
        return bean;
    }
}
------------------------註解----------------------------------------------------
/**
 * 標記需要做業務日誌的方法
 */
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface BussinessLog {

    /**
     * 業務的名稱,例如:"修改選單"
     */
    String value() default "";

    /**
     * 被修改的實體的唯一標識,例如:選單實體的唯一標識為"id"
     */
    String key() default "id";

    /**
     * 字典(用於查詢key的中文名稱和欄位的中文名稱)
     */
    String dict() default "SystemDict";
}


複製程式碼

這是一個切面,

@Aspect
@Component
public class LogAop {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Pointcut(value = "@annotation(com.guo.guns.common.annotion.log.BussinessLog)")
    public void cutService() {
    }

    @Around("cutService()")
    public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {

        //先執行業務
        Object result = point.proceed();

        try {
            handle(point);
        } catch (Exception e) {
            log.error("日誌記錄出錯!", e);
        }

        return result;
    }

    private void handle(ProceedingJoinPoint point) throws Exception {

        //獲取攔截的方法名
        Signature sig = point.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("該註解只能用於方法");
        }
        msig = (MethodSignature) sig;
        Object target = point.getTarget();
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        String methodName = currentMethod.getName();

        //如果當前使用者未登入,不做日誌
        ShiroUser user = ShiroKit.getUser();
        if (null == user) {
            return;
        }

        //獲取攔截方法的引數
        String className = point.getTarget().getClass().getName();
        Object[] params = point.getArgs();

        //獲取操作名稱
        BussinessLog annotation = currentMethod.getAnnotation(BussinessLog.class);
        String bussinessName = annotation.value();
        String key = annotation.key();
        String dictClass = annotation.dict();

        StringBuilder sb = new StringBuilder();
        for (Object param : params) {
            sb.append(param);
            sb.append(" & ");
        }

        //如果涉及到修改,比對變化
        String msg;
        if (bussinessName.indexOf("修改") != -1 || bussinessName.indexOf("編輯") != -1) {
            Object obj1 = LogObjectHolder.me().get();
            Map<String, String> obj2 = HttpKit.getRequestParameters();
            msg = Contrast.contrastObj(dictClass, key, obj1, obj2);
        } else {
            Map<String, String> parameters = HttpKit.getRequestParameters();
            AbstractDictMap dictMap = DictMapFactory.createDictMap(dictClass);
            msg = Contrast.parseMutiKey(dictMap,key,parameters);
        }

        LogManager.me().executeLog(LogTaskFactory.bussinessLog(user.getId(), bussinessName, className, methodName, msg));
    }
}

複製程式碼

業務邏輯還需好好研究下。這裡只是走一個過程,用的時候心裡有個印象。真的好想把作者的名字都貼上去,但是地方不允許。這裡感謝要abel533stylefeng,像大佬學習。

今晚就先到這裡吧,下一個是ehcache,前臺的jd外掛和beet模板引擎留到最後看。gogogo。

相關文章