feat: 新增微信手机号快速验证登录功能
- 后端新增 POST /api/wxPhoneLogin 接口 - 前端登录页改用微信 getPhoneNumber 授权 - 保留原有微信登录和手机号验证码登录接口
This commit is contained in:
parent
140881e595
commit
3db0764780
|
|
@ -86,6 +86,19 @@ export const isTokenExpired = () => {
|
||||||
return Date.now() >= tokenExpireTime;
|
return Date.now() >= tokenExpireTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信手机号快速验证登录
|
||||||
|
* @param {String} code 微信手机号授权code(getPhoneNumber返回)
|
||||||
|
* @param {String} pid 推荐人ID
|
||||||
|
* @returns {Promise} 登录结果
|
||||||
|
*/
|
||||||
|
export const wxPhoneLogin = async (code, pid = '') => {
|
||||||
|
return await RequestManager.post('/wxPhoneLogin', {
|
||||||
|
code,
|
||||||
|
pid
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 微信登录
|
* 微信登录
|
||||||
* @param {Object} params 登录参数
|
* @param {Object} params 登录参数
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@
|
||||||
|
|
||||||
<!-- 底部登录区域 -->
|
<!-- 底部登录区域 -->
|
||||||
<view class="login-bottom">
|
<view class="login-bottom">
|
||||||
<!-- 小程序一键登录按钮 -->
|
<!-- 小程序手机号快速验证登录按钮 -->
|
||||||
<button v-if="isMpWeixin" class="login-btn" @click="getUserProfile">
|
<button v-if="isMpWeixin" class="login-btn" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber">
|
||||||
一键注册/登录
|
一键注册/登录
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { wxLogin, mobileLogin, sendSms, saveLoginTokens } from '@/common/server/auth.js';
|
import { wxLogin, wxPhoneLogin, mobileLogin, sendSms, saveLoginTokens } from '@/common/server/auth.js';
|
||||||
import { getUser } from '@/common/server/user.js';
|
import { getUser } from '@/common/server/user.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -129,6 +129,31 @@
|
||||||
// #endif
|
// #endif
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 微信手机号快速验证登录
|
||||||
|
async onGetPhoneNumber(e) {
|
||||||
|
if (!this.isAgree) {
|
||||||
|
return uni.showToast({ title: '请先同意用户协议和隐私政策', icon: 'none' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!e.detail.code) {
|
||||||
|
return uni.showToast({ title: '授权失败,请重试', icon: 'none' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await wxPhoneLogin(e.detail.code, uni.getStorageSync('pid'));
|
||||||
|
if (res.status == 1) {
|
||||||
|
saveLoginTokens(res.data);
|
||||||
|
this.$c.msg("登录成功");
|
||||||
|
this.handleLoginSuccess();
|
||||||
|
} else {
|
||||||
|
this.$c.msg("登录失败: " + res.msg);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.$c.msg("登录失败,请重试");
|
||||||
|
console.error('微信手机号登录失败:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async sendVerifyCode() {
|
async sendVerifyCode() {
|
||||||
if (this.countdown > 0) return;
|
if (this.countdown > 0) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,37 @@ public class AuthController : ControllerBase
|
||||||
return ApiResponse<LoginResponse>.Fail(result.ErrorMessage ?? "登录失败");
|
return ApiResponse<LoginResponse>.Fail(result.ErrorMessage ?? "登录失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 微信手机号快速验证登录
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// POST /api/wxPhoneLogin
|
||||||
|
///
|
||||||
|
/// 使用微信getPhoneNumber获取的code进行登录,返回双Token(Access Token + Refresh Token)
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="request">微信手机号登录请求参数,包含手机号授权code</param>
|
||||||
|
/// <returns>登录成功返回LoginResponse(包含accessToken、refreshToken、expiresIn),失败返回错误信息</returns>
|
||||||
|
[HttpPost("wxPhoneLogin")]
|
||||||
|
[ProducesResponseType(typeof(ApiResponse<LoginResponse>), StatusCodes.Status200OK)]
|
||||||
|
public async Task<ApiResponse<LoginResponse>> WechatPhoneLogin([FromBody] WechatPhoneLoginRequest request)
|
||||||
|
{
|
||||||
|
if (request == null || string.IsNullOrWhiteSpace(request.Code))
|
||||||
|
{
|
||||||
|
return ApiResponse<LoginResponse>.Fail("手机号授权code不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _authService.WechatPhoneLoginAsync(request.Code, request.Pid);
|
||||||
|
|
||||||
|
if (result.Success && result.LoginResponse != null)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("WeChat phone login successful: UserId={UserId}", result.UserId);
|
||||||
|
return ApiResponse<LoginResponse>.Success(result.LoginResponse, "登录成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogWarning("WeChat phone login failed: {Error}", result.ErrorMessage);
|
||||||
|
return ApiResponse<LoginResponse>.Fail(result.ErrorMessage ?? "登录失败");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 刷新 Token
|
/// 刷新 Token
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,14 @@ public interface IAuthService
|
||||||
/// <returns>绑定结果</returns>
|
/// <returns>绑定结果</returns>
|
||||||
Task<BindMobileResponse> WechatBindMobileAsync(int userId, string wechatCode);
|
Task<BindMobileResponse> WechatBindMobileAsync(int userId, string wechatCode);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 微信手机号快速验证登录
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="phoneCode">微信手机号授权code(getPhoneNumber返回)</param>
|
||||||
|
/// <param name="pid">推荐人ID</param>
|
||||||
|
/// <returns>登录结果</returns>
|
||||||
|
Task<LoginResult> WechatPhoneLoginAsync(string phoneCode, int? pid);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 记录登录信息
|
/// 记录登录信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -384,6 +384,111 @@ public class AuthService : IAuthService
|
||||||
return new BindMobileResponse { Token = null };
|
return new BindMobileResponse { Token = null };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 微信手机号快速验证登录
|
||||||
|
/// 使用微信getPhoneNumber获取的code直接登录
|
||||||
|
/// </summary>
|
||||||
|
public async Task<LoginResult> WechatPhoneLoginAsync(string phoneCode, int? pid)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("[AuthService] 微信手机号登录开始,pid={Pid}", pid);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(phoneCode))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("[AuthService] 微信手机号登录失败:code为空");
|
||||||
|
return new LoginResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
ErrorMessage = "手机号授权code不能为空"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 防抖机制 - 3秒内不允许重复登录
|
||||||
|
var debounceKey = $"{LoginDebounceKeyPrefix}wxphone:{phoneCode}";
|
||||||
|
var lockAcquired = await _redisService.TryAcquireLockAsync(debounceKey, "1", TimeSpan.FromSeconds(DebounceSeconds));
|
||||||
|
if (!lockAcquired)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("[AuthService] 防抖触发,拒绝重复登录请求");
|
||||||
|
return new LoginResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
ErrorMessage = "请勿频繁登录"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用微信API获取手机号
|
||||||
|
_logger.LogInformation("[AuthService] 开始调用微信API获取手机号...");
|
||||||
|
var mobileResult = await _wechatService.GetMobileAsync(phoneCode);
|
||||||
|
|
||||||
|
if (!mobileResult.Success || string.IsNullOrWhiteSpace(mobileResult.Mobile))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("[AuthService] 获取手机号失败: {Error}", mobileResult.ErrorMessage);
|
||||||
|
return new LoginResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
ErrorMessage = mobileResult.ErrorMessage ?? "获取手机号失败"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var mobile = mobileResult.Mobile;
|
||||||
|
_logger.LogInformation("[AuthService] 获取手机号成功: {Mobile}", MaskMobile(mobile));
|
||||||
|
|
||||||
|
// 根据手机号查找用户
|
||||||
|
var user = await _userService.GetUserByMobileAsync(mobile);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
// 用户不存在,创建新用户
|
||||||
|
_logger.LogInformation("[AuthService] 用户不存在,开始创建新用户...");
|
||||||
|
var createDto = new CreateUserDto
|
||||||
|
{
|
||||||
|
Mobile = mobile,
|
||||||
|
Nickname = $"用户{Random.Shared.Next(100000, 999999)}",
|
||||||
|
Headimg = GenerateDefaultAvatar(mobile),
|
||||||
|
Pid = pid?.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
user = await _userService.CreateUserAsync(createDto);
|
||||||
|
|
||||||
|
// 设置手机号已绑定状态
|
||||||
|
await _userService.UpdateUserAsync(user.Id, new UpdateUserDto { Mobile = mobile });
|
||||||
|
|
||||||
|
_logger.LogInformation("[AuthService] 新用户创建成功: UserId={UserId}, Mobile={Mobile}", user.Id, MaskMobile(mobile));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogInformation("[AuthService] 找到已有用户: UserId={UserId}", user.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成双 Token
|
||||||
|
_logger.LogInformation("[AuthService] 开始生成双 Token: UserId={UserId}", user.Id);
|
||||||
|
var loginResponse = await GenerateLoginResponseAsync(user, null);
|
||||||
|
|
||||||
|
// 更新UserAccount表
|
||||||
|
await CreateOrUpdateAccountTokenAsync(user.Id, loginResponse.AccessToken);
|
||||||
|
|
||||||
|
_logger.LogInformation("[AuthService] 微信手机号登录成功: UserId={UserId}", user.Id);
|
||||||
|
|
||||||
|
return new LoginResult
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Token = loginResponse.AccessToken,
|
||||||
|
UserId = user.Id,
|
||||||
|
LoginResponse = loginResponse
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "[AuthService] 微信手机号登录异常: {Message}", ex.Message);
|
||||||
|
return new LoginResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
ErrorMessage = "网络故障,请稍后再试"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 记录登录信息
|
/// 记录登录信息
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace HoneyBox.Model.Models.Auth;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 微信手机号快速验证登录请求
|
||||||
|
/// </summary>
|
||||||
|
public class WechatPhoneLoginRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 微信手机号授权code(getPhoneNumber返回的code)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("code")]
|
||||||
|
public string Code { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 推荐人ID(前端可能传空字符串,所以用string接收)
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("pid")]
|
||||||
|
public string? PidStr { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取推荐人ID(转换为int)
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public int? Pid => string.IsNullOrWhiteSpace(PidStr) ? null : int.TryParse(PidStr, out var pid) ? pid : null;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user