2025年10月5日 星期日

以屬性為條件,只要符合任何一條即成立的規則條件設計分享

 跟上次分享的文章:

以屬性為條件且屬性有階層關係的的規則可擴充權限設計分享

類似,

不過這次的需求比較簡單,是做出類似 URL Redirect 的規則設計,
一樣是有多個可能會日後動態增加的屬性,
只要符合一個條件即符合規則。

案例:

  1. 公司會收到客服詢問案件 (暫簡稱 Case),詢問案件會被標記上各種屬性,例如 : 語言 (lang)、是問哪個產品的問題 (productId) 等。
  2. 不同的屬性值可以設成一個 assign 規則,用來決定把客服詢問案件指派 (assign) 給哪個負責人員 (staffId)。
  3. 每個規則可以 assign 給不同的 staffId,但只會有一個規則被選用,規則可以按某個順序排,先符合的規則先被選用。

直接上實作部份範例,Dabase 採用 PostgreSql、程式部份採用 Java:

首先是 Database Table 設定的部份:

--設定 Case assign 規則及其規則要 assign 給哪個 staffId
CREATE TABLE case_assign_rule (
	id SERIAL PRIMARY KEY,
	assignee_staff_id INT	
);

--設定 Case 屬性,可用來判斷要選取哪個 Case assign rule 
CREATE TABLE case_assign_rule_type (
	id SERIAL PRIMARY KEY,
	label VARCHAR NOT NULL
);

--設定 Case assign rule,這裡沒有設定優先級,暫且先用 case_assign_rule_id 來當作優先級,
--越小的優先。
--Note:
--一個規則為把同一個 case_assign_rule_id 的多個規則用 AND 的方式結合在一起成為一個規則
--例如: case_assign_rule_id = 1 assign 給 staffId = 111 有兩條規則: 
--"productid 等於 100" 和 "langId 不等於 3"
-- 等同於 case_assign_rule_id = 1: "productid 等於 100" 且 "langId 不等於 3" 時 assign 給 staffId = 111
CREATE TABLE case_assign_rule_assignment_condition (
	case_assign_rule_id INT,
	case_assign_rule_type_id INT,
	is_equal_case_assign_rule_type_value BOOLEAN,
	case_assign_rule_type_value INT,
	PRIMARY KEY(case_assign_rule_id, case_assign_rule_type_id, is_equal_case_assign_rule_type_value, case_assign_rule_type_value)
);

如果想要設定兩條規則 (以下用 "==" 代表 "等於"、用 "!=" 代表 "不等於"):

  1. productId = 100 且 langId != 3 時, assign 給 staffId = 111。
  2. productId = 100 時 assign 給 staffId = 123。
這樣的話當 Case 的 productId = 100 且 langId != 3 時就會選用第一個 assign rule 被 assign 給 staffId = 111。
而如果 Case 的 product = 100 但  langId == 3 時就會選用第二個 rule 被 assign 給 staffId = 123。

我們可以這樣設定:

--設定 Case assign rule condition
--設定 productId = 100 且 langId != 3 時, assign 給 staffId = 111。
INSERT INTO case_assign_rule_assignment_condition(case_assign_rule_id,
	                                          case_assign_rule_type_id,
	                                          is_equal_case_assign_rule_type_value,
	                                          case_assign_rule_type_value)
            VALUES(1, 1, true, 100);
INSERT INTO case_assign_rule_assignment_condition(case_assign_rule_id,
	                                          case_assign_rule_type_id,
	                                          is_equal_case_assign_rule_type_value,
	                                          case_assign_rule_type_value)
            VALUES(1, 2, false, 3);

--設定 productId = 100 時 assign 給 staffId = 123。
INSERT INTO case_assign_rule_assignment_condition(case_assign_rule_id,
	                                          case_assign_rule_type_id,
	                                          is_equal_case_assign_rule_type_value,
	                                          case_assign_rule_type_value)
            VALUES(2, 1, true, 100);


