|
|
@@ -0,0 +1,165 @@
|
|
|
+package com.malk.pro.tenant;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.malk.server.aliwork.YDAuth;
|
|
|
+import com.malk.server.aliwork.YDConf;
|
|
|
+import com.malk.service.aliwork.YDClient_Form;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.scheduling.annotation.Scheduled;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 租户注册表
|
|
|
+ *
|
|
|
+ * <p>启动时全量拉取宜搭应用表 → 内存 Map。`@Scheduled` 定时刷新。</p>
|
|
|
+ *
|
|
|
+ * <p>入口凭据用 mjava-pro 自身的 YDConf({@code aliwork.appType} / {@code aliwork.systemToken}),
|
|
|
+ * 从其 searchForm 获取租户记录;每条记录的 appKey/appSecret 属于某个**租户 + vendor**。</p>
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class TenantRegistryService {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private TenantRegistryProperties props;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private YDConf selfConf;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private YDClient_Form ydForm;
|
|
|
+
|
|
|
+ /** 内存注册表:tenantId → profile */
|
|
|
+ private final Map<String, TenantProfile> registry = new ConcurrentHashMap<>();
|
|
|
+
|
|
|
+ @PostConstruct
|
|
|
+ public void init() {
|
|
|
+ try {
|
|
|
+ loadAll();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("[TenantRegistry] 启动加载失败", e);
|
|
|
+ if (props.isFailFast()) {
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Scheduled(fixedDelayString = "${tenant.registry.ttlSeconds:600}000")
|
|
|
+ public void refresh() {
|
|
|
+ try {
|
|
|
+ loadAll();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("[TenantRegistry] 定时刷新失败,旧值继续可用", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 全量加载:按 formUuid 分页 searchForm → 转 TenantProfile → 替换 registry
|
|
|
+ */
|
|
|
+ public synchronized void loadAll() {
|
|
|
+ if (props.getFormUuid() == null || props.getFormUuid().isEmpty()) {
|
|
|
+ log.warn("[TenantRegistry] tenant.registry.formUuid 未配置,跳过加载");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ YDAuth auth = YDAuth.ofGlobal(selfConf);
|
|
|
+ Map<String, TenantProfile> snapshot = new HashMap<>();
|
|
|
+ int page = 1;
|
|
|
+ while (true) {
|
|
|
+ Map<String, Object> r = ydForm.searchForm(auth, props.getFormUuid(), "{}", page, 100, null);
|
|
|
+ List<Map<String, Object>> data = listFrom(r, "data");
|
|
|
+ if (data == null || data.isEmpty()) break;
|
|
|
+ for (Map<String, Object> row : data) {
|
|
|
+ mergeRow(snapshot, toFormData(row));
|
|
|
+ }
|
|
|
+ Number total = numberFrom(r, "totalCount");
|
|
|
+ if (total == null || snapshot.size() >= total.intValue()) break;
|
|
|
+ page++;
|
|
|
+ }
|
|
|
+ registry.clear();
|
|
|
+ registry.putAll(snapshot);
|
|
|
+ log.info("[TenantRegistry] 加载完成 tenants={} vendors={}",
|
|
|
+ registry.size(),
|
|
|
+ registry.values().stream().mapToInt(p -> p.getVendorCredentials() == null ? 0 : p.getVendorCredentials().size()).sum());
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 合并宜搭表一行:同一 tenantId 的多行按 vendor 聚合 */
|
|
|
+ private void mergeRow(Map<String, TenantProfile> snapshot, Map<String, Object> row) {
|
|
|
+ TenantRegistryProperties.FieldMap f = props.getField();
|
|
|
+ String tenantId = stringFrom(row, f.getTenantId());
|
|
|
+ String vendor = stringFrom(row, f.getVendor());
|
|
|
+ String enabled = stringFrom(row, f.getEnabled());
|
|
|
+ if (tenantId == null || vendor == null) return;
|
|
|
+ if (!"on".equalsIgnoreCase(enabled)) return;
|
|
|
+
|
|
|
+ VendorCredential cred = VendorCredential.builder()
|
|
|
+ .vendor(vendor)
|
|
|
+ .appKey(stringFrom(row, f.getAppKey()))
|
|
|
+ .appSecret(stringFrom(row, f.getAppSecret()))
|
|
|
+ .corpId(stringFrom(row, f.getCorpId()))
|
|
|
+ .extra(parseExtra(stringFrom(row, f.getExtraJson())))
|
|
|
+ .build();
|
|
|
+
|
|
|
+ TenantProfile profile = snapshot.computeIfAbsent(tenantId, id ->
|
|
|
+ TenantProfile.builder()
|
|
|
+ .tenantId(id)
|
|
|
+ .vendorCredentials(new HashMap<>())
|
|
|
+ .enabled(true)
|
|
|
+ .build());
|
|
|
+ profile.getVendorCredentials().put(vendor, cred);
|
|
|
+ }
|
|
|
+
|
|
|
+ public TenantProfile get(String tenantId) {
|
|
|
+ return registry.get(tenantId);
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean contains(String tenantId) {
|
|
|
+ return registry.containsKey(tenantId);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int size() {
|
|
|
+ return registry.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ // --------- helpers ---------
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private static Map<String, Object> toFormData(Map<String, Object> row) {
|
|
|
+ Object fd = row.get("formData");
|
|
|
+ if (fd instanceof Map) return (Map<String, Object>) fd;
|
|
|
+ return row;
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private static List<Map<String, Object>> listFrom(Map<String, Object> m, String key) {
|
|
|
+ Object v = m.get(key);
|
|
|
+ return v instanceof List ? (List<Map<String, Object>>) v : new ArrayList<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Number numberFrom(Map<String, Object> m, String key) {
|
|
|
+ Object v = m.get(key);
|
|
|
+ return v instanceof Number ? (Number) v : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String stringFrom(Map<String, Object> m, String key) {
|
|
|
+ Object v = m.get(key);
|
|
|
+ return v == null ? null : String.valueOf(v);
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private static Map<String, Object> parseExtra(String json) {
|
|
|
+ if (json == null || json.isEmpty()) return null;
|
|
|
+ try {
|
|
|
+ return JSON.parseObject(json, Map.class);
|
|
|
+ } catch (Exception e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|