企業權限管理系統

簡介

此項目使用Spring+SpringMVC+MyBatis框架整合,用於企業後臺權限管理。數據庫使用MySQL,前端頁面使用Jsp基於AdminLTE模板進行改寫。html

功能介紹

  • 商品查詢
    • 基於SSM整合基礎上完成商品查詢,實現主頁頁面main.jsp以及商品顯示頁面product-list.jsp頁面的建立。
  • 商品添加
    • 進一步鞏固SSM整合,並完成商品添加功能。實現頁面product-add.jsp的建立。
  • 訂單查詢
    • 訂單的查詢操做,它主要完成簡單的多表查詢操做,查詢訂單時,須要查詢出與訂單關聯的其它表中信息。
  • 訂單分頁查詢
    • 訂單分頁查詢,這裏使用的是mybatis分頁插件PageHelper。
  • 訂單詳情查詢
    • 訂單詳情是用於查詢某一個訂單的詳細信息,主要涉及複雜的多表查詢操做。
  • Spring Security
    • Spring Security是 Spring 項目組中用來提供安全認證服務的框架。此項目中只涉及Spring Security框架的配置及基本的認證與受權操做。
  • 用戶管理
    • 用戶管理中實現了基於Spring Security的用戶登陸、退出操做,以及用戶查詢、添加、詳情等操做,和訂單模塊相似。
  • 角色管理
    • 角色管理主要完成角色查詢、角色添加。角色擁有對應的權限。
  • 資源權限管理
    • 資源權限管理主要完成查詢、添加操做,它的操做與角色管理相似,角色管理以及資源權限管理都是對權限管理的
      補充。
  • 權限關聯與控制
    • 完成用戶角色關聯、角色權限關聯,這兩個操做是爲了後續完成受權操做的基礎。
  • AOP日誌處理
    • 使用Spring AOP切面來完成系統級別的日誌收集。

數據庫介紹

數據庫使用MySQL前端

  • 產品表

  • 訂單表

  • 會員表

  • 旅客表

  • 訂單旅客表

  • 用戶表

  • 角色表

  • 用戶角色表

由 userId 和 roleId 構成,分別爲users表 以及 role表的外鍵,用來關聯用戶與角色的多對多關係java

  • 資源權限表

  • 權限角色表

由 perimissionId 和 roleId 構成,分別爲permission表 以及 role表的外鍵,用來關聯資源權限與角色的多對多關係。mysql

  • 日誌表

SSM整合

Spring環境搭建

  1. 編寫Spring配置文件applicationContext.xml
    • 配置spring建立容器時要掃描的包,開啓註解掃描,管理service和dao。
  2. 使用註解配置業務層

Spring MVC環境搭建

  1. web.xml配置Spring MVC核心控制器
    • 配置初始化參數,用於讀取springmvc的配置文件
    • 配置 servlet 的對象的建立時間點:應用加載時建立。取值只能是非 0 正整數,表示啓動順序
    • 配置SpringMVC編碼過濾器等
  2. 配置Spring MVC配置文件springmvc.xml
    • 配置掃描controller的註解
    • 配置視圖解析器
      • 設置靜態資源不過濾
      • 開啓對SpringMVC註解的支持
  3. 編寫Controller

Spring 與 Spring MVC 整合

在 web.xml 中git

  1. 配置加載類路徑的配置文件,加載 applicationContext.xml 以及 用於權限認證的 spring-security.xml
  2. 配置監聽器

Spring 與 MyBatis 整合

整合思路:將mybatis配置文件(mybatis.xml)中內容配置到spring配置文件中。github

  1. Spring接管mybatis的Session工廠
  • 建立 db.properties 存放數據庫鏈接屬性web

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf8
    jdbc.username=root
    jdbc.password=root
  • 在 applicationContext.xml 中配置鏈接池spring

  • 將 SqlSessionFactory 交給IOC管理sql

  1. 自動掃描全部Mapper接口和文件數據庫

    • 掃描dao接口
  2. 配置Spring事務

    配置Spring的聲明式事務管理

SSM產品操做

主要包括查詢全部產品以及添加產品兩個功能,下面是兩個功能的流程圖。

商品的狀態屬性數據庫存放的爲int數據 productStatus,0表明關閉1表明開啓,實體類中多添加了一個String類型的變量爲productStatusStr,在該變量的getter中對productStatus進行判斷並處理成對應屬性以放到頁面中展現。

出發時間的屬性經過 @DateTimeFormat(pattern="yyyy-MM-dd HH:mm") 註解來轉換格式,並編寫了一個工具類data2String,將時間類轉換成字符串用於頁面展現。

springmvc參數類型轉換三種方式

  1. 實體類中加日期格式化註解

    @DateTimeFormat(pattern="yyyy-MM-dd hh:MM")
    private Date creationTime;
  2. 屬性編輯器

    spring3.1以前 在Controller類中經過@InitBinder完成

    /**
         * 在controller層中加入一段數據綁定代碼
         * @param webDataBinder
         */
        @InitBinder
        public void initBinder(WebDataBinder webDataBinder) throws Exception{
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
            simpleDateFormat.setLenient(false);
            webDataBinder.registerCustomEditor(Date.class , new CustomDateEditor(simpleDateFormat , true));
        }

    **備註:自定義類型轉換器必須實現PropertyEditor接口或者繼承PropertyEditorSupport類 **

    寫一個類 extends propertyEditorSupport(implements PropertyEditor){
         public void setAsText(String text){
             SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy -MM-dd hh:mm");
            Date date = simpleDateFormat.parse(text);
            this.setValue(date);
         }
         public String getAsTest(){
          Date date = (Date)this.getValue(); 
          return this.dateFormat.format(date);
         }
    }
  3. 類型轉換器Converter

(spring 3.0之前使用正常,之後的版本須要使用< mvc:annotation-driven/>註冊使用)使用xml配置實現類型轉換(系統全局轉換器)

(1)註冊conversionservice

<!-- 註冊ConversionService-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="com.ezubo.global.portal.util.StringToDateConverter">
                        <constructor-arg index="0" value="yyyy-MM-dd hh:mm"/>
            </bean>
        </set>
    </property>
</bean>

StringToDateConverter.java的實現

public class StringToDateConverter implements Converter<String,Date> {

    private static final Logger logger = LoggerFactory.getLogger(StringToDateConverter.class);

    private String pattern;

    public StringToDateConverter(String pattern){
        this.pattern = pattern;
    }

    public Date convert(String s) {

        if(StringUtils.isBlank(s)){
            return null;
        }

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
        simpleDateFormat.setLenient(false);
        try{
            return simpleDateFormat.parse(s);
        }catch(ParseException e){
            logger.error("轉換日期異常:"+e.getMessage() , e);
            throw new IllegalArgumentException("轉換日期異常:"+e.getMessage() , e);
        }
    }
}

(2)使用 ConfigurableWebBindingInitializer 註冊conversionService

<!--使用 ConfigurableWebBindingInitializer 註冊conversionService-->
<bean id="webBindingInitializer" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
	<property name="conversionService" ref="conversionService"/>
</bean>

(3)註冊ConfigurableWebBindingInitializer到RequestMappingHandlerAdapter

<!-- 註冊ConfigurableWebBindingInitializer 到RequestMappingHandlerAdapter-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
	<property name="webBindingInitializer" ref="webBindingInitializer"/>
	<!-- 線程安全的訪問session-->
	<property name="synchronizeOnSession" value="true"/>
</bean>

(spring 3.2之後使用正常)使用<mvc:annotation-driven/>註冊conversionService

(1)註冊ConversionService

<!-- 註冊ConversionService-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
	<property name="converters">
		<set>
			<bean class="com.ezubo.global.portal.util.StringToDateConverter">
				<constructor-arg index="0" value="yyyy-MM-dd hh:mm"/>
			</bean>
		</set>
	</property>
</bean>

(2)須要修改springmvc.xml配置文件中的annotation-driven,增長屬性conversion-service指向新增的 conversionService。

<mvc:annotation-driven conversion-service="conversionService">
	<mvc:message-converters register-defaults="true">
		<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
			<property name="supportedMediaTypes" value="text/html;charset=UTF-8"/>
			<!--轉換時設置特性-->
			<property name="features">
				<array>
                    <!--避免默認的循環引用替換-->
                    <ref bean="DisableCircularReferenceDetect"/>
                    <ref bean="WriteMapNullValue"/>
                    <ref bean="WriteNullStringAsEmpty"/>
                    <ref bean="WriteNullNumberAsZero"/>
                </array>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

