ABP vNext 多租户系统实现登录页自定义 Logo 的最佳实践

🚀 ABP vNext 多租户系统实现登录页自定义 Logo 的最佳实践


🧭 版本信息与运行环境

  • ABP Framework:v8.1.5
  • .NET SDK:8.0
  • 数据库:PostgreSQL(支持 SQLServer、MySQL 等)
  • BLOB 存储:本地/OSS/Azure Blob(推荐启用 CDN)
  • 部署建议:支持单库/多库多租户结构,推荐搭配 CI/CD 自动迁移

📚 目录

  • 🚀 ABP vNext 多租户系统实现登录页自定义 Logo 的最佳实践
    • 🧭 版本信息与运行环境
    • 🔍 实现背景
    • 📦 总体思路与流程图
    • 🧱 步骤一:扩展租户实体添加 Logo 字段
      • 🔧 实体扩展配置流程
    • 🖼 步骤二:上传并持久化租户 Logo
      • 📥 Logo 上传与持久化流程
    • 🌐 步骤三:登录页动态加载租户 Logo
      • ⚙️ 登录页 Logo 加载流程
      • 🏗️ CI/CD 自动迁移流程
    • 🧠 最佳实践建议


🔍 实现背景

ABP vNext 提供完备的多租户能力,支持根据请求自动切换上下文。在实际项目中,为每个租户提供个性化登录 Logo 是增强品牌感、提升用户体验的重要方式。


📦 总体思路与流程图

使用 ABP 的实体扩展和 Blob 模块,结合租户解析机制,动态加载 Logo:

访问登录页
TenantResolutionMiddleware
当前租户 ID 是否为空?
调用 AppService 获取 Logo URL
使用默认 Logo
渲染 Logo 图片

🧱 步骤一:扩展租户实体添加 Logo 字段

🔧 实体扩展配置流程

应用启动
触发 PreConfigureServices
调用 MyTenantEntityExtension.Configure()
ObjectExtensionManager 注册 LogoUrl 属性
生成 EF Core Migration 包含 LogoUrl 字段

💡 推荐位置*.Domain.Shared + *.Domain

// MyTenantConsts.cs
public static class MyTenantConsts
{public const string LogoUrlPropertyName = "LogoUrl";
}
// MyTenantEntityExtension.cs
public static class MyTenantEntityExtension
{public static void Configure(){ObjectExtensionManager.Instance.Modules().ConfigureSaas(sa =>{sa.ConfigureTenant(t =>{t.AddOrUpdateProperty<string>(MyTenantConsts.LogoUrlPropertyName);});});}
}

在模块类中 PreConfigureServices 阶段调用:

public override void PreConfigureServices(ServiceConfigurationContext context)
{MyTenantEntityExtension.Configure();
}

确保迁移脚本包含新字段


🖼 步骤二:上传并持久化租户 Logo

📥 Logo 上传与持久化流程

前端页面 TenantUiAppService BlobContainer ITenantRepository UploadLogoAsync(tenantId, file) 校验文件大小和格式 SaveAsync(blobPath, stream) 保存成功 GetAsync(tenantId) 返回 Tenant 实体 UpdateAsync(tenant.SetProperty) 持久化完成 返回带签名的下载 URL 前端页面 TenantUiAppService BlobContainer ITenantRepository

💡 建议封装至 Application Service:

