白沙黎族自治县网站建设_网站建设公司_UI设计师_seo优化
2025/12/21 21:40:16 网站建设 项目流程

用 .NET MAUI 10 + VS Copilot 从 0 开发一个签到 App(五)

一、本文背景

到目前为止,这个签到 App 已经具备了三个核心能力:

  • 用户可以签到
  • 可以查看自己的签到历史
  • 所有数据都具备多租户隔离

但还缺少一个真正意义上的业务基础能力

用户从哪里来?登录状态如何建立?

因此,项目推进的下一步非常明确:

注册 + 登录

这一篇,我们继续沿用同一个原则:

👉 优先跑通业务闭环,而不是一开始就追求“完美架构”

并且需要特别说明的是:

本文中的注册页面(XAML + Code-behind)全部由 Visual Studio Copilot 生成,没有人工重构。


二、注册功能的真实业务约束

在开始让 Copilot 写代码之前,我心里其实已经有几个非常明确的业务约束:

  1. 用户必须属于某个租户(公司)
  2. 可以选择已有公司,也可以新建公司
  3. 用户名在同一租户下必须唯一

这些约束并不复杂,但已经足够区分“Demo 注册页”和“真实业务注册页”


三、Copilot 生成的注册页面(XAML)

下面是 Copilot 生成的注册页面 UI:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"x:Class="SignInMauiApp.RegisterPage"><VerticalStackLayout Padding="30,60" Spacing="20"><Label Text="用户注册" FontSize="24" HorizontalOptions="Center" /><Picker x:Name="TenantPicker" Title="选择公司" /><Entry x:Name="NewTenantEntry" Placeholder="新建公司(可选)" /><Entry x:Name="UsernameEntry" Placeholder="用户名" /><Entry x:Name="PasswordEntry" Placeholder="密码" IsPassword="True" /><Button Text="注册" Clicked="OnRegisterClicked" /><Label x:Name="ErrorLabel" TextColor="Red" IsVisible="False" /></VerticalStackLayout>
</ContentPage>

这个页面有一个非常“业务化”的设计点:

  • 选择已有租户
  • 或创建新租户

这并不是我事后补的想法,而是 Copilot 在已有 Tenant 模型上下文中,自动推断出来的 UI 结构。


四、Copilot 生成的注册逻辑(Code-behind)

注册页面对应的逻辑代码如下:

public partial class RegisterPage : ContentPage
{private readonly IFreeSql? _fsql;private List<Tenant> _tenants = new();public RegisterPage(){InitializeComponent();_fsql = IPlatformApplication.Current?.Services.GetService<IFreeSql>();LoadTenants();}private void LoadTenants(){_tenants = _fsql!.Select<Tenant>().ToList();TenantPicker.ItemsSource = _tenants.Select(t => t.Name).ToList();int lastTenantId = Preferences.Get("LastTenantId", -1);int idx = 0;if (_tenants.Count > 0){if (lastTenantId > 0){idx = _tenants.FindIndex(t => t.Id == lastTenantId);if (idx < 0) idx = 0;}TenantPicker.SelectedIndex = idx;}}private async void OnRegisterClicked(object sender, EventArgs e){ErrorLabel.IsVisible = false;var username = UsernameEntry.Text?.Trim();var password = PasswordEntry.Text;var newTenant = NewTenantEntry.Text?.Trim();int tenantId = -1;if (!string.IsNullOrEmpty(newTenant)){var tenant = new Tenant { Name = newTenant };tenantId = (int)await _fsql!.Insert(tenant).ExecuteIdentityAsync();}else if (TenantPicker.SelectedIndex >= 0){tenantId = _tenants[TenantPicker.SelectedIndex].Id;}if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || tenantId < 0){ErrorLabel.Text = "请填写完整信息";ErrorLabel.IsVisible = true;return;}if (_fsql!.Select<User>().Any(u => u.Username == username && u.TenantId == tenantId)){ErrorLabel.Text = "该用户已存在";ErrorLabel.IsVisible = true;return;}var user = new User { Username = username, Password = password, TenantId = tenantId };await _fsql!.Insert(user).ExecuteAffrowsAsync();await DisplayAlertAsync("注册成功", "请返回登录", "确定");await Navigation.PopAsync();}
}

五、几个“不像 AI 写的”关键细节

1️⃣ 自动处理租户选择与创建

Copilot 并没有简单假设“租户一定已存在”,而是允许:

  • 直接选择
  • 或新建

这是一个非常贴近真实业务的判断。


2️⃣ 用户唯一性校验落在正确的维度

.Any(u => u.Username == username && u.TenantId == tenantId)

不是全局唯一,而是租户内唯一

这是多租户系统中最容易写错的地方之一。


3️⃣ 登录体验的隐性优化

Preferences.Get("LastTenantId", -1)

Copilot 自动使用了 Preferences 来记忆上一次选择的租户。

这个细节,已经明显超出了“教程级注册页”。


六、为什么这一阶段依然不引入身份框架?

很多读者看到这里,可能会下意识地想到:

  • ASP.NET Identity
  • JWT
  • OAuth

但在这个项目中,我刻意没有引入任何复杂身份体系

原因很简单:

这是一个内部业务 App,
当前阶段,验证流程价值远大于安全体系完整性

而 Copilot 在这种“直接、明确”的业务逻辑下,表现反而更稳定。


七、Copilot 在“状态”问题上的真实边界

需要明确的是:

  • Copilot 可以帮你生成注册逻辑
  • 但它并不会自动帮你设计“登录态生命周期”

例如:

  • 登录成功后导航如何重置
  • 退出登录后状态如何清空

这些问题,已经开始进入系统设计层面,而不是页面代码层面。


八、下一步:初始化数据与租户管理

当注册和登录跑通之后,接下来一定会遇到:

  • 系统首次启动怎么办?
  • 没有租户怎么办?
  • 管理员是谁?

也就是:

初始化数据 + 租户管理

这是 Copilot 开始明显“需要人类介入”的阶段。


九、下一篇预告

下一篇将进入:

第 6 篇:初始化数据与租户管理 —— Copilot 能写 CRUD,但决策必须由你来做

从这一篇开始,这个项目将真正具备“长期演进”的基础。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询