在此項目中使用的是第一種,比較簡便。

SSM訂單操做

訂單操做的相關功能介紹:

訂單的查詢操做,它主要完成簡單的多表查詢操做,查詢訂單時,須要查詢出與訂單關聯的其它表中信息。下圖爲訂單表及其關聯表關係。

下圖爲查詢全部訂單流程:

下圖爲查詢訂單詳情流程:

PageHelper

使用PageHelper進行分頁查詢,PageHelper是國內很是優秀的一款開源的mybatis分頁插件,它支持基本主流與經常使用的數據庫,例如mysql、oracle、mariaDB、DB二、SQLite、Hsqldb等。

PageHelper使用起來很是簡單,只須要導入依賴而後在spring配置文件中配置後便可使用。

分頁插件參數介紹:

  1. helperDialect :分頁插件會自動檢測當前的數據庫連接,自動選擇合適的分頁方式。 你能夠配置
    helperDialect 屬性來指定分頁插件使用哪一種方言。配置時,可使用下面的縮寫值:
    oracle , mysql , mariadb , sqlite , hsqldb , postgresql , db2 , sqlserver , informix , h2 , sqlserver201
    2 , derby
    特別注意 :使用 SqlServer2012 數據庫時,須要手動指定爲 sqlserver2012 ,不然會使用 SqlServer2005 的
    方式進行分頁。
    你也能夠實現 AbstractHelperDialect ,而後配置該屬性爲實現類的全限定名稱便可使用自定義的實現方
    法。
  2. offsetAsPageNum :默認值爲 false ,該參數對使用 RowBounds 做爲分頁參數時有效。 當該參數設置爲
    true 時,會將 RowBounds 中的 offset 參數當成 pageNum 使用,能夠用頁碼和頁面大小兩個參數進行分
    頁。
  3. rowBoundsWithCount :默認值爲 false ,該參數對使用 RowBounds 做爲分頁參數時有效。 當該參數設置
    爲 true 時,使用 RowBounds 分頁會進行 count 查詢。
  4. pageSizeZero :默認值爲 false ,當該參數設置爲 true 時,若是 pageSize=0 或者 RowBounds.limit =
    0 就會查詢出所有的結果(至關於沒有執行分頁查詢,可是返回結果仍然是 Page 類型)。
  5. reasonable :分頁合理化參數,默認值爲 false 。當該參數設置爲 true 時, pageNum<=0 時會查詢第一
    頁, pageNum>pages (超過總數時),會查詢最後一頁。默認 false 時,直接根據參數進行查詢。
  6. params :爲了支持 startPage(Object params) 方法,增長了該參數來配置參數映射,用於從對象中根據屬
    性名取值, 能夠配置 pageNum,pageSize,count,pageSizeZero,reasonable ,不配置映射的用默認值, 默認
    值爲pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero
  7. supportMethodsArguments :支持經過 Mapper 接口參數來傳遞分頁參數,默認值 false ,分頁插件會從查
    詢方法的參數值中,自動根據上面 params 配置的字段中取值,查找到合適的值時就會自動分頁。 使用方法
    能夠參考測試代碼中的 com.github.pagehelper.test.basic 包下的 ArgumentsMapTest 和
    ArgumentsObjTest 。
  8. autoRuntimeDialect :默認值爲 false 。設置爲 true 時,容許在運行時根據多數據源自動識別對應方言
    的分頁 (不支持自動選擇 sqlserver2012 ,只能使用 sqlserver ),用法和注意事項參考下面的場景五。
  9. closeConn:默認值爲 true 。當使用運行時動態數據源或沒有設置 helperDialect 屬性自動獲取數據庫類
    型時,會自動獲取一個數據庫鏈接, 經過該屬性來設置是否關閉獲取的這個鏈接,默認 true 關閉,設置爲
    false 後,不會關閉獲取的鏈接,這個參數的設置要根據本身選擇的數據源來決定。

基本使用有6種方式,最經常使用的有兩種:

  1. RowBounds方式的調用

