提交代码
This commit is contained in:
parent
98f4214809
commit
af397d25c8
|
|
@ -4,6 +4,7 @@ using ChouBox.Code.TencentCloudExtend.Model;
|
||||||
using ChouBox.Model.Entities;
|
using ChouBox.Model.Entities;
|
||||||
|
|
||||||
using HuanMeng.DotNetCore.Base;
|
using HuanMeng.DotNetCore.Base;
|
||||||
|
using HuanMeng.DotNetCore.Extensions;
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
@ -89,10 +90,66 @@ public class ChouBoxCodeBase
|
||||||
if (_httpContextAccessor == null)
|
if (_httpContextAccessor == null)
|
||||||
{
|
{
|
||||||
_httpContextAccessor = _serviceProvider.GetRequiredService<IHttpContextAccessor>();
|
_httpContextAccessor = _serviceProvider.GetRequiredService<IHttpContextAccessor>();
|
||||||
|
|
||||||
}
|
}
|
||||||
return _httpContextAccessor;
|
return _httpContextAccessor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取客户端真实IP地址,考虑Nginx等代理情况
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>客户端真实IP地址</returns>
|
||||||
|
protected string GetRealIpAddress()
|
||||||
|
{
|
||||||
|
var context = HttpContextAccessor.HttpContext;
|
||||||
|
if (context == null)
|
||||||
|
return "unknown";
|
||||||
|
|
||||||
|
// 尝试从X-Forwarded-For获取真实IP
|
||||||
|
string ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
|
||||||
|
|
||||||
|
// 如果X-Forwarded-For为空,尝试X-Real-IP
|
||||||
|
if (string.IsNullOrEmpty(ip))
|
||||||
|
{
|
||||||
|
ip = context.Request.Headers["X-Real-IP"].FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试其他可能包含真实IP的头
|
||||||
|
if (string.IsNullOrEmpty(ip))
|
||||||
|
{
|
||||||
|
ip = context.Request.Headers["Proxy-Client-IP"].FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(ip))
|
||||||
|
{
|
||||||
|
ip = context.Request.Headers["WL-Proxy-Client-IP"].FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(ip))
|
||||||
|
{
|
||||||
|
ip = context.Request.Headers["HTTP_CLIENT_IP"].FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(ip))
|
||||||
|
{
|
||||||
|
ip = context.Request.Headers["HTTP_X_FORWARDED_FOR"].FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果还是获取不到,则使用RemoteIpAddress
|
||||||
|
if (string.IsNullOrEmpty(ip))
|
||||||
|
{
|
||||||
|
ip = context.Connection.RemoteIpAddress?.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果X-Forwarded-For包含多个IP,取第一个非内网IP
|
||||||
|
if (!string.IsNullOrEmpty(ip) && ip.Contains(","))
|
||||||
|
{
|
||||||
|
ip = ip.Split(',')[0].Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip ?? "unknown";
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 日志
|
#region 日志
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
using ChouBox.Code.AppExtend;
|
using ChouBox.Code.AppExtend;
|
||||||
using ChouBox.Code.TencentCloudExtend;
|
using ChouBox.Code.TencentCloudExtend;
|
||||||
|
using ChouBox.Model.Entities;
|
||||||
|
|
||||||
|
using HuanMeng.DotNetCore.Base;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
@ -16,17 +21,23 @@ namespace ChouBox.Code.Other;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SMSBLL : ChouBoxCodeBase
|
public class SMSBLL : ChouBoxCodeBase
|
||||||
{
|
{
|
||||||
|
private const string RETRY_COUNT_SUFFIX = ":RetryCount";
|
||||||
|
private const string LOCK_SUFFIX = ":Lock";
|
||||||
|
|
||||||
public SMSBLL(IServiceProvider serviceProvider) : base(serviceProvider)
|
public SMSBLL(IServiceProvider serviceProvider) : base(serviceProvider)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送验证码
|
/// 发送验证码
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="phone"></param>
|
/// <param name="phone"></param>
|
||||||
/// <returns></returns>
|
/// <param name="codeType">验证码类型:1-注册,2-登录,3-找回密码,4-修改手机,5-修改邮箱,6-其他</param>
|
||||||
/// <exception cref="ArgumentNullException"></exception>
|
/// <param name="userId">用户ID,可为空表示未登录用户</param>
|
||||||
|
/// <returns>返回下一次可请求的等待秒数,-1表示当天不能再请求</returns>
|
||||||
|
/// <exception cref="CustomException"></exception>
|
||||||
/// <exception cref="Exception"></exception>
|
/// <exception cref="Exception"></exception>
|
||||||
public async Task<string> SendPhoneAsync(string phone)
|
public async Task<int> SendPhoneAsync(string phone, sbyte codeType = 2, ulong? userId = null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(phone))
|
if (string.IsNullOrEmpty(phone))
|
||||||
{
|
{
|
||||||
|
|
@ -37,23 +48,131 @@ public class SMSBLL : ChouBoxCodeBase
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException("暂未开放发送验证码!");
|
throw new ArgumentNullException("暂未开放发送验证码!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查当天发送次数限制
|
||||||
|
var dailyCountKey = $"VerificationCodeDailyCount:{phone}:{DateTime.Now:yyyyMMdd}";
|
||||||
|
var dailyCount = RedisCache.StringGet<int?>(dailyCountKey) ?? 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 获取重试次数
|
||||||
|
var retryCountKey = $"VerificationCode:{phone}{RETRY_COUNT_SUFFIX}";
|
||||||
|
var retryCount = RedisCache.StringGet<int?>(retryCountKey) ?? 0;
|
||||||
|
var resendLockKey = $"VerificationCode:{phone}{LOCK_SUFFIX}";
|
||||||
|
var resendLock = RedisCache.StringGet<string>(resendLockKey);
|
||||||
|
|
||||||
|
// 计算本次等待时间
|
||||||
|
int waitSeconds = GetWaitSeconds(retryCount);
|
||||||
|
|
||||||
|
if (resendLock != null && !string.IsNullOrEmpty(resendLock))
|
||||||
|
{
|
||||||
|
// 获取锁的剩余过期时间(秒)
|
||||||
|
var remainingSeconds = RedisCache.KeyTimeToLive(resendLockKey)?.TotalSeconds ?? 0;
|
||||||
|
remainingSeconds = Math.Ceiling(remainingSeconds); // 向上取整
|
||||||
|
|
||||||
|
string message;
|
||||||
|
if (remainingSeconds <= 60)
|
||||||
|
{
|
||||||
|
message = $"验证码发送过于频繁,请{remainingSeconds}秒后再试。";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int remainingMinutes = (int)Math.Floor(remainingSeconds / 60);
|
||||||
|
message = $"验证码发送过于频繁,请{remainingMinutes}分钟后再试。";
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new CustomException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置每天最大发送次数为5次
|
||||||
|
const int maxDailyCount = 5;
|
||||||
|
if (dailyCount >= maxDailyCount)
|
||||||
|
{
|
||||||
|
// 记录达到每日上限日志
|
||||||
|
Logger.LogWarning($"手机号{phone}当天验证码发送次数已达上限({maxDailyCount}次)");
|
||||||
|
throw new CustomException($"当天验证码发送次数已达上限,请明天再试");
|
||||||
|
}
|
||||||
|
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
var verificationCode = random.Next(1000, 9999);
|
var verificationCode = random.Next(1000, 9999);
|
||||||
var redisKey = $"VerificationCode:{phone}";
|
var redisKey = $"VerificationCode:{phone}";
|
||||||
var redisVerificationCode = RedisCache.StringGet<string>(redisKey);
|
|
||||||
if (redisVerificationCode != null && !string.IsNullOrEmpty(redisVerificationCode))
|
|
||||||
{
|
|
||||||
throw new Exception("验证码已发送!");
|
|
||||||
}
|
|
||||||
|
|
||||||
TencentSMSSendVerificationCode tencentSMSSendVerificationCode = new TencentSMSSendVerificationCode(smsConfig, phone);
|
// 获取客户端真实IP地址
|
||||||
var result = await tencentSMSSendVerificationCode.SendVerificationCode(verificationCode.ToString(), 5);
|
string ipAddress = GetRealIpAddress();
|
||||||
if (!result)
|
|
||||||
{
|
|
||||||
throw new Exception("验证码发送失败");
|
|
||||||
}
|
|
||||||
await RedisCache.StringSetAsync(redisKey, verificationCode.ToString(), TimeSpan.FromMinutes(5));
|
|
||||||
|
|
||||||
return "验证码已发送";
|
try
|
||||||
|
{
|
||||||
|
TencentSMSSendVerificationCode tencentSMSSendVerificationCode = new TencentSMSSendVerificationCode(smsConfig, phone);
|
||||||
|
var result = await tencentSMSSendVerificationCode.SendVerificationCode(verificationCode.ToString(), 5);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
// 记录发送失败日志
|
||||||
|
Logger.LogError($"验证码发送失败,手机号:{phone},IP:{ipAddress}");
|
||||||
|
throw new CustomException("验证码发送失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送成功,存入Redis,5分钟有效期
|
||||||
|
await RedisCache.StringSetAsync(redisKey, verificationCode.ToString(), TimeSpan.FromMinutes(5));
|
||||||
|
|
||||||
|
// 更新重试次数和锁定时间
|
||||||
|
retryCount++;
|
||||||
|
var nextWaitSeconds = GetWaitSeconds(retryCount);
|
||||||
|
|
||||||
|
// 设置不能重发的锁
|
||||||
|
await RedisCache.StringSetAsync(resendLockKey, "1", TimeSpan.FromSeconds(waitSeconds));
|
||||||
|
|
||||||
|
// 更新重试次数,5分钟后过期
|
||||||
|
await RedisCache.StringSetAsync(retryCountKey, retryCount.ToString(), TimeSpan.FromMinutes(5));
|
||||||
|
|
||||||
|
// 更新当天发送次数计数并设置过期时间为当天剩余时间
|
||||||
|
dailyCount++;
|
||||||
|
var endOfDay = DateTime.Today.AddDays(1).AddSeconds(-1); // 当天23:59:59
|
||||||
|
var expireTime = endOfDay - DateTime.Now;
|
||||||
|
await RedisCache.StringSetAsync(dailyCountKey, dailyCount.ToString(), expireTime);
|
||||||
|
|
||||||
|
// 保存到数据库
|
||||||
|
var verificationRecord = new UserVerificationCodes
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
Account = phone,
|
||||||
|
Code = verificationCode.ToString(),
|
||||||
|
CodeType = codeType,
|
||||||
|
Channel = "sms",
|
||||||
|
IpAddress = ipAddress,
|
||||||
|
Status = 0, // 未使用
|
||||||
|
CreatedAt = DateTime.Now,
|
||||||
|
UpdatedAt = DateTime.Now,
|
||||||
|
ExpiredAt = DateTime.Now.AddMinutes(5)
|
||||||
|
};
|
||||||
|
|
||||||
|
await Dao.Context.UserVerificationCodes.AddAsync(verificationRecord);
|
||||||
|
await Dao.Context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return nextWaitSeconds;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// 记录异常日志
|
||||||
|
Logger.LogError($"发送验证码异常:{ex.Message},手机号:{phone},IP:{ipAddress}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据重试次数获取等待时间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="retryCount">重试次数</param>
|
||||||
|
/// <returns>等待秒数,-1表示不允许再次请求</returns>
|
||||||
|
private int GetWaitSeconds(int retryCount)
|
||||||
|
{
|
||||||
|
return retryCount switch
|
||||||
|
{
|
||||||
|
0 => 30, // 第1次请求后等待30秒
|
||||||
|
1 => 60, // 第2次请求后等待60秒
|
||||||
|
2 => 60, // 第3次请求后等待60秒
|
||||||
|
3 => 300, // 第4次请求后等待300秒(5分钟)
|
||||||
|
4 => 300, // 第5次请求后等待300秒(5分钟)
|
||||||
|
_ => -1 // 超过5次则不允许再次请求
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -296,4 +296,9 @@ public partial class Goods
|
||||||
/// 每日限购次数
|
/// 每日限购次数
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int DailyXiangou { get; set; }
|
public int DailyXiangou { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下架金额,当盒子利润值小于它时,下架(如100 盒子利润出现负数后,超过-100就下架)
|
||||||
|
/// </summary>
|
||||||
|
public int XiajiaJine { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,4 +39,14 @@ public partial class GoodsOffshelfLog
|
||||||
/// 下架时间
|
/// 下架时间
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int CreateTime { get; set; }
|
public int CreateTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注
|
||||||
|
/// </summary>
|
||||||
|
public string? Remarks { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否已读
|
||||||
|
/// </summary>
|
||||||
|
public int IsRead { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
70
ChouBox.Model/Entities/UserVerificationCodes.cs
Normal file
70
ChouBox.Model/Entities/UserVerificationCodes.cs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace ChouBox.Model.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户验证码表
|
||||||
|
/// </summary>
|
||||||
|
public partial class UserVerificationCodes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 主键ID
|
||||||
|
/// </summary>
|
||||||
|
public ulong Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户ID,可为空表示未登录用户
|
||||||
|
/// </summary>
|
||||||
|
public ulong? UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 账号(手机号/邮箱)
|
||||||
|
/// </summary>
|
||||||
|
public string Account { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证码
|
||||||
|
/// </summary>
|
||||||
|
public string Code { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证码类型:1-注册,2-登录,3-找回密码,4-修改手机,5-修改邮箱,6-其他
|
||||||
|
/// </summary>
|
||||||
|
public sbyte CodeType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送渠道:sms-短信,email-邮件
|
||||||
|
/// </summary>
|
||||||
|
public string Channel { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求IP地址
|
||||||
|
/// </summary>
|
||||||
|
public string? IpAddress { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态:0-未使用,1-已使用,2-已失效
|
||||||
|
/// </summary>
|
||||||
|
public sbyte Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime UpdatedAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 过期时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime ExpiredAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? UsedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,6 @@ using Pomelo.EntityFrameworkCore.MySql.Scaffolding.Internal;
|
||||||
|
|
||||||
namespace ChouBox.Model.Entities;
|
namespace ChouBox.Model.Entities;
|
||||||
|
|
||||||
///
|
|
||||||
public partial class YoudaContext : DbContext
|
public partial class YoudaContext : DbContext
|
||||||
{
|
{
|
||||||
public YoudaContext()
|
public YoudaContext()
|
||||||
|
|
@ -407,6 +406,11 @@ public partial class YoudaContext : DbContext
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual DbSet<UserTaskList> UserTaskList { get; set; }
|
public virtual DbSet<UserTaskList> UserTaskList { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户验证码表
|
||||||
|
/// </summary>
|
||||||
|
public virtual DbSet<UserVerificationCodes> UserVerificationCodes { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 会员vip
|
/// 会员vip
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -964,6 +968,8 @@ public partial class YoudaContext : DbContext
|
||||||
|
|
||||||
entity.HasIndex(e => e.Id, "id").IsUnique();
|
entity.HasIndex(e => e.Id, "id").IsUnique();
|
||||||
|
|
||||||
|
entity.HasIndex(e => new { e.UserId, e.Addtime, e.Status }, "idx_user_addtime_status");
|
||||||
|
|
||||||
entity.Property(e => e.Id)
|
entity.Property(e => e.Id)
|
||||||
.HasColumnType("int(10) unsigned")
|
.HasColumnType("int(10) unsigned")
|
||||||
.HasColumnName("id");
|
.HasColumnName("id");
|
||||||
|
|
@ -1469,6 +1475,10 @@ public partial class YoudaContext : DbContext
|
||||||
.HasComment("下架生效抽数")
|
.HasComment("下架生效抽数")
|
||||||
.HasColumnType("int(11)")
|
.HasColumnType("int(11)")
|
||||||
.HasColumnName("xiajia_auto_coushu");
|
.HasColumnName("xiajia_auto_coushu");
|
||||||
|
entity.Property(e => e.XiajiaJine)
|
||||||
|
.HasComment("下架金额,当盒子利润值小于它时,下架(如100 盒子利润出现负数后,超过-100就下架)")
|
||||||
|
.HasColumnType("int(11)")
|
||||||
|
.HasColumnName("xiajia_jine");
|
||||||
entity.Property(e => e.XiajiaLirun)
|
entity.Property(e => e.XiajiaLirun)
|
||||||
.HasComment("下架利润率")
|
.HasComment("下架利润率")
|
||||||
.HasColumnType("int(11)")
|
.HasColumnType("int(11)")
|
||||||
|
|
@ -1807,6 +1817,10 @@ public partial class YoudaContext : DbContext
|
||||||
.HasPrecision(10, 2)
|
.HasPrecision(10, 2)
|
||||||
.HasComment("出货总价值")
|
.HasComment("出货总价值")
|
||||||
.HasColumnName("goods_total");
|
.HasColumnName("goods_total");
|
||||||
|
entity.Property(e => e.IsRead)
|
||||||
|
.HasComment("是否已读")
|
||||||
|
.HasColumnType("int(11)")
|
||||||
|
.HasColumnName("is_read");
|
||||||
entity.Property(e => e.OrderTotal)
|
entity.Property(e => e.OrderTotal)
|
||||||
.HasPrecision(10, 2)
|
.HasPrecision(10, 2)
|
||||||
.HasComment("订单总价值")
|
.HasComment("订单总价值")
|
||||||
|
|
@ -1815,6 +1829,10 @@ public partial class YoudaContext : DbContext
|
||||||
.HasPrecision(10, 2)
|
.HasPrecision(10, 2)
|
||||||
.HasComment("当前利润率")
|
.HasComment("当前利润率")
|
||||||
.HasColumnName("profit_rate");
|
.HasColumnName("profit_rate");
|
||||||
|
entity.Property(e => e.Remarks)
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasComment("备注")
|
||||||
|
.HasColumnName("remarks");
|
||||||
entity.Property(e => e.XiajiaLirun)
|
entity.Property(e => e.XiajiaLirun)
|
||||||
.HasPrecision(10, 2)
|
.HasPrecision(10, 2)
|
||||||
.HasComment("配置的下架利润阈值")
|
.HasComment("配置的下架利润阈值")
|
||||||
|
|
@ -2733,8 +2751,12 @@ public partial class YoudaContext : DbContext
|
||||||
|
|
||||||
entity.HasIndex(e => e.Id, "id").IsUnique();
|
entity.HasIndex(e => e.Id, "id").IsUnique();
|
||||||
|
|
||||||
|
entity.HasIndex(e => new { e.GoodsId, e.ShangId, e.ParentGoodsListId }, "idx_count_fast");
|
||||||
|
|
||||||
entity.HasIndex(e => new { e.GoodsId, e.UserId, e.Addtime }, "idx_gid_uid_time");
|
entity.HasIndex(e => new { e.GoodsId, e.UserId, e.Addtime }, "idx_gid_uid_time");
|
||||||
|
|
||||||
|
entity.HasIndex(e => new { e.ShangId, e.ParentGoodsListId, e.GoodsId, e.OrderType, e.Id }, "idx_opt_query");
|
||||||
|
|
||||||
entity.HasIndex(e => new { e.UserId, e.Addtime }, "idx_user_addtime");
|
entity.HasIndex(e => new { e.UserId, e.Addtime }, "idx_user_addtime");
|
||||||
|
|
||||||
entity.HasIndex(e => e.InsuranceIs, "insurance_is");
|
entity.HasIndex(e => e.InsuranceIs, "insurance_is");
|
||||||
|
|
@ -3922,7 +3944,7 @@ public partial class YoudaContext : DbContext
|
||||||
.HasColumnType("int(11) unsigned")
|
.HasColumnType("int(11) unsigned")
|
||||||
.HasColumnName("mb_number");
|
.HasColumnName("mb_number");
|
||||||
entity.Property(e => e.Mobile)
|
entity.Property(e => e.Mobile)
|
||||||
.HasMaxLength(11)
|
.HasMaxLength(15)
|
||||||
.HasComment("手机号")
|
.HasComment("手机号")
|
||||||
.HasColumnName("mobile");
|
.HasColumnName("mobile");
|
||||||
entity.Property(e => e.Money)
|
entity.Property(e => e.Money)
|
||||||
|
|
@ -4739,6 +4761,71 @@ public partial class YoudaContext : DbContext
|
||||||
.HasColumnName("z_number");
|
.HasColumnName("z_number");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<UserVerificationCodes>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
||||||
|
entity.ToTable("user_verification_codes", tb => tb.HasComment("用户验证码表"));
|
||||||
|
|
||||||
|
entity.HasIndex(e => new { e.Account, e.CodeType }, "idx_account_code_type");
|
||||||
|
|
||||||
|
entity.HasIndex(e => e.Code, "idx_code");
|
||||||
|
|
||||||
|
entity.HasIndex(e => e.ExpiredAt, "idx_expired_at");
|
||||||
|
|
||||||
|
entity.Property(e => e.Id)
|
||||||
|
.HasComment("主键ID")
|
||||||
|
.HasColumnType("bigint(20) unsigned")
|
||||||
|
.HasColumnName("id");
|
||||||
|
entity.Property(e => e.Account)
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasComment("账号(手机号/邮箱)")
|
||||||
|
.HasColumnName("account");
|
||||||
|
entity.Property(e => e.Channel)
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasComment("发送渠道:sms-短信,email-邮件")
|
||||||
|
.HasColumnName("channel");
|
||||||
|
entity.Property(e => e.Code)
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasComment("验证码")
|
||||||
|
.HasColumnName("code");
|
||||||
|
entity.Property(e => e.CodeType)
|
||||||
|
.HasComment("验证码类型:1-注册,2-登录,3-找回密码,4-修改手机,5-修改邮箱,6-其他")
|
||||||
|
.HasColumnType("tinyint(4)")
|
||||||
|
.HasColumnName("code_type");
|
||||||
|
entity.Property(e => e.CreatedAt)
|
||||||
|
.HasDefaultValueSql("CURRENT_TIMESTAMP")
|
||||||
|
.HasComment("创建时间")
|
||||||
|
.HasColumnType("datetime")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
entity.Property(e => e.ExpiredAt)
|
||||||
|
.HasComment("过期时间")
|
||||||
|
.HasColumnType("datetime")
|
||||||
|
.HasColumnName("expired_at");
|
||||||
|
entity.Property(e => e.IpAddress)
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasComment("请求IP地址")
|
||||||
|
.HasColumnName("ip_address");
|
||||||
|
entity.Property(e => e.Status)
|
||||||
|
.HasComment("状态:0-未使用,1-已使用,2-已失效")
|
||||||
|
.HasColumnType("tinyint(4)")
|
||||||
|
.HasColumnName("status");
|
||||||
|
entity.Property(e => e.UpdatedAt)
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasDefaultValueSql("CURRENT_TIMESTAMP")
|
||||||
|
.HasComment("更新时间")
|
||||||
|
.HasColumnType("datetime")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
entity.Property(e => e.UsedAt)
|
||||||
|
.HasComment("使用时间")
|
||||||
|
.HasColumnType("datetime")
|
||||||
|
.HasColumnName("used_at");
|
||||||
|
entity.Property(e => e.UserId)
|
||||||
|
.HasComment("用户ID,可为空表示未登录用户")
|
||||||
|
.HasColumnType("bigint(20) unsigned")
|
||||||
|
.HasColumnName("user_id");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity<UserVip>(entity =>
|
modelBuilder.Entity<UserVip>(entity =>
|
||||||
{
|
{
|
||||||
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
entity.HasKey(e => e.Id).HasName("PRIMARY");
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,5 @@ dotnet ef dbcontext scaffold "Server=192.168.1.56;Database=youda;User=youda;Pass
|
||||||
|
|
||||||
|
|
||||||
dotnet ef dbcontext scaffold "Server=192.168.1.56;Database=youda;User=youda;Password=youda;" Pomelo.EntityFrameworkCore.MySql -o Entities/ --use-database-names --no-pluralize --force --context-dir Context --project ChouBox.Model
|
dotnet ef dbcontext scaffold "Server=192.168.1.56;Database=youda;User=youda;Password=youda;" Pomelo.EntityFrameworkCore.MySql -o Entities/ --use-database-names --no-pluralize --force --context-dir Context --project ChouBox.Model
|
||||||
dotnet ef dbcontext scaffold "Server=192.168.1.56;Database=youda;User=youda;Password=youda;" Pomelo.EntityFrameworkCore.MySql -o Entities/ --no-pluralize --force
|
dotnet ef dbcontext scaffold "server=192.168.195.13;port=3306;database=youda;user=youda;password=youda;charset=utf8mb4" Pomelo.EntityFrameworkCore.MySql -o Entities/ --no-pluralize --force
|
||||||
```
|
```
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
using HuanMeng.DotNetCore.AttributeExtend;
|
using HuanMeng.DotNetCore.AttributeExtend;
|
||||||
using HuanMeng.DotNetCore.Base;
|
using HuanMeng.DotNetCore.Base;
|
||||||
using HuanMeng.DotNetCore.Extensions;
|
using HuanMeng.DotNetCore.Extensions;
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
|
@ -14,7 +15,7 @@ namespace ChouBox.WebApi.Controllers;
|
||||||
/// <param name="serviceProvider"></param>
|
/// <param name="serviceProvider"></param>
|
||||||
[Route("api/v2/[controller]")]
|
[Route("api/v2/[controller]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class AccountController(IServiceProvider serviceProvider) : ControllerBase
|
public class AccountController(IServiceProvider serviceProvider, IHttpContextAccessor httpContextAccessor) : ControllerBase
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -25,9 +26,10 @@ public class AccountController(IServiceProvider serviceProvider) : ControllerBas
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("sendSms")]
|
[Route("sendSms")]
|
||||||
public async Task<string> SendPhoneAsync([FromForm]string phone)
|
[Message("验证码发送成功!")]
|
||||||
|
public async Task<int> SendPhoneAsync([FromForm] string phone)
|
||||||
{
|
{
|
||||||
SMSBLL sms = new SMSBLL(serviceProvider);
|
SMSBLL sms = new SMSBLL(serviceProvider);
|
||||||
return await sms.SendPhoneAsync(phone);
|
return await sms.SendPhoneAsync(phone); ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,15 +56,112 @@ namespace ChouBox.WebApi.Filters
|
||||||
context.Result = new JsonResult(response);
|
context.Result = new JsonResult(response);
|
||||||
}
|
}
|
||||||
// 处理其他类型的结果(如ActionResult<string>)
|
// 处理其他类型的结果(如ActionResult<string>)
|
||||||
else if (context.Result is ObjectResult objResult && objResult.Value is string stringValue)
|
else if (context.Result is ObjectResult objResult)
|
||||||
{
|
{
|
||||||
var response = new BaseResponse<string>
|
// 处理基础类型
|
||||||
|
if (objResult.Value is string stringValue)
|
||||||
{
|
{
|
||||||
Status = 1,
|
var response = new BaseResponse<string>
|
||||||
Msg = customMessage,
|
{
|
||||||
Data = stringValue
|
Status = 1,
|
||||||
};
|
Msg = customMessage,
|
||||||
context.Result = new JsonResult(response);
|
Data = stringValue
|
||||||
|
};
|
||||||
|
context.Result = new JsonResult(response);
|
||||||
|
}
|
||||||
|
else if (objResult.Value is int intValue)
|
||||||
|
{
|
||||||
|
var response = new BaseResponse<int>
|
||||||
|
{
|
||||||
|
Status = 1,
|
||||||
|
Msg = customMessage,
|
||||||
|
Data = intValue
|
||||||
|
};
|
||||||
|
context.Result = new JsonResult(response);
|
||||||
|
}
|
||||||
|
else if (objResult.Value is bool boolValue)
|
||||||
|
{
|
||||||
|
var response = new BaseResponse<bool>
|
||||||
|
{
|
||||||
|
Status = 1,
|
||||||
|
Msg = customMessage,
|
||||||
|
Data = boolValue
|
||||||
|
};
|
||||||
|
context.Result = new JsonResult(response);
|
||||||
|
}
|
||||||
|
else if (objResult.Value is long longValue)
|
||||||
|
{
|
||||||
|
var response = new BaseResponse<long>
|
||||||
|
{
|
||||||
|
Status = 1,
|
||||||
|
Msg = customMessage,
|
||||||
|
Data = longValue
|
||||||
|
};
|
||||||
|
context.Result = new JsonResult(response);
|
||||||
|
}
|
||||||
|
else if (objResult.Value is double doubleValue)
|
||||||
|
{
|
||||||
|
var response = new BaseResponse<double>
|
||||||
|
{
|
||||||
|
Status = 1,
|
||||||
|
Msg = customMessage,
|
||||||
|
Data = doubleValue
|
||||||
|
};
|
||||||
|
context.Result = new JsonResult(response);
|
||||||
|
}
|
||||||
|
else if (objResult.Value is decimal decimalValue)
|
||||||
|
{
|
||||||
|
var response = new BaseResponse<decimal>
|
||||||
|
{
|
||||||
|
Status = 1,
|
||||||
|
Msg = customMessage,
|
||||||
|
Data = decimalValue
|
||||||
|
};
|
||||||
|
context.Result = new JsonResult(response);
|
||||||
|
}
|
||||||
|
else if (objResult.Value is DateTime dateTimeValue)
|
||||||
|
{
|
||||||
|
var response = new BaseResponse<DateTime>
|
||||||
|
{
|
||||||
|
Status = 1,
|
||||||
|
Msg = customMessage,
|
||||||
|
Data = dateTimeValue
|
||||||
|
};
|
||||||
|
context.Result = new JsonResult(response);
|
||||||
|
}
|
||||||
|
else if (objResult.Value is Guid guidValue)
|
||||||
|
{
|
||||||
|
var response = new BaseResponse<Guid>
|
||||||
|
{
|
||||||
|
Status = 1,
|
||||||
|
Msg = customMessage,
|
||||||
|
Data = guidValue
|
||||||
|
};
|
||||||
|
context.Result = new JsonResult(response);
|
||||||
|
}
|
||||||
|
// 如果不是基础类型,则直接使用object类型的BaseResponse
|
||||||
|
else if (objResult.Value != null)
|
||||||
|
{
|
||||||
|
// 处理其他非基础类型
|
||||||
|
var response = new BaseResponse<object>
|
||||||
|
{
|
||||||
|
Status = 1,
|
||||||
|
Msg = customMessage,
|
||||||
|
Data = objResult.Value
|
||||||
|
};
|
||||||
|
context.Result = new JsonResult(response);
|
||||||
|
}
|
||||||
|
// 处理null值
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var response = new BaseResponse<object>
|
||||||
|
{
|
||||||
|
Status = 1,
|
||||||
|
Msg = customMessage,
|
||||||
|
Data = null
|
||||||
|
};
|
||||||
|
context.Result = new JsonResult(response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,8 +86,8 @@ builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
builder.Services.AddScoped<ChouBoxCodeBase>();
|
builder.Services.AddScoped<ChouBoxCodeBase>();
|
||||||
#region 添加跨域
|
#region 添加跨域
|
||||||
var _myAllowSpecificOrigins = "_myAllowSpecificOrigins";
|
//var _myAllowSpecificOrigins = "_myAllowSpecificOrigins";
|
||||||
builder.Services.AddCustomCors(_myAllowSpecificOrigins);
|
//builder.Services.AddCustomCors(_myAllowSpecificOrigins);
|
||||||
#endregion
|
#endregion
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|
@ -102,7 +102,7 @@ app.UseHttpsRedirection();
|
||||||
|
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
//使用跨域
|
//使用跨域
|
||||||
app.UseCors(_myAllowSpecificOrigins);
|
//app.UseCors(_myAllowSpecificOrigins);
|
||||||
|
|
||||||
//执行扩展中间件
|
//执行扩展中间件
|
||||||
app.UseMiddlewareAll();
|
app.UseMiddlewareAll();
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,12 @@
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
|
}, //服务器配置
|
||||||
|
"Kestrel": {
|
||||||
|
"Endpoints": {
|
||||||
|
"Http": {
|
||||||
|
"Url": "http://*:81"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
60
Utile/HuanMeng.DotNetCore/Base/CustomException.cs
Normal file
60
Utile/HuanMeng.DotNetCore/Base/CustomException.cs
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace HuanMeng.DotNetCore.Base
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义异常类,用于特定业务场景
|
||||||
|
/// 该异常被捕获时不会记录日志,适用于普通业务逻辑错误
|
||||||
|
/// </summary>
|
||||||
|
public class CustomException : Exception
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 错误参数名
|
||||||
|
/// </summary>
|
||||||
|
public string ParamName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建自定义异常
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">错误消息</param>
|
||||||
|
public CustomException(string message) : base(message)
|
||||||
|
{
|
||||||
|
ParamName = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建自定义异常
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">错误消息</param>
|
||||||
|
/// <param name="paramName">参数名</param>
|
||||||
|
public CustomException(string message, string paramName) : base(message)
|
||||||
|
{
|
||||||
|
ParamName = paramName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建自定义异常
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">错误消息</param>
|
||||||
|
/// <param name="innerException">内部异常</param>
|
||||||
|
public CustomException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
ParamName = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建自定义异常
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">错误消息</param>
|
||||||
|
/// <param name="paramName">参数名</param>
|
||||||
|
/// <param name="innerException">内部异常</param>
|
||||||
|
public CustomException(string message, string paramName, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
ParamName = paramName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,12 @@ public enum ResponseCode
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 成功
|
/// 成功
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Success = 0,
|
Success = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通用
|
||||||
|
/// </summary>
|
||||||
|
Common = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 正在处理中
|
/// 正在处理中
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ namespace HuanMeng.DotNetCore.MiddlewareExtend
|
||||||
|
|
||||||
|
|
||||||
context.Response.StatusCode = StatusCodes.Status200OK;
|
context.Response.StatusCode = StatusCodes.Status200OK;
|
||||||
BaseResponse<object> baseResponse = new BaseResponse<object>(ResponseCode.ParamError, ex.ParamName ?? "参数错误", null)
|
BaseResponse<object> baseResponse = new BaseResponse<object>(ResponseCode.Common, ex.ParamName ?? "参数错误", null)
|
||||||
{
|
{
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
@ -44,6 +44,15 @@ namespace HuanMeng.DotNetCore.MiddlewareExtend
|
||||||
// 将异常信息写入 HTTP 响应
|
// 将异常信息写入 HTTP 响应
|
||||||
await context.Response.WriteAsync(baseResponse.ToString());
|
await context.Response.WriteAsync(baseResponse.ToString());
|
||||||
}
|
}
|
||||||
|
catch (CustomException ex)
|
||||||
|
{
|
||||||
|
// 对自定义异常的特殊处理,不记录日志
|
||||||
|
context.Response.StatusCode = StatusCodes.Status200OK;
|
||||||
|
BaseResponse<object> baseResponse = new BaseResponse<object>(ResponseCode.Common, ex.ParamName ?? "", null);
|
||||||
|
context.Response.ContentType = "application/json; charset=utf-8";
|
||||||
|
// 将异常信息写入 HTTP 响应
|
||||||
|
await context.Response.WriteAsync(baseResponse.ToString());
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user