再來是 Java 程式判斷的部份:

CASE_ASSIGN_RULE_TYPE.java:

package constant;

public enum CASE_ASSIGN_RULE_TYPE {
	LANG_ID(1), PRODUCT_ID(2);
	
	private int id;
	
	private CASE_ASSIGN_RULE_TYPE(int id) {
        this.id = id;
    }
	
	public static CASE_ASSIGN_RULE_TYPE getCaseAssignRuleTypeById(int id, CASE_ASSIGN_RULE_TYPE defaultEnum) {
        for (CASE_ASSIGN_RULE_TYPE value : values()) {
            if (value.id == id) {
            	return value;
            }
        }
        
        return defaultEnum;
	}
}

CaseAssignRuleConditionBean.java:

package bean;

import constant.CASE_ASSIGN_RULE_ASSIGNEE_TYPE;

public class CaseAssignRuleConditionBean {
	private int caseAssignRuleId;
	private CASE_ASSIGN_RULE_ASSIGNEE_TYPE caseAssignRuleAssigneeType;
	private boolean isEqualCaseAssignRuleTypeValue;
	private int caseAssigneeTypeValue;

	public int getcaseAssignRuleId() {
		return caseAssignRuleId;
	}

	public void setcaseAssignRuleId(int caseAssignRuleId) {
		this.caseAssignRuleId = caseAssignRuleId;
	}

	public CASE_ASSIGN_RULE_ASSIGNEE_TYPE getcaseAssignRuleAssigneeType() {
		return caseAssignRuleAssigneeType;
	}

	public void setcaseAssignRuleAssigneeType(CASE_ASSIGN_RULE_ASSIGNEE_TYPE caseAssignRuleAssigneeType) {
		this.caseAssignRuleAssigneeType = caseAssignRuleAssigneeType;
	}
	
	public void setcaseAssignRuleAssigneeType(int caseAssignRuleAssigneeTypeId) {
		this.caseAssignRuleAssigneeType = CASE_ASSIGN_RULE_ASSIGNEE_TYPE.getcaseAssignRuleAssigneeTypeById(caseAssignRuleAssigneeTypeId, null);
	}

	public boolean getIsEqualCaseAssignRuleTypeValue() {
		return isEqualCaseAssignRuleTypeValue;
	}

	public void setIsEqualCaseAssignRuleTypeValue(boolean isEqualCaseAssignRuleTypeValue) {
		this.isEqualCaseAssignRuleTypeValue = isEqualCaseAssignRuleTypeValue;
	}

	public int getCaseAssigneeTypeValue() {
		return caseAssigneeTypeValue;
	}

	public void stCaseAssigneeTypeValue(int caseAssigneeTypeValue) {
		this.caseAssigneeTypeValue = caseAssigneeTypeValue;
	}
}

CaseAssignRuleBean.java:

package bean;

import java.util.ArrayList;
import java.util.List;

public class CaseAssignRuleBean {
	private int id;
	private int caseStaffAccountId;
	private List<CaseAssignRuleConditionBean> caseAssignRuleConditionList;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public int getCaseStaffAccountId() {
		return caseStaffAccountId;
	}

	public void setCaseStaffAccountId(int caseStaffAccountId) {
		this.caseStaffAccountId = caseStaffAccountId;
	}

	public List<CaseAssignRuleConditionBean> getCaseAssignRuleConditionList() {
		if (caseAssignRuleConditionList == null) {
			this.caseAssignRuleConditionList = new ArrayList<>();
		}
		return caseAssignRuleConditionList;
	}

	public void setCaseAssignRuleConditionList(List<CaseAssignRuleConditionBean> caseAssignRuleConditionList) {
		this.caseAssignRuleConditionList = caseAssignRuleConditionList;
	}
}

CaseStaffBean.java:

package bean;

public class CaseStaffBean {
	int accountId;
	
	public int getAccountId() {
		return accountId;
	}
	public void setAccountId(int accountId) {
		this.accountId = accountId;
	}
}

