Java實現用戶權限控制功能


         項目中使用數據庫表+視圖+存儲過程+緩存的方式實現用戶權限的控制。通過用戶表、角色表、權限表以及用戶角色表、角色權限表兩個中間表可以得到一個用戶對應的權限有哪些。創建一個視圖將這五個表連接起來,可以查詢出每個用戶對應的權限有哪些。Java層通過調用存儲過程,存儲過程再查詢該視圖,用戶權限可以傳遞到Java層。Java層將用戶權限緩存起來,可以提高效率。本文主要介紹:“涉及到的數據庫表”、“創建連接表的視圖”、“調用視圖的存儲過程”、“Java層加載權限信息”、“Java層判斷用戶是否有權限”、“可以使用Excel管理角色權限信息”。

1、涉及到的數據庫表

涉及5個數據庫表:

(1)用戶表t_staff

DROP TABLE IF EXISTS T_Staff; 	   
CREATE TABLE T_Staff		           			   -- 門店用戶		
    (
	F_ID INT NOT NULL AUTO_INCREMENT,			   -- ID
	F_Name VARCHAR(12) NOT NULL,				   -- 店員名稱
	F_Phone VARCHAR(32) NOT NULL,				   -- 手機號碼
	F_ICID VARCHAR(20) NULL,				   -- 身份證號碼
	F_WeChat VARCHAR(20) NULL,				   -- 微信號
	F_OpenID VARCHAR(100)  NULL,			   -- 微信用戶的唯一標識。
	F_Unionid VARCHAR(100) NULL,				   -- 只有將公眾號綁定到微信開放平台帳號后,才會出現該字段,同一用戶的unionid是唯一的。
	F_pwdEncrypted VARCHAR(0) NULL,				   -- 公鑰加密后的用戶密碼
	F_Salt VARCHAR(32) NOT NULL,			   	   -- 加鹽后的MD5值
	F_PasswordExpireDate DATETIME NULL,	       -- 密碼有效期
	F_IsFirstTimeLogin INT NOT NULL DEFAULT 1,     -- 首次登錄成功?
	F_ShopID INT NOT NULL,						   -- 門店ID
	F_DepartmentID INT NOT NULL,				   -- 所屬的部門。默認為1
	F_Status INT NOT NULL,						   -- 0,在職。1,離職。
	F_CreateDatetime DATETIME NOT NULL DEFAULT now(),    -- 創建時間
	F_UpdateDatetime DATETIME NOT NULL DEFAULT now(),	 -- 修改時間
	
	PRIMARY KEY (F_ID), 
	FOREIGN KEY (F_ShopID) REFERENCES T_Shop(F_ID), 
	FOREIGN KEY (F_DepartmentID) REFERENCES T_Department(F_ID)
--	FOREIGN KEY (F_IDInPOS) REFERENCES T_POS(F_ID) -- ,
--	UNIQUE KEY F_ICID (F_ICID),
--	UNIQUE KEY F_WeChat (F_WeChat)
--	UNIQUE KEY F_Phone (F_Phone)     status為1的phone可以重新創建一個正常使用的staff。
	)	
ENGINE=InnoDB
DEFAULT CHARACTER SET='utf8' COLLATE='utf8_unicode_ci';	

(2)角色表t_role

DROP TABLE IF EXISTS T_Role; 	   
CREATE TABLE T_Role		          		  		   -- 角色		
    (
	F_ID INT NOT NULL AUTO_INCREMENT,			   -- ID
	F_Name VARCHAR(20) NOT NULL,			   -- 角色名稱
	F_CreateDatetime DATETIME NOT NULL DEFAULT now(),    -- 創建時間
	F_UpdateDatetime DATETIME NOT NULL DEFAULT now(),	 -- 修改時間
	
	PRIMARY KEY (F_ID)
	)	
ENGINE=InnoDB
DEFAULT CHARACTER SET='utf8' COLLATE='utf8_unicode_ci';	

(3)權限表t_permission

關鍵字段F_SP

