SpringCloud微服務(wù)架構(gòu)實戰(zhàn):商家權(quán)限體系設(shè)計及開發(fā)
商家管理后臺與sso設(shè)計
在本文的電商平臺實例中,商家是這個平臺的主角,商家管理后臺是專門為這個主角提供的一個安全可靠的操作平臺。在商家管理后臺中,商家可以進(jìn)行商品管理、訂單管理、物流管理、會員管理、評價管理等各個方面的管理工作。這些管理功能及其服務(wù)功能分別由不同的微服務(wù)項目實現(xiàn),并通過不同的應(yīng)用進(jìn)行部署?,F(xiàn)在我們要做的,就是將這些分布在不同應(yīng)用中的管理功能,組成一個具有相同訪問控制設(shè)計的管理后臺。
單點登錄(Single Sign On,SSO)設(shè)計可以將這種分散的應(yīng)用,通過統(tǒng)一的訪問控制和權(quán)限管理,整合成一個有機(jī)整體,為分布式環(huán)境中的不同應(yīng)用,提供一個統(tǒng)一的登錄控制和授權(quán)認(rèn)證管理。商家管理員只需在任何一個應(yīng)用中登錄一次,就可以得到使用其他應(yīng)用的權(quán)限。所以,不管商家管理后臺的功能由多少個微服務(wù)應(yīng)用組成,對于一個商家管理員來說,它始終只是一個完整的平臺。
商家管理后臺的設(shè)計和開發(fā)主要由商家管理開發(fā)和SSO開發(fā)兩部分組成。其中,商家管理開發(fā)主要包含商家信息管理及其權(quán)限體系設(shè)計兩部分。
這些設(shè)計集中在商家管理微服務(wù)項目merchant-microservice 中進(jìn)行開發(fā),完整的源代碼可以從本書源代碼中下載,本章的實例對應(yīng)分支V2.1。
商家及其權(quán)限體系設(shè)計由 merchant-object、merchant-domain、merchant-restapi、merchant-client和 merchant-web等模塊組成。SSO設(shè)計由merchant-sso模塊和merchant-security模塊組成。SSO的客戶端接入可通過merchant-web模塊進(jìn)行體驗。

商家權(quán)限體系的設(shè)計及開發(fā)
商家權(quán)限體系設(shè)計由權(quán)限管理模型和菜單管理模型兩大功能模型組成。其中,權(quán)限管理模型包含商家、用戶、角色等實體設(shè)計,菜單管理模型包含資源、模塊、分類等實體設(shè)計。兩大模型之間通過角色與資源的關(guān)聯(lián)關(guān)系,組成一個完整的權(quán)限菜單體系結(jié)構(gòu),如圖10-1所示。

在圖10-1中,實體之間的關(guān)聯(lián)關(guān)系使用單向關(guān)聯(lián)設(shè)計,關(guān)聯(lián)關(guān)系如下所示:
用戶從屬于商家,是多對一的關(guān)聯(lián)關(guān)系。
用戶擁有角色,是多對多的關(guān)聯(lián)關(guān)系。
角色擁有資源,是多對多的關(guān)聯(lián)關(guān)系。
資源從屬于模塊,是多對一的關(guān)聯(lián)關(guān)系。
模塊從屬于分類,是多對一的關(guān)聯(lián)關(guān)系。
在圖10-1所示的關(guān)聯(lián)關(guān)系中,箭頭所指一方為關(guān)聯(lián)關(guān)系的主鍵,另一方為外鍵。其中,用戶與角色、角色與資源分別使用一個中間表來存儲關(guān)聯(lián)關(guān)系。
這些對象所對應(yīng)的物理模型,經(jīng)過PowerDesigner 設(shè)計之后,最后完成的表格定義及其關(guān)聯(lián)關(guān)系如圖10-2所示。

在圖10-2中,商家、用戶、角色、資源、模塊和分類等表格分別為t_merchant、
t_usert_role.t_resource.t_model和 t_kind,用戶與角色、角色與資源的關(guān)聯(lián)關(guān)系的表格分別為user_role和role_resource。此外,表格persistent_logins是在用戶處于登錄狀態(tài)時,用來存儲臨時數(shù)據(jù)的。
權(quán)限管理模型設(shè)計
權(quán)限管理模型主要由商家、用戶、角色、資源、模塊和分類等實體組成。下面對這些實體分別進(jìn)行簡要說明。
商家實體主要由ID、名稱、郵箱、電話、地址、聯(lián)系人和創(chuàng)建日期等屬性組成,實現(xiàn)代碼如下所示:
@Entity
@Table(name = "t merchant")
public class Merchant implements java.io.Serializable{
@Id
@GeneratedValue(strategy =GenerationType.IDENTITY)private Long id;
private String name;private String email;private String phone;private String address;private String linkman;
@DateTimeFormat(pattern = "yyyY-MM-dd HH:mm:ss")
@column (name = "created",columnDefinition = "timestamp defaultCurrent timestamp")
@Temporal (TemporalType.TIMESTAMP)private Date created;
// 0neToMany (cascade ={ },mappedBy ="merchant")// private List users;
public Merchant() {
}
...
}用戶實體主要由ID、名稱、密碼、郵箱、性別和創(chuàng)建日期等屬性組成,實現(xiàn)代碼如下所示:
@Entity
@Table(name = "tuser")
public class User implements java.io.Serializable{
@Id
@GeneratedValue (strategy = GenerationType.IDENTITY)private Long id;
private String name;private String password;private String email;
eColumn (name = "sex",length= 1,columnDefinition = "tinyint")private Integer sex;
@DateTimeFormat (pattern= "YyyY-MM-dd HH:mm: ss")
eColumn (name = "created", columnDefinition = "timestamp defaultcurrent timestamp ")
@Temporal (TemporalType.TIMESTAMP)private Date created;
@ManyToMany (cascade = {,fetch = FetchType.EAGER)@JoinTable(name = "user role",
joinColumns ={@JoinColumn (name = "user id")],
inverseJoinColumns -{GJoinColumn (name = "role_id")})
private List roles;
@ManyTo0ne
CJoinColumn(name = "merchant_id")@JsonIgnore
private Merchant merchant;
public User() {
}
...
} 其中,@ManyToMany是一個多對多的正向關(guān)聯(lián)關(guān)系,這里使用一個中間表user_role保存關(guān)聯(lián)關(guān)系的數(shù)據(jù)。
@ManyToOne是一個反向關(guān)聯(lián)設(shè)計,即使用mercnant_1a TFAh廣oPIl Mo體建立關(guān)聯(lián)關(guān)系。
角色實體由ID、名稱和創(chuàng)建日期等屬性組成,實現(xiàn)代碼如下所示:
@Entity
@Table(name ="t role")
public class Role implements java.io.Serializable{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;
private String name;
@DateTimeFormat (pattern = "yyyy-MM-dd HH:mm:ss")
eColumn(name = "created",columnDefinition = "timestamp defaultcurrent timestamp ")
@Temporal (TemporalType.TIMESTAMP)private Date created;
@ManyToMany(cascade = {}, fetch = FetchType.EAGER)
@JoinTable(name = "role resource",
joinColumns = {@JoinColumn (name - "role_id")},
inverseJoinColumns = {@JoinColumn (name = "resource_id")))
private List resources;
public Role() {
}
...
} 角色實體與資源實體是一個多對多的關(guān)聯(lián)關(guān)系,因此使用@ManyToMany進(jìn)行設(shè)置。通過這種關(guān)聯(lián)關(guān)系,可以將權(quán)限管理模型與菜單管理模型組成一個完整的商家權(quán)限體系。
資源實體由ID、名稱、統(tǒng)一資源定位和創(chuàng)建日期等屬性組成,實現(xiàn)代碼如下所示:
@Entity
@Table(name ="t resource")
public class Resource implements java.io.Serializable {
@Id
@GeneratedValue (strategy =GenerationType.IDENTITY)private Long id;
private string name;private string url;
@DateTimeFormat (pattern = "yyyy-MM-dd HH:mm:ss")
@Column (name = "created", columnDefinition = "timestamp defaultcurrent timestamp")
@Temporal(TemporalType.TIMESTAMP)private Date created;
@ManyToOne
@JoinColumn(name = "mid")@JsonManagedReference
private Model model;
public Resource() {
}
...
}資源實體與模塊的關(guān)聯(lián)關(guān)系同樣使用@ManyToOne進(jìn)行反向關(guān)聯(lián)設(shè)計,這與用戶與商家的關(guān)聯(lián)關(guān)系的設(shè)計原理相同。
模塊實體由ID、名稱、主機(jī)、圖標(biāo)和創(chuàng)建日期等屬性組成,實現(xiàn)代碼如下所示:
@Entity
@Table(name ="tmodel")
public class Model implements java.io.Serializable{
@Id
@Generatedvalue(strategy -GenerationType.IDENTITY)private Long id;
private String name;private String host;private String icon;
@DateTimeFormat (pattern = "yyyy-MM-dd HH:mm:ss")
eColumn (name = "created", columnDefinition = "timestamp defaultcurrent timestamp")
@Temporal (TemporalType.TIMESTAMP)private Date created;
@ManyTo0ne
@JoinColumn (name = "kid")@JsonIgnore
private Kind kind;
public Model() {}
...
}模塊實體的關(guān)聯(lián)關(guān)系設(shè)計與資源實體的關(guān)聯(lián)設(shè)計一樣,也是使用@ManyToOne進(jìn)行反向關(guān)聯(lián)設(shè)計的。
分類實體由ID、名稱、鏈接服務(wù)和創(chuàng)建日期等屬性組成,實現(xiàn)代碼如下所示:
@Entity
@Table(name = "t kind")
public class Kind implements java.io. Serializable{
@Id
@Generatedvalue (strategy =GenerationType.IDENTITY)private Long id;
private String name;private String link;
@DateTimeFormat (pattern= "yyyy-MM-dd HH:mm:ss")
cColumn (name = "created", columnDefinition = "timestamp defaultcurrent timestamp")
@Temporal (TemporalType.TIMESTAMP)private Date created;
public Kind() {}
...
}分類實體在菜單模型結(jié)構(gòu)中是一個頂級菜單,所以不需要進(jìn)行關(guān)聯(lián)設(shè)計。
單向關(guān)聯(lián)設(shè)計可以提高數(shù)據(jù)的訪問性能,但也有不足的地方。比如,在角色實體中,已經(jīng)實現(xiàn)了角色實體與資源實體的單向關(guān)聯(lián)設(shè)計,因此從角色實體中查詢資源列表,則是非常容易的。但是反過來,從資源實體中查詢角色列表就有些費(fèi)力了。為了彌補(bǔ)這種不足,可以使用SQIL查詢語句實現(xiàn),具體會在后面的持久化設(shè)計中進(jìn)行說明。
權(quán)限管理模型的持久化設(shè)計
在權(quán)限管理模型設(shè)計完成之后,為各個實體創(chuàng)建一個存儲庫接口,并與JPA的存儲庫接口進(jìn)行綁定,就可以給實體賦予操作行為,實現(xiàn)實體的持久化設(shè)計。這一過程,其實就是存儲庫接口設(shè)計的工作。
例如,可以創(chuàng)建一個如下所示的存儲庫接口實現(xiàn)商家實體的持久化設(shè)計:
@Repository
public interface MerchantRepository extends JpaRepository<Merchant,Long>,JpaSpecificationExecutor<Merchant> {
}在這個接口設(shè)計中,通過繼承JpaRepository,可以讓這個接口具有增刪改查的操作功能。再通過繼承.JpaSpecificationExecutor,就可以進(jìn)行復(fù)雜的分頁查詢設(shè)計。如果不做其他特殊的查詢設(shè)計,這樣就已經(jīng)完成了商家實體的持久化設(shè)計了。
如果對于一個實體,還需要實現(xiàn)一些復(fù)雜的查詢設(shè)計,如對用戶實體進(jìn)行持久化設(shè)計,則使用如下所示的代碼:
@Repository
public interface UserRepository extends JpaRepository<User, Long>,JpaSpecificationExecutor<User> {
cQuery ("select distinct u from User u where u.name= :name")User findByName (@Param ("name") String name) ;
@Query("select u from User u"+
" left join u.roles r"+
"where r.name= :name")
User findByRoleName (@Param ("name") String name) ;
@Query ("select distinct u from User u where u.id= :id")User findById(@Param( "id") Long id);
@Query ( "select u from User u"+
"left join u.roles r "+"where r.id = :id")
List findByRoleId(@Param("id") Long id);
} 這里多了幾個使用注解“@Query”進(jìn)行自定義查詢設(shè)計的聲明方法。
其中,findByName和findByld主要使用distinct進(jìn)行了去重查詢,以避免在多對多的關(guān)聯(lián)查詢中,出現(xiàn)數(shù)據(jù)重復(fù)的情況。
另外,findByRoleName和 findByRoleld就是前面提到的,為彌補(bǔ)單向關(guān)聯(lián)設(shè)計的不足而設(shè)計的查詢。findByRoleName實現(xiàn)了從角色名稱中查詢用戶列表的功能,而findByRoleld實現(xiàn)了從角色I(xiàn)D中查詢用戶列表的功能。
在角色實體存儲庫接口設(shè)計中,也需要增加一個查詢設(shè)計,代碼如下所示:
@Repository
public interface RoleRepository extends JpaRepository<Role,Long>,JpaSpecificationExecutor<Role>{
CQuery("select o from Role o"+
"left join o.resources r"+"where r.id = :id")
List findByResourceId(@Param("id") Long id);
} 在這個設(shè)計中,findByResourceld是一個反向關(guān)聯(lián)查詢,即使用資源ID查詢角色列表。
其他實體的持久化設(shè)計與商家實體的持久化設(shè)計類似,只需為它們創(chuàng)建一個存儲庫接口就可以了。
權(quán)限管理模型的服務(wù)封裝
在領(lǐng)域服務(wù)開發(fā)中,服務(wù)層的實現(xiàn)是對存儲庫接口調(diào)用的一種封裝設(shè)計,這樣,不但可以在存儲庫接口調(diào)用過程中實現(xiàn)統(tǒng)一的事務(wù)管理,還可以增加其他功能。
下面我們以用戶服務(wù)層的開發(fā)為例進(jìn)行說明,其他各個業(yè)務(wù)服務(wù)層的開發(fā)與此類似,不再贅述。
在用戶服務(wù)層的設(shè)計中,增刪改查各個操作的實現(xiàn)代碼如下所示:@Service
@Transactional
public class UserService {
CAutowired
private UserRepository userRepository;
public String insert (User user){
try {
User old = findByName(user.getName());if(old == null) {
userRepository.save(user);
return user.getId() .toString();}else{
return "用戶名' "+ old.getName()+ "'已經(jīng)存在!";
}catch(Exception e){
e.printStackTrace();return e.getMessage();
public String update (User user){
try {
userRepository.save(user);
return user.getId() .toString();}catch(Exception e){
e.printStackTrace();return e.getMessage ();
}
}
public String delete (Long id){
try {
userRepository.deleteById(id);return id.toString ();
}catch (Exception e){
e.printStackTrace();
return e.getMessage();
}
}
public User find0ne (Long id){
return userRepository.findByUserId(id);
}
public List findA11(){
return userRepository.findAl1();
}
} 在這個設(shè)計中,注解@Transactional 實現(xiàn)了隱式的事務(wù)管理功能。由于登錄用戶必須以用戶名為依據(jù),所以在新增用戶名時,做了同名檢測。
用戶領(lǐng)域服務(wù)的分頁查詢功能的實現(xiàn)代碼如下所示:
@service
@Transactional
public class UserService {
CAutowired
private UserRepository userRepository;
public Page findAl1 (UserQo userQo) {
Sort sort = Sort.by(Sort.Direction. DESC, "created");
Pageable pageable = PageRequest.of (userQo.getPage(), userQo.getSize(.
sort);
return userRepository.findAll(new Specification(){
@override
public Predicate toPredicate(Root root, CriteriaQuery>qu
CriteriaBuilder criteriaBuilder ) {
List predicatesList =new ArrayList();
if (CommonUtils.isNotNull (userQo.getName())){
predicatesList.add (criteriaBuilder.like(root.get ("name"),
"g"+userQo.getName() +"%"));
}
if(CommonUtils.isNotNull (userQo.getMerchant ())){
predicatesList.add (criteriaBuilder.equal (root. get ("merchant"),userQo.getMerchant().getId()));
}
if (CommonUtils.isNotNull(userQo.getCreated())){
predicatesList.add(criteriaBuilder.greaterThan(root.get ("created"),userQo.getCreated()));
query.where (predicatesList.toArray(new Predicate[predicatesList.
size()]));
return guery.getRestriction();
}
}, pageable);
}
} 這里主要使用findAll 方法實現(xiàn)分頁查詢的功能,并通過查詢對象userQo 傳遞查詢參數(shù),這些參數(shù)包含了用戶名稱、商家對象和創(chuàng)建日期等屬性。
在領(lǐng)域服務(wù)設(shè)計中,我們使用了一些查詢對象,這些查詢對象統(tǒng)一在merchant-object模塊中實現(xiàn)。查詢對象的屬性基本上與實體對象的屬性相互對應(yīng),并且還增加了幾個分頁查詢的屬性。
查詢對象的實現(xiàn)代碼如下所示:
public class User0o extends PageQo implements java.io.Serializable{
private Long id;
private String name;
private String password;private String email;private Integer sex;
@DateTimeFormat (pattern = "Yyyy-MM-dd HH:mm:ss")private Date created;
private Listroles = new ArrayList<>();
private MerchantQo merchant;
public UserQ0() {
}
...
} 在完成服務(wù)層開發(fā)之后,商家權(quán)限體系的設(shè)計基本告一段落。下篇我們對商家管理微服務(wù)進(jìn)行設(shè)計。
本文給大家講解的內(nèi)容SpringCloud微服務(wù)架構(gòu)實戰(zhàn)商家管理后臺與sso設(shè)計:商家權(quán)限體系的設(shè)計及開發(fā)
下篇文章給大家講解的是商家管理后臺與sso設(shè)計:商家管理微服務(wù)設(shè)計;
覺得文章不錯的朋友可以轉(zhuǎn)發(fā)此文關(guān)注小編;
感謝大家的支持!
本文就是愿天堂沒有BUG給大家分享的內(nèi)容,大家有收獲的話可以分享下,想學(xué)習(xí)更多的話可以到微信公眾號里找我,我等你哦。
