Commit 13b6d2e35e81b6418624fe1f4602693ab1c2c14e
Merge branch 'main' of http://39.98.150.180/wangming/Food-Labeling-Management-Platform
Showing
7 changed files
with
497 additions
and
0 deletions
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppAuth/UsAppBoundLocationDto.cs
0 → 100644
| 1 | +namespace FoodLabeling.Application.Contracts.Dtos.UsAppAuth; | ||
| 2 | + | ||
| 3 | +/// <summary> | ||
| 4 | +/// App 端展示的绑定门店信息 | ||
| 5 | +/// </summary> | ||
| 6 | +public class UsAppBoundLocationDto | ||
| 7 | +{ | ||
| 8 | + /// <summary>门店主键 Id(Guid 字符串)</summary> | ||
| 9 | + public string Id { get; set; } = string.Empty; | ||
| 10 | + | ||
| 11 | + /// <summary>业务编码,如 LOC-1</summary> | ||
| 12 | + public string LocationCode { get; set; } = string.Empty; | ||
| 13 | + | ||
| 14 | + /// <summary>门店名称</summary> | ||
| 15 | + public string LocationName { get; set; } = string.Empty; | ||
| 16 | + | ||
| 17 | + /// <summary>拼接后的完整地址,便于移动端展示</summary> | ||
| 18 | + public string FullAddress { get; set; } = string.Empty; | ||
| 19 | + | ||
| 20 | + /// <summary>门店是否启用</summary> | ||
| 21 | + public bool State { get; set; } | ||
| 22 | +} |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppAuth/UsAppLoginInputVo.cs
0 → 100644
| 1 | +namespace FoodLabeling.Application.Contracts.Dtos.UsAppAuth; | ||
| 2 | + | ||
| 3 | +/// <summary> | ||
| 4 | +/// 美国版 App 登录入参(使用邮箱作为账号,支持图形验证码) | ||
| 5 | +/// </summary> | ||
| 6 | +public class UsAppLoginInputVo | ||
| 7 | +{ | ||
| 8 | + /// <summary>登录邮箱(对应 user.Email)</summary> | ||
| 9 | + public string Email { get; set; } = string.Empty; | ||
| 10 | + | ||
| 11 | + public string Password { get; set; } = string.Empty; | ||
| 12 | + | ||
| 13 | + /// <summary>图形验证码 UUID(开启验证码时必填)</summary> | ||
| 14 | + public string? Uuid { get; set; } | ||
| 15 | + | ||
| 16 | + /// <summary>图形验证码(开启验证码时必填)</summary> | ||
| 17 | + public string? Code { get; set; } | ||
| 18 | +} |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppAuth/UsAppLoginOutputDto.cs
0 → 100644
| 1 | +namespace FoodLabeling.Application.Contracts.Dtos.UsAppAuth; | ||
| 2 | + | ||
| 3 | +/// <summary> | ||
| 4 | +/// 美国版 App 登录返回(含 Token 与绑定门店) | ||
| 5 | +/// </summary> | ||
| 6 | +public class UsAppLoginOutputDto | ||
| 7 | +{ | ||
| 8 | + public string Token { get; set; } = string.Empty; | ||
| 9 | + | ||
| 10 | + public string RefreshToken { get; set; } = string.Empty; | ||
| 11 | + | ||
| 12 | + /// <summary>当前账号在 userlocation 中绑定的门店列表</summary> | ||
| 13 | + public List<UsAppBoundLocationDto> Locations { get; set; } = new(); | ||
| 14 | +} |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppAuthAppService.cs
0 → 100644
| 1 | +using FoodLabeling.Application.Contracts.Dtos.UsAppAuth; | ||
| 2 | +using Volo.Abp.Application.Services; | ||
| 3 | + | ||
| 4 | +namespace FoodLabeling.Application.Contracts.IServices; | ||
| 5 | + | ||
| 6 | +/// <summary> | ||
| 7 | +/// 美国版移动端认证(登录返回绑定门店) | ||
| 8 | +/// </summary> | ||
| 9 | +public interface IUsAppAuthAppService : IApplicationService | ||
| 10 | +{ | ||
| 11 | + /// <summary> | ||
| 12 | + /// App 登录:使用邮箱 + 密码校验并签发 Token,同时返回 userlocation 绑定的门店 | ||
| 13 | + /// </summary> | ||
| 14 | + Task<UsAppLoginOutputDto> LoginAsync(UsAppLoginInputVo input); | ||
| 15 | + | ||
| 16 | + /// <summary> | ||
| 17 | + /// 获取当前登录账号已绑定的门店(用于切换门店等场景) | ||
| 18 | + /// </summary> | ||
| 19 | + Task<List<UsAppBoundLocationDto>> GetMyLocationsAsync(); | ||
| 20 | +} |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/FoodLabeling.Application.csproj
| @@ -2,6 +2,10 @@ | @@ -2,6 +2,10 @@ | ||
| 2 | <Import Project="..\..\..\common.props" /> | 2 | <Import Project="..\..\..\common.props" /> |
| 3 | 3 | ||
| 4 | <ItemGroup> | 4 | <ItemGroup> |
| 5 | + <PackageReference Include="Lazy.Captcha.Core" Version="2.0.7" /> | ||
| 6 | + </ItemGroup> | ||
| 7 | + | ||
| 8 | + <ItemGroup> | ||
| 5 | <ProjectReference Include="..\..\..\framework\Yi.Framework.Ddd.Application\Yi.Framework.Ddd.Application.csproj" /> | 9 | <ProjectReference Include="..\..\..\framework\Yi.Framework.Ddd.Application\Yi.Framework.Ddd.Application.csproj" /> |
| 6 | <ProjectReference Include="..\FoodLabeling.Application.Contracts\FoodLabeling.Application.Contracts.csproj" /> | 10 | <ProjectReference Include="..\FoodLabeling.Application.Contracts\FoodLabeling.Application.Contracts.csproj" /> |
| 7 | <ProjectReference Include="..\FoodLabeling.Domain\FoodLabeling.Domain.csproj" /> | 11 | <ProjectReference Include="..\FoodLabeling.Domain\FoodLabeling.Domain.csproj" /> |
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppAuthAppService.cs
0 → 100644
| 1 | +using System; | ||
| 2 | +using System.Collections.Generic; | ||
| 3 | +using System.IdentityModel.Tokens.Jwt; | ||
| 4 | +using System.Linq; | ||
| 5 | +using System.Security.Claims; | ||
| 6 | +using System.Text; | ||
| 7 | +using System.Threading.Tasks; | ||
| 8 | +using FoodLabeling.Application.Contracts.Dtos.UsAppAuth; | ||
| 9 | +using FoodLabeling.Application.Contracts.IServices; | ||
| 10 | +using FoodLabeling.Application.Services.DbModels; | ||
| 11 | +using FoodLabeling.Domain.Entities; | ||
| 12 | +using Lazy.Captcha.Core; | ||
| 13 | +using Mapster; | ||
| 14 | +using Microsoft.AspNetCore.Authorization; | ||
| 15 | +using Microsoft.AspNetCore.Http; | ||
| 16 | +using Microsoft.Extensions.Options; | ||
| 17 | +using Microsoft.IdentityModel.Tokens; | ||
| 18 | +using SqlSugar; | ||
| 19 | +using Volo.Abp; | ||
| 20 | +using Volo.Abp.Application.Services; | ||
| 21 | +using Volo.Abp.EventBus.Local; | ||
| 22 | +using Volo.Abp.Security.Claims; | ||
| 23 | +using Volo.Abp.Users; | ||
| 24 | +using Yi.Framework.Core.Helper; | ||
| 25 | +using Yi.Framework.Rbac.Domain.Entities; | ||
| 26 | +using Yi.Framework.Rbac.Domain.Managers; | ||
| 27 | +using Yi.Framework.Rbac.Domain.Shared.Consts; | ||
| 28 | +using Yi.Framework.Rbac.Domain.Shared.Dtos; | ||
| 29 | +using Yi.Framework.Rbac.Domain.Shared.Etos; | ||
| 30 | +using Yi.Framework.Rbac.Domain.Shared.Options; | ||
| 31 | +using Yi.Framework.SqlSugarCore.Abstractions; | ||
| 32 | + | ||
| 33 | +namespace FoodLabeling.Application.Services; | ||
| 34 | + | ||
| 35 | +/// <summary> | ||
| 36 | +/// 美国版 App 登录:邮箱 + 密码(与 AccountManager 相同盐值哈希)签发 JWT,并返回 userlocation 绑定门店 | ||
| 37 | +/// </summary> | ||
| 38 | +public class UsAppAuthAppService : ApplicationService, IUsAppAuthAppService | ||
| 39 | +{ | ||
| 40 | + private readonly IAccountManager _accountManager; | ||
| 41 | + private readonly ISqlSugarRepository<UserAggregateRoot, Guid> _userRepository; | ||
| 42 | + private readonly ISqlSugarDbContext _dbContext; | ||
| 43 | + private readonly IHttpContextAccessor _httpContextAccessor; | ||
| 44 | + private readonly ICaptcha _captcha; | ||
| 45 | + private readonly RbacOptions _rbacOptions; | ||
| 46 | + private readonly JwtOptions _jwtOptions; | ||
| 47 | + | ||
| 48 | + public UsAppAuthAppService( | ||
| 49 | + IAccountManager accountManager, | ||
| 50 | + ISqlSugarRepository<UserAggregateRoot, Guid> userRepository, | ||
| 51 | + ISqlSugarDbContext dbContext, | ||
| 52 | + IHttpContextAccessor httpContextAccessor, | ||
| 53 | + ICaptcha captcha, | ||
| 54 | + IOptions<JwtOptions> jwtOptions, | ||
| 55 | + IOptions<RbacOptions> rbacOptions) | ||
| 56 | + { | ||
| 57 | + _accountManager = accountManager; | ||
| 58 | + _userRepository = userRepository; | ||
| 59 | + _dbContext = dbContext; | ||
| 60 | + _httpContextAccessor = httpContextAccessor; | ||
| 61 | + _captcha = captcha; | ||
| 62 | + _jwtOptions = jwtOptions.Value; | ||
| 63 | + _rbacOptions = rbacOptions.Value; | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + protected ILocalEventBus LocalEventBus => LazyServiceProvider.LazyGetRequiredService<ILocalEventBus>(); | ||
| 67 | + | ||
| 68 | + /// <summary> | ||
| 69 | + /// App 登录:签发 Token / RefreshToken,并返回当前账号绑定的门店列表 | ||
| 70 | + /// </summary> | ||
| 71 | + /// <remarks> | ||
| 72 | + /// 行为与系统 <c>AccountService.PostLoginAsync</c> 一致(含验证码、登录日志事件)。 | ||
| 73 | + /// 门店数据来自 <c>userlocation</c> 与 <c>location</c> 表。 | ||
| 74 | + /// </remarks> | ||
| 75 | + /// <param name="input">邮箱、密码;若系统开启验证码则需传 Uuid、Code</param> | ||
| 76 | + /// <returns>Token、RefreshToken 与绑定门店</returns> | ||
| 77 | + /// <response code="200">登录成功</response> | ||
| 78 | + /// <response code="400">参数或验证码错误</response> | ||
| 79 | + /// <response code="500">服务器错误</response> | ||
| 80 | + [AllowAnonymous] | ||
| 81 | + public virtual async Task<UsAppLoginOutputDto> LoginAsync(UsAppLoginInputVo input) | ||
| 82 | + { | ||
| 83 | + if (string.IsNullOrWhiteSpace(input.Password) || string.IsNullOrWhiteSpace(input.Email)) | ||
| 84 | + { | ||
| 85 | + throw new UserFriendlyException("请输入合理数据!"); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + ValidationImageCaptcha(input.Uuid, input.Code); | ||
| 89 | + | ||
| 90 | + var user = await FindActiveUserByEmailAsync(input.Email.Trim()); | ||
| 91 | + if (user is null) | ||
| 92 | + { | ||
| 93 | + throw new UserFriendlyException("登录失败!邮箱不存在!"); | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + if (user.EncryPassword.Password != MD5Helper.SHA2Encode(input.Password, user.EncryPassword.Salt)) | ||
| 97 | + { | ||
| 98 | + throw new UserFriendlyException(UserConst.Login_Error); | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + // App 端不依赖 RBAC 权限体系:允许“无权限账号”登录拿 Token(H5 再做权限控制) | ||
| 102 | + var accessToken = CreateAppAccessToken(user); | ||
| 103 | + var refreshToken = _accountManager.CreateRefreshToken(user.Id); | ||
| 104 | + | ||
| 105 | + if (_httpContextAccessor.HttpContext is not null) | ||
| 106 | + { | ||
| 107 | + var loginEntity = new LoginLogAggregateRoot().GetInfoByHttpContext(_httpContextAccessor.HttpContext); | ||
| 108 | + var loginEto = loginEntity.Adapt<LoginEventArgs>(); | ||
| 109 | + loginEto.UserName = user.UserName; | ||
| 110 | + loginEto.UserId = user.Id; | ||
| 111 | + await LocalEventBus.PublishAsync(loginEto); | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + var locations = await LoadBoundLocationsAsync(user.Id); | ||
| 115 | + | ||
| 116 | + return new UsAppLoginOutputDto | ||
| 117 | + { | ||
| 118 | + Token = accessToken, | ||
| 119 | + RefreshToken = refreshToken, | ||
| 120 | + Locations = locations | ||
| 121 | + }; | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + /// <summary> | ||
| 125 | + /// 获取当前登录用户已绑定的门店(切换门店时可重新拉取) | ||
| 126 | + /// </summary> | ||
| 127 | + [Authorize] | ||
| 128 | + public virtual async Task<List<UsAppBoundLocationDto>> GetMyLocationsAsync() | ||
| 129 | + { | ||
| 130 | + if (!CurrentUser.Id.HasValue) | ||
| 131 | + { | ||
| 132 | + throw new UserFriendlyException("用户未登录"); | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + return await LoadBoundLocationsAsync(CurrentUser.Id.Value); | ||
| 136 | + } | ||
| 137 | + | ||
| 138 | + private void ValidationImageCaptcha(string? uuid, string? code) | ||
| 139 | + { | ||
| 140 | + if (!_rbacOptions.EnableCaptcha) | ||
| 141 | + { | ||
| 142 | + return; | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + if (!_captcha.Validate(uuid, code)) | ||
| 146 | + { | ||
| 147 | + throw new UserFriendlyException("验证码错误"); | ||
| 148 | + } | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + /// <summary> | ||
| 152 | + /// 按邮箱查找未删除且启用的用户(邮箱比较忽略大小写) | ||
| 153 | + /// </summary> | ||
| 154 | + private async Task<UserAggregateRoot?> FindActiveUserByEmailAsync(string email) | ||
| 155 | + { | ||
| 156 | + var normalized = email.Trim().ToLowerInvariant(); | ||
| 157 | + var users = await _userRepository._DbQueryable | ||
| 158 | + .Where(u => !u.IsDeleted && u.State == true) | ||
| 159 | + .Where(u => u.Email != null && SqlFunc.ToLower(u.Email) == normalized) | ||
| 160 | + .ToListAsync(); | ||
| 161 | + return users.FirstOrDefault(); | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + private string CreateAppAccessToken(UserAggregateRoot user) | ||
| 165 | + { | ||
| 166 | + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.SecurityKey)); | ||
| 167 | + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); | ||
| 168 | + | ||
| 169 | + var claims = new List<Claim> | ||
| 170 | + { | ||
| 171 | + new(AbpClaimTypes.UserId, user.Id.ToString()), | ||
| 172 | + new(AbpClaimTypes.UserName, user.UserName) | ||
| 173 | + }; | ||
| 174 | + | ||
| 175 | + if (!string.IsNullOrWhiteSpace(user.Email)) | ||
| 176 | + { | ||
| 177 | + claims.Add(new Claim(AbpClaimTypes.Email, user.Email)); | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + var token = new JwtSecurityToken( | ||
| 181 | + issuer: _jwtOptions.Issuer, | ||
| 182 | + audience: _jwtOptions.Audience, | ||
| 183 | + claims: claims, | ||
| 184 | + expires: DateTime.Now.AddMinutes(_jwtOptions.ExpiresMinuteTime), | ||
| 185 | + notBefore: DateTime.Now, | ||
| 186 | + signingCredentials: creds); | ||
| 187 | + | ||
| 188 | + return new JwtSecurityTokenHandler().WriteToken(token); | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + private async Task<List<UsAppBoundLocationDto>> LoadBoundLocationsAsync(Guid userId) | ||
| 192 | + { | ||
| 193 | + var userIdStr = userId.ToString(); | ||
| 194 | + var links = await _dbContext.SqlSugarClient.Queryable<UserLocationDbEntity>() | ||
| 195 | + .Where(x => !x.IsDeleted && x.UserId == userIdStr) | ||
| 196 | + .Select(x => x.LocationId) | ||
| 197 | + .ToListAsync(); | ||
| 198 | + | ||
| 199 | + if (links.Count == 0) | ||
| 200 | + { | ||
| 201 | + return new List<UsAppBoundLocationDto>(); | ||
| 202 | + } | ||
| 203 | + | ||
| 204 | + var wanted = links.Distinct().ToList(); | ||
| 205 | + var locations = (await _dbContext.SqlSugarClient.Queryable<LocationAggregateRoot>() | ||
| 206 | + .Where(x => !x.IsDeleted) | ||
| 207 | + .Where(x => wanted.Contains(x.Id.ToString())) | ||
| 208 | + .ToListAsync()) | ||
| 209 | + .OrderBy(x => x.OrderNum) | ||
| 210 | + .ThenBy(x => x.LocationName) | ||
| 211 | + .ToList(); | ||
| 212 | + | ||
| 213 | + return locations.Select(x => new UsAppBoundLocationDto | ||
| 214 | + { | ||
| 215 | + Id = x.Id.ToString(), | ||
| 216 | + LocationCode = x.LocationCode ?? string.Empty, | ||
| 217 | + LocationName = x.LocationName ?? string.Empty, | ||
| 218 | + FullAddress = BuildFullAddress(x), | ||
| 219 | + State = x.State | ||
| 220 | + }).ToList(); | ||
| 221 | + } | ||
| 222 | + | ||
| 223 | + private static string BuildFullAddress(LocationAggregateRoot loc) | ||
| 224 | + { | ||
| 225 | + var street = loc.Street?.Trim(); | ||
| 226 | + var city = loc.City?.Trim(); | ||
| 227 | + var state = loc.StateCode?.Trim(); | ||
| 228 | + var zip = loc.ZipCode?.Trim(); | ||
| 229 | + | ||
| 230 | + var line2Parts = new List<string>(); | ||
| 231 | + if (!string.IsNullOrEmpty(city)) | ||
| 232 | + { | ||
| 233 | + line2Parts.Add(city); | ||
| 234 | + } | ||
| 235 | + | ||
| 236 | + if (!string.IsNullOrEmpty(state)) | ||
| 237 | + { | ||
| 238 | + line2Parts.Add(state); | ||
| 239 | + } | ||
| 240 | + | ||
| 241 | + var line2 = line2Parts.Count > 0 ? string.Join(", ", line2Parts) : string.Empty; | ||
| 242 | + if (!string.IsNullOrEmpty(zip)) | ||
| 243 | + { | ||
| 244 | + line2 = string.IsNullOrEmpty(line2) ? zip : $"{line2} {zip}"; | ||
| 245 | + } | ||
| 246 | + | ||
| 247 | + var segments = new List<string>(); | ||
| 248 | + if (!string.IsNullOrEmpty(street)) | ||
| 249 | + { | ||
| 250 | + segments.Add(street); | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | + if (!string.IsNullOrEmpty(line2)) | ||
| 254 | + { | ||
| 255 | + segments.Add(line2); | ||
| 256 | + } | ||
| 257 | + | ||
| 258 | + return segments.Count == 0 ? "无" : string.Join(", ", segments); | ||
| 259 | + } | ||
| 260 | +} |
项目相关文档/美国版App登录接口说明.md
0 → 100644
| 1 | +# 美国版 App 登录接口说明 | ||
| 2 | + | ||
| 3 | +## 概述 | ||
| 4 | + | ||
| 5 | +美国版移动端认证由 `food-labeling-us` 模块的 **`UsAppAuthAppService`** 提供,采用 ABP 约定式动态 API。宿主统一前缀为 **`/api/app`**,建议以 Swagger 为准核对路径(本地示例:`http://localhost:19001/swagger`,搜索 `UsAppAuth`)。 | ||
| 6 | + | ||
| 7 | +| 说明 | 内容 | | ||
| 8 | +|------|------| | ||
| 9 | +| 账号标识 | 使用 **`User.Email`**(邮箱)登录,邮箱比对**忽略大小写** | | ||
| 10 | +| 密码 | 与 Web 共用 `User` 表,校验方式与 RBAC **`AccountManager`** 一致(盐值 + `MD5Helper.SHA2Encode`) | | ||
| 11 | +| 权限 | **App 登录不校验角色/权限**(允许“未配置权限”的账号登录);H5 管理端再按 RBAC 权限控制 | | ||
| 12 | +| 验证码 | 当配置 **`Rbac:EnableCaptcha`** 为 `true` 时,需先拉取图形验证码,本接口入参传 `uuid`、`code`;未开启时可传空或不传 | | ||
| 13 | + | ||
| 14 | +--- | ||
| 15 | + | ||
| 16 | +## 接口 1:App 登录 | ||
| 17 | + | ||
| 18 | +签发 **Access Token**、**Refresh Token**,并返回当前用户在 **`userlocation`** 中绑定的门店列表(关联 **`location`** 表详情)。 | ||
| 19 | + | ||
| 20 | +### HTTP | ||
| 21 | + | ||
| 22 | +- **方法**:`POST` | ||
| 23 | +- **路径**:`/api/app/us-app-auth/login` | ||
| 24 | +- **Content-Type**:`application/json` | ||
| 25 | +- **鉴权**:无需登录(匿名) | ||
| 26 | + | ||
| 27 | +### 请求体参数(UsAppLoginInputVo) | ||
| 28 | + | ||
| 29 | +| 参数名(JSON) | 类型 | 必填 | 说明 | | ||
| 30 | +|----------------|------|------|------| | ||
| 31 | +| `email` | string | 是 | 登录邮箱,对应数据库 `User.Email` | | ||
| 32 | +| `password` | string | 是 | 明文密码 | | ||
| 33 | +| `uuid` | string | 条件 | 图形验证码 UUID;**开启验证码时必填** | | ||
| 34 | +| `code` | string | 条件 | 图形验证码;**开启验证码时必填** | | ||
| 35 | + | ||
| 36 | +### 传参示例(请求 Body) | ||
| 37 | + | ||
| 38 | +未开启图形验证码时: | ||
| 39 | + | ||
| 40 | +```json | ||
| 41 | +{ | ||
| 42 | + "email": "admin@example.com", | ||
| 43 | + "password": "123456" | ||
| 44 | +} | ||
| 45 | +``` | ||
| 46 | + | ||
| 47 | +开启图形验证码时(需与系统验证码接口返回的 `uuid`、用户输入的验证码一致): | ||
| 48 | + | ||
| 49 | +```json | ||
| 50 | +{ | ||
| 51 | + "email": "test@example.com", | ||
| 52 | + "password": "您的密码", | ||
| 53 | + "uuid": "验证码接口返回的 uuid", | ||
| 54 | + "code": "用户看到的验证码" | ||
| 55 | +} | ||
| 56 | +``` | ||
| 57 | + | ||
| 58 | +### 响应体(UsAppLoginOutputDto) | ||
| 59 | + | ||
| 60 | +| 字段(JSON) | 类型 | 说明 | | ||
| 61 | +|--------------|------|------| | ||
| 62 | +| `token` | string | 访问令牌(Bearer),后续业务接口放在 Header `Authorization: Bearer {token}` | | ||
| 63 | +| `refreshToken` | string | 刷新令牌(与系统账号体系一致,用于刷新 access token,具体用法与 Web 一致) | | ||
| 64 | +| `locations` | array | 绑定门店列表,元素见下表 | | ||
| 65 | + | ||
| 66 | +#### `locations[]` 元素(UsAppBoundLocationDto) | ||
| 67 | + | ||
| 68 | +| 字段(JSON) | 类型 | 说明 | | ||
| 69 | +|--------------|------|------| | ||
| 70 | +| `id` | string | 门店主键(Guid 字符串) | | ||
| 71 | +| `locationCode` | string | 业务编码,如 LOC-1 | | ||
| 72 | +| `locationName` | string | 门店名称 | | ||
| 73 | +| `fullAddress` | string | 拼接后的完整地址(街道、城市、州、邮编等;无数据时可能为 `"无"`) | | ||
| 74 | +| `state` | bool | 门店是否启用 | | ||
| 75 | + | ||
| 76 | +### 响应示例 | ||
| 77 | + | ||
| 78 | +```json | ||
| 79 | +{ | ||
| 80 | + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", | ||
| 81 | + "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", | ||
| 82 | + "locations": [ | ||
| 83 | + { | ||
| 84 | + "id": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", | ||
| 85 | + "locationCode": "LOC-1", | ||
| 86 | + "locationName": "Downtown Kitchen", | ||
| 87 | + "fullAddress": "123 Main St, New York, NY 10001", | ||
| 88 | + "state": true | ||
| 89 | + } | ||
| 90 | + ] | ||
| 91 | +} | ||
| 92 | +``` | ||
| 93 | + | ||
| 94 | +### 常见错误提示(业务异常文案) | ||
| 95 | + | ||
| 96 | +- 邮箱或密码为空:`请输入合理数据!` | ||
| 97 | +- 邮箱在库中不存在(未删除且启用用户中无匹配邮箱):`登录失败!邮箱不存在!` | ||
| 98 | +- 密码错误:`登录失败!用户名或密码错误!`(与 `UserConst.Login_Error` 一致) | ||
| 99 | +- 验证码错误(开启验证码时):`验证码错误` | ||
| 100 | + | ||
| 101 | +--- | ||
| 102 | + | ||
| 103 | +## 接口 2:获取当前账号绑定门店 | ||
| 104 | + | ||
| 105 | +无需重新登录即可刷新 **`userlocation`** 绑定门店列表(例如切换门店前先同步列表)。 | ||
| 106 | + | ||
| 107 | +### HTTP | ||
| 108 | + | ||
| 109 | +- **方法**:`GET` | ||
| 110 | +- **路径**:`/api/app/us-app-auth/my-locations` | ||
| 111 | +- **鉴权**:需要登录,请求头携带 **`Authorization: Bearer {token}`**(使用接口 1 返回的 `token`) | ||
| 112 | + | ||
| 113 | +### 请求参数 | ||
| 114 | + | ||
| 115 | +无 Query / Body 参数;用户身份由 JWT 解析。 | ||
| 116 | + | ||
| 117 | +### 传参示例 | ||
| 118 | + | ||
| 119 | +```http | ||
| 120 | +GET /api/app/us-app-auth/my-locations HTTP/1.1 | ||
| 121 | +Host: localhost:19001 | ||
| 122 | +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... | ||
| 123 | +``` | ||
| 124 | + | ||
| 125 | +若前端统一约定 GET 使用 `data` 封装,可自行在客户端组装;本接口服务端**不读取额外 Query 参数**。 | ||
| 126 | + | ||
| 127 | +### 响应体 | ||
| 128 | + | ||
| 129 | +与登录接口中 **`locations`** 相同:**`UsAppBoundLocationDto[]`**(数组)。 | ||
| 130 | + | ||
| 131 | +### 响应示例 | ||
| 132 | + | ||
| 133 | +```json | ||
| 134 | +[ | ||
| 135 | + { | ||
| 136 | + "id": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", | ||
| 137 | + "locationCode": "LOC-1", | ||
| 138 | + "locationName": "Downtown Kitchen", | ||
| 139 | + "fullAddress": "123 Main St, New York, NY 10001", | ||
| 140 | + "state": true | ||
| 141 | + } | ||
| 142 | +] | ||
| 143 | +``` | ||
| 144 | + | ||
| 145 | +### 常见错误 | ||
| 146 | + | ||
| 147 | +- 未登录或 Token 无效:按网关/ABP 返回 401 及统一错误体 | ||
| 148 | +- 无用户上下文:`用户未登录` | ||
| 149 | + | ||
| 150 | +--- | ||
| 151 | + | ||
| 152 | +## 与其他登录方式的区别 | ||
| 153 | + | ||
| 154 | +| 场景 | 说明 | | ||
| 155 | +|------|------| | ||
| 156 | +| Web 管理端 | 仍使用 RBAC **`AccountService.PostLoginAsync`**,一般为人 **`userName`** + 密码 | | ||
| 157 | +| 美国版 App | **仅**本模块 **`/api/app/us-app-auth/login`** 使用 **邮箱 + 密码** | | ||
| 158 | + | ||
| 159 | +两者共用同一 `User` 表与 JWT 体系;App 端需保证账号已维护 **`Email`** 字段,否则无法通过邮箱登录。 |