靈活控制!結(jié)合 Spring Boot 3.3 的 API 基于 Togglz 的特性開(kāi)關(guān)和前端 UI 實(shí)現(xiàn)特性開(kāi)關(guān)的管理
在軟件開(kāi)發(fā)的持續(xù)集成和交付過(guò)程中,特性開(kāi)關(guān)(Feature Flags)作為一種非常重要的策略工具,能夠?yàn)殚_(kāi)發(fā)團(tuán)隊(duì)帶來(lái)靈活的功能管理方式。通過(guò)特性開(kāi)關(guān),開(kāi)發(fā)者可以在不重新部署應(yīng)用的情況下,動(dòng)態(tài)地啟用或禁用某些功能。這種方法不僅適用于漸進(jìn)式發(fā)布和灰度發(fā)布,還能幫助開(kāi)發(fā)者實(shí)現(xiàn) A/B 測(cè)試、風(fēng)險(xiǎn)控制以及不同環(huán)境下的功能差異化。
傳統(tǒng)上,功能的控制往往通過(guò)條件語(yǔ)句或分支管理來(lái)實(shí)現(xiàn),而特性開(kāi)關(guān)為這一過(guò)程提供了更為靈活且細(xì)粒度的控制方式。通過(guò)引入 Togglz 框架,Spring Boot 應(yīng)用可以輕松管理特性開(kāi)關(guān)的啟用、禁用以及其背后的配置。在本篇文章中,我們將深入探討如何基于 Togglz 框架,結(jié)合 Spring Boot3.3 的 API 和前端 UI 實(shí)現(xiàn)特性開(kāi)關(guān)的管理,并使用 MySQL 數(shù)據(jù)庫(kù)存儲(chǔ)開(kāi)關(guān)狀態(tài)。
Togglz 框架及其特性
Togglz 是一個(gè)用于管理特性開(kāi)關(guān)的輕量級(jí)框架,它提供了靈活的 API 和豐富的擴(kuò)展性,能夠幫助開(kāi)發(fā)者在應(yīng)用程序中快速集成特性開(kāi)關(guān)管理功能。它的主要功能包括:
- 簡(jiǎn)單的特性開(kāi)關(guān)管理:通過(guò)注解和配置,可以快速定義和管理特性開(kāi)關(guān)。
- 多種持久化方式:Togglz 支持將特性開(kāi)關(guān)的狀態(tài)存儲(chǔ)在內(nèi)存、文件系統(tǒng)或數(shù)據(jù)庫(kù)中。
- 支持 Web UI 管理:提供默認(rèn)的 Web UI,用于在生產(chǎn)環(huán)境中實(shí)時(shí)管理特性開(kāi)關(guān)。
- 集成多種條件控制:通過(guò)條件表達(dá)式和用戶分組,可以針對(duì)不同用戶或環(huán)境啟用或禁用特性。
項(xiàng)目網(wǎng)址:https://github.com/togglz/togglz
運(yùn)行效果:
圖片
若想獲取項(xiàng)目完整代碼以及其他文章的項(xiàng)目源碼,且在代碼編寫時(shí)遇到問(wèn)題需要咨詢交流,歡迎加入下方的知識(shí)星球。
本文將基于 Togglz 框架,結(jié)合 API 調(diào)用實(shí)現(xiàn)特性開(kāi)關(guān)的動(dòng)態(tài)管理,并在前端頁(yè)面通過(guò) AJAX 調(diào)用實(shí)現(xiàn)特性狀態(tài)的動(dòng)態(tài)獲取與更新。
項(xiàng)目依賴配置
Maven pom.xml 配置
<?xml versinotallow="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.icoderoad</groupId>
<artifactId>togglz-flag</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>togglz-flag</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<togglz.version>4.4.0</togglz.version>
</properties>
<dependencies>
<!-- Spring Boot 核心依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Togglz 依賴 -->
<dependency>
<groupId>org.togglz</groupId>
<artifactId>togglz-spring-boot-starter</artifactId>
<version>${togglz.version}</version>
</dependency>
<!-- Spring Boot Starter for JDBC and JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 數(shù)據(jù)庫(kù)驅(qū)動(dòng)依賴 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Boot Web 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Thymeleaf 模板引擎依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yaml 配置
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/togglz_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
后端實(shí)現(xiàn)
大家可以通過(guò)創(chuàng)建一個(gè)實(shí)現(xiàn) TogglzConfig 接口的配置類來(lái)定義 FeatureManager 的配置。例如,可以配置它使用 InMemoryStateRepository 或 JDBCStateRepository 來(lái)存儲(chǔ)特性開(kāi)關(guān)的狀態(tài)。
正確的 Togglz 配置示例
package com.icoderoad.togglzflag.config;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.togglz.core.Feature;
import org.togglz.core.manager.FeatureManager;
import org.togglz.core.manager.FeatureManagerBuilder;
import org.togglz.core.manager.TogglzConfig;
import org.togglz.core.repository.StateRepository;
import org.togglz.core.repository.jdbc.JDBCStateRepository;
import org.togglz.core.user.SimpleFeatureUser;
import org.togglz.core.user.UserProvider;
import com.icoderoad.togglzflag.enums.MyFeatures;
/**
* Togglz 的配置類,管理特性開(kāi)關(guān)的配置
*/
@Configuration
public class TogglzFeatureConfig implements TogglzConfig {
private final DataSource dataSource;
public TogglzFeatureConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Class<? extends Feature> getFeatureClass() {
return MyFeatures.class; // 返回定義的特性枚舉類
}
@Override
public StateRepository getStateRepository() {
// 使用 JDBC 存儲(chǔ)特性狀態(tài)
return JDBCStateRepository.newBuilder(dataSource).build();
}
@Override
public UserProvider getUserProvider() {
// 定義用戶提供者
return () -> new SimpleFeatureUser("admin", true);
}
@Bean
public FeatureManager featureManager() {
return new FeatureManagerBuilder()
.featureEnum(MyFeatures.class) // 綁定特性枚舉類
.stateRepository(getStateRepository()) // 設(shè)置狀態(tài)存儲(chǔ)庫(kù)
.userProvider(getUserProvider()) // 設(shè)置用戶提供者
.build();
}
}
通過(guò)這樣的配置,你可以靈活地管理 Spring Boot 應(yīng)用中的特性開(kāi)關(guān),并使用數(shù)據(jù)庫(kù)來(lái)持久化特性狀態(tài)。
如果大家想使用內(nèi)存中的狀態(tài)存儲(chǔ)(InMemoryStateRepository),可以在 getStateRepository() 方法中返回一個(gè)簡(jiǎn)單的內(nèi)存存儲(chǔ):
@Override
public StateRepository getStateRepository() {
return new InMemoryStateRepository();
}
數(shù)據(jù)庫(kù)配置
如果你使用 JDBCStateRepository 來(lái)持久化特性狀態(tài),需要確保你的數(shù)據(jù)庫(kù)中有一張存儲(chǔ)特性開(kāi)關(guān)狀態(tài)的表。Togglz 提供了創(chuàng)建這張表的 SQL 腳本:
CREATE TABLE `TOGGLZ` (
`FEATURE_NAME` varchar(100) NOT NULL,
`FEATURE_ENABLED` int(11) NOT NULL,
`STRATEGY_ID` varchar(200) DEFAULT NULL,
`STRATEGY_PARAMS` varchar(2000) DEFAULT NULL,
PRIMARY KEY (`FEATURE_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
配置 application.yml
在 application.yml 中配置數(shù)據(jù)庫(kù)連接信息:
spring:
datasource:
url: jdbc:mysql://localhost:3306/togglz_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
定義特性開(kāi)關(guān)枚舉類
在 Togglz 中,特性開(kāi)關(guān)通過(guò)枚舉類來(lái)管理,每個(gè)枚舉值代表一個(gè)開(kāi)關(guān)。該枚舉類定義了應(yīng)用程序中的所有特性開(kāi)關(guān),并且可以為每個(gè)特性開(kāi)關(guān)指定標(biāo)簽和默認(rèn)狀態(tài)。
package com.icoderoad.togglzflag.enums;
import org.togglz.core.Feature;
import org.togglz.core.annotation.EnabledByDefault;
import org.togglz.core.annotation.Label;
/**
* 定義應(yīng)用程序的特性開(kāi)關(guān)
*/
public enum MyFeatures implements Feature {
@Label("功能一")
FEATURE_ONE,
@EnabledByDefault
@Label("功能二")
FEATURE_TWO;
}
實(shí)現(xiàn) API 接口
我們創(chuàng)建一個(gè)控制器來(lái)處理特性開(kāi)關(guān)的查詢和更新,通過(guò) RESTful API 實(shí)現(xiàn)后端和前端的交互。
package com.icoderoad.togglzflag.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.togglz.core.Feature;
import org.togglz.core.manager.FeatureManager;
import org.togglz.core.repository.FeatureState;
import com.icoderoad.togglzflag.enums.MyFeatures;
@RestController
@RequestMapping("/api/features")
public class FeatureToggleController {
@Autowired
private FeatureManager featureManager;
/**
* 獲取當(dāng)前所有特性狀態(tài)
*
* @return 特性狀態(tài)的映射
*/
@GetMapping
public Map<String, Boolean> getFeatures() {
Map<String, Boolean> features = new HashMap<>();
features.put("featureOne", featureManager.isActive(MyFeatures.FEATURE_ONE));
features.put("featureTwo", featureManager.isActive(MyFeatures.FEATURE_TWO));
return features;
}
/**
* 更新特性開(kāi)關(guān)狀態(tài)
*
* @param featureStates 包含所有特性狀態(tài)的映射
* @return 更新結(jié)果
*/
@PostMapping("/toggle")
public Map<String, String> toggleFeatures(@RequestBody Map<String, Boolean> featureStates) {
try {
for (Map.Entry<String, Boolean> entry : featureStates.entrySet()) {
String featureName = entry.getKey();
boolean enabled = entry.getValue();
Feature feature = getFeature(featureName);
if (feature != null) {
featureManager.setFeatureState(new FeatureState(feature, enabled));
} else {
return Map.of("status", "error", "message", "未找到特性 " + featureName);
}
}
return Map.of("status", "success", "message", "所有特性狀態(tài)已更新");
} catch (Exception e) {
return Map.of("status", "error", "message", "更新特性狀態(tài)時(shí)發(fā)生錯(cuò)誤");
}
}
private Feature getFeature(String featureName) {
if ("featureOne".equalsIgnoreCase(featureName)) {
return MyFeatures.FEATURE_ONE;
} else if ("featureTwo".equalsIgnoreCase(featureName)) {
return MyFeatures.FEATURE_TWO;
}
return null;
}
}
前端實(shí)現(xiàn)
在前端,我們使用 Thymeleaf 渲染數(shù)據(jù),并結(jié)合 jQuery 和 Bootstrap 實(shí)現(xiàn)用戶友好的特性開(kāi)關(guān)管理界面。
在 src/main/resources/templates/index.html 文件中使用 Thymeleaf 模板顯示配置信息:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>特性開(kāi)關(guān)管理</title>
<link rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div class="container mt-5">
<h1>特性開(kāi)關(guān)管理</h1>
<!-- 特性一開(kāi)關(guān) -->
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="featureOne">
<label class="form-check-label" for="featureOne">功能一</label>
</div>
<!-- 特性二開(kāi)關(guān) -->
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="featureTwo">
<label class="form-check-label" for="featureTwo">功能二</label>
</div>
<!-- 保存按鈕 -->
<button class="btn btn-primary mt-3" id="saveButton">保存設(shè)置</button>
</div>
<script>
// 頁(yè)面加載時(shí),通過(guò) Ajax 請(qǐng)求獲取當(dāng)前的特性狀態(tài)
$(document).ready(function () {
$.get("/api/features", function (data) {
// 根據(jù)返回的 JSON 數(shù)據(jù)設(shè)置頁(yè)面開(kāi)關(guān)狀態(tài)
$('#featureOne').prop('checked', data.featureOne);
$('#featureTwo').prop('checked', data.featureTwo);
});
});
// 點(diǎn)擊保存按鈕時(shí),發(fā)送 Ajax 請(qǐng)求更新特性狀態(tài)
$('#saveButton').on('click', function () {
const featureOne = $('#featureOne').is(':checked');
const featureTwo = $('#featureTwo').is(':checked');
// 更新功能一狀態(tài)
$.post("/api/features/toggle", { feature: 'feature-one', enabled: featureOne });
// 更新功能二狀態(tài)
$.post("/api/features/toggle", { feature: 'feature-two', enabled: featureTwo });
});
</script>
</body>
</html>
結(jié)論
通過(guò)結(jié)合 Togglz 框架和 Spring Boot3.3,我們成功實(shí)現(xiàn)了基于 API 和 UI 的特性開(kāi)關(guān)管理方案。特性開(kāi)關(guān)管理不僅為應(yīng)用程序提供了更高的靈活性,還可以幫助團(tuán)隊(duì)實(shí)現(xiàn)無(wú)縫的功能發(fā)布。Togglz 的簡(jiǎn)單集成、持久化配置和豐富的擴(kuò)展性為特性開(kāi)關(guān)管理提供了理想的解決方案。在未來(lái)的工作中,這一方案可以進(jìn)一步優(yōu)化,以支持更多特性控制的復(fù)雜場(chǎng)景,如按用戶組啟用、按環(huán)境發(fā)布等。