news 2026/9/10 19:14:17

Selenium自动化整活靶场:电子神庙项目踩坑笔记

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Selenium自动化整活靶场:电子神庙项目踩坑笔记

最近整理旧笔记时翻出一个挺抽象的Demo:一个挂着金色大字“电子神庙”的本地网页,布告栏上写着“自动售卖赎罪券,扫码立付立得”。这其实是我当时为了练Selenium自动化,专门搭出来的一个整活靶场。结果没想到,就这么一个花里胡哨的小破站,把Selenium领域的经典硬骨头全给踩了一遍:selenium安装和浏览器驱动匹配、网页拼图验证怎么自动化、点击按钮就下载图片后怎么等文件真正落盘、selenium上传本地文件的正确姿势,还有运行时被反爬识别、多浏览器并发隔离这些糟心事。

这篇文章没有太多虚的,全是实测过的代码和踩坑记录。项目背景就是虚构的“电子神庙”,代码和站点都只在本地实验环境跑,不映射任何现实组织或事件。适合正在学Python Selenium的初学者,也适合想搞Web自动化回归测试的兄弟参考,尤其是那些搜“selenium图片滑块验证”和“selenium怎样使文件下载完成之后才进行下一步”的人,建议直接跳到对应的章节看。

1. 电子神庙到底是什么:项目背景与自动化目标

1.1 一个整活靶场为什么值得测

第一次看到“电子神庙自动售赎罪券”这个目标,第一反应肯定是搞笑。但认真琢磨一下,这个场景放到自动化测试里其实非常典型:一个页面里有表单、有选择器、有滑块验证、有文件下载、有文件上传,几乎覆盖了日常Web交互的所有常见形态。

我当时搭的神庙页面很简单:顶部是一行大字“电子神庙功德系统”,中间列出三种赎罪券套餐(普通版、尊享版、典藏版),下面是一个购买表单,要填写姓名和供奉金额。提交表单之前,必须通过一个拼图滑块验证,验证过了才能提交订单。提交订单之后,页面会生成一张带防伪码的电子赎罪券图片,点击按钮就能下载。最后还有一个“供奉功德箱”的入口,允许上传一张善款截图。整个流程走下来,像极了一个真实业务系统里的注册、下单、下载凭证、上传材料,只不过换成了整活的皮。

这种“不正经”的页面反而是最好的自动化练手对象,因为交互丰富、状态多,你不得不去处理各种等待和异常。

1.2 赎罪券购买的用户旅程与自动化拆解

我把自动化目标拆成了下面几条路径:

  • 进入电子神庙首页,检查页面的关键元素是否存在;
  • 选择赎罪券等级,填写姓名和供奉金额;
  • 组装滑块验证轨迹,通过拼图验证;
  • 提交订单,等待订单成功提示;
  • 点击“下载赎罪券”按钮,把图片保存到本地指定目录;
  • 打开功德箱上传入口,上传本地供奉文件;
  • 最后校验下载文件存在、上传列表出现新文件名。

这一套流程跑通之后,其实就等价于一条完整的Web业务端到端回归用例。之后不管是谁改了页面布局还是换了按钮ID,脚本都能在几分钟内告诉你“庙塌了”。

1.3 为什么选Python+Selenium这套组合

选型的时候也纠结过Playwright、Cypress这些新一代框架,但最后我还是用回了Selenium。原因很实在:网上搜出来的自动化问题十有八九都是Selenium体系的,踩坑资料最多。而且Python生态处理滑块图片识别太顺手了,OpenCV、Pillow直接拿来用,配合Selenium驱动浏览器,整个链路特别顺。

再说,很多人问的“selenium音乐下载器”,本质上和这个项目的赎罪券下载一模一样:打开页面、锁定下载链接或按钮、点击触发下载、等待文件完成。把神庙这个项目吃透,其他下载场景基本都是换汤不换药。

2. 开荒前的环境准备:selenium安装与驱动版本那些坑

2.1 五分钟装好Selenium基础环境

最基础的安装其实就一行命令:

pip install selenium

然后确保本机装了一个Chrome浏览器,就可以开始跑了。很多人会在这一步卡住,不是selenium装不上,而是Python环境乱。建议用虚拟环境:

python -m venv .venv .venv\Scripts\activate # Windows # 或者 source .venv/bin/activate # Linux/macOS pip install selenium

装完之后写个最简单的脚本验证环境:

from selenium import webdriver driver = webdriver.Chrome() driver.get("http://localhost:8080") print(driver.title) driver.quit()

如果这一句能正常弹窗并打印出页面标题,基础环境就OK了。

2.2 Chrome和ChromeDriver版本不匹配怎么办

Selenium本身不是直接操控浏览器的,它通过一个浏览器驱动(ChromeDriver)来发指令。驱动和浏览器版本必须匹配,否则你会碰到一条非常经典的报错:

session not created: This version of ChromeDriver only supports Chrome version XX

我第一次跑的时候就被这个干懵了,明明代码没问题,就是起不来。原因很简单:Chrome自动更新到了新版本,而手里的ChromeDriver还是旧的。解决方式有两种:

一是手动到ChromeDriver官方渠道下载和本机Chrome大版本一致的驱动,把路径写进Service里;

二是用下面的方式自动匹配,强烈推荐这种,省心。

2.3 webdriver-manager实现驱动自动管理

直接用webdriver-manager这个库托管驱动,它会自动检测本机Chrome版本并下载对应驱动:

pip install webdriver-manager
from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager options = webdriver.ChromeOptions() options.add_argument("--start-maximized") service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service, options=options)

实际体验下来,这个方案基本能解决90%的驱动版本问题。唯一要注意的是首次运行会下载驱动,比较慢。如果公司内网没法访问外网,那就只能手动下载驱动放到指定目录,然后用Service指定路径。

2.4 浏览器启动方式:正常模式、Headless和无痕模式

调试阶段建议正常模式启动,能直观看到每一步操作;脚本稳定之后,如果要挂到CI上跑,就用无头模式:

options.add_argument("--headless=new") options.add_argument("--disable-gpu")

无痕模式也可以考虑,尤其是需要隔离Cookie和用户数据的场景:

options.add_argument("--incognito")

还有几个常用参数,都是遇到过问题的:

  • --no-sandbox:Linux环境下没有用户权限隔离时会用到;
  • --disable-dev-shm-usage:Docker容器里避免临时空间不足;
  • --user-agent=:需要模拟某类终端时设置。

3. 自动购买赎罪券:从定位元素到强制点击

3.1 用显式等待代替无脑sleep

新手写Selenium最爱用time.sleep(3),觉得等三秒钟页面肯定加载完。但这种方式又慢又脆:页面加载快了,白白多等两秒;网络一慢,三秒可能还不够,脚本直接报错找不到元素。

正确的做法是显式等待。页面里只要元素出现或可点击,脚本立刻继续:

from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC buy_btn = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, "button.buy-btn")) ) buy_btn.click()

我一般会封装一个简单的工具函数,后面所有操作都复用:

def wait_clickable(driver, by, selector, timeout=10): return WebDriverWait(driver, timeout).until( EC.element_to_be_clickable((by, selector)) )

3.2 表单填写与按钮点击的细节处理

神庙页面的表单有三个关键控件:套餐单选按钮、姓名输入框、供奉金额输入框。定位的时候,能用ID就用ID,能用CSS选择器就用CSS选择器,XPath反而容易因为页面微调而失效。

# 选择尊享版套餐 driver.find_element(By.CSS_SELECTOR, "input[name='plan'][value='premium']").click() # 填写姓名和金额 name_input = driver.find_element(By.ID, "believer-name") name_input.clear() name_input.send_keys("自动化测试员") amount_input = driver.find_element(By.ID, "donation-amount") amount_input.clear() amount_input.send_keys("998")

这里有个小细节:send_keys之前最好先clear(),否则遇到输入框里本来有默认值的情况,会出现“输入内容被拼接”的问题。另外,如果输入框有格式化逻辑,比如金额有限制,直接输入长字符串可能会被截断,所以输入完成后最好再读一下输入框的value值做校验。

3.3 元素被遮挡或没绑事件时如何"曲线救国"

用Selenium跑业务流程,最烦的就是元素明明在页面上,但点击时报一堆异常。我遇到过的典型情况有几种:

第一种,元素被弹窗或浮层遮挡。这时候直接click会报element click intercepted,解决办法是先尝试关闭弹窗,或者用ActionChains把鼠标移过去再点。

第二种,元素不在浏览器可视区域内。Selenium为了保证点击真实,会要求元素可见且可交互,如果元素在页面底部,它会自动滚动,但有时候滚动不到,手动来一下更稳:

driver.execute_script("arguments[0].scrollIntoView({block:'center'});", element)

