Skip to content

Commit 26922c5

Browse files
committed
feat: support js library
1 parent 2587f5f commit 26922c5

File tree

3 files changed

+165
-0
lines changed

3 files changed

+165
-0
lines changed

server/api-service/openblocks-infra/src/main/java/com/openblocks/infra/constant/NewUrl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ private NewUrl() {
2727
public static final String FOLDER_URL = PREFIX + "/folders";
2828

2929
public static final String GITHUB_STAR = PREFIX + "/misc/github-star";
30+
public static final String JS_LIBRARY = PREFIX + "/misc/js-library";
3031
public static final String MATERIAL_URL = PREFIX + "/materials";
3132
public static final String CONTACT_SYNC = PREFIX + "/sync";
3233
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package com.openblocks.api.misc;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.nio.charset.Charset;
6+
import java.time.Duration;
7+
import java.util.Collection;
8+
import java.util.Collections;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.Optional;
12+
import java.util.concurrent.ConcurrentHashMap;
13+
import java.util.concurrent.ConcurrentMap;
14+
15+
import org.apache.commons.collections4.CollectionUtils;
16+
import org.apache.commons.collections4.MapUtils;
17+
import org.apache.commons.io.IOUtils;
18+
import org.apache.commons.lang3.StringUtils;
19+
import org.jetbrains.annotations.NotNull;
20+
import org.springframework.core.ParameterizedTypeReference;
21+
import org.springframework.web.bind.annotation.GetMapping;
22+
import org.springframework.web.bind.annotation.RequestMapping;
23+
import org.springframework.web.bind.annotation.RequestParam;
24+
import org.springframework.web.bind.annotation.RestController;
25+
import org.springframework.web.reactive.function.client.ExchangeStrategies;
26+
27+
import com.google.common.cache.CacheBuilder;
28+
import com.google.common.cache.CacheLoader;
29+
import com.google.common.cache.LoadingCache;
30+
import com.openblocks.api.framework.view.ResponseView;
31+
import com.openblocks.infra.constant.NewUrl;
32+
import com.openblocks.infra.localcache.ReloadableCache;
33+
import com.openblocks.sdk.webclient.WebClientBuildHelper;
34+
35+
import lombok.Builder;
36+
import lombok.Data;
37+
import lombok.extern.slf4j.Slf4j;
38+
import reactor.core.publisher.Flux;
39+
import reactor.core.publisher.Mono;
40+
41+
@Slf4j
42+
@RestController
43+
@RequestMapping(NewUrl.JS_LIBRARY)
44+
public class JsLibraryController {
45+
46+
private static final String META_URL_TEMPLATE = "https://registry.npmjs.com/%s";
47+
48+
private static final ConcurrentMap<String, ReloadableCache<JsLibraryMeta>> RECOMMENDED_JS_LIB_META_CACHE = new ConcurrentHashMap<>();
49+
50+
private static final LoadingCache<String, Mono<JsLibraryMeta>> JS_LIB_META_CACHE = CacheBuilder.newBuilder()
51+
.maximumSize(1000)
52+
.expireAfterAccess(Duration.ofDays(1))
53+
.build(new CacheLoader<>() {
54+
@Override
55+
public Mono<JsLibraryMeta> load(@NotNull String key) {
56+
return fetch(key).cache();
57+
}
58+
});
59+
60+
private static final ExchangeStrategies EXCHANGE_STRATEGY = ExchangeStrategies
61+
.builder()
62+
.codecs(configure -> configure.defaultCodecs().maxInMemorySize(-1))
63+
.build();
64+
65+
static {
66+
try (InputStream is = JsLibraryController.class.getClassLoader().getResourceAsStream("recommendedJsLibraries")) {
67+
if (is != null) {
68+
List<String> names = IOUtils.readLines(is, Charset.defaultCharset());
69+
log.info("find recommended js library names: {}", names);
70+
for (String libName : names) {
71+
ReloadableCache<JsLibraryMeta> reloadableCache = ReloadableCache.<JsLibraryMeta> newBuilder()
72+
.setName(libName)
73+
.setInterval(Duration.ofDays(1))
74+
.setFactory(() -> {
75+
Mono<JsLibraryMeta> fetch = fetch(libName);
76+
log.info("reloaded recommended js library: {}", libName);
77+
return fetch;
78+
})
79+
.build();
80+
RECOMMENDED_JS_LIB_META_CACHE.put(libName, reloadableCache);
81+
}
82+
}
83+
} catch (IOException e) {
84+
log.error("load recommended js library name error.", e);
85+
}
86+
}
87+
88+
@GetMapping("/recommendations")
89+
public Mono<ResponseView<List<JsLibraryMeta>>> getRecommendationMetas() {
90+
return getMeta(RECOMMENDED_JS_LIB_META_CACHE.keySet());
91+
}
92+
93+
@GetMapping("/metas")
94+
public Mono<ResponseView<List<JsLibraryMeta>>> getMeta(@RequestParam("name") Collection<String> names) {
95+
if (CollectionUtils.isEmpty(names)) {
96+
return Mono.just(ResponseView.success(Collections.emptyList()));
97+
}
98+
return Flux.fromIterable(names)
99+
.flatMap(name -> {
100+
if (RECOMMENDED_JS_LIB_META_CACHE.containsKey(name)) {
101+
return RECOMMENDED_JS_LIB_META_CACHE.get(name).getMonoValue();
102+
}
103+
return JS_LIB_META_CACHE.getUnchecked(name)
104+
.onErrorReturn(JsLibraryMeta.builder().name(name).build());
105+
})
106+
.collectList()
107+
.map(ResponseView::success);
108+
}
109+
110+
@SuppressWarnings("unchecked")
111+
private static Mono<JsLibraryMeta> fetch(String name) {
112+
log.info("fetch js library:{}", name);
113+
return WebClientBuildHelper.builder()
114+
.systemProxy()
115+
.toWebClientBuilder()
116+
.exchangeStrategies(EXCHANGE_STRATEGY)
117+
.build()
118+
.get()
119+
.uri(META_URL_TEMPLATE.formatted(name))
120+
.exchangeToMono(response -> {
121+
if (response.statusCode().is2xxSuccessful()) {
122+
return response.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
123+
});
124+
}
125+
return Mono.empty();
126+
})
127+
.map(map -> {
128+
String description = MapUtils.getString(map, "description");
129+
String homepage = MapUtils.getString(map, "homepage");
130+
if (StringUtils.isBlank(homepage)) {
131+
homepage = Optional.ofNullable((Map<String, Object>) MapUtils.getMap(map, "repository"))
132+
.filter(repository -> "git".equals(MapUtils.getString(repository, "type")))
133+
.map(repository -> MapUtils.getString(repository, "url"))
134+
.map(url -> url.replaceFirst("git\\+", ""))
135+
.orElse(null);
136+
}
137+
String latestVersion = Optional.ofNullable((Map<String, Object>) MapUtils.getMap(map, "dist-tags"))
138+
.map(distTags -> MapUtils.getString(distTags, "latest"))
139+
.orElse(null);
140+
return JsLibraryMeta.builder()
141+
.name(name)
142+
.latestVersion(latestVersion)
143+
.homepage(homepage)
144+
.description(description)
145+
.build();
146+
})
147+
.defaultIfEmpty(JsLibraryMeta.builder().name(name).build());
148+
}
149+
150+
@Data
151+
@Builder
152+
public static class JsLibraryMeta {
153+
private String name;
154+
private String latestVersion;
155+
private String homepage;
156+
private String description;
157+
}
158+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fast-xml-parser
2+
jsprint
3+
jspdf
4+
jmespath
5+
jsonpath
6+
i18next

0 commit comments

Comments
 (0)