复杂策略授权-实战

1.配置用户跟角色关联表

/// <summary>
/// 用户跟角色关联表
/// </summary>
public class UserRole : RootEntityTkey<long>
{
    /// <summary>
    /// 用户ID
    /// </summary>
    public long UserId { get; set; }
    /// <summary>
    /// 角色ID
    /// </summary>
    public long RoleId { get; set; }
    /// <summary>
    ///获取或设置是否禁用,逻辑上的删除,非物理删除
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public bool? IsDeleted { get; set; }

    /// <summary>
    /// 创建ID
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public long? CreateId { get; set; }
    /// <summary>
    /// 创建者
    /// </summary>
    [SugarColumn(Length = 50, IsNullable = true)]
    public string CreateBy { get; set; }
    /// <summary>
    /// 创建时间
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public DateTime? CreateTime { get; set; }
    /// <summary>
    /// 修改ID
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public int? ModifyId { get; set; }
    /// <summary>
    /// 修改者
    /// </summary>
    [SugarColumn(Length = 50, IsNullable = true)]
    public string ModifyBy { get; set; }
    /// <summary>
    /// 修改时间
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public DateTime? ModifyTime { get; set; }

}


/// <summary>
/// 用户跟角色关联表
/// </summary>
public class UserRoleVo
{
    public long UserId { get; set; }

    public long RoleId { get; set; }

    public bool? IsDeleted { get; set; }

    public long? CreateId { get; set; }

    public string CreateBy { get; set; }

    public DateTime? CreateTime { get; set; }

    public int? ModifyId { get; set; }

    public string ModifyBy { get; set; }

    public DateTime? ModifyTime { get; set; }

}

2.配置通用返回信息类MessageModel

/// <summary>
/// 通用返回信息类
/// </summary>
public class MessageModel<T>
{
    /// <summary>
    /// 状态码
    /// </summary>
    public int status { get; set; } = 200;
    /// <summary>
    /// 操作是否成功
    /// </summary>
    public bool success { get; set; } = false;
    /// <summary>
    /// 返回信息
    /// </summary>
    public string msg { get; set; } = "";
    /// <summary>
    /// 开发者信息
    /// </summary>
    public string msgDev { get; set; }
    /// <summary>
    /// 返回数据集合
    /// </summary>
    public T response { get; set; }

    /// <summary>
    /// 返回成功
    /// </summary>
    /// <param name="msg">消息</param>
    /// <returns></returns>
    public static MessageModel<T> Success(string msg)
    {
        return Message(true, msg, default);
    }
    /// <summary>
    /// 返回成功
    /// </summary>
    /// <param name="msg">消息</param>
    /// <param name="response">数据</param>
    /// <returns></returns>
    public static MessageModel<T> Success(string msg, T response)
    {
        return Message(true, msg, response);
    }
    /// <summary>
    /// 返回失败
    /// </summary>
    /// <param name="msg">消息</param>
    /// <returns></returns>
    public static MessageModel<T> Fail(string msg)
    {
        return Message(false, msg, default);
    }
    /// <summary>
    /// 返回失败
    /// </summary>
    /// <param name="msg">消息</param>
    /// <param name="response">数据</param>
    /// <returns></returns>
    public static MessageModel<T> Fail(string msg, T response)
    {
        return Message(false, msg, response);
    }
    /// <summary>
    /// 返回消息
    /// </summary>
    /// <param name="success">失败/成功</param>
    /// <param name="msg">消息</param>
    /// <param name="response">数据</param>
    /// <returns></returns>
    public static MessageModel<T> Message(bool success, string msg, T response)
    {
        return new MessageModel<T>() { msg = msg, response = response, success = success };
    }
}

public class MessageModel
{
    /// <summary>
    /// 状态码
    /// </summary>
    public int status { get; set; } = 200;
    /// <summary>
    /// 操作是否成功
    /// </summary>
    public bool success { get; set; } = false;
    /// <summary>
    /// 返回信息
    /// </summary>
    public string msg { get; set; } = "";
    /// <summary>
    /// 返回数据集合
    /// </summary>
    public object response { get; set; }
}

