-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
4,049 additions
and
3,541 deletions.
There are no files selected for viewing
3,646 changes: 146 additions & 3,500 deletions
3,646
src/main/java/com/htmake/reader/api/YueduApi.kt
Large diffs are not rendered by default.
Oops, something went wrong.
340 changes: 340 additions & 0 deletions
340
src/main/java/com/htmake/reader/api/controller/BaseController.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,340 @@ | ||
package com.htmake.reader.api.controller | ||
|
||
import io.legado.app.data.entities.Book | ||
import io.legado.app.data.entities.BookChapter | ||
import io.legado.app.data.entities.SearchBook | ||
import io.legado.app.data.entities.BookGroup | ||
import io.legado.app.data.entities.BookSource | ||
import io.legado.app.data.entities.RssSource | ||
import io.legado.app.data.entities.RssArticle | ||
import io.legado.app.help.storage.OldRule | ||
import io.legado.app.model.webBook.WebBook | ||
import io.vertx.ext.web.Route | ||
import io.vertx.ext.web.Router | ||
import io.vertx.ext.web.RoutingContext | ||
import io.vertx.ext.web.handler.StaticHandler; | ||
import mu.KotlinLogging | ||
import com.htmake.reader.config.AppConfig | ||
import com.htmake.reader.config.BookConfig | ||
import io.legado.app.constant.DeepinkBookSource | ||
import com.htmake.reader.utils.error | ||
import com.htmake.reader.utils.success | ||
import com.htmake.reader.utils.getStorage | ||
import com.htmake.reader.utils.saveStorage | ||
import com.htmake.reader.utils.asJsonArray | ||
import com.htmake.reader.utils.asJsonObject | ||
import com.htmake.reader.utils.toDataClass | ||
import com.htmake.reader.utils.toMap | ||
import com.htmake.reader.utils.fillData | ||
import com.htmake.reader.utils.getWorkDir | ||
import com.htmake.reader.utils.getRandomString | ||
import com.htmake.reader.utils.genEncryptedPassword | ||
import com.htmake.reader.entity.User | ||
import com.htmake.reader.utils.SpringContextUtils | ||
import com.htmake.reader.utils.deleteRecursively | ||
import com.htmake.reader.utils.unzip | ||
import com.htmake.reader.utils.zip | ||
import com.htmake.reader.utils.jsonEncode | ||
import com.htmake.reader.utils.getRelativePath | ||
import com.htmake.reader.verticle.RestVerticle | ||
import com.htmake.reader.SpringEvent | ||
import org.springframework.stereotype.Component | ||
import io.vertx.core.json.JsonObject | ||
import io.vertx.core.json.JsonArray | ||
import io.vertx.core.http.HttpMethod | ||
import com.htmake.reader.api.ReturnData | ||
import io.legado.app.utils.MD5Utils | ||
import java.net.URLDecoder; | ||
import java.net.URLEncoder; | ||
import java.net.URL; | ||
import java.util.UUID; | ||
import io.vertx.ext.web.client.WebClient | ||
import org.springframework.beans.factory.annotation.Autowired | ||
import org.springframework.core.env.Environment | ||
import java.io.File | ||
import java.lang.Runtime | ||
import kotlin.collections.mutableMapOf | ||
import kotlin.system.measureTimeMillis | ||
import kotlin.coroutines.CoroutineContext | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.launch | ||
import java.text.SimpleDateFormat; | ||
import io.legado.app.utils.EncoderUtils | ||
import io.legado.app.model.rss.Rss | ||
import org.springframework.scheduling.annotation.Scheduled | ||
import io.legado.app.localBook.LocalBook | ||
import java.nio.file.Paths | ||
import kotlinx.coroutines.withContext | ||
import kotlinx.coroutines.async | ||
import kotlinx.coroutines.Deferred | ||
import kotlinx.coroutines.CoroutineScope | ||
|
||
private val logger = KotlinLogging.logger {} | ||
|
||
open class BaseController(override val coroutineContext: CoroutineContext): CoroutineScope { | ||
var loginExpireDays = 7 | ||
|
||
val appConfig: AppConfig | ||
val env: Environment | ||
|
||
init { | ||
appConfig = SpringContextUtils.getBean("appConfig", AppConfig::class.java) | ||
env = SpringContextUtils.getBean(Environment::class.java) | ||
} | ||
|
||
suspend fun saveUserSession(context: RoutingContext, userMap: MutableMap<String, Map<String, Any>>, user: User, regenerateToken: Boolean = true): Map<String, Any> { | ||
user.last_login_at = System.currentTimeMillis() | ||
if (regenerateToken) { | ||
user.token = genEncryptedPassword(user.username, System.currentTimeMillis().toString()) | ||
var tokenMap: MutableMap<String, Long>? = null | ||
var expire = System.currentTimeMillis() + loginExpireDays * 86400 * 1000 | ||
if (user.token_map != null) { | ||
tokenMap = user.token_map as? MutableMap<String, Long> | ||
} | ||
if (tokenMap == null) { | ||
tokenMap = mutableMapOf(user.token to expire) | ||
} else { | ||
tokenMap.put(user.token, expire) | ||
} | ||
// 删除已过期token | ||
tokenMap.values.removeAll { it < user.last_login_at } | ||
user.token_map = tokenMap | ||
} | ||
userMap.put(user.username, user.toMap()) | ||
saveStorage("data", "users", value = userMap) | ||
|
||
val loginData = formatUser(user) | ||
|
||
context.session().put("username", user.username) | ||
context.put("username", user.username) | ||
|
||
return loginData | ||
} | ||
|
||
suspend fun checkAuth(context: RoutingContext): Boolean { | ||
if (!appConfig.secure) { | ||
return true | ||
} | ||
var username = context.session().get("username") as String? ?: "" | ||
var userInfo = getUserInfoClass(username) | ||
if (userInfo != null) { | ||
context.put("username", userInfo.username) | ||
context.put("userInfo", userInfo) | ||
return true | ||
} | ||
// 自动登录 | ||
var accessToken = context.queryParam("accessToken").firstOrNull() ?: "" | ||
if (accessToken.isNotEmpty()) { | ||
var userMap = mutableMapOf<String, Map<String, Any>>() | ||
var userMapJson: JsonObject? = asJsonObject(getStorage("data", "users")) | ||
if (userMapJson != null) { | ||
userMap = userMapJson.map as? MutableMap<String, Map<String, Any>> ?: mutableMapOf<String, Map<String, Any>>() | ||
} | ||
var tmp = accessToken.split(":", limit=2) | ||
if (tmp.size >= 2) { | ||
var _username = tmp[0] | ||
var token = tmp[1] | ||
var existedUser: User? = userMap.getOrDefault(_username, null)?.toDataClass() | ||
if (existedUser != null && token.isNotEmpty()) { | ||
var isLogin = false | ||
if (existedUser.token.isNotEmpty() && existedUser.token.equals(token)) { | ||
isLogin = true | ||
} | ||
// 查找历史有效会话 | ||
if (!isLogin && existedUser.token_map != null) { | ||
var tokenMap = existedUser.token_map as? MutableMap<String, Long> | ||
if (tokenMap != null && | ||
tokenMap.containsKey(token)) { | ||
if (tokenMap.getOrDefault(token, 0L) > System.currentTimeMillis()) { | ||
isLogin = true | ||
// 延长有效期 | ||
tokenMap.put(token, System.currentTimeMillis() + loginExpireDays * 86400 * 1000) | ||
} else { | ||
// 删除过期token | ||
tokenMap.remove(token) | ||
} | ||
existedUser.token_map = tokenMap | ||
} | ||
} | ||
if (isLogin) { | ||
// 保存用户session | ||
saveUserSession(context, userMap, existedUser, false) | ||
context.put("username", existedUser.username) | ||
context.put("userInfo", existedUser) | ||
} | ||
return isLogin | ||
} | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
fun checkManagerAuth(context: RoutingContext): Boolean { | ||
if (!appConfig.secure) { | ||
return true | ||
} | ||
if (appConfig.secureKey.isEmpty()) { | ||
return true | ||
} | ||
var secureKey = context.queryParam("secureKey").firstOrNull() ?: "" | ||
if (secureKey.equals(appConfig.secureKey)) { | ||
// 判断是否需要修改 userNameSpace | ||
var userNS = context.queryParam("userNS").firstOrNull() | ||
if (userNS != null && userNS.isNotEmpty()) { | ||
context.put("userNameSpace", userNS) | ||
} | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
fun getUserNameSpace(context: RoutingContext): String { | ||
if (!appConfig.secure) { | ||
return "default" | ||
} | ||
// 管理权限,可以修改 userNameSpace 来获取任意用户信息 | ||
checkManagerAuth(context) | ||
var userNS = context.get("userNameSpace") as String? | ||
if (userNS != null && userNS.isNotEmpty()) { | ||
return userNS | ||
} | ||
var username = context.get("username") as String? | ||
if (username != null) { | ||
return username; | ||
} | ||
return "default" | ||
} | ||
|
||
fun getUserStorage(context: Any, vararg path: String): String? { | ||
var userNameSpace = "" | ||
when(context) { | ||
is RoutingContext -> userNameSpace = getUserNameSpace(context) | ||
is String -> userNameSpace = context | ||
} | ||
if (userNameSpace.isEmpty()) { | ||
return getStorage("data", *path) | ||
} | ||
return getStorage("data", userNameSpace, *path) | ||
} | ||
|
||
fun saveUserStorage(context: Any, path: String, value: Any) { | ||
var userNameSpace = "" | ||
when(context) { | ||
is RoutingContext -> userNameSpace = getUserNameSpace(context) | ||
is String -> userNameSpace = context | ||
} | ||
if (userNameSpace.isEmpty()) { | ||
return saveStorage("data", path, value = value) | ||
} | ||
return saveStorage("data", userNameSpace, path, value = value) | ||
} | ||
|
||
fun getUserInfoClass(username: String): User? { | ||
var user: User? = getUserInfoMap(username)?.toDataClass() | ||
return user | ||
} | ||
|
||
fun getUserInfoMap(username: String): Map<String, Any>? { | ||
if (username.isEmpty()) { | ||
return null | ||
} | ||
var userMap = mutableMapOf<String, Map<String, Any>>() | ||
var userMapJson: JsonObject? = asJsonObject(getStorage("data", "users")) | ||
if (userMapJson != null) { | ||
userMap = userMapJson.map as MutableMap<String, Map<String, Any>> | ||
} | ||
return userMap.getOrDefault(username, null) | ||
} | ||
|
||
fun formatUser(userInfo: Any): MutableMap<String, Any> { | ||
var user: User? = null | ||
if (userInfo !is User) { | ||
var userMap = userInfo as? Map<String, Any> | ||
if (userMap != null) { | ||
user = userMap.toDataClass() | ||
} | ||
} else { | ||
user = userInfo | ||
} | ||
if (user == null) { | ||
return mutableMapOf() | ||
} | ||
return mutableMapOf( | ||
"username" to user.username, | ||
"lastLoginAt" to user.last_login_at, | ||
"accessToken" to user.username + ":" + user.token, | ||
"enableWebdav" to user.enable_webdav, | ||
"createdAt" to user.created_at | ||
) | ||
} | ||
|
||
fun getUserWebdavHome(context: Any): String { | ||
var prefix = getWorkDir("storage", "data") | ||
var userNameSpace = "" | ||
when(context) { | ||
is RoutingContext -> userNameSpace = getUserNameSpace(context) | ||
is String -> userNameSpace = context | ||
} | ||
if (userNameSpace.isNotEmpty()) { | ||
prefix = prefix + File.separator + userNameSpace | ||
} | ||
prefix = prefix + File.separator + "webdav" | ||
var file = File(prefix) | ||
if (!file.exists()) { | ||
file.mkdirs() | ||
} | ||
return prefix | ||
} | ||
|
||
fun getFileExt(url: String, defaultExt: String=""): String { | ||
try { | ||
var seqs = url.split("?", ignoreCase = true, limit = 2) | ||
var file = seqs[0].split("/").last() | ||
return file.split(".", ignoreCase = true, limit = 2).last()?.toLowerCase() | ||
} catch (e: Exception) { | ||
return defaultExt | ||
} | ||
} | ||
|
||
suspend fun limitConcurrent(concurrentCount: Int, startIndex: Int, endIndex: Int, handler: suspend CoroutineScope.(Int) -> Any) { | ||
limitConcurrent(concurrentCount, startIndex, endIndex, handler) {_, _ -> | ||
true | ||
} | ||
} | ||
|
||
suspend fun limitConcurrent(concurrentCount: Int, startIndex: Int, endIndex: Int, handler: suspend CoroutineScope.(Int) -> Any, needContinue: (ArrayList<Any>, Int) -> Boolean) { | ||
var lastIndex = startIndex | ||
var loopCount = 0; | ||
while(true) { | ||
var deferredList = arrayListOf<Deferred<Any>>() | ||
var croutineCount = 0; | ||
for(i in lastIndex until endIndex) { | ||
croutineCount += 1; | ||
deferredList.add(async { | ||
handler(i) | ||
}) | ||
|
||
lastIndex = i | ||
if (croutineCount >= concurrentCount) { | ||
break; | ||
} | ||
} | ||
val resultList = arrayListOf<Any>() | ||
val costTime = measureTimeMillis { | ||
for (i in 0 until deferredList.size) { | ||
resultList.add(deferredList.get(i).await()) | ||
} | ||
} | ||
loopCount += 1; | ||
logger.info("Loop: {} concurrentCount: {} lastIndex: {} costTime: {} ms", loopCount, croutineCount, lastIndex, costTime) | ||
if (lastIndex >= endIndex) { | ||
break; | ||
} | ||
if (!needContinue(resultList, loopCount)) { | ||
break; | ||
} | ||
lastIndex = lastIndex + 1 | ||
} | ||
} | ||
} |
Oops, something went wrong.