用 .NET 8 + uni-app 做一套多租户畜牧 SaaS:我们在秦巴牧云踩过的 6 个坑

对工时评估的思考:当AI颠覆了coding效率 在AI时代,“人日”作为软件工时度量衡已然失效。开发的价值核心正从“编码实现”转向“深度思考”与“精准沟通”——即如何将人类的模糊需求精确地传达给AI。当最有价值的“思想工作”变得最难量化时,传统的项目管理范式随之动摇。我们必须超越对过程的算计,开始探索衡量思想、沟通与业务价值的新标尺。 阅读详情

一、先交代背景:我们搭了什么

大家好,我是码农刚子。秦巴牧云(QinbaPastureCloud)是一套面向富硒畜牧养殖场的多租户 SaaS。一个服务商可以管多个农场,每个农场只看到自己的数据。整体技术栈:

  • 后端:ASP.NET Core 8(MVC 风格 API)+ EF Core 8 + SQL Server / SQLite 双 Provider;
  • Web 后台:Blazor(.NET 8),直接项目引用后端 DTO,强类型到底;
  • 小程序:uni-app(Vue3),编译到微信小程序;
  • AI 能力:微信小程序「开发模式」beta,把业务封装成一组 Skill(原子接口 + 原子组件)。

这套组合在产业互联网里很典型:重后台 + 轻前端 + 微信触达。但落到代码里,有几个坑是我们真正掉进去、又爬出来的。下面按"价值密度"排序分享。本文源自:码录集 - CoderLog 中文技术博客

二、坑 1:多租户隔离,别让客户端自选租户

多租户最危险的写法,是"前端传个租户 ID,后端就信"。我们早期也差点这么干。

正确做法是租户身份只从认证上下文取:

// TenantProvider:仅 Admin 才允许通过请求头切换租户
public string? ResolveTenantId(ClaimsPrincipal user, IHeaderDictionary headers)
{
    var tid = user.FindFirst("tid")?.Value;          // JWT 里的租户键,最高优先级
    if (user.IsInRole("Admin") && headers.TryGetValue("X-Tenant-Id", out var h))
        return h!;                                     // 只有平台管理员能越租户查看
    return tid;                                        // 普通用户一律以 JWT 为准
}

配套两条铁律:

  1. 全局查询过滤器合并软删除与租户:query = query.Where(e => e.TenantId == tid && !e.IsDeleted);
  2. 任何"让客户端自选租户"的改动,都必须守住 X-Tenant-Id 仅 Admin 可信这条线——否则任意客户端可越权读别人农场的牛。

角色层级也别含糊:Admin(0) 是平台级超级管理员,权限高于单租户的 Owner。授权统一写 User.IsInRole("Owner") || User.IsInRole("Admin"),前端对应 IsOwnerOrAdmin,避免散落的魔法字符串。

三、坑 2:前后端序列化契约,前端别用数字比对枚举

后端 .NET 8 统一用 CamelCase + JsonStringEnumConverter,意味着:

  • 属性全 camelCase;
  • 枚举一律以字符串输出(OnFarm / Cattle / Male / Income / Red / Estrous …)。

我们最庆幸的一个决定:Web 端 ApiClient 直接项目引用后端的 DTOs 与 Domain.* 枚举。于是字段一致性由 C# 编译器保证,结构上不可能错位——这是 Blazor 相比 JS 前端的天然优势。

但小程序是 JS 宽松类型,必须人肉对齐。我们立了几条前端红线(写进 AGENTS.md 让 AI 也遵守):

  • 禁止用数字/中文比对枚举,必须比 === 'OnFarm';
  • 禁止用旧字段名(earTagId → earTagCode、breed → breedName、birthDate → birthDateUtc、weight → entryWeightKg);
  • ApiResponse<T> 结构是 {code, message, data, timestamp},消费前先 res.code === 200 && res.data;
  • amount 这类 decimal 在后端的字符串,前端要 parseFloat。

教训:契约不是文档,是两人之间的暗号。一端改了枚举顺序或字段名,另一端不报错但悄悄出错,比编译错误更难查。

