14 changed files with 16921 additions and 4 deletions
-
40src/Shentun.Peis.Application.Contracts/ThirdUsers/ThirdLoginDto.cs
-
38src/Shentun.Peis.Application.Contracts/ThirdUsers/WechatSession.cs
-
1src/Shentun.Peis.Application/Shentun.Peis.Application.csproj
-
39src/Shentun.Peis.Application/ThirdUsers/ThirdUserAppService.cs
-
80src/Shentun.Peis.Application/ThirdUsers/WeChatHelper.cs
-
57src/Shentun.Peis.Domain/ThirdUsers/ThirdUser.cs
-
28src/Shentun.Peis.EntityFrameworkCore/DbMapping/ThirdUsers/ThirdUserDbMapping.cs
-
9src/Shentun.Peis.EntityFrameworkCore/EntityFrameworkCore/PeisDbContext.cs
-
16375src/Shentun.Peis.EntityFrameworkCore/Migrations/20250430072513_insert_third_user.Designer.cs
-
47src/Shentun.Peis.EntityFrameworkCore/Migrations/20250430072513_insert_third_user.cs
-
71src/Shentun.Peis.EntityFrameworkCore/Migrations/PeisDbContextModelSnapshot.cs
-
129src/Shentun.Peis.HttpApi.Host/Controllers/MiniProgramTokenController.cs
-
5src/Shentun.Peis.HttpApi.Host/PeisHttpApiHostModule.cs
-
2src/Shentun.Peis.HttpApi.Host/appsettings.json
@ -0,0 +1,40 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Shentun.Peis.ThirdUsers |
||||
|
{ |
||||
|
public class ThirdLoginDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 是否有token
|
||||
|
/// </summary>
|
||||
|
public string IsToken { get; set; } = "Y"; |
||||
|
/// <summary>
|
||||
|
/// 微信OpenId
|
||||
|
/// </summary>
|
||||
|
public string OpenId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 访问TOKEN
|
||||
|
/// </summary>
|
||||
|
public string AccessToken { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 刷新Token
|
||||
|
/// </summary>
|
||||
|
public string RefreshToken { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 过期时间(单位秒)
|
||||
|
/// </summary>
|
||||
|
public int ExpiresIn { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 会话键
|
||||
|
/// </summary>
|
||||
|
public string SessionKey { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 会话键值
|
||||
|
/// </summary>
|
||||
|
public string SessionKeyValue { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Shentun.Peis.ThirdUsers |
||||
|
{ |
||||
|
public class WechatSession |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 用户唯一标识
|
||||
|
/// </summary>
|
||||
|
public string OpenId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 会话密钥
|
||||
|
/// </summary>
|
||||
|
public string Session_Key { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回,详见UnionID 机制说明。
|
||||
|
/// </summary>
|
||||
|
public string Unionid { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 错误码
|
||||
|
/// </summary>
|
||||
|
public int ErrCode { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 错误信息
|
||||
|
/// </summary>
|
||||
|
public string ErrMsg { get; set; } |
||||
|
/// <summary>
|
||||
|
/// Redis里面OpenId值所对应的键OpenIdKey
|
||||
|
/// </summary>
|
||||
|
public string OpenIdKey { get; set; } |
||||
|
/// <summary>
|
||||
|
/// Redis里面Session_Key值所对应的键SessionKey
|
||||
|
/// </summary>
|
||||
|
public string SessionKey { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using Microsoft.AspNetCore.Authentication; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using OpenIddict.Abstractions; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Security.Claims; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
using Volo.Abp.Identity; |
||||
|
|
||||
|
namespace Shentun.Peis.ThirdUsers |
||||
|
{ |
||||
|
public class ThirdUserAppService : ApplicationService |
||||
|
{ |
||||
|
//private readonly IOpenIddictScopeManager _scopeManager;
|
||||
|
//private readonly SignInManager<IdentityUser> _signInManager;
|
||||
|
//private readonly IRepository<IdentityUser,Guid> _identityUserRepository;
|
||||
|
//private readonly IdentityUserManager _identityUserManager;
|
||||
|
|
||||
|
//public ThirdUserAppService(
|
||||
|
// IOpenIddictScopeManager scopeManager,
|
||||
|
// SignInManager<IdentityUser> signInManager,
|
||||
|
// IRepository<IdentityUser, Guid> identityUserRepository,
|
||||
|
// IdentityUserManager identityUserManager)
|
||||
|
//{
|
||||
|
// _scopeManager = scopeManager;
|
||||
|
// _signInManager = signInManager;
|
||||
|
// _identityUserRepository = identityUserRepository;
|
||||
|
// _identityUserManager = identityUserManager;
|
||||
|
//}
|
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Net.Http; |
||||
|
using System.Net; |
||||
|
using System.Text; |
||||
|
using System.Text.Json; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Shentun.Peis.ThirdUsers |
||||
|
{ |
||||
|
public class WeChatHelper |
||||
|
{ |
||||
|
public static async Task<WechatSession> GetWechatSession(IConfiguration configSection1, string jsCode) |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(jsCode)) |
||||
|
{ |
||||
|
throw new Exception("jsCode不能为空"); |
||||
|
} |
||||
|
IConfigurationSection configSection = configSection1.GetSection("WeChat"); |
||||
|
if (configSection == null) |
||||
|
{ |
||||
|
throw new Exception("微信配置不能为空"); |
||||
|
} |
||||
|
string url = configSection.GetValue("SessionUrl", "https://api.weixin.qq.com/sns/jscode2session?"); |
||||
|
string appId = configSection.GetValue("AppID", ""); |
||||
|
string appSecret = configSection.GetValue("AppSecret", ""); |
||||
|
if (string.IsNullOrWhiteSpace(url)) |
||||
|
{ |
||||
|
throw new Exception("微信url不能为空"); |
||||
|
} |
||||
|
if (string.IsNullOrWhiteSpace(appId)) |
||||
|
{ |
||||
|
throw new Exception("微信appId不能为空"); |
||||
|
} |
||||
|
if (string.IsNullOrWhiteSpace(appSecret)) |
||||
|
{ |
||||
|
throw new Exception("微信appSecret不能为空"); |
||||
|
} |
||||
|
|
||||
|
url = url + "appid=" + appId + "&secret=" + appSecret + "&js_code=" + jsCode + "&grant_type=authorization_code"; |
||||
|
|
||||
|
var handler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip }; |
||||
|
|
||||
|
using (var httpClient = new HttpClient(handler)) |
||||
|
{ |
||||
|
//await异步等待回应
|
||||
|
|
||||
|
var responseResult = await httpClient.GetAsync(url); |
||||
|
//确保HTTP成功状态值
|
||||
|
if (!responseResult.IsSuccessStatusCode) |
||||
|
{ |
||||
|
throw new Exception("获取微信小程序数据失败," + responseResult.StatusCode); |
||||
|
} |
||||
|
|
||||
|
//await异步读取最后的JSON(注意此时gzip已经被自动解压缩了,因为上面的AutomaticDecompression = DecompressionMethods.GZip)
|
||||
|
string responseContent = await responseResult.Content.ReadAsStringAsync(); |
||||
|
var wechatSession = JsonSerializer.Deserialize<WechatSession>(responseContent, |
||||
|
new JsonSerializerOptions() { PropertyNameCaseInsensitive = true } |
||||
|
); |
||||
|
if (wechatSession == null) |
||||
|
{ |
||||
|
throw new Exception("获取微信小程序wechatSession为空"); |
||||
|
} |
||||
|
if (wechatSession.ErrCode != 0) |
||||
|
{ |
||||
|
//wechatSession.OpenId = "obZGv5RhSNxxpkDwT0Xaf9Fzn8NM";
|
||||
|
//wechatSession.SessionKey = "O+VJ49EHaPLR5+oOjsG0xA==";
|
||||
|
throw new Exception("微信账户登陆失败" + wechatSession.ErrMsg); |
||||
|
} |
||||
|
if (string.IsNullOrWhiteSpace(wechatSession.OpenId)) |
||||
|
{ |
||||
|
throw new Exception("微信账户OpenId不能为空"); |
||||
|
} |
||||
|
return wechatSession; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Entities.Auditing; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
using System.ComponentModel.DataAnnotations.Schema; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
|
||||
|
namespace Shentun.Peis.Models |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 第三方用户用户表
|
||||
|
/// </summary>
|
||||
|
[Table("third_user")] |
||||
|
[Index(nameof(MobilePhone), Name = "ix_third_user_mobile_phone_index", IsUnique = true)] |
||||
|
public class ThirdUser : AuditedEntity<Guid>, IHasConcurrencyStamp |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 手机号码
|
||||
|
/// </summary>
|
||||
|
[Column("mobile_phone")] |
||||
|
public string MobilePhone { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// abp用户表Id
|
||||
|
/// </summary>
|
||||
|
[Column("abp_user_id")] |
||||
|
public Guid? AbpUserId { get; set; } |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 是否启用
|
||||
|
/// </summary>
|
||||
|
[Column("is_active")] |
||||
|
public char IsActive { get; set; } |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 微信openid
|
||||
|
/// </summary>
|
||||
|
[Column("wechat_openid")] |
||||
|
public string? WechatOpenId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 用户注册方式(0.小程序注册 1.后台添加) 默认0
|
||||
|
/// </summary>
|
||||
|
[Column("user_register_flag")] |
||||
|
public char UserRegisterFlag { get; set; } |
||||
|
|
||||
|
|
||||
|
[Column("concurrency_stamp")] |
||||
|
public string ConcurrencyStamp { get; set; } |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Shentun.Peis.Models; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Shentun.Peis.EntityFrameworkCore; |
||||
|
|
||||
|
namespace Shentun.Peis.DbMapping |
||||
|
{ |
||||
|
internal class ThirdUserDbMapping : IEntityTypeConfiguration<ThirdUser> |
||||
|
{ |
||||
|
public void Configure(EntityTypeBuilder<ThirdUser> entity) |
||||
|
{ |
||||
|
entity.HasComment("第三方用户表"); |
||||
|
entity.Property(t => t.AbpUserId).HasComment("abpuser表id"); |
||||
|
entity.Property(t => t.WechatOpenId).HasComment("微信openid"); |
||||
|
entity.Property(t => t.MobilePhone).HasComment("手机号码"); |
||||
|
entity.Property(t => t.IsActive).HasComment("是否启用").IsRequired().HasDefaultValueSql("'Y'"); |
||||
|
entity.Property(t => t.UserRegisterFlag).HasComment("用户注册方式(0.小程序注册 1.后台添加) 默认0").IsRequired().HasDefaultValueSql("'0'"); |
||||
|
entity.Property(e => e.Id).IsFixedLength().IsRequired(); |
||||
|
|
||||
|
entity.ConfigureByConvention(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
16375
src/Shentun.Peis.EntityFrameworkCore/Migrations/20250430072513_insert_third_user.Designer.cs
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,47 @@ |
|||||
|
using System; |
||||
|
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
|
||||
|
#nullable disable |
||||
|
|
||||
|
namespace Shentun.Peis.Migrations |
||||
|
{ |
||||
|
public partial class insert_third_user : Migration |
||||
|
{ |
||||
|
protected override void Up(MigrationBuilder migrationBuilder) |
||||
|
{ |
||||
|
migrationBuilder.CreateTable( |
||||
|
name: "third_user", |
||||
|
columns: table => new |
||||
|
{ |
||||
|
id = table.Column<Guid>(type: "uuid", fixedLength: true, nullable: false), |
||||
|
mobile_phone = table.Column<string>(type: "text", nullable: true, comment: "手机号码"), |
||||
|
abp_user_id = table.Column<Guid>(type: "uuid", nullable: true, comment: "abpuser表id"), |
||||
|
is_active = table.Column<char>(type: "character(1)", nullable: false, defaultValueSql: "'Y'", comment: "是否启用"), |
||||
|
wechat_openid = table.Column<string>(type: "text", nullable: true, comment: "微信openid"), |
||||
|
user_register_flag = table.Column<char>(type: "character(1)", nullable: false, defaultValueSql: "'0'", comment: "用户注册方式(0.小程序注册 1.后台添加) 默认0"), |
||||
|
concurrency_stamp = table.Column<string>(type: "character varying(40)", maxLength: 40, nullable: true), |
||||
|
creation_time = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), |
||||
|
creator_id = table.Column<Guid>(type: "uuid", nullable: false), |
||||
|
last_modification_time = table.Column<DateTime>(type: "timestamp without time zone", nullable: false), |
||||
|
last_modifier_id = table.Column<Guid>(type: "uuid", nullable: false) |
||||
|
}, |
||||
|
constraints: table => |
||||
|
{ |
||||
|
table.PrimaryKey("PK_third_user", x => x.id); |
||||
|
}, |
||||
|
comment: "第三方用户表"); |
||||
|
|
||||
|
migrationBuilder.CreateIndex( |
||||
|
name: "ix_third_user_mobile_phone_index", |
||||
|
table: "third_user", |
||||
|
column: "mobile_phone", |
||||
|
unique: true); |
||||
|
} |
||||
|
|
||||
|
protected override void Down(MigrationBuilder migrationBuilder) |
||||
|
{ |
||||
|
migrationBuilder.DropTable( |
||||
|
name: "third_user"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,129 @@ |
|||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using OpenIddict.Abstractions; |
||||
|
using OpenIddict.Server.AspNetCore; |
||||
|
using Shentun.Peis.Models; |
||||
|
using Shentun.Peis.ThirdUsers; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
using Volo.Abp.Identity; |
||||
|
using Volo.Abp.OpenIddict.Controllers; |
||||
|
|
||||
|
namespace Shentun.Peis.Controllers |
||||
|
{ |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 小程序登录
|
||||
|
/// </summary>
|
||||
|
[Route("/connect/token", Order = -1)] |
||||
|
public class MiniProgramTokenController : TokenController |
||||
|
{ |
||||
|
|
||||
|
private readonly IConfiguration _configuration; |
||||
|
private readonly IRepository<ThirdUser, Guid> _thirdUserRepository; |
||||
|
private readonly SignInManager<Volo.Abp.Identity.IdentityUser> _signInManager; |
||||
|
private readonly IRepository<Volo.Abp.Identity.IdentityUser, Guid> _identityUserRepository; |
||||
|
|
||||
|
|
||||
|
public MiniProgramTokenController( |
||||
|
IConfiguration configuration, |
||||
|
IRepository<ThirdUser, Guid> thirdUserRepository, |
||||
|
SignInManager<Volo.Abp.Identity.IdentityUser> signInManager, |
||||
|
IRepository<Volo.Abp.Identity.IdentityUser, Guid> identityUserRepository) |
||||
|
{ |
||||
|
_configuration = configuration; |
||||
|
_thirdUserRepository = thirdUserRepository; |
||||
|
_signInManager = signInManager; |
||||
|
_identityUserRepository = identityUserRepository; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
public override async Task<IActionResult> HandleAsync() |
||||
|
{ |
||||
|
//MiniProgram
|
||||
|
var request = await GetOpenIddictServerRequestAsync(HttpContext); |
||||
|
|
||||
|
string grantType = request.GrantType; |
||||
|
|
||||
|
if (grantType == "mini_program") |
||||
|
{ |
||||
|
//小程序登录
|
||||
|
|
||||
|
// 获取小程序 code 并换取 openid
|
||||
|
var jsCode = request.GetParameter("jsCode").ToString(); |
||||
|
|
||||
|
var wechatSession = await WeChatHelper.GetWechatSession(_configuration, jsCode); |
||||
|
|
||||
|
var thirdUserEnt = await _thirdUserRepository.FirstOrDefaultAsync(f => f.WechatOpenId == wechatSession.OpenId && f.IsActive == 'Y' && f.AbpUserId != null); |
||||
|
if (thirdUserEnt == null) |
||||
|
{ |
||||
|
throw new UserFriendlyException("用户未授权"); |
||||
|
} |
||||
|
// 查询关联的 AbpUser
|
||||
|
var abpUser = await _identityUserRepository.FirstOrDefaultAsync(f => f.Id == thirdUserEnt.AbpUserId); |
||||
|
if (abpUser == null) |
||||
|
{ |
||||
|
throw new UserFriendlyException("用户未关联权限"); |
||||
|
} |
||||
|
|
||||
|
// 生成声明主体
|
||||
|
var principal = await _signInManager.CreateUserPrincipalAsync(abpUser); |
||||
|
|
||||
|
var scopes = request.GetScopes(); |
||||
|
var resources = await GetResourcesAsync(scopes); |
||||
|
principal.SetScopes(scopes); |
||||
|
principal.SetResources(resources); |
||||
|
|
||||
|
await SetClaimsDestinationsAsync(principal); |
||||
|
|
||||
|
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
||||
|
} |
||||
|
else if (grantType == "phone_verify") |
||||
|
{ |
||||
|
//手机号+验证码登录认证
|
||||
|
|
||||
|
var mobilePhone = request.GetParameter("mobilePhone").ToString(); |
||||
|
var verifyCode = request.GetParameter("verifyCode").ToString(); |
||||
|
|
||||
|
|
||||
|
|
||||
|
var thirdUserEnt = await _thirdUserRepository.FirstOrDefaultAsync(f => f.MobilePhone == mobilePhone && f.IsActive == 'Y' && f.AbpUserId != null); |
||||
|
if (thirdUserEnt == null) |
||||
|
{ |
||||
|
throw new UserFriendlyException("用户未授权"); |
||||
|
} |
||||
|
// 查询关联的 AbpUser
|
||||
|
var abpUser = await _identityUserRepository.FirstOrDefaultAsync(f => f.Id == thirdUserEnt.AbpUserId); |
||||
|
if (abpUser == null) |
||||
|
{ |
||||
|
throw new UserFriendlyException("用户未关联权限"); |
||||
|
} |
||||
|
|
||||
|
// 生成声明主体
|
||||
|
var principal = await _signInManager.CreateUserPrincipalAsync(abpUser); |
||||
|
|
||||
|
var scopes = request.GetScopes(); |
||||
|
var resources = await GetResourcesAsync(scopes); |
||||
|
principal.SetScopes(scopes); |
||||
|
principal.SetResources(resources); |
||||
|
|
||||
|
await SetClaimsDestinationsAsync(principal); |
||||
|
|
||||
|
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return await base.HandleAsync(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue