Skip to content

Commit 819b887

Browse files
committed
JTools v3.20.0
功能变动: - 支持配置 JKit 简书数据源 Endpoint - 优化后端服务中的数据库连接池管理策略 - 使用 `Literal` 替代 `Enum` - 应用数据库表结构变动 - 应用最新 linting 与 formatting 规则 - 更新 .gitignore - 更新 LICENSE 年份 依赖变动: - 升级到 JKit v3.0.0b2 - 升级到 sshared v0.21.0 - 升级到 Caddy v2.9 - 升级到 Ruff v0.9.0 - 更新依赖库
2 parents b27150f + 6405e38 commit 819b887

29 files changed

+707
-605
lines changed

.gitignore

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
**/node_modules
2-
**/dist
3-
**/dev-dist
1+
.ruff_cache
2+
.venv
43
**/__pycache__
4+
**/node_modules
5+
56
config.toml

Dockerfile.frontend

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ RUN bun install --prod --frozen-lockfile
88
COPY frontend .
99
RUN bun run build
1010

11-
FROM caddy:2.8-alpine
11+
FROM caddy:2.9-alpine
1212

1313
WORKDIR /app
1414

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2024 叶子
3+
Copyright (c) 2025 叶子
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

backend/api/v1/articles.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
from __future__ import annotations
2+
13
from collections import Counter
24
from datetime import date, datetime, timedelta
3-
from typing import Annotated, Optional
5+
from typing import Annotated
46

57
from jkit.article import Article
8+
from jkit.config import CONFIG as JKIT_CONFIG
69
from jkit.constants import ARTICLE_SLUG_REGEX
710
from jkit.exceptions import ResourceUnavailableError
811
from litestar import Response, Router, get
@@ -25,13 +28,16 @@
2528
)
2629
from utils.config import CONFIG
2730

31+
if CONFIG.jianshu_endpoint:
32+
JKIT_CONFIG.datasources.jianshu.endpoint = CONFIG.jianshu_endpoint
33+
2834
splitter = WordSplitter(
2935
access_key_id=CONFIG.word_split_access_key.access_key_id,
3036
access_key_secret=CONFIG.word_split_access_key.access_key_secret,
3137
)
3238

3339

34-
async def get_earliest_can_recommend_date(author_slug: str) -> Optional[date]:
40+
async def get_earliest_can_recommend_date(author_slug: str) -> date | None:
3541
counted_article_slugs = set()
3642

3743
latest_onrank_record = await ArticleEarningRankingRecord.get_latest_record(
@@ -40,7 +46,7 @@ async def get_earliest_can_recommend_date(author_slug: str) -> Optional[date]:
4046
if not latest_onrank_record:
4147
return None
4248

43-
interval_days = 10 if latest_onrank_record.ranking <= 30 else 7
49+
interval_days = 10 if latest_onrank_record.ranking <= 30 else 7 # noqa: PLR2004
4450
counted_article_slugs.add(latest_onrank_record.slug)
4551

4652
now_record = latest_onrank_record
@@ -57,15 +63,15 @@ async def get_earliest_can_recommend_date(author_slug: str) -> Optional[date]:
5763
counted_article_slugs.add(pervious_record.slug)
5864

5965
if (
60-
now_record.ranking <= 30
61-
and (now_record.date - pervious_record.date).days + 1 >= 10
66+
now_record.ranking <= 30 # noqa: PLR2004
67+
and (now_record.date - pervious_record.date).days + 1 >= 10 # noqa: PLR2004
6268
) or (
63-
now_record.ranking > 30
64-
and (now_record.date - pervious_record.date).days + 1 >= 7
69+
now_record.ranking > 30 # noqa: PLR2004
70+
and (now_record.date - pervious_record.date).days + 1 >= 7 # noqa: PLR2004
6571
):
6672
return latest_onrank_record.date + timedelta(days=interval_days)
6773

68-
if pervious_record.ranking <= 30:
74+
if pervious_record.ranking <= 30: # noqa: PLR2004
6975
interval_days += 10
7076
else:
7177
interval_days += 7
@@ -119,7 +125,7 @@ async def get_word_freq_handler(
119125

120126
article_info = await article.info
121127
title = article_info.title
122-
text = article_info.text_content
128+
text = article_info.content_text
123129

124130
word_freq = dict(Counter(await splitter.split(text)).most_common(100))
125131

@@ -135,7 +141,7 @@ class GetLPRecommendCheckResponse(Struct, **RESPONSE_STRUCT_CONFIG):
135141
article_title: str
136142
can_recommend_now: bool
137143
FP_reward: float = field(name="FPReward")
138-
next_can_recommend_date: Optional[datetime]
144+
next_can_recommend_date: datetime | None
139145

140146

141147
@get(
@@ -184,7 +190,7 @@ async def get_LP_recommend_check_handler( # noqa: N802
184190
article_fp_reward = article_info.earned_fp_amount
185191
article_next_can_recommend_date = await get_earliest_can_recommend_date(author_slug)
186192

187-
can_recommend_now = article_fp_reward < 35 and (
193+
can_recommend_now = article_fp_reward < 35 and ( # noqa: PLR2004
188194
not article_next_can_recommend_date
189195
or article_next_can_recommend_date <= datetime.now().date()
190196
)

backend/api/v1/jpep/ftn_macket.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
from __future__ import annotations
2+
13
from asyncio import gather
24
from datetime import datetime
3-
from typing import Annotated, Literal, Optional
5+
from typing import Annotated, Literal
46

5-
from jkit.jpep.platform_settings import PlatformSettings
7+
from jkit.jpep.rules import Rules
68
from litestar import Response, Router, get
79
from litestar.params import Parameter
810
from msgspec import Struct, field
9-
from sshared.time import get_datetime_before_now, parse_td_str
11+
from sshared.time import get_past_datetime_from_now, parse_td_str
1012
from sspeedup.api.litestar import (
1113
RESPONSE_STRUCT_CONFIG,
1214
generate_response_spec,
@@ -21,7 +23,7 @@
2123
"1d": "day",
2224
}
2325

24-
PLATFORM_SETTINGS = PlatformSettings()
26+
RULES = Rules()
2527

2628

2729
class GetRulesResponse(Struct, **RESPONSE_STRUCT_CONFIG):
@@ -40,23 +42,23 @@ class GetRulesResponse(Struct, **RESPONSE_STRUCT_CONFIG):
4042
},
4143
)
4244
async def get_rules_handler() -> Response:
43-
settings = await PLATFORM_SETTINGS.get_data()
45+
rules = await RULES.get_rules()
4446

4547
return success(
4648
data=GetRulesResponse(
47-
is_open=settings.opening,
49+
is_open=rules.opening,
4850
# TODO
49-
buy_order_minimum_price=settings.ftn_sell_trade_minimum_price,
50-
sell_order_minimum_price=settings.ftn_buy_trade_minimum_price,
51-
FTN_order_fee=settings.ftn_trade_fee,
52-
goods_order_fee=settings.goods_trade_fee,
51+
buy_order_minimum_price=rules.ftn_buy_trade_minimum_price,
52+
sell_order_minimum_price=rules.ftn_buy_trade_minimum_price,
53+
FTN_order_fee=rules.ftn_trade_fee,
54+
goods_order_fee=rules.goods_trade_fee,
5355
)
5456
)
5557

5658

5759
class GetCurrentPriceResponse(Struct, **RESPONSE_STRUCT_CONFIG):
58-
buy_price: Optional[float]
59-
sell_price: Optional[float]
60+
buy_price: float | None
61+
sell_price: float | None
6062

6163

6264
@get(
@@ -81,8 +83,8 @@ async def get_current_price_handler() -> Response:
8183

8284

8385
class GetCurrentAmountResponse(Struct, **RESPONSE_STRUCT_CONFIG):
84-
buy_amount: Optional[int]
85-
sell_amount: Optional[int]
86+
buy_amount: int | None
87+
sell_amount: int | None
8688

8789

8890
@get(
@@ -121,14 +123,14 @@ async def get_price_history_handler(
121123
type_: Annotated[
122124
Literal["buy", "sell"], Parameter(description="交易单类型", query="type")
123125
],
124-
range: Annotated[ # noqa: A002
126+
range: Annotated[
125127
Literal["24h", "7d", "15d", "30d"], Parameter(description="时间范围")
126128
],
127129
resolution: Annotated[Literal["5m", "1h", "1d"], Parameter(description="统计粒度")],
128130
) -> Response:
129131
history = await FTNMacketRecord.get_price_history(
130132
type=type_.upper(), # type: ignore
131-
start_time=get_datetime_before_now(parse_td_str(range)),
133+
start_time=get_past_datetime_from_now(parse_td_str(range)),
132134
resolution=RESOLUTION_MAPPING[resolution],
133135
)
134136

@@ -154,14 +156,14 @@ async def get_amount_history_handler(
154156
type_: Annotated[
155157
Literal["buy", "sell"], Parameter(description="交易单类型", query="type")
156158
],
157-
range: Annotated[ # noqa: A002
159+
range: Annotated[
158160
Literal["24h", "7d", "15d", "30d"], Parameter(description="时间范围")
159161
],
160162
resolution: Annotated[Literal["5m", "1h", "1d"], Parameter(description="统计粒度")],
161163
) -> Response:
162164
history = await FTNMacketRecord.get_amount_history(
163165
type=type_.upper(), # type: ignore
164-
start_time=get_datetime_before_now(parse_td_str(range)),
166+
start_time=get_past_datetime_from_now(parse_td_str(range)),
165167
resolution=RESOLUTION_MAPPING[resolution],
166168
)
167169

backend/api/v1/lottery.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
from __future__ import annotations
2+
13
from asyncio import gather
24
from datetime import datetime, timedelta
3-
from typing import Annotated, Literal, Optional
5+
from typing import Annotated, Literal
46

57
from jkit.identifier_convert import user_slug_to_url
68
from litestar import Response, Router, get
@@ -25,7 +27,7 @@
2527
"锦鲤头像框1年",
2628
]
2729

28-
RANGE_TO_TIMEDELTA: dict[str, Optional[timedelta]] = {
30+
RANGE_TO_TIMEDELTA: dict[str, timedelta | None] = {
2931
"1d": timedelta(days=1),
3032
"7d": timedelta(days=7),
3133
"30d": timedelta(days=30),
@@ -37,17 +39,13 @@
3739
def get_summary_average_wins_count_per_winner(
3840
wins_count: dict[str, int], winners_count: dict[str, int]
3941
) -> dict[str, float]:
40-
result: dict[str, float] = {}
42+
result: dict[str, float] = dict.fromkeys(wins_count.keys(), 0.0)
4143

42-
for reward_name in wins_count:
43-
# 该奖项无人中奖
44-
if wins_count[reward_name] == 0:
45-
result[reward_name] = 0
44+
for reward_name, wins in wins_count.items():
45+
if winners_count[reward_name] == 0:
4646
continue
4747

48-
result[reward_name] = round(
49-
wins_count[reward_name] / winners_count[reward_name], 3
50-
)
48+
result[reward_name] = round(wins / winners_count[reward_name], 3)
5149

5250
return result
5351

@@ -115,7 +113,7 @@ async def get_records_handler(
115113
offset: Annotated[int, Parameter(description="分页偏移", ge=0)] = 0,
116114
limit: Annotated[int, Parameter(description="结果数量", gt=0, lt=100)] = 20,
117115
excluded_awards: Annotated[
118-
Optional[list[str]], Parameter(description="排除奖项列表", max_items=10)
116+
list[str] | None, Parameter(description="排除奖项列表", max_items=10)
119117
] = None,
120118
) -> Response:
121119
records: list[GetRecordsItem] = []
@@ -168,7 +166,7 @@ class GetSummaryResponse(Struct, **RESPONSE_STRUCT_CONFIG):
168166
},
169167
)
170168
async def get_summary_handler(
171-
range: Annotated[ # noqa: A002
169+
range: Annotated[
172170
Literal["1d", "7d", "30d", "all"], Parameter(description="时间范围")
173171
],
174172
) -> Response:
@@ -216,7 +214,7 @@ class GetRewardWinsHistoryResponse(Struct, **RESPONSE_STRUCT_CONFIG):
216214
},
217215
)
218216
async def get_reward_wins_history_handler(
219-
range: Annotated[Literal["1d", "30d", "60d"], Parameter(description="时间范围")], # noqa: A002
217+
range: Annotated[Literal["1d", "30d", "60d"], Parameter(description="时间范围")],
220218
resolution: Annotated[Literal["1h", "1d"], Parameter(description="统计粒度")],
221219
) -> Response:
222220
history = await LotteryWinRecord.get_wins_history(

backend/api/v1/status.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
from __future__ import annotations
2+
13
from datetime import datetime
2-
from typing import Annotated, Optional
4+
from typing import Annotated
35

46
from litestar import Response, Router, get
57
from litestar.params import Parameter
@@ -13,7 +15,7 @@
1315
success,
1416
)
1517

16-
from models.tool import StatusEnum, Tool
18+
from models.tool import StatusType, Tool
1719
from utils.config import CONFIG
1820
from utils.tools_status import (
1921
get_data_count,
@@ -39,23 +41,19 @@ async def get_handler() -> Response:
3941
return success(
4042
data=GetResponse(
4143
version=VERSION,
42-
downgraded_tools=list(
43-
await Tool.get_tools_slugs_by_status(StatusEnum.DOWNGRADED)
44-
),
45-
unavailable_tools=list(
46-
await Tool.get_tools_slugs_by_status(StatusEnum.UNAVAILABLE)
47-
),
44+
downgraded_tools=list(await Tool.get_tools_slugs_by_status("DOWNGRADED")),
45+
unavailable_tools=list(await Tool.get_tools_slugs_by_status("UNAVAILABLE")),
4846
)
4947
)
5048

5149

5250
class GetToolStatusResponse(Struct, **RESPONSE_STRUCT_CONFIG):
53-
status: StatusEnum
54-
reason: Optional[str]
55-
last_update_time: Optional[datetime]
56-
data_update_freq: Optional[str]
57-
data_count: Optional[int]
58-
data_source: Optional[dict[str, str]]
51+
status: StatusType
52+
reason: str | None
53+
last_update_time: datetime | None
54+
data_update_freq: str | None
55+
data_count: int | None
56+
data_source: dict[str, str] | None
5957

6058

6159
@get(
@@ -83,15 +81,15 @@ async def get_tool_status_handler(
8381
# 处理未填写 word_split_access_key 配置项的情况
8482
if (
8583
tool_name == "article-wordcloud-generator"
86-
and tool.status == StatusEnum.NORMAL.value
84+
and tool.status == "NORMAL"
8785
and not (
8886
CONFIG.word_split_access_key.access_key_id
8987
and CONFIG.word_split_access_key.access_key_secret
9088
)
9189
):
9290
return success(
9391
data=GetToolStatusResponse(
94-
status=StatusEnum.UNAVAILABLE,
92+
status="UNAVAILABLE",
9593
reason="后端未设置分词服务凭据",
9694
last_update_time=last_update_time,
9795
data_update_freq=tool.data_update_freq,

0 commit comments

Comments
 (0)