3.接口API地址信息表Modules

/// <summary>
/// 接口API地址信息表
/// </summary>
public class Modules : RootEntityTkey<long>
{
    public long ParentId { get; set; }


    /// <summary>
    ///获取或设置是否禁用,逻辑上的删除,非物理删除
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public bool? IsDeleted { get; set; }

    /// <summary>
    /// 名称
    /// </summary>
    [SugarColumn(Length = 50, IsNullable = true)]
    public string Name { get; set; }
    /// <summary>
    /// 菜单链接地址
    /// </summary>
    [SugarColumn(Length = 100, IsNullable = true)]
    public string LinkUrl { get; set; }
    /// <summary>
    /// 区域名称
    /// </summary>
    [SugarColumn(Length = 2000, IsNullable = true)]
    public string Area { get; set; }
    /// <summary>
    /// 控制器名称
    /// </summary>
    [SugarColumn(Length = 2000, IsNullable = true)]
    public string Controller { get; set; }
    /// <summary>
    /// Action名称
    /// </summary>
    [SugarColumn(Length = 2000, IsNullable = true)]
    public string Action { get; set; }
    /// <summary>
    /// 图标
    /// </summary>
    [SugarColumn(Length = 100, IsNullable = true)]
    public string Icon { get; set; }
    /// <summary>
    /// 菜单编号
    /// </summary>
    [SugarColumn(Length = 10, IsNullable = true)]
    public string Code { get; set; }
    /// <summary>
    /// 排序
    /// </summary>
    public int OrderSort { get; set; }
    /// <summary>
    /// /描述
    /// </summary>
    [SugarColumn(Length = 100, IsNullable = true)]
    public string Description { get; set; }
    /// <summary>
    /// 是否是右侧菜单
    /// </summary>
    public bool IsMenu { get; set; }
    /// <summary>
    /// 是否激活
    /// </summary>
    public bool Enabled { get; set; }
    /// <summary>
    /// 创建ID
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public long? CreateId { get; set; }
    /// <summary>
    /// 创建者
    /// </summary>
    [SugarColumn(Length = 50, IsNullable = true)]
    public string CreateBy { get; set; }
    /// <summary>
    /// 创建时间
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public DateTime CreateTime { get; set; } = DateTime.Now;
    /// <summary>
    /// 修改ID
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public long? ModifyId { get; set; }
    /// <summary>
    /// 修改者
    /// </summary>
    [SugarColumn(Length = 100, IsNullable = true)]
    public string ModifyBy { get; set; }
    /// <summary>
    /// 修改时间
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public DateTime? ModifyTime { get; set; } = DateTime.Now;

    //public virtual Module ParentModule { get; set; }
    //public virtual ICollection<Module> ChildModule { get; set; }
    //public virtual ICollection<ModulePermission> ModulePermission { get; set; }
    //public virtual ICollection<RoleModulePermission> RoleModulePermission { get; set; }
}

4.配置Token信息视图模型TokenInfoViewModel

public class TokenInfoViewModel
{
    public bool success { get; set; }
    public string token { get; set; }
    public double expires_in { get; set; }
    public string token_type { get; set; }
}

5.配置按钮跟权限关联表RoleModulePermission类

/// <summary>
/// 按钮跟权限关联表
/// </summary>
public class RoleModulePermission : RootEntityTkey<long>
{
    public long RoleId { get; set; }
    /// <summary>
    /// 接口ID
    /// </summary>
    public long ModuleId { get; set; }
    /// <summary>
    /// 菜单前端 ID
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public long PermissionId { get; set; }

    /// <summary>
    ///获取或设置是否禁用,逻辑上的删除,非物理删除
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public bool? IsDeleted { get; set; }