List<Country> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(1, 10));

使用這種調用方式時,可使用RowBounds參數進行分頁,這種方式侵入性最小,經過RowBounds方式調用只是使用這個參數並無增長其餘任何內容。分頁插件檢測到使用了RowBounds參數時,就會對該查詢進行物理分頁。

關於這種方式的調用,有兩個特殊的參數是針對 RowBounds 的,具體參考上面的分頁插件參數介紹。

注:不僅有命名空間方式能夠用RowBounds,使用接口的時候也能夠增長RowBounds參數,例如:

//這種狀況下也會進行物理分頁查詢
List<Country> selectAll(RowBounds rowBounds);

注意: 因爲默認狀況下的 RowBounds 沒法獲取查詢總數,分頁插件提供了一個繼承自 RowBounds
PageRowBounds ,這個對象中增長了 total 屬性,執行分頁查詢後,能夠從該屬性獲得查詢總數。

  1. PageHelper.startPage靜態方法調用

這種方式在你須要進行分頁的 MyBatis 查詢方法前調用 PageHelper.startPage 靜態方法便可,緊
跟在這個方法後的第一個MyBatis 查詢方法會被進行分頁。

例如:

//獲取第1頁,10條內容,默認查詢總數count
PageHelper.startPage(1, 10);
//緊跟着的第一個select方法會被分頁
List<Country> list = countryMapper.selectIf(1);

使用步驟總結以下:

SSM權限操做

主要涉及用戶、角色、資源權限三個模塊的功能,下圖爲三表的關係。

Spring Security

Spring Security 的前身是 Acegi Security ,是 Spring 項目組中用來提供安全認證服務的框架。

Spring Security 爲基於J2EE企業應用軟件提供了全面安全服務。包括兩個主要操做:

  • 「認證」,是爲用戶創建一個他所聲明的主體。主體通常式指用戶,設備或能夠在你係統中執行動做的其餘系
    統。
  • 「受權」指的是一個用戶可否在你的應用中執行某個操做,在到達受權判斷以前,身份的主題已經由身份驗證
    過程創建了。

快速入門步驟以下:

用戶管理

用戶登陸

使用數據庫完成springSecurity用戶登陸流程:

spring security的配置

<security:authentication-manager>
	<security:authentication-provider user-service-ref="userService">
		<!-- 配置加密的方式
		<security:password-encoder ref="passwordEncoder"/>
		-->
	</security:authentication-provider>
</security:authentication-manager>

Service

@Service("userService")
@Transactional
public class UserServiceImpl implements IUserService {
    
	@Autowired
	private IUserDao userDao;
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		UserInfo userInfo = userDao.findByUsername(username);
        List<Role> roles = userInfo.getRoles();
        List<SimpleGrantedAuthority> authoritys = getAuthority(roles);
        User user = new User(userInfo.getUsername(), "{noop}" + userInfo.getPassword(),
        userInfo.getStatus() == 0 ? false : true, true, true, true, authoritys);
        return user;
    }
    private List<SimpleGrantedAuthority> getAuthority(List<Role> roles) {
        List<SimpleGrantedAuthority> authoritys = new ArrayList();
        for (Role role : roles) {
        	authoritys.add(new SimpleGrantedAuthority(role.getRoleName()));
        }
        return authoritys;
    }
}

這裏從userInfo中 getPassword 前面須要加上"{noop}"是由於數據庫中的密碼還未進行加密,後續在添加用戶中進行加密處理後便可刪除。

Dao

public interface IUserDao {
    @Select("select * from user where id=#{id}")
    public UserInfo findById(Long id) throws Exception;
    
    @Select("select * from user where username=#{username}")
    @Results({
        @Result(id = true, property = "id", column = "id"),
        @Result(column = "username", property = "username"),
        @Result(column = "email", property = "email"),
        @Result(column = "password", property = "password"),
        @Result(column = "phoneNum", property = "phoneNum"),
        @Result(column = "status", property = "status"),
        @Result(column = "id", property = "roles", javaType = List.class, many =
    		@Many(select = "com.itheima.ssm.dao.IRoleDao.findRoleByUserId")) })
    public UserInfo findByUsername(String username);
}

用戶退出