CaseAssignRuleDAO.java:

package dao;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import bean.CaseStaffBean;
import bean.CaseAssignRuleBean;
import bean.CaseAssignRuleConditionBean;
import constant.CASE_ASSIGN_RULE_TYPE;
import bean.CaseBean;

@Repository
public class CaseAssignRuleDAO {

	private static CaseAssignRuleDAO instance;

	private static DataSource postgreSQLDataSource;
	private static JdbcTemplate postgreSQLJdbcTemplate;
	
	private static userDAO userDAO;

	private CaseAssignRuleDAO() {
	}

	@Autowired
	private void setUp(CaseAssignRuleDAO instance,
					   @Qualifier("postgreSQLDataSource") DataSource postgreSQLDataSource,
					   @Qualifier("postgreSQLJdbcTemplate") JdbcTemplate postgreSQLJdbcTemplate,
					   userDAO userDAO) {
		CaseAssignRuleDAO.instance = instance;

		CaseAssignRuleDAO.postgreSQLDataSource = postgreSQLDataSource;
		CaseAssignRuleDAO.postgreSQLJdbcTemplate = postgreSQLJdbcTemplate;
		CaseAssignRuleDAO.userDAO = userDAO;
	}

	public static synchronized CaseAssignRuleDAO getInstance() {
		if (instance == null) {
			throw new RuntimeException(
					CaseAssignRuleDAO.class.getName() + " instance is not initialized correctly.");
		}
		return instance;
	}

    //查詢一個 Case 應該要 assign 給哪一個 CaseStaff
	public CaseStaffBean queryCaseStaffByCaseAssignRule(CaseBean case) {
		
		List<CaseAssignRuleBean> caseAssignRuleList = querycaseAssignRuleList();
		
		//決定 Case 的屬性應該要選擇哪一個 Case assign rule
		CaseAssignRuleBean chosenCaseAssignRule = caseAssignRuleList.stream()
		                     .filter(caseAssignRule ->
		                     			caseAssignRule.getcaseAssignRuleConditionList()
		                     							 .stream()
														 //Case assign rule 要被選擇的條件是它的 condition 都要被滿足(allMatch)
		                     							 .allMatch(caseAssignRuleCondition -> {
														              //比較 langId
						                    	                	  if (CASE_ASSIGN_RULE_TYPE.LANG_ID == caseAssignRuleCondition.getcaseAssignRuleAssigneeType()) {
																	      //如果條件要求 typeValue 要 "等於"
						                    	                		  if (caseAssignRuleCondition.getIsEqualCaseAssignRuleTypeValue()) {
						                    	                			  return caseAssignRuleCondition.getAssigneeTypeValue() == case.getLangId();
						                    	                		  }
																		  //如果條件要求 typeValue 要 "不等於"
						                    	                		  return caseAssignRuleCondition.getAssigneeTypeValue() != case.getLangId();
						                    	                	  }
				                    	                	  
															          //比較 productId
						                    	                	  if (CASE_ASSIGN_RULE_TYPE.PRODUCT_ID == caseAssignRuleCondition.getcaseAssignRuleAssigneeType()) {
						                    	                		  //如果條件要求 productId 要 "等於"
																		  if (caseAssignRuleCondition.getIsEqualCaseAssignRuleTypeValue()) {
						                    	                			  return caseAssignRuleCondition.getAssigneeTypeValue() == case.getProductId();
						                    	                		  }
																		  //如果條件要求 productId 要 "不等於"
						                    	                		  return caseAssignRuleCondition.getAssigneeTypeValue() != case.getProductId();
						                    	                	  }
						                    	                	  
																	  //如果執行到這裡代表規則設定了不應該存在的 case_type,在這裡我視為不符合條件。
						                    	                	  return false;
		                     							 			})
		                    		 )
									 //以第一個找到的 Case assign rule 為主
									 .findFirst().orElse(null);
		
		if (chosenCaseAssignRule == null) {
			return null;
		}
		
		return userDAO.queryCaseStaffByAccountId(chosenCaseAssignRule.getAssigneeStaffAccountId());
	}

