Spring Boot (十四): Spring Boot 整合 Shiro-登錄認證和許可權管理

来源:https://www.cnblogs.com/justdojava/archive/2019/07/30/11212104.html
-Advertisement-
Play Games

​ 這篇文章我們來學習如何使用 Spring Boot 集成 Apache Shiro 。安全應該是互聯網公司的一道生命線,幾乎任何的公司都會涉及到這方面的需求。在 Java 領域一般有 Spring Security、 Apache Shiro 等安全框架,但是由於 Spring Security ...


這篇文章我們來學習如何使用 Spring Boot 集成 Apache Shiro 。安全應該是互聯網公司的一道生命線,幾乎任何的公司都會涉及到這方面的需求。在 Java 領域一般有 Spring Security、 Apache Shiro 等安全框架,但是由於 Spring Security 過於龐大和複雜,大多數公司會選擇 Apache Shiro 來使用,這篇文章會先介紹一下 Apache Shiro ,在結合 Spring Boot 給出使用案例。

Apache Shiro

What is Apache Shiro?

Apache Shiro 是一個功能強大、靈活的,開源的安全框架。它可以乾凈利落地處理身份驗證、授權、企業會話管理和加密。

Apache Shiro 的首要目標是易於使用和理解。安全通常很複雜,甚至讓人感到很痛苦,但是 Shiro 卻不是這樣子的。一個好的安全框架應該屏蔽複雜性,向外暴露簡單、直觀的 API,來簡化開發人員實現應用程式安全所花費的時間和精力。

Shiro 能做什麼呢?

  • 驗證用戶身份

  • 用戶訪問許可權控制,比如:1、判斷用戶是否分配了一定的安全形色。2、判斷用戶是否被授予完成某個操作的許可權

  • 在非 Web 或 EJB 容器的環境下可以任意使用 Session API

  • 可以響應認證、訪問控制,或者 Session 生命周期中發生的事件

  • 可將一個或以上用戶安全數據源數據組合成一個複合的用戶 "view"(視圖)

  • 支持單點登錄(SSO)功能

  • 支持提供“Remember Me”服務,獲取用戶關聯信息而無需登錄

等等——都集成到一個有凝聚力的易於使用的 API。

Shiro 致力在所有應用環境下實現上述功能,小到命令行應用程式,大到企業應用中,而且不需要藉助第三方框架、容器、應用伺服器等。當然 Shiro 的目的是儘量的融入到這樣的應用環境中去,但也可以在它們之外的任何環境下開箱即用。

Apache Shiro Features 特性

Apache Shiro 是一個全面的、蘊含豐富功能的安全框架。下圖為描述 Shiro 功能的框架圖:

Authentication(認證), Authorization(授權), Session Management(會話管理), Cryptography(加密)被 Shiro 框架的開發團隊稱之為應用安全的四大基石。那麼就讓我們來看看它們吧:

  • Authentication(認證):用戶身份識別,通常被稱為用戶“登錄”

  • Authorization(授權):訪問控制。比如某個用戶是否具有某個操作的使用許可權。

  • Session Management(會話管理):特定於用戶的會話管理,甚至在非web 或 EJB 應用程式。

  • Cryptography(加密):在對數據源使用加密演算法加密的同時,保證易於使用。

還有其他的功能來支持和加強這些不同應用環境下安全領域的關註點。特別是對以下的功能支持:

  • Web支持:Shiro 提供的 Web 支持 api ,可以很輕鬆的保護 Web 應用程式的安全。

  • 緩存:緩存是 Apache Shiro 保證安全操作快速、高效的重要手段。

  • 併發:Apache Shiro 支持多線程應用程式的併發特性。

  • 測試:支持單元測試和集成測試,確保代碼和預想的一樣安全。

  • "Run As":這個功能允許用戶假設另一個用戶的身份(在許可的前提下)。

  • "Remember Me":跨 session 記錄用戶的身份,只有在強制需要時才需要登錄。

