feat(init): init

This commit is contained in:
Bunny 2024-08-08 22:23:36 +08:00
commit 42a4e76d33
82 changed files with 11028 additions and 0 deletions

30
.dockerignore Normal file
View File

@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
# Build and Release Folders
bin-debug/
bin-release/
[Oo]bj/
[Bb]in/
# Other files and folders
.settings/
# Executables
*.swf
*.air
*.ipa
*.apk
*.log
# .vs
# .idea
.DS_Store
**/bin
**/obj

View File

@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/modules.xml
/.idea.Bunny.WebApi.iml
/contentModel.xml
/projectSettingsUpdater.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1 @@
Bunny.WebApi

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -0,0 +1,87 @@
using Microsoft.Extensions.Configuration;
namespace Bunny.Common;
public class AppSettings
{
public AppSettings(IConfiguration configuration)
{
Configuration = configuration;
}
private static IConfiguration Configuration { get; set; } = null!;
/// <summary>
/// 封装要操作的字符
/// </summary>
/// <param name="sections">节点配置</param>
public static string? App(params string[] sections)
{
try
{
if (sections.Any()) return Configuration[string.Join(":", sections)];
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message);
}
return "";
}
/// <summary>
/// 递归获取配置信息数组
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sections"></param>
/// <returns></returns>
public static List<T> App<T>(params string[] sections)
{
List<T> list = [];
try
{
if (sections.Any()) Configuration.Bind(string.Join(":", sections), list);
}
catch
{
return list;
}
return list;
}
public static T Bind<T>(string key, T t)
{
Configuration.Bind(key, t);
return t;
}
public static T GetAppConfig<T>(string key, T? defaultValue = default)
{
var setting = (T)Convert.ChangeType(Configuration[key], typeof(T));
var value = setting;
return value;
}
/// <summary>
/// 获取配置文件
/// </summary>
/// <param name="key">eg: WeChat:Token</param>
/// <returns></returns>
public static string GetConfig(string key)
{
return Configuration[key];
}
/// <summary>
/// 获取配置节点并转换成指定类型
/// </summary>
/// <typeparam name="T">节点类型</typeparam>
/// <param name="key">节点路径</param>
/// <returns>节点类型实例</returns>
public static T Get<T>(string key)
{
return Configuration.GetSection(key).Get<T>();
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Bunny.Dao\Bunny.Dao.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,28 @@
using System.Net;
namespace Bunny.Common.Exception;
public class BunnyException : System.Exception
{
public HttpStatusCode Code;
public new string Message;
public BunnyException(string message)
{
Message = message;
Code = HttpStatusCode.InternalServerError;
}
public BunnyException(HttpStatusCode code, string message)
{
Message = message;
Code = code;
}
public BunnyException(string message, System.Exception innerException, HttpStatusCode code) : base(message,
innerException)
{
Message = message;
Code = code;
}
}

View File

@ -0,0 +1,36 @@
using Bunny.Common.Exception;
using Bunny.Dao.Entity.Constant;
using Bunny.Dao.Result;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
namespace Bunny.Common.Filter;
public class GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger) : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
// 错误的消息
var message = context.Exception.Message;
// 异常返回结果包装 也可以自定自定义返回类型
var result = Result<string>.Error(ExceptionConstant.ServerError);
// 如果是自己自定义的异常
if (context.Exception is BunnyException)
{
var contextException = (BunnyException)context.Exception;
message = contextException.Message;
result = Result<string>.Error(contextException.Code, message);
}
// 日志记录
logger.LogError(context.Exception, message);
// 重新复写返回结果
context.Result = new ObjectResult(result);
context.ExceptionHandled = true;
}
}

View File

@ -0,0 +1,69 @@
using System.Net;
using System.Net.Mail;
using System.Net.Mime;
using System.Text;
using Bunny.Dao.Entity.Email;
namespace Bunny.Common.Utils;
public class EmailUtil
{
/// <summary>
/// 发送邮件建议使用QQ端口587,465发送不成功
/// </summary>
public static void SendEmail(EmailSendEntity entity)
{
// 创建邮件发送对象
var smtpClient = new SmtpClient();
// 设置发送者邮件和接受者邮件
var sendMailAddress = new MailAddress(entity.SendEmail);
var receiverMailAddress = new MailAddress(entity.ReceiverEmail);
// 创建邮件发送消息
var mailMessage = new MailMessage(sendMailAddress, receiverMailAddress)
{
Subject = entity.Subject,
SubjectEncoding = Encoding.UTF8,
Body = entity.Body,
BodyEncoding = Encoding.UTF8,
IsBodyHtml = entity.IsHtml
};
// 判断是否有抄送
if (entity.CC != null)
{
var ccArray = entity.CC.Split(',');
// 是抄送
if (entity.IsBbc == false)
foreach (var ccAddress in ccArray)
mailMessage.CC.Add(new MailAddress(ccAddress));
// 是密送
else
foreach (var ccAddress in ccArray)
mailMessage.Bcc.Add(new MailAddress(ccAddress));
}
// 如果设置了富文本
if (entity.IsHtml)
{
// 设置HTML文本
var htmlView = AlternateView
.CreateAlternateViewFromString(entity.Body, Encoding.UTF8, MediaTypeNames.Text.Html);
// 添加富文本视图到邮件消息
mailMessage.AlternateViews.Add(htmlView);
}
// 创建SMTP客户端并发送邮件
smtpClient.Credentials = new NetworkCredential(entity.SendEmail, entity.SendEmailPassword);
smtpClient.EnableSsl = true;
smtpClient.Port = entity.SmtpPort;
smtpClient.Host = entity.SmtpService;
// 发送邮件
smtpClient.Send(mailMessage);
}
}

View File

@ -0,0 +1,18 @@
using System.Net;
using Bunny.Common.Exception;
namespace Bunny.Common.Utils;
public class EmptyUtil
{
/// <summary>
/// 判断对象或者字符串是否为空
/// </summary>
/// <param name="obj">传入的对象</param>
/// <param name="code">出错的状态码</param>
/// <param name="message">出错的消息</param>
public static void CheckIfNullOrEmpty(object? obj, HttpStatusCode code, string message)
{
if (obj == null || (obj is string s && string.IsNullOrEmpty(s))) throw new BunnyException(code, message);
}
}

View File

@ -0,0 +1,82 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
namespace Bunny.Common.Utils.Jwt;
public static partial class JwtUtil
{
/// <summary>
/// 生成token
/// </summary>
/// <param name="data">实体类数据</param>
/// <param name="day">多少天之后过期</param>
/// <returns>生成好的token</returns>
public static string GenerateJwt<T>(T data, int day = Day)
{
if (data == null) return "";
// 初始化字典
var propertyInfos = data.GetType().GetProperties();
var claims = (from propertyInfo in propertyInfos
let name = propertyInfo.Name
let value = propertyInfo.GetValue(data)?.ToString()
select value.IsNullOrEmpty() ? new Claim(name, "") : new Claim(name, value!)).ToList();
// 生成token
var token = new JwtSecurityToken(expires: DateTime.UtcNow.AddDays(day), claims: claims);
return new JwtSecurityTokenHandler().WriteToken(token);
}
/// <summary>
/// 生成token
/// </summary>
/// <param name="claims">负载,放入你的内容</param>
/// <param name="day">多少天之后过期</param>
/// <returns>生成好的token</returns>
public static string GenerateJwt(IEnumerable<Claim>? claims = default, int day = Day)
{
var token = new JwtSecurityToken(expires: DateTime.UtcNow.AddDays(day), claims: claims);
return new JwtSecurityTokenHandler().WriteToken(token);
}
/// <summary>
/// 生成token
/// </summary>
/// <param name="jwtKey">生成JWT的key</param>
/// <param name="claims">负载,放入你的内容</param>
/// <param name="day">多少天之后过期</param>
/// <returns>生成好的token</returns>
public static string GenerateJwt(string jwtKey = JwtKey, IEnumerable<Claim>? claims = default, int day = Day)
{
var token = new JwtSecurityToken(
expires: DateTime.UtcNow.AddDays(day),
claims: claims,
signingCredentials: Credentials(jwtKey));
return new JwtSecurityTokenHandler().WriteToken(token);
}
/// <summary>
/// 生成token
/// </summary>
/// <param name="issuer">授权</param>
/// <param name="audience">认证</param>
/// <param name="jwtKey">生成JWT的key</param>
/// <param name="claims">负载,放入你的内容</param>
/// <param name="day">多少天之后过期</param>
/// <returns>生成好的token</returns>
public static string GenerateJwt(string issuer, string audience, string jwtKey = JwtKey,
IEnumerable<Claim>? claims = default, int day = Day)
{
var token = new JwtSecurityToken(
issuer,
audience,
expires: DateTime.UtcNow.AddDays(day),
claims: claims,
signingCredentials: Credentials(jwtKey));
return new JwtSecurityTokenHandler().WriteToken(token);
}
}

View File

@ -0,0 +1,188 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Bunny.Common.Exception;
using Bunny.Dao.Entity.Constant;
using Newtonsoft.Json;
namespace Bunny.Common.Utils.Jwt;
public static partial class JwtUtil
{
/// <summary>
/// 返回所有的 jwt
/// </summary>
/// <param name="token">token解析</param>
public static JwtSecurityToken JwtParse(string token)
{
if (token == null) return null;
var tokenHandler = new JwtSecurityTokenHandler();
return tokenHandler.ReadJwtToken(token);
}
/// <summary>
/// 返回发行者
/// </summary>
/// <param name="jwt">token解析</param>
public static string JwtParseIssuer(string jwt)
{
return JwtParse(jwt).Issuer;
}
/// <summary>
/// 返回授权者
/// </summary>
/// <param name="jwt">token解析</param>
public static IEnumerable<string> JwtParseAudiences(string jwt)
{
return JwtParse(jwt).Audiences;
}
/// <summary>
/// 返回负载中内容
/// </summary>
/// <param name="jwt">jwt 解析</param>
public static IEnumerable<Claim> JwtParseClaim(string jwt)
{
return JwtParse(jwt).Claims;
}
/// <summary>
/// 将生成的JWT值转成字典格式
/// </summary>
/// <param name="jwt"></param>
/// <returns>Dictionary</returns>
public static Dictionary<string, object> ParseJwtToDictionary(string jwt)
{
var tokenHandler = new JwtSecurityTokenHandler();
var jwtSecurityToken = tokenHandler.ReadJwtToken(jwt);
// 初始化字典
var dictionary = new Dictionary<string, object>();
var jClaims = jwtSecurityToken.Claims;
// 将得到的每一个值都放入字典中
foreach (var claim in jClaims) dictionary[claim.Type] = claim.Value;
// 返回字典
return dictionary;
}
/// <summary>
/// 将生成的 JwtSecurityToken 值转成字典格式
/// </summary>
/// <param name="jwtToken"></param>
/// <returns>Dictionary</returns>
public static Dictionary<string, object> ParseJwtToDictionary(JwtSecurityToken jwtToken)
{
// 初始化字典
var dictionary = new Dictionary<string, object>();
// 将得到的每一个值都放入字典中
foreach (var claim in jwtToken.Claims) dictionary[claim.Type] = claim.Value;
// 返回字典
return dictionary;
}
/// <summary>
/// 将 jwt 转成 实体类
/// </summary>
/// <param name="jwt">jwt</param>
/// <returns>实体类</returns>
public static T? ParseJwtToObject<T>(string jwt)
{
if (jwt == null) throw new BunnyException(ExceptionConstant.TokenIsEmpty);
var dictionary = new Dictionary<string, object>();
// 将 jwt 转成 Claim
var claims = JwtParseClaim(jwt);
// 找到对应的字典 Dictionary
foreach (var claim in claims) dictionary[claim.Type] = claim.Value;
// 返回实体类
var json = JsonConvert.SerializeObject(dictionary);
// 将JSON转为对象
return JsonConvert.DeserializeObject<T>(json);
}
/// <summary>
/// 将 JSON 转成 Dictionary
/// </summary>
/// <param name="jsonString"></param>
/// <returns>Dictionary</returns>
public static Dictionary<string, object> ParseJsonToDictionary(string jsonString)
{
// 初始化字典
var dictionary = new Dictionary<string, object>();
// 将JSON转成字典格式
var claims = ParseJsonToClaimEnumerable(jsonString);
// 将得到的每一个值都放入字典中
foreach (var claim in claims) dictionary[claim.Type] = claim.Value;
// 返回 Dictionary
return dictionary;
}
/// <summary>
/// 将 JSON 转成 Claim
/// </summary>
/// <param name="jsonString"></param>
/// <returns>Claim</returns>
public static IEnumerable<Claim> ParseJsonToClaimEnumerable(string jsonString)
{
// 将JSON转成字典格式
var deserializeWithDictionary = JsonConvert.DeserializeObject<IDictionary<string, object>>(jsonString);
// 返回Claim
return deserializeWithDictionary!.Select(item =>
{
if (item.Value == null)
return new Claim(item.Key, string.Empty);
return new Claim(item.Key, item.Value.ToString()!);
})
.ToList();
}
/// <summary>
/// 将 JSON 转成 实体类
/// </summary>
/// <param name="jsonString">JSON字符串</param>
/// <returns>实体类</returns>
public static T? ParseJsonToObject<T>(string jsonString)
{
// 将JSON转成字典格式
var claims = ParseJsonToClaimEnumerable(jsonString);
// 初始化字典
var dictionary = new Dictionary<string, object>();
// 将得到的每一个值都放入字典中
foreach (var claim in claims) dictionary[claim.Type] = claim.Value;
// 返回实体类
var json = JsonConvert.SerializeObject(dictionary);
// 将JSON转为对象
return JsonConvert.DeserializeObject<T>(json);
}
/// <summary>
/// 将 Claim 抓成 Dictionary
/// </summary>
/// <param name="claims"></param>
/// <returns>Dictionary</returns>
public static Dictionary<string, object> ParseClaimToDictionary(IEnumerable<Claim> claims)
{
// 初始化字典
var dictionary = new Dictionary<string, object>();
// 将得到的每一个值都放入字典中
foreach (var claim in claims) dictionary[claim.Type] = claim.Value;
// 返回字典格式
return dictionary;
}
}

View File

@ -0,0 +1,88 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
namespace Bunny.Common.Utils.Jwt;
public static partial class JwtUtil
{
/// <summary>
/// 默认发布者
/// </summary>
private const string JwtKey = "这你放的是你的秘钥必须要256位以上否则会报错";
/// <summary>
/// 默认发布者
/// </summary>
private const string Issuer = "Bunny-Issuer";
/// <summary>
/// 默认接受者
/// </summary>
private const string Audience = "Bunny-Audience";
/// <summary>
/// 默认有效时间,按天计算
/// </summary>
private const int Day = 7;
/// <summary>
/// 判断是否过期
/// </summary>
/// <param name="jwtToken">token</param>
/// <returns>过期true未过期false</returns>
public static bool IsJwtExpired(string? jwtToken)
{
var tokenHandler = new JwtSecurityTokenHandler();
// Token无效直接返回过期
if (tokenHandler.ReadToken(jwtToken) is not JwtSecurityToken jwtTokenObj) return true;
// 判断是否过期
return DateTime.UtcNow > jwtTokenObj.ValidTo;
}
/// <summary>
/// 生成 SigningCredentials
/// </summary>
private static SigningCredentials Credentials(string jwtKey)
{
var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey));
return new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256Signature);
}
/// <summary>
/// 将字典转为实体类
/// </summary>
/// <param name="dictionary">字典</param>
/// <returns>所对应的实体类</returns>
// public static T? ConvertDictionaryToObject(Dictionary<string, object> dictionary)
// {
// var json = JsonConvert.SerializeObject(dictionary);
// // 将JSON转为对象
// return JsonConvert.DeserializeObject<T>(json);
// }
public static T? ConvertDictionaryToObject<T>(Dictionary<string, object> dictionary)
{
var json = JsonConvert.SerializeObject(dictionary);
// 将JSON转为对象
return JsonConvert.DeserializeObject<T>(json);
}
/// <summary>
/// 将对象转为Claim
/// </summary>
/// <param name="object">要转换的对象</param>
/// <returns>Claim</returns>
public static IEnumerable<Claim> CreateClaim<T>(T @object)
{
// 将对象序列化成JSON
var json = JsonConvert.SerializeObject(@object);
// 将JSON复制给Claim
return ParseJsonToClaimEnumerable(json);
}
}

View File

@ -0,0 +1,33 @@
using System.Security.Cryptography;
using System.Text;
namespace Bunny.Common.Utils.Net;
public static partial class NetUtil
{
/// <summary>
/// MD5 加密32位-大写
/// </summary>
/// <param name="password">密码</param>
/// <returns>加密后结果</returns>
public static string Encryption32(string password)
{
var md5 = new MD5CryptoServiceProvider();
var bytes = Encoding.UTF8.GetBytes(password);
var result = BitConverter.ToString(md5.ComputeHash(bytes));
return result.Replace("-", "");
}
/// <summary>
/// md5 加密32位-小写
/// </summary>
/// <param name="password">密码</param>
/// <returns>加密后结果</returns>
public static string Encryption32Lower(string password)
{
var md5 = new MD5CryptoServiceProvider();
var bytes = Encoding.UTF8.GetBytes(password);
var result = BitConverter.ToString(md5.ComputeHash(bytes));
return result.Replace("-", "").ToLower();
}
}

View File

@ -0,0 +1,48 @@
using Microsoft.AspNetCore.Http;
namespace Bunny.Common.Utils.Net;
public static partial class NetUtil
{
/// <summary>
/// 仿写java对象拷贝
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
public static void CopyProperties<T1, T2>(T1 source, T2 target)
{
var sourceProperties = source!.GetType().GetProperties();
var targetProperties = target!.GetType().GetProperties();
foreach (var sourceProperty in sourceProperties)
{
var targetProperty = targetProperties.FirstOrDefault(x =>
x.Name == sourceProperty.Name && x.PropertyType == sourceProperty.PropertyType);
if (targetProperty == null || !targetProperty.CanWrite) continue;
var value = sourceProperty.GetValue(source);
targetProperty.SetValue(target, value);
}
}
/// <summary>
/// 获取token
/// </summary>
/// <param name="request">请求</param>
/// <returns>token值</returns>
public static string? GetToken(HttpRequest request)
{
return request.Headers["token"];
}
/// <summary>
/// 获取请求路径
/// </summary>
/// <param name="request">请求</param>
public static string? GetRequestPath(HttpRequest request)
{
return request.Path.Value;
}
}

View File

@ -0,0 +1,44 @@
using System.Text.RegularExpressions;
using Microsoft.IdentityModel.Tokens;
namespace Bunny.Common.Utils.Net;
public static partial class NetUtil
{
public static List<string> Ignores = ["/api/Login"];
/// <summary>
/// 当前路径是否匹配ant path路径
/// </summary>
/// <param name="pattern">ant path值</param>
/// <param name="path">当前路径</param>
/// <returns>是否匹配</returns>
public static bool AntPath(string pattern, string? path = "")
{
// 全部转成小写
path = path!.ToLower();
pattern = pattern!.ToLower();
// Ant Path匹配规则的正则表达式
var antPattern = "^" + Regex.Escape(pattern)
.Replace("\\?", ".")
.Replace("\\*", ".*")
.Replace("\\**", ".*") + "$";
// 判断匹配规则是否成立
return path.IsNullOrEmpty() ? Regex.IsMatch("", antPattern) : Regex.IsMatch(path!, antPattern);
}
/// <summary>
/// 是否匹配当前路径,同时判断是否在忽略列表中
/// 如果在忽略列表中,则不进行判断
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static bool MatchPath(string? path)
{
// 如果当前路径是在忽略列表中,返回未匹配
return !Ignores.Select(ignore => AntPath(ignore, path)).Any(isMatch => isMatch);
}
}

View File

@ -0,0 +1,32 @@
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.IdentityModel.Tokens;
namespace Bunny.Common.Utils.Net;
public static partial class NetUtil
{
/// <summary>
/// 获取请求的IP地址
/// </summary>
/// <param name="context">HttpContext 上下问对象</param>
/// <param name="getIpv4AddressType">是否获取IPv4地址默认为IPv4</param>
/// <returns>IP地址</returns>
public static string GetRequestIpAddress(HttpContext context, bool getIpv4AddressType = true)
{
// 获取IP地址
var ipAddress = context.Connection.RemoteIpAddress!.ToString();
// 转换IP地址格式
var ip = IPAddress.Parse(ipAddress);
// 转换成IPv4
var toIPv4 = ip.MapToIPv4().ToString();
// 转换成IPv6
var toIPv6 = ip.MapToIPv6().ToString();
// 如果获取IPv4地址
ipAddress = getIpv4AddressType ? toIPv4 : toIPv6;
// 如果有IP直接返回
return ipAddress.IsNullOrEmpty() ? "" : ipAddress;
}
}

View File

@ -0,0 +1,52 @@
using System.Net;
using Bunny.Dao.Result;
using Microsoft.AspNetCore.Mvc;
namespace Bunny.Common.Utils.Net;
public static partial class NetUtil
{
/// <summary>
/// 自定义错误消息和内容
/// </summary>
/// <param name="code"></param>
/// <param name="message"></param>
/// <returns></returns>
public static JsonResult Out(int code, string message)
{
// 生成处理结果
var result = Result<string>.Error(code, message);
// 返回处理结果
return new JsonResult(result);
}
/// <summary>
/// 自定义错误消息和内容
/// </summary>
/// <param name="code"></param>
/// <param name="message"></param>
/// <returns></returns>
public static JsonResult Out(HttpStatusCode code, string message)
{
// 生成处理结果
var result = Result<string>.Error(code, message);
// 返回处理结果
return new JsonResult(result);
}
/// <summary>
/// 默认传入code是500
/// </summary>
/// <param name="message">自定义错误消息</param>
/// <returns></returns>
public static JsonResult Out(string message)
{
// 生成处理结果
var result = Result<string>.Error(HttpStatusCode.InternalServerError, message);
// 返回处理结果
return new JsonResult(result);
}
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,24 @@
namespace Bunny.Dao.Dto.Email;
public class EmailTemplateDto
{
/// <summary>
/// 模板名称
/// </summary>
public string? TemplateName { get; set; }
/// <summary>
/// 主题
/// </summary>
public string? Subject { get; set; }
/// <summary>
/// 邮件内容
/// </summary>
public string? Body { get; set; }
/// <summary>
/// 邮件类型
/// </summary>
public string? Type { get; set; }
}

View File

@ -0,0 +1,24 @@
namespace Bunny.Dao.Dto.Email;
public class EmailUsersDto
{
/// <summary>
/// 模板名称
/// </summary>
public string? TemplateName { get; set; }
/// <summary>
/// 主题
/// </summary>
public string? Subject { get; set; }
/// <summary>
/// 邮件内容
/// </summary>
public string? Body { get; set; }
/// <summary>
/// 邮件类型
/// </summary>
public string? Type { get; set; }
}

View File

@ -0,0 +1,8 @@
namespace Bunny.Dao.Dto.User;
public class LoginDto
{
public string? Username { get; set; }
public string? Password { get; set; }
public string? EmailCode { get; set; }
}

View File

@ -0,0 +1,14 @@
namespace Bunny.Dao.Entity.Base;
public class BaseEntity
{
public string? Id { get; set; }
public DateTime CreateTime { get; set; }
}
// public DateTime UpdateTime { get; set; }
// public long CreateUserId { get; set; }
// public long UpdateUserId { get; set; }
// public string? OperationMessage { get; set; }
// public bool IsDelete { get; set; }

View File

@ -0,0 +1,61 @@
namespace Bunny.Dao.Entity.Constant;
/// <summary>
/// 错误常量
/// </summary>
public class ExceptionConstant
{
public const string FileSystemException = "文件系统错误";
public const string UnknownException = "未知错误";
public const string TokenIsEmpty = "token为空";
public const string DataIsEmpty = "数据为空";
public const string RequestDataNotEmptyException = "请求参数为空";
public const string UpdateDtoIsNullException = "修改参数为空";
public const string AddDataIsEmptyException = "添加数据为空";
public const string DeleteIdIsNotEmptyException = "删除id不能为空";
public const string ServerError = "服务器错误";
// 文章操作相关
public const string DoLikeCommentNotExist = "点赞内容不存在";
public const string ReplyUserEmptyException = "回复的用户不存在";
public const string ReplyUserIdEmptyException = "回复的用户不能为空";
public const string MenuIsNotExistException = "菜单不存在";
public const string PostCommentEmptyException = "评论内容不能为空";
public const string ArticleIdNotEmptyException = "文章id不能为空";
public const string UpdateIdIsNotEmptyException = "修改id不能为空";
public const string CannotTopOtherUser = "不能操作此内容";
public const string ArticleNotFoundException = "文章未找到";
// 登录相关
public const string UserTokenOutOfDateException = "用户登录过期";
public const string LoginDtoIsEmptyException = "登录参数不能为空";
public const string LoginFailedException = "登录失败";
// 账号相关
public const string AccountNotFoundException = "账号不存在";
public const string AccountLockedException = "账号被锁定";
// 用户相关
public const string UserNotLoginException = "用户未登录";
public const string UsernameIsEmptyException = "用户名不能为空";
public const string AlreadyUserException = "用户已存在";
public const string UserNotFoundException = "用户不存在";
// 密码相关
public const string PasswordException = "密码错误";
public const string AccountException = "账号或密码错误";
public const string PasswordNotEmptyException = "密码不能为空";
public const string OldPasswordException = "旧密码不匹配";
public const string PasswordEditException = "密码修改失败";
public const string OldPasswordSameNewPasswordException = "旧密码与新密码相同";
// 验证码错误
public const string PleaseSendEmailCodeException = "请先发送验证码";
public const string MessageCodeNotPassException = "短信验证码未过期";
public const string MessageCodeUnauthorizedException = "短信验证码未授权,请联系管理员";
public const string VerificationCodeErrorException = "验证码错误";
public const string CaptchaIsEmptyException = "验证码不能为空";
public const string KeyIsEmptyException = "验证码key不能为空";
public const string VerificationCodeDoesNotMatchException = "验证码不匹配";
public const string VerificationCodeIsEmptyException = "验证码失效或不存在";
}

View File

@ -0,0 +1,22 @@
namespace Bunny.Dao.Entity.Constant;
/// <summary>
/// 文件消息相关常量
/// </summary>
public class FileMessageConstant
{
public static readonly string DownloadBucketException = "下载文件失败";
public static readonly string FileUploadException = "文件上传失败";
public static readonly string BucketExistsException = "查询文化部对象失败";
public static readonly string DeleteBucketException = "删除文件对象失败";
public static readonly string FileIsEmpty = "文件信息为空";
public static readonly string FileIsNotExits = "文件信息为空";
public static readonly string GetBucketException = "获取文件信息失败";
public static readonly string QueryBucketException = "查询文件信息失败";
public static readonly string CreateBucketException = "创建文件对象失败";
public static readonly string UpdateBucketException = "更新文件对象失败";
public static readonly string ComposeObjectException = "对象错误";
public static readonly string CopyBucketException = "复制文件内容失败";
public static readonly string DisableBucketException = "禁用文件失败";
public static readonly string EnableBucketException = "启用文件失败";
}

View File

@ -0,0 +1,12 @@
namespace Bunny.Dao.Entity.Constant;
/// <summary>
/// 事件相关常量
/// </summary>
public class LocalDateTimeConstant
{
public static readonly string DefaultDateFormat = "yyyy-MM-dd";
public static readonly string DefaultDateTimeFormat = "yyyy-MM-dd HH:mm";
public static readonly string DefaultDateTimeSecondFormat = "yyyy-MM-dd HH:mm:ss";
public static readonly string DefaultTimeFormat = "HH:mm:ss";
}

View File

@ -0,0 +1,13 @@
namespace Bunny.Dao.Entity.Constant;
/// <summary>
/// 邮箱消息先关常量
/// </summary>
public class MailMessageConstant
{
public static readonly string EmptySendObject = "空发送对象";
public static readonly string AddressNotNull = "收件人不能为空";
public static readonly string TitleNotNull = "标题不能为空";
public static readonly string SendMessageNotNull = "发送消息不能为空";
public static readonly string EmailConfigNotFound = "邮箱配置为空";
}

View File

@ -0,0 +1,38 @@
namespace Bunny.Dao.Entity.Constant;
/// <summary>
/// Redis相关常量
/// </summary>
public class RedisUserConstant
{
private const string AdminLoginInfoPrefix = "ADMIN::LOGIN_INFO::";
private const string AdminEmailCodePrefix = "ADMIN::EMAIL_CODE::";
private const string UserLoginInfoPrefix = "USER::LOGIN_INFO::";
private const string UserEmailCodePrefix = "USER::EMAIL_CODE::";
private const string UserDoLikePrefix = "USER::doLike::";
public static string GetAdminLoginInfoPrefix(string adminUser)
{
return AdminLoginInfoPrefix + adminUser;
}
public static string GetAdminUserEmailCodePrefix(string adminUser)
{
return AdminEmailCodePrefix + adminUser;
}
public static string GetUserLoginInfoPrefix(string user)
{
return UserLoginInfoPrefix + user;
}
public static string GetUserEmailCodePrefix(string user)
{
return UserEmailCodePrefix + user;
}
public static string GetUserDoLikePrefix(string user)
{
return UserDoLikePrefix + user;
}
}

View File

@ -0,0 +1,9 @@
namespace Bunny.Dao.Entity.Constant;
/// <summary>
/// 鉴权相关常量
/// </summary>
public class SecurityConstant
{
public static string User = "";
}

View File

@ -0,0 +1,12 @@
namespace Bunny.Dao.Entity.Constant;
/// <summary>
/// 数据库相关常量
/// </summary>
public class SqlConstant
{
public static readonly string SetCreateTime = "setCreateTime";
public static readonly string SetUpdateTime = "setUpdateTime";
public static readonly string SetCreateUser = "setCreateUser";
public static readonly string SetUpdateUser = "setUpdateUser";
}

View File

@ -0,0 +1,10 @@
namespace Bunny.Dao.Entity.Constant;
/// <summary>
/// 状态相关常量
/// </summary>
public class StatusConstant
{
public const int Enable = 1;
public const int Disable = 0;
}

View File

@ -0,0 +1,10 @@
namespace Bunny.Dao.Entity.Constant;
/// <summary>
/// 用户相关常量
/// </summary>
public class UserConstant
{
public const string UserAvatar =
"https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132";
}

View File

@ -0,0 +1,62 @@
namespace Bunny.Dao.Entity.Email;
/// <summary>
/// 发送邮件对象
/// </summary>
public abstract class EmailSendEntity
{
/// <summary>
/// SMTP 服务器
/// </summary>
public string SmtpService { get; set; }
/// <summary>
/// SMTP 服务器端口号
/// </summary>
public int SmtpPort { get; set; }
/// <summary>
/// 发送者邮件
/// </summary>
public string SendEmail { get; set; }
/// <summary>
/// 发送者密码
/// </summary>
public string SendEmailPassword { get; set; }
/// <summary>
/// 接受这邮件
/// </summary>
public string ReceiverEmail { get; set; }
/// <summary>
/// 发送邮件的主题
/// </summary>
public string Subject { get; set; }
/// <summary>
/// 发送邮件的内容
/// </summary>
public string Body { get; set; }
/// <summary>
/// 设置抄送
/// </summary>
public string? CC { get; set; }
/// <summary>
/// 是否密送
/// </summary>
public bool IsBbc { get; set; } = false;
/// <summary>
/// 是否设置HTML内容
/// </summary>
public bool IsHtml { get; set; } = false;
/// <summary>
/// 是否已SSL发送
/// </summary>
public bool IsSSl { get; set; } = true;
}

View File

@ -0,0 +1,5 @@
namespace Bunny.Dao.Entity.Enum;
public enum Enum
{
}

View File

@ -0,0 +1,19 @@

namespace Bunny.Dao.Models.Base;
public class BaseModel
{
public string? Id { get; set; }
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }
public long CreateUserId { get; set; }
public long UpdateUserId { get; set; }
public string? OperationMessage { get; set; }
public bool IsDelete { get; set; }
}

View File

@ -0,0 +1,11 @@

namespace Bunny.Dao.Models.Base;
public class BasePersonModel : BaseModel
{
public string? Name { get; set; }
public string? Type { get; set; }
public string? Description { get; set; }
}

View File

@ -0,0 +1,15 @@

namespace Bunny.Dao.Models.Base;
public class BaseStuffModel : BaseModel
{
public string? Name { get; set; }
public string? Type { get; set; }
public string? Description { get; set; }
public string? Spec { get; set; }
public bool IsUse { get; set; }
}

View File

@ -0,0 +1,12 @@
using Bunny.Dao.Models.Base;
namespace Bunny.Dao.Models.System;
public class Product : BaseStuffModel
{
public string? CustomerId { get; set; }
public string? CustomerName { get; set; }
public string? PackageType { get; set; }
}

View File

@ -0,0 +1,20 @@
using Bunny.Dao.Models.Base;
namespace Bunny.Dao.Models.System;
public class User : BaseModel
{
public string? UserName { get; set; }
public string? Password { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
public string? QrCode { get; set; }
public string? CompanyCode { get; set; }
public string? DeptCode { get; set; }
}

View File

@ -0,0 +1,5 @@
namespace Bunny.Dao.Result.Constant;
public class ErrorConstant
{
}

View File

@ -0,0 +1,5 @@
namespace Bunny.Dao.Result.Constant;
public class SuccessConstant
{
}

View File

@ -0,0 +1,50 @@
using System.ComponentModel;
namespace Bunny.Dao.Result.Enum;
/// <summary>
/// 返回结果状态码枚举
/// </summary>
public enum ResultCodeEnum
{
// 成功操作 200
[Description("操作成功")] Success = 200,
// 验证错误 201
[Description("账号或密码错误")] LoginError = 201,
// 数据相关 206
[Description("非法请求")] IllegalRequest = 206,
// 身份过期 208
[Description("请先登陆")] LoginAuth = 206,
[Description("身份验证过期")] AuthenticationExpired = 207,
[Description("会话过期")] SessionExpiration = 208,
[Description("该账户被封禁")] FailNoAccessDeniedUserLocked = 209,
// 提示错误
[Description("URL编码失败")] UrlEncodeError = 216,
[Description("非法回调请求")] IllegalCallbackRequestError = 217,
[Description("获取用户信息失败")] FetchUserinfoError = 219,
// 无权访问 403
[Description("无权访问")] FailNoAccessDenied = 403,
// 系统错误 500
[Description("请求失败")] Error = 500
}
/// <summary>
/// 自定义属性
/// </summary>
public static class ResultCodeExtensions
{
public static string GetDescription(this ResultCodeEnum eCodeEnum)
{
var fieldInfo = eCodeEnum.GetType().GetField(eCodeEnum.ToString());
if (fieldInfo == null) return string.Empty;
var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : eCodeEnum.ToString();
}
}

View File

@ -0,0 +1,29 @@
namespace Bunny.Dao.Result.Page;
public class Pagination<T>
{
/// <summary>
/// 当前页
/// </summary>
public int PageNo { get; set; }
/// <summary>
/// 页容量
/// </summary>
public int PageSize { get; set; }
/// <summary>
/// 总页数
/// </summary>
public int Pages { get; set; }
/// <summary>
/// 总条数
/// </summary>
public int Total { get; set; }
/// <summary>
/// 数据
/// </summary>
public IList<T> Rows { get; set; }
}

228
Bunny.Dao/Result/Result.cs Normal file
View File

@ -0,0 +1,228 @@
using System.Net;
using Bunny.Dao.Result.Enum;
namespace Bunny.Dao.Result;
[Serializable]
public class Result<T>(HttpStatusCode code, T? data, string? message)
{
public Result(HttpStatusCode code, string? message) : this(code, default, message)
{
}
private Result(int code, T? data, string message) : this((HttpStatusCode)code, data, message)
{
}
/// <summary>
/// 返回码
/// </summary>
public HttpStatusCode Code { get; set; } = code;
/// <summary>
/// 返回说明
/// </summary>
public string? Message { get; set; } = message;
/// <summary>
/// 返回数据体 可为空
/// </summary>
public T? Data { get; set; } = data;
// 不再继承 IActionResult
// public Task ExecuteResultAsync(ActionContext context)
// {
// return new Task<Result<T>>(() => new Result<T>(code, data, message));
// }
/// <summary>
/// 操作成功
/// </summary>
public static Result<T> Success()
{
const ResultCodeEnum codeEnum = ResultCodeEnum.Success;
return new Result<T>((HttpStatusCode)codeEnum, default, codeEnum.GetDescription());
}
/// <summary>
/// 返回成功对象消息
/// </summary>
/// <param name="data">数据内容分</param>
/// <returns>返回的数据内容</returns>
public static Result<T> Success(T data)
{
const ResultCodeEnum codeEnum = ResultCodeEnum.Success;
return new Result<T>((HttpStatusCode)codeEnum, data, codeEnum.GetDescription());
}
/// <summary>
/// 自定义返回成功消息
/// </summary>
/// <param name="code">code状态码</param>
/// <param name="message">消息内容</param>
/// <returns>返回的数据内容</returns>
public static Result<T> Success(HttpStatusCode code, string message)
{
return new Result<T>(code, default, message);
}
/// <summary>
/// 自定义返回成功消息
/// </summary>
/// <param name="code">code状态码</param>
/// <param name="data">数据内容分</param>
/// <returns>返回的数据内容</returns>
public static Result<T> Success(HttpStatusCode code, T data)
{
const ResultCodeEnum codeEnum = ResultCodeEnum.Success;
return new Result<T>(code, data, codeEnum.GetDescription());
}
/// <summary>
/// 自定义返回成功消息
/// </summary>
/// <param name="message">消息内容</param>
/// <param name="data">数据内容分</param>
/// <returns>返回的数据内容</returns>
public static Result<T> Success(string message, T data)
{
const HttpStatusCode codeEnum = (HttpStatusCode)ResultCodeEnum.Success;
return new Result<T>(codeEnum, data, message);
}
/// <summary>
/// 自定义返回成功消息
/// </summary>
/// <param name="codeEnum">code枚举类</param>
/// <param name="data">数据内容分</param>
/// <returns>返回的数据内容</returns>
public static Result<T> Success(ResultCodeEnum codeEnum, T data)
{
return new Result<T>((HttpStatusCode)codeEnum, data, codeEnum.GetDescription());
}
/// <summary>
/// 自定义返回成功消息
/// </summary>
/// <param name="code">code状态码</param>
/// <param name="data">数据内容分</param>
/// <param name="message">消息内容</param>
/// <returns>返回的数据内容</returns>
public static Result<T> Success(HttpStatusCode code, T data, string message)
{
return new Result<T>(code, data, message);
}
/// <summary>
/// 返回错误对象
/// </summary>
/// <returns>返回的数据内容</returns>
public static Result<T> Error()
{
const ResultCodeEnum codeEnum = ResultCodeEnum.Error;
return new Result<T>((HttpStatusCode)codeEnum, default, codeEnum.GetDescription());
}
/// <summary>
/// 返回错误对象
/// </summary>
/// <param name="code">code状态码</param>
/// <returns>返回的数据内容</returns>
public static Result<T> Error(HttpStatusCode code)
{
const ResultCodeEnum codeEnum = ResultCodeEnum.Error;
return new Result<T>(code, default, codeEnum.GetDescription());
}
/// <summary>
/// 返回错误对象
/// </summary>
/// <param name="data">数据内容分</param>
/// <returns>返回的数据内容</returns>
public static Result<T> Error(T? data)
{
const ResultCodeEnum codeEnum = ResultCodeEnum.Error;
return new Result<T>((HttpStatusCode)codeEnum, data, codeEnum.GetDescription());
}
/// <summary>
/// 返回错误对象
/// </summary>
/// <param name="codeEnum">code枚举类</param>
/// <returns>返回的数据内容</returns>
public static Result<T> Error(ResultCodeEnum codeEnum)
{
return new Result<T>((HttpStatusCode)codeEnum, default, codeEnum.GetDescription());
}
/// <summary>
/// 自定义返回成功消息
/// </summary>
/// <param name="code">code状态码</param>
/// <param name="message">消息内容</param>
/// <param name="data">错误数据</param>
/// <returns>返回的数据内容</returns>
public static Result<T> Error(int code, string message, T? data = default)
{
return new Result<T>(code, data, message);
}
/// <summary>
/// 自定义返回成功消息
/// </summary>
/// <param name="code">code状态码</param>
/// <param name="message">消息内容</param>
/// <returns>返回的数据内容</returns>
public static Result<T> Error(HttpStatusCode code, string message)
{
return new Result<T>(code, default, message);
}
/// <summary>
/// 返回错误对象
/// </summary>
/// <param name="code">code状态码</param>
/// <param name="data">数据内容分</param>
/// <returns>返回的数据内容</returns>
public static Result<T> Error(HttpStatusCode code, T data)
{
const ResultCodeEnum codeEnum = ResultCodeEnum.Error;
return new Result<T>(code, data, codeEnum.GetDescription());
}
/// <summary>
/// 返回错误对象
/// </summary>
/// <param name="message">消息内容</param>
/// <param name="data">数据内容分</param>
/// <returns>返回的数据内容</returns>
public static Result<T> Error(string message, T data)
{
const HttpStatusCode codeEnum = (HttpStatusCode)ResultCodeEnum.Error;
return new Result<T>(codeEnum, data, message);
}
/// <summary>
/// 返回错误对象
/// </summary>
/// <param name="codeEnum">code枚举类</param>
/// <param name="data">数据内容分</param>
/// <returns>返回的数据内容</returns>
public static Result<T> Error(ResultCodeEnum codeEnum, T data)
{
return new Result<T>((HttpStatusCode)codeEnum, data, codeEnum.GetDescription());
}
/// <summary>
/// 自定义返回成功消息
/// </summary>
/// <param name="code">code状态码</param>
/// <param name="data">数据内容分</param>
/// <param name="message">消息内容</param>
/// <returns>返回的数据内容</returns>
public static Result<T> Error(HttpStatusCode code, T data, string message)
{
return new Result<T>(code, data, message);
}
}

View File

@ -0,0 +1,13 @@
namespace Bunny.Dao.Vo.Email;
public class EmailTemplateVo
{
// 模板名称
public string? TemplateName { get; set; }
// 主题
public string? Subject { get; set; }
// 邮件内容
public string? Body { get; set; }
}

View File

@ -0,0 +1,12 @@
using Bunny.Dao.Entity.Base;
namespace Bunny.Dao.Vo.User;
public class LoginVo : BaseEntity
{
public string? UserName { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
public string? QrCode { get; set; }
public string? Token { get; set; }
}

View File

@ -0,0 +1,21 @@
using Microsoft.Extensions.Hosting;
namespace Bunny.Service.BackgroundModule;
/// <summary>
/// 定时任务
/// </summary>
public class TemplateBackgroundModule : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine("TemplateService started");
await Task.Delay(1000, stoppingToken);
await Task.Run(() => { Console.WriteLine("执行了。。。"); }, stoppingToken);
await Task.Delay(1000, stoppingToken);
Console.WriteLine("--------------------------------");
}
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Fleck" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Bunny.Common\Bunny.Common.csproj"/>
</ItemGroup>
<ItemGroup>
<Folder Include="IRepository\UserRepository\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,78 @@
using System.Net;
using Bunny.Common.Utils.Jwt;
using Bunny.Common.Utils.Net;
using Bunny.Dao.Entity.Constant;
using Bunny.Dao.Vo.User;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.IdentityModel.Tokens;
namespace Bunny.Service.Filter;
/// <summary>
/// 自定义权限过滤器
/// </summary>
public class AuthorizationFilter : IAuthorizationFilter
{
private string? _ipAddress;
private HttpRequest? _request;
private string? _requestPath;
private string? _token;
public void OnAuthorization(AuthorizationFilterContext context)
{
PathIgnore();
var request = context.HttpContext.Request;
_request = context.HttpContext.Request;
_token = NetUtil.GetToken(request);
_requestPath = NetUtil.GetRequestPath(request);
_ipAddress = NetUtil.GetRequestIpAddress(request.HttpContext);
// 是否匹配当前路径
var matchPath = NetUtil.MatchPath(_requestPath);
// 不匹配直接返回
if (!matchPath) return;
// 检查token
CheckToken(context);
// token 不为空
if (_token == null) return;
var jsonToObject = JwtUtil.ParseJwtToObject<LoginVo>(_token);
// 打印日志
Console.WriteLine(jsonToObject);
}
/// <summary>
/// 检查token相关内容
/// </summary>
/// <param name="context">上下文对象</param>
private void CheckToken(AuthorizationFilterContext context)
{
// token为空直接返回
if (_token.IsNullOrEmpty() && _token == null)
{
Console.Error.WriteLine($"未授权登录访问:{_ipAddress}");
context.Result = NetUtil.Out(HttpStatusCode.Unauthorized, ExceptionConstant.UserNotLoginException);
return;
}
var loginVo = JwtUtil.ParseJwtToObject<LoginVo>(_token!);
// 验证token是否过期
if (!JwtUtil.IsJwtExpired(_token) && loginVo != null) return;
Console.Error.WriteLine($"登录过期用户Id{loginVo!.Id},用户邮箱:{loginVo.Email}请求IP{_ipAddress}");
context.Result = NetUtil.Out(HttpStatusCode.Unauthorized, ExceptionConstant.UserTokenOutOfDateException);
}
/// <summary>
/// 忽略匹配路径
/// </summary>
private static void PathIgnore()
{
var list = new List<string> { "/", "/api/**" };
NetUtil.Ignores.AddRange(list);
}
}

View File

@ -0,0 +1,8 @@
using Bunny.Dao.Models.System;
namespace Bunny.Service.IService;
public interface IBaseService
{
string TestIndex();
}

View File

@ -0,0 +1,9 @@
using Bunny.Dao.Dto.User;
using Bunny.Dao.Vo.User;
namespace Bunny.Service.IService;
public interface ILoginService
{
}

View File

@ -0,0 +1,13 @@
using Bunny.Dao.Models.System;
namespace Bunny.Service.IService.Service;
public class BaseService : IBaseService
{
public string TestIndex()
{
const string result = "bunny-template 实现自动注入成功";
return result;
}
}

View File

@ -0,0 +1,12 @@
using Bunny.Common.Exception;
using Bunny.Common.Utils.Jwt;
using Bunny.Common.Utils.Net;
using Bunny.Dao.Dto.User;
using Bunny.Dao.Entity.Constant;
using Bunny.Dao.Vo.User;
namespace Bunny.Service.IService.Service;
public class LoginService : ILoginService
{
}

View File

@ -0,0 +1,9 @@
namespace Bunny.Service.WebSocket;
public static class WebSocketInitial
{
public static void Start()
{
WebSocketTest.Start();
}
}

View File

@ -0,0 +1,23 @@
using Fleck;
namespace Bunny.Service.WebSocket;
public static class WebSocketTest
{
public static void Start()
{
var webSocketServer = new WebSocketServer("ws://0.0.0.0:8000");
webSocketServer.RestartAfterListenError = true;
webSocketServer.Start(socket =>
{
socket.OnOpen = () => Console.WriteLine("Open!");
socket.OnClose = () => Console.WriteLine("Close!");
socket.OnMessage = message =>
{
Console.WriteLine(message);
socket.Send(message);
};
});
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0"/>
<PackageReference Include="NUnit" Version="4.1.0"/>
<PackageReference Include="xunit.core" Version="2.9.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Bunny.Common\Bunny.Common.csproj"/>
<ProjectReference Include="..\Bunny.Dao\Bunny.Dao.csproj"/>
<ProjectReference Include="..\Bunny.WebApi\Bunny.WebApi.csproj"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,34 @@
using Bunny.Common.Utils.Jwt;
using Bunny.Dao.Dto.Email;
using NUnit.Framework;
namespace Bunny.Test.Until.Jwt;
[TestFixture]
public class JwtConvertTest
{
[SetUp]
public void Initialize()
{
var emailUsersDto = new EmailUsersDto
{
Body = "test@example.com",
Subject = "苏北",
TemplateName = "test@example.com",
Type = "邮件类型"
};
_emailUsersDto = emailUsersDto;
}
private EmailUsersDto? _emailUsersDto;
[Test]
public void CreateJClaimByEmail()
{
var claims = JwtUtil.CreateClaim(_emailUsersDto!);
Console.WriteLine(claims);
var jwt = JwtUtil.GenerateJwt(claims);
Console.WriteLine(jwt);
}
}

View File

@ -0,0 +1,61 @@
using System.Security.Claims;
using NUnit.Framework;
using JwtUtil = Bunny.Common.Utils.Jwt.JwtUtil;
namespace Bunny.Test.Until.Jwt;
[TestFixture]
public class JwtTest
{
// 生层JWT
[Test]
public void JwtCreateTest()
{
var claims = new List<Claim>();
var claim1 = new Claim("bunny", "小兔子");
claims.Add(claim1);
var generateJwt = JwtUtil.GenerateJwt(claims);
Console.WriteLine(generateJwt);
}
// 是否过期
[Test]
public void IsJwtExpired()
{
var isJwtExpired = JwtUtil.IsJwtExpired("");
Console.WriteLine(isJwtExpired);
}
[Test]
public void JwtParse()
{
var token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJidW5ueSI6IuWwj-WFlOWtkCIsImV4cCI6MTcxODAyODU5OH0.\n";
var jwtParse = JwtUtil.JwtParse(token);
var jwtParseIssuer = JwtUtil.JwtParseIssuer(token);
var jwtParseAudiences = JwtUtil.JwtParseAudiences(token);
var jwtParseClaim = JwtUtil.JwtParseClaim(token);
Console.WriteLine(jwtParse);
Console.WriteLine(jwtParseIssuer);
Console.WriteLine(jwtParseAudiences);
Console.WriteLine(jwtParseClaim);
foreach (var claim in jwtParseClaim)
{
Console.WriteLine($"Type:{claim.Type}");
Console.WriteLine($"Value:{claim.Value}");
}
}
[Test]
public void JwtParseClaim()
{
var token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJidW5ueSI6IuWwj-WFlOWtkCIsImV4cCI6MTcxODAyODU5OH0.\n";
var jwtParseClaim = JwtUtil.JwtParseClaim(token);
var enumerable = jwtParseClaim.Select(s => s.Type);
var join = string.Join(" ", enumerable);
Console.WriteLine(join);
}
}

View File

@ -0,0 +1,69 @@
using Bunny.Common.Utils.Net;
using Bunny.Dao.Dto.Email;
using Newtonsoft.Json;
using NUnit.Framework;
namespace Bunny.Test.Until;
public class NetUtilTest
{
[Test]
public void NetTest1()
{
var emailTemplateDto = new EmailTemplateDto
{
Body = "test@example.com",
Subject = "主题",
TemplateName = "hjh"
};
var emailUsersDto = new EmailUsersDto();
NetUtil.CopyProperties(emailTemplateDto, emailUsersDto);
Console.WriteLine(JsonConvert.SerializeObject(emailUsersDto));
}
[Test]
public void Encryption32()
{
// Arrange
var password = "1";
// Act
var result = NetUtil.Encryption32(password);
Console.WriteLine(result);
}
[Test]
public void Encryption32Lower()
{
// Arrange
var password = "1";
// Act
var result = NetUtil.Encryption32Lower(password);
Console.WriteLine(result);
}
[Test]
public void GetProperties()
{
var emailTemplateDto = new EmailTemplateDto
{
Body = "test@example.com",
Subject = "主题",
TemplateName = "hjh"
};
var propertyInfos = emailTemplateDto.GetType().GetProperties();
foreach (var propertyInfo in propertyInfos)
{
Console.WriteLine(propertyInfo.Name);
Console.WriteLine(propertyInfo.GetValue(emailTemplateDto));
return;
}
}
}

View File

@ -0,0 +1,35 @@
using Bunny.Service.IService.Service;
using Bunny.WebApi.Controllers.Base;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Xunit;
using Xunit.Abstractions;
namespace Bunny.Test.Until.Redis;
[TestFixture]
public class RedisConnect
{
private readonly ITestOutputHelper _testOutputHelper;
private readonly ServiceProvider _provider;
public RedisConnect(ITestOutputHelper testOutputHelper)
{
_testOutputHelper = testOutputHelper;
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging();
serviceCollection.AddTransient<TemplateController>();
_provider = serviceCollection.BuildServiceProvider();
}
[Fact]
public void RedisConnectTest()
{
var baseService = new BaseService();
var testIndex = baseService.TestIndex();
_testOutputHelper.WriteLine(testIndex);
var controller = _provider.GetService<TemplateController>();
}
}

View File

@ -0,0 +1,60 @@
using System.Net;
using Bunny.Dao.Entity.Constant;
using Bunny.Dao.Result;
using Bunny.Dao.Result.Enum;
using Newtonsoft.Json;
using NUnit.Framework;
namespace Bunny.Test.Until;
[TestFixture]
public class TestResultCodeEnum
{
[Test]
public void TestResultCodeExtensions()
{
const ResultCodeEnum resultCodeEnum = ResultCodeEnum.Success;
Console.WriteLine(resultCodeEnum);
Console.WriteLine(resultCodeEnum.GetDescription());
}
[Test]
public void TestSuccessResult()
{
var success = Result<Dictionary<string, object>>.Success();
var serializeObject = JsonConvert.SerializeObject(success);
Console.WriteLine(serializeObject);
}
[Test]
public void TestErrorResult()
{
var result = Result<string>.Error();
var serializeObject = JsonConvert.SerializeObject(result);
Console.WriteLine(serializeObject);
}
[Test]
public void TestCodeResult()
{
var result = Result<string>.Error(HttpStatusCode.OK);
var serializeObject = JsonConvert.SerializeObject(result);
Console.WriteLine(serializeObject);
}
[Test]
public void TestMessageResult()
{
var result = Result<string>.Error(ExceptionConstant.UnknownException);
var serializeObject = JsonConvert.SerializeObject(result);
Console.WriteLine(serializeObject);
}
[Test]
public void TestCodeMessageResult()
{
var result = Result<string>.Error(HttpStatusCode.Ambiguous, ExceptionConstant.UnknownException);
var serializeObject = JsonConvert.SerializeObject(result);
Console.WriteLine(serializeObject);
}
}

49
Bunny.WebApi.sln Normal file
View File

@ -0,0 +1,49 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.34928.147
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bunny.WebApi", "Bunny.WebApi\Bunny.WebApi.csproj", "{28753039-0C3B-4FE5-95F8-7EDC92DCBA26}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bunny.Common", "Bunny.Common\Bunny.Common.csproj", "{40D580AA-63CD-4912-835F-ECBB2C232F9E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bunny.Test.Until", "Bunny.Test.Until\Bunny.Test.Until.csproj", "{945BE294-8057-40EE-9A98-5598D1A728B9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bunny.Dao", "Bunny.Dao\Bunny.Dao.csproj", "{A0A986A6-A0A4-4627-B45A-94A882FC70ED}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bunny.Service", "Bunny.Service\Bunny.Service.csproj", "{7CEBC594-134C-48C1-96ED-263E047E7D96}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{28753039-0C3B-4FE5-95F8-7EDC92DCBA26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{28753039-0C3B-4FE5-95F8-7EDC92DCBA26}.Debug|Any CPU.Build.0 = Debug|Any CPU
{28753039-0C3B-4FE5-95F8-7EDC92DCBA26}.Release|Any CPU.ActiveCfg = Release|Any CPU
{28753039-0C3B-4FE5-95F8-7EDC92DCBA26}.Release|Any CPU.Build.0 = Release|Any CPU
{40D580AA-63CD-4912-835F-ECBB2C232F9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{40D580AA-63CD-4912-835F-ECBB2C232F9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{40D580AA-63CD-4912-835F-ECBB2C232F9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{40D580AA-63CD-4912-835F-ECBB2C232F9E}.Release|Any CPU.Build.0 = Release|Any CPU
{945BE294-8057-40EE-9A98-5598D1A728B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{945BE294-8057-40EE-9A98-5598D1A728B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{945BE294-8057-40EE-9A98-5598D1A728B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{945BE294-8057-40EE-9A98-5598D1A728B9}.Release|Any CPU.Build.0 = Release|Any CPU
{A0A986A6-A0A4-4627-B45A-94A882FC70ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A0A986A6-A0A4-4627-B45A-94A882FC70ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A0A986A6-A0A4-4627-B45A-94A882FC70ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A0A986A6-A0A4-4627-B45A-94A882FC70ED}.Release|Any CPU.Build.0 = Release|Any CPU
{7CEBC594-134C-48C1-96ED-263E047E7D96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7CEBC594-134C-48C1-96ED-263E047E7D96}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7CEBC594-134C-48C1-96ED-263E047E7D96}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7CEBC594-134C-48C1-96ED-263E047E7D96}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3215F6BB-D889-4D2D-9572-509020181496}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,17 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue">&lt;AssemblyExplorer&gt;&#xD;
&lt;Assembly Path="D:\software\Microsoft\Visual Studio\packages\stackexchange.redis\2.7.33\lib\net6.0\StackExchange.Redis.dll" /&gt;&#xD;
&lt;Assembly Path="D:\software\Microsoft\Visual Studio\packages\minio\6.0.2\lib\net7.0\Minio.dll" /&gt;&#xD;
&lt;/AssemblyExplorer&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=eb9da086_002D824a_002D4ac4_002Db755_002D94438e510122/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="Test1" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;TestAncestor&gt;&#xD;
&lt;TestId&gt;NUnit3x::945BE294-8057-40EE-9A98-5598D1A728B9::net8.0::Bunny.Test.Until.NetUtilTest.Encryption32Lower&lt;/TestId&gt;&#xD;
&lt;TestId&gt;NUnit3x::945BE294-8057-40EE-9A98-5598D1A728B9::net8.0::Bunny.Test.Until.NetUtilTest.GetProperties&lt;/TestId&gt;&#xD;
&lt;TestId&gt;NUnit3x::945BE294-8057-40EE-9A98-5598D1A728B9::net8.0::Bunny.Test.Until.Redis.RedisConnect.RedisConnectTest&lt;/TestId&gt;&#xD;
&lt;TestId&gt;xUnit::945BE294-8057-40EE-9A98-5598D1A728B9::net8.0::Bunny.Test.Until.Redis.RedisConnect.RedisConnectTest&lt;/TestId&gt;&#xD;
&lt;/TestAncestor&gt;&#xD;
&lt;/SessionState&gt;</s:String>
</wpf:ResourceDictionary>

View File

@ -0,0 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<DocumentationFile>bin\Debug\net8.0\Swagger.xml</DocumentationFile>
<ProduceReferenceAssembly>False</ProduceReferenceAssembly>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<_WebToolingArtifacts Remove="Properties\launchSettings.json"/>
</ItemGroup>
<ItemGroup>
<Content Include="Properties\launchSettings.json"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="IGeekFan.AspNetCore.Knife4jUI" Version="0.0.16"/>
<PackageReference Include="log4net" Version="2.0.17"/>
<PackageReference Include="Microsoft.Extensions.Logging.Log4Net.AspNetCore" Version="8.0.0"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2"/>
<PackageReference Include="System.Data.SqlClient" Version="4.8.6"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Bunny.Service\Bunny.Service.csproj"/>
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="Config\log4net.config"/>
<_ContentIncludedByDefault Remove="Config\NLog.config"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ActiveDebugProfile>http</ActiveDebugProfile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,6 @@
@Bunny.WebApi_HostAddress = http://localhost:5106
GET {{Bunny.WebApi_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -0,0 +1,87 @@
using Bunny.Common;
using Bunny.Service.WebSocket;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Bunny.WebApi.Config;
public class BaseConfig
{
/// <summary>
/// 只要Controller和Service放在 WebApi 项目下即可完成自动注入
/// 遵守这个规则不用再启动类上注入IOC
/// 遵守这个规则在Controller可以使用属性方式注入Service
/// 只要写接口和实现、Controller中正常写即可
/// </summary>
/// <param name="builder"></param>
public static void Initialize(WebApplicationBuilder builder)
{
Console.Title = "Bunny Template";
// 配置
UseConfiguration(builder);
// 使用扩展第三方如Redis、Minio、SQLSugar
UseExtend(builder);
// 配置跨域
UseCors(builder);
}
private static void UseConfiguration(WebApplicationBuilder builder)
{
// 配置日志相关
builder.Logging.AddLog4Net("Config/log4net.config");
//配置文件
builder.Services.AddSingleton(new AppSettings(builder.Configuration));
// 添加 SignalR
builder.Services.AddSignalR();
// 让控制器实例由容器创建
builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
// 如果将Service放在 WebApi同级目录下可以完成Controller自动注入不需要写构造函数
builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
builder.Services.AddControllers().AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new JsonDateTimeConverter()));
}
/// <summary>
/// 扩展服务
/// </summary>
/// <param name="builder">WebApplicationBuilder</param>
private static void UseExtend(WebApplicationBuilder builder)
{
// 设置过滤器
FilterConfig.Initialize(builder);
// 初始化Redis
// RedisTemplate.Initial();
// 初始化 Knife4Net文档
Knife4Net.Initialize(builder);
// 启动webSocket
WebSocketInitial.Start();
}
/// <summary>
/// 配置跨域
/// </summary>
/// <param name="builder"></param>
private static void UseCors(IHostApplicationBuilder builder)
{
// SignalR 配置跨域
builder.Services.AddSignalRCore();
// 配置跨域
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policyBuilder => policyBuilder.WithOrigins("*")
.WithHeaders("*")
.WithMethods("*"));
});
}
}

View File

@ -0,0 +1,21 @@
using Bunny.Common.Filter;
using Bunny.Service.Filter;
namespace Bunny.WebApi.Config;
public static class FilterConfig
{
/// <summary>
/// 配置过滤器
/// </summary>
/// <param name="builder"></param>
public static void Initialize(WebApplicationBuilder builder)
{
// 添加自定义过滤器---全局异常捕获
builder.Services.AddControllers(option =>
{
option.Filters.Add<GlobalExceptionFilter>();
option.Filters.Add<AuthorizationFilter>();
});
}
}

View File

@ -0,0 +1,17 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Bunny.WebApi.Config;
public class JsonDateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateTime.TryParse(reader.GetString(), out var date) ? date : default;
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss"));
}
}

View File

@ -0,0 +1,38 @@
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.OpenApi.Models;
namespace Bunny.WebApi.Config;
public static class Knife4Net
{
/// <summary>
/// 配置 Knife4j 文档
/// </summary>
public static void Initialize(WebApplicationBuilder builder)
{
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
// 读取设置的文档,将注释读取
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "Swagger.xml"), true);
// 设置文档版本和说明
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Bunny 文档", Version = "v1", Description = "文档说明"
});
options.AddServer(new OpenApiServer
{
Url = "http://localhost:8800"
});
options.CustomOperationIds(apiDesc =>
{
var controllerAction = apiDesc.ActionDescriptor as ControllerActionDescriptor;
return controllerAction?.ControllerName + "-" + controllerAction?.ActionName;
});
});
}
}

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<log4net>
<!-- Define some output appenders -->
<appender name="rollingAppender" type="log4net.Appender.RollingFileAppender">
<file value="log4\log.txt"/>
<!--追加日志内容-->
<appendToFile value="true"/>
<!--防止多线程时不能写Log,官方说线程非安全-->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
<!--可以为:Once|Size|Date|Composite-->
<!--Composite为Size和Date的组合-->
<rollingStyle value="Composite"/>
<!--当备份文件时,为文件名加的后缀-->
<datePattern value="yyyyMMdd.TXT"/>
<!--日志最大个数,都是最新的-->
<!--rollingStyle节点为Size时,只能有value个日志-->
<!--rollingStyle节点为Composite时,每天有value个日志-->
<maxSizeRollBackups value="20"/>
<!--可用的单位:KB|MB|GB-->
<maximumFileSize value="3MB"/>
<!--置为true,当前最新日志文件名永远为file节中的名字-->
<staticLogFileName value="true"/>
<!--输出级别在INFO和ERROR之间的日志-->
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="ALL"/>
<param name="LevelMax" value="FATAL"/>
</filter>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %Message%newline"/>
</layout>
</appender>
<!--SqlServer形式-->
<!--log4net日志配置http://logging.apache.org/log4net/release/config-examples.html -->
<appender name="AdoNetAppender_SqlServer" type="log4net.Appender.AdoNetAppender">
<!--日志缓存写入条数 设置为0时只要有一条就立刻写到数据库-->
<bufferSize value="0"/>
<!--日志数据库连接串-->
<connectionType
value="System.Data.SqlClient.SqlConnection,System.Data.SqlClient, Version=4.6.1.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
<connectionString
value="Data Source=192.168.3.98;Initial Catalog=LogManager;Persist Security Info=True;User ID=sa;Password=abc1234."/>
<commandText
value="INSERT INTO Log4Net ([Date],[Thread],[Level],[Logger],[Message],[Exception]) VALUES (@log_date, @thread, @log_level, @logger, @Message, @exception)"/>
<parameter>
<parameterName value="@log_date"/>
<dbType value="DateTime"/>
<layout type="log4net.Layout.RawTimeStampLayout"/>
</parameter>
<parameter>
<parameterName value="@thread"/>
<dbType value="String"/>
<size value="255"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread"/>
</layout>
</parameter>
<parameter>
<parameterName value="@log_level"/>
<dbType value="String"/>
<size value="50"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level"/>
</layout>
</parameter>
<parameter>
<parameterName value="@logger"/>
<dbType value="String"/>
<size value="255"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger"/>
</layout>
</parameter>
<parameter>
<parameterName value="@Message"/>
<dbType value="String"/>
<size value="4000"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%Message"/>
</layout>
</parameter>
<parameter>
<parameterName value="@exception"/>
<dbType value="String"/>
<size value="2000"/>
<layout type="log4net.Layout.ExceptionLayout"/>
</parameter>
</appender>
<root>
<!--控制级别,由低到高: ALL|DEBUG|INFO|WARN|ERROR|FATAL|OFF-->
<!--OFF:0-->
<!--FATAL:FATAL-->
<!--ERROR: ERROR,FATAL-->
<!--WARN: WARN,ERROR,FATAL-->
<!--INFO: INFO,WARN,ERROR,FATAL-->
<!--DEBUG: INFO,WARN,ERROR,FATAL-->
<!--ALL: DEBUG,INFO,WARN,ERROR,FATAL-->
<priority value="ALL"/>
<!-- TODO 修改成你自己的类型 -->
<level value="WARN"/>
<appender-ref ref="rollingAppender"/>
<appender-ref ref="AdoNetAppender_SqlServer"/>
</root>
</log4net>

View File

@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Mvc;
namespace Bunny.WebApi.Controllers.Base;
/// <summary>
/// 主页请求
/// </summary>
[ApiController]
[Route("/")]
public class IndexController : ControllerBase
{
/// <summary>
/// 请求主页面响应内容
/// </summary>
/// <returns></returns>
[HttpGet("")]
public IActionResult Index()
{
return Ok("看到这里页面说明你已经成功启动了本项目:)\n\n如果觉得项目有用打赏作者喝杯咖啡作为奖励>>https://gitee.com/BunnyBoss/bunny-template.git");
}
}

View File

@ -0,0 +1,30 @@
using Bunny.Common.Exception;
using Bunny.Dao.Models.System;
using Bunny.Dao.Result;
using Bunny.Service.IService;
using Microsoft.AspNetCore.Mvc;
namespace Bunny.WebApi.Controllers.Base;
/// <summary>
/// 测试请求模板
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class TemplateController : ControllerBase
{
public IBaseService? BaseService { get; set; }
/// <summary>
/// 测试走缓存
/// </summary>
/// <returns></returns>
[HttpGet("GetCacheController")]
public Result<string> CacheControl()
{
Response.Headers.CacheControl = "max-age=20";
return Result<string>.Success($"测试走缓存内容:{Response.Headers.CacheControl}");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,140 @@
/*
:
1.Log4Net写数据库日志脚本
2. Nlog写数据库日志脚本
*/
USE [master]
GO
/*
LogManager
*/
CREATE DATABASE [LogManager];
GO
ALTER DATABASE [LogManager] SET COMPATIBILITY_LEVEL = 150
GO
IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled'))
begin
EXEC [LogManager].[dbo].[sp_fulltext_database] @action = 'enable'
end
GO
ALTER DATABASE [LogManager] SET ANSI_NULL_DEFAULT OFF
GO
ALTER DATABASE [LogManager] SET ANSI_NULLS OFF
GO
ALTER DATABASE [LogManager] SET ANSI_PADDING OFF
GO
ALTER DATABASE [LogManager] SET ANSI_WARNINGS OFF
GO
ALTER DATABASE [LogManager] SET ARITHABORT OFF
GO
ALTER DATABASE [LogManager] SET AUTO_CLOSE OFF
GO
ALTER DATABASE [LogManager] SET AUTO_SHRINK OFF
GO
ALTER DATABASE [LogManager] SET AUTO_UPDATE_STATISTICS ON
GO
ALTER DATABASE [LogManager] SET CURSOR_CLOSE_ON_COMMIT OFF
GO
ALTER DATABASE [LogManager] SET CURSOR_DEFAULT GLOBAL
GO
ALTER DATABASE [LogManager] SET CONCAT_NULL_YIELDS_NULL OFF
GO
ALTER DATABASE [LogManager] SET NUMERIC_ROUNDABORT OFF
GO
ALTER DATABASE [LogManager] SET QUOTED_IDENTIFIER OFF
GO
ALTER DATABASE [LogManager] SET RECURSIVE_TRIGGERS OFF
GO
ALTER DATABASE [LogManager] SET DISABLE_BROKER
GO
ALTER DATABASE [LogManager] SET AUTO_UPDATE_STATISTICS_ASYNC OFF
GO
ALTER DATABASE [LogManager] SET DATE_CORRELATION_OPTIMIZATION OFF
GO
ALTER DATABASE [LogManager] SET TRUSTWORTHY OFF
GO
ALTER DATABASE [LogManager] SET ALLOW_SNAPSHOT_ISOLATION OFF
GO
ALTER DATABASE [LogManager] SET PARAMETERIZATION SIMPLE
GO
ALTER DATABASE [LogManager] SET READ_COMMITTED_SNAPSHOT OFF
GO
ALTER DATABASE [LogManager] SET HONOR_BROKER_PRIORITY OFF
GO
ALTER DATABASE [LogManager] SET RECOVERY FULL
GO
ALTER DATABASE [LogManager] SET MULTI_USER
GO
ALTER DATABASE [LogManager] SET PAGE_VERIFY CHECKSUM
GO
ALTER DATABASE [LogManager] SET DB_CHAINING OFF
GO
ALTER DATABASE [LogManager] SET FILESTREAM( NON_TRANSACTED_ACCESS = OFF )
GO
ALTER DATABASE [LogManager] SET TARGET_RECOVERY_TIME = 60 SECONDS
GO
ALTER DATABASE [LogManager] SET DELAYED_DURABILITY = DISABLED
GO
ALTER DATABASE [LogManager] SET ACCELERATED_DATABASE_RECOVERY = OFF
GO
EXEC sys.sp_db_vardecimal_storage_format N'LogManager', N'ON'
GO
ALTER DATABASE [LogManager] SET QUERY_STORE = OFF
GO
USE [LogManager]
GO
/****** Object: Table [dbo].[Log4Net] Script Date: 2021/11/26 10:56:35 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/*
Log4net的表
*/
CREATE TABLE [dbo].[Log4Net](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Date] [datetime] NOT NULL,
[Thread] [varchar](255) NOT NULL,
[Level] [varchar](50) NOT NULL,
[Logger] [varchar](255) NOT NULL,
[Message] [varchar](4000) NOT NULL,
[Exception] [varchar](2000) NULL
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[NLog] Script Date: 2021/11/26 10:56:35 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/*
Log4net的表
*/
CREATE TABLE [dbo].[NLog](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Application] [nvarchar](50) NOT NULL,
[Logged] [datetime] NOT NULL,
[Level] [nvarchar](50) NOT NULL,
[Message] [nvarchar](max) NOT NULL,
[Logger] [nvarchar](250) NULL,
[Callsite] [nvarchar](max) NULL,
[Exception] [nvarchar](max) NULL,
CONSTRAINT [PK_dbo.Log] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
USE [master]
GO
ALTER DATABASE [LogManager] SET READ_WRITE
GO

24
Bunny.WebApi/Dockerfile Normal file
View File

@ -0,0 +1,24 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["Bunny.WebApi/Bunny.WebApi.csproj", "Bunny.WebApi/"]
RUN dotnet restore "./Bunny.WebApi/Bunny.WebApi.csproj"
COPY . .
WORKDIR "/src/Bunny.WebApi"
RUN dotnet build "./Bunny.WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Bunny.WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Bunny.WebApi.dll"]

43
Bunny.WebApi/Program.cs Normal file
View File

@ -0,0 +1,43 @@
using Bunny.WebApi.Config;
using IGeekFan.AspNetCore.Knife4jUI;
namespace Bunny.WebApi;
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// 其它配置
BaseConfig.Initialize(builder);
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
app.UseKnife4UI(options =>
{
options.RoutePrefix = "bunnyDocs"; // serve the UI at root
options.DocumentTitle = "Bunny 文档";
options.SwaggerEndpoint("//swagger/v1/swagger.json", "Bunny 文档");
});
}
// 跨域配置
app.UseCors();
// 身份验证
app.UseAuthentication();
// 授权
app.UseAuthorization();
app.MapControllers();
app.UseRouting();
// app.UseWebSockets();
app.Run();
}
}

View File

@ -0,0 +1,34 @@
{
"profiles": {
"http": {
"commandName": "Project",
"launchBrowser": false,
"launchUrl": "bunnyDocs",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": false,
"applicationUrl": "http://*:8900"
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"environmentVariables": {
"ASPNETCORE_HTTPS_PORTS": "8900",
"ASPNETCORE_HTTP_PORTS": "8900"
},
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "https://localhost:8900",
"sslPort": 8900
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Information"
}
}
}

View File

@ -0,0 +1,18 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Information"
}
},
"AllowedHosts": "*",
"DataBase": {
"SqlServerConnection": "Data Source=192.168.3.98;Initial Catalog=BunnyTest;Persist Security Info=True;User ID=sa;Password=abc1234.",
"TimeOut": 6
},
"JWT": {
"Issuer": "Issuer",
"Audience": "Audience"
}
}