    //查詢所有的 case_assign_rule
	public List<CaseAssignRuleBean> querycaseAssignRuleList() {
		String sql = "SELECT * FROM case_assign_rule ORDER BY id";

		return postgreSQLJdbcTemplate.query(sql, new RowMapper<CaseAssignRuleBean>() {

			@Override
			public CaseAssignRuleBean mapRow(ResultSet rs, int rowNum) throws SQLException {
				CaseAssignRuleBean caseAssignRule = new CaseAssignRuleBean();
				caseAssignRule.setId(rs.getInt("id"));
				caseAssignRule.setAssigneeStaffAccountId(rs.getInt("assignee_staff_id"));
				caseAssignRule.setcaseAssignRuleConditionList(
						querycaseAssignRuleConditionListByRuleId(caseAssignRule.getId()));

				return caseAssignRule;
			}

		});
	}

    //把特定 case_assign_rule id 的 condition list 查出來。
	public List<CaseAssignRuleConditionBean> querycaseAssignRuleConditionListByRuleId(int caseAssignRuleId) {
		String sql = "SELECT * " + "FROM case_assign_rule_assignment_condition " + "WHERE case_assign_rule_id = ?";

		return postgreSQLJdbcTemplate.query(sql, new RowMapper<CaseAssignRuleConditionBean>() {

			@Override
			public CaseAssignRuleConditionBean mapRow(ResultSet rs, int rowNum) throws SQLException {
				CaseAssignRuleConditionBean caseAssignRuleCondition = new CaseAssignRuleConditionBean();
				caseAssignRuleCondition.setcaseAssignRuleId(rs.getInt("case_assign_rule_id"));
				caseAssignRuleCondition
						.setcaseAssignRuleAssigneeType(rs.getInt("case_assign_rule_assignee_type_id"));
				caseAssignRuleCondition.setIsEqualAssigneeTypeValue(rs.getBoolean("is_equal_assignee_type_value"));
				caseAssignRuleCondition.setAssigneeTypeValue(rs.getInt("assignee_type_value"));

				return caseAssignRuleCondition;
			}

		}, caseAssignRuleId);
	}
}

接下來如果日後有更多的屬性想要來供判斷、以及有更多的 rule 想要設定給不同的 Case Staff 時,就只要再加資料至 case_assign_rule_type, case_assign_rule, case_assign_rule_assignment_condition 這三個 Database Table 就可以了。

2025年10月4日 星期六

以屬性為條件且屬性有階層關係的的規則可擴充權限設計分享

最近公司有如下需求:

需求:

  1. 權限有多個,要可動態增減 (假設互相獨立,例如先不要有 PERMISSION_ALL 包含 PERMISSION_READ、PERMISSION_WRITE 這種)
  2. 條件有多個,要可動態增減,條件可以有優先層級關係
案例:

  1. 假設現在有一個權限叫做 PERMISSION_BLOG_ARTICLE_EDIT。
  2. 假設 user 的屬性有 accountId, groupId,user 只會屬於一個 group,group 可包含多個 user。
  3. 可以設定 accountId = x 時 Allow 或 Deny 權限,groupId = x 時 Allow 或 Deny 權限。
  4. 在檢查權限時,設定 accountId 層級比 groupId 大,會先檢查 accountId 有沒有權限,如果有權限 (Allow) 就有權限,如果沒有權限 (Deny) 就沒有權限,
    如果不確定的話 (代表此權限沒有設定 accountId 相關的規則) 就去檢查 groupId 有沒有權限。
    如果所有要檢查的屬性都檢查完了還是不確定是否有權限,視為沒有權限。

網上查到了 屬性型存取控制 (Attribute-based Access Control , ABAC),並以此為靈感,最後以以下方式實作了並在此紀綠分享:

Database 的 Table 可以如下設定 (以 PostgreSQL 為例):

-- 各種 permission
CREATE TABLE permission (
 id SERIAL PRIMARY KEY, -- permission id
 label VARCHAR NOT NULL -- 為 permission 取個名字方便辨視
);

-- permission 的 權限設定細節
CREATE TABLE permission_assignment_condition (
 permission_assignee_type_id INT, -- 代表對哪一個 user 屬性去設定 rule
 is_equal_assignee_type_value BOOLEAN, -- 代表 user 屬性要 "等於" 還是 "不等於" assignee_type_value
 assignee_type_value INT, -- 代表 user 屬性要 "等於" 還是 "不等於" 某個值
 is_allowed_permission BOOLEAN, -- 代表此規則是 Allow 還是 Deny 權限
 permission_id INT, -- 此規則是對哪一個權限做設定
 PRIMARY KEY(permission_assignee_type_id, is_equal_assignee_type_value, assignee_type_value, permission_id)
);


-- user 的各種屬性,例如 accountId, groupId 等
CREATE TABLE permission_assignee_type (
 id SERIAL PRIMARY KEY, -- 屬性 id
 label VARCHAR NOT NULL -- 為屬性取名方便辨視
);

如果想要設定:

  1. groupId = 3 對 PERMISSION_BLOG_ARTICLE_EDIT 為 Deny (就是 groupId = 3 沒有權限)。
  2. accountId = 100 對 PERMISSION_BLOG_ARTICLE_EDIT  為 Allow (但是 accountId 有權限,即使他的 groupId = 3 也是有權限,也就是 accountId 的層級比 groupId 大)。

我們就可以這樣設定:

-- 設定 permission
INSERT INTO permission(id, label) VALUES(1, 'PERMISSION_BLOG_ARTICLE_EDIT');

-- 設定 permission 規則要判斷的屬性
INSERT INTO permission_assignee_type(id, label) VALUES(1, accountId);
INSERT INTO permission_assignee_type(id, label) VALUES(2, groupId);

--設定 permission 的判斷規則
--設定 accountId = 100 ALLOW PERMISSION_BLOG_ARTICLE_EDIT 
INSERT INTO permission_assignment_condition('permission_assignee_type_id',
                                            'is_equal_assignee_type_value',
                                            'assignee_type_value',
                                            'is_allowed_permission',
                                            'permission_id')
			VALUES(1, true, 100, true, 1);
			
--設定 groupId = 3 DENY PERMISSION_BLOG_ARTICLE_EDIT 
INSERT INTO permission_assignment_condition('permission_assignee_type_id',
                                            'is_equal_assignee_type_value',
                                            'assignee_type_value',
                                            'is_allowed_permission',
                                            'permission_id')
			VALUES(2, true, 3, false, 1);

有了設定好的資料後,我們就可以用程式來判斷一個 User 是否有特定的權限,以下由 Java 來做例子,只要執行 PermissionDAO 的 isUserHasPermission(UserBean user, PERMISSION permission) 就可以得知此 User 有沒有被授權特別 permission 的權限:

UserBean.java:

     package bean;

import java.util.Date;

public class UserBean {
	int accountId;
	int groupId;
	
	public int getAccountId() {
		return accountId;
	}
	
	public void setAccountId(int accountId) {
		this.accountId = accountId;
	}
	
	public int getGroupId() {
		return groupId;
	}
	public void setGroupId(int groupId) {
		this.groupId = groupId;
	}
}

PERMISSION.java :

package constant;

public enum PERMISSION {
	PERMISSION_BLOG_ARTICLE_EDIT(1, "PERMISSION_BLOG_ARTICLE_EDIT");
	
	private int id;
	private String label;
	
	private PERMISSION(int id, String label) {
        this.id = id;
        this.label = label;
    }
	
