我有一个 Play 框架项目,提供在多个前端之间共享的 API,目前,我正在开发单个前端,但我想创建一个 Multi-Tenancy 后端,每个前端都有自己的 Firebase帐户。
我的问题是,我必须考虑访问哪个 Firebase 项目取决于请求 header 值,而不同的值取决于前端。
我现在拥有的: FirebaseAppProvider.java:
public class FirebaseAppProvider implements Provider<FirebaseApp> {
private final Logger.ALogger logger;
private final Environment environment;
private final Configuration configuration;
@Inject
public FirebaseAppProvider(Environment environment, Configuration configuration) {
this.logger = Logger.of(this.getClass());
this.environment = environment;
this.configuration = configuration;
}
@Singleton
@Override
public FirebaseApp get() {
HashMap<String, String> firebaseProjects = (HashMap<String, String>) configuration.getObject("firebase");
firebaseProjects.forEach((websiteId, projectId) -> {
FileInputStream serviceAccount = null;
try {
serviceAccount = new FileInputStream(environment.classLoader().getResource(String.format("firebase/%s.json", projectId)).getPath());
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
FirebaseOptions options = new FirebaseOptions.Builder().setCredential(FirebaseCredentials.fromCertificate(serviceAccount))
.setDatabaseUrl(String.format("https://%s.firebaseio.com/", projectId))
.build();
FirebaseApp firebaseApp = FirebaseApp.initializeApp(options, projectId);
logger.info("FirebaseApp initialized");
});
return FirebaseApp.getInstance();
}
}
同样适用于数据库: FirebaseDatabaseProvider.java
public class FirebaseDatabaseProvider implements Provider<FirebaseDatabase> {
private final FirebaseApp firebaseApp;
public static List<TaxItem> TAXES = new ArrayList<>();
@Inject
public FirebaseDatabaseProvider(FirebaseApp firebaseApp) {
this.firebaseApp = firebaseApp;
fetchTaxes();
}
@Singleton
@Override
public FirebaseDatabase get() {
return FirebaseDatabase.getInstance(firebaseApp);
}
@Singleton
public DatabaseReference getUserDataReference() {
return this.get().getReference("/usersData");
}
@Singleton
public DatabaseReference getTaxesConfigurationReference() {
return this.get().getReference("/appData/taxConfiguration");
}
private void fetchTaxes() {
DatabaseReference bundlesRef = getTaxesConfigurationReference().child("taxes");
bundlesRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
TAXES.clear();
dataSnapshot.getChildren().forEach(tax -> TAXES.add(tax.getValue(TaxItem.class)));
Logger.info(String.format("==> %d taxes records loaded", TAXES.size()));
}
@Override
public void onCancelled(DatabaseError databaseError) {
Logger.warn("The read failed: " + databaseError.getCode());
}
});
}
}
所以我也从 Module.java 绑定(bind)它们:
public class Module extends AbstractModule {
@Override
public void configure() { bind(FirebaseApp.class).toProvider(FirebaseAppProvider.class).asEagerSingleton();
bind(FirebaseAuth.class).toProvider(FirebaseAuthProvider.class).asEagerSingleton();
bind(FirebaseDatabase.class).toProvider(FirebaseDatabaseProvider.class).asEagerSingleton();
}
}
我的 ActionCreator:
public class ActionCreator implements play.http.ActionCreator {
@Inject
public ActionCreator() {
}
@Override
public Action createAction(Http.Request request, Method actionMethod) {
switchTenancyId(request);
return new Action.Simple() {
@Override
public CompletionStage<Result> call(Http.Context ctx) {
return delegate.call(ctx);
}
};
}
private void switchTenancyId(Http.RequestHeader request) {
// DO something here
}
private Optional<String> getTenancyId(Http.RequestHeader request) {
String websiteId = request.getHeader("Website-ID");
System.out.println(websiteId);
return null;
}
}
我想要的是当我使用数据库服务或身份验证服务时,我读取网站 ID 并决定访问哪个 firebase 项目,我真的尝试了像这里答案这样的解决方案: Multi tenancy with Guice Custom Scopes and Jersey
请注意,我愿意为每个前端使用不同的项目,而不是相同的 Firebase 项目。
但是有点迷失,特别是请求只能从 Controller 或 ActionCreator 访问,所以我从上面的问题中得到的是通过键将提供程序加载到 ThreadLocal 中,并为每个请求切换它们取决于注释,但由于缺乏知识,我无法做到这一点。
我的项目的最小化版本可以在这里找到:https://github.com/almothafar/play-with-multi-tenant-firebase
此外,我还上传了 taxes-data-export.json
文件以导入到 firebase 项目中进行测试。
最佳答案
是的,所以我知道 Play 比 FireBase 好得多,但在我看来,您想在将其输入 FrieBase 后端之前从请求中提取租户 ID 吗?在运行中编写 Java 时,上下文是线程本地的,但即使在异步执行操作时,您也可以通过注入(inject)执行上下文来确保 Http.context 信息顺利进行。我不会通过操作创建者执行此操作,除非您想拦截调用哪个操作。 (尽管我也有一个黑客解决方案。)
因此,在发表评论后,我将尝试在这里阐明,您传入的请求将被路由到 Controller ,如下所示(如果您需要清理路由等,请告诉我):
下面是一个基于从请求中检索到的“Website-ID”来缓存检索到的 FireBaseApp 的解决方案,尽管我可能会将 tenancyId 放入 session 中。
import javax.inject.Inject;
import java.util.concurrent.CompletionStage;
public class MyController extends Controller {
private HttpExecutionContext ec; //This is the execution-context.
private FirebaseAppProvider appProvider;
private CacheApi cache;
@Inject
public MyController(HttpExecutionContext ec, FireBaseAppProvider provider,CacheApi cache) {
this.ec = ec;
this.appProvider = provider;
this.cache = cache;
}
/**
*Retrieves a website-id from request and attempts to retrieve
*FireBaseApp object from Cache.
*If not found a new FireBaseApp will be constructed by
*FireBaseAppProvider and cached.
**/
private FireBaseApp getFireBaseApp(){
String tenancyId = request.getHeader("Website-ID);
FireBaseApp app = (FireBaseApp)cache.get(tenancyId);
if(app==null){
app=appProvider.get();
cache.put(tenancyId,app);
}
return app;
}
public CompletionStage<Result> index() {
return CompletableFuture.supplyAsync(() -> {
FireBaseApp app = getFireBaseApp();
//Do things with app.
}, ec.current()); //Used here.
}
}
现在,在 FireBaseAppProvider 中,您可以通过 play.mvc.Controller 访问 header ,您唯一需要的就是通过 ec.current 提供 HttpExecutionContext。因此(我再次避免任何 FireBase 特定的内容),在 FireBaseProvider 中:
import play.mvc.Controller;
public class FireBaseAppProvider {
public String getWebsiteKey(){
String website = Controller.request().getHeader("Website-ID");
//If you need to handle a missing header etc, do it here.
return website;
}
public FireBaseApp get(){
String tenancyId = getWebsiteKey();
//Code to do actual construction here.
}
}
请告诉我这是否接近您所要求的内容,我会为您解决。
此外,如果您想存储 token 验证等,最好将它们放在返回请求的“ session ”中,这由 Play Framework 签名并允许通过请求存储数据。对于更大的数据,您可以使用 session ID 作为 key 的一部分来缓存它。
关于java - Firebase Multi-Tenancy 与 Play 框架取决于 HTTP 请求的 header 值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44647753/