四、坑 3:record 位置参数的 DTO,序列化后字段名短得吓人

最阴间的一个 bug。健康看板的体温点 DTO 长这样:

public record TemperaturePointDto(DateTime T, double TempC);

camelCase 序列化后,字段变成了 t 和 tempC——不是 recordedAtUtc / temperatureC。前端照着实体属性名去读 t.temperatureC / t.recordedAtUtc,拿到 undefined,于是体温图高度算成 NaN%、日期标签 NaN/NaN。

修复很简单,但教训在流程:

前端取数必须严格对照 DTO 定义的参数名,切忌按实体属性名臆测。 尤其是"图/曲线单点"这类位置参数 record,字段名往往极短。

我们在 health.vue 里改成读 t.tempC / t.t,并加了 isFinite 防御 + 高度夹取(8%~100%)+ 柱上显示数值,杜绝 NaN 残留。

五、坑 4:开发改了字段,发布到线上"没了"

这是产业项目最高频、也最隐蔽的事故。根因在 Program.cs 按 Provider 分流:

if (dbOpts.IsSqlite)
    db.Database.EnsureCreated();   // 开发:按当前模型自动建/改表
else
    db.Database.Migrate();          // 生产:只认 EF 迁移

EnsureCreated() 的语义是:数据库文件不存在才建表,存在就什么都不做,绝不 alter 已有表。所以——

  • 开发时你给 Tenant 加了 TenantKey / Version,本地库被重建过(或恰好新建),本地有这两列;
  • 线上的旧 SQLite 文件在模型变更前就存在 → EnsureCreated 看到文件在、跳过 → 缺列;
  • 表现就是"开发能看到、线上缺失",而且因为迁移异常被 try/catch 吞掉只记日志,应用还不报错,只是字段缺。

修复(测试环境):删掉线上旧 SQLite 文件,重启让 EnsureCreated 用最新模型重建(代价是清空测试数据,但播种会重跑)。

根治建议:开发 / 测试 / 生产统一走 Migrate(),改实体必 Add-Migration + Update-Database,三套环境 schema 一致,不再漂移。代价是现有 EnsureCreated 库无 __EFMigrationsHistory,需先删库让 Migrate 从 Initial 全新建。

另外,把"迁移失败即 Fatal 退出"加上——沉默的失败比崩溃更贵。

六、坑 5:Razor 里的 @page 不是你想的那个 page

给预警列表加分页时,分页条页号变量叫 page,标记区原样写 @page:

@page / @(page)   ← 编译报 RZ2005/RZ1016

Razor 把 @page 当成路由指令解析了。正确写法是用括号包起来:@(page)。一个括号,半下午。

类似的 Razor 泛型坑:@EnumHelper.GetDisplayName<AppRole>(x) 里 <AppRole> 会被当成 HTML 标签(RZ9980)。解法:把泛型调用移进 @code 方法,标记区只调无泛型参数的方法。

七、坑 6:微信 AI 开发模式——原子接口要返回"三层"

微信小程序 AI 开发模式(beta)要求我们把自己的业务封装成 Skill。一个 Skill = mcp.json(能力声明)+ index.js(注册)+ apis/(原子接口)+ components/(原子组件,页面接力渲染卡片)。

原子接口的返回我们沉淀成三层契约,框架才好消费:

return {
  isError: false,
  content: [{ type: 'text', text: '摘要文案(给对话流)' }],
  structuredContent: { /* 卡片真正消费的强结构数据 */ items: [...] },
  _meta: { /* 可选调试信息 */ }
};

卡片组件只读 structuredContent.items(或 detail.* / summary.*),绝不依赖 content 里的自然语言去解析数据——自然语言是给人看的,结构化数据才是给组件的。

关键工程纪律(这些写进 SKILL 的 description 高权重区,连 AI 都强制遵守):

  1. 业务 ID 必须来自后端真实返回,禁止推断或编造(尤其溯源码,必须用户明确提供);
  2. 写操作(饲喂录入、预警处置)先二次确认;
  3. 失败如实说,不假装成功。