使用spring security完成用戶退出,很是簡單

  • 配置
<security:logout invalidate-session="true" logout-url="/logout.do" logout-success-
url="/login.jsp" />
  • 頁面中
<a href="${pageContext.request.contextPath}/logout.do"
		class="btn btn-default btn-flat">註銷</a>

用戶查詢

用戶添加

  1. 添加完成後經過redirect 重定向跳轉到查詢全部用戶。
  2. 前期數據庫存的用戶密碼沒有加密,如今添加用戶時,咱們須要對用戶密碼進行加密。
<!-- 配置加密類 -->
<bean id="passwordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

用戶詳情

Dao

@Select("select * from user where id=#{id}")
@Results({ @Result(id = true, property = "id", column = "id"), @Result(column = "username",
property = "username"),
		@Result(column = "email", property = "email"), @Result(column ="password", property = "password"),
		@Result(column = "phoneNum", property = "phoneNum"), @Result(column ="status", property = "status"),
		@Result(column = "id", property = "roles", javaType = List.class, many =
@Many(select = "com.itheima.ssm.dao.IRoleDao.findRoleByUserId")) })
public UserInfo findById(Long id) throws Exception;

@Select("select * from role where id in( select roleId from user_role where userId=#{userId})")
@Results(
			{
				@Result(id=true,column="id",property="id"),
                @Result(column="roleName",property="roleName"),
                @Result(column="roleDesc",property="roleDesc"),					@Result(column="id",property="permissions",javaType=List.class,many=@Many(select="com.itheima.ssm
                .dao.IPermissionDao.findByRoleId"))
})
public List<Role> findRoleByUserId(Long userId);

咱們須要將用戶的全部角色及權限查詢出來因此須要調用IRoleDao中的findRoleByUserId,而在IRoleDao中須要調用IPermissionDao的findByRoleId

@Select("select * from permission where id in (select permissionId from role_permission where
roleId=#{roleId})")
public List<Permission> findByRoleId(Long roleId);

角色管理

角色查詢

角色添加

資源權限管理

資源權限查詢以及添加的流程和角色管理模塊的同樣(參考上圖),只是針對的表不一樣。

權限的關聯與控制

用戶角色關聯

用戶與角色之間是多對多關係,咱們要創建它們之間的關係,只須要在中間表user_role插入數據便可。

流程以下:

角色權限關聯

角色與權限之間是多對多關係,咱們要創建它們之間的關係,只須要在中間表role_permission插入數據便可。

流程和用戶角色關聯相同,參考上圖。

服務器端方法級權限控制

在服務器端咱們能夠經過Spring security提供的註解對方法來進行權限控制。Spring Security在方法的權限控制上支持三種類型的註解,JSR-250註解、@Secured註解和支持表達式的註解,這三種註解默認都是沒有啓用的,須要單獨經過global-method-security元素的對應屬性進行啓用。

開啓註解使用

  • 配置文件
    <security:global-method-security jsr250-annotations="enabled"/>
    <security:global-method-security secured-annotations="enabled"/>
    <security:global-method-security pre-post-annotations="disabled"/>
  • 註解開啓
    @EnableGlobalMethodSecurity :Spring Security默認是禁用註解的,要想開啓註解,須要在繼承WebSecurityConfigurerAdapter的類上加@EnableGlobalMethodSecurity註解,並在該類中將AuthenticationManager定義爲Bean。

JSR-250註解

  • @RolesAllowed表示訪問對應方法時所應該具備的角色

示例:
@RolesAllowed({"USER", "ADMIN"}) 該方法只要具備"USER", "ADMIN"任意一種權限就能夠訪問。這裏能夠省略前綴ROLE_,實際的權限多是ROLE_ADMIN

  • @PermitAll表示容許全部的角色進行訪問,也就是說不進行權限控制
  • @DenyAll是和PermitAll相反的,表示不管什麼角色都不能訪問

支持表達式的註解

  • @PreAuthorize 在方法調用以前,基於表達式的計算結果來限制對方法的訪問
示例:
@PreAuthorize("#userId == authentication.principal.userId or hasAuthority(‘ADMIN’)")
void changePassword(@P("userId") long userId ){ }
這裏表示在changePassword方法執行以前,判斷方法參數userId的值是否等於principal中保存的當前用戶的userId,或者當前用戶是否具備ROLE_ADMIN權限,兩種符合其一,就能夠訪問該方法。
  • @PostAuthorize 容許方法調用,可是若是表達式計算結果爲false,將拋出一個安全性異常
示例:
@PostAuthorize
User getUser("returnObject.userId == authentication.principal.userId or
hasPermission(returnObject, 'ADMIN')");
  • @PostFilter 容許方法調用,但必須按照表達式來過濾方法的結果
  • @PreFilter 容許方法調用,但必須在進入方法以前過濾輸入值

@Secured註解

  • @Secured註解標註的方法進行權限控制的支持,其值默認爲disabled。
示例:
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);
@Secured("ROLE_TELLER")

頁面端標籤控制權限

在jsp頁面中咱們可使用spring security提供的權限標籤來進行權限控制

導入:

  • maven導入
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>version</version>
</dependency>
  • 頁面導入
<%@taglib uri="http://www.springframework.org/security/tags" prefix="security"%>

經常使用標籤

在jsp中咱們可使用如下三種標籤,其中authentication表明的是當前認證對象,能夠獲取當前認證對象信息,例如用戶名。其它兩個標籤咱們能夠用於權限控制

authentication
<security:authentication property="" htmlEscape="" scope="" var=""/>
  • property: 只容許指定Authentication所擁有的屬性,能夠進行屬性的級聯獲取,如「principle.username」,不容許直接經過方法進行調用
  • htmlEscape:表示是否須要將html進行轉義。默認爲true
  • scope:與var屬性一塊兒使用,用於指定存放獲取的結果的屬性名的做用範圍,默認我pageContext。Jsp中擁有的做用範圍都進行進行指定
  • var: 用於指定一個屬性名,這樣當獲取到了authentication的相關信息後會將其以var指定的屬性名進行存放,默認是存放在pageConext中
authorize

authorize是用來判斷普通權限的,經過判斷用戶是否具備對應的權限而控制其所包含內容的顯示

<security:authorize access="" method="" url="" var=""></security:authorize>
  • access: 須要使用表達式來判斷權限,當表達式的返回結果爲true時表示擁有對應的權限
  • method:method屬性是配合url屬性一塊兒使用的,表示用戶應當具備指定url指定method訪問的權限,method的默認值爲GET,可選值爲http請求的7種方法
  • url:url表示若是用戶擁有訪問指定url的權限即表示能夠顯示authorize標籤包含的內容
  • var:用於指定將權限鑑定的結果存放在pageContext的哪一個屬性中
accesscontrollist

accesscontrollist標籤是用於鑑定ACL權限的。其一共定義了三個屬性:hasPermission、domainObject和var,
其中前兩個是必須指定的

<security:accesscontrollist hasPermission="" domainObject="" var=""></security:accesscontrollist>
  • hasPermission:hasPermission屬性用於指定以逗號分隔的權限列表
  • domainObject:domainObject用於指定對應的域對象
  • var:var則是用以將鑑定的結果以指定的屬性名存入pageContext中,以供同一頁面的其它地方使用

SSMAOP日誌

基於AOP來獲取每一次操做的訪問時間、操做者用戶名、訪問ip、訪問資源url、執行市場以及訪問方法存入到數據庫日誌表sysLog中,並展現到頁面中。

流程以下:

建立切面類處理日誌

@Component
@Aspect
public class LogAop {
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private ISysLogService sysLogService;
    
    private Date startTime; // 訪問時間
    private Class executionClass;// 訪問的類
    private Method executionMethod; // 訪問的方法
    // 主要獲取訪問時間、訪問的類、訪問的方法
    
    @Before("execution(* com.itheima.ssm.controller.*.*(..))")
    public void doBefore(JoinPoint jp) throws NoSuchMethodException, SecurityException {
        startTime = new Date(); // 訪問時間
        // 獲取訪問的類
        executionClass = jp.getTarget().getClass();
        // 獲取訪問的方法
        String methodName = jp.getSignature().getName();// 獲取訪問的方法的名稱
        Object[] args = jp.getArgs();// 獲取訪問的方法的參數
        if (args == null || args.length == 0) {// 無參數
        	executionMethod = executionClass.getMethod(methodName); // 只能獲取無參數方法
        } else {
        	// 有參數,就將args中全部元素遍歷,獲取對應的Class,裝入到一個Class[]
        	Class[] classArgs = new Class[args.length];
            for (int i = 0; i < args.length; i++) {
				classArgs[i] = args[i].getClass();
			}
			executionMethod = executionClass.getMethod(methodName, classArgs);// 獲取有參數方法
		}
	}
    // 主要獲取日誌中其它信息,時長、ip、url...
    @After("execution(* com.itheima.ssm.controller.*.*(..))")
    public void doAfter(JoinPoint jp) throws Exception {
        // 獲取類上的@RequestMapping對象
        if (executionClass != SysLogController.class) {
        	RequestMapping classAnnotation = (RequestMapping)executionClass.getAnnotation(RequestMapping.class);
            if (classAnnotation != null) {
                // 獲取方法上的@RequestMapping對象
                RequestMapping methodAnnotation = executionMethod.getAnnotation(RequestMapping.class);
                if (methodAnnotation != null) {
                    String url = ""; // 它的值應該是類上的@RequestMapping的value+方法上的@RequestMapping的value
                    url = classAnnotation.value()[0] + methodAnnotation.value()[0];
                    SysLog sysLog = new SysLog();
                    // 獲取訪問時長
                    Long executionTime = new Date().getTime() - startTime.getTime();
                    // 將sysLog對象屬性封裝
                    sysLog.setExecutionTime(executionTime);
                    sysLog.setUrl(url);
                    // 獲取ip
                    String ip = request.getRemoteAddr();
                    sysLog.setIp(ip);
                    // 能夠經過securityContext獲取,也能夠從request.getSession中獲取
                    SecurityContext context = SecurityContextHolder.getContext(); //request.getSession().getAttribute("SPRING_SECURITY_CONTEXT")
                    String username = ((User)
                    (context.getAuthentication().getPrincipal())).getUsername();
                    sysLog.setUsername(username);
                    sysLog.setMethod("[類名]" + executionClass.getName() + "[方法名]" +
                    executionMethod.getName());
                    sysLog.setVisitTime(startTime);
                    // 調用Service,調用dao將sysLog insert數據庫
                    sysLogService.save(sysLog);
                }
            }
        }
    }
}

在切面類中咱們須要獲取登陸用戶的username,還須要獲取ip地址,咱們怎麼處理?

  • username獲取

    SecurityContextHolder獲取

  • ip地址獲取

    ip地址的獲取咱們能夠經過request.getRemoteAddr()方法獲取到。
    在Spring中能夠經過RequestContextListener來獲取request或session對象。

SysLogController

@RequestMapping("/sysLog")
@Controller
public class SysLogController {
    @Autowired
    private ISysLogService sysLogService;
    @RequestMapping("/findAll.do")
    public ModelAndView findAll() throws Exception {
        ModelAndView mv = new ModelAndView();
        List<SysLog> sysLogs = sysLogService.findAll();
        mv.addObject("sysLogs", sysLogs);
        mv.setViewName("syslog-list");
        return mv;
	}
}

Service

@Service
@Transactional
public class SysLogServiceImpl implements ISysLogService {
    @Autowired
    private ISysLogDao sysLogDao;
    @Override
    public void save(SysLog log) throws Exception {
    	sysLogDao.save(log);
    }
    @Override
    public List<SysLog> findAll() throws Exception {
    	return sysLogDao.findAll();
    }
}

Dao

public interface ISysLogDao {
    @Select("select * from syslog")
    @Results({
        @Result(id=true,column="id",property="id"),
        @Result(column="visitTime",property="visitTime"),
        @Result(column="ip",property="ip"),
        @Result(column="url",property="url"),
        @Result(column="executionTime",property="executionTime"),
        @Result(column="method",property="method"),
        @Result(column="username",property="username")
    })
	public List<SysLog> findAll() throws Exception;
	@Insert("insert into syslog(visitTime,username,ip,url,executionTime,method) values(#{visitTime},#{username},#{ip},#{url},#{executionTime},#{method})")
	public void save(SysLog log) throws Exception;
}
相關文章
相關標籤/搜索