源碼:SpringBoot整合MyBatis-Plus權(quán)限實(shí)戰(zhàn))
簡(jiǎn)介一套基于Java的智慧園區(qū)管理系統(tǒng)源碼采用SpringBoot加Ant Design Pro技術(shù)棧前后端分離架構(gòu)適合有Java基礎(chǔ)的學(xué)習(xí)者、畢設(shè)選題或園區(qū)數(shù)字化項(xiàng)目參考。系統(tǒng)覆蓋駕駛艙工作臺(tái)、租戶管理、園區(qū)列表、樓宇管理、房間管理、入駐申請(qǐng)等核心業(yè)務(wù)模塊駕駛艙提供本月賬單、物業(yè)費(fèi)、租金、水電費(fèi)等TOP10柱形圖統(tǒng)計(jì)房間維度精確到每棟每層每間可配置收租面積、租金和閑置天數(shù)入駐流程銜接小程序申請(qǐng)整體業(yè)務(wù)鏈完整。資源包共593個(gè)文件其中Java源碼280個(gè)、Vue組件105個(gè)、JS腳本91個(gè)配合XML配置、SQL及YML工程文件壓縮包僅1.82MB便于下載部署與二次開發(fā)。當(dāng)前已有738人學(xué)習(xí)源碼結(jié)構(gòu)清晰能幫助快速掌握多租戶園區(qū)中臺(tái)的數(shù)據(jù)報(bào)表、資產(chǎn)管理及入駐審批等典型實(shí)現(xiàn)。1. 基于Java的智慧園區(qū)管理系統(tǒng)源碼先想清楚它到底解決誰(shuí)的什么問題園區(qū)運(yùn)營(yíng)方最頭疼的不是單一系統(tǒng)不好用而是門禁、道閘、訪客登記、設(shè)備報(bào)修各自為政。一個(gè)訪客要進(jìn)樓保安翻三個(gè)后臺(tái)才能確認(rèn)身份和放行權(quán)限?;?Java 的智慧園區(qū)管理系統(tǒng)源碼就是把“人、車、設(shè)備、工單”統(tǒng)一收進(jìn)一套 Spring Boot MyBatis 的后端里讓這些數(shù)據(jù)從一個(gè)入口管起來。這套東西適合三類人準(zhǔn)備接手園區(qū)類項(xiàng)目的 Java 工程師拿它當(dāng) Java 課程設(shè)計(jì)案例源碼的學(xué)生以及想快速搭建信息化底座的中小型園區(qū) IT 負(fù)責(zé)人。說句反直覺的話這類源碼真正費(fèi)勁的往往不是業(yè)務(wù)代碼而是數(shù)據(jù)權(quán)限和建表這些“地基”活兒。2. 技術(shù)選型與工程骨架為什么 Spring Boot MyBatis-Plus 是復(fù)現(xiàn)這套源碼的最短路徑2.1 選型邏輯先定數(shù)據(jù)模型再定 ORM最后才輪到前端智慧園區(qū)管理的本質(zhì)是“人、車、物、事”四個(gè)對(duì)象的關(guān)系網(wǎng)。人員綁定樓棟訪客關(guān)聯(lián)人員設(shè)備掛在樓棟和樓層下工單又圍繞設(shè)備流轉(zhuǎn)。這種場(chǎng)景下聯(lián)表查詢、報(bào)表統(tǒng)計(jì)、權(quán)限過濾會(huì)占掉一大半接口邏輯所以 ORM 選型的第一原則是 SQL 可控性。MyBatis-Plus 在這一點(diǎn)上的優(yōu)勢(shì)非常直接單表 CRUD 不用手寫 XML復(fù)雜聯(lián)查又可以退回去寫原生 SQL進(jìn)退都有余地。相比之下JPA 的全自動(dòng)映射在簡(jiǎn)單場(chǎng)景省事但一碰到跨園區(qū)匯總、多條件動(dòng)態(tài) SQL調(diào)試成本會(huì)明顯上升。這些判斷說白了是 Java 基礎(chǔ)里的常識(shí)不是玄學(xué)。前端在標(biāo)題里不是重點(diǎn)工程上按前后端分離的常規(guī)做法處理即可后端只輸出 JSON做好 REST 風(fēng)格的接口約定前端用 Vue 或 React 都行。很多源碼包還會(huì)附帶一套管理后臺(tái)頁(yè)面但復(fù)現(xiàn)時(shí)的核心工作在后端先把數(shù)據(jù)模型和服務(wù)層跑通前端頁(yè)面只是殼。2.2 可復(fù)現(xiàn)的 Maven 工程結(jié)構(gòu)依賴與配置一次到位先把工程骨架搭出來。常見的做法是標(biāo)準(zhǔn) Maven 多模塊或單模塊結(jié)構(gòu)我一般用單模塊起步包結(jié)構(gòu)固定為 controller、service、mapper、entity、config、common 六層后面按業(yè)務(wù)規(guī)模再拆。核心依賴如下dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies依賴?yán)镒铌P(guān)鍵的是 MyBatis-Plus 的 starter它把分頁(yè)插件、MyBatis 自動(dòng)配置、條件構(gòu)造器全部集成進(jìn)來了。Lombok 可以省掉實(shí)體類的 getter/setter但要注意如果你的團(tuán)隊(duì)不習(xí)慣 Lombok也可以去掉它改用手寫方法功能上沒有差別。mysql-connector-j 的 scope 設(shè)為 runtime只參與運(yùn)行時(shí)不參與編譯。配套的application.yml是這樣spring: datasource: url: jdbc:mysql://localhost:3306/smart_park?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password jackson: time-zone: Asia/Shanghai mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml type-aliases-package: com.smartpark.entity configuration: map-underscore-to-camel-case: true兩個(gè)參數(shù)容易踩坑serverTimezoneAsia/Shanghai不配置Java 8 時(shí)間類型在 JDBC 驅(qū)動(dòng) 8.0 以上版本會(huì)報(bào)時(shí)區(qū)異常map-underscore-to-camel-case開啟后數(shù)據(jù)庫(kù)的building_id才能自動(dòng)映射到實(shí)體的buildingId字段省去一堆TableField注解。2.3 園區(qū)四層數(shù)據(jù)模型園區(qū)、樓棟、樓層、房間怎么落表智慧園區(qū)的數(shù)據(jù)模型核心是“租戶-園區(qū)-樓棟-樓層-房間”這條層級(jí)鏈。租戶不是每個(gè)項(xiàng)目都有但只要是多園區(qū)運(yùn)營(yíng)tenant_id建議從一開始就加上不然后面數(shù)據(jù)權(quán)限會(huì)返工。我常按下面的表結(jié)構(gòu)設(shè)計(jì)表名用途關(guān)鍵字段park_building樓棟id, tenant_id, name, address, lat, lngpark_floor樓層id, building_id, floor_no, namepark_room房間id, floor_id, room_no, use_type, areapark_person常駐人員id, tenant_id, building_id, name, phone, id_cardpark_visitor訪客記錄id, person_id, visit_time, expire_time, statuspark_device設(shè)備id, building_id, device_type, status, install_datepark_work_order工單id, device_id, reporter, assignee, status, create_time樓層和房間單獨(dú)建表而不是用一個(gè)location字符串字段糊弄這是為了后面做樓層級(jí)權(quán)限和能耗統(tǒng)計(jì)時(shí)能直接按字段過濾不用解析字符串。tenant_id放進(jìn)人員表和設(shè)備表而不是只放園區(qū)表也是同樣的道理行級(jí)權(quán)限過濾時(shí)直接在業(yè)務(wù)表上加條件避免先查園區(qū)再二次過濾。3. 核心業(yè)務(wù)落庫(kù)用 MyBatis-Plus 實(shí)體類反向生成建表 SQL3.1 實(shí)體類怎么設(shè)計(jì)才不返工實(shí)體類設(shè)計(jì)是面向?qū)ο缶幊汤镒罨A(chǔ)但也最容易返工的一環(huán)。很多源碼跑不起來不是代碼邏輯錯(cuò)而是實(shí)體字段和表結(jié)構(gòu)對(duì)不上。MyBatis-Plus 里三個(gè)注解最關(guān)鍵TableName指定表名TableId標(biāo)記主鍵及生成策略TableField處理字段映射和存在性。Data TableName(park_person) public class ParkPerson { TableId(type IdType.ASSIGN_ID) private Long id; TableField(tenant_id) private Long tenantId; TableField(building_id) private Long buildingId; private String name; private String phone; private String idCard; private String faceUrl; private LocalDateTime createTime; }IdType.ASSIGN_ID是雪花算法主鍵適合分布式環(huán)境生成的 ID 是 Long 類型不用依賴數(shù)據(jù)庫(kù)自增。faceUrl存的是人臉圖片的文件路徑不是 base64 字符串這是常見做法。createTime建議統(tǒng)一放到每個(gè)表里做時(shí)間范圍查詢和審計(jì)時(shí)都會(huì)用到。一個(gè)細(xì)節(jié)TableField(exist false)可以標(biāo)記實(shí)體里存在但表里沒有的字段比如臨時(shí)用的startTime、endTime查詢參數(shù)這樣既能在代碼里用又不會(huì)在建表或查詢時(shí)報(bào)錯(cuò)。3.2 寫一個(gè)自動(dòng)生成 CREATE TABLE 的工具M(jìn)yBatis-Plus 本身不提供從實(shí)體類直接生成建表 SQL 的能力但可以通過反射自己寫一個(gè)輕量工具。這個(gè)工具在項(xiàng)目初始化時(shí)特別有用實(shí)體類改完了跑一下 main 方法所有表的 DDL 就出來了不用手寫第二遍。public class DdlGenerator { private static final MapClass?, String TYPE_MAP new HashMap(); static { TYPE_MAP.put(String.class, VARCHAR(255)); TYPE_MAP.put(Long.class, BIGINT); TYPE_MAP.put(Integer.class, INT); TYPE_MAP.put(LocalDateTime.class, DATETIME); TYPE_MAP.put(BigDecimal.class, DECIMAL(18,2)); } public static String generate(Class? entityClass) { TableName tableName entityClass.getAnnotation(TableName.class); StringBuilder ddl new StringBuilder(); ddl.append(CREATE TABLE IF NOT EXISTS ).append(tableName.value()).append( (\n); for (Field field : entityClass.getDeclaredFields()) { TableField tf field.getAnnotation(TableField.class); if (tf ! null !tf.exist()) { continue; } String column camelToUnderscore(field.getName()); String type TYPE_MAP.getOrDefault(field.getType(), VARCHAR(255)); ddl.append( ).append(column).append( ).append(type); if (field.isAnnotationPresent(TableId.class)) { ddl.append( PRIMARY KEY); } ddl.append(,\n); } ddl.append( create_time DATETIME DEFAULT CURRENT_TIMESTAMP\n); ddl.append() ENGINEInnoDB DEFAULT CHARSETutf8mb4;); return ddl.toString(); } private static String camelToUnderscore(String name) { return name.replaceAll(([a-z])([A-Z]), $1_$2).toLowerCase(); } }邏輯不復(fù)雜反射遍歷實(shí)體類的所有聲明字段讀TableName拿表名讀TableField判斷字段是否真實(shí)存在用TYPE_MAP做 Java 類型到 MySQL 類型的映射最后拼成 DDL。camelToUnderscore把buildingId轉(zhuǎn)成building_id保證和 MyBatis-Plus 的駝峰映射規(guī)則一致。TYPE_MAP是可以擴(kuò)展的項(xiàng)目里用到LocalDate、Boolean就補(bǔ)兩行映射Boolean 映射成TINYINT(1)LocalDate 映射成DATE。這個(gè)工具不是生產(chǎn)環(huán)境的遷移工具但配合 Flyway 做初始化比手寫 SQL 省力得多。3.3 通用 CRUD 最小閉環(huán)Mapper、Service、Controller 一次跑通實(shí)體類建好后Mapper 層幾乎不用寫業(yè)務(wù)代碼繼承BaseMapper就有單表 CRUDpublic interface ParkPersonMapper extends BaseMapperParkPerson { }Service 層我習(xí)慣繼承ServiceImpl這樣既能拿到通用的save、getById、page方法又能自己擴(kuò)展方法。Controller 層寫分頁(yè)查詢時(shí)用LambdaQueryWrapper構(gòu)造條件避免手拼 SQLRestController RequestMapping(/api/person) public class ParkPersonController { Autowired private ParkPersonService personService; GetMapping(/page) public IPageParkPerson page(RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int size, RequestParam(required false) String keyword) { LambdaQueryWrapperParkPerson wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(keyword), ParkPerson::getName, keyword) .orderByDesc(ParkPerson::getCreateTime); return personService.page(new Page(page, size), wrapper); } }LambdaQueryWrapper的好處是方法引用直接指向?qū)嶓w字段字段改名時(shí)編譯期就能發(fā)現(xiàn)不會(huì)像手寫字符串那樣把name拼錯(cuò)。page和size直接透?jìng)鹘o MyBatis-Plus 的分頁(yè)插件返回的IPage里有 records、total、current 三個(gè)關(guān)鍵值前端表格組件直接能用。跑通這個(gè)閉環(huán)人員、訪客、設(shè)備、工單四套 CRUD 都是同一個(gè)模板后面的業(yè)務(wù)開發(fā)就快了。4. 權(quán)限與數(shù)據(jù)隔離多園區(qū)場(chǎng)景下行級(jí)權(quán)限的實(shí)現(xiàn)思路4.1 只做前端菜單控制等于裸奔一個(gè)常見誤區(qū)登錄后根據(jù)角色隱藏菜單就以為權(quán)限做完了。實(shí)際上后端接口完全沒有數(shù)據(jù)范圍校驗(yàn)A 園區(qū)的樓棟管理員只要手動(dòng)調(diào)接口就能把 B 園區(qū)的工單列表拉出來。這個(gè)問題在智慧園區(qū)里尤其嚴(yán)重因?yàn)橐粋€(gè)運(yùn)營(yíng)方往往管著多個(gè)園區(qū)數(shù)據(jù)天然需要按租戶和樓棟隔離。數(shù)據(jù)權(quán)限這塊是 Java 面試題里反復(fù)出現(xiàn)的高頻點(diǎn)面試官問的不只是“你怎么實(shí)現(xiàn)”而是你有沒有考慮過行級(jí)過濾的落點(diǎn)。落點(diǎn)只有一個(gè)SQL 層。在 Service 里手動(dòng)拼where tenant_id ?是能跑但每個(gè)接口都要寫一遍漏一個(gè)就出大事故。4.2 用 DataPermissionInterceptor 做統(tǒng)一的行級(jí)數(shù)據(jù)過濾MyBatis-Plus 從 3.5 開始自帶DataPermissionInterceptor它可以攔截 Mapper 方法生成的 SQL在執(zhí)行前自動(dòng)拼接上當(dāng)前用戶的數(shù)據(jù)權(quán)限條件。用它的好處是業(yè)務(wù)代碼不用做任何改動(dòng)權(quán)限規(guī)則收攏到一處配置。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); DataPermissionHandler handler new DataPermissionHandler() { Override public Expression getSqlSegment(Expression where, String mappedStatementId) { Long buildingId UserContext.get().getBuildingId(); if (buildingId null) { return where; } EqualsTo equalsTo new EqualsTo( new Column(building_id), new LongValue(buildingId)); if (where null) { return equalsTo; } return new AndExpression(where, equalsTo); } }; interceptor.addInnerInterceptor(new DataPermissionInterceptor(handler)); return interceptor; } }關(guān)鍵點(diǎn)在getSqlSegment方法where是原始 SQL 的 WHERE 條件如果當(dāng)前用戶是園區(qū)級(jí)管理員buildingId為 null直接返回原條件不做過濾如果用戶是樓棟管理員就把building_id 當(dāng)前樓棟的條件用AndExpression拼接到原條件后面。UserContext是自定義的 ThreadLocal 工具用戶登錄時(shí)把當(dāng)前用戶信息放進(jìn)去請(qǐng)求結(jié)束由攔截器清理避免線程復(fù)用導(dǎo)致的數(shù)據(jù)串號(hào)。這種方案有一個(gè)使用前提所有需要行級(jí)隔離的表都要有building_id字段。這就是第 2 章設(shè)計(jì)表結(jié)構(gòu)時(shí)把層級(jí)字段鋪到每張表的原因。如果有些表實(shí)在沒有層級(jí)字段可以用mappedStatementId判斷當(dāng)前執(zhí)行的是哪個(gè) Mapper 方法按白名單跳過過濾。4.3 角色-菜單-數(shù)據(jù)范圍三層模型與授權(quán)落點(diǎn)權(quán)限體系建議拆成三層而不是把角色和范圍混在一個(gè)表里層級(jí)內(nèi)容示例菜單權(quán)限用戶能看到哪些功能和路由工單管理、設(shè)備管理、報(bào)表中心操作權(quán)限對(duì)功能能做什么動(dòng)作新增工單、派單、關(guān)閉工單數(shù)據(jù)范圍能看到哪一層的業(yè)務(wù)數(shù)據(jù)全部園區(qū)、本園區(qū)、本樓棟、僅本人菜單和操作權(quán)限做成 RBAC即用戶-角色-菜單的三張關(guān)聯(lián)表。數(shù)據(jù)范圍不放進(jìn) RBAC而是作為角色上的一個(gè)擴(kuò)展屬性比如data_scope字段取值A(chǔ)LL、TENANT、BUILDING、SELF。授權(quán)時(shí)給角色分配數(shù)據(jù)范圍用戶登錄時(shí)把范圍解析成具體的過濾條件在攔截器里消費(fèi)。這樣新增一個(gè)“只要本樓層數(shù)據(jù)”的角色只需要新增一個(gè)枚舉值再補(bǔ)一個(gè)樓層字段的過濾邏輯不用動(dòng)任何業(yè)務(wù)接口。5. 避坑手冊(cè)源碼拿回來跑不起來時(shí)先查這 5 個(gè)地方5.1 Java 啟動(dòng)失敗端口被占、數(shù)據(jù)庫(kù)連不上、依賴沖突現(xiàn)象Application啟動(dòng)類一運(yùn)行控制臺(tái)先報(bào)Web server failed to start或者等了幾秒后報(bào)Communications link failure。原因最常見的是 8080 端口被其他進(jìn)程占用其次是數(shù)據(jù)庫(kù)連接串里的庫(kù)名和密碼不對(duì)本地根本連不上 MySQL。依賴沖突一般出現(xiàn)在 MyBatis-Plus 和原生 MyBatis 同時(shí)引入時(shí)兩個(gè)框架的 SqlSessionFactory 會(huì)打架。解決先在命令行確認(rèn)端口占用情況netstat -ano | grep 8080找到 PID 后按系統(tǒng)結(jié)束進(jìn)程。數(shù)據(jù)庫(kù)連接串、賬號(hào)、密碼逐項(xiàng)核對(duì)smart_park這個(gè)庫(kù)要先建出來連接串的庫(kù)名是大小寫敏感的。依賴沖突的話檢查 pom 里是否把mybatis和mybatis-spring重復(fù)引入了MyBatis-Plus 的 starter 已經(jīng)內(nèi)含了不要手動(dòng)再加原生 MyBatis 依賴。5.2 自動(dòng)建表腳本沒生效實(shí)體類沒被掃描到類型映射悄悄出錯(cuò)現(xiàn)象DdlGenerator 跑完沒有任何輸出或者生成的 CREATE TABLE 里字段類型全變成 VARCHAR(255)。原因前者通常是實(shí)體類不在type-aliases-package指定的包路徑下反射拿不到TableName注解后者是TYPE_MAP里沒有對(duì)應(yīng)該字段類型的映射走了getOrDefault的兜底分支。解決確認(rèn)實(shí)體類統(tǒng)一放在com.smartpark.entity包下并且application.yml里的type-aliases-package和實(shí)際包名完全一致。新增字段類型時(shí)同步更新TYPE_MAP特別是Boolean、LocalDate這幾種常見類型提前加好映射還能避免建出來的表字段語(yǔ)義不對(duì)。5.3 日期和時(shí)區(qū)錯(cuò)亂JSON 返回的時(shí)間比數(shù)據(jù)庫(kù)早 8 小時(shí)現(xiàn)象數(shù)據(jù)庫(kù)里存的是2025-01-15 10:30:00接口返回給前端變成2025-01-15 02:30:00所有時(shí)間整體少了 8 小時(shí)。原因MySQL 驅(qū)動(dòng)在連接串里沒有指定時(shí)區(qū)時(shí)默認(rèn)按 JVM 時(shí)區(qū)換算而 JDBC 驅(qū)動(dòng) 8.0 以上版本默認(rèn)使用服務(wù)器時(shí)區(qū)兩者不一致就產(chǎn)生了偏移。同時(shí) Jackson 序列化 LocalDateTime 時(shí)也有自己的時(shí)區(qū)設(shè)置兩個(gè)地方只要有一個(gè)沒對(duì)齊就會(huì)翻車。解決application.yml里spring.datasource.url的serverTimezoneAsia/Shanghai和spring.jackson.time-zone必須同時(shí)設(shè)置成同一個(gè)時(shí)區(qū)。只改其中一個(gè)治標(biāo)不治本。數(shù)據(jù)庫(kù)連接串里的characterEncodingutf8也順手加上中文亂碼問題一并避免。5.4 多表聯(lián)查變慢N1 查詢和缺失索引現(xiàn)象門禁記錄列表頁(yè)打開要 3 秒以上或者同一批數(shù)據(jù)在數(shù)據(jù)量過萬后明顯卡頓。原因最常見的是 Service 層循環(huán)里調(diào)用getById查關(guān)聯(lián)數(shù)據(jù)這就是典型的 N1 查詢。另外park_work_order表按device_id查、park_visitor表按building_id查時(shí)沒有建索引全表掃描在數(shù)據(jù)量上來后就是災(zāi)難。解決循環(huán)查庫(kù)改成批量查詢比如一次查出所有工單的device_id再統(tǒng)一listByIds取設(shè)備信息內(nèi)存里做關(guān)聯(lián)組裝。索引方面每個(gè)表的building_id、tenant_id都要建普通索引工單表的device_id、訪客表的visit_time同樣加上。用EXPLAIN看執(zhí)行計(jì)劃確認(rèn) type 列不是ALL。5.5 文件上傳路徑Windows 和 Linux 下的路徑分隔符現(xiàn)象人臉圖片上傳后在本地能訪問部署到 Linux 服務(wù)器后全部 404。原因代碼里用字符串拼接路徑寫死了D:\\upload\\face\這種 Windows 風(fēng)格路徑而 Linux 的文件系統(tǒng)結(jié)構(gòu)完全不同。更隱蔽的是用\和/混拼出來的路徑Windows 能容忍Linux 直接報(bào)找不到文件。解決上傳路徑做成配置項(xiàng)放到application.yml的custom.upload-path里不同環(huán)境用不同的配置文件覆蓋。路徑拼接統(tǒng)一用Paths.get()或File.separator不要手寫斜杠。另外上傳的文件不應(yīng)該直接放在項(xiàng)目目錄下否則重新部署就會(huì)丟文件建議掛載獨(dú)立的數(shù)據(jù)盤或?qū)ο蟠鎯?chǔ)。6. 源碼值不值得投入先跑通這三步驗(yàn)收再?zèng)Q定拿到任何一套智慧園區(qū)源碼我都先做三層驗(yàn)收過了才談改造成本。第一層是空庫(kù)啟動(dòng)把數(shù)據(jù)庫(kù)清空配好連接串啟動(dòng)項(xiàng)目觀察能不能自動(dòng)建表、內(nèi)置數(shù)據(jù)能不能初始化成功。這一步卡住的多半是依賴版本或配置項(xiàng)問題按第 5 章的順序查。第二層是權(quán)限驗(yàn)證造兩個(gè)賬號(hào)一個(gè)是園區(qū)管理員一個(gè)是樓棟管理員用同一套接口請(qǐng)求數(shù)據(jù)對(duì)比兩個(gè)賬號(hào)返回的列表數(shù)量是否不同如果完全相同說明數(shù)據(jù)權(quán)限沒有真正生效。第三層是性能摸底打開慢 SQL 日志故意查詢一次大范圍數(shù)據(jù)看打印出來的 SQL 有沒有全表掃描。慢 SQL 日志這樣配logging: level: com.smartpark.mapper: debug把 Mapper 包下的日志級(jí)別調(diào)到 debugMyBatis 會(huì)把每次執(zhí)行的預(yù)編譯 SQL 和參數(shù)都打出來。這條配置在排查“接口突然變慢”時(shí)價(jià)值極高生產(chǎn)環(huán)境不建議長(zhǎng)期開啟但驗(yàn)收階段必須開。我接過一套老園區(qū)系統(tǒng)業(yè)務(wù)代碼寫得還算規(guī)整但數(shù)據(jù)權(quán)限全靠 Service 層手動(dòng)拼條件散落了十幾個(gè)地方一次漏改就導(dǎo)致跨園區(qū)數(shù)據(jù)泄露。后來花了整整一周把它收攏到攔截器里副作用是重構(gòu)期間測(cè)試量暴漲。從那以后我養(yǎng)成了一個(gè)習(xí)慣先檢查權(quán)限過濾有沒有統(tǒng)一收口再動(dòng)業(yè)務(wù)代碼。三層驗(yàn)收法也是那時(shí)候逼出來的現(xiàn)在每次接手新源碼都會(huì)先跑一遍。這套基于 Java 的智慧園區(qū)管理系統(tǒng)源碼只要過了這三層驗(yàn)收剩下的就是按你自己的業(yè)務(wù)場(chǎng)景做減法把用不到的模塊裁掉把報(bào)表和 BI 這類增值功能慢慢補(bǔ)上。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取