我们用 uni-app 的 vite 插件桥接:编译 mp-weixin 时,仅在 NODE_ENV !== 'production' 注入 app.json 的 agent 配置并拷贝 skills/ 分包;生产审核版本自动跳过——beta 能力绝不进审核包,这是平台红线。

八、我们在工程上做对的三件事

踩坑之外,也有几个现在看很值的决定:

  1. Web 强类型引用后端 DTO:Blazor 端字段一致性由编译器兜底,24 个页面逐字段核对过零不一致。JS 前端则靠 AGENTS.md 红线条例 + 静态核验兜底。
  2. DemoDataSeeder 回填基线:MVP 演示不用等 7 天硬件时序,直接回填 Baseline*C / Activity / ComputedAtUtc 跳过等待期,一键 POST /api/dev/seed-demo 造出 10 头牲畜 + 历史体温 + 触发预警,联调和演示都稳。
  3. IoT 用 MockDeviceAdapter 解耦:硬件未到位时 mock 跑通全链路,适配器抽象让真实设备后续无痛接入。

九、给同样在做产业 SaaS 的你

如果只带走一句话:

产业系统的复杂度,八成不在功能,而在"数据可信"与"环境一致"。 租户别让客户端自选、枚举别让前端猜、字段别让开发库替生产库背锅、AI 别替用户编 ID。

秦巴牧云还在长。上面每一个坑,都是从一次真实的"线上缺字段 / 图表 NaN / 越权告警"里抠出来的。希望它们帮你省下几个下午。

Unity UGUI深度优化 Unity UGUI优化相关 阅读详情

相关推荐

【TiktokAPI】Tiktok直播API与实时互动功能开发完全指南

Tiktok直播API允许开发者创建、管理直播,并实现各种互动功能。/*** 获取Tiktok直播API访问令牌* @param {string} client_id - 应用ID* @param {string} client_secret - 应用密钥* @param {string} code - 授权码* @param {string} redirect_uri - 重定向URI* @returns {Promise<object>} 访问令牌响应*/try {}),

Mageia的专栏 3202

.NET Core 中采用独立数据库的SAAS(多租户)方法

本文介绍了多租户架构及其在.NET Core中的实现方案。多租户架构允许单个软件实例服务多个客户,提供数据隔离和资源共享。文章重点讨论了三种实现方式:共享数据库、共享数据库单独模式、独立数据库。作者详细演示了在.NET Core中实现独立数据库方案的技术细节,包括创建租户表、通过请求头获取连接字符串、动态配置DbContext等关键步骤。最后指出可通过缓存优化性能,并预告将探讨内存缓存和Redis缓存的实现。该方案为构建可扩展的多租户系统提供了实用参考。

csdn_aspnet的专栏,请点击博客主页右上角三个点中的私信联系 1331

预处理详解(三)—— 命令行定义 + 条件编译 + 文件包含

文章目录前言 前言

2021dragon的博客 2189

微软技术日报 11-09:.NET 8 与 9 明日同时停止支持,注册表编辑器出了 WinUI 3 版

今天这四条消息的共同点是收口。.NET 8 和 9 的支持期到点,注册表编辑器的新界面只放在仓库里给人看,GitHub 把一类危险触发器按住默认,微软在安全报告里把防线从地址黑名单挪到行为。收口并不等于关门。.NET 10 还在支持期里,被按住的触发器可以用策略按文件放回来,实验室里的样例随时可能变成产品。区别在于这些动作要有人签字,不再是默认开着。如果今天只做一件事,去看一眼构建机上的 .NET 版本。明天之后,8 和 9 的漏洞补丁不会再来了。

海盗Sharp的博客 370

Excel转LaTeX表格神器:excel2latex.xla插件使用全攻略(含下载链接与配置教程)

本文详细介绍了Excel转LaTeX表格神器excel2latex.xla插件的完整使用攻略。从下载安装、基础转换到高级配置,深入讲解了如何利用该插件高效生成LaTeX代码,特别针对IEEE双栏等复杂排版需求提供了专业解决方案,帮助用户彻底告别手动编码,提升学术与专业文档的排版效率。