	public static PERMISSION getPermissionById(int id, PERMISSION defaultPermission) {
        for (PERMISSION value : values()) {
            if (value.id == id) {
            	return value;
            }
        }
        
        return defaultPermission;
	}
	
	public static PERMISSION getPermissionByLabel(String label, PERMISSION defaultPermission) {
        for (PERMISSION value : values()) {
            if (value.label.equalsIgnoreCase(label)) {
            	return value;
            }
        }
        
        return defaultPermission;
	}
	
	public int getId() {
		return id;
	}
	
	public String getLabel() {
		return label;
	}
}

PERMISSION_ALLOW_STATUS.java

package constant;

public enum PERMISSION_ALLOW_STATUS {
	NO_DECISION, 
	ALLOW,
	DENY;
}

PERMISSION_ASSIGNEE_TYPE.java

package constant;

public enum PERMISSION_ASSIGNEE_TYPE {
	USER_ACCOUNT_ID(1, "USER_ACCOUNT_ID"),
	USER_GROUP_ID(2, "USER_GROUP_ID")
	
	private int id;
	private String label;
	
	private PERMISSION_ASSIGNEE_TYPE(int id, String label) {
        this.id = id;
        this.label = label;
    }
	
	public static PERMISSION_ASSIGNEE_TYPE getPermissionAssigneeTypeById(int id, PERMISSION_ASSIGNEE_TYPE defaultPermissionAssigneeType) {
        for (PERMISSION_ASSIGNEE_TYPE value : values()) {
            if (value.id == id) {
            	return value;
            }
        }
        
        return defaultPermissionAssigneeType;
	}
	
	public static PERMISSION_ASSIGNEE_TYPE getPermissionAssigneeTypeByLabel(String label, PERMISSION_ASSIGNEE_TYPE defaultPermissionAssigneeType) {
        for (PERMISSION_ASSIGNEE_TYPE value : values()) {
            if (value.label.equalsIgnoreCase(label)) {
            	return value;
            }
        }
        
        return defaultPermissionAssigneeType;
	}
	
	public int getId() {
		return id;
	}
	
	public String getLabel() {
		return label;
	}
}

PermissionDAO.java

package dao;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import bean.UserBean;
import bean.PermissionAssignmentConditionBean;
import constant.PERMISSION;
import constant.PERMISSION_ALLOW_STATUS;
import constant.PERMISSION_ASSIGNEE_TYPE;

@Repository
public class PermissionDAO {
	
	private JdbcTemplate jdbctemplate;
	
	@Autowired
	public PermissionDAO(JdbcTemplate jdbctemplate) {
		this.jdbctemplate = jdbctemplate;
	}
	
	public boolean isUserHasPermission(UserBean user, PERMISSION permission) {
		PERMISSION_ALLOW_STATUS permissionAllowStatus;
		
		//取得特定 permission 的授權規則
		List<PermissionAssignmentConditionBean> permissionAssignmentConditionList = getPermissionAssignmentCondition(permission);
		
		//檢查 permission 授權規則對於此 accountId 符合 Allow, Deny, 還是 還未知 (NO_DECISION)
		permissionAllowStatus = getPermissionAllowStatusByAssigneeType(permissionAssignmentConditionList,
										                               permission,
										                               PERMISSION_ASSIGNEE_TYPE.USER_ACCOUNT_ID,
										                               user.getAccountId());
		
		//如果不是 NO_DECISION ,即代表確定是 ALLOW 或 DENY,即可回傳確定的授權結果。
		if (permissionAllowStatus != PERMISSION_ALLOW_STATUS.NO_DECISION) {
			return permissionAllowStatus == PERMISSION_ALLOW_STATUS.ALLOW;
		}
		
		//如果對於 accountId 的授權是未知狀態 (即授權規則裡沒特別設定),就再檢查 groupId
		permissionAllowStatus = getPermissionAllowStatusByAssigneeType(permissionAssignmentConditionList,
										                               permission,
										                               PERMISSION_ASSIGNEE_TYPE.USER_GROUP_ID,
										                               user.getGroupId());
		
		//跟檢查 accountId 一樣,如果不是 NO_DECISION ,即代表確定是 ALLOW 或 DENY,即可回傳確定的授權結果。
		if (permissionAllowStatus != PERMISSION_ALLOW_STATUS.NO_DECISION) {
			return permissionAllowStatus == PERMISSION_ALLOW_STATUS.ALLOW;
		}
		
		//如果已檢查全部要檢查的屬性,但結果還是 NO_DECISION 時,就視為沒有權限 (DENY)
		return false;
	}
	
