MOD:更换为Vertx框架 提高效率

This commit is contained in:
leosam1024
2023-06-15 15:56:41 +08:00
parent 08fe94f020
commit d78c34d18a
21 changed files with 913 additions and 325 deletions
+128 -15
View File
@@ -2,16 +2,11 @@
<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.1.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.leosam.tvbox</groupId>
<artifactId>tvbox-mv</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>tvbox-mv</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
@@ -20,46 +15,164 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.resources.overwrite>true</maven.resources.overwrite>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-shade-plugin.version>3.2.4</maven-shade-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
<exec-maven-plugin.version>3.0.0</exec-maven-plugin.version>
<vertx.version>4.4.3</vertx.version>
<junit-jupiter.version>5.9.2</junit-jupiter.version>
<main.verticle>com.leosam.tvbox.mv.MainVerticle</main.verticle>
<launcher.class>io.vertx.core.Launcher</launcher.class>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<groupId>io.vertx</groupId>
<artifactId>vertx-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.7</version>
</dependency>
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<version>3.1.9</version>
</dependency>
<dependency>
<groupId>org.fusesource.jansi</groupId>
<artifactId>jansi</artifactId>
<version>2.4.0</version>
</dependency>
<!--Lucene Core-->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>8.5.2</version>
<version>9.5.0</version>
</dependency>
<!--Lucene Queryparser-->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>8.5.2</version>
<version>9.5.0</version>
</dependency>
<!-- Lucene提供的中文分词器模块,lucene-analyzers-smartcn:Lucene 的中文分词器 SmartChineseAnalyzer -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-smartcn</artifactId>
<version>8.5.2</version>
<version>8.11.2</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stack-depchain</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>${launcher.class}</Main-Class>
<Main-Verticle>${main.verticle}</Main-Verticle>
</manifestEntries>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<finalName>${project.artifactId}-${project.version}</finalName>
<!-- <outputFile>${project.build.directory}/${project.artifactId}-${project.version}.jar</outputFile>-->
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>${exec-maven-plugin.version}</version>
<configuration>
<mainClass>io.vertx.core.Launcher</mainClass>
<arguments>
<argument>run</argument>
<argument>${main.verticle}</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
@@ -0,0 +1,123 @@
package com.leosam.tvbox.mv;
import com.leosam.tvbox.mv.data.MvResult;
import com.leosam.tvbox.mv.data.VodResult;
import com.leosam.tvbox.mv.service.MvService;
import com.leosam.tvbox.mv.utils.NumberUtils;
import com.leosam.tvbox.mv.utils.StringUtils;
import com.leosam.tvbox.mv.utils.VertxUtils;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServer;
import io.vertx.core.json.Json;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CompletableFuture;
public class MainVerticle extends AbstractVerticle {
private static final Logger logger = LoggerFactory.getLogger(MainVerticle.class);
private static MvService mvService;
private static int port = 0;
static {
CompletableFuture.runAsync(MainVerticle::initIndex)
.thenAcceptAsync((a) -> {
if (port > 0) {
logger.info("索引完成,可以请求数据了, HTTP server port " + port);
} else {
logger.info("索引完成,可以请求数据了");
}
});
}
private static void initIndex() {
try {
MvService mvService = new MvService();
mvService.initIndex();
MainVerticle.mvService = mvService;
} catch (Exception e) {
logger.error("初始化索引失败", e);
}
}
@Override
public void start(Promise<Void> startPromise) throws Exception {
HttpServer httpServer = vertx.createHttpServer();
Router router = Router.router(vertx);
router.route("/mv/search").handler(this::searchMv);
router.route("/mv/vod").handler(this::searchMvVod);
router.route().handler(req -> req.response().putHeader("content-type", "text/plain").end("Hello from Vert.x!"));
httpServer.requestHandler(router);
httpServer.listen(7777, http -> {
if (http.succeeded()) {
startPromise.complete();
logger.info("HTTP server started on port 7777");
} else {
startPromise.fail(http.cause());
}
});
port = httpServer.actualPort();
}
private void searchMvVod(RoutingContext req) {
// 获取参数
String wd = VertxUtils.queryParam(req, "wd");
String ac = VertxUtils.queryParam(req, "ac");
String ids = VertxUtils.queryParam(req, "ids");
String maxCount = VertxUtils.queryParam(req, "maxCount");
int max = Math.min(Math.max(NumberUtils.toInt(maxCount, 100), 10), 1000);
String query = StringUtils.isNotEmpty(wd) ? wd : ids;
// 处理结果
VodResult vodResult = null;
try {
if (mvService != null) {
vodResult = mvService.searchVod(query, max);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
// 返回数据
String jsonString = Json.encode(vodResult);
req.response()
.putHeader("content-type", "application/json")
.end(jsonString);
}
private void searchMv(RoutingContext req) {
// 获取参数
String query = VertxUtils.queryParam(req, "query");
String maxCount = VertxUtils.queryParam(req, "maxCount");
int max = Math.min(Math.max(NumberUtils.toInt(maxCount, 100), 10), 1000);
// 处理结果
MvResult search = null;
try {
if (mvService != null) {
search = mvService.search(query, max);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
// 返回数据
String jsonString = Json.encode(search);
req.response()
.putHeader("content-type", "application/json")
.end(jsonString);
}
public static void main(String[] args) throws Exception {
Vertx vertx = Vertx.vertx();
vertx.deployVerticle(new MainVerticle());
}
}
@@ -1,13 +0,0 @@
package com.leosam.tvbox.mv;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TvboxMvApplication {
public static void main(String[] args) {
SpringApplication.run(TvboxMvApplication.class, args);
}
}
@@ -1,26 +0,0 @@
package com.leosam.tvbox.mv.controller;
import com.leosam.tvbox.mv.data.MvResult;
import com.leosam.tvbox.mv.service.MvService;
import com.leosam.tvbox.mv.utils.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author admin
* @since 2023/6/10 17:34
*/
@RestController
public class IndexController {
@Autowired
private MvService mvService;
@RequestMapping(value = {"/mv/search"})
public MvResult searchMv(String query, String maxCount) throws Exception {
int max = Math.min(Math.max(NumberUtils.toInt(maxCount, 100), 10), 1000);
return mvService.search(query, max);
}
}
@@ -1,28 +0,0 @@
package com.leosam.tvbox.mv.controller;
import com.leosam.tvbox.mv.data.VodResult;
import com.leosam.tvbox.mv.service.MvService;
import com.leosam.tvbox.mv.utils.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author admin
* @since 2023/6/11 18:54
*/
@RestController
public class VodController {
@Autowired
private MvService mvService;
@RequestMapping(value = {"/mv/vod"})
public VodResult searchVod(String wd, String ac, String ids, String maxCount) throws Exception {
int max = Math.min(Math.max(NumberUtils.toInt(maxCount, 100), 10), 1000);
String query = StringUtils.hasText(wd) ? wd : ids;
return mvService.searchVod(query, max);
}
}
@@ -1,10 +1,12 @@
package com.leosam.tvbox.mv.lucene;
import io.vertx.ext.web.impl.LRUCache;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.StoredFields;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
@@ -24,39 +26,58 @@ import java.nio.file.Paths;
public class MvSearcher {
private IndexReader reader;
private IndexSearcher searcher;
private StoredFields storedFields;
private final LRUCache<Integer, Document> documentLruCache = new LRUCache<>(1000);
private final LRUCache<String, TopDocs> topDocsLruCache = new LRUCache<>(100);
public MvSearcher(String indexDirectoryPath) throws IOException {
Path indexPath = Paths.get(indexDirectoryPath);
Directory directory = FSDirectory.open(indexPath);
reader = DirectoryReader.open(directory);
searcher = new IndexSearcher(reader);
storedFields = reader.storedFields();
}
public TopDocs searchTopDocs(String field, String queryStr, int maxHit) throws Exception {
String cacheKey = field + "_" + queryStr + "_" + maxHit;
TopDocs topDocs = topDocsLruCache.get(cacheKey);
if (topDocs != null) {
return topDocs;
}
Analyzer analyzer = new SmartChineseAnalyzer();
// Analyzer analyzer = new StandardAnalyzer();
QueryParser parser = new QueryParser(field, analyzer);
Query query = parser.parse(queryStr);
TopDocs docs = searcher.search(query, maxHit);
topDocsLruCache.put(cacheKey, docs);
return docs;
}
public void search(String field, String queryStr) throws Exception {
Analyzer analyzer = new SmartChineseAnalyzer();
// Analyzer analyzer = new StandardAnalyzer();
// Analyzer analyzer = new StandardAnalyzer();
QueryParser parser = new QueryParser(field, analyzer);
Query query = parser.parse(queryStr);
TopDocs docs = searcher.search(query, 200);
// 处理搜索结果
for (ScoreDoc scoreDoc : docs.scoreDocs) {
Document document = searcher.doc(scoreDoc.doc);
Document document = getDocument(scoreDoc.doc);
System.out.println(document.get("name") + "," + document.get("url") + " score=" + scoreDoc.score);
}
}
public Document getDocument(int docID) throws IOException {
Document document = searcher.doc(docID);
public Document getDocument(int docId) throws IOException {
Document document = documentLruCache.get(docId);
if (document != null) {
return document;
}
document = storedFields.document(docId);
if (document != null) {
documentLruCache.put(docId, document);
}
return document;
}
@@ -7,6 +7,9 @@ import com.leosam.tvbox.mv.data.VodResult;
import com.leosam.tvbox.mv.lucene.MvIndex;
import com.leosam.tvbox.mv.lucene.MvSearcher;
import com.leosam.tvbox.mv.utils.ClassPathReaderUtils;
import com.leosam.tvbox.mv.utils.CollectionUtils;
import com.leosam.tvbox.mv.utils.StopWatch;
import com.leosam.tvbox.mv.utils.StringUtils;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
@@ -15,14 +18,10 @@ import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedList;
@@ -34,16 +33,16 @@ import java.util.concurrent.atomic.AtomicInteger;
* @author admin
* @since 2023/6/10 17:35
*/
@Service
public class MvService implements InitializingBean {
public class MvService {
private static final Logger logger = LoggerFactory.getLogger(MvService.class);
private static final String indexDirectoryPath = "index";
private static final String MV_FILE = "tvbox/16wMV.txt";
private MvSearcher mvSearcher;
public MvResult search(String query, int max) throws Exception {
if (!StringUtils.hasText(query)) {
if (StringUtils.isEmpty(query) || mvSearcher == null) {
return new MvResult();
}
@@ -55,7 +54,7 @@ public class MvService implements InitializingBean {
MvResult result = new MvResult();
result.setQuery(query);
result.setTotalHits(topDocs.totalHits.value);
result.setList(new LinkedList<>());
result.setList(new ArrayList<>(topDocs.scoreDocs.length));
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
Document document = mvSearcher.getDocument(scoreDoc.doc);
if (document == null) {
@@ -66,8 +65,9 @@ public class MvService implements InitializingBean {
String songName = name;
String songUser = null;
if (name.contains("-")) {
songUser = name.split("-", 2)[0];
songName = name.split("-", 2)[1];
String[] split = name.split("-", 2);
songUser = split[0];
songName = split[1];
}
String url = document.get("url");
if (!url.startsWith("http")) {
@@ -96,12 +96,13 @@ public class MvService implements InitializingBean {
vod.setVodId(wd);
vod.setVodName(wd);
vod.setVodPlayFrom("mv");
vod.setVodPic("http://m4.auto.itc.cn/auto/content/20230611/45d65d8a001f0c5aa008030f41c98666.jpeg");
vod.setVodPic("http://yanxuan.nosdn.127.net/b6fb987ce79f308949e44f5129a4b51c.jpeg");
// vod.setVodPic("http://m4.auto.itc.cn/auto/content/20230611/45d65d8a001f0c5aa008030f41c98666.jpeg");
List<String> playUrlList = new LinkedList<>();
Set<String> vodActorList = new LinkedHashSet<>();
for (MvContent content : search.getList()) {
playUrlList.add(content.getName() + "$" + content.getUrl());
if (vodActorList.size() < 5 && StringUtils.hasText(content.getSongUser())) {
if (vodActorList.size() < 5 && StringUtils.isNotEmpty(content.getSongUser())) {
vodActorList.add(content.getSongUser());
}
}
@@ -115,8 +116,8 @@ public class MvService implements InitializingBean {
return vodResult;
}
@Override
public void afterPropertiesSet() throws Exception {
public void initIndex() throws Exception {
// 重建索引
String indexAbsolutePath = new File(indexDirectoryPath).getAbsoluteFile().getAbsolutePath();
reBuildIndex(indexAbsolutePath);
@@ -130,13 +131,22 @@ public class MvService implements InitializingBean {
private static void reBuildIndex(String indexDirectoryPath) throws IOException {
File file = new File(indexDirectoryPath).getAbsoluteFile();
int mvFileSize = ClassPathReaderUtils.getSize(MV_FILE);
File mvFileSizeTxt = Path.of(file.getAbsolutePath(), "" + mvFileSize + ".txt").toFile();
if (mvFileSizeTxt.exists()) {
logger.info("{}已经索引, 跳过索引", MV_FILE);
return;
}
StopWatch stopWatch = new StopWatch();
logger.info("重建索引中....");
// 清空以前的索引
stopWatch.start("清空以前索引");
logger.info("清空历史索引....");
File file = new File(indexDirectoryPath).getAbsoluteFile();
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File file1 : files) {
@@ -152,7 +162,7 @@ public class MvService implements InitializingBean {
logger.info("创建索引中....");
AtomicInteger line = new AtomicInteger();
MvIndex mvIndex = new MvIndex(indexDirectoryPath);
ClassPathReaderUtils.getBufferedReader("tvbox/16wMV.txt").lines()
ClassPathReaderUtils.getBufferedReader(MV_FILE).lines()
.filter(l -> l.contains(",h"))
.filter(l -> l.contains("-"))
.forEach(l -> {
@@ -182,6 +192,7 @@ public class MvService implements InitializingBean {
}
});
mvIndex.close();
mvFileSizeTxt.createNewFile();
logger.info("创建索引中....完成, 总共{}条", line.get());
stopWatch.stop();
logger.info("重建索引中....完成,耗时 {} 毫秒, 索引位置:{}", stopWatch.getTotalTimeMillis(), file.getPath());
@@ -1,12 +1,12 @@
package com.leosam.tvbox.mv.utils;
import org.springframework.core.io.ClassPathResource;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
/**
* @author admin
@@ -14,19 +14,33 @@ import java.util.Properties;
*/
public class ClassPathReaderUtils {
public static int getSize(String path) {
try {
ClassPathResource resource = new ClassPathResource(path);
InputStream inputStream = resource.getInputStream();
int available = inputStream.available();
return available;
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
public static InputStreamReader getInputStreamReader(String path) {
try {
ClassPathResource resource = new ClassPathResource(path);
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream(),StandardCharsets.UTF_8);
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8);
return inputStreamReader;
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
public static BufferedReader getBufferedReader(String path) {
try {
ClassPathResource resource = new ClassPathResource(path);
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream(),StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(inputStreamReader);
return reader;
@@ -35,17 +49,6 @@ public class ClassPathReaderUtils {
}
}
public static Properties getProperties(String path) {
Properties properties = new Properties();
try {
ClassPathResource resource = new ClassPathResource(path);
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
properties.load(new BufferedReader(inputStreamReader));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return properties;
}
public static String getContent(String path) {
StringBuilder content = new StringBuilder();
@@ -70,5 +73,52 @@ public class ClassPathReaderUtils {
System.out.println(json);
}
public static class ClassPathResource {
private final String path;
private final ClassLoader classLoader;
public ClassPathResource(String path) {
this.path = path;
this.classLoader = getDefaultClassLoader();
}
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
} else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(path + " cannot be opened because it does not exist");
}
return is;
}
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
} catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = ClassPathResource.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
cl = ClassLoader.getSystemClassLoader();
} catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return cl;
}
}
}
@@ -0,0 +1,15 @@
package com.leosam.tvbox.mv.utils;
import java.util.Collection;
/**
* @author admin
* @since 2023/6/12 21:15
*/
public class CollectionUtils {
public static boolean isEmpty(Collection<?> collection) {
return (collection == null || collection.isEmpty());
}
}
@@ -1,93 +0,0 @@
package com.leosam.tvbox.mv.utils;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashMap;
/**
*
*/
public class JsonUtils {
private static final Logger LOG = LoggerFactory.getLogger(JsonUtils.class);
private static final TypeReference<HashMap<String, String>> TYPE_MAP =
new TypeReference<HashMap<String, String>>() {
};
public static ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.registerModule(new Jdk8Module());
objectMapper.enable(JsonParser.Feature.ALLOW_COMMENTS);
}
public static String writeValue(Object o) {
try {
return writeValueThrowException(o);
} catch (Exception e) {
return "";
}
}
public static String writeValuePrettyPrinter(Object o) {
try {
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(o);
} catch (Exception e) {
LOG.error("exception occur when Json serializePretty", e);
return "";
}
}
public static String writeValueThrowException(Object o) throws JsonProcessingException {
try {
return objectMapper.writeValueAsString(o);
} catch (Exception e) {
LOG.error("exception occur when Json serialize", e);
throw e;
}
}
public static <T> T readValue(String json, Class<T> clazz) {
try {
return readValueThrowException(json, clazz);
} catch (IOException e) {
return null;
}
}
public static <T> T readValueThrowException(String json, Class<T> clazz) throws IOException {
try {
return objectMapper.readValue(json, clazz);
} catch (IOException e) {
LOG.warn("Failed to deserialize from JSON string", e);
throw e;
}
}
public static <T> T readValue(String json, TypeReference<T> typeReference) {
try {
return readValueThrowException(json, typeReference);
} catch (IOException e) {
return null;
}
}
public static <T> T readValueThrowException(String json, TypeReference<T> typeReference) throws IOException {
try {
return objectMapper.readValue(json, typeReference);
} catch (IOException e) {
LOG.warn("Failed to deserialize from JSON string", e);
throw e;
}
}
}
@@ -7,7 +7,7 @@ package com.leosam.tvbox.mv.utils;
public class NumberUtils {
public static int toInt(final String str, final int defaultValue) {
if (str == null) {
if (str == null || str.length() == 0) {
return defaultValue;
}
try {
@@ -0,0 +1,392 @@
package com.leosam.tvbox.mv.utils;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Simple stop watch, allowing for timing of a number of tasks, exposing total
* running time and running time for each named task.
*
* <p>Conceals use of {@link System#nanoTime()}, improving the readability of
* application code and reducing the likelihood of calculation errors.
*
* <p>Note that this object is not designed to be thread-safe and does not use
* synchronization.
*
* <p>This class is normally used to verify performance during proof-of-concept
* work and in development, rather than as part of production applications.
*
* <p>As of Spring Framework 5.2, running time is tracked and reported in
* nanoseconds.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Sam Brannen
* @since May 2, 2001
*/
public class StopWatch {
/**
* Identifier of this {@code StopWatch}.
* <p>Handy when we have output from multiple stop watches and need to
* distinguish between them in log or console output.
*/
private final String id;
private boolean keepTaskList = true;
private final List<TaskInfo> taskList = new ArrayList<>(1);
/**
* Start time of the current task.
*/
private long startTimeNanos;
/**
* Name of the current task.
*/
private String currentTaskName;
private TaskInfo lastTaskInfo;
private int taskCount;
/**
* Total running time.
*/
private long totalTimeNanos;
/**
* Construct a new {@code StopWatch}.
* <p>Does not start any task.
*/
public StopWatch() {
this("");
}
/**
* Construct a new {@code StopWatch} with the given ID.
* <p>The ID is handy when we have output from multiple stop watches and need
* to distinguish between them.
* <p>Does not start any task.
*
* @param id identifier for this stop watch
*/
public StopWatch(String id) {
this.id = id;
}
/**
* Get the ID of this {@code StopWatch}, as specified on construction.
*
* @return the ID (empty String by default)
* @see #StopWatch(String)
* @since 4.2.2
*/
public String getId() {
return this.id;
}
/**
* Configure whether the {@link StopWatch.TaskInfo} array is built over time.
* <p>Set this to {@code false} when using a {@code StopWatch} for millions
* of intervals; otherwise, the {@code TaskInfo} structure will consume
* excessive memory.
* <p>Default is {@code true}.
*/
public void setKeepTaskList(boolean keepTaskList) {
this.keepTaskList = keepTaskList;
}
/**
* Start an unnamed task.
* <p>The results are undefined if {@link #stop()} or timing methods are
* called without invoking this method first.
*
* @see #start(String)
* @see #stop()
*/
public void start() throws IllegalStateException {
start("");
}
/**
* Start a named task.
* <p>The results are undefined if {@link #stop()} or timing methods are
* called without invoking this method first.
*
* @param taskName the name of the task to start
* @see #start()
* @see #stop()
*/
public void start(String taskName) throws IllegalStateException {
if (this.currentTaskName != null) {
throw new IllegalStateException("Can't start StopWatch: it's already running");
}
this.currentTaskName = taskName;
this.startTimeNanos = System.nanoTime();
}
/**
* Stop the current task.
* <p>The results are undefined if timing methods are called without invoking
* at least one pair of {@code start()} / {@code stop()} methods.
*
* @see #start()
* @see #start(String)
*/
public void stop() throws IllegalStateException {
if (this.currentTaskName == null) {
throw new IllegalStateException("Can't stop StopWatch: it's not running");
}
long lastTime = System.nanoTime() - this.startTimeNanos;
this.totalTimeNanos += lastTime;
this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
if (this.keepTaskList) {
this.taskList.add(this.lastTaskInfo);
}
++this.taskCount;
this.currentTaskName = null;
}
/**
* Determine whether this {@code StopWatch} is currently running.
*
* @see #currentTaskName()
*/
public boolean isRunning() {
return (this.currentTaskName != null);
}
/**
* Get the name of the currently running task, if any.
*
* @see #isRunning()
* @since 4.2.2
*/
public String currentTaskName() {
return this.currentTaskName;
}
/**
* Get the time taken by the last task in nanoseconds.
*
* @see #getLastTaskTimeMillis()
* @since 5.2
*/
public long getLastTaskTimeNanos() throws IllegalStateException {
if (this.lastTaskInfo == null) {
throw new IllegalStateException("No tasks run: can't get last task interval");
}
return this.lastTaskInfo.getTimeNanos();
}
/**
* Get the time taken by the last task in milliseconds.
*
* @see #getLastTaskTimeNanos()
*/
public long getLastTaskTimeMillis() throws IllegalStateException {
if (this.lastTaskInfo == null) {
throw new IllegalStateException("No tasks run: can't get last task interval");
}
return this.lastTaskInfo.getTimeMillis();
}
/**
* Get the name of the last task.
*/
public String getLastTaskName() throws IllegalStateException {
if (this.lastTaskInfo == null) {
throw new IllegalStateException("No tasks run: can't get last task name");
}
return this.lastTaskInfo.getTaskName();
}
/**
* Get the last task as a {@link StopWatch.TaskInfo} object.
*/
public StopWatch.TaskInfo getLastTaskInfo() throws IllegalStateException {
if (this.lastTaskInfo == null) {
throw new IllegalStateException("No tasks run: can't get last task info");
}
return this.lastTaskInfo;
}
/**
* Get the total time in nanoseconds for all tasks.
*
* @see #getTotalTimeMillis()
* @see #getTotalTimeSeconds()
* @since 5.2
*/
public long getTotalTimeNanos() {
return this.totalTimeNanos;
}
/**
* Get the total time in milliseconds for all tasks.
*
* @see #getTotalTimeNanos()
* @see #getTotalTimeSeconds()
*/
public long getTotalTimeMillis() {
return nanosToMillis(this.totalTimeNanos);
}
/**
* Get the total time in seconds for all tasks.
*
* @see #getTotalTimeNanos()
* @see #getTotalTimeMillis()
*/
public double getTotalTimeSeconds() {
return nanosToSeconds(this.totalTimeNanos);
}
/**
* Get the number of tasks timed.
*/
public int getTaskCount() {
return this.taskCount;
}
/**
* Get an array of the data for tasks performed.
*/
public StopWatch.TaskInfo[] getTaskInfo() {
if (!this.keepTaskList) {
throw new UnsupportedOperationException("Task info is not being kept!");
}
return this.taskList.toArray(new StopWatch.TaskInfo[0]);
}
/**
* Get a short description of the total running time.
*/
public String shortSummary() {
return "StopWatch '" + getId() + "': running time = " + getTotalTimeNanos() + " ns";
}
/**
* Generate a string with a table describing all tasks performed.
* <p>For custom reporting, call {@link #getTaskInfo()} and use the task info
* directly.
*/
public String prettyPrint() {
StringBuilder sb = new StringBuilder(shortSummary());
sb.append('\n');
if (!this.keepTaskList) {
sb.append("No task info kept");
} else {
sb.append("---------------------------------------------\n");
sb.append("ns % Task name\n");
sb.append("---------------------------------------------\n");
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMinimumIntegerDigits(9);
nf.setGroupingUsed(false);
NumberFormat pf = NumberFormat.getPercentInstance();
pf.setMinimumIntegerDigits(3);
pf.setGroupingUsed(false);
for (StopWatch.TaskInfo task : getTaskInfo()) {
sb.append(nf.format(task.getTimeNanos())).append(" ");
sb.append(pf.format((double) task.getTimeNanos() / getTotalTimeNanos())).append(" ");
sb.append(task.getTaskName()).append('\n');
}
}
return sb.toString();
}
/**
* Generate an informative string describing all tasks performed
* <p>For custom reporting, call {@link #getTaskInfo()} and use the task info
* directly.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder(shortSummary());
if (this.keepTaskList) {
for (StopWatch.TaskInfo task : getTaskInfo()) {
sb.append("; [").append(task.getTaskName()).append("] took ").append(task.getTimeNanos()).append(" ns");
long percent = Math.round(100.0 * task.getTimeNanos() / getTotalTimeNanos());
sb.append(" = ").append(percent).append('%');
}
} else {
sb.append("; no task info kept");
}
return sb.toString();
}
private static long nanosToMillis(long duration) {
return TimeUnit.NANOSECONDS.toMillis(duration);
}
private static double nanosToSeconds(long duration) {
return duration / 1_000_000_000.0;
}
/**
* Nested class to hold data about one task executed within the {@code StopWatch}.
*/
public static final class TaskInfo {
private final String taskName;
private final long timeNanos;
TaskInfo(String taskName, long timeNanos) {
this.taskName = taskName;
this.timeNanos = timeNanos;
}
/**
* Get the name of this task.
*/
public String getTaskName() {
return this.taskName;
}
/**
* Get the time in nanoseconds this task took.
*
* @see #getTimeMillis()
* @see #getTimeSeconds()
* @since 5.2
*/
public long getTimeNanos() {
return this.timeNanos;
}
/**
* Get the time in milliseconds this task took.
*
* @see #getTimeNanos()
* @see #getTimeSeconds()
*/
public long getTimeMillis() {
return nanosToMillis(this.timeNanos);
}
/**
* Get the time in seconds this task took.
*
* @see #getTimeMillis()
* @see #getTimeNanos()
*/
public double getTimeSeconds() {
return nanosToSeconds(this.timeNanos);
}
}
}
@@ -0,0 +1,16 @@
package com.leosam.tvbox.mv.utils;
/**
* @author admin
* @since 2023/6/12 21:32
*/
public class StringUtils {
public static boolean isNotEmpty(final CharSequence cs) {
return !isEmpty(cs);
}
public static boolean isEmpty(final CharSequence cs) {
return cs == null || cs.length() == 0;
}
}
@@ -0,0 +1,20 @@
package com.leosam.tvbox.mv.utils;
import io.vertx.ext.web.RoutingContext;
import java.util.List;
/**
* @author admin
* @since 2023/6/12 21:16
*/
public class VertxUtils {
public static String queryParam(RoutingContext context, String wd) {
List<String> param = context.queryParam(wd);
if (param != null && param.size() > 0) {
return param.get(0);
}
return "";
}
}
@@ -1,40 +0,0 @@
package com.leosam.tvbox.mv.view;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* @author admin
* @since 2023/6/11 20:03
*/
public class MyTimeInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(MyTimeInterceptor.class);
/**
* 线程变量 - 开始执行时间
*/
private final ThreadLocal<Long> startTime = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
startTime.set(System.nanoTime());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
if (startTime.get() == null) {
return;
}
long processTime = System.nanoTime() - startTime.get();
if (logger.isInfoEnabled()) {
logger.info("RequestURL[{}], Referer[{}], TIME[{} ms]", request.getRequestURL().toString(), request.getHeader("Referer"), String.format("%.3f", (processTime / 1_000_000.0)));
}
startTime.remove();
}
}
@@ -1,27 +0,0 @@
package com.leosam.tvbox.mv.view;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author admin
*/
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 页面运行时间统计
registry.addInterceptor(new MyTimeInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/static/**")
.excludePathPatterns("/favicon.ico");
}
}
-34
View File
@@ -1,34 +0,0 @@
server:
port: 7777
spring:
profiles:
active: test
logging:
file:
name: ./logs/tvbox-mv.log
level:
.: error
org.spring.framework: debug
com.leosam: debug
---
server:
port: 7777
spring:
config:
activate:
on-profile: test
logging:
file:
name: ./logs/tvbox-mv.log
---
server:
port: 7777
spring:
config:
activate:
on-profile: linux
logging:
file:
name: /opt/logs/tvbox-mv.log
+66
View File
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 不输出logback 初始化日志 -->
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
<property name="ENCODING" value="utf-8" />
<property name="PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %n"/>
<property name="PATTERN-COLOR" value="%yellow(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%thread] %highlight(%-5level) %green(%logger{36}) - %highlight(%msg) %n"/>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${PATTERN}</pattern>
</encoder>
</appender>
<!-- 控制台输出-带颜色 -->
<appender name="CONSOLE-WITH-COLOR" class="ch.qos.logback.core.ConsoleAppender">
<!-- 根据操作环境设置编码器 -->
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<!-- 设置控制台输出编码 -->
<charset>${ENCODING}</charset>
</encoder>
<if condition=' !property("java.class.path").contains("SNAPSHOT") || property("jboss.modules.system.pkgs").contains("intellij")'>
<then>
<!-- 控制台输出-带颜色 -->
<encoder>
<pattern>${PATTERN-COLOR}</pattern>
<withJansi>true</withJansi>
</encoder>
</then>
<else>
<!-- 控制台输出-不带颜色 -->
<encoder>
<pattern>${PATTERN}</pattern>
<withJansi>true</withJansi>
</encoder>
</else>
</if>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>./logs/run.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志保存7天 -->
<maxHistory>7</maxHistory>
<fileNamePattern>./logs/run.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>${PATTERN}</pattern>
</encoder>
</appender>
<logger name="io.vertx" level="INFO"/>
<logger name="io.netty" level="INFO"/>
<root level="DEBUG">
<appender-ref ref="CONSOLE-WITH-COLOR" />
<appender-ref ref="FILE" />
</root>
</configuration>
+13
View File
@@ -0,0 +1,13 @@
{
"sites": [
{
"key": "MV_vod",
"name": "👀┃MV┃视频",
"type": 1,
"api": "http://192.168.31.253:7777/mv/vod",
"searchable": 1,
"quickSearch": 1,
"filterable": 1
}
]
}
@@ -0,0 +1,22 @@
package com.leosam.tvbox.mv;
import io.vertx.core.Vertx;
import io.vertx.junit5.VertxExtension;
import io.vertx.junit5.VertxTestContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(VertxExtension.class)
public class TestMainVerticle {
@BeforeEach
void deploy_verticle(Vertx vertx, VertxTestContext testContext) {
vertx.deployVerticle(new MainVerticle(), testContext.succeeding(id -> testContext.completeNow()));
}
@Test
void verticle_deployed(Vertx vertx, VertxTestContext testContext) throws Throwable {
testContext.completeNow();
}
}
@@ -1,13 +0,0 @@
package com.leosam.tvbox.mv;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class TvboxMvApplicationTests {
@Test
void contextLoads() {
}
}