第三种,后台JS没绑定好点击事件,或者按钮的onclick需要某种前置状态。让Selenium直接执行JS强制点击:

driver.execute_script("arguments[0].click();", element)

这种写法有点暴力,但预处理一些特殊控件非常好用。注意,不要所有地方都强制点击,不然就失去了真实用户模拟的意义。

4. 神庙门口的拼图验证:图片滑块验证自动化思路

4.1 滑块验证到底在验证什么

滑块验证的核心机制其实不复杂:后端或前端生成一张带缺口的背景图,同时给出一块拼图,用户必须把拼图拖到缺口位置才能通过。校验的时候,服务端看的是两个东西:最终坐标的误差范围,以及拖拽过程中产生的轨迹数据。

自动化滑块验证难在两个地方:一是怎么找到拼图应该落到的x坐标,二是怎么让拖拽轨迹看起来像人。我在电子神庙里放的滑块组件是自己写的,就是为了可控地做验证码组件测试和自动化学习。

这里必须把话说清楚:本文所有自动化操作,都是在我自己搭建的本地Demo站点上完成的,目的是练习自动化测试和验证码组件自测。线上真实站点的验证码是安全防护的一部分,未经授权使用自动化绕过,轻则违反用户协议,重则触犯法律。下面的内容仅供技术学习和受控环境测试使用。

4.2 读取背景图并计算缺口距离

神庙的滑块背景图是一张带缺口的风景图,拼图块是一个小方块。最佳方案是用OpenCV做模板匹配。首先把背景图和拼图块拿到本地。

背景图如果是img标签,可以直接取src下载;如果是canvas渲染的,需要通过JS把canvas转成base64再写文件:

import base64 import cv2 import numpy as np from selenium import webdriver # 从canvas里抽背景图 canvas_base64 = driver.execute_script(""" var canvas = document.querySelector('#slider-bg'); return canvas.toDataURL('image/png').replace(/^data:image\\/\\w+;base64,/, ''); """) img_bytes = base64.b64decode(canvas_base64) with open("bg.png", "wb") as f: f.write(img_bytes)

然后用OpenCV做匹配:

def find_gap_x(bg_path, tpl_path, scale=1.0): bg = cv2.imread(bg_path) tpl = cv2.imread(tpl_path) result = cv2.matchTemplate(bg, tpl, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc = cv2.minMaxLoc(result) # max_loc[0] 是缺口左上角的x坐标,最终算的是拼图中心点 gap_x = max_loc[0] * scale return gap_x

有一个特别容易踩的坑:页面显示图片的尺寸和原始图片物理尺寸不一定一致。如果页面里背景图宽400px,实际图片分辨率是800px,那算出来的坐标必须乘以0.5再进行拖拽。这个比例问题不搞定,轨迹再像人也会差一大截坐标,怎么拖都不会成功。

还有一些验证码会在背景图上加干扰线,模板匹配效果差,可以先用高斯模糊或边缘检测预处理:

bg = cv2.GaussianBlur(bg, (3, 3), 0)

4.3 用ActionChains模拟真人拖拽轨迹

拿到缺口坐标后,直接一次性拖到位是最容易失败的,因为正常人手拖滑块不可能一步到位。这里我用ActionChains模拟分段的拖拽过程:

from selenium.webdriver.common.action_chains import ActionChains import random, time slider = driver.find_element(By.CSS_SELECTOR, ".slider-button") ActionChains(driver).click_and_hold(slider).perform() time.sleep(0.2) movement = x_offset steps = track_generator(movement) for step in steps: ActionChains(driver).move_by_offset(step, random.uniform(-1, 1)).perform() time.sleep(random.uniform(0.02, 0.06)) time.sleep(0.3) ActionChains(driver).release().perform()

轨迹生成函数要模拟“先慢-中间快-再慢”的特征:

def track_generator(distance): steps = [] remaining = distance while remaining > 0: if remaining > distance * 0.6: span = random.randint(18, 30) elif remaining > distance * 0.3: span = random.randint(6, 16) else: span = random.randint(2, 6) span = min(span, remaining) steps.append(span) remaining -= span return steps

启动拖拽之前最好先让鼠标移动到滑块上,再按下;松开之前稍微顿一顿。这些细节看起来不起眼,但对轨迹识别的结果影响非常大。

4.4 失败重试与轨迹参数调优

滑块验证失败之后页面一般会提示“拼图失败,请重试”,同时重置滑块。脚本不能傻等,要做重试机制。我习惯把它包成一个循环:

for attempt in range(5): try: gap_x = locate_gap() drag_slider(gap_x) WebDriverWait(driver, 5).until( EC.visibility_of_element_located((By.CSS_SELECTOR, ".slider-success")) ) print("通过验证") break except Exception as e: print(f"验证失败,第{attempt + 1}次重试") driver.refresh() time.sleep(1)

调参的时候重点关注三件事:坐标换算准不准、拖拽总步数是否过少、每一次移动之间有没有随机停顿。实测下来,路径控制在8到15步、总时长在0.8到1.8秒之间是最接近人手习惯的。太快的轨迹本身就会触发风控。

4.5 关于验证码自动化的边界

再强调一次,滑块验证的目的是保护网站和用户,不是给自动化脚本当陪练。我的建议是,想学这个技术就自己搭一个带滑块验证的Demo站来做实验。现在很多开源的前端组件库都自带滑块组件,本地启动一个页面再开发一套自己的验证逻辑,足够你研究清楚套路。等项目里需要给自研验证码组件做自动化测试的时候,这套东西才会真正派上用场。

5. 赎罪券图片下载:点击下载与"下载完成后再执行下一步"

5.1 为什么不能直接点击完就继续

神庙页面提交订单之后,会生成一张赎罪券图片,页面上有一个“下载悔罪券”按钮。我用Selenium点击这个按钮之后,最开始的做法是等一秒钟就去检查下载目录,结果经常拿到一个0字节的临时文件,或者干脆找不到文件。

原因在于:click()只是触发了浏览器的下载动作,浏览器需要时间去请求文件、写入磁盘。尤其文件从服务器返回的过程中,本地目录里会先出现一个以.crdownload结尾的临时文件,文件真正写完之前,大小一直在变化。如果脚本不等它写完就去做下一步操作,必然出问题。

5.2 设置浏览器下载目录的prefs配置

为了让下载落到固定目录,并且不让浏览器弹“另存为”对话框,需要在ChromeOptions里设置prefs:

download_dir = os.path.abspath("./downloads") os.makedirs(download_dir, exist_ok=True) options.add_experimental_option("prefs", { "download.default_directory": download_dir, "download.prompt_for_download": False, "download.directory_upgrade": True, "safebrowsing.enabled": True })

这里比较关键的就是download.default_directory,必须用绝对路径,相对路径在部分版本的ChromeDriver里不生效。safebrowsing.enabled置为True可以避免下载未被识别文件时被安全浏览拦一道。

5.3 点击按钮就下载图片的代码写法

场景不同,写法也不同。神庙页面是点击按钮触发下载:

download_btn = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, "download-indulgence")) ) download_btn.click()

如果是页面里的一个链接,直接指向图片地址,也可以通过Selenium拿到href后,在下载器里处理:

img_url = driver.find_element(By.ID, "download-link").get_attribute("href")

注意,Selenium本身并没有内置“点击下载后自动等待完成”的功能,很多人搜“selenium中点击按钮就下载图片的代码怎么写”,其实难点不在点击,而在点击之后的等待,这个后面细说。

5.4 轮询判断下载完成:大小稳定法与.crdownload法

正确等待下载完成的思路是轮询下载目录,直到满足以下三个条件:目录里有文件;没有.crdownload.tmp后缀的临时文件;文件大小稳定不变。

我封装的函数长这样:

import os import time def wait_for_download(download_dir, timeout=60): end_time = time.time() + timeout while time.time() < end_time: files = [f for f in os.listdir(download_dir) if not f.startswith('.')] active_files = [f for f in files if f.endswith('.crdownload') or f.endswith('.tmp')] done_files = [f for f in files if not f.endswith('.crdownload') and not f.endswith('.tmp')] if done_files and not active_files: target = os.path.join(download_dir, done_files[0]) size1 = os.path.getsize(target) time.sleep(1) size2 = os.path.getsize(target) if size1 == size2 and size1 > 0: return target time.sleep(0.5) raise TimeoutError("等待下载超时")

这里还有一个小技巧:如果担心文件只下载到一半而且恰好没有临时后缀,可以再校验一下文件头。比如赎罪券图片是PNG,就检查前8个字节是不是PNG签名。这样在本地Demo环境跑,几乎不会出现“以为完成实际半截”的尴尬。

5.5 超时和重试的处理

下载超时后,脚本不应该直接崩溃,而是清理可能存在的半成品文件,再重试一次或切换下载通道:

try: path = wait_for_download(download_dir) print(f"下载完成:{path}") except TimeoutError: for f in os.listdir(download_dir): os.remove(os.path.join(download_dir, f)) download_btn.click() path = wait_for_download(download_dir, timeout=90)

这种“清理重试”的思路,在批量下载场景里尤其重要,不然整个测试队列会被一个失败下载卡死。

6. 供奉功德箱:selenium上传本地文件的正确姿势

6.1 优先寻找input[type=file]标签

繁琐的下单和下载都跑通了,最后还剩一个上传步骤。神庙页面底部有个“供奉功德箱”区域,点击按钮后会弹出文件选择窗口。Selenium对弹窗式的文件选择器是无能为力的,但绝大多数网页上传控件的本质都是一个隐藏的input[type=file]标签,外面套一个按钮做样式。

所以第一步永远是去找这个input标签:

file_input = driver.find_element(By.CSS_SELECTOR, "input[type='file']")

如果页面里只有一个文件上传入口,上面的选择器通常没问题。

6.2 send_keys直接写入完整路径

找到input之后,不需要真的弹Windows窗口,直接用send_keys把本地文件路径写进去就行:

import os file_path = os.path.abspath("offering.jpg") file_input = driver.find_element(By.CSS_SELECTOR, "input[type='file']") file_input.send_keys(file_path)

这个操作本质上是把文件路径填入了input的值,浏览器会自动触发上传。注意两点:第一,路径必须使用绝对路径,相对路径大概率不认;第二,Windows下路径分隔符用\\或者直接用os.path.abspath处理,别手写C:\xxx

6.3 当上传控件不是一个简单input时怎么处理

有些上传组件会把input[type=file]藏得特别深,或者干脆没有这个标签,点击后就是打开系统文件选择器。这种情况Selenium直接处理不了,我通常用两个方案。

第一个方案是通过JS构造一个DataTransfer对象模拟文件拖拽。如果上传区域支持drop事件,可以直接把文件数据丢进去:

const input = document.querySelector('input[type=file]') const dt = new DataTransfer() dt.items.add(new File(['test'], 'offering.jpg', { type: 'image/jpeg' })) input.files = dt.files input.dispatchEvent(new Event('change', { bubbles: true }))

Selenium里执行这段JS,可以在很多支持拖拽上传的组件上蒙混过关。第二个方案是用pyautogui或AutoIT直接向文件选择窗口输入路径并回车:

import pyautogui, time upload_btn.click() time.sleep(1) pyautogui.write(os.path.abspath("offering.jpg")) pyautogui.press("enter")

这种方式能用,但非常依赖操作系统的图形界面,而且对焦点要求高,不宜大规模使用。

6.4 上传后的结果校验

上传完成后不能直接不管,要验证文件是不是真的传上去了。神庙页面上传成功后,功德箱列表里会出现新文件的名称。自动化脚本里可以做一次等待和判断:

WebDriverWait(driver, 10).until( EC.text_to_be_present_in_element((By.CSS_SELECTOR, ".offering-list"), "offering.jpg") )

更严谨一点,可以去读取上传文件的元数据接口,或者检查页面的预览区域image标签的src。总之,上传之后的校验逻辑和下载之后的校验逻辑同样重要,不然真到线上回归测试,可能上传接口早就挂了你还浑然不知。

7. 跑完整流程时踩过的坑:从识别到并发

7.1 Chrome提示"Chrome正在受到自动测试软件的控制"

每次启动浏览器,顶部都会出现“Chrome正在受到自动测试软件的控制”的提示条。这个条本身不影响脚本跑,但会让有些页面判断你正在被自动化工具驱动。如果不想让人一眼看出来,可以加两个参数:

options.add_argument("--disable-blink-features=AutomationControlled")

然后删掉webdriver标记:

driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})" })

注意顺序:这两个配置要在浏览器加载任何页面之前生效。实际项目里,如果是给自己公司内部的测试环境写脚本,不搞这些也完全没影响;但模拟用户行为做演示或测试的时候,这个遮罩还是干净一点好。

7.2 滑块轨迹被识别为机器人的进阶对抗

我的神庙Demo站点后期也加了一个“轨迹检测”的功能,用来模拟真实平台的轨迹风控。也就是说,光有缺口坐标不够,轨迹数据如果呈现出机械特征,照样判失败。

在手势生成的细节上,我还做了几件事让过程更接近真人:先移动鼠标到滑块上方停顿0.1秒,按下后再停顿0.2秒才开始拖;拖的过程中除了横向速度变化,纵向也加轻微抖动,甚至偶尔会在某个位置停一下再继续。实测下来,这种轨迹被后端标记为“可疑”的概率明显低于匀速拖动。

但要再次强调,这些都是为了测试自研验证码组件的健壮性。不要在真实网站上折腾这类对抗手段。

7.3 页面懒加载导致元素不在DOM中

神庙布告栏下面有一长串“往期善款记录”,页面底部采用懒加载,滚动到哪才渲染到哪。如果脚本一上来就去点“供奉功德箱”入口,可能会因为元素还没渲染而报找不到。

解决方式是先滚动页面,再等待元素出现:

driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") offering_btn = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, "offering-entry")) )

这种“先滚动再等待”的组合拳,在长页面、瀑布流页面里特别常用。另外要注意,懒加载页面的DOM结构可能是动态插入的,如果用XPath写死了层级,一旦插入顺序变化就崩。最好的办法是给目标元素加稳定的data-testid或ID属性。

7.4 多开浏览器跑批量赎罪券时的隔离问题

把整个流程跑通之后,我试着用多线程同时跑多个浏览器实例,每个实例给不同的人购买赎罪券。结果发现浏览器实例之间Cookie和登录状态互相串,原因是没有做用户数据目录隔离。

解决办法是给每个实例指定独立的user-data-dir:

options.add_argument(f"--user-data-dir={os.path.abspath(f'./chrome-profile-{thread_id}')}")

用这种方式隔离用户数据之后,多线程同时跑就不会互相干扰了。另外一个细节是webdriver-manager首次下载驱动的并发问题,多线程同时启动时可能抢同一个临时文件,最好在脚本Startup阶段预先调用一次ChromeDriverManager().install(),把驱动提前准备好。

7.5 一套思路延伸到更多自动化场景

这个神庙项目跑通之后,我会拿同一套代码去处理别的事。比如之前提到“selenium音乐下载器”,从搜索引擎下载页面里点翻页、点下载、等待文件完成,本质上就是神庙流程里“点击按钮下载图片”部分的复刻。还有日常做报表导出的自动化,也可以用这套思路:登录后台、按条件查询、点击导出、等待Excel文件落地、把文件移动到共享目录。

我自己的感受是,Selenium这套东西看着简单,真正让脚本稳定跑完一个完整业务流程,坑基本都藏在“等待”和“环境”上。元素没出现你等了、下载没完成你等了、上传路径对吗、浏览器驱动版本对吗,把这些细节收拾利索,剩下的就是业务逻辑堆代码。电子神庙这种整活项目,最大的价值就是用最低的成本,把这些坑一次性全踩完。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 19:12:06

Material3 HorizontalUncontainedCarousel问题解析与替代方案

1. HorizontalUncontainedCarousel组件问题解析 最近在Material3组件库中尝试使用HorizontalUncontainedCarousel时遇到了无法调用的问题。这个组件在官方文档中被描述为"一个不限制内容边界的水平轮播容器"&#xff0c;理论上应该能实现类似电商APP首页那种可以无限…

作者头像 李华
网站建设 2026/9/10 19:11:28

OpenSSL 3.0 Provider架构实践:从零编写自定义摘要算法模块

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 19:11:26

C++实现二叉搜索树(BST)核心原理与工程实践

1. 二叉搜索树基础概念解析二叉搜索树&#xff08;Binary Search Tree&#xff0c;BST&#xff09;是一种特殊的二叉树数据结构&#xff0c;它满足以下关键性质&#xff1a;对于树中的任意节点&#xff0c;其左子树所有节点的值都小于该节点的值&#xff0c;而右子树所有节点的…

作者头像 李华
网站建设 2026/9/10 19:04:30

百考通得力助手:AI赋能任务书生成

在学术研究、课程设计与项目开发的起步阶段&#xff0c;一份规范、清晰的任务书是指引方向的核心纲领。但从选题构思到内容撰写&#xff0c;往往让研究者与学生陷入困境&#xff1a;选题迷茫、逻辑混乱、要求表述模糊&#xff0c;严重拖慢项目推进节奏。百考通&#xff08;http…

作者头像 李华
网站建设 2026/9/10 19:04:08

Gson默认转义=和?详解HTML安全转义及关闭方法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华