	public List<PermissionAssignmentConditionBean> getPermissionAssignmentCondition(PERMISSION permission) {
	    //將特定 permission 的授權規則查出來
		String sql = "SELECT * FROM permission_assignment_condition WHERE permission_id = ?";
		
		return jdbctemplate.query(sql, new RowMapper<PermissionAssignmentConditionBean>() {

			@Override
			public PermissionAssignmentConditionBean mapRow(ResultSet rs, int rowNum) throws SQLException {
				PermissionAssignmentConditionBean permissionAssignmentCondition = new PermissionAssignmentConditionBean();
				permissionAssignmentCondition.setPermissionAssigneeType(rs.getInt("permission_assignee_type_id"));
				permissionAssignmentCondition.setIsEqualAssigneeTypeValue(rs.getBoolean("is_equal_assignee_type_value"));
				permissionAssignmentCondition.setAssigneeTypeValue(rs.getInt("assignee_type_value"));
				permissionAssignmentCondition.setIsAllowedPermission(rs.getBoolean("is_allowed_permission"));
				permissionAssignmentCondition.setPermission(rs.getInt("permission_id"));
				
				return permissionAssignmentCondition;
			}
			
		}, permission.getId());
	}
	
	private PERMISSION_ALLOW_STATUS getPermissionAllowStatusByAssigneeType(List<PermissionAssignmentConditionBean> permissionAssignmentConditionList,
							                                               PERMISSION permission,
							                                               PERMISSION_ASSIGNEE_TYPE permissionAssigneeType,
							                                               int assigneeValue) {
		for (PermissionAssignmentConditionBean condition : permissionAssignmentConditionList) {
		    //濾掉其他沒有要檢查的 permission (包括 permission 不對 或 permissionAssigneeType 屬性不對) 
			if (permission != condition.getPermission() ||
				permissionAssigneeType != condition.getPermissionAssigneeType()) {
				continue;
			}
			
			//如果授權規則是要屬性值要 "等於" 某值
			if (condition.getIsEqualAssigneeTypeValue()) {
			    //如果屬性值的確等於某值,回傳規則設定的 ALLOW 或 DENY
				if (assigneeValue == condition.getAssigneeTypeValue()) {
					return condition.getIsAllowedPermission() ? PERMISSION_ALLOW_STATUS.ALLOW : PERMISSION_ALLOW_STATUS.DENY;
				}
				//如果屬性值不等於某值,並不代表一定是 DENY,而是 NO_DECISION
				continue;
			}
			
			//如果授權規則是要屬性值要 "不等於" 某值
			//且屬性值的確不等於某值,回傳規則設定的 ALLOW 或 DENY
			if (assigneeValue != condition.getAssigneeTypeValue()) {
				return condition.getIsAllowedPermission() ? PERMISSION_ALLOW_STATUS.ALLOW : PERMISSION_ALLOW_STATUS.DENY;
			}
			//如果屬性值等於某值,並不代表一定是 DENY,而是 NO_DECISION
		}
		
		//找不到相應的 permission + permissionAssignmentType ,視為 NO_DECISION
		return PERMISSION_ALLOW_STATUS.NO_DECISION;
	}
}


這樣的設計也具有擴充性,日後可以依需求增加更多的 permission 和 permission_assignee_type,只要再增加資料進 PERMISSION 和 PERMISSION_ASSIGNEE_TYPE 的 Database Table 即可。