首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

基於 SSM 框架的 Web 系統用戶權限控制實現全流程解析

     編輯:Java 相關知識

系統架構概覽

採用 SSM (Spring + Spring MVC + MyBatis) + Freemarker 模板引擎,前端權限樹使用 zTree 插件。核心流程:

  1. 數據庫設計:用戶表、模塊表(遞歸樹結構)。
  2. 後端構建權限樹 JSON → 前端 zTree 渲染勾選狀態。
  3. 管理員勾選保存 → 後端解析權限碼字符串存入用戶表。
  4. 頁面渲染時 Freemarker 自定義指令根據 Session 權限碼控制模塊顯隱。

1. 數據庫設計

-- 模塊表:遞歸樹結構
CREATE TABLE modules (
    id INT PRIMARY KEY AUTO_INCREMENT,
    module VARCHAR(30) COMMENT '模塊名稱',
    pid INT COMMENT '父級ID',
    level INT COMMENT '層級:0-一級,1-二級,2-三級'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 用戶表:權限碼存儲格式 "一級碼,二級碼;二級碼,三級碼;三級碼..."
CREATE TABLE users (
    user_code VARCHAR(10) PRIMARY KEY COMMENT '用戶代碼',
    user_name VARCHAR(40),
    user_password VARCHAR(100),
    auth_code TEXT COMMENT '權限碼:層級間分號分隔,同層級逗號分隔'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

權限碼示例:1,2,3;4,5,6;7,8,9 表示擁有一級模塊 1/2/3、二級 4/5/6、三級 7/8/9。

2. 後端實現

Controller:Spring MVC 接口

@RequestMapping("getUserRightMaskById")
@ResponseBody
public String getUserRightMaskById(HttpSession session, String id, String substoreid) {
    // 1. 判斷版本(完整版/簡易版)查詢不同模塊集
    List<Map> versions = setupService.getHotelHotelVersions(substoreid);
    String version = (String) versions.get(0).get("versions");
    
    Map<String, Object> treeMap = rightMaskService.getUserRightMaskOnTree(substoreid, id, version);
    return JSONObject.toJSONString(Map.of("datas", treeMap), SerializerFeature.WriteMapNullValue);
}

@RequestMapping("updateUserRightMaskByAjax")
@ResponseBody
public String updateUserRightMaskByAjax(String rootId, String midId, String minId, String userId) {
    // 前端傳遞三層級勾選的 ID 字符串,Service 拼接保存
    rightMaskService.updateUserRightMask(rootId, midId, minId, userId);
    return "1"; // 成功
}

Service:權限樹構建核心邏輯

public Map<String, Object> getUserRightMaskOnTree(String substoreid, String userId, String version) {
    // 1. 獲取用戶現有權限碼
    Map<String, Object> userMask = iRightMaskDao.getUserRightMaskBySubAndId(substoreid, userId);
    
    // 2. 查詢各層級模塊列表
    List<Map> level1 = "complete".equals(version) ? 
        iRightMaskDao.getRightMaskOnHotelOne() : iRightMaskDao.getRightMaskOnTavernOne();
    List<Map> level2 = ...; // level3 同理
    
    // 3. 遞歸構建 zTree 節點
    List<Map> result = new ArrayList<>();
    buildTree(result, level1, level2, level3, userMask);
    
    return Map.of("data", result);
}

private void buildTree(List<Map> result, List<Map> l1, List<Map> l2, List<Map> l3, Map<String, Object> auth) {
    for (Map m1 : l1) {
        Map<String, Object> node1 = Map.of("id", m1.get("id"), "name", m1.get("module"));
        node1.put("checked", hasPermission(l1, auth, m1));
        
        List<Map> children2 = new ArrayList<>();
        for (Map m2 : l2) if (m2.get("pid").equals(m1.get("id"))) {
            Map<String, Object> node2 = Map.of("id", m2.get("id"), "name", m2.get("module"));
            node2.put("checked", hasPermission(l2, auth, m2));
            
            List<Map> children3 = new ArrayList<>();
            for (Map m3 : l3) if (m3.get("pid").equals(m2.get("id"))) {
                Map<String, Object> node3 = Map.of("id", m3.get("id"), "name", m3.get("module"));
                node3.put("checked", hasPermission(l3, auth, m3));
                children3.add(node3);
            }
            node2.put("children", children3);
            children2.add(node2);
        }
        node1.put("children", children2);
        result.add(node1);
    }
}

private boolean hasPermission(List<Map> list, Map auth, Map node) {
    String code = (String) auth.get("auth_code");
    if (code == null) return false;
    String[] levels = code.split(";");
    // 根據節點所在層級解析對應層級碼
    // 此處簡化:實際需傳入層級索引
    return Arrays.stream(levels[level].split(",")).anyMatch(id -> id.equals(node.get("id")));
}

DAO:MyBatis 映射

<select id="getRightMaskOnHotelOne" resultType="map">
    select id, module, pid, level from modules where level = 0
</select>
<select id="getUserRightMaskBySubAndId" resultType="map">
    select auth_code from users where user_code = #{userId}
</select>

3. 前端 zTree 渲染與交互

// setUserauthOnTree.html
$.post("${ctx}/Setup/getUserRightMaskById", {id: "${userId}"}, function(res) {
    zTreeObj = $.fn.zTree.init($("#treeDemo"), setting, res.datas.data);
    zTreeObj.expandAll(true);
}, "json");

var setting = {
    check: { enable: true, chkStyle: "checkbox", chkboxType: { "Y": "ps", "N": "ps" } },
    callback: { onCheck: zTreeOnCheck }
};

function editModle() {
    var ids = { rootId: "", midId: "", minId: "" };
    var nodes = zTreeObj.getCheckedNodes();
    nodes.forEach(n => {
        if (n.level == 0) ids.rootId += "," + n.id;
        else if (n.level == 1) ids.midId += "," + n.id;
        else if (n.level == 2) ids.minId += "," + n.id;
    });
    // 去除首個逗號後提交
    $.post("${ctx}/Setup/updateUserRightMaskByAjax", { ...ids, userId: "${userId}" }, ...);
}

4. Freemarker 頁面級權限控制

配置自定義指令

<bean id="freeMarkerViewResolver" class="...FreeMarkerViewResolver">
    <property name="attributesMap">
        <map>
            <entry key="menucall"><bean class="com.leike.util.MenuFunction"/></entry>
        </map>
    </property>
</bean>

MenuFunction 實現

public class MenuFunction implements TemplateMethodModel {
    public Object exec(List args) {
        int level = Integer.parseInt(args.get(0).toString());
        int moduleId = Integer.parseInt(args.get(1).toString());
        
        HttpSession session = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes())
                              .getRequest().getSession();
        Info info = (Info) session.getAttribute("info");
        String authCode = info.getUser().getAuthCode(); // "1,2;3,4;5,6"
        
        String[] levels = authCode.split(";");
        String[] modules = levels[level].split(",");
        return Arrays.stream(modules).anyMatch(m -> m.equals(String.valueOf(moduleId)));
    }
}

頁面使用

<#if menucall(1, 122)>
    <li><a href="#" id="booknew"><i class="glyphicon"></i>預訂</a></li>
</#if>

關鍵設計點總結

層面設計決策優缺點
權限存儲字符串編碼(分號/逗號分隔)簡單、無需關聯表;但查詢/統計困難,長度受限
樹構建Service 層多層循環組裝 Map直觀、靈活;大數據量時性能一般,可改用遞歸 CTE 或緩存
前端交互zTree checkbox + 三狀態聯動用戶體驗好;需注意大量節點渲染性能(分頁/虛擬滾動)
頁面控制Freemarker 指令讀取 Session服務端渲染安全;但模板邏輯與後端耦合,現代 SPA 應改為前端路由守衛

改進建議

  1. 權限模型升級為 RBAC(用戶-角色-權限)三表關聯,支援角色繼承、動態授權。
  2. 引入 Redis 緩存用戶權限集合(Set),登錄時加載,頁面檢查 O(1)。
  3. 前後端分離架構:後端提供 /api/permissions 返回權限標識集合,前端路由守衛/按鈕級 v-if 控制。
  4. 按鈕/接口級權限:AOP 切面 + 注解 @RequiresPermission("order:create") 統一攔截。

總結

本方案展示了傳統 SSM + 服務端渲染架構下完整的權限控制實現:從數據庫編碼存儲、樹形結構動態構建、前端可視化授權、到模板引擎級別的顯隱控制。雖然技術棧略顯老舊,但核心思想(權限編碼、樹構建、多層校驗)在現代微服務、前後端分離架構中依然具有參考價值。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved