diff --git a/src/admin/Bootstrap.Admin/Controllers/AccountController.cs b/src/admin/Bootstrap.Admin/Controllers/AccountController.cs
index 2491ad4f..a32202b7 100644
--- a/src/admin/Bootstrap.Admin/Controllers/AccountController.cs
+++ b/src/admin/Bootstrap.Admin/Controllers/AccountController.cs
@@ -3,6 +3,7 @@ using Bootstrap.DataAccess;
using Bootstrap.Security.Mvc;
using Longbow.GiteeAuth;
using Longbow.GitHubAuth;
+using Longbow.Web.SMS;
using Longbow.WeChatAuth;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
@@ -11,7 +12,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -109,12 +109,12 @@ namespace Bootstrap.Admin.Controllers
Password = code,
Icon = "default.jpg",
Description = "手机用户",
- App = provider.Option.App
+ App = provider.Options.App
};
UserHelper.Save(user);
// 根据配置文件设置默认角色
- var roles = RoleHelper.Retrieves().Where(r => provider.Option.Roles.Any(rl => rl.Equals(r.RoleName, StringComparison.OrdinalIgnoreCase))).Select(r => r.Id);
+ var roles = RoleHelper.Retrieves().Where(r => provider.Options.Roles.Any(rl => rl.Equals(r.RoleName, StringComparison.OrdinalIgnoreCase))).Select(r => r.Id);
RoleHelper.SaveByUserId(user.Id, roles);
}
}
diff --git a/src/admin/Bootstrap.Admin/Controllers/Api/LoginController.cs b/src/admin/Bootstrap.Admin/Controllers/Api/LoginController.cs
index 5aaea6cf..8b4c4888 100644
--- a/src/admin/Bootstrap.Admin/Controllers/Api/LoginController.cs
+++ b/src/admin/Bootstrap.Admin/Controllers/Api/LoginController.cs
@@ -2,9 +2,9 @@
using Bootstrap.DataAccess;
using Bootstrap.Security.Authentication;
using Longbow.Web.Mvc;
+using Longbow.Web.SMS;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
namespace Bootstrap.Admin.Controllers.Api
@@ -52,7 +52,7 @@ namespace Bootstrap.Admin.Controllers.Api
///
///
[HttpPut]
- public async Task Put([FromServices]ISMSProvider provider, [FromQuery]string phone) => string.IsNullOrEmpty(phone) ? false : await provider.SendCodeAsync(phone);
+ public async Task Put([FromServices]ISMSProvider provider, [FromQuery]string phone) => string.IsNullOrEmpty(phone) ? new SMSResult() { Result = false, Msg = "手机号不可为空" } : await provider.SendCodeAsync(phone);
///
/// 跨域握手协议
diff --git a/src/admin/Bootstrap.Admin/Extensions/SMSExtensions.cs b/src/admin/Bootstrap.Admin/Extensions/SMSExtensions.cs
index cd99f189..f1e4a641 100644
--- a/src/admin/Bootstrap.Admin/Extensions/SMSExtensions.cs
+++ b/src/admin/Bootstrap.Admin/Extensions/SMSExtensions.cs
@@ -1,15 +1,5 @@
-using Microsoft.AspNetCore.WebUtilities;
-using Microsoft.Extensions.Configuration;
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.Net.Http;
-using System.Security.Cryptography;
-using System.Text;
-using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
+using Longbow.Web.SMS;
+using Longbow.Web.SMS.Tencent;
namespace Microsoft.Extensions.DependencyInjection
{
@@ -25,228 +15,8 @@ namespace Microsoft.Extensions.DependencyInjection
///
public static IServiceCollection AddSMSProvider(this IServiceCollection services)
{
- services.AddTransient();
+ services.AddTransient();
return services;
}
}
-
- ///
- /// 短信登录接口
- ///
- public interface ISMSProvider
- {
- ///
- /// 手机下发验证码方法
- ///
- /// 手机号
- ///
- Task SendCodeAsync(string phoneNumber);
-
- ///
- /// 验证手机验证码是否正确方法
- ///
- /// 手机号
- /// 验证码
- ///
- bool Validate(string phoneNumber, string code);
-
- ///
- /// 获得 配置信息
- ///
- SMSOptions Option { get; }
- }
-
- ///
- /// 手机号登陆帮助类
- ///
- internal class DefaultSMSProvider : ISMSProvider
- {
- private static ConcurrentDictionary _pool = new ConcurrentDictionary();
-
- ///
- /// 获得 短信配置信息
- ///
- public SMSOptions Option { get; protected set; }
-
- private HttpClient _client;
-
- ///
- /// 构造函数
- ///
- ///
- ///
- public DefaultSMSProvider(IConfiguration configuration, IHttpClientFactory factory)
- {
- Option = configuration.GetSection().Get();
- _client = factory.CreateClient();
- }
-
- ///
- /// 下发验证码方法
- ///
- ///
- ///
- public async Task SendCodeAsync(string phoneNumber)
- {
- Option.Timestamp = (DateTimeOffset.UtcNow.Ticks - 621355968000000000) / 10000000;
- Option.Phone = phoneNumber;
- var requestParameters = new Dictionary()
- {
- { "CompanyCode", Option.CompanyCode },
- { "Phone", Option.Phone },
- { "TimeStamp", Option.Timestamp.ToString() },
- { "Sign", Sign() }
- };
-
- var url = QueryHelpers.AddQueryString(Option.RequestUrl, requestParameters);
- var req = await _client.GetAsync(url);
- var content = await req.Content.ReadAsStringAsync();
- var result = JsonSerializer.Deserialize(content, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
- var ret = false;
- if (result.Code == 1)
- {
- _pool.AddOrUpdate(Option.Phone, key => new AutoExpireValidateCode(Option.Phone, result.Data, Option.Expires), (key, v) => v.Reset(result.Data));
- ret = true;
- }
- else
- {
- new Exception(result.Msg).Log(new NameValueCollection()
- {
- ["UserId"] = Option.Phone,
- ["url"] = url,
- ["content"] = content
- });
- }
- return ret;
- }
-
- ///
- /// 验证验证码方法
- ///
- /// 手机号
- /// 验证码
- ///
- public bool Validate(string phone, string code) => _pool.TryGetValue(phone, out var signKey) && Hash($"{code}{Option.MD5Key}") == signKey.Code;
-
- private string Sign()
- {
- return Hash($"{Option.CompanyCode}{Option.Phone}{Option.Timestamp}{Option.MD5Key}");
- }
-
- private static string Hash(string data)
- {
- using (var md5 = MD5.Create())
- {
- var sign = BitConverter.ToString(md5.ComputeHash(Encoding.UTF8.GetBytes(data)));
- sign = sign.Replace("-", "").ToLowerInvariant();
- return sign;
- }
- }
-
- private class SMSResult
- {
- public int Code { get; set; }
-
- public string Data { get; set; }
-
- public string Msg { get; set; }
- }
-
- private class AutoExpireValidateCode
- {
- public AutoExpireValidateCode(string phone, string code, TimeSpan expires)
- {
- Phone = phone;
- Code = code;
- Expires = expires;
- RunAsync();
- }
-
- ///
- ///
- ///
- public string Code { get; private set; }
-
- ///
- ///
- ///
- public string Phone { get; }
-
- ///
- ///
- ///
- public TimeSpan Expires { get; set; }
-
- private CancellationTokenSource _tokenSource;
-
- private Task RunAsync() => Task.Run(() =>
- {
- _tokenSource = new CancellationTokenSource();
- if (!_tokenSource.Token.WaitHandle.WaitOne(Expires)) _pool.TryRemove(Phone, out var _);
- });
-
- ///
- ///
- ///
- ///
- public AutoExpireValidateCode Reset(string code)
- {
- Code = code;
- _tokenSource.Cancel();
- RunAsync();
- return this;
- }
- }
- }
-
- ///
- /// 短信网关配置类
- ///
- public class SMSOptions
- {
- ///
- /// 获得/设置 公司编码
- ///
- public string CompanyCode { get; set; }
-
- ///
- /// 获得/设置 下发手机号码
- ///
- public string Phone { get; set; }
-
- ///
- /// 获得/设置 签名密钥
- ///
- public string MD5Key { get; set; }
-
- ///
- /// 获得/设置 时间戳
- ///
- public long Timestamp { get; set; }
-
- ///
- /// 获得/设置 验证码有效时长
- ///
- public TimeSpan Expires { get; set; } = TimeSpan.FromMinutes(5);
-
- ///
- /// 获得/设置 角色集合
- ///
- public ICollection Roles { get; } = new HashSet();
-
- ///
- /// 获得/设置 登陆后首页
- ///
- public string HomePath { get; set; }
-
- ///
- /// 获得/设置 默认授权 App
- ///
- public string App { get; set; } = "0";
-
- ///
- /// 获得/设置 短信下发网关地址
- ///
- public string RequestUrl { get; set; } = "http://open.bluegoon.com/api/sms/sendcode";
- }
}
diff --git a/src/admin/Bootstrap.Admin/appsettings.Development.json b/src/admin/Bootstrap.Admin/appsettings.Development.json
index 9cd8411c..80ec42f5 100644
--- a/src/admin/Bootstrap.Admin/appsettings.Development.json
+++ b/src/admin/Bootstrap.Admin/appsettings.Development.json
@@ -70,13 +70,8 @@
"ClientSecret": "3427f2d901ba9afc76c1842a7303b2d67f8e098e71acc15051f89fe6f3d265db",
"CallbackPath": "/signin-gitee",
"HomePath": "/Admin/Profiles",
- "Scope": [
- "user_info",
- "projects"
- ],
- "Roles": [
- "Administrators"
- ],
+ "Scope": [ "user_info", "projects" ],
+ "Roles": [ "Administrators" ],
"App": "0",
"StarredUrl": "https://gitee.com/api/v5/user/starred/LongbowEnterprise/BootstrapAdmin"
},
@@ -86,13 +81,8 @@
"ClientSecret": "ffa759ca599df941b869efecb5e750bc1b27334e",
"CallbackPath": "/signin-github",
"HomePath": "/Admin/Profiles",
- "Scope": [
- "user_info",
- "repo"
- ],
- "Roles": [
- "Administrators"
- ],
+ "Scope": [ "user_info", "repo" ],
+ "Roles": [ "Administrators" ],
"App": "0",
"StarredUrl": "https://api.github.com/user/starred/ArgoZhang/BootstrapAdmin"
},
@@ -102,23 +92,27 @@
"ClientSecret": "",
"CallbackPath": "/signin-weixin",
"HomePath": "/Admin/Profiles",
- "Scope": [
- "snsapi_login"
- ],
- "Roles": [
- "Administrators"
- ],
+ "Scope": [ "snsapi_login" ],
+ "Roles": [ "Administrators" ],
"App": "0"
},
"SMSOptions": {
"CompanyCode": "",
"MD5Key": "MD5Key",
- "Roles": [
- "Administrators"
- ],
+ "Roles": [ "Administrators" ],
"HomePath": "/Admin/Profiles",
"App": "0"
},
+ "TencentSMSOptions": {
+ "AppId": "",
+ "AppKey": "",
+ "TplId": 0,
+ "Sign": "",
+ "Roles": [ "Default" ],
+ "HomePath": "/Admin/Profiles",
+ "App": "Demo",
+ "Debug": true
+ },
"AppMenus": [
"首页",
"测试页面",
diff --git a/src/admin/Bootstrap.Admin/appsettings.json b/src/admin/Bootstrap.Admin/appsettings.json
index d2a03f26..48c852dd 100644
--- a/src/admin/Bootstrap.Admin/appsettings.json
+++ b/src/admin/Bootstrap.Admin/appsettings.json
@@ -105,6 +105,15 @@
"HomePath": "/Home/Index",
"App": "2"
},
+ "TencentSMSOptions": {
+ "AppId": "",
+ "AppKey": "",
+ "TplId": 0,
+ "Sign": "",
+ "Roles": [ "Default" ],
+ "HomePath": "/Admin/Profiles",
+ "App": "Demo"
+ },
"LongbowCache": {
"Enabled": true,
"CorsItems": [
diff --git a/src/admin/Bootstrap.Admin/wwwroot/js/login.js b/src/admin/Bootstrap.Admin/wwwroot/js/login.js
index 76971c62..4199535b 100644
--- a/src/admin/Bootstrap.Admin/wwwroot/js/login.js
+++ b/src/admin/Bootstrap.Admin/wwwroot/js/login.js
@@ -90,7 +90,7 @@
return false;
});
- $('button[type="submit"]').on('click', function (e) {
+ var $loginButton = $('button[type="submit"]').on('click', function (e) {
$.captchaCheck($('#login .slidercaptcha'), function () {
$('form').submit();
});
@@ -165,6 +165,7 @@
$loginMobile.removeClass('d-none');
$this.attr('data-value', 'sms').text('用户名密码登陆');
+ $loginButton.attr('data-original-title', '请输入手机号码并点击发送验证码');
}
else {
// sms model
@@ -174,6 +175,7 @@
$loginMobile.addClass('d-none');
$this.attr('data-value', 'username').text('短信验证登陆');
+ $loginButton.attr('data-original-title', '不填写密码默认使用 Gitee 认证');
}
});
}
@@ -195,16 +197,22 @@
url: apiUrl,
method: 'PUT',
callback: function (result) {
- $this.attr('data-original-title', result ? "发送成功" : "短信登录体验活动结束").tooltip('show');
+ $this.attr('data-original-title', result.Result ? "发送成功" : "短信登录体验活动结束").tooltip('show');
var handler = setTimeout(function () {
clearTimeout(handler);
$this.tooltip('hide').attr('data-original-title', "点击发送验证码");
- }, 1000);
+ }, 2000);
- if (result) {
+ if (result.Result) {
// send success
$this.text('已发送').attr('disabled', true);
$('#code').removeAttr('disabled');
+ if (result.Data === null) $loginButton.attr('data-original-title', '请输入验证码');
+ else {
+ $('#code').val(result.Data);
+ $loginButton.attr('data-original-title', '点击登录系统');
+ }
+
timeHanlder = setTimeout(function () {
clearTimeout(timeHanlder);
var count = 299;
diff --git a/src/admin/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj b/src/admin/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj
index 9ad9d96a..7ab5772c 100644
--- a/src/admin/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj
+++ b/src/admin/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/test/UnitTest/BAWebHost.cs b/test/UnitTest/BAWebHost.cs
index f8aa41c7..6758c13f 100644
--- a/test/UnitTest/BAWebHost.cs
+++ b/test/UnitTest/BAWebHost.cs
@@ -1,17 +1,18 @@
using Longbow.Data;
+using Longbow.Web.SMS;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.Mvc.Testing.Handlers;
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
+using System.Threading.Tasks;
using UnitTest;
using Xunit;
-using Microsoft.Extensions.DependencyInjection;
-using System.Threading.Tasks;
namespace Bootstrap.Admin
{
@@ -138,14 +139,14 @@ namespace Bootstrap.Admin
///
/// 获得 短信配置信息
///
- public SMSOptions Option { get; protected set; } = new SMSOptions();
+ public SMSOptions Options { get; protected set; } = new SMSOptions();
///
/// 下发验证码方法
///
///
///
- public Task SendCodeAsync(string phoneNumber) => Task.FromResult(true);
+ public Task SendCodeAsync(string phoneNumber) => Task.FromResult(new SMSResult() { Result = true });
///
/// 验证验证码方法
diff --git a/test/UnitTest/Bootstrap.Admin/Api/SQLServer/LoginTest.cs b/test/UnitTest/Bootstrap.Admin/Api/SQLServer/LoginTest.cs
index e8b6dbd8..c51d379b 100644
--- a/test/UnitTest/Bootstrap.Admin/Api/SQLServer/LoginTest.cs
+++ b/test/UnitTest/Bootstrap.Admin/Api/SQLServer/LoginTest.cs
@@ -1,5 +1,6 @@
using Bootstrap.DataAccess;
using Longbow.Web.Mvc;
+using Longbow.Web.SMS;
using System.Net.Http;
using Xunit;
@@ -36,12 +37,14 @@ namespace Bootstrap.Admin.Api.SqlServer
public async void Put_Ok()
{
var resq = await Client.PutAsync("?phone=", new StringContent(""));
- var _token = await resq.Content.ReadAsStringAsync();
- Assert.Equal("false", _token);
+ var payload = await resq.Content.ReadAsStringAsync();
+ var resp = System.Text.Json.JsonSerializer.Deserialize(payload, new System.Text.Json.JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
+ Assert.False(resp.Result);
- resq = await Client.PutAsync("?phone=18910001000", new StringContent(""));
- _token = await resq.Content.ReadAsStringAsync();
- Assert.Equal("true", _token);
+ resq = await Client.PutAsync("?phone=18910281024", new StringContent(""));
+ payload = await resq.Content.ReadAsStringAsync();
+ resp = System.Text.Json.JsonSerializer.Deserialize(payload, new System.Text.Json.JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
+ Assert.True(resp.Result);
}
[Fact]
diff --git a/test/UnitTest/appsettings.json b/test/UnitTest/appsettings.json
index e11222a4..12da8d7b 100644
--- a/test/UnitTest/appsettings.json
+++ b/test/UnitTest/appsettings.json
@@ -1,4 +1,4 @@
-{
+{
"Logging": {
"LogLevel": {
"Default": "Error",
@@ -63,12 +63,19 @@
"SMSOptions": {
"CompanyCode": "",
"MD5Key": "MD5Key",
- "Roles": [
- "Administrators"
- ],
+ "Roles": [ "Administrators" ],
"HomePath": "/Admin/Profiles",
"App": "0"
},
+ "TencentSMSOptions": {
+ "AppId": "",
+ "AppKey": "",
+ "TplId": "",
+ "Sign": "",
+ "Roles": [ "Default" ],
+ "HomePath": "/Admin/Profiles",
+ "App": "Demo"
+ },
"LongbowCache": {
"Enabled": false
}