This commit is contained in:
qaq
2025-08-15 12:25:06 +08:00
parent 23ad5ef68b
commit 6f80e26a8f
32 changed files with 98 additions and 32 deletions
+2 -1
View File
@@ -94,7 +94,8 @@ CREATE TABLE `statistics_flow` (
`user_id` int(10) NOT NULL,
`flow` bigint(20) NOT NULL,
`total_flow` bigint(20) NOT NULL,
`time` varchar(100) NOT NULL
`time` varchar(100) NOT NULL,
`created_time` bigint(20) NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
+24
View File
@@ -809,9 +809,33 @@ CREATE TABLE IF NOT EXISTS \`statistics_flow\` (
\`flow\` bigint(20) NOT NULL,
\`total_flow\` bigint(20) NOT NULL,
\`time\` varchar(100) NOT NULL,
\`created_time\` bigint(20) NOT NULL,
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- statistics_flow 表:添加 created_time 字段(如果不存在)
SET @sql = (
SELECT IF(
NOT EXISTS (
SELECT 1
FROM information_schema.COLUMNS
WHERE table_schema = DATABASE()
AND table_name = 'statistics_flow'
AND column_name = 'created_time'
),
'ALTER TABLE \`statistics_flow\` ADD COLUMN \`created_time\` BIGINT(20) NOT NULL DEFAULT 0 COMMENT "创建时间毫秒时间戳";',
'SELECT "Column \`created_time\` already exists in \`statistics_flow\`";'
)
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 为现有记录设置当前毫秒时间戳(仅当 created_time 为 0 或 NULL 时)
UPDATE \`statistics_flow\`
SET \`created_time\` = UNIX_TIMESTAMP() * 1000
WHERE \`created_time\` = 0 OR \`created_time\` IS NULL;
@@ -16,6 +16,7 @@ import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Slf4j
@@ -33,6 +34,19 @@ public class StatisticsFlowAsync {
public void statistics_flow() {
LocalDateTime currentHour = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0);
String hourString = currentHour.format(DateTimeFormatter.ofPattern("HH:mm"));
long time = new Date().getTime();
// 删除48小时前的数据
long nowMs = System.currentTimeMillis();
long cutoffMs = nowMs - 48L * 60 * 60 * 1000;
statisticsFlowService.remove(
new LambdaQueryWrapper<StatisticsFlow>()
.lt(StatisticsFlow::getCreateTime, cutoffMs)
);
List<User> list = userService.list();
List<StatisticsFlow> statisticsFlowList = new ArrayList<>();
@@ -65,6 +79,7 @@ public class StatisticsFlowAsync {
statisticsFlow.setFlow(incrementFlow);
statisticsFlow.setTotalFlow(currentTotalFlow);
statisticsFlow.setTime(hourString);
statisticsFlow.setCreateTime(time);
statisticsFlowList.add(statisticsFlow);
}
@@ -4,11 +4,15 @@ import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.resource.FontCache;
import cloud.tianai.captcha.resource.ResourceStore;
import cloud.tianai.captcha.resource.common.model.dto.Resource;
import cloud.tianai.captcha.resource.common.model.dto.ResourceMap;
import cloud.tianai.captcha.resource.impl.provider.ClassPathResourceProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import static cloud.tianai.captcha.common.constant.CommonConstant.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH;
@Component
@RequiredArgsConstructor
public class CaptchaResourceConfiguration {
@@ -22,16 +26,24 @@ public class CaptchaResourceConfiguration {
@PostConstruct
public void init() {
// 添加自定义背景图片
for (int i = 1; i <= 10; i++) {
ResourceMap template = new ResourceMap("default", 4);
template.put("active.png", new Resource(ClassPathResourceProvider.NAME, "slide/" + i + "/1.png"));
template.put("fixed.png", new Resource(ClassPathResourceProvider.NAME, "slide/" + i + "/2.png"));
resourceStore.addTemplate(CaptchaTypeConstant.SLIDER, template);
}
// 添加自定义背景图片
for (String option : OPTIONS) {
for (int i = 1; i <= 26; i++) {
resourceStore.addResource(option, new Resource("classpath", "bgimages/"+i + ".jpg", "default"));
resourceStore.addResource(option, new Resource("classpath", "bgimages/" + i + ".jpg", "default"));
}
}
//添加自定义字体
resourceStore.addResource(FontCache.FONT_TYPE,new Resource("classpath", "fonts/SIMSUN.TTC", "default"));
resourceStore.addResource(FontCache.FONT_TYPE, new Resource("classpath", "fonts/SIMSUN.TTC", "default"));
}
}
@@ -28,5 +28,7 @@ public class StatisticsFlow {
private String time;
private Long createTime;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

+1 -1
View File
@@ -4,7 +4,7 @@ export type SiteConfig = typeof siteConfig;
// 缓存相关常量
const CACHE_PREFIX = 'vite_config_';
const VERSION = "1.2.8";
const VERSION = "1.2.9";
const getInitialConfig = () => {
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+9 -2
View File
@@ -63,6 +63,7 @@ export default function DashboardPage() {
const [userTunnels, setUserTunnels] = useState<UserTunnel[]>([]);
const [forwardList, setForwardList] = useState<Forward[]>([]);
const [statisticsFlows, setStatisticsFlows] = useState<StatisticsFlow[]>([]);
const [isAdmin, setIsAdmin] = useState(false);
const [addressModalOpen, setAddressModalOpen] = useState(false);
const [addressModalTitle, setAddressModalTitle] = useState('');
@@ -166,6 +167,10 @@ export default function DashboardPage() {
setForwardList([]);
setStatisticsFlows([]);
// 检查用户是否是管理员
const adminStatus = localStorage.getItem('admin');
setIsAdmin(adminStatus === 'true');
loadPackageData();
localStorage.setItem('e', '/dashboard');
}, []);
@@ -751,8 +756,9 @@ export default function DashboardPage() {
</CardBody>
</Card>
{/* 隧道权限 */}
<Card className="mb-6 lg:mb-8 border border-gray-200 dark:border-default-200 shadow-md">
{/* 隧道权限 - 管理员不显示 */}
{!isAdmin && (
<Card className="mb-6 lg:mb-8 border border-gray-200 dark:border-default-200 shadow-md">
<CardHeader className="pb-3">
<div className="flex items-center gap-2">
<svg className="w-5 h-5 text-primary" fill="currentColor" viewBox="0 0 20 20">
@@ -828,6 +834,7 @@ export default function DashboardPage() {
)}
</CardBody>
</Card>
)}
{/* 转发配置 */}
<Card className="border border-gray-200 dark:border-default-200 shadow-md">
+23 -6
View File
@@ -10,6 +10,7 @@ import DefaultLayout from "@/layouts/default";
import { login, LoginData, checkCaptcha } from "@/api";
import "@/utils/tac.css";
import "@/utils/tac.min.js";
import bgImage from "@/images/bg.jpg";
interface LoginForm {
@@ -31,6 +32,8 @@ interface CaptchaConfig {
}
interface CaptchaStyle {
btnUrl?: string;
bgUrl?: string;
logoUrl?: string | null;
moveTrackMaskBgColor?: string;
moveTrackMaskBorderColor?: string;
@@ -117,7 +120,6 @@ export default function IndexPage() {
performLogin();
},
validFail: (_: any, _captcha: any, tac: any) => {
toast.error("验证码验证失败,请重试");
tac.reloadCaptcha();
},
btnCloseFun: (_event: any, tac: any) => {
@@ -130,10 +132,19 @@ export default function IndexPage() {
}
};
// 检测暗黑模式
const isDarkMode = document.documentElement.classList.contains('dark') ||
document.documentElement.getAttribute('data-theme') === 'dark' ||
window.matchMedia('(prefers-color-scheme: dark)').matches;
// 根据主题调整颜色
const trackColor = isDarkMode ? "#4a5568" : "#7db0be"; // 暗黑模式使用更深的灰蓝色
const style: CaptchaStyle = {
logoUrl: null, // 去除默认logo
moveTrackMaskBgColor: "#0298f8",
moveTrackMaskBorderColor: "#0298f8"
bgUrl: bgImage,
logoUrl: null,
moveTrackMaskBgColor: trackColor,
moveTrackMaskBorderColor: trackColor
};
tacInstanceRef.current = new window.TAC(config, style);
@@ -289,14 +300,20 @@ export default function IndexPage() {
{/* 验证码弹层 */}
{showCaptcha && (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* 背景遮罩层 - 模糊效果 */}
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm captcha-backdrop-enter" />
{/* 背景遮罩层 - 模糊效果,暗黑模式下更深 */}
<div className="absolute inset-0 bg-black/60 dark:bg-black/80 backdrop-blur-sm captcha-backdrop-enter" />
{/* 验证码容器 */}
<div className="mb-4">
<div
id="captcha-container"
ref={captchaContainerRef}
className="w-full flex justify-center"
style={{
filter: document.documentElement.classList.contains('dark') ||
document.documentElement.getAttribute('data-theme') === 'dark' ||
window.matchMedia('(prefers-color-scheme: dark)').matches
? 'brightness(0.8) contrast(0.9)' : 'none'
}}
/>
</div>
</div>
+1 -13
View File
@@ -879,21 +879,9 @@ export default function NodePage() {
<Alert
color="primary"
variant="flat"
description="服务器ip是你要添加的服务器的ip地址,不是面板的ip地址"
description="服务器ip是你要添加的服务器的ip地址,不是面板的ip地址。入口ip是用于展示在转发页面,面向用户的访问地址。实在理解不到说明你没这个需求,都填节点的服务器ip就行!"
className="mt-4"
/>
<Alert
color="primary"
variant="flat"
description="入口ip是用于展示在转发页面,面向用户的访问地址"
className="mt-4"
/>
<Alert
color="primary"
variant="flat"
description="实在理解不到说明你没这个需求,都填节点的服务器ip就行"
className="mt-4"
/>
</div>
</ModalBody>
<ModalFooter>
+6 -6
View File
@@ -100,8 +100,8 @@ export default function TunnelPage() {
inNodeId: null,
outNodeId: null,
protocol: 'tls',
tcpListenAddr: '0.0.0.0',
udpListenAddr: '0.0.0.0',
tcpListenAddr: '[::]',
udpListenAddr: '[::]',
interfaceName: '',
flow: 1,
trafficRatio: 1.0,
@@ -195,8 +195,8 @@ export default function TunnelPage() {
inNodeId: null,
outNodeId: null,
protocol: 'tls',
tcpListenAddr: '0.0.0.0',
udpListenAddr: '0.0.0.0',
tcpListenAddr: '[::]',
udpListenAddr: '[::]',
interfaceName: '',
flow: 1,
trafficRatio: 1.0,
@@ -216,8 +216,8 @@ export default function TunnelPage() {
inNodeId: tunnel.inNodeId,
outNodeId: tunnel.outNodeId || null,
protocol: tunnel.protocol || 'tls',
tcpListenAddr: tunnel.tcpListenAddr || '0.0.0.0',
udpListenAddr: tunnel.udpListenAddr || '0.0.0.0',
tcpListenAddr: tunnel.tcpListenAddr || '[::]',
udpListenAddr: tunnel.udpListenAddr || '[::]',
interfaceName: tunnel.interfaceName || '',
flow: tunnel.flow,
trafficRatio: tunnel.trafficRatio,
+1 -1
View File
@@ -988,7 +988,7 @@ export default function UserPage() {
aria-label="用户隧道权限列表"
classNames={{
wrapper: "shadow-none",
th: "bg-gray-50 text-gray-700 font-medium"
th: "bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-300 font-medium"
}}
>
<TableHeader>