DROP TABLE IF EXISTS T_Permission; 	   
CREATE TABLE T_Permission		          		   -- 操作權限表		
    (
	F_ID INT NOT NULL AUTO_INCREMENT,			   -- ID
	F_SP VARCHAR(80) NOT NULL,					   -- 對應操作的SP
	F_Name VARCHAR(20) NOT NULL,  	   			   -- 操作名稱
	F_Domain VARCHAR(16) NOT NULL ,				       -- 領域名稱
	F_Remark VARCHAR(32) NOT NULL,				   -- 操作備注
	F_CreateDatetime DATETIME NOT NULL DEFAULT now(),    -- 創建時間
	
	PRIMARY KEY (F_ID)
	)	
ENGINE=InnoDB
DEFAULT CHARACTER SET='utf8' COLLATE='utf8_unicode_ci';	

(4)用戶角色表t_staffrole

DROP TABLE IF EXISTS T_StaffRole; 	   
CREATE TABLE T_StaffRole		          		   -- 用戶角色表		
    (
	F_ID INT NOT NULL AUTO_INCREMENT,			   -- ID
	F_StaffID INT NOT NULL,						   -- 用戶ID
	F_RoleID INT NOT NULL,						   -- 角色ID
	
	PRIMARY KEY (F_ID),
	FOREIGN KEY (F_StaffID ) REFERENCES T_Staff(F_ID),
	FOREIGN KEY (F_RoleID ) REFERENCES T_Role(F_ID),
	UNIQUE KEY F_StaffID(F_StaffID)	 -- ... 一個員工有且只有一個角色
	)	
ENGINE=InnoDB
DEFAULT CHARACTER SET='utf8' COLLATE='utf8_unicode_ci';	

 (5)角色權限表t_role_permission

DROP TABLE IF EXISTS T_Role_Permission; 	   
CREATE TABLE T_Role_Permission		          	   -- 角色操作中間表		
    (
	F_ID INT NOT NULL AUTO_INCREMENT,			   -- ID
	F_RoleID INT NOT NULL,			  			   -- 角色ID
	F_PermissionID INT NOT NULL,				   -- 操作ID
	
	PRIMARY KEY (F_ID),
	FOREIGN KEY (F_RoleID ) REFERENCES T_Role(F_ID),
	FOREIGN KEY (F_PermissionID ) REFERENCES T_Permission(F_ID)
	)	
ENGINE=InnoDB
DEFAULT CHARACTER SET='utf8' COLLATE='utf8_unicode_ci';	

2、創建連接表的視圖

根據5張數據庫表創建用戶-角色-權限視圖v_staff_permission:

DROP VIEW IF EXISTS V_Staff_Permission;   			-- 用戶, 角色, 權限視圖
CREATE VIEW V_Staff_Permission
AS
SELECT s.F_ID AS StaffID, s.F_Name AS StaffName,r.F_ID AS RoleID, r.F_Name AS RoleName, p.F_SP, p.F_Name PermissionName, p.F_Remark FROM t_staff s
LEFT JOIN t_staffrole sr ON sr.F_StaffID = s.F_ID
JOIN t_role r ON sr.F_RoleID = r.F_ID
JOIN t_role_permission ap ON ap.F_RoleID = r.F_ID
JOIN t_permission p ON p.F_ID = ap.F_PermissionID
WHERE s.F_Status = 0;

3、調用視圖的存儲過程

存儲過程SP_Staff_RetrieveNPermission調用視圖,Java層調用存儲過程獲取所有的用戶權限:

DROP PROCEDURE IF EXISTS `SP_Staff_RetrieveNPermission`;
CREATE DEFINER=`root`@`localhost` PROCEDURE `SP_Staff_RetrieveNPermission` (
	OUT iErrorCode INT,
	OUT sErrorMsg VARCHAR(64),
	IN iPageIndex INT,
	IN iPageSize INT,
	OUT iTotalRecord INT
	)