註意: Shiro 不會去維護用戶、維護許可權,這些需要我們自己去設計/提供,然後通過相應的介面註入給 Shiro

High-Level Overview 高級概述

在概念層,Shiro 架構包含三個主要的理念:Subject,SecurityManager和 Realm。下麵的圖展示了這些組件如何相互作用,我們將在下麵依次對其進行描述。

  • Subject:當前用戶,Subject 可以是一個人,但也可以是第三方服務、守護進程帳戶、時鐘守護任務或者其它--當前和軟體交互的任何事件。

  • SecurityManager:管理所有Subject,SecurityManager 是 Shiro 架構的核心,配合內部安全組件共同組成安全傘。

  • Realms:用於進行許可權信息的驗證,我們自己實現。Realm 本質上是一個特定的安全 DAO:它封裝與數據源連接的細節,得到Shiro 所需的相關的數據。在配置 Shiro 的時候,你必須指定至少一個Realm 來實現認證(authentication)和/或授權(authorization)。

我們需要實現Realms的Authentication 和 Authorization。其中 Authentication 是用來驗證用戶身份,Authorization 是授權訪問控制,用於對用戶進行的操作授權,證明該用戶是否允許進行當前操作,如訪問某個鏈接,某個資源文件等。

快速上手

基礎信息

pom包依賴

  1. <dependencies>

  2. <dependency>

  3. <groupId>org.springframework.boot</groupId>

  4. <artifactId>spring-boot-starter-data-jpa</artifactId>

  5. </dependency>

  6. <dependency>

  7. <groupId>org.springframework.boot</groupId>

  8. <artifactId>spring-boot-starter-thymeleaf</artifactId>

  9. </dependency>

  10. <dependency>

  11. <groupId>net.sourceforge.nekohtml</groupId>

  12. <artifactId>nekohtml</artifactId>

  13. <version>1.9.22</version>

  14. </dependency>

  15. <dependency>

  16. <groupId>org.springframework.boot</groupId>

  17. <artifactId>spring-boot-starter-web</artifactId>

  18. </dependency>

  19. <dependency>

  20. <groupId>org.apache.shiro</groupId>

  21. <artifactId>shiro-spring</artifactId>

  22. <version>1.4.0</version>

  23. </dependency>

  24. <dependency>

  25. <groupId>mysql</groupId>

  26. <artifactId>mysql-connector-java</artifactId>

  27. <scope>runtime</scope>

  28. </dependency>

  29. </dependencies>

重點是 shiro-spring 包

配置文件

  1. spring:

  2. datasource:

  3. url: jdbc:mysql://localhost:3306/test

  4. username: root

  5. password: root

  6. driver-class-name: com.mysql.jdbc.Driver

  7.  

  8. jpa:

  9. database: mysql

  10. show-sql: true

  11. hibernate:

  12. ddl-auto: update

  13. naming:

  14. strategy: org.hibernate.cfg.DefaultComponentSafeNamingStrategy

  15. properties:

  16. hibernate:

  17. dialect: org.hibernate.dialect.MySQL5Dialect

  18.  

  19. thymeleaf:

  20. cache: false

  21. mode: LEGACYHTML5

thymeleaf的配置是為了去掉html的校驗

頁面

我們新建了六個頁面用來測試:

  • index.html :首頁

  • login.html :登錄頁

  • userInfo.html : 用戶信息頁面

  • userInfoAdd.html :添加用戶頁面

  • userInfoDel.html :刪除用戶頁面

  • 403.html : 沒有許可權的頁面

除過登錄頁面其它都很簡單,大概如下:

  1. <!DOCTYPE html>

  2. <html lang="en">

  3. <head>

  4. <meta charset="UTF-8">

  5. <title>Title</title>

  6. </head>

  7. <body>

  8. <h1>index</h1>

  9. </body>

  10. </html>

RBAC

RBAC 是基於角色的訪問控制(Role-Based Access Control )在 RBAC 中,許可權與角色相關聯,用戶通過成為適當角色的成員而得到這些角色的許可權。這就極大地簡化了許可權的管理。這樣管理都是層級相互依賴的,許可權賦予給角色,而把角色又賦予用戶,這樣的許可權設計很清楚,管理起來很方便。

採用 Jpa 技術來自動生成基礎表格,對應的實體如下:

用戶信息

  1. @Entity

  2. public class UserInfo implements Serializable {

  3. @Id

  4. @GeneratedValue

  5. private Integer uid;

  6. @Column(unique =true)

  7. private String username;//帳號

  8. private String name;//名稱(昵稱或者真實姓名,不同系統不同定義)

  9. private String password; //密碼;

  10. private String salt;//加密密碼的鹽

  11. private byte state;//用戶狀態,0:創建未認證(比如沒有激活,沒有輸入驗證碼等等)--等待驗證的用戶 , 1:正常狀態,2:用戶被鎖定.

  12. @ManyToMany(fetch= FetchType.EAGER)//立即從資料庫中進行載入數據;

  13. @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })

  14. private List<SysRole> roleList;// 一個用戶具有多個角色

  15.  

  16. // 省略 get set 方法

  17. }

角色信息

  1. @Entity

  2. public class SysRole {

  3. @Id@GeneratedValue

  4. private Integer id; // 編號

  5. private String role; // 角色標識程式中判斷使用,如"admin",這個是唯一的:

  6. private String description; // 角色描述,UI界面顯示使用

  7. private Boolean available = Boolean.FALSE; // 是否可用,如果不可用將不會添加給用戶

  8.  

  9. //角色 -- 許可權關係:多對多關係;

  10. @ManyToMany(fetch= FetchType.EAGER)

  11. @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})

  12. private List<SysPermission> permissions;

  13.  

  14. // 用戶 - 角色關係定義;

  15. @ManyToMany

  16. @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})

  17. private List<UserInfo> userInfos;// 一個角色對應多個用戶

  18.  

  19. // 省略 get set 方法

  20. }

許可權信息

  1. @Entity

  2. public class SysPermission implements Serializable {

  3. @Id@GeneratedValue

  4. private Integer id;//主鍵.

  5. private String name;//名稱.

  6. @Column(columnDefinition="enum('menu','button')")

  7. private String resourceType;//資源類型,[menu|button]

  8. private String url;//資源路徑.

  9. private String permission; //許可權字元串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view

  10. private Long parentId; //父編號

  11. private String parentIds; //父編號列表

  12. private Boolean available = Boolean.FALSE;

  13. @ManyToMany

  14. @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})

  15. private List<SysRole> roles;

  16.  

  17. // 省略 get set 方法

  18. }

根據以上的代碼會自動生成 userinfo(用戶信息表)、sysrole(角色表)、syspermission(許可權表)、sysuserrole(用戶角色表)、sysrole_permission(角色許可權表)這五張表,為了方便測試我們給這五張表插入一些初始化數據:

  1. INSERT INTO `user_info` (`uid`,`username`,`name`,`password`,`salt`,`state`) VALUES ('1', 'admin', '管理員', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0);

  2. INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (1,0,'用戶管理',0,'0/','userInfo:view','menu','userInfo/userList');

  3. INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (2,0,'用戶添加',1,'0/1','userInfo:add','button','userInfo/userAdd');

  4. INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (3,0,'用戶刪除',1,'0/1','userInfo:del','button','userInfo/userDel');

  5. INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (1,0,'管理員','admin');

  6. INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (2,0,'VIP會員','vip');

  7. INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (3,1,'test','test');

  8. INSERT INTO `sys_role_permission` VALUES ('1', '1');

  9. INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (1,1);

  10. INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (2,1);

  11. INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (3,2);

  12. INSERT INTO `sys_user_role` (`role_id`,`uid`) VALUES (1,1);

Shiro 配置

首先要配置的是 ShiroConfig 類,Apache Shiro 核心通過 Filter 來實現,就好像 SpringMvc 通過 DispachServlet 來主控制一樣。 既然是使用 Filter 一般也就能猜到,是通過 URL 規則來進行過濾和許可權校驗,所以我們需要定義一系列關於 URL 的規則和訪問許可權。

ShiroConfig

  1. @Configuration

  2. public class ShiroConfig {

  3. @Bean

  4. public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {

  5. System.out.println("ShiroConfiguration.shirFilter()");

  6. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

  7. shiroFilterFactoryBean.setSecurityManager(securityManager);

  8. //攔截器.

  9. Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();

  10. // 配置不會被攔截的鏈接 順序判斷

  11. filterChainDefinitionMap.put("/static/**", "anon");

  12. //配置退出 過濾器,其中的具體的退出代碼Shiro已經替我們實現了

  13. filterChainDefinitionMap.put("/logout", "logout");

  14. //<!-- 過濾鏈定義,從上向下順序執行,一般將/**放在最為下邊 -->:這是一個坑呢,一不小心代碼就不好使了;

  15. //<!-- authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問-->

  16. filterChainDefinitionMap.put("/**", "authc");

  17. // 如果不設置預設會自動尋找Web工程根目錄下的"/login.jsp"頁面

  18. shiroFilterFactoryBean.setLoginUrl("/login");

  19. // 登錄成功後要跳轉的鏈接

  20. shiroFilterFactoryBean.setSuccessUrl("/index");

  21.  

  22. //未授權界面;

  23. shiroFilterFactoryBean.setUnauthorizedUrl("/403");

  24. shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

  25. return shiroFilterFactoryBean;

  26. }

  27.  

  28. @Bean

  29. public MyShiroRealm myShiroRealm(){

  30. MyShiroRealm myShiroRealm = new MyShiroRealm();

  31. return myShiroRealm;

  32. }

  33.  

  34.  

  35. @Bean

  36. public SecurityManager securityManager(){

  37. DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

  38. securityManager.setRealm(myShiroRealm());

  39. return securityManager;

  40. }

  41. }

Filter Chain 定義說明:

  • 1、一個URL可以配置多個 Filter,使用逗號分隔

  • 2、當設置多個過濾器時,全部驗證通過,才視為通過

  • 3、部分過濾器可指定參數,如 perms,roles

Shiro 內置的 FilterChain

  • anon:所有 url 都都可以匿名訪問

  • authc: 需要認證才能進行訪問

  • user:配置記住我或認證通過可以訪問

 

登錄認證實現

在認證、授權內部實現機制中都有提到,最終處理都將交給Real進行處理。因為在 Shiro 中,最終是通過 Realm 來獲取應用程式中的用戶、角色及許可權信息的。通常情況下,在 Realm 中會直接從我們的數據源中獲取 Shiro 需要的驗證信息。可以說,Realm 是專用於安全框架的 DAO. Shiro 的認證過程最終會交由 Realm 執行,這時會調用 Realm 的 getAuthenticationInfo(token)方法。

該方法主要執行以下操作:

  • 1、檢查提交的進行認證的令牌信息

  • 2、根據令牌信息從數據源(通常為資料庫)中獲取用戶信息

  • 3、對用戶信息進行匹配驗證。

  • 4、驗證通過將返回一個封裝了用戶信息的 AuthenticationInfo實例。

  • 5、驗證失敗則拋出 AuthenticationException異常信息。

而在我們的應用程式中要做的就是自定義一個 Realm 類,繼承AuthorizingRealm 抽象類,重載 doGetAuthenticationInfo(),重寫獲取用戶信息的方法。

doGetAuthenticationInfo 的重寫

  1. @Override

  2. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)

  3. throws AuthenticationException {

  4. System.out.println("MyShiroRealm.doGetAuthenticationInfo()");

  5. //獲取用戶的輸入的賬號.

  6. String username = (String)token.getPrincipal();

  7. System.out.println(token.getCredentials());

  8. //通過username從資料庫中查找 User對象,如果找到,沒找到.

  9. //實際項目中,這裡可以根據實際情況做緩存,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重覆執行該方法

  10. UserInfo userInfo = userInfoService.findByUsername(username);

  11. System.out.println("----->>userInfo="+userInfo);

  12. if(userInfo == null){

  13. return null;

  14. }

  15. SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(

  16. userInfo, //用戶名

  17. userInfo.getPassword(), //密碼

  18. ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt

  19. getName() //realm name

  20. );

  21. return authenticationInfo;

  22. }

鏈接許可權的實現

Shiro 的許可權授權是通過繼承 AuthorizingRealm抽象類,重載 doGetAuthorizationInfo();當訪問到頁面的時候,鏈接配置了相應的許可權或者 Shiro 標簽才會執行此方法否則不會執行,所以如果只是簡單的身份認證沒有許可權的控制的話,那麼這個方法可以不進行實現,直接返回 null 即可。在這個方法中主要是使用類: SimpleAuthorizationInfo進行角色的添加和許可權的添加。

  1. @Override

  2. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

  3. System.out.println("許可權配置-->MyShiroRealm.doGetAuthorizationInfo()");

  4. SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

  5. UserInfo userInfo = (UserInfo)principals.getPrimaryPrincipal();

  6. for(SysRole role:userInfo.getRoleList()){

  7. authorizationInfo.addRole(role.getRole());

  8. for(SysPermission p:role.getPermissions()){

  9. authorizationInfo.addStringPermission(p.getPermission());<

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近這段時間公司特別忙,新的一年新的開始新一代的創業者都會選擇每年的春天開始創業,對於創業者來說現在企業製作一個新的網站要花多少錢?是比較關註的一個問題,建站行業的價格每年都會有所變化,目前上海網站建設公司製作一個常規的企業站普遍價格在八九千元,知名的比較大的網路公司價格會在萬元以上,當然建站也有價 ...
  • Allot Transfer $(document).ready(function() { $('input[type=radio][name=bedStatus]').change(function() { if (this.value == 'allot') { alert("Allot Tha... ...
  • Web前端三大框架_angular.js 6.0(一) 需要視頻教程,看頭像昵稱處 一、Angular 6.0 1.1樣式 html中引入樣式:內嵌式,外鏈式,行內式。 ng6中組件引入樣式的方式也有三種: 外鏈式 ng6中,已經將css預編譯語言配置出來了,因此我們可以直接使用他們 在組件註解類中 ...
  • 隊列與棧不同,它遵從先進先出(FIFO——First In First Out)原則,新添加的元素排在隊列的尾部,元素只能從隊列頭部移除。 我們在前一篇文章中描述瞭如何用JavaScript來實現棧這種數據結構,這裡我們對應地來實現隊列。 與棧的實現方式類似,唯一不同的是從隊列移除元素時取的是隊列頭 ...
  • CSS3動畫animation的學習筆記,包括animation的屬性及其值的設定,以及keyframes的值的設定方法 ...
  • 1、面向對象特點:封裝、繼承、多態。2、構造函數 = 構造器 + 原型對象;(1)父類function UserClass(name,age,word){ //構造器 constructor this.name=name; this.age =age; this.word =word; this.i ...
  • 前言 上一節我們說了介面隔離原則,就是讓介面的職責最小化。這樣對維護代碼簡單,調用方法也清晰。 這節我們來研究依賴倒置原則。這個原則我認為是特別特別重要的。在很多地方我們能看到。比如Dubbo中使用到的SPI等等。 基本介紹 什麼是依賴倒置原則? 我們可以將其分為兩點: 1) 高層模塊不應該依賴低層 ...
  • 官網:www.fhadmin.org 特別註意: Springboot 工作流 前後分離 + 跨域 版本 (許可權控制到菜單和按鈕) 後臺框架:springboot2.1.2+ activiti6.0.0+ mybaits+maven+介面 前端頁面:html +vue.js 形式 jquery aj ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...