    /// <summary>
    /// 创建ID
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public long? CreateId { get; set; }
    /// <summary>
    /// 创建者
    /// </summary>
    [SugarColumn(Length = 50, IsNullable = true)]
    public string CreateBy { get; set; }
    /// <summary>
    /// 创建时间
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public DateTime? CreateTime { get; set; } = DateTime.Now;
    /// <summary>
    /// 修改ID
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public long? ModifyId { get; set; }
    /// <summary>
    /// 修改者
    /// </summary>
    [SugarColumn(Length = 50, IsNullable = true)]
    public string ModifyBy { get; set; }
    /// <summary>
    /// 修改时间
    /// </summary>
    [SugarColumn(IsNullable = true)]
    public DateTime? ModifyTime { get; set; } = DateTime.Now;

    // 下边三个实体参数,只是做传参作用,所以忽略下
    [SugarColumn(IsIgnore = true)]
    public Role Role { get; set; }
    [SugarColumn(IsIgnore = true)]
    public Modules Module { get; set; }

}

6.重新配置Model层UserVo类

public class UserVo
    {
        public long Id { get; set; }
        public string UserName { get; set; }

        public string LoginName { get; set; }

        public string LoginPWD { get; set; }

        public string RealName { get; set; }

        public int Status { get; set; } = 0;

        public long DepartmentId { get; set; } = -1;

        public string Remark { get; set; }

        public DateTime CreateTime { get; set; } = DateTime.Now;

        public DateTime UpdateTime { get; set; } = DateTime.Now;

        public DateTime CriticalModifyTime { get; set; } = DateTime.Now;

        public DateTime LastErrorTime { get; set; } = DateTime.Now;

        public int ErrorCount { get; set; } = 0;

        public string Name { get; set; }

        public int Sex { get; set; } = 0;

        public int Age { get; set; }

        public DateTime Birth { get; set; } = DateTime.Now;

        public string Address { get; set; }

        public bool Enable { get; set; } = true;

        public bool IsDeleted { get; set; } = false;

        public long TenantId { get; set; }

        public List<string> RoleNames { get; set; }

        public List<long> Dids { get; set; }

        public string DepartmentName { get; set; }

        public List<long> RIDs { get; set; }
    }

7.IBaseRepository和BaseRepository添加方法
image

Task<List<TEntity>> Query(Expression<Func<TEntity, bool>> whereExpression);
Task<List<TResult>> QueryMuch<T, T2, T3, TResult>(Expression<Func<T, T2, T3, object[]>> joinExpression, Expression<Func<T, T2, T3, TResult>> selectExpression, Expression<Func<T, T2, T3, bool>> whereLambda = null) where T : class, new();

image

public async Task<List<TEntity>> Query(Expression<Func<TEntity, bool>> whereExpression = null)
{
    await Console.Out.WriteLineAsync(Db.GetHashCode().ToString());
    return await _db.Queryable<TEntity>().WhereIF(whereExpression != null, whereExpression).ToListAsync();
}

public async Task<List<TResult>> QueryMuch<T, T2, T3, TResult>(
Expression<Func<T, T2, T3, object[]>> joinExpression,
Expression<Func<T, T2, T3, TResult>> selectExpression,
Expression<Func<T, T2, T3, bool>> whereLambda = null) where T : class, new()
{
    if (whereLambda == null)
    {
        return await _db.Queryable(joinExpression).Select(selectExpression).ToListAsync();
    }

    return await _db.Queryable(joinExpression).Where(whereLambda).Select(selectExpression).ToListAsync();
}

8.配置IUserRepository和UserRepository

public interface IUserRepository
{
    Task<List<SysUserInfo>> Query();
    Task<List<RoleModulePermission>> RoleModuleMaps();
}

public class UserRepository : BaseRepository<SysUserInfo>, IUserRepository
{
    public UserRepository(IUnitOfWorkManage unitOfWorkManage) : base(unitOfWorkManage)
    {
    }

    public async Task<List<SysUserInfo>> Query()
    {
        await Task.CompletedTask;
        var data = "[{\"Id\": 18,\"Name\":\"laozhang\"}]";
        return JsonConvert.DeserializeObject<List<SysUserInfo>>(data) ?? new List<SysUserInfo>();
    }

