Compare commits

..

10 Commits

Author SHA1 Message Date
ddb78e29a3 feat: 首次提交NBA项目 2026-05-14 22:09:55 +08:00
0adfe7d539 Merge remote-tracking branch 'origin/master' 2025-06-13 16:42:25 +08:00
332b79c13e 优化 2025-06-13 16:41:47 +08:00
7231281302 update src/main/resources/application.yml. 2025-04-26 02:03:46 +00:00
6bb06266b0 update README.md.
Signed-off-by: Bravepping <1131302745@qq.com>
2025-04-23 15:31:28 +00:00
251e8446d6 update README.md.
Signed-off-by: Bravepping <1131302745@qq.com>
2025-04-23 15:24:29 +00:00
2d152da62a update README.md.
Signed-off-by: Bravepping <1131302745@qq.com>
2025-04-23 15:24:22 +00:00
ffb564db01 add README.md.
Signed-off-by: Bravepping <1131302745@qq.com>
2025-04-23 15:23:25 +00:00
1c68932238 优化 2025-04-22 18:47:36 +08:00
31dc7c9cde 项目完善,全自动获取腾讯体育NBA比赛直播源 2025-04-21 17:31:05 +08:00
51 changed files with 2990 additions and 337 deletions

278
.idea/IntelliLang.xml generated Normal file
View File

@@ -0,0 +1,278 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="LanguageInjectionConfiguration">
<injection language="http-header-reference" injector-id="java">
<display-name>Apache HttpClient 4 HTTP Header (org.apache.http)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("setHeader", "addHeader", "getFirstHeader", "getLastHeader", "removeHeaders").definedInClass("org.apache.http.HttpMessage"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("setHeader", "addHeader", "getFirstHeader", "getLastHeader", "removeHeaders").definedInClass("org.apache.http.message.AbstractHttpMessage"))]]></place>
</injection>
<injection language="http-header-reference" injector-id="java">
<display-name>Apache HttpClient 5 HTTP Header (org.apache.hc.core5)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("setHeader", "addHeader").definedInClass("org.apache.hc.core5.http.message.BasicHttpRequest"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("setHeader", "addHeader", "getFirstHeader", "getLastHeader", "removeHeaders").definedInClass("org.apache.hc.client5.http.async.methods.SimpleRequestBuilder"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("setHeader", "addHeader", "getFirstHeader", "getLastHeader", "removeHeaders").definedInClass("org.apache.hc.core5.http.io.support.ClassicRequestBuilder"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("setHeader", "addHeader", "getFirstHeader", "getLastHeader", "removeHeaders").definedInClass("org.apache.hc.core5.http.nio.support.AsyncRequestBuilder"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("setHeader", "addHeader", "getFirstHeader", "getLastHeader", "removeHeaders").definedInClass("org.apache.hc.core5.http.support.BasicRequestBuilder"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>AsyncQueryRunner (org.apache.commons.dbutils)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("batch").withParameterCount(2).definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("insertBatch").withParameterCount(3).definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "insert").withParameters("java.lang.String", "org.apache.commons.dbutils.ResultSetHandler").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "insert").withParameters("java.lang.String", "org.apache.commons.dbutils.ResultSetHandler", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("update").withParameters("java.lang.String").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("update").withParameters("java.lang.String", "java.lang.Object").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("update").withParameters("java.lang.String", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("batch").withParameterCount(3).definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("insertBatch").withParameterCount(4).definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("query", "insert").withParameters("java.sql.Connection", "java.lang.String", "org.apache.commons.dbutils.ResultSetHandler").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("query", "insert").withParameters("java.sql.Connection", "java.lang.String", "org.apache.commons.dbutils.ResultSetHandler", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("update").withParameters("java.sql.Connection", "java.lang.String").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("update").withParameters("java.sql.Connection", "java.lang.String", "java.lang.Object").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("update").withParameters("java.sql.Connection", "java.lang.String", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>Jodd (jodd.db)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query").withParameterCount(1).definedInClass("jodd.db.DbQuery"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("DbQuery").withParameterCount(2).definedInClass("jodd.db.DbQuery"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("query").withParameterCount(2).definedInClass("jodd.db.DbQuery"))]]></place>
<place><![CDATA[psiParameter().ofMethod(2, psiMethod().withName("DbQuery").withParameterCount(3).definedInClass("jodd.db.DbQuery"))]]></place>
</injection>
<injection language="http-header-reference" injector-id="java">
<display-name>MockServer Header (org.mockserver)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("header").definedInClass("org.mockserver.model.Header"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("withHeader").definedInClass("org.mockserver.model.HttpRequest"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("withHeader", "getHeader", "getFirstHeader", "containsHeader", "removeHeader").definedInClass("org.mockserver.model.HttpResponse"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>QueryRunner (org.apache.commons.dbutils)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("batch").withParameterCount(2).definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("insertBatch").withParameterCount(3).definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "insert").withParameters("java.lang.String", "org.apache.commons.dbutils.ResultSetHandler").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "insert", "execute").withParameters("java.lang.String", "org.apache.commons.dbutils.ResultSetHandler", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("update").withParameters("java.lang.String").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("update").withParameters("java.lang.String", "java.lang.Object").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("update", "execute").withParameters("java.lang.String", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("batch").withParameterCount(3).definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("insertBatch").withParameterCount(4).definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("query", "insert").withParameters("java.sql.Connection", "java.lang.String", "org.apache.commons.dbutils.ResultSetHandler", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("query", "insert", "execute").withParameters("java.sql.Connection", "java.lang.String", "org.apache.commons.dbutils.ResultSetHandler").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("update").withParameters("java.sql.Connection", "java.lang.String").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("update").withParameters("java.sql.Connection", "java.lang.String", "java.lang.Object").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("update", "execute").withParameters("java.sql.Connection", "java.lang.String", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>R2DBC (io.r2dbc)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("add").definedInClass("io.r2dbc.spi.Batch"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("createStatement").definedInClass("io.r2dbc.spi.Connection"))]]></place>
</injection>
<injection language="PostgreSQL" injector-id="java">
<display-name>Reactiverse Postgres Client (io.reactiverse)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.reactiverse.pgclient.PgConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.reactiverse.pgclient.PgTransaction"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.reactiverse.reactivex.pgclient.PgClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.reactiverse.reactivex.pgclient.PgConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.reactiverse.reactivex.pgclient.PgPool"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.reactiverse.reactivex.pgclient.PgTransaction"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "preparedQuery", "preparedBatch").definedInClass("io.reactiverse.axle.pgclient.PgClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "preparedQuery", "preparedBatch").definedInClass("io.reactiverse.pgclient.PgClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "preparedQuery", "preparedBatch").definedInClass("io.reactiverse.pgclient.PgPool"))]]></place>
</injection>
<injection language="http-header-reference" injector-id="java">
<display-name>RestAssured HTTP Header (io.restassured)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("addHeader").definedInClass("io.restassured.builder.RequestSpecBuilder"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("header", "getHeader", "headers").definedInClass("io.restassured.response.ResponseOptions"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("header", "getHeader", "headers").definedInClass("io.restassured.response.ValidatableResponseOptions"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("header", "headers").definedInClass("io.restassured.specification.RequestSpecification"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>SmallRye Axle SqlClient (io.vertx.axle.sqlclient)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "preparedQuery", "preparedBatch").definedInClass("io.vertx.axle.sqlclient.Pool"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "preparedQuery", "preparedBatch").definedInClass("io.vertx.axle.sqlclient.SqlClient"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>SmallRye Mutiny SqlClient (io.vertx.mutiny.sqlclient)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "preparedQuery", "preparedBatch").definedInClass("io.vertx.mutiny.sqlclient.Pool"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "preparedQuery", "preparedBatch").definedInClass("io.vertx.mutiny.sqlclient.SqlClient"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>SmallRye Mutiny SqlConnection (io.vertx.mutiny.sqlclient)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("prepare", "prepareAndAwait").definedInClass("io.vertx.mutiny.db2client.DB2Connection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("prepare", "prepareAndAwait").definedInClass("io.vertx.mutiny.mssqlclient.MSSQLConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("prepare", "prepareAndAwait").definedInClass("io.vertx.mutiny.mysqlclient.MySQLConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("prepare", "prepareAndAwait").definedInClass("io.vertx.mutiny.pgclient.PgConnection"))]]></place>
</injection>
<injection language="SpEL" injector-id="java">
<display-name>Spring @Cacheable and @CacheEvict</display-name>
<single-file value="true" />
<place><![CDATA[psiMethod().withName("condition").withParameters().definedInClass("org.springframework.cache.annotation.CacheEvict")]]></place>
<place><![CDATA[psiMethod().withName("condition").withParameters().definedInClass("org.springframework.cache.annotation.CachePut")]]></place>
<place><![CDATA[psiMethod().withName("condition").withParameters().definedInClass("org.springframework.cache.annotation.Cacheable")]]></place>
<place><![CDATA[psiMethod().withName("key").withParameters().definedInClass("org.springframework.cache.annotation.CacheEvict")]]></place>
<place><![CDATA[psiMethod().withName("key").withParameters().definedInClass("org.springframework.cache.annotation.CachePut")]]></place>
<place><![CDATA[psiMethod().withName("key").withParameters().definedInClass("org.springframework.cache.annotation.Cacheable")]]></place>
<place><![CDATA[psiMethod().withName("unless").withParameters().definedInClass("org.springframework.cache.annotation.CachePut")]]></place>
<place><![CDATA[psiMethod().withName("unless").withParameters().definedInClass("org.springframework.cache.annotation.Cacheable")]]></place>
</injection>
<injection language="http-header-reference" injector-id="java">
<display-name>Spring HttpHeaders (org.springframework.http)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("header").definedInClass("org.springframework.http.ResponseEntity.HeadersBuilder"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("header").definedInClass("org.springframework.web.servlet.function.ServerResponse.HeadersBuilder"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("set", "add", "addAll", "getFirst", "containsKey", "get", "put", "getFirstDate", "setDate", "setInstant", "setZonedDateTime").definedInClass("org.springframework.http.HttpHeaders"))]]></place>
</injection>
<injection language="SpEL" injector-id="java">
<display-name>Spring Integration/Messaging</display-name>
<single-file value="true" />
<place><![CDATA[psiMethod().withName("expression").withParameters().definedInClass("org.springframework.messaging.handler.annotation.Payload")]]></place>
<place><![CDATA[psiMethod().withName("value").withParameters().definedInClass("org.springframework.integration.annotation.Payload")]]></place>
<place><![CDATA[psiMethod().withName("value").withParameters().definedInClass("org.springframework.messaging.handler.annotation.Payload")]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>Spring JDBC (org.springframework.jdbc.core.JdbcOperations)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("batchUpdate").definedInClass("org.springframework.jdbc.core.JdbcOperations"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("execute").definedInClass("org.springframework.jdbc.core.JdbcOperations"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query").definedInClass("org.springframework.jdbc.core.JdbcOperations"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("queryForInt").definedInClass("org.springframework.jdbc.core.JdbcOperations"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("queryForList").definedInClass("org.springframework.jdbc.core.JdbcOperations"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("queryForLong").definedInClass("org.springframework.jdbc.core.JdbcOperations"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("queryForMap").definedInClass("org.springframework.jdbc.core.JdbcOperations"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("queryForObject").definedInClass("org.springframework.jdbc.core.JdbcOperations"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("queryForRowSet").definedInClass("org.springframework.jdbc.core.JdbcOperations"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("queryForStream").definedInClass("org.springframework.jdbc.core.JdbcOperations"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("sql").definedInClass("org.springframework.jdbc.core.simple.JdbcClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("update").definedInClass("org.springframework.jdbc.core.JdbcOperations"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>Spring JDBC (org.springframework.jdbc.core.PreparedStatementCreatorFactory)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("PreparedStatementCreatorFactory").withParameters("java.lang.String").definedInClass("org.springframework.jdbc.core.PreparedStatementCreatorFactory"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("PreparedStatementCreatorFactory").withParameters("java.lang.String", "int[]").definedInClass("org.springframework.jdbc.core.PreparedStatementCreatorFactory"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("PreparedStatementCreatorFactory").withParameters("java.lang.String", "java.util.List").definedInClass("org.springframework.jdbc.core.PreparedStatementCreatorFactory"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>Spring JDBC (org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("createCustomException").withParameters("java.lang.String", "java.lang.String", "java.sql.SQLException", "java.lang.Class").definedInClass("org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("customTranslate").withParameters("java.lang.String", "java.lang.String", "java.sql.SQLException").definedInClass("org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("doTranslate").withParameters("java.lang.String", "java.lang.String", "java.sql.SQLException").definedInClass("org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("logTranslation").withParameters("java.lang.String", "java.lang.String", "java.sql.SQLException", "boolean").definedInClass("org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator"))]]></place>
</injection>
<injection language="SpEL" injector-id="java">
<display-name>Spring Security @PostAuthorize/@PostFilter/@PreAuthorize/@PreFilter/@AuthenticationPrincipal</display-name>
<single-file value="true" />
<place><![CDATA[psiMethod().withName("expression").withParameters().definedInClass("org.springframework.security.core.annotation.AuthenticationPrincipal")]]></place>
<place><![CDATA[psiMethod().withName("expression").withParameters().definedInClass("org.springframework.security.core.annotation.CurrentSecurityContext")]]></place>
<place><![CDATA[psiMethod().withName("value").withParameters().definedInClass("org.springframework.security.access.prepost.PostAuthorize")]]></place>
<place><![CDATA[psiMethod().withName("value").withParameters().definedInClass("org.springframework.security.access.prepost.PostFilter")]]></place>
<place><![CDATA[psiMethod().withName("value").withParameters().definedInClass("org.springframework.security.access.prepost.PreAuthorize")]]></place>
<place><![CDATA[psiMethod().withName("value").withParameters().definedInClass("org.springframework.security.access.prepost.PreFilter")]]></place>
</injection>
<injection language="SpEL" injector-id="java">
<display-name>Spring State Machine</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("event").withParameters("java.lang.String").definedInClass("org.springframework.statemachine.config.configurers.SecurityConfigurer"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("guardExpression").withParameters("java.lang.String").definedInClass("org.springframework.statemachine.config.configurers.TransitionConfigurer"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("secured").withParameters("java.lang.String").definedInClass("org.springframework.statemachine.config.configurers.TransitionConfigurer"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("transition").withParameters("java.lang.String").definedInClass("org.springframework.statemachine.config.configurers.SecurityConfigurer"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>Vert.x SQL Extensions (io.vertx.ext.sql)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "queryWithParams", "queryStream", "queryStreamWithParams", "querySingle", "querySingleWithParams", "update", "updateWithParams", "call", "callWithParams").definedInClass("io.vertx.ext.sql.SQLClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "queryWithParams", "queryStream", "queryStreamWithParams", "querySingle", "querySingleWithParams", "update", "updateWithParams", "call", "callWithParams").definedInClass("io.vertx.ext.sql.SQLOperations"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "queryWithParams", "queryStream", "queryStreamWithParams", "querySingle", "querySingleWithParams", "update", "updateWithParams", "call", "callWithParams", "execute", "batchWithParams", "batchCallableWithParams").definedInClass("io.vertx.ext.sql.SQLConnection"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>Vert.x SQL Reactive Extensions (io.vertx.reactivex.ext.sql)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "queryWithParams", "queryStream", "queryStreamWithParams", "querySingle", "querySingleWithParams", "update", "updateWithParams", "call", "callWithParams").definedInClass("io.vertx.reactivex.ext.sql.SQLOperations"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "queryWithParams", "queryStream", "queryStreamWithParams", "querySingle", "querySingleWithParams", "update", "updateWithParams", "call", "callWithParams", "execute", "batchWithParams", "batchCallableWithParams", "rxQuerySingle", "rxQuerySingleWithParams", "rxQuery", "rxQueryWithParams", "rxQueryStream", "rxQueryStreamWithParams", "rxUpdate", "rxUpdateWithParams", "rxCall", "rxCallWithParams", "rxExecute", "rxBatchWithParams", "rxBatchCallableWithParams").definedInClass("io.vertx.reactivex.ext.sql.SQLClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "queryWithParams", "queryStream", "queryStreamWithParams", "querySingle", "querySingleWithParams", "update", "updateWithParams", "call", "callWithParams", "execute", "batchWithParams", "batchCallableWithParams", "rxQuerySingle", "rxQuerySingleWithParams", "rxQuery", "rxQueryWithParams", "rxQueryStream", "rxQueryStreamWithParams", "rxUpdate", "rxUpdateWithParams", "rxCall", "rxCallWithParams", "rxExecute", "rxBatchWithParams", "rxBatchCallableWithParams").definedInClass("io.vertx.reactivex.ext.sql.SQLConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("querySingle", "rxQuerySingle", "querySingleWithParams", "rxQuerySingleWithParams").definedInClass("io.vertx.reactivex.ext.asyncsql.AsyncSQLClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("querySingle", "rxQuerySingle", "querySingleWithParams", "rxQuerySingleWithParams").definedInClass("io.vertx.reactivex.ext.asyncsql.MySQLClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("querySingle", "rxQuerySingle", "querySingleWithParams", "rxQuerySingleWithParams").definedInClass("io.vertx.reactivex.ext.asyncsql.PostgreSQLClient"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>Vert.x SqlClient (io.vertx.sqlclient)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.vertx.mssqlclient.MSSQLConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.vertx.mysqlclient.MySQLConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.vertx.pgclient.PgConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.vertx.sqlclient.Pool"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.vertx.sqlclient.SqlClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.vertx.sqlclient.SqlConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.vertx.sqlclient.Transaction"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>Vert.x SqlClient RxJava2 (io.vertx.reactivex.sqlclient)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.mysqlclient.MySQLConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.pgclient.PgConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.sqlclient.SqlConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.sqlclient.Transaction"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.mysqlclient.MySQLPool"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.pgclient.PgPool"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.sqlclient.Pool"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.sqlclient.SqlClient"))]]></place>
</injection>
<injection language="JSON" injector-id="java">
<display-name>WireMock (com.github.tomakehurst.wiremock.client)</display-name>
<single-file value="false" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("equalToJson").withParameters("java.lang.String").definedInClass("com.github.tomakehurst.wiremock.client.WireMock"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("equalToJson").withParameters("java.lang.String", "boolean", "boolean").definedInClass("com.github.tomakehurst.wiremock.client.WireMock"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("jsonResponse").withParameters("java.lang.String", "int").definedInClass("com.github.tomakehurst.wiremock.client.WireMock"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("okJson").withParameters("java.lang.String").definedInClass("com.github.tomakehurst.wiremock.client.WireMock"))]]></place>
</injection>
<injection language="XML" injector-id="java">
<display-name>WireMock (com.github.tomakehurst.wiremock.client)</display-name>
<single-file value="false" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("equalToXml").withParameters("java.lang.String").definedInClass("com.github.tomakehurst.wiremock.client.WireMock"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("okTextXml").withParameters("java.lang.String").definedInClass("com.github.tomakehurst.wiremock.client.WireMock"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("okXml").withParameters("java.lang.String").definedInClass("com.github.tomakehurst.wiremock.client.WireMock"))]]></place>
</injection>
<injection language="RegExp" injector-id="java">
<display-name>WireMock (com.github.tomakehurst.wiremock.client)</display-name>
<single-file value="false" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("matching").withParameters("java.lang.String").definedInClass("com.github.tomakehurst.wiremock.client.WireMock"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("notMatching").withParameters("java.lang.String").definedInClass("com.github.tomakehurst.wiremock.client.WireMock"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("urlMatching").withParameters("java.lang.String").definedInClass("com.github.tomakehurst.wiremock.client.WireMock"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("urlPathMatching").withParameters("java.lang.String").definedInClass("com.github.tomakehurst.wiremock.client.WireMock"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>jOOQ (org.jooq.DSLContext)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("batch").withParameters("java.lang.String", "java.lang.Object[]...").definedInClass("org.jooq.DSLContext"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "fetch", "fetchLazy", "fetchAsync", "fetchStream", "fetchMany", "fetchOne", "fetchSingle", "fetchOptional", "fetchValue", "fetchOptionalValue", "fetchValues", "execute", "resultQuery").withParameters("java.lang.String", "java.lang.Object...").definedInClass("org.jooq.DSLContext"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "fetch", "fetchLazy", "fetchAsync", "fetchStream", "fetchMany", "fetchOne", "fetchSingle", "fetchOptional", "fetchValue", "fetchOptionalValue", "fetchValues", "execute", "resultQuery", "batch").withParameters("java.lang.String").definedInClass("org.jooq.DSLContext"))]]></place>
<place><![CDATA[psiParameter().ofMethod(psiMethod().withName("batch").withParameters("java.lang.String...").definedInClass("org.jooq.DSLContext"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>rxjava2-jdbc (org.davidmoten.rx.jdbc)</display-name>
<single-file value="true" />
<place><![CDATA[psiMethod().withName("value").definedInClass("org.davidmoten.rx.jdbc.annotations.Query")]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("call", "select", "update").definedInClass("org.davidmoten.rx.jdbc.Database"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("call", "select", "update").definedInClass("org.davidmoten.rx.jdbc.TransactedBuilder"))]]></place>
</injection>
<injection language="SpEL" injector-id="xml">
<display-name>SpEL for Spring Cache</display-name>
<single-file value="true" />
<place><![CDATA[xmlAttribute().withLocalName("condition").withParent(xmlTag().withNamespace(string().equalTo("http://www.springframework.org/schema/cache")))]]></place>
<place><![CDATA[xmlAttribute().withLocalName("key").withParent(xmlTag().withNamespace(string().equalTo("http://www.springframework.org/schema/cache")))]]></place>
<place><![CDATA[xmlAttribute().withLocalName("unless").withParent(xmlTag().withNamespace(string().equalTo("http://www.springframework.org/schema/cache")))]]></place>
</injection>
</component>
</project>

28
.idea/dataSources.xml generated
View File

@@ -1,11 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="NBA@110.42.255.182" uuid="989a1610-33a4-4371-ba47-77639ee274e4">
<data-source source="LOCAL" name="nba@154.36.154.211" uuid="989a1610-33a4-4371-ba47-77639ee274e4">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://110.42.255.182:3306/NBA</jdbc-url>
<jdbc-url>jdbc:mysql://154.36.154.211:9002/nba</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="@103.244.88.91" uuid="198b1b07-b5bc-4475-ad72-25ab848d510c">
<driver-ref>redis</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>jdbc.RedisDriver</jdbc-driver>
<jdbc-url>jdbc:redis://103.244.88.91:6739/</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="0@154.36.154.211" uuid="310cedf0-fad7-43c7-84b6-88e011089af6">
<driver-ref>redis</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>jdbc.RedisDriver</jdbc-driver>
<jdbc-url>jdbc:redis://154.36.154.211:6379/0</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />

11
.idea/db-forest-config.xml generated Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="db-forest-configuration">
<data version="2">.
----------------------------------------
1:0:989a1610-33a4-4371-ba47-77639ee274e4
2:0:198b1b07-b5bc-4475-ad72-25ab848d510c
3:0:310cedf0-fad7-43c7-84b6-88e011089af6
.</data>
</component>
</project>

563
.idea/dbnavigator.xml generated Normal file
View File

@@ -0,0 +1,563 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DBNavigator.Project.DDLFileAttachmentManager">
<mappings />
<preferences />
</component>
<component name="DBNavigator.Project.DatabaseAssistantManager">
<assistants />
</component>
<component name="DBNavigator.Project.DatabaseBrowserManager">
<autoscroll-to-editor value="false" />
<autoscroll-from-editor value="true" />
<show-object-properties value="true" />
<loaded-nodes />
</component>
<component name="DBNavigator.Project.DatabaseConsoleManager">
<connection id="2a966116-5af8-4991-8ec7-bb1cab412346">
<console name="Connection" type="STANDARD" schema="nba" session="Main" />
</connection>
</component>
<component name="DBNavigator.Project.DatabaseFileManager">
<open-files />
</component>
<component name="DBNavigator.Project.DatasetFilterManager">
<filter-actions connection-id="2a966116-5af8-4991-8ec7-bb1cab412346" dataset="nba.nba_user" active-filter-id="" />
</component>
<component name="DBNavigator.Project.ObjectQuickFilterManager">
<last-used-operator value="EQUAL" />
<filters />
</component>
<component name="DBNavigator.Project.Settings">
<connections>
<connection source-id="" id="2a966116-5af8-4991-8ec7-bb1cab412346" active="true" signed="true">
<database>
<name value="Connection" />
<description value="" />
<database-type value="MYSQL" />
<config-type value="BASIC" />
<database-version value="5.7" />
<driver-source value="BUNDLED" />
<driver-library value="" />
<driver value="" />
<url-type value="DATABASE" />
<host value="103.244.88.91" />
<port value="3306" />
<database value="nba" />
<tns-folder value="" />
<tns-profile value="" />
<server-type value="" />
<protocol value="" />
<url-parameters />
<type value="USER_PASSWORD" />
<user value="nba" />
<token-type value="" />
<token-config-file value="" />
<token-profile value="" />
<session-user value="nba@182.101.122.174" />
</database>
<properties>
<auto-commit value="false" />
</properties>
<ssh-settings>
<active value="false" />
<proxy-host value="" />
<proxy-port value="22" />
<proxy-user value="" />
<auth-type value="PASSWORD" />
<key-file value="" />
</ssh-settings>
<ssl-settings>
<active value="false" />
<certificate-authority-file value="" />
<client-certificate-file value="" />
<client-key-file value="" />
</ssl-settings>
<details>
<charset value="UTF-8" />
<session-management value="true" />
<ddl-file-binding value="true" />
<database-logging value="true" />
<connect-automatically value="true" />
<restore-workspace value="true" />
<restore-workspace-deep value="false" />
<environment-type value="default" />
<connectivity-timeout value="30" />
<idle-time-to-disconnect value="30" />
<idle-time-to-disconnect-pool value="5" />
<credential-expiry-time value="10" />
<max-connection-pool-size value="7" />
<alternative-statement-delimiter value="" />
</details>
<debugger>
<compile-dependencies value="true" />
<tcp-driver-tunneling value="false" />
<tcp-host-address value="" />
<tcp-port-from value="4000" />
<tcp-port-to value="4999" />
<debugger-type value="ASK" />
</debugger>
<object-filters hide-empty-schemas="false" hide-pseudo-columns="false" hide-audit-columns="false">
<object-filters />
<object-type-filter use-master-settings="true">
<object-type name="SCHEMA" enabled="true" />
<object-type name="USER" enabled="true" />
<object-type name="ROLE" enabled="true" />
<object-type name="PRIVILEGE" enabled="true" />
<object-type name="CHARSET" enabled="true" />
<object-type name="TABLE" enabled="true" />
<object-type name="VIEW" enabled="true" />
<object-type name="JSON_VIEW" enabled="true" />
<object-type name="MATERIALIZED_VIEW" enabled="true" />
<object-type name="NESTED_TABLE" enabled="true" />
<object-type name="COLUMN" enabled="true" />
<object-type name="INDEX" enabled="true" />
<object-type name="CONSTRAINT" enabled="true" />
<object-type name="DATASET_TRIGGER" enabled="true" />
<object-type name="DATABASE_TRIGGER" enabled="true" />
<object-type name="SYNONYM" enabled="true" />
<object-type name="SEQUENCE" enabled="true" />
<object-type name="PROCEDURE" enabled="true" />
<object-type name="FUNCTION" enabled="true" />
<object-type name="PACKAGE" enabled="true" />
<object-type name="TYPE" enabled="true" />
<object-type name="TYPE_ATTRIBUTE" enabled="true" />
<object-type name="ARGUMENT" enabled="true" />
<object-type name="JAVA_CLASS" enabled="true" />
<object-type name="JAVA_FIELD" enabled="true" />
<object-type name="JAVA_METHOD" enabled="true" />
<object-type name="JAVA_RESOURCE" enabled="true" />
<object-type name="DIMENSION" enabled="true" />
<object-type name="CLUSTER" enabled="true" />
<object-type name="DBLINK" enabled="true" />
<object-type name="CREDENTIAL" enabled="true" />
<object-type name="AI_PROFILE" enabled="true" />
</object-type-filter>
</object-filters>
</connection>
</connections>
<browser-settings>
<general>
<display-mode value="TABBED" />
<navigation-history-size value="100" />
<show-object-details value="false" />
<enable-sticky-paths value="true" />
<enable-quick-filters value="false" />
</general>
<filters>
<object-type-filter>
<object-type name="SCHEMA" enabled="true" />
<object-type name="USER" enabled="true" />
<object-type name="ROLE" enabled="true" />
<object-type name="PRIVILEGE" enabled="true" />
<object-type name="CHARSET" enabled="true" />
<object-type name="TABLE" enabled="true" />
<object-type name="VIEW" enabled="true" />
<object-type name="JSON_VIEW" enabled="true" />
<object-type name="MATERIALIZED_VIEW" enabled="true" />
<object-type name="NESTED_TABLE" enabled="true" />
<object-type name="COLUMN" enabled="true" />
<object-type name="INDEX" enabled="true" />
<object-type name="CONSTRAINT" enabled="true" />
<object-type name="DATASET_TRIGGER" enabled="true" />
<object-type name="DATABASE_TRIGGER" enabled="true" />
<object-type name="SYNONYM" enabled="true" />
<object-type name="SEQUENCE" enabled="true" />
<object-type name="PROCEDURE" enabled="true" />
<object-type name="FUNCTION" enabled="true" />
<object-type name="PACKAGE" enabled="true" />
<object-type name="TYPE" enabled="true" />
<object-type name="TYPE_ATTRIBUTE" enabled="true" />
<object-type name="ARGUMENT" enabled="true" />
<object-type name="JAVA_CLASS" enabled="true" />
<object-type name="JAVA_FIELD" enabled="true" />
<object-type name="JAVA_METHOD" enabled="true" />
<object-type name="JAVA_RESOURCE" enabled="true" />
<object-type name="DIMENSION" enabled="true" />
<object-type name="CLUSTER" enabled="true" />
<object-type name="DBLINK" enabled="true" />
<object-type name="CREDENTIAL" enabled="true" />
<object-type name="AI_PROFILE" enabled="true" />
</object-type-filter>
</filters>
<sorting>
<object-type name="COLUMN" sorting-type="NAME" />
<object-type name="FUNCTION" sorting-type="NAME" />
<object-type name="PROCEDURE" sorting-type="NAME" />
<object-type name="ARGUMENT" sorting-type="POSITION" />
<object-type name="TYPE ATTRIBUTE" sorting-type="POSITION" />
</sorting>
<default-editors>
<object-type name="VIEW" editor-type="SELECTION" />
<object-type name="PACKAGE" editor-type="SELECTION" />
<object-type name="TYPE" editor-type="SELECTION" />
</default-editors>
</browser-settings>
<navigation-settings>
<lookup-filters>
<lookup-objects>
<object-type name="SCHEMA" enabled="true" />
<object-type name="USER" enabled="false" />
<object-type name="ROLE" enabled="false" />
<object-type name="PRIVILEGE" enabled="false" />
<object-type name="CHARSET" enabled="false" />
<object-type name="TABLE" enabled="true" />
<object-type name="VIEW" enabled="true" />
<object-type name="JSON VIEW" enabled="true" />
<object-type name="MATERIALIZED VIEW" enabled="true" />
<object-type name="INDEX" enabled="true" />
<object-type name="CONSTRAINT" enabled="true" />
<object-type name="DATASET TRIGGER" enabled="true" />
<object-type name="DATABASE TRIGGER" enabled="true" />
<object-type name="SYNONYM" enabled="false" />
<object-type name="SEQUENCE" enabled="true" />
<object-type name="PROCEDURE" enabled="true" />
<object-type name="FUNCTION" enabled="true" />
<object-type name="PACKAGE" enabled="true" />
<object-type name="TYPE" enabled="true" />
<object-type name="JAVA CLASS" enabled="true" />
<object-type name="INNER CLASS" enabled="true" />
<object-type name="JAVA FIELD" enabled="true" />
<object-type name="JAVA METHOD" enabled="true" />
<object-type name="JAVA PARAMETER" enabled="true" />
<object-type name="JAVA RESOURCE" enabled="true" />
<object-type name="DIMENSION" enabled="false" />
<object-type name="CLUSTER" enabled="false" />
<object-type name="DBLINK" enabled="false" />
<object-type name="CREDENTIAL" enabled="false" />
</lookup-objects>
<force-database-load value="false" />
<prompt-connection-selection value="true" />
<prompt-schema-selection value="true" />
</lookup-filters>
</navigation-settings>
<dataset-grid-settings>
<general>
<enable-zooming value="true" />
<enable-column-tooltip value="true" />
</general>
<sorting>
<nulls-first value="true" />
<max-sorting-columns value="4" />
</sorting>
<audit-columns>
<column-names value="" />
<visible value="true" />
<editable value="false" />
</audit-columns>
</dataset-grid-settings>
<dataset-editor-settings>
<text-editor-popup>
<active value="false" />
<active-if-empty value="false" />
<data-length-threshold value="100" />
<popup-delay value="1000" />
</text-editor-popup>
<values-actions-popup>
<show-popup-button value="true" />
<element-count-threshold value="1000" />
<data-length-threshold value="250" />
</values-actions-popup>
<general>
<fetch-block-size value="100" />
<fetch-timeout value="30" />
<trim-whitespaces value="true" />
<convert-empty-strings-to-null value="true" />
<select-content-on-cell-edit value="true" />
<large-value-preview-active value="true" />
</general>
<filters>
<prompt-filter-dialog value="true" />
<default-filter-type value="BASIC" />
</filters>
<qualified-text-editor text-length-threshold="300">
<content-types>
<content-type name="Text" enabled="true" />
<content-type name="Properties" enabled="true" />
<content-type name="XML" enabled="true" />
<content-type name="DTD" enabled="true" />
<content-type name="HTML" enabled="true" />
<content-type name="XHTML" enabled="true" />
<content-type name="Java" enabled="true" />
<content-type name="SQL" enabled="true" />
<content-type name="PL/SQL" enabled="true" />
<content-type name="JSON" enabled="true" />
<content-type name="JSON5" enabled="true" />
<content-type name="Groovy" enabled="true" />
<content-type name="YAML" enabled="true" />
<content-type name="Manifest" enabled="true" />
</content-types>
</qualified-text-editor>
<record-navigation>
<navigation-target value="VIEWER" />
</record-navigation>
</dataset-editor-settings>
<code-editor-settings>
<general>
<show-object-navigation-gutter value="false" />
<show-spec-declaration-navigation-gutter value="true" />
<enable-spellchecking value="true" />
<enable-reference-spellchecking value="false" />
</general>
<confirmations>
<save-changes value="false" />
<revert-changes value="true" />
<exit-on-changes value="ASK" />
</confirmations>
</code-editor-settings>
<code-completion-settings>
<filters>
<basic-filter>
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
<filter-element type="RESERVED_WORD" id="function" selected="true" />
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
<filter-element type="OBJECT" id="schema" selected="true" />
<filter-element type="OBJECT" id="role" selected="true" />
<filter-element type="OBJECT" id="user" selected="true" />
<filter-element type="OBJECT" id="privilege" selected="true" />
<user-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="json view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="false" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</user-schema>
<public-schema>
<filter-element type="OBJECT" id="table" selected="false" />
<filter-element type="OBJECT" id="view" selected="false" />
<filter-element type="OBJECT" id="json view" selected="false" />
<filter-element type="OBJECT" id="materialized view" selected="false" />
<filter-element type="OBJECT" id="index" selected="false" />
<filter-element type="OBJECT" id="constraint" selected="false" />
<filter-element type="OBJECT" id="trigger" selected="false" />
<filter-element type="OBJECT" id="synonym" selected="false" />
<filter-element type="OBJECT" id="sequence" selected="false" />
<filter-element type="OBJECT" id="procedure" selected="false" />
<filter-element type="OBJECT" id="function" selected="false" />
<filter-element type="OBJECT" id="package" selected="false" />
<filter-element type="OBJECT" id="type" selected="false" />
<filter-element type="OBJECT" id="dimension" selected="false" />
<filter-element type="OBJECT" id="cluster" selected="false" />
<filter-element type="OBJECT" id="dblink" selected="false" />
</public-schema>
<any-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="json view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</any-schema>
</basic-filter>
<extended-filter>
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
<filter-element type="RESERVED_WORD" id="function" selected="true" />
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
<filter-element type="OBJECT" id="schema" selected="true" />
<filter-element type="OBJECT" id="user" selected="true" />
<filter-element type="OBJECT" id="role" selected="true" />
<filter-element type="OBJECT" id="privilege" selected="true" />
<user-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="json view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</user-schema>
<public-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="json view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</public-schema>
<any-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="json view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</any-schema>
</extended-filter>
</filters>
<sorting enabled="true">
<sorting-element type="RESERVED_WORD" id="keyword" />
<sorting-element type="RESERVED_WORD" id="datatype" />
<sorting-element type="OBJECT" id="column" />
<sorting-element type="OBJECT" id="table" />
<sorting-element type="OBJECT" id="view" />
<sorting-element type="OBJECT" id="json view" />
<sorting-element type="OBJECT" id="materialized view" />
<sorting-element type="OBJECT" id="index" />
<sorting-element type="OBJECT" id="constraint" />
<sorting-element type="OBJECT" id="trigger" />
<sorting-element type="OBJECT" id="synonym" />
<sorting-element type="OBJECT" id="sequence" />
<sorting-element type="OBJECT" id="procedure" />
<sorting-element type="OBJECT" id="function" />
<sorting-element type="OBJECT" id="package" />
<sorting-element type="OBJECT" id="type" />
<sorting-element type="OBJECT" id="dimension" />
<sorting-element type="OBJECT" id="cluster" />
<sorting-element type="OBJECT" id="dblink" />
<sorting-element type="OBJECT" id="schema" />
<sorting-element type="OBJECT" id="role" />
<sorting-element type="OBJECT" id="user" />
<sorting-element type="RESERVED_WORD" id="function" />
<sorting-element type="RESERVED_WORD" id="parameter" />
</sorting>
<format>
<enforce-code-style-case value="true" />
</format>
</code-completion-settings>
<execution-engine-settings>
<statement-execution>
<fetch-block-size value="100" />
<execution-timeout value="20" />
<debug-execution-timeout value="600" />
<focus-result value="false" />
<prompt-execution value="false" />
</statement-execution>
<script-execution>
<command-line-interfaces />
<execution-timeout value="300" />
</script-execution>
<method-execution>
<execution-timeout value="30" />
<debug-execution-timeout value="600" />
<parameter-history-size value="10" />
</method-execution>
</execution-engine-settings>
<operation-settings>
<transactions>
<uncommitted-changes>
<on-project-close value="ASK" />
<on-disconnect value="ASK" />
<on-autocommit-toggle value="ASK" />
</uncommitted-changes>
<multiple-uncommitted-changes>
<on-commit value="ASK" />
<on-rollback value="ASK" />
</multiple-uncommitted-changes>
</transactions>
<session-browser>
<disconnect-session value="ASK" />
<kill-session value="ASK" />
<reload-on-filter-change value="false" />
</session-browser>
<compiler>
<compile-type value="KEEP" />
<compile-dependencies value="ASK" />
<always-show-controls value="false" />
</compiler>
</operation-settings>
<ddl-file-settings>
<extensions>
<mapping file-type-id="VIEW" extensions="vw" />
<mapping file-type-id="TRIGGER" extensions="trg" />
<mapping file-type-id="PROCEDURE" extensions="prc" />
<mapping file-type-id="FUNCTION" extensions="fnc" />
<mapping file-type-id="PACKAGE" extensions="pkg" />
<mapping file-type-id="PACKAGE_SPEC" extensions="pks" />
<mapping file-type-id="PACKAGE_BODY" extensions="pkb" />
<mapping file-type-id="TYPE" extensions="tpe" />
<mapping file-type-id="TYPE_SPEC" extensions="tps" />
<mapping file-type-id="TYPE_BODY" extensions="tpb" />
<mapping file-type-id="JAVA_SOURCE" extensions="sql" />
</extensions>
<general>
<lookup-ddl-files value="true" />
<create-ddl-files value="false" />
<synchronize-ddl-files value="true" />
<use-qualified-names value="false" />
<make-scripts-rerunnable value="true" />
</general>
</ddl-file-settings>
<assistant-settings>
<credential-settings>
<credentials />
</credential-settings>
</assistant-settings>
<general-settings>
<regional-settings>
<date-format value="MEDIUM" />
<number-format value="UNGROUPED" />
<locale value="zh" />
<use-custom-formats value="false" />
</regional-settings>
<environment>
<environment-types>
<environment-type id="development" name="Development" description="Development environment" color="-2430209/-12296320" readonly-code="false" readonly-data="false" />
<environment-type id="integration" name="Integration" description="Integration environment" color="-2621494/-12163514" readonly-code="true" readonly-data="false" />
<environment-type id="production" name="Production" description="Productive environment" color="-11574/-10271420" readonly-code="true" readonly-data="true" />
<environment-type id="other" name="Other" description="" color="-1576/-10724543" readonly-code="false" readonly-data="false" />
</environment-types>
<visibility-settings>
<connection-tabs value="true" />
<dialog-headers value="true" />
<object-editor-tabs value="true" />
<script-editor-tabs value="false" />
<execution-result-tabs value="true" />
</visibility-settings>
</environment>
</general-settings>
</component>
</project>

2
.idea/misc.xml generated
View File

@@ -8,7 +8,7 @@
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -37,19 +37,18 @@
<option name="fileName" value="${domain.fileName}Mapper" />
<option name="fileNameWithSuffix" value="${domain.fileName}Mapper.xml" />
<option name="modulePath" value="$PROJECT_DIR$" />
<option name="packageName" value="mapper" />
<option name="packageName" value="${domain.basePackage}.mapper" />
</ModuleInfoGo>
</list>
</option>
<option name="needsComment" value="true" />
<option name="needsModel" value="true" />
<option name="relativePackage" value="pojo" />
<option name="superClass" value="" />
<option name="tableUIInfoList">
<list>
<TableUIInfo>
<option name="className" value="Games" />
<option name="tableName" value="games" />
<option name="className" value="LoginLog" />
<option name="tableName" value="login_log" />
</TableUIInfo>
</list>
</option>

43
README.md Normal file
View File

@@ -0,0 +1,43 @@
# NBA赛事共享在线观看平台
## 项目简介
本项目是一个基于 NBA 赛事的共享在线观看平台,支持实时展示赛程、赛事直播播放等功能。用户可以便捷地浏览最新赛程信息,并使用内嵌播放器观看赛事直播。平台通过整合腾讯体育资源,自动化采集与维护直播数据,实现赛事数据的高效管理与分享。
## 技术栈
### 后端技术
- **Spring Boot**:构建高性能的后端服务。
- **MyBatis**:简化数据库操作,灵活执行 SQL。
- **MySQL**:存储赛程、直播间等核心数据。
- **Redis**:缓存腾讯体育 Cookie 及直播数据,提升请求效率。
- **Scheduled Task**:定时任务机制,周期性同步直播信息。
### 前端技术
- **Vue 3**:构建响应式单页应用。
- **Pinia**:集中式状态管理,存储赛程相关数据。
- **Vue Router**:实现页面间路由导航。
- **Axios**:与后端进行 HTTP 通信。
- **DPlayer**:集成播放器,用于播放赛事直播流。
## 核心功能实现
- **Cookie自动更新与存储**:后端首次手动配置腾讯体育 Cookie 并保存至 Redis通过定时任务定期刷新 Cookie保证接口调用的稳定性。
- **直播数据采集**:根据数据库中的赛程 ID携带 Cookie 向腾讯体育接口发起请求,抓取对应的直播间信息,并定期存储至直播间信息表。
- **前端展示与播放**
- 使用 Axios 获取后端赛程数据,展示赛程信息。
- 用户点击感兴趣的赛程后,前端将相关 ID 保存到 Pinia 中,用于后续状态追踪与页面展示。
- 基于 DPlayer 实现直播流的嵌入播放体验。
## 项目亮点
- 实现与第三方平台(腾讯体育)无官方 API 接口的半自动数据采集与更新。
- 利用 Redis 缓存与定时刷新机制,提升系统稳定性与数据实时性。
- 前后端分离架构,增强了系统的可维护性和拓展性。
- 播放器集成体验良好,用户可便捷观看赛事直播。
## 后续优化方向
- 接入用户评论与弹幕系统,提升用户互动性。
- 增加赛事订阅与提醒功能,优化用户体验。
- 提升异常处理与容错机制,增强系统健壮性。

17
pom.xml
View File

@@ -68,6 +68,23 @@
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<exclusions>
<exclusion>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>

View File

@@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
public class GlobalException {
@ExceptionHandler(Exception.class)
public Object handleException(Exception e) {
//e.printStackTrace();
e.printStackTrace();
return e.getMessage();
}

View File

@@ -0,0 +1,33 @@
package com.ping.study.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConf {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
// key
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
// value
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
return template;
}
}

View File

@@ -2,18 +2,37 @@ package com.ping.study.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvc implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://jrs77.xyz","https://jrs77.xyz",
"http://localhost:5173", "https://nba.1024x.icu",
"http://nba.1024x.icu","http://167.253.156.235:9006",
"http://116.62.173.2:9001")
.allowedMethods("GET","POST","PUT","DELETE","OPTIONS")
.allowedHeaders("X-Timestamp","X-Sign","*")
.allowCredentials(true) // 若不用 Cookie 也可为 false
.maxAge(3600);
}
// 原有的 NBA WebClient
@Bean("nbaWebClient")
public WebClient nbaWebClient() {
return WebClient.create("https://api.nba.cn/sib/v2");
}
@Bean("matchWebClient")
public WebClient txWebClient() {
return WebClient.create("https://matchweb.sports.qq.com");
}
// 新增的腾讯视频 WebClient
@Bean("tencentWebClient")

View File

@@ -5,26 +5,37 @@ package com.ping.study.controller;
import com.ping.study.model.dto.addUrls;
import com.ping.study.model.vo.live.LiveUrl;
import com.ping.study.pojo.Games;
import com.ping.study.pojo.NbaUser;
import com.ping.study.service.GamesService;
import com.ping.study.service.UrlsService;
import com.ping.study.utils.NbaApi;
import com.ping.study.utils.UserUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
@RequestMapping("/api")
@RestController
@Data
@Slf4j
//@CrossOrigin(origins = "http://nba.new9.me")
@CrossOrigin
//@CrossOrigin(origins = {"http://nba.1024x.icu/","https://nba.1024x.icu/","http://localhost:5173/"})
public class NbaController {
@Autowired
@@ -33,26 +44,59 @@ public class NbaController {
private GamesService gamesService;
@Autowired
private UrlsService urlsService;
@Autowired
private UserUtil userUtil;
@Autowired
private RedisTemplate<String, String> stringRedisTemplate;
//添加定时任务每日凌晨0点执行一次
// 每天 00:00:00 执行
@Scheduled(cron = "0 0 0 * * ?")
@RequestMapping("/add")
public List<Games> addGames() {
public List<Games> addGames() throws InterruptedException {
//先删除数据库中所有赛程
log.info("执行定时方法删除数据库中所有赛程");
gamesService.deleteAllGames();
Thread.sleep(100);
//再删除数据库中所有直播链接
log.info("执行定时方法删除数据库中所有直播链接");
urlsService.deleteAllUrls();
Thread.sleep(100);
log.info("执行定时方法添加当天赛程");
return nbaApi.addGames();
List<Games> gamesList = new ArrayList<>();
try {
gamesList = nbaApi.addGames();
if (gamesList.isEmpty()){
stringRedisTemplate.opsForValue().set("games","0");
}
stringRedisTemplate.delete("live:urls:all");
log.info("添加赛程成功:{}",gamesList);
}catch (Exception e){
stringRedisTemplate.opsForValue().set("games","0");
log.info("添加赛程失败:{}",e.getMessage());
}
return gamesList;
}
@Scheduled(cron = "0 0 0 * * ?")
@RequestMapping("/updateLive")
public void updateLive() {
log.info("执行定时方法更新当天赛程直播链接");
@Scheduled(cron = "0 0/10 0-1 * * ?")
public void updateGames(){
String games_status = stringRedisTemplate.opsForValue().get("games");
List<Games> gamesList = new ArrayList<>();
if (Objects.equals(games_status, "0")){
try {
gamesList = nbaApi.addGames();
if (gamesList.isEmpty()){
stringRedisTemplate.opsForValue().set("games","0");
return;
}
stringRedisTemplate.opsForValue().set("games","1");
stringRedisTemplate.delete("live:urls:all");
}catch (Exception e){
stringRedisTemplate.opsForValue().set("games","0");
log.info("添加赛程失败:{}",e.getMessage());
}
}
log.info("=======跳过更新数据库赛程========");
}
@RequestMapping("/games")
public List<Games> getGames() {
@@ -60,12 +104,85 @@ public class NbaController {
return gamesService.getGames();
}
@RequestMapping("/urls")
public List<HashMap<String, List<LiveUrl>>> getUrls() {
log.info("获取所有赛程直播链接");
return urlsService.getUrls();
// @RequestMapping("/urls")
// public List<HashMap<String, List<LiveUrl>>> getUrls() {
// log.info("获取所有赛程直播链接");
// return urlsService.getUrls();
// }
//根据比赛id和直播流类型获取流
@GetMapping("/live/url")
public ResponseEntity<?> getPlayUrl(
@RequestParam("gameId") String gameId,
@RequestParam("type") String playType,
@RequestHeader(value = "X-Timestamp", required = false) String timestamp,
@RequestHeader(value = "X-Sign", required = false) String sign,
HttpSession session,
HttpServletRequest request
) throws Exception {
String secretKey = "20251125";
if (timestamp == null || sign == null) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Missing headers");
}
// 超时校验5分钟内有效
long ts = Long.parseLong(timestamp);
if (Math.abs(System.currentTimeMillis() - ts) > 5 * 60 * 1000) {
// log.info("Request expired: " + timestamp);
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Request expired");
}
// log.info("Request timestamp: "+timestamp +", sign: " + sign);
String expected = DigestUtils.md5DigestAsHex((timestamp + secretKey).getBytes(StandardCharsets.UTF_8));
if (!expected.equalsIgnoreCase(sign)) {
// log.info("Invalid sign: " + sign + ", expected: " + expected);
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Invalid sign");
}
NbaUser loginUser = (NbaUser) session.getAttribute("loginUser");
if (loginUser == null){
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("请先登录");
}
log.info("{}:获取直播链接开始",loginUser.getEmail());
String ip = userUtil.getClientIp(request);
String url = urlsService.findUrlByGameIdAndType(gameId, playType);
// log.info("来自ip:{} 用户:{}:获取直播链接成功:{}",ip,loginUser.getEmail(),url);
return ResponseEntity.ok(url);
}
@GetMapping(value = "/urls")
public ResponseEntity<?> getUrls(
@RequestHeader(value = "X-Timestamp", required = false) String timestamp,
@RequestHeader(value = "X-Sign", required = false) String sign,
@RequestParam(value = "includeM3u8", required = false, defaultValue = "0") String includeM3u8
) {
String secretKey = "20251125";
if (timestamp == null || sign == null) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Missing headers");
}
// 超时校验5分钟内有效
long ts = Long.parseLong(timestamp);
if (Math.abs(System.currentTimeMillis() - ts) > 5 * 60 * 1000) {
// log.info("Request expired: " + timestamp);
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Request expired");
}
//log.info("Request timestamp: "+timestamp +", sign: " + sign);
String expected = DigestUtils.md5DigestAsHex((timestamp + secretKey).getBytes(StandardCharsets.UTF_8));
if (!expected.equalsIgnoreCase(sign)) {
// log.info("Invalid sign: " + sign + ", expected: " + expected);
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Invalid sign");
}
log.info("================"+String.valueOf(includeM3u8));
List<HashMap<String, List<LiveUrl>>> data = urlsService.getUrls(includeM3u8);
return ResponseEntity.ok(data);
}
@RequestMapping("/go")
public Boolean go(@RequestParam("pwd") String pwd) {
return pwd.equals("inspur123");
@@ -81,4 +198,13 @@ public class NbaController {
public void deleteUrlById(@PathVariable("id") Integer id){
urlsService.deleteUrlById(id);
}
//修改直播链接根据id
@RequestMapping("/update")
//使用hashmap接收参数
public void updateUrlById(@RequestBody HashMap<String, String> liveUrl){
urlsService.updateUrlById(liveUrl);
}
// public void updateUrlById(@RequestBody LiveUrl liveUrl){
// urlsService.updateUrlById(liveUrl);
// }
}

View File

@@ -1,15 +1,16 @@
package com.ping.study.controller.tx;
import com.ping.study.service.UrlsService;
import com.ping.study.service.tx.LiveInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/tx/nba")
@Slf4j
public class LiveInfoController {
private final LiveInfoService liveInfoService;
@@ -19,15 +20,17 @@ public class LiveInfoController {
}
@GetMapping("/live/{cnlid}")
public String getLiveInfo(@PathVariable String cnlid) {
public String getLiveInfo(@PathVariable String cnlid) throws Exception {
log.info("执行查询直播id: {}", cnlid);
return liveInfoService.getLiveInfo(cnlid);
}
//定时执行更新直播链接
//定时任务 从北京时间凌晨到12点每过半个小时执行一次
@Scheduled(cron = "0 0/30 0-11 * * ?")
//定时任务 从北京时间凌晨到12:00点,每过5分钟执行一次
@Scheduled(cron = "0 0/10 0-12 * * ?")
@RequestMapping("/live/refresh")
public String refreshLiveInfo() {
public String refreshLiveInfo() throws Exception {
log.info("=========开始执行更新直播链接=========");
return liveInfoService.refreshLiveInfo();
}
}

View File

@@ -1,8 +1,10 @@
package com.ping.study.controller.tx;
import com.ping.study.model.dto.tx.MatchListRequest;
import com.ping.study.model.vo.live.LiveIds;
import com.ping.study.service.tx.MatchService;
import com.ping.study.service.tx.SportsQqService;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@@ -23,22 +25,25 @@ public class MatchController {
this.matchService = matchService;
}
@GetMapping("/matches")
public Mono<String> getMatches(
@RequestParam(required = false, defaultValue = "100000") Integer columnId,
@RequestParam String startTime,
@RequestParam String endTime) {
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime) {
MatchListRequest request = new MatchListRequest();
request.setColumnId(columnId);
request.setStartTime(LocalDate.parse(startTime));
request.setEndTime(LocalDate.parse(endTime));
LocalDate currentDate = LocalDate.now();
request.setStartTime(startTime != null ? LocalDate.parse(startTime) : currentDate);
request.setEndTime(endTime != null ? LocalDate.parse(endTime) : currentDate);
return sportsQqService.getMatchList(request);
}
@GetMapping("/lives")
public List<String> getPlayoffLiveIds(
public List<LiveIds> getPlayoffLiveIds(
@RequestParam String startTime,
@RequestParam String endTime) {

View File

@@ -14,8 +14,6 @@ public class TokenRefreshController {
@Autowired
private TxSportTokenRefreshService txSportTokenRefreshService;
@RequestMapping("/cookie/refresh")
public Boolean refreshToken()
{

View File

@@ -0,0 +1,168 @@
package com.ping.study.controller.user;
import com.ping.study.model.dto.DtoUpdateUser;
import com.ping.study.model.dto.registerUserDto;
import com.ping.study.pojo.NbaUser;
import com.ping.study.service.NbaUserService;
import com.ping.study.utils.MailUtil;
import com.ping.study.utils.UserUtil;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.web.bind.annotation.*;
import java.util.UUID;
@RestController
@CrossOrigin(
origins = {"http://nba.1024x.icu", "https://nba.1024x.icu","http://jrs77.xyz","https://jrs77.xyz"},
allowCredentials = "true"
)
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private MailUtil mailUtil;
@Autowired
private NbaUserService nbaUserService;
@Autowired
private UserUtil userUtil;
//发送验证码
@RequestMapping("/send")
public String send(String email,Integer type) {
switch (type){
case 1://注册用户
if (nbaUserService.findByEmail(email) != null){
return "邮箱已存在";
}
break;
case 2://修改密码
if (nbaUserService.findByEmail(email) == null){
return "邮箱不存在";
}
break;
}
return mailUtil.sendVerificationCode(email);
// return "发送成功";
}
//注册
@PostMapping("/register")
public String register(@RequestBody registerUserDto registerUserDto, HttpServletRequest request){
String ip = userUtil.getClientIp(request);
registerUserDto.setRegisterIp(ip);
return nbaUserService.register(registerUserDto);
}
// 登录 + rememberToken 功能
@PostMapping("/login")
public String login(
@RequestParam String email,
@RequestParam String password,
HttpSession session,
HttpServletRequest request,
HttpServletResponse response
) {
String ip = userUtil.getClientIp(request);
NbaUser user = nbaUserService.login(email, password,ip);
if (user != null){
// session 存 7 天
session.setMaxInactiveInterval(1 * 24 * 60 * 60);
session.setAttribute("loginUser", user);
// 生成 rememberToken
String token = UUID.randomUUID().toString();
nbaUserService.saveRememberToken(Long.valueOf(user.getId()), token);
ResponseCookie rememberCookie = ResponseCookie
.from("rememberToken", token)
.httpOnly(true)
.secure(true) // 生产 https 环境一定要 true
.path("/")
.sameSite("None")
.maxAge(1 * 24 * 60 * 60)
.build();
response.addHeader(HttpHeaders.SET_COOKIE, rememberCookie.toString());
// Cookie cookie = new Cookie("rememberToken", token);
// cookie.setPath("/");
// cookie.setMaxAge(7 * 24 * 60 * 60); // 7 天
// cookie.setHttpOnly(true); // JS 不能读
// // 如果你用 https请启用cookie.setSecure(true);
// response.addCookie(cookie);
log.info("用户 {} 登录成功(记住我 token={}", user.getEmail(), token);
return "登录成功";
}
return "登录失败";
}
@PostMapping("/update")
public String updateUser(@RequestBody DtoUpdateUser dtoUpdateUser){
return nbaUserService.updateUser(dtoUpdateUser);
}
// 返回当前登录用户(支持自动登录)
@GetMapping("/me")
public Object me(
HttpSession session,
@CookieValue(value = "rememberToken", required = false) String rememberToken
) {
// 1. session 已登录
NbaUser loginUser = (NbaUser) session.getAttribute("loginUser");
if (loginUser != null) {
return loginUser.getEmail();
}
// 2. 尝试自动登录(记住我)
if (rememberToken != null && !rememberToken.isEmpty()) {
NbaUser user = nbaUserService.findByRememberToken(rememberToken);
if (user != null) {
// 自动恢复 session
session.setMaxInactiveInterval(1 * 24 * 60 * 60);
session.setAttribute("loginUser", user);
log.info("自动登录成功 -> {}", user.getEmail());
return user.getEmail();
}
}
return "未登录";
}
// 退出登录(清 session + 清 token + 清 cookie
@PostMapping("/logout")
public String logout(
HttpSession session,
HttpServletResponse response,
@CookieValue(value = "rememberToken", required = false) String rememberToken
) {
session.invalidate();
// 清数据库 token
if (rememberToken != null) {
nbaUserService.deleteRememberToken(rememberToken);
}
// 清 cookie
Cookie cookie = new Cookie("rememberToken", "");
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
return "退出成功";
}
}

View File

@@ -0,0 +1,25 @@
package com.ping.study.mapper;
import com.ping.study.pojo.LoginLog;
/**
* @author Administrator
* @description 针对表【login_log】的数据库操作Mapper
* @createDate 2025-12-22 13:10:39
* @Entity com.ping.study.pojo.LoginLog
*/
public interface LoginLogMapper {
int deleteByPrimaryKey(Long id);
int insert(LoginLog record);
int insertSelective(LoginLog record);
LoginLog selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(LoginLog record);
int updateByPrimaryKey(LoginLog record);
}

View File

@@ -0,0 +1,38 @@
package com.ping.study.mapper;
import com.ping.study.pojo.NbaUser;
/**
* @author Administrator
* @description 针对表【nba_user】的数据库操作Mapper
* @createDate 2025-11-19 22:54:55
* @Entity com.ping.study.pojo.NbaUser
*/
public interface NbaUserMapper {
int deleteByPrimaryKey(Long id);
int insert(NbaUser record);
int insertSelective(NbaUser record);
NbaUser selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(NbaUser record);
int updateByPrimaryKey(NbaUser record);
NbaUser login(String email, String password);
void updateRememberToken(Long userId, String token);
NbaUser selectByRememberToken(String token);
void clearRememberToken(String token);
NbaUser findByEmail(String email);
NbaUser selectByEmail(String email);
void updatePasswordByEmail(String email, String password);
}

View File

@@ -26,9 +26,16 @@ public interface UrlsMapper {
int updateByPrimaryKeySelective(Urls record);
int updateByPrimaryKey(Urls record);
List<String> selectGameIds();
List<LiveUrl> selectUrlsListByGameId(String gameId);
List<LiveUrl> selectUrlsListByGameId(
@Param("gameId") String gameId
);
List<LiveUrl> selectUrlsListByAdmin(
@Param("gameId") String gameId
);
void insertUrlsWithGameId(@Param("gameId") String gameId, @Param("list") List<LiveUrl> urls);
@@ -38,5 +45,12 @@ public interface UrlsMapper {
void deleteAllUrls();
//更新比赛直播链接
void updateUrlsWithGameId(String gameId, String s);
void updateUrlsWithGameId(String gameId, String s,String type);
String selectUrlsListByGameIdAndType(String gameId, String type);
//更新比赛状态
void updateStatusWithGameId(String gameId, String status);
void updateUrlsById(String id, String url);
}

View File

@@ -0,0 +1,11 @@
package com.ping.study.model.dto;
import lombok.Data;
@Data
public class DtoUpdateUser {
private String email;
private String password;
private String code;
}

View File

@@ -0,0 +1,20 @@
package com.ping.study.model.dto;
import lombok.Data;
import java.util.Date;
@Data
public class registerUserDto {
// private Integer id;
private String username;
private String password;
private String email;
private String registerIp;
private String code;
}

View File

@@ -0,0 +1,59 @@
package com.ping.study.model.dto.tx;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Builder;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TxMatchRequest {
/**
* 当前日期 yyyy-MM-dd默认当前日期
*/
@Builder.Default
private String today = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
/**
* 查询开始日期 yyyy-MM-dd默认当前日期
*/
@Builder.Default
private String startTime = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
/**
* 查询结束日期 yyyy-MM-dd默认当前日期
*/
@Builder.Default
private String endTime = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
/**
* 栏目 ID
*/
private Integer columnId=100000;
/**
* 页码或索引
*/
private Integer index =1;
/**
* 是否初始化
*/
private Boolean isInit= true;
/**
* 时间戳(默认:当前系统时间毫秒)
*/
@Builder.Default
private Long timestamp = System.currentTimeMillis();
/**
* 回调函数名
*/
private String callback = "fetchScheduleListCallback100000";
}

View File

@@ -11,4 +11,5 @@ public class Game {
private String awayTeamLogoDark;
private String startDate; // 格式:"2025-04-17"
private String startTime; // 格式:"07:30:00"
private String status;//比赛状态
}

View File

@@ -0,0 +1,11 @@
package com.ping.study.model.vo.live;
import lombok.Data;
@Data
public class LiveIds {
private String mId;
private String playId;
}

View File

@@ -12,10 +12,9 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
public class LiveUrl {
// private Integer gameId;
// private Integer gameId;
private Integer id;
private String type;
private String url;
}

View File

@@ -5,7 +5,9 @@ import lombok.Data;
@Data
public class MatchInfo {
private String mId;
private String liveId;
private String matchType;
private String competitionId;
// 其他需要的字段...
}

View File

@@ -56,4 +56,7 @@ public class Games implements Serializable {
private String awayTeamLogoDark;
private static final long serialVersionUID = 1L;
//比赛状态
private String status;
}

View File

@@ -0,0 +1,18 @@
package com.ping.study.pojo;
import java.util.Date;
import lombok.Data;
/**
* @TableName login_log
*/
@Data
public class LoginLog {
private Integer id;
private Integer userId;
private Date loginTime;
private String loginIp;
}

View File

@@ -0,0 +1,28 @@
package com.ping.study.pojo;
import java.util.Date;
import lombok.Data;
/**
* @TableName nba_user
*/
@Data
public class NbaUser {
private Integer id;
private String username;
private String password;
private String email;
private Date createTime;
private Date updateTime;
private Date lastLoginTime;
private String lastLoginIp;
private String registerIp;
}

View File

@@ -27,4 +27,6 @@ public class Urls {
*
*/
private String type;
private Integer status;
}

View File

@@ -0,0 +1,27 @@
package com.ping.study.service;
import com.ping.study.model.dto.DtoUpdateUser;
import com.ping.study.model.dto.registerUserDto;
import com.ping.study.pojo.NbaUser;
/**
* @author Administrator
* @description 针对表【nba_user】的数据库操作Service
* @createDate 2025-11-19 22:52:24
*/
public interface NbaUserService{
String register(registerUserDto registerUserDto);
NbaUser login(String email, String password,String ip);
void saveRememberToken(Long userId, String token);
NbaUser findByRememberToken(String token);
void deleteRememberToken(String token);
String updateUser(DtoUpdateUser dtoUpdateUser);
NbaUser findByEmail(String email);
}

View File

@@ -11,7 +11,7 @@ public interface UrlsService {
//查询当天的直播地址
public List<HashMap<String, List<LiveUrl>>> getUrls();
public List<HashMap<String, List<LiveUrl>>> getUrls(String includeM3u8);
//添加直播地址到对应赛事
public void addUrls(addUrls addUrls);
@@ -19,4 +19,8 @@ public interface UrlsService {
public void deleteUrlById(Integer id);
void deleteAllUrls();
String findUrlByGameIdAndType(String gameId, String type);
void updateUrlById(HashMap<String, String> liveUrl);
}

View File

@@ -0,0 +1,112 @@
package com.ping.study.service.impl;
import com.ping.study.mapper.LoginLogMapper;
import com.ping.study.mapper.NbaUserMapper;
import com.ping.study.model.dto.DtoUpdateUser;
import com.ping.study.model.dto.registerUserDto;
import com.ping.study.pojo.LoginLog;
import com.ping.study.pojo.NbaUser;
import com.ping.study.service.NbaUserService;
import com.ping.study.utils.UserUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
@Service
@Slf4j
public class NbaUserServiceImpl implements NbaUserService {
@Autowired
private RedisTemplate<String, String> stringRedisTemplate;
@Autowired
private NbaUserMapper nbaUserMapper;
@Autowired
private LoginLogMapper loginLogMapper;
@Override
public String register(registerUserDto registerUserDto) {
String s = stringRedisTemplate.opsForValue().get(registerUserDto.getEmail());
if (s != null && s.equals(registerUserDto.getCode())) {
if (nbaUserMapper.findByEmail(registerUserDto.getEmail()) != null){
return "邮箱已存在";
}
NbaUser nbaUser = new NbaUser();
nbaUser.setUsername(registerUserDto.getUsername());
nbaUser.setPassword(registerUserDto.getPassword());
nbaUser.setEmail(registerUserDto.getEmail());
nbaUser.setCreateTime(new Date());
nbaUser.setUpdateTime(new Date());
nbaUser.setLastLoginTime(new Date());
nbaUser.setLastLoginIp("");
nbaUser.setRegisterIp(registerUserDto.getRegisterIp());
stringRedisTemplate.delete(registerUserDto.getEmail());
nbaUserMapper.insert(nbaUser);
return "注册成功";
}
else {
//throw new RuntimeException("验证码错误");
return "验证码错误";
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public NbaUser login(String email, String password, String ip) {
NbaUser loginUser = nbaUserMapper.login(email, password);
if (loginUser!=null){
LoginLog loginLog = new LoginLog();
loginLog.setUserId(loginUser.getId());
loginLog.setLoginTime(new Date());
loginLog.setLoginIp(ip);
loginLogMapper.insert(loginLog);
loginUser.setLastLoginTime(new Date());
nbaUserMapper.updateByPrimaryKeySelective(loginUser);
return loginUser;
}
else return null;
//return loginUser;
}
@Override
public void saveRememberToken(Long userId, String token) {
// update nba_user set remember_token = ? where id = ?
nbaUserMapper.updateRememberToken(userId, token);
}
@Override
public NbaUser findByRememberToken(String token) {
return nbaUserMapper.selectByRememberToken(token);
}
@Override
public void deleteRememberToken(String token) {
nbaUserMapper.clearRememberToken(token);
}
@Override
public String updateUser(DtoUpdateUser dtoUpdateUser) {
NbaUser nbaUser = nbaUserMapper.selectByEmail(dtoUpdateUser.getEmail());
if (nbaUser != null){
String s = stringRedisTemplate.opsForValue().get(dtoUpdateUser.getEmail());
if (s != null && s.equals(dtoUpdateUser.getCode())){
nbaUserMapper.updatePasswordByEmail(dtoUpdateUser.getEmail(), dtoUpdateUser.getPassword());
stringRedisTemplate.delete(dtoUpdateUser.getEmail());
return "更新成功";
}
return "验证码错误";
}
return "当前邮箱无账号";
}
@Override
public NbaUser findByEmail(String email) {
return nbaUserMapper.findByEmail(email);
}
}

View File

@@ -5,39 +5,85 @@ import com.ping.study.model.dto.addUrls;
import com.ping.study.model.vo.live.LiveUrl;
import com.ping.study.service.UrlsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Service
public class UrlsServiceImpl implements UrlsService {
@Autowired
private UrlsMapper urlsMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public List<HashMap<String, List<LiveUrl>>> getUrls() {
public List<HashMap<String, List<LiveUrl>>> getUrls(String includeM3u8) {
String redisKey = "live:urls:all";
// ② 查数据库兜底
List<HashMap<String,List<LiveUrl>>> urlsList = new ArrayList<>();
List<String> gameIds = urlsMapper.selectGameIds();
if (includeM3u8.equals("1")){
gameIds.forEach(gameId -> {
List<LiveUrl> maps = urlsMapper.selectUrlsListByAdmin(gameId);
HashMap<String, List<LiveUrl>> map = new HashMap<>();
map.put(gameId,maps);
urlsList.add(map);
});
return urlsList;
}
// ① 先读缓存Redis 挂了会抛异常,进入 catch → 去 DB
try {
List<HashMap<String, List<LiveUrl>>> cache =
(List<HashMap<String, List<LiveUrl>>>) redisTemplate.opsForValue().get(redisKey);
if(cache != null){
return cache;
}
} catch (Exception ignored) {
// Redis 错了不要打印到控制台,生产只记录日志
}
gameIds.forEach(gameId -> {
List<LiveUrl> maps = urlsMapper.selectUrlsListByGameId(gameId);
HashMap<String, List<LiveUrl>> map = new HashMap<>();
map.put(gameId,maps);
urlsList.add(map);
}); // 添加右括号和分号
});
// ③ DB 查询结果写回 Redis可选
try {
// redisTemplate.opsForValue().set(redisKey, urlsList, 1, TimeUnit.HOURS);
redisTemplate.opsForValue().set(redisKey, urlsList);
} catch (Exception ignored) {
// redis 宕机不影响业务
}
return urlsList;
}
@Override
public void addUrls(addUrls addUrls) {
urlsMapper.insertUrlsWithGameId(addUrls.getGameId(), addUrls.getUrls());
//删除redis缓存
redisTemplate.delete("live:urls:all");
}
@Override
public void deleteUrlById(Integer id) {
try {
urlsMapper.deleteUrlById(id);
//删除redis缓存
redisTemplate.delete("live:urls:all");
}
catch (Exception e) {
e.printStackTrace();
@@ -48,6 +94,40 @@ public class UrlsServiceImpl implements UrlsService {
@Override
public void deleteAllUrls() {
urlsMapper.deleteAllUrls();
//删除redis缓存
redisTemplate.delete("live:urls:all");
}
@Override
public String findUrlByGameIdAndType(String gameId, String type) {
String redis_url = "url:" + gameId+":"+type;
String url;
try {
url = (String) redisTemplate.opsForValue().get(redis_url);
if (url != null){
return url;
}
}catch (Exception ignored){
}
url = urlsMapper.selectUrlsListByGameIdAndType(gameId, type);
try {
if (!url.equals("null")){
redisTemplate.opsForValue().set(redis_url, url,1,TimeUnit.HOURS);
}
}catch (Exception ignored){
}
return url;
}
//修改直播链接
@Override
public void updateUrlById(HashMap<String, String> liveUrl) {
//修改当前id直播链接
urlsMapper.updateUrlsById(liveUrl.get("id"),liveUrl.get("url"));
//删除redis缓存 url:0042500224:tx
redisTemplate.delete("url:"+liveUrl.get("gameId")+":"+liveUrl.get("type"));
}
}

View File

@@ -1,5 +1,7 @@
package com.ping.study.service.tx;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ping.study.mapper.GamesMapper;
import com.ping.study.mapper.UrlsMapper;
@@ -8,10 +10,12 @@ import com.ping.study.model.vo.live.LiveInfoResponse;
import com.ping.study.pojo.Games;
import com.ping.study.utils.tx.CKeyGenerator;
import com.ping.study.utils.tx.TxVideoAuthRedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
@@ -35,84 +39,166 @@ public class LiveInfoService {
private GamesMapper gamesMapper;
@Autowired
private UrlsMapper urlsMapper;
@Autowired
private TxSportTokenRefreshService txSportTokenRefreshService;;
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisTemplate<String, String> stringRedisTemplate;
public LiveInfoService(@Qualifier("tencentWebClient")WebClient webClient, CKeyGenerator cKeyGenerator, StringRedisTemplate stringRedisTemplate) {
@Autowired
private TxVideoAuthRedisUtil txVideoAuthRedisUtil;
public LiveInfoService(@Qualifier("tencentWebClient")WebClient webClient, CKeyGenerator cKeyGenerator) {
this.webClient = webClient;
this.cKeyGenerator = cKeyGenerator;
this.stringRedisTemplate = stringRedisTemplate;
}
// 2. 远程调用时拼接 Cookie
public String getCookieForRequest() {
public String getCookieForRequest() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
// 读取
String cookie = stringRedisTemplate.opsForValue().get("tx_sports_cookie");
return cookie;
String cookieJson = stringRedisTemplate.opsForValue().get("tx_sports_cookie_map");
Map<String, String> cookieMap = objectMapper.readValue(cookieJson, new TypeReference<>() {});
return buildCookieHeader(cookieMap);
}
public String getLiveInfo(String cnlid) {
public String getLiveInfo(String cnlid) throws Exception {
//先调用刷新cookie
txSportTokenRefreshService.refreshCookies();
Thread.sleep(3000);
String platform = "40201";
String ckey = cKeyGenerator.generateCKey(cnlid, platform);
// 1. 打印请求参数
String queryParams = String.format(
"ckey=%s&encrypt_ver=%s&platform=%s&tm=%d&cnlid=%s",
ckey,
LiveInfoRequest.generateEncryptVer(DayOfWeek.from(LocalDate.now())),
platform,
Instant.now().getEpochSecond(),
cnlid
);
log.info("请求参数: {}", queryParams);
// String queryParams = String.format(
// "ckey=%s&encrypt_ver=%s&platform=%s&tm=%d&cnlid=%s",
// ckey,
// LiveInfoRequest.generateEncryptVer(DayOfWeek.from(LocalDate.now())),
// platform,
// Instant.now().getEpochSecond(),
// cnlid
// );
// log.info("请求参数: {}", queryParams);
// 2. 打印请求头Cookie
String cookie = getCookieForRequest();
log.info("请求头 Cookie: {}", cookie);
// log.info("请求头 Cookie: {}", cookie);
// String s = txVideoAuthRedisUtil.buildAuthAndLoginToken("tx_sports_cookie_map").get("auth_ext_raw");
// String s1 = txVideoAuthRedisUtil.buildAuthAndLoginToken("tx_sports_cookie_map").get("logintoken_raw");
// log.info("请求头 auth_ext_encoded: {}", s);
// log.info("请求头 logintoken_encoded: {}", s1);
// 3. 发起请求
return webClient.get()
//defnswitch、auth_ext、logintoken为高帧率fhd_hfps请求参数
String res = webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/cgi-bin/getliveinfo")
.queryParam("ckey", ckey)
.queryParam("encrypt_ver", LiveInfoRequest.generateEncryptVer(
DayOfWeek.from(LocalDate.now())))
.queryParam("auth_ext", txVideoAuthRedisUtil.buildAuthAndLoginToken("tx_sports_cookie_map").get("auth_ext_encoded"))
.queryParam("logintoken", txVideoAuthRedisUtil.buildAuthAndLoginToken("tx_sports_cookie_map").get("logintoken_encoded"))
.queryParam("encrypt_ver", LiveInfoRequest.generateEncryptVer(DayOfWeek.from(LocalDate.now())))
.queryParam("platform", platform)
.queryParam("auth_from", 4001)
.queryParam("sphttps", 1)
.queryParam("tm", Instant.now().getEpochSecond())
.queryParam("cnlid", cnlid)
.queryParam("defn", "fhd")
.queryParam("ufps", "auto")
.build())
.header("Cookie", "_qimei_q32=b6ff8aedc45c2d93874747a3e7f5e3dd; ptui_loginuin=1131302745; vqq_access_token=308C7CAFBC867AF65212034AC90E71D9; lcad_appuser=CCC4A35B6047F544; tvfe_boss_uuid=d41b10b066856291; _qimei_uuid42=184170b170810037e9eacb93c4fe35a57c7df28f38; LW_sid=31h7T2M8f3O5O280q0K0L365H2; o_cookie=941039061; lcad_LDERturn=503; last_nick=%E8%85%BE%E8%AE%AF%E7%BD%91%E5%8F%8B; uin_cookie=o0941039061; lcad_LPDFturn=43; spt_uid=1829817828670177361; RK=dudkRjOGUW; ied_qq=o0941039061; lcad_LPVLturn=470; lcad_o_minduid=OCcytZR6an_BFYcypXv6ZzuBX65okAHc; pgv_pvid=67939534; lcad_LVINturn=636; pgv_info=ssid=s6516917000; last_main_login=qq; eas_sid=E1i7L1k4j4D7q1D0Q5J894p636; fqm_pvqid=5e0a1a80-2ffb-42c9-8878-99ef08c0bd21; last_avatar=https://mat1.gtimg.com/sports/sportapp/default_profile_big_after_v1_4.png; lcad_LPPBturn=28; lcad_LPSJturn=135; livelink_pvid=7900946432; LW_uid=P1J7P2i8S3R5a2r0j020t3n543; o2_uin=941039061; pac_uid=0_ePEHB57t75p7c; ptcz=d9e319494b15cb621d9f54a8e03a1b55fb6c6c3564ff6c4985d1ff7b4797ce4e; qq_domain_video_guid_verify=119f9c981e9ffd34; vqq_appid=101481799; vqq_openid=2E6A77E3002C87A9CA68908FB058D4E5; vqq_vuserid=3468482246; _qimei_fingerprint=aaf3a442da56251083fbe4352a04dc72; _qimei_q36=09d3b683102573c400e0b201300015518b19; _qimei_h38=fdedada2b12cacb7c731c46802000004418b19; vqq_vusession=jjtKD6NC-SPkVdItUGl3Sg.N; ts_last=sports.qq.com/kbsweb/mycenter.htm; ts_uid=9292458684; ukey=174520991101356433; boss_user=loginType=1&nbaVipType=0&qq=&wxOpenId=2E6A77E3002C87A9CA68908FB058D4E5&wxAppId=101481799&uKey=174520991101356433; ptag=film_video_qq_com|; main_login=qq; spt_session=CLuygiIQ7MONwAYY8ZWXwAYg1wE; spt_token=roxqAw5zstnrHn4H7DMOSbTeSkCVC0JjQGPz6Lp5Jx1-rxtW6mfo-wgg8jq41xEi; next_refresh_time=1745216492421; bossv2_isvip=-1")
// --- 在这里添加伪造 IP 的 Header ---
.header("X-Forwarded-For", "59.54.11.234")
.header("X-Real-IP", "59.54.11.234")
.header("Client-IP", "59.54.11.234")
// -------------------------------------
.header("Cookie", cookie)
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36") // 确保 UA 也像浏览器
.header("Referer", "https://sports.qq.com/") // 强烈建议加上 Referer通常防盗链会查这个
.retrieve()
.bodyToMono(String.class)
.block();
// String res = webClient.get()
// .uri(uriBuilder -> uriBuilder
// .path("/cgi-bin/getliveinfo")
// .queryParam("ckey", ckey)
//// .queryParam("defnswitch",1)
// .queryParam("auth_ext", txVideoAuthRedisUtil.buildAuthAndLoginToken("tx_sports_cookie_map").get("auth_ext_encoded"))
// .queryParam("logintoken",txVideoAuthRedisUtil.buildAuthAndLoginToken("tx_sports_cookie_map").get("logintoken_encoded"))
// .queryParam("encrypt_ver", LiveInfoRequest.generateEncryptVer(
// DayOfWeek.from(LocalDate.now())))
// .queryParam("platform", platform)
//// .queryParam("hevclv",26)
//// .queryParam("host","sports.qq.com")
// .queryParam("auth_from",4001)
// .queryParam("sphttps",1)
// .queryParam("tm", Instant.now().getEpochSecond())
// .queryParam("cnlid", cnlid)
// .queryParam("defn","fhd")
// .queryParam("ufps","auto")
// .build())
// .header("Cookie", cookie)
// .retrieve()
// .bodyToMono(String.class)
// .block();
List<String> list = extractPlayUrls(res);
// log.info("提取到的播放URL: {}", list);
return res;
}
private String buildCookieHeader(Map<String, String> cookieMap) {
return cookieMap.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("; "));
}
public List<String> extractPlayUrls(String liveInfoJson) throws Exception {
ObjectMapper mapper = new ObjectMapper();
LiveInfoResponse response = mapper.readValue(liveInfoJson, LiveInfoResponse.class);
// 首先将JSON字符串解析为Map结构
Map<String, Object> responseMap = mapper.readValue(liveInfoJson, new TypeReference<Map<String, Object>>() {});
List<String> urls = new ArrayList<>();
if (response.getPlay_list() != null) {
for (LiveInfoResponse.PlayItem item : response.getPlay_list()) {
if (item.getUrls() != null) {
urls.addAll(item.getUrls());
// 检查play_list是否存在
if (responseMap.containsKey("play_list")) {
List<Map<String, Object>> playList = (List<Map<String, Object>>) responseMap.get("play_list");
for (Map<String, Object> playItem : playList) {
// 检查urls是否存在
if (playItem.containsKey("urls")) {
List<String> itemUrls = (List<String>) playItem.get("urls");
urls.addAll(itemUrls);
}
}
}
// log.info("提取到的播放URL数量: {}", urls.size());
return urls;
}
public String refreshLiveInfo() {
public String refreshLiveInfo() throws Exception {
List<Games> gamesList = gamesMapper.selectAll();
for (Games games : gamesList) {
String playId = games.getPlayId();
String liveInfo = getLiveInfo(playId);
log.info("获取到的直播信息: {}", liveInfo);
// log.info("当前直播id: {}", playId);
// log.info("获取到的直播信息: {}", liveInfo);
try {
// log.info("进入refreshLiveInfo解析liveInfo");
List<String> urls = extractPlayUrls(liveInfo);
log.info("提取到的播放URL: {}", urls);
urlsMapper.updateUrlsWithGameId(games.getGameId(), urls.get(0));
// log.info("提取到的播放URL: {}", urls);
if (urls.size()<2){
log.info("当前直播id: {}, 获取直播源失败", playId);
log.info("跳过当前直播,等待下一次获取");
continue;
// Thread.sleep(500);
// liveInfo = getLiveInfo(playId);
// urls = extractPlayUrls(liveInfo);
}
urlsMapper.updateUrlsWithGameId(games.getGameId(), urls.get(0),"tx");
log.info("当前直播id: {}, 获取直播源成功", playId);
Thread.sleep(100);
// log.info("提取的直播链接:{}",urls.get(urls.size()-1));
// urlsMapper.updateUrlsWithGameId(games.getGameId(), urls.get(1),"tx2");
// urlsMapper.updateUrlsWithGameId(games.getGameId(), urls.get(0),"zb");
// 这里可以保存urls到数据库或其他处理
} catch (Exception e) {
log.error("解析直播信息失败");

View File

@@ -1,5 +1,6 @@
package com.ping.study.service.tx;
import com.ping.study.model.vo.live.LiveIds;
import com.ping.study.model.vo.tx.MatchInfo;
import com.ping.study.model.vo.tx.MatchResponse;
import org.springframework.stereotype.Service;
@@ -28,7 +29,7 @@ public class MatchService {
/**
* 获取指定日期范围内所有NBA季后赛(matchType=2)的直播ID
*/
public List<String> getPlayoffLiveIdsBlocking(LocalDate startDate, LocalDate endDate) {
public List<LiveIds> getPlayoffLiveIdsBlocking(LocalDate startDate, LocalDate endDate) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/matchUnion/list")
@@ -42,18 +43,53 @@ public class MatchService {
if (response.getCode() != 0 || response.getData() == null) {
return Flux.error(new RuntimeException("API返回异常: " + response.getMsg()));
}
// 提取所有matchType=2的比赛
List<String> liveIds = response.getData().values().stream()
// 提取所有 matchType=2 & competitionId=100000 的比赛
List<LiveIds> liveIds = response.getData().values().stream()
.flatMap(List::stream)
.filter(match -> "2".equals(match.getMatchType()))
.map(MatchInfo::getLiveId)
.filter(match -> "100000".equals(match.getCompetitionId()))
.map(match -> {
LiveIds ids = new LiveIds();
ids.setMId(match.getMId());
ids.setPlayId(match.getLiveId());
return ids;
})
.collect(Collectors.toList());
return Flux.fromIterable(liveIds);
})
.collectList()
.block(); // 阻塞等待结果
}
// public List<String> getPlayoffLiveIdsBlocking(LocalDate startDate, LocalDate endDate) {
// return webClient.get()
// .uri(uriBuilder -> uriBuilder
// .path("/matchUnion/list")
// .queryParam("columnId", 100000)
// .queryParam("startTime", startDate.format(DATE_FORMATTER))
// .queryParam("endTime", endDate.format(DATE_FORMATTER))
// .build())
// .retrieve()
// .bodyToMono(MatchResponse.class)
// .flatMapMany(response -> {
// if (response.getCode() != 0 || response.getData() == null) {
// return Flux.error(new RuntimeException("API返回异常: " + response.getMsg()));
// }
// // 提取所有matchType=2的比赛
// List<String> liveIds = response.getData().values().stream()
// .flatMap(List::stream)
// .filter(match -> "2".equals(match.getMatchType()))
// .filter(match -> "100000".equals(match.getCompetitionId()))
// .map(MatchInfo::getLiveId)
// .collect(Collectors.toList());
// return Flux.fromIterable(liveIds);
// })
// .collectList()
// .block(); // 阻塞等待结果
// }
/**
* 获取完整比赛信息过滤matchType=2
*/

View File

@@ -1,36 +0,0 @@
package com.ping.study.service.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service
public class QQCookieService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final String REDIS_KEY = "user:nba"; // 固定 Key
// 存储 Cookie无需 uin 参数)
public void saveCookie(Map<String, String> cookies) {
stringRedisTemplate.opsForHash().putAll(REDIS_KEY, cookies);
stringRedisTemplate.expire(REDIS_KEY, 30, TimeUnit.DAYS);
}
// 获取 Cookie无需 uin 参数)
public Map<String, String> getCookie() {
Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(REDIS_KEY);
Map<String, String> result = new HashMap<>();
entries.forEach((key, value) ->
result.put(key.toString(), value.toString())
);
return result;
}
}

View File

@@ -1,49 +0,0 @@
package com.ping.study.service.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.util.Map;
@Service
public class QQRequestService {
@Autowired
private RestTemplate restTemplate; // 现在可以正常注入
@Autowired
private QQCookieService qqCookieService;
@Autowired
private QQTokenRefreshService tokenRefreshService;
// 发起请求(自动处理 Cookie 和 Token 刷新)
public String fetchData(String uin, String apiUrl) throws IOException {
Map<String, String> cookies = qqCookieService.getCookie();
// 检查 Token 是否即将过期
long expiresIn = Long.parseLong(cookies.getOrDefault("vqq_next_refresh_time", "0"));
if (expiresIn < 300) { // 剩余 <5 分钟时刷新
tokenRefreshService.refreshToken();
cookies = qqCookieService.getCookie(); // 重新加载
}
// 构造请求头(模拟浏览器)
HttpHeaders headers = new HttpHeaders();
headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
cookies.forEach((name, value) -> headers.add("Cookie", name + "=" + value));
// 发起请求
ResponseEntity<String> response = restTemplate.exchange(
apiUrl,
HttpMethod.GET,
new HttpEntity<>(headers),
String.class
);
return response.getBody();
}
}

View File

@@ -1,50 +0,0 @@
package com.ping.study.service.tx;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.util.Map;
@Service
public class QQTokenRefreshService {
@Autowired
private QQCookieService qqCookieService;
@Autowired
private RestTemplate restTemplate;
// 刷新 Token
public boolean refreshToken() throws IOException {
Map<String, String> cookies = qqCookieService.getCookie();
String refreshToken = cookies.get("vqq_refresh_token");
if (refreshToken == null || refreshToken.isEmpty()) {
throw new IllegalStateException("No refresh_token available, need re-login");
}
// 调用 QQ 的 Token 刷新接口(示例,需替换真实 API
String url = "https://xui.ptlogin2.qq.com/refresh?refresh_token=" + refreshToken;
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
// 解析新 Token 并更新 Cookie
String newAccessToken = parseNewToken(response.getBody());
cookies.put("vqq_access_token", newAccessToken);
cookies.put("vqq_next_refresh_time", "7200"); // 假设新有效期 2 小时
qqCookieService.saveCookie(cookies);
return true;
} else {
return false; // 刷新失败,需重新登录
}
}
private String parseNewToken(String responseBody) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(responseBody);
return root.get("access_token").asText();
}
}

View File

@@ -1,18 +1,35 @@
package com.ping.study.service.tx;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ping.study.mapper.UrlsMapper;
import com.ping.study.model.dto.tx.MatchListRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@Slf4j
@Service
public class SportsQqService {
private final WebClient webClient;
private static final DateTimeFormatter DATE_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Autowired
private UrlsMapper urlsMapper;
@Autowired
private ObjectMapper objectMapper; // Spring Boot 自动注入 Jackson
public SportsQqService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder
.baseUrl("https://matchweb.sports.qq.com")
@@ -20,8 +37,34 @@ public class SportsQqService {
.build();
}
// --- 修改点 1: 新增一个无参方法专门用于定时任务 ---
// 定时任务入口:不需要参数,也不需要返回值(或者返回Mono<Void>)
@Scheduled(cron = "0 0/5 3-16 * * ?")
public void scheduledSyncTask() {
log.info("开始执行定时同步直播源任务...");
// 1. 在这里手动构造请求参数
// 假设你需要查询当天的比赛
MatchListRequest request = new MatchListRequest();
//request.setColumnId("100000"); // 示例值,根据你的实际情况设置
request.setStartTime(LocalDate.now()); // 设置开始时间
request.setEndTime(LocalDate.now()); // 设置结束时间
// 2. 调用业务逻辑,并且必须订阅 (.subscribe) 才能触发执行
getMatchList(request)
.subscribe(
result -> log.info("定时任务执行完成"),
error -> log.error("定时任务执行出错", error)
);
}
// --- 修改点 2: 原来的方法去掉 @Scheduled作为一个普通的服务方法 ---
public Mono<String> getMatchList(MatchListRequest request) {
request.validate(); // 参数校验
// 参数校验 (建议保留)
if (request == null) {
return Mono.error(new IllegalArgumentException("Request cannot be null"));
}
// request.validate(); // 如果你有 validate 方法
return webClient.get()
.uri(uriBuilder -> uriBuilder
@@ -31,6 +74,128 @@ public class SportsQqService {
.queryParam("endTime", request.getEndTime().format(DATE_FORMATTER))
.build())
.retrieve()
.bodyToMono(String.class);
.bodyToMono(String.class)
.doOnNext(jsonString -> {
try {
JsonNode rootNode = objectMapper.readTree(jsonString);
JsonNode dataNode = rootNode.path("data");
dataNode.fields().forEachRemaining(entry -> {
JsonNode matchesArray = entry.getValue();
if (matchesArray.isArray()) {
for (JsonNode match : matchesArray) {
String mid = match.path("mid").asText("");
String matchPeriod = match.path("matchPeriod").asText("");
// 3. 处理 mid 获取后 10 位
String targetId = "";
if (mid.contains(":")) {
String[] parts = mid.split(":");
String suffix = parts[1]; // 这里拿到的是冒号后的完整部分,如 10022500079 (11位) 或 20257089 (8位)
// 如果长度大于10截取最后10位否则保持原样
if (suffix.length() > 10) {
targetId = suffix.substring(suffix.length() - 10);
} else {
targetId = suffix;
}
} else {
// 防止没有冒号的情况,同样做截取保护
if (mid.length() > 10) {
targetId = mid.substring(mid.length() - 10);
} else {
targetId = mid;
}
}
if (!targetId.isEmpty()) {
urlsMapper.updateStatusWithGameId(targetId, matchPeriod);
log.info("更新比赛:{} 直播状态成功,阶段: {}", targetId, matchPeriod);
}
}
}
});
} catch (Exception e) {
log.error("解析或更新数据库失败", e);
}
});
}
//更新直播源状态
//定时任务 每隔10分钟执行一次,从每天早上3点到下午5点之间执行
// @Scheduled(cron = "0 0/5 3-16 * * ?")
// public Mono<String> updateGameStatus() {
//// request.validate();
// MatchListRequest request = new MatchListRequest();
// return webClient.get()
// .uri(uriBuilder -> uriBuilder
// .path("/matchUnion/list")
// .queryParam("columnId", request.getColumnId())
// .queryParam("startTime", request.getStartTime().format(DATE_FORMATTER))
// .queryParam("endTime", request.getEndTime().format(DATE_FORMATTER))
// .build())
// .retrieve()
// .bodyToMono(String.class)
// .doOnNext(jsonString -> {
// // --- 在这里进行解析和数据库保存操作 ---
// try {
// JsonNode rootNode = objectMapper.readTree(jsonString);
// JsonNode dataNode = rootNode.path("data");
//
// // 1. data 下面的 key 是动态日期 (例如 "2025-11-29"),所以我们要遍历字段
// dataNode.fields().forEachRemaining(entry -> {
// // String dateKey = entry.getKey(); // 如果需要日期,可以拿这个
// JsonNode matchesArray = entry.getValue();
//
// // 2. 遍历当天的比赛列表
// if (matchesArray.isArray()) {
// for (JsonNode match : matchesArray) {
// // 提取 mid
// String mid = match.path("mid").asText("");
// // 提取 matchPeriod
// String matchPeriod = match.path("matchPeriod").asText("");
//
// // 3. 处理 mid 获取后 10 位 (或冒号后的部分)
// String targetId = "";
// if (mid.contains(":")) {
// // 逻辑A获取冒号后面的所有数字 (推荐通常这是唯一ID)
// String[] parts = mid.split(":");
// targetId = parts[1];
//
// // 逻辑B如果你严格需要“最后10位字符” (如果ID长度超过10会截取)
// if (targetId.length() > 10) {
// targetId = targetId.substring(targetId.length() - 10);
// }
// } else {
// targetId = mid; // 防止没有冒号的情况
// }
//
// // 4. 保存到数据库
// if (!targetId.isEmpty()) {
// //saveMatchInfoToDb(mid, targetId, matchPeriod);
// urlsMapper.updateStatusWithGameId(targetId,matchPeriod);
// log.info("更新比赛:{}直播状态成功:",targetId);
// }
// }
// }
// });
// } catch (Exception e) {
// e.printStackTrace();
// // 记录日志:解析失败
// }
// });
// }
// public Mono<String> getMatchList(MatchListRequest request) {
// request.validate(); // 参数校验
//
// return webClient.get()
// .uri(uriBuilder -> uriBuilder
// .path("/matchUnion/list")
// .queryParam("columnId", request.getColumnId())
// .queryParam("startTime", request.getStartTime().format(DATE_FORMATTER))
// .queryParam("endTime", request.getEndTime().format(DATE_FORMATTER))
// .build())
// .retrieve()
// .bodyToMono(String.class);
// }
}

View File

@@ -1,14 +1,12 @@
package com.ping.study.service.tx;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ping.study.model.vo.live.LiveInfoResponse;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import com.ping.study.utils.MailUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@@ -23,115 +21,97 @@ import java.util.stream.Collectors;
public class TxSportTokenRefreshService {
private final WebClient webClient;
private final StringRedisTemplate redisTemplate;
private static final String REDIS_COOKIE_KEY = "tx_sports_cookie";
private final RedisTemplate<String, String> redisTemplate;
private final ObjectMapper objectMapper = new ObjectMapper();
private static final String REDIS_COOKIE_MAP_KEY = "tx_sports_cookie_map";
@Autowired
private MailUtil mailUtil;
public TxSportTokenRefreshService(WebClient.Builder builder,
StringRedisTemplate redisTemplate) {
RedisTemplate<String, String> redisTemplate) {
this.webClient = builder.baseUrl("https://app.sports.qq.com").build();
this.redisTemplate = redisTemplate;
}
@PostConstruct
public void initBaseCookies() {
String baseCookies = "pgv_pvid=67939534; fqm_pvqid=5e0a1a80-2ffb-42c9-8878-99ef08c0bd21; RK=dudkRjOGUW; ptcz=d9e319494b15cb621d9f54a8e03a1b55fb6c6c3564ff6c4985d1ff7b4797ce4e; video_platform=2; qq_domain_video_guid_verify=119f9c981e9ffd34; _qimei_uuid42=184170b170810037e9eacb93c4fe35a57c7df28f38; _qimei_q36=; _qimei_h38=215914e0566ee066f810eb4b0200000e81840b; video_guid=119f9c981e9ffd34; tvfe_boss_uuid=d41b10b066856291; eas_sid=E1i7L1k4j4D7q1D0Q5J894p636; livelink_pvid=7900946432; check_16=584445b5d815fd331bb7206205f8c603; _qimei_fingerprint=3c06d28104a1e802703470a164d81dca; ptui_loginuin=1131302745";
redisTemplate.opsForValue().set(REDIS_COOKIE_KEY, baseCookies, 30, TimeUnit.DAYS);
}
/**
* 刷新Cookie并存储到Redis
*/
// 在首次成功时分离并存储基础登录态Cookie
public boolean refreshCookies() {
try {
// 1. 从Redis获取基础登录态Cookie首次需手动初始化
String baseCookie = redisTemplate.opsForValue().get(REDIS_COOKIE_KEY);
log.info("当前Cookie: {}", baseCookie);
// 2. 携带基础Cookie发起请求
String cookieJson = redisTemplate.opsForValue().get(REDIS_COOKIE_MAP_KEY);
if (cookieJson == null) {
log.error("Redis 中未找到 Cookie无法刷新");
return false;
}
// log.info("旧的cookie{}",cookieJson);
Map<String, String> baseCookies = objectMapper.readValue(cookieJson, new TypeReference<>() {});
String cookieHeader = buildCookieHeader(baseCookies);
log.info("新的cookie{}",cookieHeader);
String response = webClient.get()
.uri("/init/refresh?os=web")
.header("Cookie", baseCookie) // 关键点:始终携带基础登录态
.header("Cookie", cookieHeader)
.retrieve()
.bodyToMono(String.class)
.block();
// 3. 合并新旧Cookie保留基础登录态
JsonNode data = new ObjectMapper().readTree(response).path("data");
String newCookie = extractNewCookies(data); // 提取spt_token等
String finalCookie = mergeCookies(baseCookie, newCookie);
// 4. 存储到Redis
JsonNode data = objectMapper.readTree(response).path("data");
Map<String, String> newCookies = extractNewCookieMap(data);
baseCookies.putAll(newCookies);
redisTemplate.opsForValue().set(
REDIS_COOKIE_KEY,
finalCookie,
REDIS_COOKIE_MAP_KEY,
objectMapper.writeValueAsString(baseCookies),
Long.parseLong(data.path("tokenTTL").asText()),
TimeUnit.SECONDS
);
return true;
} catch (Exception e) {
log.error("刷新失败", e);
log.error("刷新 Cookie 失败,原因:{}", e.getMessage());
return false;
}
}
/**
* 从腾讯API响应中提取新的Cookie信息
*/
private String extractNewCookies(JsonNode dataNode) {
if (dataNode == null || !dataNode.has("cookie")) {
return "";
private Map<String, String> extractNewCookieMap(JsonNode dataNode) {
Map<String, String> result = new HashMap<>();
if (dataNode != null && dataNode.has("cookie")) {
for (JsonNode cookie : dataNode.get("cookie")) {
result.put(cookie.get("name").asText(), cookie.get("value").asText());
}
}
StringBuilder cookieStr = new StringBuilder();
Iterator<JsonNode> cookies = dataNode.path("cookie").elements();
while (cookies.hasNext()) {
JsonNode cookie = cookies.next();
cookieStr.append(cookie.path("name").asText())
.append("=")
.append(cookie.path("value").asText())
.append("; ");
}
return cookieStr.toString();
return result;
}
// 合并Cookie的工具方法
private String mergeCookies(String base, String news) {
Map<String, String> map = new LinkedHashMap<>();
// 先解析基础Cookie
Arrays.stream(base.split("; "))
.forEach(c -> {
String[] kv = c.split("=", 2);
if (kv.length == 2) map.put(kv[0], kv[1]);
});
// 用新Cookie覆盖
Arrays.stream(news.split("; "))
.forEach(c -> {
String[] kv = c.split("=", 2);
if (kv.length == 2) map.put(kv[0], kv[1]);
});
// 重新拼接
return map.entrySet().stream()
private String buildCookieHeader(Map<String, String> cookieMap) {
return cookieMap.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("; "));
}
/**
* 定时刷新任务每1小时执行一次
*/
// @Scheduled(fixedRate = 3600000)
// 可选:定时自动刷新(你可以打开)
@Scheduled(fixedRate = 1800000)
public void scheduledRefresh() {
if (!refreshCookies()) {
log.warn("首次刷新失败尝试重新初始化基础Cookie...");
initBaseCookies();
// try {
// Thread.sleep(30000); // 等待30秒后重试
// if (!refreshCookies()) {
// log.error("重试刷新仍然失败");
// }
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// }
log.info("定时刷新Cookie");
boolean success = refreshCookies();
if (success) {
redisTemplate.delete("errors");
log.info("Cookie 刷新成功");
return;
}
Long errorCount = redisTemplate.opsForValue().increment("errors");
log.warn("Cookie 刷新失败,当前连续失败次数:{}", errorCount);
if (errorCount != null && errorCount > 2) {
log.warn("Cookie 刷新失败超过 2 次Redis 中可能缺失数据");
mailUtil.sendEmail2(
"1131302745@qq.com",
"NBA服务异常通知",
"腾讯体育超级会员 Cookie 刷新失败,请排查。当前连续失败次数:" + errorCount
);
}
}
}
}

View File

@@ -0,0 +1,47 @@
package com.ping.study.test;
import com.ping.study.model.dto.tx.TxMatchRequest;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
@RestController
@RequestMapping("/test")
public class TxMatch {
private final WebClient webClient;
public TxMatch(@Qualifier("matchWebClient") WebClient webClient) {
this.webClient = webClient;
}
@RequestMapping("/match")
public String getMatch() {
TxMatchRequest request = new TxMatchRequest();
System.out.println("TxMatchRequest = " + request);
// 正式发起请求
return webClient.get()
.uri(uriBuilder -> {
uriBuilder.path("/matchUnion/list")
.queryParam("today", request.getToday())
.queryParam("startTime", request.getStartTime())
.queryParam("endTime", request.getEndTime())
.queryParam("columnId", request.getColumnId())
.queryParam("index", request.getIndex())
.queryParam("isInit", request.getIsInit())
.queryParam("timestamp", request.getTimestamp())
.queryParam("callback", request.getCallback());
return uriBuilder.build();
})
.retrieve()
.bodyToMono(String.class)
.block();
}
}

View File

@@ -0,0 +1,201 @@
package com.ping.study.utils;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
/**
* 邮件发送工具类
*/
@Component
@Slf4j
public class MailUtil {
@Autowired
private RedisUtil redisUtil;
private final JavaMailSender javaMailSender;
@Autowired
public MailUtil(JavaMailSender javaMailSender) {
this.javaMailSender = javaMailSender;
}
/**
* 生成 6 位数字验证码
*
* @return 6 位验证码
*/
private String generateVerificationCode() {
Random random = new Random();
int code = 100000 + random.nextInt(900000); // 生成 100000 - 999999 的随机数
return String.valueOf(code);
}
/**
* 发送验证码邮件
*
* @param to 收件人邮箱
*/
public String sendVerificationCode(String to) {
Assert.hasText(to, "邮箱不能为空");
// 生成验证码
String verificationCode = generateVerificationCode();
// 构造邮件
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("xdd9@vip.qq.com");
message.setTo(to);
message.setSubject("【NBA轻松看验证码】");
message.setText("您的验证码是:" + verificationCode + ",有效期 5 分钟"+"。请勿回复此邮件。"+"\n反馈邮箱super@2026123.xyz"+"\n发布页2026123.xyz");
// message.setText("发布页2026123.xyz");
// 发送邮件
try {
// 返回验证码(存入 Redis 用于验证)
if (redisUtil.redisTemplateT(to,verificationCode)){
javaMailSender.send(message);
log.info("收件邮箱 -> {}", to);
log.info("验证码存入redis -> {}", verificationCode);
return "验证码发送成功,因邮件发送有延迟请不要重复发送";
}
// redisUtil.redisTemplateT(to,verificationCode);
return "验证码已发送到邮箱,请不要重复点击,如未收到邮件检查垃圾箱或邮箱是否正确";
// return verificationCode;
}catch (Exception e){
//System.out.println("发送邮件失败:" + e.getMessage());
return "发送邮件失败,请检查邮箱号是否正确";
}
}
/**
* 发送邮件
*
* @param to 收件人邮箱
* @param subject 邮件主题
* @param text 邮件内容
*/
public String sendEmail2(String to,String subject,String text) {
Assert.hasText(to, "邮箱不能为空");
// 构造邮件
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("xdd9@vip.qq.com");
message.setTo(to);
message.setSubject(subject);
message.setText(text);
// message.setText("发布页2026123.xyz");
// 发送邮件
try {
javaMailSender.send(message);
return "发送成功";
// return verificationCode;
}catch (Exception e){
log.info("发送邮件失败:{}", e.getMessage());
return "发送邮件失败";
}
}
/**
*
* @param map
*/
public void sendEmail(HashMap<String,String> map) {
Assert.hasText(map.get("mail"), "收件人不能为空");
// 构造邮件
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("xdd9@vip.qq.com");
message.setTo(map.get("mail"));
message.setSubject(map.get("subject"));
message.setText(map.get("text"));
// 发送邮件
javaMailSender.send(message);
}
/**
* 发送 HTML 邮件
*
* @param to 收件人邮箱
* @param subject 邮件主题
* @param html HTML 内容
*/
public void sendHtmlEmail(String to, String subject, String html) throws MessagingException, jakarta.mail.MessagingException {
Assert.hasText(to, "邮箱不能为空");
Assert.hasText(subject, "邮件主题不能为空");
Assert.hasText(html, "HTML 内容不能为空");
jakarta.mail.internet.MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(html, true); // true 表示支持 HTML
javaMailSender.send(message);
}
/**
* 发送带附件的邮件
*
* @param to 收件人邮箱
* @param subject 邮件主题
* @param text 邮件内容
* @param attachments 附件文件列表
*/
public void sendEmailWithAttachments(String to, String subject, String text, List<File> attachments) throws MessagingException, jakarta.mail.MessagingException {
Assert.hasText(to, "收件人邮箱不能为空");
Assert.hasText(subject, "邮件主题不能为空");
Assert.hasText(text, "邮件内容不能为空");
Assert.notEmpty(attachments, "附件列表不能为空");
jakarta.mail.internet.MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(text);
// 添加附件
for (File file : attachments) {
helper.addAttachment(file.getName(), file);
}
javaMailSender.send(message);
}
/**
* 发送带内联资源的 HTML 邮件
*
* @param to 收件人邮箱
* @param subject 邮件主题
* @param html HTML 内容
* @param inlineFiles 内联资源文件列表
*/
public void sendHtmlEmailWithInline(String to, String subject, String html, List<File> inlineFiles) throws MessagingException, jakarta.mail.MessagingException {
Assert.hasText(to, "收件人邮箱不能为空");
Assert.hasText(subject, "邮件主题不能为空");
Assert.hasText(html, "HTML 内容不能为空");
Assert.notEmpty(inlineFiles, "内联资源文件列表不能为空");
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(html, true);
// 添加内联资源
for (File file : inlineFiles) {
helper.addInline(file.getName(), file);
}
javaMailSender.send(message);
}
}

View File

@@ -1,10 +1,13 @@
package com.ping.study.utils;
import com.ping.study.mapper.UrlsMapper;
import com.ping.study.model.dto.NbaStatsRequestDto;
import com.ping.study.model.vo.ApiResponse;
import com.ping.study.model.vo.Game;
import com.ping.study.model.vo.Group;
import com.ping.study.model.vo.live.LiveIds;
import com.ping.study.pojo.Games;
import com.ping.study.pojo.Urls;
import com.ping.study.service.GamesService;
import com.ping.study.service.tx.MatchService;
import lombok.Data;
@@ -34,17 +37,22 @@ public class NbaApi {
@Autowired
private MatchService matchService;
@Autowired
private UrlsMapper urlsMapper;
public NbaApi(@Qualifier("nbaWebClient")WebClient webClient) {
this.webClient = webClient;
}
public List<Games> addGames() {
public List<Games> addGames() throws InterruptedException {
// 创建请求DTO会自动设置当前时间戳和日期
NbaStatsRequestDto requestParams = new NbaStatsRequestDto();
log.info("{}", requestParams);
log.info("进入{},执行{}", this.getClass().getName(), "getGames");
//获取所有当天直播id
List<String> lives = matchService.getPlayoffLiveIdsBlocking(LocalDate.now(), LocalDate.now());
// log.info("{}", requestParams);
// log.info("进入{},执行{}", this.getClass().getName(), "getGames");
//获取所有当天直播id集合
List<LiveIds> lives = matchService.getPlayoffLiveIdsBlocking(LocalDate.now(), LocalDate.now());
Thread.sleep(1000);
List<Games> gameEntities = webClient.get()
.uri("/game/schedule", uriBuilder -> uriBuilder
.queryParam("app_key", requestParams.getAppKey())
@@ -64,7 +72,7 @@ public class NbaApi {
.retrieve()
.bodyToMono(ApiResponse.class)
.map(response -> {
log.info("{}", response);
List<Games> entities = new ArrayList<>();
// 遍历groups中的games
for (Group group : response.getData().getGroups()) {
@@ -76,7 +84,14 @@ public class NbaApi {
entity.setAwayTeamName(group.getGames().get(i).getAwayTeamName());
entity.setHomeTeamLogoDark(group.getGames().get(i).getHomeTeamLogoDark());
entity.setAwayTeamLogoDark(group.getGames().get(i).getAwayTeamLogoDark());
entity.setPlayId(lives.get(i));
entity.setStatus(group.getGames().get(i).getStatus());
for (int j = 0; j < lives.size(); j++){
if (lives.get(j).getMId().contains(group.getGames().get(i).getGameId())){
entity.setPlayId(lives.get(j).getPlayId());
break;
}
}
// entity.setPlayId(lives.get(i));
// 合并日期和时间
String dateTimeStr = group.getGames().get(i).getStartDate() + " " + group.getGames().get(i).getStartTime();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -86,31 +101,28 @@ public class NbaApi {
// 处理异常或设置默认值
entity.setStartTime(null);
}
for (int j = 0; j < 2; j++){
Urls urls = new Urls(); // 每次新建一个对象
urls.setGameId(entity.getGameId());
switch (j) {
case 0:
urls.setType("tx");
urls.setUrl("");
break;
case 1:
urls.setType("zb");
urls.setUrl("");
break;
// case 2:
// urls.setType("zb");
// urls.setUrl("");
// break;
}
urlsMapper.insertSelective(urls);
}
gamesService.insertGames(entity);
entities.add(entity);
}
// for (Game game : group.getGames()) {
// Games entity = new Games();
// entity.setDate(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
// entity.setGameId(game.getGameId());
// entity.setHomeTeamName(game.getHomeTeamName());
// entity.setAwayTeamName(game.getAwayTeamName());
// entity.setHomeTeamLogoDark(game.getHomeTeamLogoDark());
// entity.setAwayTeamLogoDark(game.getAwayTeamLogoDark());
//
// // 合并日期和时间
// String dateTimeStr = game.getStartDate() + " " + game.getStartTime();
// SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// try {
// entity.setStartTime(format.parse(dateTimeStr));
// } catch (ParseException e) {
// // 处理异常或设置默认值
// entity.setStartTime(null);
// }
// gamesService.insertGames(entity);
// entities.add(entity);
// }
}
return entities;
})

View File

@@ -0,0 +1,24 @@
package com.ping.study.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
@Component
@Slf4j
public class RedisUtil {
@Autowired
private RedisTemplate<String, String> stringRedisTemplate;
public boolean redisTemplateT(String email,String code) {
//log.info("验证码:{}",code);
return stringRedisTemplate.opsForValue().setIfAbsent(email, code, Duration.ofSeconds(60*5));
}
}

View File

@@ -0,0 +1,24 @@
package com.ping.study.utils;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
@Component
public class UserUtil {
public String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 取第一段 IP
return ip.split(",")[0];
}
ip = request.getHeader("X-Real-IP");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
return ip;
}
return request.getRemoteAddr();
}
}

View File

@@ -0,0 +1,194 @@
package com.ping.study.utils.tx;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* 从 Redis 获取 cookie 并生成 auth_ext / logintokenraw + urlencoded
*
* 使用:
* @Autowired TxVideoAuthRedisUtil txUtil;
* Map<String,String> result = txUtil.buildAuthAndLoginToken("tx_sports_cookie_map");
* String authExtEncoded = result.get("auth_ext_encoded");
* String loginTokenEncoded = result.get("logintoken_encoded");
*/
@Component
public class TxVideoAuthRedisUtil {
private final RedisTemplate<String, String> stringRedisTemplate;
private final ObjectMapper objectMapper;
public TxVideoAuthRedisUtil(RedisTemplate<String, String> stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
this.objectMapper = new ObjectMapper();
}
/**
* 主方法:给定 redis key存 cookie 的值),返回 map 包含:
* - auth_ext_raw
* - auth_ext_encoded
* - logintoken_raw
* - logintoken_encoded
* - cookie_map_json (string representation of parsed cookie map)
*/
public Map<String, String> buildAuthAndLoginToken(String redisKey) {
String redisValue = stringRedisTemplate.opsForValue().get(redisKey);
Map<String, String> cookieMap = parseRedisCookieValue(redisValue);
String authExtRaw = genAuthExt(cookieMap);
String logintokenRaw = genLoginToken(cookieMap);
String authExtEncoded = urlEncode(authExtRaw);
String logintokenEncoded = urlEncode(logintokenRaw);
Map<String, String> out = new HashMap<>();
out.put("auth_ext_raw", authExtRaw);
out.put("auth_ext_encoded", authExtEncoded);
out.put("logintoken_raw", logintokenRaw);
out.put("logintoken_encoded", logintokenEncoded);
try {
out.put("cookie_map_json", objectMapper.writeValueAsString(cookieMap));
} catch (Exception e) {
out.put("cookie_map_json", cookieMap.toString());
}
return out;
}
/**
* 解析 Redis 中的值 —— 支持两种常见情况:
* 1) JSON 字符串(例如你直接把 Map->JSON 存进去);
* 2) Cookie 的原始字符串 "k=v; k2=v2; ..."
*/
private Map<String, String> parseRedisCookieValue(String redisValue) {
Map<String, String> map = new HashMap<>();
if (redisValue == null) return map;
String trimmed = redisValue.trim();
// 1) 如果像 JSON以 { 开头 和 } 结尾),尝试解析为 Map
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
(trimmed.startsWith("[") && trimmed.endsWith("]"))) {
try {
Map<String, Object> temp = objectMapper.readValue(trimmed, new TypeReference<Map<String,Object>>() {});
for (Map.Entry<String, Object> e : temp.entrySet()) {
map.put(e.getKey(), e.getValue() == null ? "" : String.valueOf(e.getValue()));
}
return map;
} catch (Exception ignored) {
// 解析失败 —— 退回到 cookie 规则解析
}
}
// 2) cookie 格式解析: "a=1; b=2; c=3"
String[] parts = redisValue.split(";");
for (String p : parts) {
if (p == null) continue;
String kv = p.trim();
if (kv.isEmpty()) continue;
int idx = kv.indexOf('=');
if (idx <= 0) continue;
String k = kv.substring(0, idx).trim();
String v = kv.substring(idx + 1).trim();
if (v.startsWith("\"") && v.endsWith("\"") && v.length() >= 2) {
v = v.substring(1, v.length() - 1);
}
map.put(k, v);
}
return map;
}
/**
* 生成 auth_ext raw JSON 字符串(未 encode
* 形如: {"main_login":"qq","vqq_openid":"...","vqq_appid":"...","vqq_access_token":"..."}
* 若 cookie 缺字段则使用空串
*/
private String genAuthExt(Map<String, String> cookie) {
Map<String, String> auth = new LinkedHashMap<>();
auth.put("main_login", cookie.getOrDefault("main_login", "qq"));
auth.put("vqq_openid", cookie.getOrDefault("vqq_openid", cookie.getOrDefault("openid", "")));
auth.put("vqq_appid", cookie.getOrDefault("vqq_appid", cookie.getOrDefault("appid", "")));
auth.put("vqq_access_token", cookie.getOrDefault("vqq_access_token", cookie.getOrDefault("access_token", "")));
try {
return objectMapper.writeValueAsString(auth);
} catch (Exception e) {
// 手工构造(极小概率)
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, String> en : auth.entrySet()) {
if (!first) sb.append(",");
sb.append("\"").append(escapeJson(en.getKey())).append("\":");
sb.append("\"").append(escapeJson(en.getValue())).append("\"");
first = false;
}
sb.append("}");
return sb.toString();
}
}
/**
* 生成 logintoken raw JSON 字符串(未 encode
* 常见字段来源于 cookievqq_access_token, vqq_appid, vqq_vusession/vqq_vsession, vqq_openid, vqq_vuserid, video_guid, main_login
*/
private String genLoginToken(Map<String, String> cookie) {
Map<String, String> t = new LinkedHashMap<>();
t.put("access_token", cookie.getOrDefault("vqq_access_token", cookie.getOrDefault("access_token", "")));
t.put("appid", cookie.getOrDefault("vqq_appid", cookie.getOrDefault("appid", "")));
// 多种可能命名,优先常见 vqq_vusession -> vqq_vusession -> vusession -> vusess
String vusession = cookie.getOrDefault("vqq_vusession",
cookie.getOrDefault("vqq_vsession",
cookie.getOrDefault("vusession", "")));
t.put("vusession", vusession);
t.put("openid", cookie.getOrDefault("vqq_openid", cookie.getOrDefault("openid", "")));
t.put("vuserid", cookie.getOrDefault("vqq_vuserid", cookie.getOrDefault("vuserid", "")));
t.put("video_guid", cookie.getOrDefault("video_guid", cookie.getOrDefault("qq_domain_video_guid_verify", "")));
t.put("main_login", cookie.getOrDefault("main_login", "qq"));
try {
return objectMapper.writeValueAsString(t);
} catch (Exception e) {
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, String> en : t.entrySet()) {
if (!first) sb.append(",");
sb.append("\"").append(escapeJson(en.getKey())).append("\":");
sb.append("\"").append(escapeJson(en.getValue())).append("\"");
first = false;
}
sb.append("}");
return sb.toString();
}
}
private String urlEncode(String s) {
if (s == null) return "";
return URLEncoder.encode(s, StandardCharsets.UTF_8);
}
// 简单 JSON 字符串转义(用于 fallback
private String escapeJson(String s) {
if (s == null) return "";
StringBuilder sb = new StringBuilder();
for (char ch : s.toCharArray()) {
switch (ch) {
case '"': sb.append("\\\""); break;
case '\\': sb.append("\\\\"); break;
case '\b': sb.append("\\b"); break;
case '\f': sb.append("\\f"); break;
case '\n': sb.append("\\n"); break;
case '\r': sb.append("\\r"); break;
case '\t': sb.append("\\t"); break;
default:
if (ch < 0x20) sb.append(String.format("\\u%04x", (int) ch));
else sb.append(ch);
}
}
return sb.toString();
}
}

View File

@@ -1,17 +1,63 @@
server:
servlet:
session:
cookie:
same-site: None
secure: true # 生产是 https 必须 true
port: 9005
#spring:
# data:
# redis:
# host: 167.253.156.235
# port: 6739
# password: redis_a7Gw6f
#spring:
# data:
# redis:
# host: 103.244.88.91
# port: 6379
# password: inspur123
# timeout: 5000ms
spring:
data:
redis:
host: 110.42.255.182
port: 6739
host: 116.62.173.2
port: 6379
password: redis_cfF356
timeout: 5000ms
application:
name: NBA
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://110.42.255.182:3306/NBA?useUnicode=true&characterEncoding=utf-8&useSSL=false
#mysql5.7.4配置
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/nba?useUnicode=true&characterEncoding=utf-8&useSSL=false
# url: jdbc:mysql://116.62.173.2:3306/nba?useUnicode=true&characterEncoding=utf-8&useSSL=false
# url: jdbc:mysql://154.36.154.211:9002/nba?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: inspur123
password: 123123321 #本地
# password: mysql_k2KzJj #阿里云杭州
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://103.244.88.91:3306/nba?useUnicode=true&characterEncoding=utf-8&useSSL=false
# username: nba
# password: d2mzG6w8277NpxFW
mail:
host: smtp.qq.com
port: 465
username: xdd9@vip.qq.com
password: qhcladjicfydbejj
default-encoding: UTF-8
properties:
mail.smtp.connectiontimeout: 5000 # 连接超时 5 秒
mail.smtp.timeout: 5000 # 读超时 5 秒
mail.smtp.writetimeout: 5000 # 写超时 5 秒
mail:
smtp:
auth: true
ssl:
enable: true
starttls:
enable: true
required: true
mybatis:
configuration:
map-underscore-to-camel-case: true

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ping.study.mapper.LoginLogMapper">
<resultMap id="BaseResultMap" type="com.ping.study.pojo.LoginLog">
<id property="id" column="id" />
<result property="userId" column="user_id" />
<result property="loginTime" column="login_time" />
<result property="loginIp" column="login_ip" />
</resultMap>
<sql id="Base_Column_List">
id,user_id,login_time,login_ip
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from login_log
where id = #{id}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete from login_log
where id = #{id}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.ping.study.pojo.LoginLog" useGeneratedKeys="true">
insert into login_log
( id,user_id,login_time,login_ip)
values (#{id},#{userId},#{loginTime},#{loginIp})
</insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.ping.study.pojo.LoginLog" useGeneratedKeys="true">
insert into login_log
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="userId != null">user_id,</if>
<if test="loginTime != null">login_time,</if>
<if test="loginIp != null">login_ip,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="userId != null">#{userId},</if>
<if test="loginTime != null">#{loginTime},</if>
<if test="loginIp != null">#{loginIp},</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.ping.study.pojo.LoginLog">
update login_log
<set>
<if test="userId != null">
user_id = #{userId},
</if>
<if test="loginTime != null">
login_time = #{loginTime},
</if>
<if test="loginIp != null">
login_ip = #{loginIp},
</if>
</set>
where id = #{id}
</update>
<update id="updateByPrimaryKey" parameterType="com.ping.study.pojo.LoginLog">
update login_log
set
user_id = #{userId},
login_time = #{loginTime},
login_ip = #{loginIp}
where id = #{id}
</update>
</mapper>

View File

@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ping.study.mapper.NbaUserMapper">
<resultMap id="BaseResultMap" type="com.ping.study.pojo.NbaUser">
<id property="id" column="id" />
<result property="username" column="username" />
<result property="password" column="password" />
<result property="email" column="email" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
<result property="lastLoginTime" column="last_login_time" />
<result property="lastLoginIp" column="last_login_ip" />
<result property="registerIp" column="register_ip" />
</resultMap>
<sql id="Base_Column_List">
id,username,password,email,create_time,update_time,
last_login_time,last_login_ip,register_ip
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from nba_user
where id = #{id}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete from nba_user
where id = #{id}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.ping.study.pojo.NbaUser" useGeneratedKeys="true">
insert into nba_user
( id,username,password,email,create_time,update_time,
last_login_time,last_login_ip,register_ip)
values (#{id},#{username},#{password},#{email},#{createTime},#{updateTime},
#{lastLoginTime},#{lastLoginIp},#{registerIp})
</insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.ping.study.pojo.NbaUser" useGeneratedKeys="true">
insert into nba_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="username != null">username,</if>
<if test="password != null">password,</if>
<if test="email != null">email,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
<if test="lastLoginTime != null">last_login_time,</if>
<if test="lastLoginIp != null">last_login_ip,</if>
<if test="registerIp != null">register_ip,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="username != null">#{username},</if>
<if test="password != null">#{password},</if>
<if test="email != null">#{email},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="lastLoginTime != null">#{lastLoginTime},</if>
<if test="lastLoginIp != null">#{lastLoginIp},</if>
<if test="registerIp != null">#{registerIp},</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.ping.study.pojo.NbaUser">
update nba_user
<set>
<if test="username != null">
username = #{username},
</if>
<if test="password != null">
password = #{password},
</if>
<if test="email != null">
email = #{email},
</if>
<if test="createTime != null">
create_time = #{createTime},
</if>
<if test="updateTime != null">
update_time = #{updateTime},
</if>
<if test="lastLoginTime != null">
last_login_time = #{lastLoginTime},
</if>
<if test="lastLoginIp != null">
last_login_ip = #{lastLoginIp},
</if>
<if test="registerIp != null">
register_ip = #{registerIp},
</if>
</set>
where id = #{id}
</update>
<update id="updateByPrimaryKey" parameterType="com.ping.study.pojo.NbaUser">
update nba_user
set
username = #{username},
password = #{password},
email = #{email},
create_time = #{createTime},
update_time = #{updateTime},
last_login_time = #{lastLoginTime},
last_login_ip = #{lastLoginIp},
register_ip = #{registerIp}
where id = #{id}
</update>
<select id="login" resultType="com.ping.study.pojo.NbaUser">
select * from nba_user where email = #{email} and password = #{password}
</select>
<update id="updateRememberToken">
update nba_user set remember_token = #{token} where id = #{userId}
</update>
<select id="selectByRememberToken" resultType="NbaUser">
select * from nba_user where remember_token = #{token} limit 1
</select>
<update id="clearRememberToken">
update nba_user set remember_token = null where remember_token = #{token}
</update>
<select id="findByEmail" resultType="NbaUser">
select * from nba_user where email = #{email}
</select>
<select id="selectByEmail" resultType="NbaUser">
select * from nba_user where email = #{email}
</select>
<update id="updatePasswordByEmail">
update nba_user set password = #{password} where email = #{email}
</update>
</mapper>

View File

@@ -77,12 +77,16 @@
</select>
<select id="selectUrlsListByGameId" resultType="map">
select urls.id,urls.type,urls.url from urls where game_id = #{gameId}
select urls.id,urls.type from urls where game_id = #{gameId}
</select>
<select id="selectUrlsListByAdmin" resultType="map">
select urls.id,urls.type,urls.m3u8_url from urls where game_id = #{gameId}
</select>
<insert id="insertUrlsWithGameId">
insert into urls
(game_id, url, type)
(game_id, m3u8_url, type)
values
<foreach collection="list" item="item" separator=",">
(#{gameId}, #{item.url}, #{item.type})
@@ -96,6 +100,22 @@
</delete>
<update id="updateUrlsWithGameId" parameterType="map">
update urls set url = #{s},type = 'tx' where game_id = #{gameId}
update urls set url = #{s} where game_id = #{gameId} and type = #{type}
</update>
<!-- <select id="findUrlByGameIdAndType" resultType="string">-->
<!-- select url from urls where game_id = #{gameId} and type = #{type}-->
<!-- </select>-->
<select id="selectUrlsListByGameIdAndType" resultType="string">
select m3u8_url from urls where game_id = #{gameId} and type = #{type}
</select>
<update id="updateStatusWithGameId">
update urls set status = #{status} where game_id = #{gameId}
</update>
<update id="updateUrlsById">
update urls set m3u8_url = #{url} where id = #{id}
</update>
</mapper>