1、前言
? 在Spring Boot的SMM框架(SpringBoot+Mysql+Mybatis)的WEB項(xiàng)目中,CRUD(增刪改查)大致占了50%-70%左右的工作量。提高CRUD的代碼質(zhì)量,提高CRUD的開發(fā)效率,是一件值得探討的事項(xiàng)。
? 一般認(rèn)為,CRUD是一件體力活。在SMM框架項(xiàng)目開發(fā)中,項(xiàng)目團(tuán)隊(duì)通常將這類CRUD的開發(fā)任務(wù)交由知道如何寫Mybatis腳本的初中級(jí)Java工程師來做,一般往往僅僅是業(yè)務(wù)層面的交代。實(shí)踐表明,如果不在一開始制定CRUD的開發(fā)規(guī)范,得到的結(jié)果往往差強(qiáng)人意。由于缺乏整體規(guī)劃,不同工程師的代碼風(fēng)格迥異,方法命名,參數(shù)命名,五花八門,檢查項(xiàng)也不完整,代碼臃腫,重復(fù)性高,后期常常需要大量修修補(bǔ)補(bǔ),維護(hù)成本高得驚人。 ?
? 而基于CRUD開發(fā)規(guī)范,則可大大提高了CRUD代碼的易維護(hù)性、接口的可用性和健壯性、用戶友好性,以及功能擴(kuò)展靈活性。多個(gè)項(xiàng)目實(shí)踐顯示,使用CRUD開發(fā)規(guī)范至少可以提高20%的開發(fā)效率,考慮包括后期維護(hù)成本,可以有效降低開發(fā)成本超過30%。
2、CRUD的常規(guī)功能
? CRUD的常規(guī)功能如下:
? 1)增加單個(gè)對(duì)象;
? 2)批量增加對(duì)象;
? 3)修改單個(gè)對(duì)象;
? 4)批量修改對(duì)象;
? 5)刪除單個(gè)對(duì)象;
? 6)批量刪除對(duì)象;
? 7)前端分頁查詢;
? 8)獲取指定對(duì)象;
? 9)根據(jù)條件查詢對(duì)象;
? 10)導(dǎo)入Excel數(shù)據(jù);
? 11)導(dǎo)出Excel數(shù)據(jù)。
? 其中,導(dǎo)入Excel數(shù)據(jù)是批量增加或修改對(duì)象的一種方式,導(dǎo)出Excel數(shù)據(jù)是數(shù)據(jù)查詢的一種形式。Excel作為最方便的辦公文件格式,在數(shù)據(jù)交換中使用十分廣泛。
? 根據(jù)代碼分層開發(fā)思想,CRUD將涉及8個(gè)文件:
? 1)實(shí)體類Entity,基本與表結(jié)構(gòu)字段進(jìn)行映射對(duì)應(yīng);簡單起見,可忽略其它POJO對(duì)象,在實(shí)體類中添加其它需要的屬性。
? 2)數(shù)據(jù)訪問對(duì)象類Dao,這個(gè)在SMM框架中,為支持Mybatis或Mybatis-Plus的接口類。
? 3)Dao實(shí)現(xiàn)類Mybatis,為xml腳本文件,使用Mybatis腳本語言實(shí)現(xiàn)Dao的相關(guān)接口。
? 4)服務(wù)接口類Service,定義Controller層接口所需的各種CRUD接口形式,以及內(nèi)部處理所需的其它接口形式。
? 5)服務(wù)接口實(shí)現(xiàn)類ServiceImpl,實(shí)現(xiàn)Service的相關(guān)接口。
? 6)服務(wù)單元測(cè)試類ServiceTest,使用JUnit,實(shí)現(xiàn)Service的相關(guān)接口的單元測(cè)試。
? 7)業(yè)務(wù)方法接口類Controller,也可稱為API接口類,提供HTTP接口服務(wù)。
? 8)API接口文檔,格式比較靈活,當(dāng)然使用YAPI導(dǎo)出的格式最好,但錄入很費(fèi)時(shí)間。當(dāng)然不管使用哪種格式,API接口相關(guān)的要素說明都要有。
3、數(shù)據(jù)庫設(shè)計(jì)規(guī)范
? 由于CRUD與數(shù)據(jù)庫表結(jié)構(gòu)高度相關(guān)?,因此先簡單說明一下表結(jié)構(gòu)的設(shè)計(jì)規(guī)范。
3.1、表結(jié)構(gòu)設(shè)計(jì)例子
? 先看2個(gè)表結(jié)構(gòu)的DDL(Data Definition Language,數(shù)據(jù)庫定義語言)腳本例子。
-- ----------------------------
-- Table structure for exa_users
-- 用戶表
-- ----------------------------
DROP TABLE IF EXISTS exa_users;
CREATE TABLE exa_users
(
user_id BIGINT(20) NOT NULL DEFAULT 0 COMMENT '用戶ID',
-- 登錄信息
user_name VARCHAR(80) UNIQUE NOT NULL DEFAULT '' COMMENT '用戶名',
-- 加密算法:md5(concat(md5(密碼明文),salt));
-- 前端發(fā)送一次md5的值到服務(wù)器,服務(wù)器添加salt值,計(jì)算二次md5(32)大寫值,與passwd值比較
password VARCHAR(64) NOT NULL DEFAULT '' COMMENT '用戶密碼',
-- salt,可用記錄生成的時(shí)間
salt VARCHAR(64) NOT NULL DEFAULT '' COMMENT '加鹽md5算法中的鹽',
user_type TINYINT(4) NOT NULL DEFAULT 3 COMMENT '用戶類型,1-系統(tǒng)管理員、2-公司內(nèi)部用戶、3-外部用戶,由系統(tǒng)參數(shù)表user_type類別定義',
org_id INT(11) NOT NULL DEFAULT 0 COMMENT '組織ID',
-- 用戶資料
real_name VARCHAR(64) NOT NULL DEFAULT '' COMMENT '真實(shí)姓名',
email VARCHAR(100) NOT NULL DEFAULT '' COMMENT 'Email',
phone_number VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手機(jī)號(hào)碼',
sex TINYINT(4) NOT NULL DEFAULT 1 COMMENT '性別,1-無值、2-男、3-女、4-其它,由系統(tǒng)參數(shù)表sex類別定義',
birth DATETIME DEFAULT NULL COMMENT '生日',
id_no VARCHAR(30) NOT NULL DEFAULT '' COMMENT '身份證號(hào)碼',
open_id VARCHAR(40) NOT NULL DEFAULT "" COMMENT '微信小程序的openid',
woa_openid VARCHAR(40) NOT NULL DEFAULT "" COMMENT '微信公眾號(hào)openid',
remark VARCHAR(200) NOT NULL DEFAULT '' COMMENT '備注',
-- 記錄操作信息
operator_name VARCHAR(80) NOT NULL DEFAULT '' COMMENT '操作人賬號(hào)',
delete_flag TINYINT(4) NOT NULL DEFAULT 0 COMMENT '記錄刪除標(biāo)記,0-正常、1-禁用,由系統(tǒng)參數(shù)表delete_flag類別定義',
create_time DATETIME NOT NULL DEFAULT NOW() COMMENT '創(chuàng)建時(shí)間',
update_time DATETIME DEFAULT NULL ON UPDATE NOW() COMMENT '更新時(shí)間',
PRIMARY KEY (user_id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8 COMMENT '用戶表';
-- ----------------------------
-- Table structure for exa_user_roles
-- 用戶和角色關(guān)系表
-- 用戶和角色是多對(duì)多關(guān)系
-- ----------------------------
DROP TABLE IF EXISTS exa_user_roles;
CREATE TABLE exa_user_roles
(
user_id BIGINT(20) NOT NULL DEFAULT 0 COMMENT '用戶ID',
role_id INT(11) NOT NULL DEFAULT 0 COMMENT '角色I(xiàn)D',
-- 記錄操作信息
operator_name VARCHAR(80) NOT NULL DEFAULT '' COMMENT '操作人賬號(hào)',
delete_flag TINYINT(4) NOT NULL DEFAULT 0 COMMENT '記錄刪除標(biāo)記,保留字段',
create_time DATETIME NOT NULL DEFAULT NOW() COMMENT '創(chuàng)建時(shí)間',
update_time DATETIME DEFAULT NULL ON UPDATE NOW() COMMENT '更新時(shí)間',
PRIMARY KEY (user_id, role_id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT ='用戶和角色關(guān)系表';
3.2、表結(jié)構(gòu)設(shè)計(jì)規(guī)范
? 1)表名使用項(xiàng)目簡稱前綴,這個(gè)前綴一般為3-4個(gè)字符,使用了表名前綴,數(shù)據(jù)表屬于哪個(gè)項(xiàng)目一目了然。由于不同項(xiàng)目,有一些如用戶表,角色表等通用的表,對(duì)于測(cè)試團(tuán)隊(duì)而言,有了表的前綴,只需一個(gè)測(cè)試數(shù)據(jù)庫,就可以支持多個(gè)項(xiàng)目的測(cè)試。表名一般使用復(fù)數(shù)形式后綴,因?yàn)閿?shù)據(jù)表是對(duì)象或關(guān)系的集合。
? 2)字段名采用下劃線規(guī)則,小寫字母,使用英文詞匯或易理解的英文縮寫,不能使用漢語拼音或漢語拼音縮寫,那樣會(huì)令人費(fèi)解。
? 3)字符串類型使用VARCHAR類型,長文本考慮TEXT或更長的TEXT類型。
? 4)枚舉類型一般使用TINYINT類型,更長的可考慮INT類型,且枚舉類型均在系統(tǒng)參數(shù)表定義,不建議使用字符串作為枚舉值。
? 5)除DATETIME、BLOB和TEXT類型外,均使用NOT NULL DEFAULT 形式。
? 6)TIMESTAMP類型,為4字節(jié),有至多到2038年問題,為避免類似于千年蟲問題,不再使用,使用DATETIME類型。如果需要精確到毫秒,可以用DATETIME(3),NOW默認(rèn)值對(duì)應(yīng)為NOW(3)。
? 7)所有字段都要有注釋,即有COMMENT部分。
? 8)所有表都要有記錄操作信息部分,即有operator_name、delete_flag、create_time、update_time這4個(gè)字段。operator_name值為空串,表示系統(tǒng)內(nèi)部生成的記錄(如統(tǒng)計(jì)記錄);delete_flag字段,對(duì)于對(duì)象表(如用戶表),通過delete_flag字段的值,啟用或禁用對(duì)象,不做記錄的物理刪除;對(duì)于關(guān)系表(如用戶和角色關(guān)系表),則支持物理刪除,delete_flag字段保留。create_time和update_time用于標(biāo)記記錄的生成時(shí)間和最后修改時(shí)間,insert和update時(shí)無需考慮,由數(shù)據(jù)庫自動(dòng)維護(hù),這2個(gè)字段在記錄的增量使用時(shí),非常有用,如表記錄的定時(shí)同步,或采樣數(shù)據(jù)的增量統(tǒng)計(jì)和分析。
? 9)對(duì)象表的主鍵ID字段,使用全局ID,不建議使用自增ID,因?yàn)樽栽鯥D在進(jìn)行數(shù)據(jù)庫的集群或分布式擴(kuò)容以及數(shù)據(jù)遷移時(shí),會(huì)遇到很大麻煩。
? 10)ID字段名稱,應(yīng)盡量使用符合對(duì)象身份的ID名稱,如user_id、org_id等,而不應(yīng)隨便都用id或rec_id這樣的通用名稱(特殊情況除外),這樣處理一方面可降低溝通成本,另外在多表聯(lián)結(jié)時(shí),也不必考慮字段別名以及別名與實(shí)體類屬性的映射,可降低開發(fā)成本。另外,ID字段盡量使用INTEGER或BIGINT類型,而避免使用字符串類型,這樣多表聯(lián)結(jié)查詢有性能優(yōu)勢(shì)。
? 11)要有主鍵字段定義。
? 12)要有表名注釋。
? 13)關(guān)于數(shù)據(jù)庫引擎,使用InnoDB,目前阿里云數(shù)據(jù)庫已取消了對(duì)MYISAM的支持,因?yàn)閮烧咝阅芤巡顒e不大,而MYISAM不支持事務(wù)處理,顯然不如統(tǒng)一使用InnoDB。
? 14)關(guān)于字符集,一般使用utf8,特殊場(chǎng)景,如需支持微信表情,則可考慮utf8mb4。
? 15)密碼字段,考慮信息安全,簽名密碼可使用簽名算法如MD5,第三方系統(tǒng)的賬號(hào)密碼如郵箱密碼等,使用AES算法存儲(chǔ)。
? 16)如果本表涉及數(shù)據(jù)權(quán)限,則應(yīng)添加相關(guān)數(shù)據(jù)權(quán)限字段,以方便數(shù)據(jù)權(quán)限控制,這將在數(shù)據(jù)權(quán)限討論中詳細(xì)展開。
? 表結(jié)構(gòu)設(shè)計(jì),必須有字段說明,對(duì)于Mysql數(shù)據(jù)庫而言,字段和表注釋可以直接在建表語句中加入,這樣即使不查閱表結(jié)構(gòu)設(shè)計(jì)文檔,也可利用數(shù)據(jù)庫訪問工具如Navicat等,通過查看表的DDL腳本了解各字段的含義和用處。對(duì)于如Hibernate、Golang等支持ORM(Object-Relation Mapping,對(duì)象關(guān)系映射)的框架,可以在代碼中直接添加字段,這個(gè)原則上是不允許的。沒有字段描述的表結(jié)構(gòu)DDL腳本,是不可取的,只會(huì)給后人留下太多的坑,從而大大提高了維護(hù)成本。
3.3、使用系統(tǒng)參數(shù)表管理字段枚舉值
? 關(guān)于系統(tǒng)參數(shù)表,表結(jié)構(gòu)及記錄的例子如下:
-- ----------------------------
-- Table structure for exa_sys_parameters
-- 系統(tǒng)參數(shù)表
-- ----------------------------
DROP TABLE IF EXISTS exa_sys_parameters;
CREATE TABLE exa_sys_parameters
(
class_id INT(11) NOT NULL DEFAULT 0 COMMENT '參數(shù)類別ID',
class_key VARCHAR(60) NOT NULL DEFAULT '' COMMENT '參數(shù)類別key',
class_name VARCHAR(60) NOT NULL DEFAULT '' COMMENT '參數(shù)類別名稱',
item_id INT(11) NOT NULL DEFAULT 0 COMMENT '參數(shù)類別下子項(xiàng)ID',
item_key VARCHAR(200) NOT NULL DEFAULT '' COMMENT '子項(xiàng)key',
item_name VARCHAR(60) NOT NULL DEFAULT '' COMMENT '子項(xiàng)名稱',
item_value VARCHAR(200) NOT NULL DEFAULT '' COMMENT '子項(xiàng)值',
item_desc VARCHAR(512) NOT NULL DEFAULT '' COMMENT '子項(xiàng)描述',
-- 記錄操作信息
operator_name VARCHAR(80) NOT NULL DEFAULT '' COMMENT '操作人賬號(hào)',
delete_flag TINYINT(4) NOT NULL DEFAULT 0 COMMENT '記錄刪除標(biāo)記,0-正常、1-已刪除',
create_time DATETIME NOT NULL DEFAULT NOW() COMMENT '創(chuàng)建時(shí)間',
update_time DATETIME DEFAULT NULL ON UPDATE NOW() COMMENT '更新時(shí)間',
PRIMARY KEY (class_id, item_id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT '系統(tǒng)參數(shù)表';
CREATE INDEX exa_sys_parameters_class_key_item_key ON exa_sys_parameters (class_key, item_key);
-- 10001-10099 保留給基礎(chǔ)表,即用戶、權(quán)限相關(guān)的類型定義
INSERT INTO exa_sys_parameters(class_id, class_key, class_name, item_id, item_key, item_name,item_value, item_desc)
VALUES (10001, 'user_type', '用戶類型', 1, '1', 'admin','系統(tǒng)管理員', '');
INSERT INTO exa_sys_parameters(class_id, class_key, class_name, item_id, item_key, item_name,item_value, item_desc)
VALUES (10001, 'user_type', '用戶類型', 2, '2', 'internal person','公司內(nèi)部用戶', '');
INSERT INTO exa_sys_parameters(class_id, class_key, class_name, item_id, item_key, item_name,item_value, item_desc)
VALUES (10001, 'user_type', '用戶類型', 3, '3', 'external person','外部用戶', '');
INSERT INTO exa_sys_parameters(class_id, class_key, class_name, item_id, item_key, item_name,item_value, item_desc)
VALUES (10002, 'sex', '性別', 1, '1', 'none','無值', '');
INSERT INTO exa_sys_parameters(class_id, class_key, class_name, item_id, item_key, item_name,item_value, item_desc)
VALUES (10002, 'sex', '性別', 2, '2', 'male','男', '');
INSERT INTO exa_sys_parameters(class_id, class_key, class_name, item_id, item_key, item_name,item_value, item_desc)
VALUES (10002, 'sex', '性別', 3, '3', 'female','女', '');
INSERT INTO exa_sys_parameters(class_id, class_key, class_name, item_id, item_key, item_name,item_value, item_desc)
VALUES (10002, 'sex', '性別', 4, '4', 'other','其它', '');
? 關(guān)于系統(tǒng)參數(shù)表的作用,參見:(<a target="_blank">使用系統(tǒng)參數(shù)表,提升系統(tǒng)的靈活性</a>)。
? 系統(tǒng)參數(shù)也可以復(fù)用,如true_false類別,0表示否,1表示是,這個(gè)系統(tǒng)參數(shù)類別可以為多個(gè)表的枚舉字段使用。
? 系統(tǒng)參數(shù)表的item_name可用于Java的枚舉類的枚舉項(xiàng)的名稱,這樣可提高代碼的可讀性,在接口參數(shù)校驗(yàn)時(shí),對(duì)于枚舉字段,需要使用枚舉類型進(jìn)行值的合法性檢查。在后面提到的單元測(cè)試隨機(jī)構(gòu)造測(cè)試樣本時(shí),枚舉字段的取值受此枚舉類約束。
3.4、關(guān)于全局ID算法
? 全局ID可以有多種生成方法,如基于Redis的算法,雪花算法等,此處提供了一種基于Mysql數(shù)據(jù)庫的全局ID方案,包括2張表和一個(gè)函數(shù)。
-- ----------------------------
-- Table structure for exa_table_code_config
-- ID編碼配置表
-- ----------------------------
DROP TABLE IF EXISTS exa_table_code_config;
CREATE TABLE exa_table_code_config
(
table_id INT(11) NOT NULL DEFAULT 0 COMMENT '表ID',
table_name VARCHAR(80) NOT NULL DEFAULT '' COMMENT '表名稱',
field_name VARCHAR(80) NOT NULL DEFAULT '' COMMENT 'ID字段名稱',
-- 格式化編碼使用,如15編碼為HR000015,即數(shù)字前面用0填補(bǔ)。
prefix VARCHAR(20) NOT NULL DEFAULT '' COMMENT '編碼前綴字符串',
prefix_len TINYINT(4) NOT NULL DEFAULT 0 COMMENT '編碼前綴字符串長度',
seqno_len TINYINT(4) NOT NULL DEFAULT 0 COMMENT '序列號(hào)長度',
PRIMARY KEY (table_id)
) ENGINE = Innodb
DEFAULT CHARSET = utf8 COMMENT 'ID編碼配置表';
CREATE INDEX exa_table_code_config_table_name ON exa_table_code_config(table_name);
-- 本系統(tǒng)的table_id以10001開始
INSERT INTO exa_table_code_config(table_id,table_name,field_name)
VALUES(10001,'exa_users','user_id');
INSERT INTO exa_table_code_config(table_id,table_name,field_name)
VALUES(10002,'exa_roles','role_id');
INSERT INTO exa_table_code_config(table_id,table_name,field_name)
VALUES(10004,'exa_orgnizations','org_id');
INSERT INTO exa_table_code_config(table_id,table_name,field_name)
VALUES(10005,'exa_functions','func_id');
-- ----------------------------
-- Table structure for exa_table_id_allocate
-- ID最新可用值表
-- ----------------------------
DROP TABLE IF EXISTS exa_table_id_allocate;
CREATE TABLE exa_table_id_allocate
(
table_id INT(11) NOT NULL DEFAULT 0 COMMENT '表ID',
last_id BIGINT(20) NOT NULL DEFAULT 0 COMMENT '最新可用ID值',
PRIMARY KEY (table_id)
) ENGINE = Innodb
DEFAULT CHARSET = utf8 COMMENT 'ID最新可用值表';
-- 初始記錄
-- exa_users 預(yù)留10個(gè)賬號(hào),保留給內(nèi)部系統(tǒng)使用
INSERT INTO exa_table_id_allocate(table_id,last_id) VALUES(10001,11);
INSERT INTO exa_table_id_allocate(table_id,last_id) VALUES(10002,4);
INSERT INTO exa_table_id_allocate(table_id,last_id) VALUES(10004,2);
INSERT INTO exa_table_id_allocate(table_id,last_id) VALUES(10005,1000);
-- ----------------------------------------------------------------------
-- 函數(shù):獲取全局ID
-- tableid: 表ID
-- record_count: 需要分配ID的記錄條數(shù),大于等于1
-- return: 第一條記錄的ID
-- ----------------------------------------------------------------------
DELIMITER ;
DROP FUNCTION IF EXISTS exa_get_global_id;
CREATE FUNCTION exa_get_global_id(tableid INT(11), record_count INT(11))
RETURNS BIGINT(20)
DETERMINISTIC
BEGIN
UPDATE exa_table_id_allocate
SET
last_id = (@exaid := last_id) + record_count
WHERE table_id = tableid;
RETURN @exaid;
END;
? 經(jīng)過測(cè)試,這種方法獲取的全局ID約為1000條/秒,而自增ID的獲取速度約為600-800條/秒,另外當(dāng)表的字段越多,自增ID的性能越差,而此方法的性能不受字段數(shù)目影響。
? 當(dāng)有高頻記錄ID需要處理時(shí),針對(duì)這類ID,可由服務(wù)器每次批量領(lǐng)取一定數(shù)目的ID,如1000個(gè)連續(xù)ID,服務(wù)器內(nèi)部使用內(nèi)存分發(fā)ID,這樣就可以達(dá)到100萬條/秒的處理性能,有點(diǎn)類似于雪花算法了,這與服務(wù)器是否分布式部署無關(guān)。
? 注意:@exaid為Mysql的全局變量,因此不同項(xiàng)目需要使用不同的變量名。
3.5、關(guān)于字段名下劃線到屬性字段駝峰的映射
? Java的實(shí)體類屬性字段名,一般使用駝峰規(guī)則。這樣就涉及到下劃線到駝峰的映射。
? 首先,配置文件需要設(shè)置mybatis使用下劃線轉(zhuǎn)駝峰的映射:
mybatis.configuration.map-underscore-to-camel-case=true
? 這樣,表字段名可以映射實(shí)體類的屬性名。
? 另外,需要注意的是,對(duì)于返回類型為Map<String, Object>或List<Map<String, Object>>的查詢,字段名不會(huì)從下劃線轉(zhuǎn)為駝峰(參見:<a target="_blank">mybatis Map查詢結(jié)果下劃線轉(zhuǎn)駝峰的實(shí)例</a>)。
? 這里提供另外一種解決思路,即開發(fā)下列三個(gè)公共方法:
// 下劃線字符串轉(zhuǎn)為駝峰字符串
public static String underlineToCamel(String input);
// key為下劃線字符串轉(zhuǎn)為key為駝峰字符串的字典
public static Map<String, Object> underlineToCamel(Map<String, Object> map);
// key為下劃線字符串轉(zhuǎn)為key為駝峰字符串的字典列表
public static List<Map<String, Object>> underlineToCamel(List<Map<String, Object>> mapList);
? 對(duì)于返回類型為Map<String, Object>或List<Map<String, Object>>的查詢結(jié)果,先調(diào)用underlineToCamel方法處理,然后就可以用屬性名訪問了。
3.6、權(quán)限管理
? 權(quán)限管理與數(shù)據(jù)庫設(shè)計(jì)有關(guān)。
? 權(quán)限管理分兩個(gè)層面,一個(gè)是功能權(quán)限,一個(gè)是數(shù)據(jù)權(quán)限。
3.6.1、功能權(quán)限管理
? 功能權(quán)限是用戶可以觀察和操作的頁面元素的權(quán)限,包括菜單權(quán)限和功能操作權(quán)限,這個(gè)通常是基于RBAC(Role-Based Access Control),即基于角色的訪問控制,就是角色表、功能表、角色與功能關(guān)系表、用戶與角色關(guān)系表,來定義功能項(xiàng),角色項(xiàng),每種角色可以操作的功能集合,用戶擁有的角色集合,從而得到用戶可以操作的功能集合。
? 下面是相關(guān)表結(jié)構(gòu):
-- ----------------------------
-- Table structure for exa_functions
-- 功能表
-- ----------------------------
DROP TABLE IF EXISTS exa_functions;
CREATE TABLE exa_functions
(
func_id INT(11) NOT NULL DEFAULT 0 COMMENT '功能ID',
func_name VARCHAR(100) NOT NULL DEFAULT '' COMMENT '功能名稱',
parent_id INT(11) NOT NULL DEFAULT 0 COMMENT '父功能ID',
level TINYINT(4) NOT NULL DEFAULT 0 COMMENT '功能所在層級(jí)',
order_no INT(11) NOT NULL DEFAULT 0 COMMENT '顯示順序',
url VARCHAR(80) NOT NULL DEFAULT '' COMMENT '訪問接口url',
dom_key VARCHAR(80) NOT NULL DEFAULT '' COMMENT 'dom對(duì)象的ID',
img_tag VARCHAR(80) NOT NULL DEFAULT '' COMMENT '節(jié)點(diǎn)icon名稱',
remark VARCHAR(200) NOT NULL DEFAULT '' COMMENT '備注',
-- 記錄操作信息
operator_name VARCHAR(80) NOT NULL DEFAULT '' COMMENT '操作人賬號(hào)',
delete_flag TINYINT(4) NOT NULL DEFAULT 0 COMMENT '記錄刪除標(biāo)記,0-正常、1-已刪除',
create_time DATETIME NOT NULL DEFAULT NOW() COMMENT '創(chuàng)建時(shí)間',
update_time DATETIME DEFAULT NULL ON UPDATE NOW() COMMENT '更新時(shí)間',
PRIMARY KEY (func_id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT ='功能表';
-- ----------------------------
-- Table structure for exa_roles
-- 角色表
-- ----------------------------
DROP TABLE IF EXISTS exa_roles;
CREATE TABLE exa_roles
(
role_id INT(11) NOT NULL DEFAULT 0 COMMENT '角色I(xiàn)D',
role_name VARCHAR(40) NOT NULL DEFAULT '' COMMENT '角色名稱',
role_type TINYINT(4) NOT NULL DEFAULT 0 COMMENT '角色類型,參見系統(tǒng)參數(shù)表',
remark VARCHAR(100) NOT NULL DEFAULT '' COMMENT '描述',
operator_name VARCHAR(80) NOT NULL DEFAULT '' COMMENT '操作人賬號(hào)',
delete_flag TINYINT(4) NOT NULL DEFAULT 0 COMMENT '記錄刪除標(biāo)記,0-正常、1-已刪除',
create_time DATETIME NOT NULL DEFAULT NOW() COMMENT '創(chuàng)建時(shí)間',
update_time DATETIME DEFAULT NULL ON UPDATE NOW() COMMENT '更新時(shí)間',
PRIMARY KEY (role_id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT ='角色表';
INSERT INTO exa_roles(role_id,role_name,role_type) VALUES(1,'系統(tǒng)管理員',1);
INSERT INTO exa_roles(role_id,role_name,role_type) VALUES(2,'賬號(hào)管理員',2);
INSERT INTO exa_roles(role_id,role_name,role_type) VALUES(3,'運(yùn)維人員',3);
-- ----------------------------
-- Table structure for exa_role_func_rights
-- 角色和功能權(quán)限關(guān)系表
-- 角色和功能權(quán)限是多對(duì)多關(guān)系
-- ----------------------------
DROP TABLE IF EXISTS exa_role_func_rights;
CREATE TABLE exa_role_func_rights
(
role_id INT(11) NOT NULL DEFAULT 0 COMMENT '角色I(xiàn)D',
func_id INT(11) NOT NULL DEFAULT 0 COMMENT '功能ID',
sub_full_flag TINYINT(4) NOT NULL DEFAULT 0 COMMENT '是否包含全部子節(jié)點(diǎn)權(quán)限,0-否,1-是',
operator_name VARCHAR(80) NOT NULL DEFAULT '' COMMENT '操作人賬號(hào)',
delete_flag TINYINT(4) NOT NULL DEFAULT 0 COMMENT '記錄刪除標(biāo)記,保留字段',
create_time DATETIME NOT NULL DEFAULT NOW() COMMENT '創(chuàng)建時(shí)間',
update_time DATETIME DEFAULT NULL ON UPDATE NOW() COMMENT '更新時(shí)間',
PRIMARY KEY (role_id, func_id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT ='角色和功能權(quán)限關(guān)系表';
? 功能表的URL字段,用于后端訪問控制,一般使用AOP來實(shí)現(xiàn)。前端使用dom_key字段(即element的id屬性),對(duì)于Vue等單頁面框架,不同模塊可以重復(fù)使用相同的dom_key值,如新增按鈕都使用"add";對(duì)于非單頁面框架,如bootstrap的js框架等,要注意確保dom_key值的唯一性。
? 關(guān)于Vue前端的訪問控制,參見:<a target="_blank">Vue前端訪問控制方案</a>和<a target="_blank">Vue 前端權(quán)限控制的優(yōu)化改進(jìn)版</a>。
? 功能權(quán)限管理與CRUD關(guān)系不大,各個(gè)模塊無需維護(hù)。
3.6.2、數(shù)據(jù)權(quán)限管理
? 對(duì)于某項(xiàng)功能,如果有功能權(quán)限時(shí),所有用戶看到或操作的數(shù)據(jù)集如果是一樣的,則無需數(shù)據(jù)權(quán)限控制,只需要功能權(quán)限控制即可。如果不同用戶,使用相同的功能,可訪問的數(shù)據(jù)集不同,則需要數(shù)據(jù)權(quán)限。
? 數(shù)據(jù)權(quán)限是用戶可以觀察和操作的數(shù)據(jù)集的權(quán)限,這個(gè)也稱為RBAC(Resource-Based Access Control),即基于資源的訪問控制,為了區(qū)別于基于角色的訪問控制,我將之定義為R2BAC。
? 如果不同組織有各自的賬號(hào)管理員,其可以分配管理自己組織的用戶賬號(hào),但不能看到和操作其它組織的用戶賬號(hào),這就是數(shù)據(jù)權(quán)限的典型例子。
? 數(shù)據(jù)權(quán)限,有“功能+行+列”3個(gè)維度。功能維度,即不同操作功能可訪問的數(shù)據(jù)集不同,如某類賬號(hào)管理員可以查詢本組織的所有賬號(hào),但只能編輯由其創(chuàng)建的那些賬號(hào)。行維度,為數(shù)據(jù)記錄集合,即可以訪問哪些記錄。列維度,為字段集合,如對(duì)某些用戶,可以看到全部字段;對(duì)另外一些用戶,某些字段不顯示。
? 功能維度,對(duì)于大部分應(yīng)用,不需要,即增刪改查的數(shù)據(jù)集是相同的(撇開功能權(quán)限)。如需要,配置數(shù)據(jù)權(quán)限的功能ID。
? 行維度,即數(shù)據(jù)篩選,實(shí)際上是定義數(shù)據(jù)過濾的條件。數(shù)據(jù)過濾條件可以非常復(fù)雜,復(fù)雜的過濾條件一般直接用代碼層面控制,而常用的數(shù)據(jù)過濾條件一般是記錄的某個(gè)或某幾個(gè)ID字段的取值范圍,相當(dāng)于sql語句的"in (...)",下面給出的設(shè)計(jì)方案是ID字段的行維度數(shù)據(jù)權(quán)限訪問控制。
? 列維度,可以用數(shù)據(jù)權(quán)限字段過濾表來定義,但由于不大常用,一般也不用,而是直接用代碼層面控制,將相關(guān)過濾的屬性值設(shè)置為null即可。
? 在CRUD代碼實(shí)現(xiàn)時(shí),需要加入數(shù)據(jù)權(quán)限的校驗(yàn)和過濾。如對(duì)組織的賬號(hào)管理員,新增時(shí),需要判斷orgId是否在許可集中;編輯時(shí),原記錄的orgId是否在許可集中,如修改了orgId,新的orgId是否在許可集中;刪除時(shí),需要判斷orgId是否在許可集中;查詢時(shí),需要將權(quán)限許可的orgIdList加入查詢條件,或查詢后,檢查orgId是否在許可集中。
? 下面是數(shù)據(jù)權(quán)限行維度訪問控制的相關(guān)表:
-- ----------------------------
-- Table structure for exa_dr_fields
-- 數(shù)據(jù)權(quán)限相關(guān)字段表
-- 為簡化處理,要求權(quán)限相關(guān)字段,在所有表中具有相同的定義和含義
-- ----------------------------
DROP TABLE IF EXISTS exa_dr_fields;
CREATE TABLE exa_dr_fields
(
field_id INT(11) NOT NULL DEFAULT 0 COMMENT '字段ID',
field_name VARCHAR(80) NOT NULL DEFAULT '' COMMENT '字段名稱',
prop_name VARCHAR(80) NOT NULL DEFAULT '' COMMENT '屬性名稱',
invalid_value VARCHAR(20) NOT NULL DEFAULT '' COMMENT '無效值,用于ID字段無權(quán)限時(shí)的查詢條件',
has_sub TINYINT(4) NOT NULL DEFAULT 0 COMMENT '是否有下級(jí)對(duì)象,0-否,1-是',
is_user_prop TINYINT(4) NOT NULL DEFAULT 0 COMMENT '是否為用戶屬性字段,0-否,1-是',
is_id TINYINT(4) NOT NULL DEFAULT 0 COMMENT '是否為ID字段,即起源表的主鍵,0-否,1-是',
remark VARCHAR(200) NOT NULL DEFAULT '' COMMENT '備注',
-- 記錄操作信息
operator_name VARCHAR(80) NOT NULL DEFAULT '' COMMENT '操作人賬號(hào)',
delete_flag TINYINT(4) NOT NULL DEFAULT 0 COMMENT '記錄標(biāo)記,,0-正常、1-已刪除',
create_time DATETIME NOT NULL DEFAULT NOW() COMMENT '創(chuàng)建時(shí)間',
update_time DATETIME DEFAULT NULL ON UPDATE NOW() COMMENT '更新時(shí)間',
PRIMARY KEY (field_id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT ='數(shù)據(jù)權(quán)限相關(guān)字段表';
INSERT INTO exa_dr_fields(field_id,field_name,prop_name,invalid_value,has_sub,is_user_prop,is_id)
VALUES(1,'org_id','orgId','-1',1,1,1);
-- ----------------------------
-- Table structure for exa_user_drs
-- 用戶數(shù)據(jù)權(quán)限表
-- 用戶對(duì)各個(gè)權(quán)限相關(guān)字段,應(yīng)都有一條記錄,如無記錄,表示無權(quán)限
-- ----------------------------
DROP TABLE IF EXISTS exa_user_drs;
CREATE TABLE exa_user_drs
(
user_id BIGINT(20) NOT NULL DEFAULT 0 COMMENT '用戶ID',
field_id INT(11) NOT NULL DEFAULT 0 COMMENT '字段ID',
field_name VARCHAR(80) NOT NULL DEFAULT '' COMMENT '字段名',
-- 如果字段為用戶屬性字段,則允許使用默認(rèn)規(guī)則,否則不允許
dr_type TINYINT(4) NOT NULL DEFAULT 1 COMMENT '數(shù)據(jù)權(quán)限類型,1-默認(rèn)規(guī)則、2-自定義、3-全部',
-- 如果字段不是ID類型,可以使用表達(dá)式,目前暫時(shí)不用
expr VARCHAR(255) NOT NULL DEFAULT '' COMMENT '表達(dá)式',
func_id INT(11) NOT NULL DEFAULT 0 COMMENT '功能ID,0-所有功能',
-- 記錄操作信息
operator_name VARCHAR(80) NOT NULL DEFAULT '' COMMENT '操作人賬號(hào)',
delete_flag TINYINT(4) NOT NULL DEFAULT 0 COMMENT '記錄標(biāo)記,保留',
create_time DATETIME NOT NULL DEFAULT NOW() COMMENT '創(chuàng)建時(shí)間',
update_time DATETIME DEFAULT NULL ON UPDATE NOW() COMMENT '更新時(shí)間',
PRIMARY KEY (user_id,field_id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT ='數(shù)據(jù)權(quán)限規(guī)則表';
-- ----------------------------
-- Table structure for exa_user_custom_drs
-- 用戶自定義數(shù)據(jù)權(quán)限表
-- 僅針對(duì)數(shù)據(jù)權(quán)限類型為自定義的用戶數(shù)據(jù)權(quán)限記錄
-- 由于不確定有多少個(gè)自定義權(quán)限字段,因此使用了記錄ID作為主鍵,方便增加字段
-- ----------------------------
DROP TABLE IF EXISTS exa_user_custom_drs;
CREATE TABLE exa_user_custom_drs
(
user_id BIGINT(20) NOT NULL DEFAULT 0 COMMENT '用戶ID',
field_id INT(11) NOT NULL DEFAULT 0 COMMENT '字段ID',
field_value INT(11) NOT NULL DEFAULT 0 COMMENT '字段值',
-- 記錄操作信息
operator_name VARCHAR(80) NOT NULL DEFAULT '' COMMENT '操作人賬號(hào)',
delete_flag TINYINT(4) NOT NULL DEFAULT 0 COMMENT '記錄標(biāo)記,保留',
create_time DATETIME NOT NULL DEFAULT NOW() COMMENT '創(chuàng)建時(shí)間',
update_time DATETIME DEFAULT NULL ON UPDATE NOW() COMMENT '更新時(shí)間',
PRIMARY KEY (user_id,field_id,field_value)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT ='用戶數(shù)據(jù)權(quán)限關(guān)系表';
? 使用了三個(gè)表來管理數(shù)據(jù)權(quán)限,如果需要列維度的數(shù)據(jù)權(quán)限,則還需要增加數(shù)據(jù)權(quán)限字段過濾表。
? 數(shù)據(jù)權(quán)限相關(guān)字段表exa_dr_fields,用于定義數(shù)據(jù)權(quán)限相關(guān)的字段,包括數(shù)據(jù)庫字段名稱,對(duì)象屬性名。為方便數(shù)據(jù)權(quán)限管理,這個(gè)名稱和含義是整個(gè)系統(tǒng)一致的,如org_id,表示組織ID,則所有表中如包含org_id字段的,其含義必須是組織ID。
? invalid_value為字段的某個(gè)無效值,如ID字段一般大于0,但有時(shí)0表示全部,所以一般取-1表示ID的無效值。當(dāng)對(duì)該字段無權(quán)限時(shí),使用無效值,如orgIdList為[-1],查詢條件為"org_id in (-1)",查詢到的結(jié)果集為空集,表示無權(quán)限。
? has_sub,表示是否有下級(jí)對(duì)象,即ID字段代表的對(duì)象是否支持樹型結(jié)構(gòu),如組織對(duì)象,支持下級(jí)對(duì)象;而班級(jí)對(duì)象,則不支持下級(jí)對(duì)象。為了方便權(quán)限設(shè)置,如果包含所有下級(jí)對(duì)象的權(quán)限,只需設(shè)置父級(jí)對(duì)象的權(quán)限,而不必一一設(shè)置。
? is_user_prop,是否為用戶屬性字段,如果為用戶屬性字段(用戶表或用戶擴(kuò)展表,用戶的某種屬性值),則可以使用默認(rèn)權(quán)限。如用戶只能屬于某一個(gè)組織,則org_id為用戶屬性字段;如果用戶可以屬于某幾個(gè)組織,則用戶表的組織ID屬性失去意義,就不能使用默認(rèn)數(shù)據(jù)權(quán)限。
? is_id,是否為ID字段,即起源表的主鍵,即為某種對(duì)象的ID。目前所有權(quán)限字段都是ID字段,該字段是為了保留擴(kuò)展的可能性(如需要增加數(shù)據(jù)權(quán)限字段過濾表,則過濾字段一般就不是ID字段)。
? 用戶數(shù)據(jù)權(quán)限表exa_user_drs,用于定義對(duì)不同數(shù)據(jù)權(quán)限字段和功能ID的數(shù)據(jù)權(quán)限類型。
? dr_type,數(shù)據(jù)權(quán)限類型,1-默認(rèn)規(guī)則、2-自定義、3-全部。如果字段為用戶屬性字段,則可以使用“默認(rèn)規(guī)則”。如用戶只能屬于某一個(gè)組織,則org_id為用戶屬性字段,默認(rèn)規(guī)則就是用戶可以訪問本組織及下屬組織的所有數(shù)據(jù)?!白远x”,就是權(quán)限范圍通過用戶自定義數(shù)據(jù)權(quán)限表exa_user_custom_drs來配置,如未配置,則無數(shù)據(jù)權(quán)限(樹型對(duì)象默認(rèn)包含全部下級(jí)對(duì)象,下級(jí)對(duì)象可不配置)?!叭俊保褪菍?duì)此字段不進(jìn)行過濾。數(shù)據(jù)權(quán)限類型支持默認(rèn)規(guī)則和全部,可以方便數(shù)據(jù)配置,減少維護(hù)工作量。如支持默認(rèn)規(guī)則,則本組織下級(jí)增加一個(gè)子組織,該用戶自動(dòng)擁有對(duì)新增子組織的訪問權(quán)限,而無需在用戶自定義數(shù)據(jù)權(quán)限表中配置。
? func_id,功能ID,用于功能維度,0表示全部。每個(gè)用戶,在每個(gè)權(quán)限字段上,都需要配置一條數(shù)據(jù)權(quán)限記錄,功能ID為0,用于不特別指定功能ID的情況下的數(shù)據(jù)權(quán)限配置。如某些功能需要特別數(shù)據(jù)權(quán)限,則再加上特定功能ID的數(shù)據(jù)權(quán)限記錄。
? expr,用于sql條件表達(dá)式,為擴(kuò)展保留,未使用。
? 用戶自定義數(shù)據(jù)權(quán)限表exa_user_custom_drs,用于數(shù)據(jù)權(quán)限類型為自定義時(shí),ID字段的取值范圍。
? field_value,字段值,不同ID字段的數(shù)據(jù)類型,此處使用整數(shù)型,如有Long型,則需要修改字段數(shù)據(jù)類型。
? 假設(shè)系統(tǒng)需要進(jìn)行數(shù)據(jù)權(quán)限控制的字段集為A={d1,d2,...,dn},某個(gè)表T如需要進(jìn)行數(shù)據(jù)權(quán)限控制,則其需要包含A的某個(gè)非空子集的字段。如果表T包括多個(gè)數(shù)據(jù)權(quán)限字段,則數(shù)據(jù)權(quán)限為交集,相當(dāng)于"d1 in (...) and d2 in (...)"。為了方便查詢過濾,表結(jié)構(gòu)設(shè)計(jì)時(shí),應(yīng)該增加相關(guān)數(shù)據(jù)權(quán)限字段的冗余設(shè)計(jì)。這樣查詢T時(shí),可以使用如:"di in (...)"形式的查詢條件,而不必進(jìn)行表的聯(lián)結(jié),并且冗余設(shè)計(jì)后,由于實(shí)體類對(duì)象包含了相關(guān)數(shù)據(jù)權(quán)限字段,也便于代碼實(shí)現(xiàn)。
4、Entity實(shí)體類 ?
4.1、實(shí)體類代碼示例
? Entity實(shí)體類的代碼示例如下:
package com.abc.example.entity;
import java.time.LocalDate;
import java.time.LocalDateTime;
import javax.persistence.Column;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* @className : User
* @description : 用戶對(duì)象實(shí)體類
* @summary :
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* yyyy/mm/dd 1.0.0 author 初版
*
*/
@Data
public class User {
// 用戶ID
@Column(name = "user_id")
@JsonFormat(shape= JsonFormat.Shape.STRING)
private Long userId = 0L;
// 用戶名
@Column(name = "user_name")
private String userName = "";
// 用戶密碼
@Column(name = "password")
private String password = "";
// 加鹽md5算法中的鹽
@Column(name = "salt")
private String salt = "";
// 用戶類型,1-系統(tǒng)管理員、2-公司內(nèi)部用戶、3-外部用戶
@Column(name = "user_type")
private Byte userType = 3;
// 組織機(jī)構(gòu)ID
@Column(name = "org_id")
private Integer orgId = 0;
// 組織名稱
private String orgName = "";
// 真實(shí)姓名
@Column(name = "real_name")
private String realName = "";
// Email
@Column(name = "email")
private String email = "";
// 手機(jī)號(hào)碼
@Column(name = "phone_number")
private String phoneNumber = "";
// 性別,1-無值、2-男、3-女、4-其它
@Column(name = "sex")
private Byte sex = 1;
// 生日
@Column(name = "birth")
@JsonFormat(shape= JsonFormat.Shape.STRING, pattern="yyyy-MM-dd")
private LocalDate birth;
// 身份證號(hào)碼
@Column(name = "id_no")
private String idNo = "";
// 微信小程序的openid
@Column(name = "open_id")
private String openId = "";
// 微信公眾號(hào)openid
@Column(name = "woa_openid")
private String woaOpenid = "";
// 備注
@Column(name = "remark")
private String remark = "";
// 操作人賬號(hào)
@Column(name = "operator_name")
private String operatorName = "";
// 記錄刪除標(biāo)記,0-正常、1-禁用
@Column(name = "delete_flag")
private Byte deleteFlag = 0;
// 創(chuàng)建時(shí)間
@Column(name = "create_time")
@JsonFormat(shape= JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
// 更新時(shí)間
@Column(name = "update_time")
@JsonFormat(shape= JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (userName == null) {
if (other.userName != null)
return false;
} else if (!userName.equals(other.userName))
return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((userName == null) ? 0 : userName.hashCode());
return result;
}
}
4.2、實(shí)體類開發(fā)規(guī)范
? 1)一般使用lombok的@Data注解,以替代getter/setter方法的冗長代碼。
? 2)屬性字段名使用駝峰規(guī)則,要有對(duì)應(yīng)的數(shù)據(jù)表字段名,以及屬性的描述。數(shù)據(jù)表字段名可使用JPA的@Column來表述,其它添加屬性字段則不需要@Column注解。
? 3)默認(rèn)值,這個(gè)要與表結(jié)構(gòu)一致,否則實(shí)現(xiàn)Mybatis腳本insert語句會(huì)比較復(fù)雜。
? 4)不同數(shù)據(jù)類型的前端支持問題,如LocalDateTime的格式化問題,前端Long型數(shù)據(jù)損失精度問題等,這些需要用@JsonFormat進(jìn)行注解。
? 5)與數(shù)據(jù)庫字段對(duì)應(yīng)的屬性字段,其數(shù)據(jù)類型使用類的形式,如Integer,Long,而不是int,long,這樣可以賦值為null??紤]到信息安全及數(shù)據(jù)權(quán)限,某些屬性不希望展示給前端,此時(shí)可以設(shè)置為null。需要注意的是,數(shù)據(jù)類型使用類的形式,類對(duì)象之間比較,==和!=操作符會(huì)失效,要使用equals方法來比較值。
? 6)是否需要支持對(duì)象克隆,如需支持,需要實(shí)現(xiàn)Cloneable的clone接口,支持克隆可大大簡化對(duì)象復(fù)制的處理代碼。
? 7)是否需要支持對(duì)象比較,如果實(shí)體類對(duì)象要作為Map的key,則需要實(shí)現(xiàn)equals和hashCode接口,這個(gè)可以使用IDE工具自動(dòng)生成代碼。
? 8)是否需要支持格式化輸出,如作為TreeNode的節(jié)點(diǎn)數(shù)據(jù),則需要實(shí)現(xiàn)toString接口。
? 9)是否需要增加其它表的引用字段,如orgId,需要引用組織表的orgName字段,否則前端不好展示。
? 10)是否有一些內(nèi)部字段,不希望傳到前端,可考慮使用@JsonIgnore進(jìn)行注解。
? 11)考慮線程安全,日期時(shí)間類型不再使用Date類型,而是使用LocalDateTime,LocalDate,LocalTime。另外,關(guān)于時(shí)間,涉及到時(shí)區(qū)問題,關(guān)于Mysql驅(qū)動(dòng)的配置項(xiàng)應(yīng)考慮GMT+8,即:
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/demodb?verifyServerCertificate=false&useSSL=false&characterEncoding=UTF-8&serverTimezone=GMT%2B8
? 12)是否需要支持Excel導(dǎo)入,如需支持,應(yīng)增加一個(gè)equals和hashCode方法,作為在導(dǎo)入數(shù)據(jù)時(shí)的異常數(shù)據(jù)行的定位,這個(gè)在后面Excel導(dǎo)入功能時(shí)將詳細(xì)討論。
(未完待續(xù)...)
文章來源地址http://www.zghlxwxcb.cn/news/detail-478860.html
文章來源:http://www.zghlxwxcb.cn/news/detail-478860.html
到了這里,關(guān)于Spring Boot實(shí)現(xiàn)高質(zhì)量的CRUD-1的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!