    public async Task<List<RoleModulePermission>> RoleModuleMaps()
    {
        return await QueryMuch<RoleModulePermission, Modules, Role, RoleModulePermission>(
            (rmp, m, r) => new object[] {
                JoinType.Left, rmp.ModuleId == m.Id,
                JoinType.Left,  rmp.RoleId == r.Id
            },

            (rmp, m, r) => new RoleModulePermission()
            {
                Role = r,
                Module = m,
                IsDeleted = rmp.IsDeleted
            },

            (rmp, m, r) => rmp.IsDeleted == false && m.IsDeleted == false && r.IsDeleted == false
            );
    }

}

9.IBaseService和BaseService添加方法
image

/// <summary>
/// 根据表达式查询信息
/// </summary>
/// <param name="whereExpression"></param>
/// <returns></returns>
Task<List<TVo>> Query(Expression<Func<TEntity, bool>>? whereExpression = null);

image

/// <summary>
/// 根据表达式查询
/// </summary>
/// <param name="whereExpression"></param>
/// <returns></returns>
public async Task<List<TVo>> Query(Expression<Func<TEntity, bool>>? whereExpression = null)
{
    var entities = await _baseRepository.Query(whereExpression);
    Console.WriteLine($"_baseRepository 实例HashCode : {_baseRepository.GetHashCode()}");
    var llout = _mapper.Map<List<TVo>>(entities);
    return llout;
}

10.在IUserService继承IBaseService并添加方法,UserService实现方法
image

public interface IUserService : IBaseService<SysUserInfo, UserVo>
{
    Task<bool> TestTranPropagation();

    Task<string> GetUserRoleNameStr(string loginName, string loginPwd);
    Task<List<RoleModulePermission>> RoleModuleMaps();
}

11.配置JWTToken生成类

/// <summary>
/// JWTToken生成类
/// </summary>
public class JwtToken
{
    /// <summary>
    /// 获取基于JWT的Token
    /// </summary>
    /// <param name="claims">需要在登陆的时候配置</param>
    /// <param name="permissionRequirement">在startup中定义的参数</param>
    /// <returns></returns>
    public static TokenInfoViewModel BuildJwtToken(Claim[] claims, PermissionRequirement permissionRequirement)
    {
        var now = DateTime.Now;
        var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("sdfsdfsrty45634kkhllghtdgdfss345t678fs"));
        // 实例化JwtSecurityToken
        var jwt = new JwtSecurityToken(
            issuer: "Blog.Core",
            audience: "wr",
            claims: claims,
            notBefore: now,
            expires: DateTime.Now.AddSeconds(3600),
            signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
        );
        // 生成 Token
        var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

        //打包返回前台
        var responseJson = new TokenInfoViewModel
        {
            success = true,
            token = encodedJwt,
            expires_in = 3600,
            token_type = "Bearer"
        };
        return responseJson;
    }
}

12.重新配置【许可要求】

 /// <summary>
 /// 用户或角色或其他凭据实体,就像是订单详情一样
 /// 之前的名字是 Permission
 /// </summary>
 public class PermissionItem
 {
     /// <summary>
     /// 用户或角色或其他凭据名称
     /// </summary>
     public virtual string Role { get; set; }
     /// <summary>
     /// 请求Url
     /// </summary>
     public virtual string Url { get; set; }
 }

public class PermissionRequirement : IAuthorizationRequirement
{
    public List<PermissionItem> Permissions { get; set; } = new List<PermissionItem>();
}

public class PermissionRequirementHandler : AuthorizationHandler<PermissionRequirement>, IAuthorizationRequirement
{
    private readonly IHttpContextAccessor _accessor;
    private readonly IUserService _userService;

    public IAuthenticationSchemeProvider Schemes { get; set; }
    public PermissionRequirementHandler(IAuthenticationSchemeProvider schemes,
        IHttpContextAccessor accessor,
        IUserService userService)
    {
        Schemes = schemes;
        _accessor = accessor;
        _userService = userService;
    }

    public List<PermissionItem> Permissions { get; set; }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
    {
        var httpContext = _accessor.HttpContext;

        // 获取系统中所有的角色和菜单的关系集合
        if (!requirement.Permissions.Any())
        {
            var data = await _userService.RoleModuleMaps();
            var list = new List<PermissionItem>();
            list = (from item in data
                    where item.IsDeleted == false
                    orderby item.Id
                    select new PermissionItem
                    {
                        Url = item.Module?.LinkUrl,
                        Role = item.Role?.Name.ObjToString(),
                    }).ToList();

            requirement.Permissions = list;
        }

        if (httpContext != null)
        {
            var questUrl = httpContext.Request.Path.Value.ToLower();

            // 整体结构类似认证中间件UseAuthentication的逻辑,具体查看开源地址
            // https://github.com/dotnet/aspnetcore/blob/master/src/Security/Authentication/Core/src/AuthenticationMiddleware.cs
            httpContext.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
            {
                OriginalPath = httpContext.Request.Path,
                OriginalPathBase = httpContext.Request.PathBase
            });


            //判断请求是否拥有凭据,即有没有登录
            var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
            if (defaultAuthenticate != null)
            {
                var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);

                // 是否开启测试环境
                var isTestCurrent = AppSettings.app(new string[] { "AppSettings", "UseLoadTest" }).ObjToBool();

                //result?.Principal不为空即登录成功
                if (result?.Principal != null || isTestCurrent)
                {
                    if (!isTestCurrent) httpContext.User = result.Principal;

                    //应该要先校验用户的信息 再校验菜单权限相关的
                    SysUserInfo user = new();
                    // 判断token是否过期,过期则重新登录
                    var isExp = false;
                    // jwt
                    isExp = (httpContext.User.Claims.FirstOrDefault(s => s.Type == ClaimTypes.Expiration)
                            ?.Value) != null &&
                        DateTime.Parse(httpContext.User.Claims
                            .FirstOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) >= DateTime.Now;

                    if (!isExp)
                    {
                        context.Fail(new AuthorizationFailureReason(this, "授权已过期,请重新授权"));
                        return;
                    }

                    // 获取当前用户的角色信息
                    var currentUserRoles = new List<string>();
                    currentUserRoles = (from item in httpContext.User.Claims
                                        where item.Type == ClaimTypes.Role
                                        select item.Value).ToList();
                    if (!currentUserRoles.Any())
                    {
                        currentUserRoles = (from item in httpContext.User.Claims
                                            where item.Type == "role"
                                            select item.Value).ToList();
                    }

                    //超级管理员 默认拥有所有权限
                    //if (currentUserRoles.All(s => s != "SuperAdmin"))
                    {
                        var isMatchRole = false;
                        var permisssionRoles =
                            requirement.Permissions.Where(w => currentUserRoles.Contains(w.Role));
                        foreach (var item in permisssionRoles)
                        {
                            try
                            {
                                if (Regex.Match(questUrl, item.Url?.ObjToString().ToLower())?.Value == questUrl)
                                {
                                    isMatchRole = true;
                                    break;
                                }
                            }
                            catch (Exception)
                            {
                                // ignored
                            }
                        }

                        //验证权限
                        if (currentUserRoles.Count <= 0 || !isMatchRole)
                        {
                            context.Fail();
                            return;
                        }
                    }

                    context.Succeed(requirement);
                    return;
                }
            }

            if ((!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType))
            {
                context.Fail();
                return;
            }
        }
    }
}

12.在program.cs文件中提供单例注册【许可要求】
image

// 自定义授权需求
builder.Services.AddScoped<IAuthorizationHandler, PermissionRequirementHandler>();
// 注册许可要求
builder.Services.AddSingleton(new PermissionRequirement());

13.通过autofac注册IUserRepository和UserRepository服务
image

// 注册IUserRepository和UserRepository服务
builder.RegisterType(typeof(UserRepository)).As(typeof(IUserRepository)).InstancePerDependency();

14.配置LoginController控制器

/// <summary>
/// 登录管理
/// </summary>
[Produces("application/json")]
[Route("api/[controller]/[action]")]
public class LoginController : ControllerBase
{
    private readonly IUserService _userService;
    private readonly IBaseService<UserRole, UserRoleVo> _userRoleService;
    readonly PermissionRequirement _requirement;
    private readonly ILogger<LoginController> _logger;

    /// <summary>
    /// 构造函数注入
    /// </summary>
    /// <param name="sysUserInfoServices"></param>
    /// <param name="userRoleServices"></param>
    /// <param name="roleServices"></param>
    /// <param name="requirement"></param>
    /// <param name="roleModulePermissionServices"></param>
    /// <param name="logger"></param>
    public LoginController(
        IUserService userService,
        IBaseService<UserRole, UserRoleVo> userRoleService,
        PermissionRequirement requirement,
        ILogger<LoginController> logger)
    {
        _userService = userService;
        _userRoleService = userRoleService;
        _requirement = requirement;
        _logger = logger;
    }

    [HttpGet]
    public async Task<MessageModel<TokenInfoViewModel>> GetJwtToken3(string name = "", string pass = "")
    {
        string jwtStr = string.Empty;

        pass = MD5Encrypt32(pass);

        var user = await _userService.Query(d => d.LoginName == name && d.LoginPWD == pass && d.IsDeleted == false);
        if (user.Count > 0)
        {
            var userRoles = await _userService.GetUserRoleNameStr(name, pass);
            //如果是基于用户的授权策略,这里要添加用户;如果是基于角色的授权策略,这里要添加角色
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, name),
                new Claim(JwtRegisteredClaimNames.Jti, user.FirstOrDefault().Id.ToString()),
                new Claim("TenantId", user.FirstOrDefault().TenantId.ToString()),
                new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.DateToTimeStamp()),
                new Claim(ClaimTypes.Expiration,
                    DateTime.Now.AddSeconds(3600).ToString())
            };
            claims.AddRange(userRoles.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));

            var token = JwtToken.BuildJwtToken(claims.ToArray(), _requirement);
            return Success(token, "获取成功");
        }
        else
        {
            return Failed<TokenInfoViewModel>("认证失败");
        }
    }

    private MessageModel<T> Success<T>(T data, string msg = "成功")
    {
        return new MessageModel<T>()
        {
            success = true,
            msg = msg,
            response = data,
        };
    }
    private MessageModel<T> Failed<T>(string msg = "失败", int status = 500)
    {
        return new MessageModel<T>()
        {
            success = false,
            status = status,
            msg = msg,
            response = default,
        };
    }
    private string MD5Encrypt32(string password = "")
    {
        string pwd = string.Empty;
        try
        {
            if (!string.IsNullOrEmpty(password) && !string.IsNullOrWhiteSpace(password))
            {
                MD5 md5 = MD5.Create(); //实例化一个md5对像
                // 加密后是一个字节类型的数组,这里要注意编码UTF8/Unicode等的选择 
                byte[] s = md5.ComputeHash(Encoding.UTF8.GetBytes(password));
                // 通过使用循环,将字节类型的数组转换为字符串,此字符串是常规字符格式化所得
                foreach (var item in s)
                {
                    // 将得到的字符串使用十六进制类型格式。格式后的字符是小写的字母,如果使用大写(X)则格式后的字符是大写字符 
                    pwd = string.Concat(pwd, item.ToString("X2"));
                }
            }
        }
        catch
        {
            throw new Exception($"错误的 password 字符串:【{password}】");
        }
        return pwd;
    }
}

15.配置控制器UserController

/// <summary>
/// 用户管理
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
[Authorize("Permission")]
public class UserController : ControllerBase
{
    private readonly ILogger<UserController> _logger;

    public UserController(ILogger<UserController> logger)
    {
        _logger = logger;
    }

    // GET: api/User
    [HttpGet]
    public string Get(int page = 1, string key = "")
    {
        _logger.LogInformation(key, page);
        return "OK!!!";
    }


    // GET: api/User/5
    [HttpGet("{id}")]
    public string Get(string id)
    {
        _logger.LogError("test wrong");
        return "value";
    }
}
posted @ 2025-08-22 08:44  一切为了尚媛小姐  阅读(4)  评论(0)    收藏  举报