app77的博客 295

VC Spyglass:Lint实战指南之TCL脚本自动化解析

本文详细介绍了如何利用TCL脚本实现VC Spyglass Lint检查的自动化流程。通过实战指南,从基础脚本编写到高级参数化技巧,帮助数字IC设计工程师摆脱手动操作,提升验证效率,并实现与CI/CD流程的集成。

h3i4j的博客 798

11-01-演进-.NET GC发展史

.NET GC发展史映射硬件演进:从单核STW到多核并发,再到区域化细粒度回收。历经分代管理、ServerGC、BackgroundGC、LOH自动压缩、POH隔离、DATC自适应线程数及Region-based管理等关键革新,代码量从5000行增至4万+,复杂度指数上升。每一步优化均在暂停时间、吞吐量与资源开销间权衡,体现“实用主义”哲学。核心启示:GC演进由硬件驱动,方向明确——更短暂停、更高效率,但代价是工程复杂性持续攀升。

设计 AI 玩游戏🎮 382

学Simulink--基于机器人与自动化系统的场景实例:六自由度机械臂的逆运动学控制仿真

摘要:本文详细介绍了在Simulink中实现六自由度机械臂逆运动学控制的仿真方法。通过构建包含机械臂模型、路径规划器、逆运动学求解器和控制器等模块的系统,利用MATLAB Robotics工具箱进行建模。文章逐步讲解了从模型创建、轨迹规划、逆运动学求解到控制器设计的完整流程,并提供了性能评估标准。该方法能有效验证逆运动学算法,优化机械臂控制策略,提高操作精度和效率,为机器人自动化系统开发提供实用参考。

xiaoheshang_123的博客 231

微软技术周报 11-02~11-09:VS Code 混装 Claude 与 Codex、EF Core 11 静默改行为、.NET 11 倒计时

11 月 6 日,Power Platform 通过消息中心通知管理员,Copilot Studio、Power Automate 和 Power Apps 里所有跑在 Anthropic Claude、xAI Grok 以及旧版 GPT 上的 prompt,12 月 2 日自动迁移到 GPT-6 系列,档位价格不变,模型选择收窄到一条产品线。效果是这样的:让 Claude 评审一个 Copilot chat 写出的方案,或者把同样的任务交给 Codex,两边并排跑在同一个会话里。更要紧的是下周的时间线。

海盗Sharp的博客 936

手把手教你学Simulink--智能控制算法的控制算法领域仿真建模实例:基于Simulink的遗传算法优化控制仿真模型

通过上述步骤,你可以设计并仿真一个基于Simulink的遗传算法优化控制模型。这种方法适用于许多复杂的优化场景,尤其是当传统方法难以找到最优解时。随着经验的积累,你可以进一步优化遗传算法的参数设置,或者结合其他智能优化技术(如粒子群优化、差分进化等)提升优化性能。

MHD0815的博客 208

用python画带有正负值的条形图

常见的条形图只能表现数据的大小,却不能表现值的正负关系。但是其实仅仅使用`pandas`和`matplotlib`两个包就可以实现y轴负轴的添加以及负值的显示,并且可以为正负值分别设置不同的颜色,让条形图看上去更加清晰,易于我们分析

weixin_44853840的博客 5384

.NET 11 - 尝试一下Blazor中的新特性SupplyParameterFromTempData

时光匆匆,.NET Conf 2026大会将会在2026年11月份举办(11/10-11/13),到时微软将会发布.NET11,这是一个STS版本(Standard Term Support), 微软提供两年支持。当前.NET 11已经处于RC3版本(Release Candidate 3),很多新特性已经被发布出来了。

limingdinghao的博客 237

【Docker】一招解决超时镜像超时报错:`ERROR: Get https://registry-1.docker.io/v2/`

Docker 镜像拉取超时或失败并不是 Docker 本身的缺陷,而是网络环境和默认配置在特定场景下的限制所导致的结果。通过本文的分析可以看到,问题的核心往往集中在三个方面:网络访问延迟、DNS 解析速度以及客户端默认超时时间。针对这些问题,最直接、有效的做法是使用国内镜像加速器,这不仅能够显著提升拉取速度,还能保证在企业内网或家庭网络中更加稳定

博客 1028

在 .NET 10 中使用 Keycloak.Admin.Sdk 管理 Keycloak:从基础接入到功能开关

面向 .NET 10 / Native AOT 的 Keycloak Admin REST API 客户端,当前仅支持 Keycloak 26.8.1。主包引用 Microsoft.Extensions.Http 10.0.14 和 Microsoft.Extensions.Http.Resilience 10.11.0,使用源生成 JSON 元数据,默认关闭反射序列化。仓库固定 .NET SDK 10.0.403。

ChaITSimpleLove的博客 377

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

基于.NET的Windows窗体编程之WinForms数据持久化(1)

本文介绍了在.NET Windows窗体开发中,基于EFCore与SqlSugar两种主流ORM框架实现数据持久化的实践。通过创建实体模型、配置DbContext或ConnectionConfig,结合增删改查操作,展示了如何高效完成数据库交互。EFCore适合复杂CRUD场景,支持LINQ与迁移;SqlSugar则以简洁API和高性能著称,适用于快速开发。两者均显著降低SQL编写量,提升代码可维护性,助力开发者专注业务逻辑。

绳锯木断,水滴石穿,专心写文,无问西东!!! 206

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

FFmpeg × C# 实战:从命令行原理到 .NET 工程化落地

本文系统讲解 FFmpeg 的核心概念(容器/编码/流/滤镜/时间基),并给出 C# 对接的五条技术路线对比。重点实现一个可落地的 FFmpegRunner 封装,解决管道死锁、结构化进度解析、取消超时、二进制分发与参数注入五大工程问题;随后覆盖转码、裁剪、合并、抽帧、限体积压缩、水印、硬件加速六大高频场景,并附安全清单与排错速查表。

李赛赛的专栏 655

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

一款基于 .NET 开源、免费的数据库自治诊断平台

DBPilot。它解决的是 DBA 日常工作中最耗时的痛点:性能趋势分析、Top SQL 定位、阻塞与死锁排查,原本散落在多个工具和脚本里的活,现在一个 Web 控制台全搞定。DBPilot 是一个自托管的数据库自治诊断平台。它持续采集实例的性能数据,在同一个 Web 控制台里完成日常 DBA 工作流:性能趋势与 AAS 负载拆解、Top SQL、执行计划变更跟踪、缺失索引建议、索引使用与碎片、阻塞分析、死锁分析、慢日志。

专注于C#/.NET/.NET Core学习、工作、面试干货和实战教程分享。这里聚焦了大量的C#/.NET/.NET Core优质文章、开源项目、实用工具和学习、工作、面试心得。 184

.NET 11 - 尝试一下Blazor中Virtualize组件的Scroll新特性

2026年11月10日,微软发布了.NET 11正式版!这是一个STS版本, 微软提供两年支持。同时.NET 8 和 .NET 9 在 2026 年11月10日开始结束支持。.NET11处于Release Candidate 3阶段的时候,只支持在Visual Studio 2026 Insiders 和 Visual Studio Code(安装了C# Dev Kit.NET extension)中创建.NET11项目。

limingdinghao的博客 158

Keycloak.Admin.Sdk 26.8.1-preview.3 发布:从管理 API 到应用协议的 .NET 10 SDK

如果你的项目正在使用 Keycloak 26.8.1,欢迎通过 NuGet 体验 **26.8.1-preview.3**,也欢迎提交具体接口、协议或运行环境下的使用反馈。

ChaITSimpleLove的博客 351

按Ctrl键会引发几次按键消息

                                                                           何志丹(一),新建一个单文档工程CtrlEg。(二),为CCtrlEgView的WM_KEYDOWN消息和WM_KEYUP消息添加响应函数。具体代码如下:void CCtrlEgView::OnKeyDown(UINT nChar, UINT nRe

闻缺陷则喜何志丹 1853

C# + .NET 8 + WPF + HALCON25.11

在解决方案资源管理器,右键→【设为启动项目】顶部菜单:【生成】→【生成解决方案】,看输出窗口,无报错按F5启动调试点击按钮【加载测试图】:图片显示,缺陷自动画红框,表格输出坐标。

qq_36657291的博客 294

微软技术日报 2026-11-11:补丁日 1093 个漏洞、Outlook 零日在野、.NET 11 正式发布

把这几条放在一起看,会发现它们指向同一个方向,留给维护的窗口在变短。补丁日一次一千多条、Azure Linux 一次四百多条 CVE 引用、ACL 把代码完整性做成可选审计再加一层默认镜像、.NET 把两个版本的支持期放在同一天结束,做的都是同一件事,把「以后再说」往后压。要做的事不算复杂。把对公网暴露的 Exchange 与 SharePoint 排进本周的窗口,Exchange 那条要预留重启时间;

海盗Sharp的博客 617

微软技术日报 11-10:.NET 11 今晚发布,.NET 8 与 9 同日停更,Copilot 连接器开写入

今天微软技术侧的重心全压在一个日期上。11 月 10 日既是 .NET Conf 2026 开幕、.NET 11 正式发布的日子,也是 .NET 8 与 .NET 9 终止支持的日子。同一个日期上,一条线开始,两条线结束。除了这条主线,还有三件事在这一两天落地。微软安全博客公布了一轮用智能体扫 Rust 标准库的研究,Copilot 连接器的写入能力转为正式可用,Windows 11 的统一应用更新页又在预览版里冒了头。

海盗Sharp的博客 894

基于 Spring Boot + Vue 3 + uni-app 的灵活用工系统:四端架构与业务流程设计

本文介绍一个基于Java 17、Spring Boot与Vue 3的灵活用工平台实现,通过四端(平台管理、企业PC/移动端、灵工移动端)协同,完整串联任务发包、报名录用、交付验收、结算支付与凭证查询全流程。系统以项目-发包-报名-订单为主线,保障数据一致性,关键设计包括权限隔离、事务控制、金额快照与操作审计。技术上采用微服务分层架构,共享业务定义确保多端状态统一。适用于需精细化管理外部协作任务的场景,支持后续对接电子签约、税务申报等第三方服务,实现从任务发布到完税凭证的全链路可追溯闭环。

weixin_42648890的博客 188

(开源)游戏攻略分享系统实战指南 基于 Java + SpringBoot + uni-app + Vue + MySQL

这是一个基于 Java + SpringBoot + uni-app + Vue + MySQL 构建的完整游戏攻略分享系统,功能持续优化中...游戏攻略管理 游戏分类管理 旅游资讯管理 用户管理 管理员管理

小小码农身兼魔法师职位!除你武器~ 365

uniapp/Flutter 打包上架苹果,频繁触发 4.3 (a)?原因和避坑方案

根本原因: 跨端框架打包后,产出的 IPA 有大量固定框架特征、资源结构、二进制特征,机审很容易判定为模板批量生成的马甲包。哪怕你是独立开发,没有做马甲矩阵,也会被机审误判。uniapp、Flutter 开发的 App,上架非常容易命中 4.3 (a) 审核拒绝,是很多开发者的共性难题。很多团队的操作:一套源码改改图片、文字,生成多个包提交,这种是 4.3a 高危行为,直接命中苹果规则。如果你是 uniapp/flutter 开发者,上架遇到 4.3a 卡审,可以交流整改经验。✅ 提前预防方案(上架前做)

wuxianda1030的博客 443
上一篇: DeepSeek Harness 爆火但安装劝退?一键启动器来了,不懂编程也能用
下一篇: ABP vNext 框架系列第1篇:企业级 .NET 开发,别再从头造轮子了
码农刚子.
博客等级 码龄8年 555粉丝 129原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农刚子.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值