public class TenantUiAppService : ApplicationService
{private readonly IBlobContainer _blobContainer;private readonly ITenantRepository _tenantRepository;public TenantUiAppService(IBlobContainer blobContainer, ITenantRepository tenantRepository){_blobContainer = blobContainer;_tenantRepository = tenantRepository;}public async Task<string> UploadLogoAsync(Guid tenantId, IFormFile file){if (file.Length > 1024 * 1024)throw new UserFriendlyException("文件不能超过 1MB");if (!file.ContentType.StartsWith("image/"))throw new UserFriendlyException("请上传 PNG 或 JPG 图片");var blobPath = $"{tenantId}/logo.png";using var stream = file.OpenReadStream();await _blobContainer.SaveAsync(blobPath, stream, overrideExisting: true);var tenant = await _tenantRepository.GetAsync(tenantId);tenant.SetProperty(MyTenantConsts.LogoUrlPropertyName, blobPath);await _tenantRepository.UpdateAsync(tenant); // 持久化return await _blobContainer.GetDownloadUrlAsync(blobPath); // 带签名 URL}
}

🌐 步骤三:登录页动态加载租户 Logo

⚙️ 登录页 Logo 加载流程

用户访问登录页
TenantUiQueryService.GetLogoUrlAsync()
CurrentTenant.Id 是否为空
返回默认 Logo 路径
从 Repository 获取 Tenant
ExtraProperties 中是否有 LogoUrl
BlobContainer.GetDownloadUrlAsync(rawPath)
返回签名 URL
前端 渲染

封装服务接口:

public class TenantUiQueryService : ApplicationService
{private readonly ITenantRepository _tenantRepository;private readonly IBlobContainer _blobContainer;public TenantUiQueryService(ITenantRepository tenantRepository, IBlobContainer blobContainer){_tenantRepository = tenantRepository;_blobContainer = blobContainer;}public async Task<string> GetLogoUrlAsync(){var tenantId = CurrentTenant.Id;if (tenantId == null)return "/images/default-logo.png";var tenant = await _tenantRepository.GetAsync(tenantId.Value);if (!tenant.ExtraProperties.TryGetValue(MyTenantConsts.LogoUrlPropertyName, out var rawPath) ||rawPath == null){return "/images/default-logo.png";}return await _blobContainer.GetDownloadUrlAsync(rawPath.ToString());}
}

前端渲染:

<img src="@logoUrl" onerror="this.src='/images/default-logo.png'" class="login-logo" />

🏗️ CI/CD 自动迁移流程

CI/CD Pipeline Trigger
Checkout 代码
Restore NuGet 包
Build 项目
为每个物理库执行迁移脚本
Update-Database 或 flyway migrate
部署新版本到目标环境

🧠 最佳实践建议

分类建议
性能租户 Logo 可使用 IDistributedCache 缓存
用户体验加载失败 fallback 默认图
可扩展性建议将租户个性化信息封装至 TenantUiCustomization 模块
安全Blob 下载 URL 推荐使用带签名的临时访问
发布配合数据库迁移工具(Flyway、DbUp)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/80390.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FastDFS分布式文件系统架构学习(一)

FastDFS分布式文件系统架构学习 1. FastDFS简介 FastDFS是一个开源的轻量级分布式文件系统&#xff0c;由淘宝资深架构师余庆设计并开发。它专为互联网应用量身定制&#xff0c;特别适合以中小文件&#xff08;如图片、文档、音视频等&#xff09;为载体的在线服务。FastDFS不…

基于单片机的防盗报警器设计与实现

标题:基于51单片机的防盗报警器设计 内容:1.摘要 本文围绕基于51单片机的防盗报警器设计展开。背景在于现代社会安全需求不断提高&#xff0c;传统防盗方式存在诸多不足。目的是设计一款成本低、可靠性高且易于使用的防盗报警器。方法上&#xff0c;以51单片机为核心控制单元&…

IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 LiteOS Studio + GCC + JLink

文章目录 概述LiteOS Studio不推荐&#xff1f;安装和使用手册呢?HCIP实验的源码呢&#xff1f; 软件和依赖安装软件下载软件安装插件安装依赖工具-方案2依赖工具-方案1 工程配置打开或新建工程板卡配置组件配置编译器配置-gcc工具链编译器配置-Makefile脚本其他配置编译完成 …

【高斯拟合最终篇】Levenberg-Marquardt(LM)算法

Levenberg-Marquardt(LM)算法是一种结合高斯-牛顿法和梯度下降法的优化方法,特别适合非线性最小二乘问题,如高斯函数拟合。它通过引入阻尼因子(damping factor)平衡高斯-牛顿法的快速收敛和梯度下降法的稳定性。以下是基于之前的 gaussian_fit.py,加入 LM 算法实现高斯拟…

信道编码技术介绍

信息与通信系统中的编码有4 种形式&#xff1a;信源编码、信道编码、密码编码和多址编码。 其中信道编码的作用是对信源经过压缩后的数据加一定数量受到控制的冗余&#xff0c;使得数据在传输中或接收中发生的差错可以被纠正或被发现&#xff0c;从而可以正确恢复出原始数据信息…

线性回归策略

一种基于ATR(平均真实范围)、线性回归和布林带的交易策略。以下是对该策略的全面总结和分析: 交易逻辑思路 1. 过滤条件: - 集合竞价过滤:在每个交易日的开盘阶段,过滤掉集合竞价产生的异常数据。 - 价格异常过滤:排除当天开盘价与最高价或最低价相同的情况,这…

WordPress Relevanssi插件时间型SQL注入漏洞(CVE-2025-4396)

免责声明 本文档所述漏洞详情及复现方法仅限用于合法授权的安全研究和学术教育用途。任何个人或组织不得利用本文内容从事未经许可的渗透测试、网络攻击或其他违法行为。使用者应确保其行为符合相关法律法规,并取得目标系统的明确授权。 对于因不当使用本文信息而造成的任何直…

支持selenium的chrome driver更新到136.0.7103.94

最近chrome释放新版本&#xff1a;136.0.7103.94 如果运行selenium自动化测试出现以下问题&#xff0c;是需要升级chromedriver才可以解决的。 selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only su…

附加:TCP如何保障数据传输

附加&#xff1a;TCP如何保障数据传输 LS-NET-012-TCP的交互过程详解 TCP 如何保障数据传输 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是互联网核心协议之一&#xff0c;负责在IP网络上提供可靠的、面向连接的数据传输服务。它位于T…

Unity 批量将图片从默认类型改为Sprite类型

先将该脚本放到Editor目录下 如何使用:选中目录,然后点击Tool里面的批量修改按钮 using System; using UnityEngine; using UnityEditor; using System.IO; using System.Linq;/// <summary> /// 此工具可以批量将图片类型修改为精灵 /// </summary> public clas…

2025认证杯数学建模C题思路+代码+模型:化工厂生产流程的预测和控制

2025认证杯数学建模C题思路代码模型&#xff0c;详细内容见文末名片 在化工厂的生产流程中&#xff0c;往往涉及到多个反应釜、管道和储罐等设备。在 流水线上也有每个位置的温度、压力、流量等诸多参数。只有参数处于正常范 围时&#xff0c;最终的产物才是合格的。这些参数…

Rust 学习笔记:关于 HashMap 的练习题

Rust 学习笔记&#xff1a;关于 HashMap 的练习题 Rust 学习笔记&#xff1a;关于 HashMap 的练习题以下代码能否通过编译&#xff1f;若能&#xff0c;输出是&#xff1f;以下代码能否通过编译&#xff1f;若能&#xff0c;输出是&#xff1f; Rust 学习笔记&#xff1a;关于 …

Vue-事件修饰符

事件修饰符 prevent &#xff08;阻止默认事件&#xff09; 超链接 点击事件 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>事件修饰符</title><!-- 引入Vue --><script …

LeetCode --- 156双周赛

题目列表 3541. 找到频率最高的元音和辅音 3542. 将所有元素变为 0 的最少操作次数 3543. K 条边路径的最大边权和 3544. 子树反转和 一、找到频率最高的元音和辅音 分别统计元音和辅音的出现次数最大值&#xff0c;然后相加即可&#xff0c;代码如下 // C class Solution {…

告别 pip:使用 uv 加速你的 Python 包管理

使用 uv:更快的 Python 包管理工具 随着 Python 生态的演进,包管理工具也在不断升级迭代。uv 是 Astral(同样维护 ruff 的团队)推出的下一代 Python 包与项目管理器,主打 单一可执行文件、极致性能,可在多数场景下取代 pip、pip-tools、pipx 与 virtualenv 等传统工具,…

MATLAB学习笔记(七):MATLAB建模城市的雨季防洪排污的问题

使用 MATLAB 对城市雨季防洪排污问题进行建模与仿真&#xff0c;需要结合数学模型、工程经验和 MATLAB 的数值计算、数据可视化及优化工具。以下是详细的步骤指南&#xff0c;包含实际案例和代码示例&#xff1a; 一、问题分析与建模框架 1. 问题拆解 • 核心目标&#xff1a; …

以项目的方式学QT开发C++(二)——超详细讲解(120000多字详细讲解,涵盖qt大量知识)逐步更新!

API 描述 函数原型 参数说明 push_back() 在 list 尾部 添加一个元素 void push_back(const T& value); value &#xff1a;要添 加到尾部的元 素 这个示例演示了如何创建 std::list 容器&#xff0c;并对其进行插入、删除和迭代操作。在实际应用中&am…

08 web 自动化之 PO 设计模式详解

文章目录 一、什么是 POM二、如何基于 POM 进行自动化框架架构&#xff1f;1、base 层封装2、pageobjects 层封装3、TestCases 层封装 三、元素和方法分离&数据分离1、哪些部分可以进行分离2、示例代码 四、总结 一、什么是 POM POM page object model 页面对象模型 WEB 自…

将 JSON 批量转换为 XML:深度解析与完整实现指南

在数据科学与机器学习项目中&#xff0c;数据预处理始终扮演着不可或缺的角色。尤其当你面对多类别图像标注任务&#xff0c;而标注数据却是以 JSON 形式存在&#xff0c;而目标检测模型却偏好 VOC 格式的 XML 时&#xff0c;这个转换过程就变得极为关键。 本文将带你深入解读…

AlphaEvolve:基于Gemini的算法发现与优化综合报告

引言 • 本报告分析Google DeepMind于2025年5月14日正式发布的AlphaEvolve技术。• AlphaEvolve是一种由Gemini大型语言模型驱动的进化式编码代理&#xff0c;专注于通用算法的发现和优化。• 报告深入探讨AlphaEvolve的技术原理、实际应用及其对未来AI和算法研究的潜在影响。…