BEGIN
	DECLARE recordIndex INT;
	DECLARE EXIT HANDLER FOR SQLEXCEPTION 
	BEGIN
		SET iErrorCode := 3;
		SET sErrorMsg := 'Êý¾Ý¿â´íÎó';
		ROLLBACK;
	END;
	
	START TRANSACTION;
	
		SET iPageIndex = iPageIndex - 1;
		
		SET recordIndex = iPageIndex * iPageSize;
		SET @i = 1;
	
		SELECT @i:=@i+1 AS F_ID, StaffID, StaffName, RoleID, RoleName, F_SP, PermissionName, F_Remark 
		FROM V_Staff_Permission 
		ORDER BY F_ID DESC LIMIT recordIndex,iPageSize;
		
		SELECT count(1) into iTotalRecord
		FROM V_Staff_Permission;
		
		SET iErrorCode := 0;
		SET sErrorMsg := '';
	
	COMMIT;
END;

4、Java層加載權限信息

StaffPermissionCache類用來緩存用戶權限信息,存放到hashtable中:

/** 權限的查詢需要支持根據staffID查詢,也要支持根據staffID和sp名查詢。后者用於檢查特定用戶有無特定權限,操作頻繁,需要快速查詢到 */
@Component("staffPermissionCache")
@Scope("prototype")
public class StaffPermissionCache extends BaseCache {
	private Log logger = LogFactory.getLog(BaseCache.class);

	/** key:staffID+sp名 <br />
	 * value:StaffPermission */
	public Hashtable<String, BaseModel> ht;

	@Resource
	private StaffPermissionBO staffPermissionBO;
……

在程序啟動時,開始加載用戶權限信息:

	/** 加載公共DB中的公司緩存和每個私有DB的普通緩存和同步緩存。 */
	@PostConstruct
	private void load() {
		resolveCurrentEnvAndDomain();
	……

			
staffPermissionCache.load(com.getDbName());

load調用doLoad方法:

	public void load(String dbName) {
		doLoad(dbName);

		register(dbName);
	}

doLoad方法調用staffPermissionBO的retrieveNObject,最后調用了SP_Staff_RetrieveNPermission存儲過程,獲取到所有的用戶和他們對應的權限信息,存放到List<?> ls:

@SuppressWarnings("unchecked")
	protected void doLoad(String dbName) {
		logger.info("加載緩存(" + sCacheName + ")...");
		BaseModel b = getMasterModel(dbName);
		DataSourceContextHolder.setDbName(dbName);
		List<?> ls = getMasterBO().retrieveNObject(BaseBO.SYSTEM, BaseBO.INVALID_CASE_ID, b);
		if (getMasterBO().getLastErrorCode() != EnumErrorCode.EC_NoError) {
			logger.error("加載緩存(" + sCacheName + ")失敗!請重啟服務器!!錯誤信息:" + getMasterBO().printErrorInfo());
			throw new RuntimeException(BaseModel.ERROR_Tag);
		}
		doLoadSlave(ls, dbName);

		logger.info("加載緩存(" + sCacheName + ")成功!");
		writeN((List<BaseModel>) ls); // 將DB的數據緩存進本對象的hashtable中
	}

writeN((List<BaseModel>) ls) 方法把ls寫入到了hashtable容器中:

	/** 清除所有舊的緩存,寫入新的數據 <br />
	 * TODO:目前存在的問題:load()中緩存加載時,並不能確定要加載多少個對象。 */
	public void writeN(List<BaseModel> ls) {
		System.out.println("writeN正在加鎖...." + lock.writeLock().getHoldCount());
		lock.writeLock().lock();
		try {
			doWriteN(ls);
		} catch (Exception e) {
			logger.error("writeN異常:" + e.getMessage());
		}
		lock.writeLock().unlock();
		System.out.println("writeN已經解鎖" + lock.writeLock().getHoldCount());
	}

	protected void doWriteN(List<BaseModel> ls) {
		setCache(ls);
	}

	@Override
	protected void setCache(List<BaseModel> list) {
		listToHashtable(list);
	}

以staffID+Sp為key,bm為Value存放到HashTable容器緩存起來:

	@Override
	protected Hashtable<String, BaseModel> listToHashtable(List<BaseModel> ls) {
		if (ls == null) {
			throw new RuntimeException("參數ls為null!");
		}
		ht = new Hashtable<String, BaseModel>(ls.size()); // 每一次設置緩存,都不會、也不能污染到本對象的副本中的ht數據成員
		for (BaseModel bm : ls) {
			ht.put(String.valueOf(((StaffPermission) bm).getStaffID()) + ((StaffPermission) bm).getSp(), bm);
		}

		return ht;
	}

5、Java層判斷用戶是否有權限

所有用戶的對數據庫的操作都是通過調用存儲過程來實現的,在Action層調用BO層的方法的時候,已經指定了操作對應的存儲過程,如下圖的第二個參數BaseBO.CASE_ResetMyPassword,它對應了一個存儲過程:

Staff staffUpdated = (Staff) staffBO.updateObject(getStaffFromSession(session).getID(), BaseBO.INVALID_CASE_ID, staff);

調用過程如下:

	@SuppressWarnings("static-access")
	protected BaseModel update(int staffID, int iUseCaseID, BaseModel s) {
		checkMapper();

		if (!checkUpdatePermission(staffID, iUseCaseID, s)) {
			lastErrorCode = EnumErrorCode.EC_NoPermission;
			lastErrorMessage = "權限不足";
			return null;
		}
		…… 


	@Override
	protected boolean checkUpdatePermission(int staffID, int iUseCaseID, BaseModel s) {
		switch (iUseCaseID) {
		case CASE_ResetMyPassword:
			return checkStaffPermission(staffID, SP_Staff_ResetPassword);
		case CASE_ResetOtherPassword:
			return checkStaffPermission(staffID, SP_Staff_ResetPassword);
		case CASE_Staff_Update_OpenidAndUnionid:
			return checkStaffPermission(staffID, SP_Staff_Update_OpenidAndUnionid);
		case CASE_Staff_Update_Unsubscribe:
			return checkStaffPermission(staffID, SP_Staff_Update_Unsubscribe);
		default:
			return checkStaffPermission(staffID, SP_Staff_Update);
		}
	}

在checkStaffPermission方法檢查是否有權限:

	/** 檢查1個staff有無權限permission */
	protected boolean checkStaffPermission(int staffID, String permission) {
		if (staffID == SYSTEM) {
			return true;
		}

		StaffPermissionCache spc = (StaffPermissionCache) CacheManager.getCache(DataSourceContextHolder.getDbName(), EnumCacheType.ECT_StaffPermission);
		ErrorInfo ecOut = new ErrorInfo();
		StaffPermission sp = spc.read1(staffID, permission, ecOut);
		if (sp == null) {
			lastErrorMessage = "Staff(" + staffID + ")沒有權限(" + permission + ")";
			logger.debug(lastErrorMessage);
			return false;
		}
		return true;
	}

在read1方法中,根據用戶ID和存儲過程拼接key,查找hashtable是否有這個key,從而判斷用戶是否有權限:

	public StaffPermission read1(int staffID, String permissionName, ErrorInfo ecOut) {
		StaffPermission bmToRead = null;

		lock.readLock().lock();

		String key = String.valueOf(staffID) + permissionName;
		if (ht.containsKey(key)) {
			bmToRead = (StaffPermission) ht.get(key);
			ecOut.setErrorCode(EnumErrorCode.EC_NoError);
		} else {
			bmToRead = null;
			ecOut.setErrorCode(EnumErrorCode.EC_NoSuchData);
			logger.debug("無此權限。key=" + key);
		}

		lock.readLock().unlock();

		return bmToRead;
	}

6、可以使用Excel管理角色權限信息

使用excel可以方便管理角色權限信息:

管理角色的excel表

管理角色的excel表2

后面幾個工作表配置角色的權限:

后面幾個角色


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM