Streamlit + Prisma 快速构建实时数据应用

📅 2026/7/14 16:18:32
Streamlit + Prisma 快速构建实时数据应用
1. 项目概述用 Streamlit Prisma 搭建实时数据应用到底省了多少事我做数据产品开发快八年了从最早手写 Flask 后端配 ECharts 前端到后来用 Dash 做 BI 看板再到近两年高频接触 Streamlit——不是因为“它火”而是因为它真正在解决一个长期被低估的痛点数据工程师和算法同学想快速验证一个想法、跑通一条数据链路、给业务方交付一个可交互的最小闭环根本不需要拉起一个完整 Web 工程团队。这篇内容讲的就是怎么用 Streamlit 和 Prisma 组合拳把“数据库连接 → 数据查询 → 实时刷新 → 表单交互 → 结果可视化”这一整条链路压缩进不到 200 行 Python 代码里且全程不碰 SQL 字符串拼接、不写 REST API 路由、不配 CORS、不处理连接池泄漏。关键词里那个 “Towards AI” 其实只是原始发布平台真正核心是Streamlit和Prisma这两个工具的协同逻辑——前者负责让 Python 数据逻辑直接变成网页后者负责让数据库操作回归“面向对象”的直觉。它适合三类人一是算法/数据科学家想自己搭个内部看数页不想等前端排期二是小团队后端开发者需要快速出 demo 验证需求三是教学场景下带学生理解“数据流如何从库走到界面”。它不替代成熟的微服务架构但能帮你砍掉 70% 的样板代码把精力聚焦在“这个指标为什么异常”“那个筛选条件是否合理”这类真实问题上。我上周刚用这套组合帮风控组上线了一个实时审批流水监控页从需求确认到部署上线只用了 1 天半其中 4 小时花在写业务逻辑剩下全是 Streamlit 自动搞定的 UI 渲染和 Prisma 自动生成的类型安全查询。2. 整体设计思路与技术选型逻辑2.1 为什么不是 Flask SQLAlchemy也不是 FastAPI React这是每次聊到这个方案同事必问的问题。我来拆解三层逻辑第一层是开发心智负担。Flask 或 FastAPI 写后端你得先定义路由app.get(/users)再写数据库查询session.query(User).filter(...)再手动序列化成 JSONjsonable_encoder()再考虑分页参数怎么传、错误怎么统一返回、JWT 怎么校验……这些都不是业务逻辑但每一步都卡点。而 Streamlit 的范式是“你写一个 Python 函数它自动变成网页入口”。比如st.dataframe(get_latest_transactions())这个函数里直接调 Prisma 查询Streamlit 负责把结果渲染成可排序、可导出的表格连分页按钮都是内置的。你不用想“这个接口该叫什么路径”因为压根没有接口。第二层是类型安全与开发效率的平衡。SQLAlchemy ORM 确实成熟但它的模型定义class User(Base): ...和查询语法.filter(User.name xxx)之间存在语义断层——你得记住filter接的是表达式order_by接的是列对象join的写法又不同。Prisma 的设计哲学是“数据库即 Schema”你用 Prisma Schema 文件schema.prisma声明表结构它自动生成类型严格的客户端。比如prisma.user.findMany(where: { status: active })IDE 能直接提示status只能是active | inactive | pending字段名写错会立刻报红。这在快速迭代时省下的调试时间远超学习成本。第三层是实时性实现的简洁度。传统方案要实现实时刷新得上 WebSocket复杂、轮询浪费资源、或者 Sse兼容性差。Streamlit 原生支持st.experimental_rerun()和st_autorefresh组件配合 Prisma 的高效查询3 行代码就能实现秒级刷新。我试过用 FastAPI Vue 做同样功能光是 WebSocket 连接状态管理、重连逻辑、消息解析就写了 200 多行而 Streamlit 版本里刷新逻辑就藏在if st.button(刷新):里点击即生效无感。所以这个组合不是“炫技”而是精准匹配“中小规模、数据驱动、快速验证”场景的技术杠杆。它把数据库当数据源把 Streamlit 当渲染引擎中间 Prisma 是类型安全的翻译官——三者各司其职没有冗余抽象。2.2 Prisma 在这里扮演什么角色它和传统 ORM 有什么本质区别很多同学第一次接触 Prisma容易把它当成另一个 SQLAlchemy。其实它更像一个“数据库代理层 代码生成器”的混合体。关键差异有三点第一Schema 驱动而非代码驱动。传统 ORM如 Django ORM、SQLModel要求你先写 Python 类再通过makemigrations生成 SQL。Prisma 要求你先写schema.prisma文件再运行prisma generate生成客户端。这个顺序反转意味着数据库结构是唯一真相源。比如你改了 MySQL 表的created_at字段为DATETIME(3)只需更新 schema 文件里的db.DateTime(3)重新生成所有查询方法的类型签名自动更新。而 SQLAlchemy 里你得手动改 Python 模型的Column(DateTime)再改迁移脚本再跑alembic upgrade稍有遗漏就类型不一致。第二查询 API 是声明式的不是命令式的。看一个典型对比SQLAlchemyusers session.query(User).filter(User.status active).order_by(User.created_at.desc()).limit(10).all()这里filter、order_by、limit是链式调用但每个方法返回的对象类型不同IDE 提示弱拼错字段名 runtime 才报错。Prismausers await prisma.user.find_many( where{status: active}, order{created_at: desc}, take10 )参数是纯字典where、order、take都是明确的关键字值的类型由 Prisma 自动生成的类型定义约束。status只能填枚举值order的created_at字段名 IDE 会自动补全写错直接编译报错。第三原生支持关系预加载Preload避免 N1 查询。这是数据应用的隐形杀手。比如查 10 个订单每个订单要显示用户姓名传统 ORM 往往写成orders get_orders() # 1 次查询 for order in orders: user get_user_by_id(order.user_id) # 10 次额外查询Prisma 一行解决orders await prisma.order.find_many( include{user: True}, # 自动 JOIN1 次查询拿到全部数据 )include参数告诉 Prisma“把关联的 user 表字段一起查出来”生成的 SQL 就是SELECT ... FROM order JOIN user ON ...没有 N1。我在一个日活 5k 的内部看板里把 SQLAlchemy 的 N1 改成 Prisma 的include首屏加载时间从 3.2s 降到 0.8s。所以 Prisma 不是“另一个 ORM”它是用现代类型系统TypeScript/Python重构数据库交互体验的尝试。它牺牲了一点“完全动态 SQL”的自由度换来了开发阶段的确定性、运行时的性能保障和团队协作的清晰契约。2.3 Streamlit 的“实时性”到底指什么它和真正的实时推送有何区别这里必须划清界限Streamlit 的“实时”不是指 WebSocket 推送而是“按需重执行”。它的底层机制是每次用户交互点按钮、拖滑块、选下拉框Streamlit 服务端会重新运行整个脚本文件从头到尾执行一遍然后把新结果渲染到前端。这听起来像“刷新页面”但它比页面刷新聪明得多——它只更新变化的组件保留输入框的值、图表的缩放状态甚至能维持 session state 中的临时数据。这种模式对数据应用恰恰够用且更可控。比如一个销售漏斗看板业务方想看“今天 vs 昨天”的对比你写date_range st.date_input(选择日期范围, [today - timedelta(days1), today]) if st.button(加载数据): data fetch_funnel_data(date_range[0], date_range[1]) # Prisma 查询 st.line_chart(data)点按钮脚本重跑fetch_funnel_data重新查库图表更新。没有长连接没有心跳包没有服务器内存泄漏风险。而真正的 WebSocket 推送如用 Socket.IO需要维护连接状态、处理断线重连、广播消息给指定用户组——这些在内部工具里纯属过度设计。当然Streamlit 也提供了st_autorefresh这样的社区组件可以设置interval50005 秒刷新一次底层还是定时触发st.experimental_rerun()。我在线上环境用它监控 Kafka 消费延迟每 5 秒查一次prisma.kafka_metric.find_first()延迟超过阈值就标红效果稳定。它不像 WebSocket 那样毫秒级响应但对“分钟级业务指标监控”这个场景5 秒刷新足够且运维简单——不需要额外部署消息中间件。总结一句话Streamlit 的实时是“用户驱动的确定性重计算”不是“服务端驱动的异步事件推送”。前者简单、可靠、易调试后者强大、灵活、需更多基础设施。选哪个取决于你的 SLA 要求。对于 90% 的内部数据应用前者是更优解。3. 核心细节解析与实操要点3.1 环境准备Python 版本、依赖安装与 Prisma 初始化别跳过这一步。我踩过最大的坑就是在一个旧项目里用 Python 3.8 装了prisma结果运行时报ModuleNotFoundError: No module named prisma折腾两小时才发现是 Prisma Python 客户端要求Python 3.9。官方文档没强调这点但实际生成的客户端代码用了typing.Union的新语法。所以第一步确认 Python 版本python --version # 必须 3.9接着创建虚拟环境强烈建议避免全局污染python -m venv .venv source .venv/bin/activate # macOS/Linux # 或 .venv\Scripts\activate.bat # Windows安装核心依赖pip install streamlit prisma python-dotenv注意prisma包本身只是 Python 客户端它需要 Prisma CLI 来生成客户端。CLI 是 Node.js 工具所以还得装 Node.js16.13。如果你没装 Node去官网下载 LTS 版本即可。装好后全局安装 CLInpm install -g prisma现在初始化 Prisma 项目prisma init这条命令会做三件事创建prisma/目录生成prisma/schema.prisma文件里面是数据库连接配置和默认模型创建.env文件让你填数据库 URL。打开prisma/schema.prisma你会看到类似这样的内容generator client { provider prisma-client-py } datasource db { provider postgresql url env(DATABASE_URL) }这里provider prisma-client-py是关键它告诉 Prisma 生成 Python 客户端而不是默认的 TypeScript 客户端。如果这行没写后面prisma generate会生成 JS 代码Python 里就 import 不了。再看.env文件它默认是DATABASE_URLpostgresql://johndoe:randompasswordlocalhost:5432/mydb你需要替换成自己的数据库地址。PostgreSQL 最常用MySQL 和 SQLite 也支持。如果是本地 PostgreSQL用psql创建数据库CREATE DATABASE streamlit_demo;然后把 URL 改成DATABASE_URLpostgresql://your_user:your_passwordlocalhost:5432/streamlit_demo密码里如果有特殊字符如、/必须 URL 编码。比如密码是pss/w0rd要写成p%40ss%2Fw0rd。这个细节很多人忽略导致连接失败报错信息还很模糊。提示Prisma CLI 会读取.env文件但 Streamlit 脚本默认不读。所以你在 Streamlit 里不能直接os.getenv(DATABASE_URL)而要用 Prisma 客户端内置的加载逻辑。这是 Prisma 的设计确保环境变量只在数据库层生效。3.2 Prisma Schema 设计从数据库表到 Python 类型的映射Schema 文件是整个数据层的基石。我们以一个真实的电商监控场景为例需要展示“今日订单量”、“各渠道转化率”、“高价值用户分布”。对应三张表orders、channels、users。在prisma/schema.prisma里这样定义generator client { provider prisma-client-py } datasource db { provider postgresql url env(DATABASE_URL) } model User { id Int id default(autoincrement()) email String unique tier String map(user_tier) // 映射数据库字段名 createdAt DateTime map(created_at) default(now()) orders Order[] map(users) // 映射表名 } model Channel { id Int id default(autoincrement()) name String conversionRate Float map(conversion_rate) createdAt DateTime map(created_at) default(now()) map(channels) } model Order { id Int id default(autoincrement()) userId Int channelId Int amount Float status String createdAt DateTime map(created_at) default(now()) user User relation(fields: [userId], references: [id]) channel Channel relation(fields: [channelId], references: [id]) map(orders) }几个关键点解释map(xxx)数据库字段名和 Python 属性名不一致时的映射。比如数据库是user_tierPython 里想叫tier就用map(user_tier)。这避免了 Python 命名规范snake_case和数据库习惯snake_case的冲突。default(now())设置默认值为当前时间Prisma 会在 INSERT 时自动填充不用在 Python 里写datetime.now()。关系定义user: User relation(...)Prisma 会自动生成反向关系。比如user.orders就能拿到该用户所有订单无需手动 JOIN。map(xxx)映射表名。如果数据库表叫user_profiles但你想在 Python 里叫UserProfile就用这个。定义完运行生成命令prisma generate成功后prisma/client目录下会出现 Python 客户端代码。此时你可以启动 Python 交互环境测试from prisma import Prisma prisma Prisma() await prisma.connect() # 必须先 connect users await prisma.user.find_many(take5) print(users) await prisma.disconnect() # 记得断开如果这步报错90% 是.env里的DATABASE_URL不对或者数据库没启动。我建议先用psql或 DBeaver 连一下数据库确认账号密码、端口、库名都正确。注意prisma generate只生成客户端不修改数据库。要创建表得用prisma db push开发环境或prisma migrate dev生产环境。db push直接同步 schema 到数据库适合本地快速试错migrate dev会生成迁移文件适合团队协作。新手推荐db push一行命令搞定prisma db push --preview-feature加 preview-feature 是因为 Python 客户端还在预览期。3.3 Streamlit 应用骨架从空白脚本到可运行页面新建一个app.py文件这是 Streamlit 的入口。不要试图一上来就写复杂逻辑先搭骨架import streamlit as st from prisma import Prisma # 1. 页面配置 st.set_page_config( page_title实时电商监控, page_icon, layoutwide # 宽屏布局适合数据看板 ) # 2. 标题与简介 st.title( 实时电商监控看板) st.markdown(基于 Streamlit Prisma 构建数据每 30 秒自动刷新) # 3. 初始化 Prisma 客户端关键 # 使用 st.cache_resource 装饰器确保客户端只初始化一次 st.cache_resource def get_prisma_client(): prisma Prisma() prisma.connect() return prisma prisma get_prisma_client() # 4. 主内容区占位 st.subheader(数据概览) st.write(这里将显示实时指标...)保存后在终端运行streamlit run app.py浏览器会自动打开http://localhost:8501看到标题和子标题。这就是最简可用版本。现在解释几个关键点st.set_page_config设置页面标题、图标、布局。layoutwide很重要Streamlit 默认窄屏数据表格会被挤成一列wide模式能让多列图表并排。st.cache_resource这是 Streamlit 2023 年新增的装饰器专为“昂贵的、全局共享的资源”设计比如数据库连接、大模型加载。它保证get_prisma_client()只执行一次后续所有用户访问都复用同一个连接实例。如果不加每次用户刷新页面都会新建一个 Prisma 连接很快耗尽数据库连接池。我在线上环境见过没加这个装饰器10 个并发就把 PostgreSQL 的 100 连接上限占满。prisma.connect()必须显式调用Prisma 不会自动连接。connect()是异步的所以get_prisma_client函数要标记为async吗不用。st.cache_resource会处理异步初始化你直接await prisma.connect()即可Streamlit 内部会等待。接下来把“数据概览”部分替换为真实查询# 在 st.subheader(数据概览) 下面添加 col1, col2, col3 st.columns(3) # 创建三列布局 # 今日订单量 with col1: st.metric(今日订单量, 1,248, 12% vs 昨天) # 今日GMV with col2: st.metric(今日GMV, ¥248,932, 8.3% vs 昨天) # 转化率 with col3: st.metric(平均转化率, 3.2%, -0.1% vs 昨天)st.metric是 Streamlit 的数据卡片组件自带箭头图标和颜色区分涨跌。数值是静态的下一步我们要让它动起来。3.4 实现数据实时加载Prisma 查询 Streamlit 刷新控制现在让指标变成动态的。我们写一个函数用 Prisma 查询今日订单数from datetime import datetime, timedelta st.cache_data(ttl30) # 缓存 30 秒避免频繁查库 def get_today_orders_count(): 获取今日订单总数 today datetime.now().date() count prisma.order.count( where{ createdAt: { gte: datetime.combine(today, datetime.min.time()), lt: datetime.combine(today timedelta(days1), datetime.min.time()) } } ) return countst.cache_data是另一个关键装饰器用于缓存函数返回值。ttl30表示 30 秒后自动失效下次调用会重新执行函数。这样既保证了数据新鲜度30 秒内不重复查库又避免了每次用户操作都触发查询比如用户点了个按钮你不希望它立刻查库而是用缓存值。然后在st.metric里调用# 替换之前的静态数字 with col1: count get_today_orders_count() st.metric(今日订单量, f{count:,}, 12% vs 昨天) # f{count:,} 自动加千分位但这样还是静态的。要实现“自动刷新”有两种方式方式一手动刷新按钮推荐给生产环境if st.button( 刷新数据): st.cache_data.clear() # 清空所有 st.cache_data 缓存 st.experimental_rerun() # 重跑整个脚本点按钮所有st.cache_data失效get_today_orders_count()重新执行页面更新。好处是可控不会因网络抖动导致意外刷新。方式二自动定时刷新适合监控大屏安装社区组件pip install streamlit-autorefresh然后在代码里from streamlit_autorefresh import st_autorefresh # 每 30 秒刷新一次 count st_autorefresh(interval30000, limit100, keyfizzbuzzcounter) # 重新获取数据注意这里不能用 st.cache_data否则缓存不更新 def get_today_orders_count_live(): today datetime.now().date() return prisma.order.count( where{ createdAt: { gte: datetime.combine(today, datetime.min.time()), lt: datetime.combine(today timedelta(days1), datetime.min.time()) } } ) count get_today_orders_count_live() st.metric(今日订单量, f{count:,})st_autorefresh返回一个递增计数器你用它作为key触发重渲染。这种方式简单但要注意st.cache_data在自动刷新时可能不生效所以查询函数最好不加缓存或者用ttl短于刷新间隔。实操心得我在线上用方式一手动刷新因为业务方有时会说“等等我还没看完这个数据”自动刷新会打断他们。而方式二更适合放在会议室大屏上没人操作纯展示。4. 实操过程与核心环节实现4.1 构建交互式数据表格支持筛选、排序、导出数据看板的核心是表格。Streamlit 的st.dataframe功能强大但需要和 Prisma 查询配合才能发挥最大价值。我们做一个“订单明细表”支持按状态、渠道、时间范围筛选。首先写一个带参数的查询函数st.cache_data(ttl60) def get_orders( status: str all, channel_id: int None, start_date: datetime None, end_date: datetime None, limit: int 100 ): 获取订单列表支持多条件筛选 where_clause {} if status ! all: where_clause[status] status if channel_id is not None: where_clause[channelId] channel_id if start_date or end_date: date_clause {} if start_date: date_clause[gte] start_date if end_date: date_clause[lte] end_date timedelta(days1) # 包含 end_date 当天 where_clause[createdAt] date_clause return prisma.order.find_many( wherewhere_clause, include{ user: True, channel: True }, takelimit, order{createdAt: desc} )这个函数用了 Prisma 的嵌套where语法createdAt的范围查询写成{gte: start, lte: end}非常直观。include加载了关联的user和channel这样表格里能直接显示用户名和渠道名不用额外查。然后在 Streamlit 页面里构建筛选控件st.subheader(订单明细) # 创建筛选区域 col1, col2, col3 st.columns(3) with col1: status_filter st.selectbox(订单状态, [all, paid, shipped, cancelled]) with col2: # 获取所有渠道用于下拉选择 channels prisma.channel.find_many() channel_options {c.name: c.id for c in channels} channel_name st.selectbox(选择渠道, [全部] list(channel_options.keys())) channel_id channel_options.get(channel_name) if channel_name ! 全部 else None with col3: date_range st.date_input(时间范围, [datetime.now().date() - timedelta(days7), datetime.now().date()]) # 执行查询 if len(date_range) 2: start_dt datetime.combine(date_range[0], datetime.min.time()) end_dt datetime.combine(date_range[1], datetime.max.time()) orders get_orders( statusstatus_filter, channel_idchannel_id, start_datestart_dt, end_dateend_dt ) else: orders get_orders(statusstatus_filter, channel_idchannel_id) # 渲染表格 if orders: # 转换为 Pandas DataFrame方便 Streamlit 渲染 import pandas as pd df pd.DataFrame([ { 订单ID: o.id, 用户邮箱: o.user.email if o.user else N/A, 渠道: o.channel.name if o.channel else N/A, 金额: f¥{o.amount:.2f}, 状态: o.status, 时间: o.createdAt.strftime(%Y-%m-%d %H:%M) } for o in orders ]) st.dataframe( df, use_container_widthTrue, # 填满容器宽度 hide_indexTrue, # 隐藏默认索引列 column_config{ # 配置列样式 金额: st.column_config.NumberColumn(金额, format¥%d), 时间: st.column_config.DatetimeColumn(时间, formatMM/DD/YY HH:mm) } ) # 添加导出按钮 csv df.to_csv(indexFalse).encode(utf-8) st.download_button( label 导出为 CSV, datacsv, file_nameforders_{datetime.now().strftime(%Y%m%d_%H%M%S)}.csv, mimetext/csv ) else: st.info(暂无符合条件的订单)这段代码展示了 Streamlit 的核心优势st.selectbox、st.date_input等控件天然绑定 Python 变量st.dataframe自动支持列排序点列头、列宽拖拽、横向滚动column_config可以格式化数字、日期提升可读性st.download_button一行实现导出不用写后端接口。注意st.dataframe渲染的是 Pandas DataFrame不是 Prisma 的 model 对象。所以必须用列表推导式把orders转成字典列表再转pd.DataFrame。这是 Streamlit 的约定无法绕过。4.2 构建动态图表用 Prisma 数据驱动 Altair 可视化Streamlit 原生支持多种图表库我推荐 Altair因为它的声明式语法和 Prisma 的查询风格高度一致——都是“描述我要什么而不是怎么画”。我们做一个“各渠道转化率趋势图”。先写查询函数st.cache_data(ttl300) def get_channel_conversion_trend(days: int 30): 获取最近 N 天各渠道转化率 from datetime import datetime, timedelta end_date datetime.now() start_date end_date - timedelta(daysdays) # Prisma 不支持直接 GROUP BY AVG所以用 raw SQL # 但我们可以用 Prisma 的 $queryRaw 方法 sql SELECT c.name as channel_name, DATE(o.created_at) as date, COUNT(*) as orders_count, COUNT(DISTINCT o.user_id) as unique_users, ROUND(COUNT(*)::decimal / NULLIF(COUNT(DISTINCT o.user_id), 0) * 100, 2) as conversion_rate FROM orders o JOIN channels c ON o.channel_id c.id WHERE o.created_at $1 AND o.created_at $2 GROUP BY c.name, DATE(o.created_at) ORDER BY c.name, date # Prisma Python 客户端支持 raw query results prisma.query_raw( sql, start_date, end_date ) # 转成 Pandas DataFrame import pandas as pd return pd.DataFrame(results)prisma.query_raw是 Prisma 的“逃生舱口”当你需要复杂聚合、窗口函数时就用它。这里我们用原生 SQL 计算转化率订单数 / 独立用户数并按渠道和日期分组。然后用 Altair 画图import altair as alt st.subheader(渠道转化率趋势) # 获取数据 df get_channel_conversion_trend(days14) if not df.empty: # Altair 图表 chart alt.Chart(df).mark_line(pointTrue).encode( xalt.X(date:T, title日期), yalt.Y(conversion_rate:Q, title转化率 (%)), coloralt.Color(channel_name:N, title渠道), tooltip[date, channel_name, conversion_rate] ).properties( width700, height400 ).interactive() # 支持缩放、平移 st.altair_chart(chart, use_container_widthTrue) else: st.warning(暂无转化率数据)mark_line(pointTrue)画折线图并标出数据点encode定义坐标轴和颜色interactive()让图表可缩放这对看趋势很重要。use_container_widthTrue确保图表填满列宽。实操心得Altair 的学习曲线比 Matplotlib 平缓因为它的 API 更接近“说人话”。比如xdate:T表示日期类型yconversion_rate:Q表示定量数值colorchannel_name:N表示分类变量。Prisma 的find_many返回对象Altair 的Chart接受 DataFrame两者通过 Pandas 无缝衔接。4.3 实现表单提交从 Streamlit 输入到数据库写入前面都是读数据现在看怎么写。我们加一个“新增渠道”功能让用户在页面上填渠道名和转化率点提交就存到数据库。Streamlit 表单用st.formst.subheader(新增渠道) # 创建表单 with st.form(add_channel_form): col1, col2 st.columns(2) with col1: new_channel_name st.text_input(渠道名称, placeholder例如微信公众号) with col2: new_conversion_rate st.number_input(转化率 (%), min_value0.0, max_value100.0, value2.5, step0.1) submitted st.form_submit_button(✅ 提交) # 处理提交 if submitted: if not new_channel_name.strip(): st.error(渠道名称不能为空) else: try: # Prisma 写入 channel prisma.channel.create( data{ name: new_channel_name.strip(), conversionRate: new_conversion_rate } ) st.success(f✅ 渠道 {channel.name} 添加成功ID: {channel.id}) # 刷新页面显示新数据 st.cache_data.clear() st.experimental_rerun() except Exception as e: st.error(f❌ 添加失败{str(e)})st.form保证表单内所有输入在点击提交前不触发重运行避免误操作。prisma.channel.create(data{...})是标准的创建语法data字典的键必须和 Schema 里定义的字段名一致name、conversionRate。这里有个隐藏技巧st.experimental_rerun()后页面重载但st.text_input的值会清空用户体验好。如果你用st.button而不是st.form_submit_button输入框的值可能残留造成混淆。注意生产环境一定要加权限控制。这段代码没有鉴权任何访问者都能添加渠道。实际项目中应该在if submitted:前加if st.session_state.get(is_admin):并通过登录流程设置st.session_state.is_admin True。Streamlit 的st.session_state是页面级状态适合存用户身份。4.4 错误处理与连接池管理避免线上事故最后也是最重要的是健壮性。我见过太多 Streamlit 应用本地跑得好好的一上生产就报Connection refused或Too many connections。根源都在连接管理。Prisma 连接池配置在prisma/schema.prisma的datasource块里加连接池参数