ABP vNext微服务架构详细教程——分布式权限框架(上)

2023-11-05 19:38

本文主要是介绍ABP vNext微服务架构详细教程——分布式权限框架(上),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1

简介

3e5764ada323a9ea07d18cdf184d98f9.png

ABP vNext框架本身提供了一套权限框架,其功能非常丰富,具体可参考官方文档:https://docs.abp.io/en/abp/latest/Authorization

0768681cd19340aa43ba9743ea22e945.gif

但是我们使用时会发现,对于正常的单体应用,ABP vNext框架提供的权限系统没有问题, 但是在微服务架构下,这种权限系统并不是非常的友好。

a58eca97ed79796317a83504dc349bee.gif

我希望我的权限系统可以满足以下要求:

每个聚合服务持有独立的权限集合

每个聚合服务可以独立声明、使用其接口访问所需的权限。

提供统一接口负责管理、存储所有服务权限并实现对角色的授权。

每个接口可以灵活组合使用一个或多个权限码。

权限框架使用尽量简单,减少额外编码量。

8934846192858daf5531d12bde980b57.gif

在ABP vNext框架基础上,重新编写了一套分布式权限框架,大体规则如下:

使用ABP vNext框架中提供的用户、角色模型不做改变,替代重新定义权限模型,重新定义权限的实体及相关服务接口。

在身份管理服务中,实现权限的统一管理、角色授权和权限认证。

在聚合服务中定义其具有的权限信息、权限关系并通过特性声明各接口所需要的权限。

在聚合服务启动时,自动将其权限信息注册到身份管理服务。

客户端访问聚合服务层服务时在聚合服务层中间件中验证当前用户是否具有该接口权限,验证过程需调用身份管理服务对应接口。 

796fc6bddb913ceedc406e955c9db6a3.gif

权限系统具体实现见下文。

2

身份认证服务

2be48d4482e970fa122386e05f90f3fa.gif

在之前的文章中我们已经搭建了身份认证服务的基础框架,这里我们直接在此基础上新增代码。

0063425d49ab05abe57474c708b9a62d.gif

在Demo.Identity.Domain项目中添加Permissions文件夹,并添加Entities子文件夹。在此文件夹下添加实体类SysPermission和RolePermissions如下:

using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Domain.Entities;namespace Demo.Identity.Permissions.Entities;/// <summary>
/// 权限实体类
/// </summary>
public class SysPermission : Entity<Guid>
{/// <summary>/// 服务名称/// </summary>[MaxLength(64)]public string ServiceName { get; set; }/// <summary>/// 权限编码/// </summary>[MaxLength(128)]public string Code { get; set; }/// <summary>/// 权限名称/// </summary>[MaxLength(64)]public string Name { get; set; }/// <summary>/// 上级权限ID/// </summary>[MaxLength(128)]public string ParentCode { get; set; }/// <summary>/// 判断两个权限是否相同/// </summary>/// <param name="obj"></param>/// <returns></returns>public override bool Equals(object? obj)
{return obj is SysPermission permission&& permission.ServiceName == ServiceName&& permission.Name == Name&& permission.Code == Code&& permission.ParentCode == ParentCode;}/// <summary>/// 设置ID的值/// </summary>/// <param name="id"></param>public void SetId(Guid id)
{Id = id;}
}
using System;
using Volo.Abp.Domain.Entities;namespace Demo.Identity.Permissions.Entities;/// <summary>
/// 角色权限对应关系
/// </summary>
public class RolePermissions : Entity<Guid>
{/// <summary>/// 角色编号/// </summary>public Guid RoleId { get; set; }/// <summary>/// 权限编号/// </summary>public Guid PermissionId { get; set; }
}

02f6bdec93f849b88bd8485abbdf1cd9.gif

将Demo.Identity.Application.Contracts项目中原有Permissions文件夹中所有类删除,并添加子文件夹Dto。在此文件夹下添加SysPermissionDto、PermissionTreeDto、SetRolePermissionsDto

类如下:

using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Application.Dtos;namespace Demo.Identity.Permissions.Dto;/// <summary>
/// 权限DTO
/// </summary>
public class SysPermissionDto:EntityDto<Guid>
{/// <summary>/// 服务名称/// </summary>[MaxLength(64)]public string ServiceName { get; set; }/// <summary>/// 权限编码/// </summary>[MaxLength(128)]public string Code { get; set; }/// <summary>/// 权限名称/// </summary>[MaxLength(64)]public string Name { get; set; }/// <summary>///     上级权限ID/// </summary>[MaxLength(128)]public string ParentCode { get; set; }
}
using System;
using System.Collections.Generic;
using Volo.Abp.Application.Dtos;namespace Demo.Identity.Permissions.Dto;/// <summary>
/// 权限树DTO
/// </summary>
public class PermissionTreeDto : EntityDto<Guid>
{/// <summary>/// 服务名称/// </summary>public string ServiceName { get; set; }/// <summary>/// 权限编码/// </summary>public string Code { get; set; }/// <summary>/// 权限名称/// </summary>public string Name { get; set; }/// <summary>/// 上级权限ID/// </summary>public string ParentCode { get; set; }/// <summary>/// 子权限/// </summary>public List<PermissionTreeDto> Children { get; set; }}
using System;
using System.Collections.Generic;namespace Demo.Identity.Permissions.Dto;/// <summary>
/// 设置角色权限DTO
/// </summary>
public class SetRolePermissionsDto
{/// <summary>/// 角色编号/// </summary>public Guid RoleId { get; set; }/// <summary>/// 权限ID列表/// </summary>public List<Guid> Permissions { get; set; }
}

282a8c294ce4f62ab02dd1a7d3dc66ba.gif

将Demo.Identity.Application.Contracts项目中Permissions文件夹下添加接口IRolePermissionsAppService如下:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Demo.Identity.Permissions.Dto;
using Volo.Abp.Application.Services;namespace Demo.Identity.Permissions;/// <summary>
///     角色管理应用服务接口
/// </summary>
public interface IRolePermissionsAppService: IApplicationService
{/// <summary>/// 获取角色所有权限/// </summary>/// <param name="roleId">角色ID</param>/// <returns></returns>Task<List<PermissionTreeDto>> GetPermission(Guid roleId);/// <summary>/// 设置角色权限/// </summary>/// <param name="dto">角色权限信息</param>/// <returns></returns>Task SetPermission(SetRolePermissionsDto dto);
}

33cb301ffb60cce4ba8bad696d3bd99c.gif

将Demo.Identity.Application.Contracts项目中Permissions文件夹下添加接口ISysPermissionAppService如下:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Demo.Identity.Permissions.Dto;
using Volo.Abp.Application.Services;namespace Demo.Identity.Permissions;/// <summary>
/// 权限管理应用服务接口 
/// </summary>
public interface ISysPermissionAppService:IApplicationService
{/// <summary>/// 按服务注册权限/// </summary>/// <param name="serviceName">服务名称</param>/// <param name="permissions">权限列表</param>/// <returns></returns>Task<bool> RegistPermission(string serviceName, List<SysPermissionDto> permissions);/// <summary>/// 按服务获取权限/// </summary>/// <param name="serviceName">服务名称</param>/// <returns>查询结果</returns>Task<List<SysPermissionDto>> GetPermissions(string serviceName);/// <summary>/// 获取完整权限树/// </summary>/// <param name="Permission"></param>/// <returns>查询结果</returns>Task<List<PermissionTreeDto>> GetPermissionTree();/// <summary>/// 获取用户权限码/// </summary>/// <param name="userId">用户编号</param>/// <returns>查询结果</returns>Task<List<string>> GetUserPermissionCode(Guid userId);
}

a842c0641ab27358777cf7627f48ef31.gif

在公共类库文件夹common中创建.Net6类库项目项目Demo.Core,用于存放通用类。

3278852ff639c4fe7f82826c9a3e471c.gif

这里我们在Demo.Core中添加文件夹CommonExtension用于存放通用扩展,添加EnumExtensions和ListExtensions类如下:

namespace Demo.Core.CommonExtension;/// <summary>
/// 枚举扩展类
/// </summary>
public static class EnumExtensions
{/// <summary>/// 获取描述特性/// </summary>/// <param name="enumValue">枚举值</param>/// <returns></returns>public static string GetDescription(this Enum enumValue){string value = enumValue.ToString();FieldInfo field = enumValue.GetType().GetField(value);object[] objs = field.GetCustomAttributes(typeof(DescriptionAttribute), false);  //获取描述属性if (objs == null || objs.Length == 0)  //当描述属性没有时,直接返回名称return value;DescriptionAttribute descriptionAttribute = (DescriptionAttribute)objs[0];return descriptionAttribute.Description;}
}
namespace Demo.Core.CommonExtension;public static class ListExtensions
{/// <summary>/// 集合去重/// </summary>/// <param name="lst">目标集合</param>/// <param name="keySelector">去重关键字</param>/// <typeparam name="T">集合元素类型</typeparam>/// <typeparam name="TKey">去重关键字数据类型</typeparam>/// <returns>去重结果</returns>public static List<T> Distinct<T,TKey>(this List<T> lst,Func<T, TKey> keySelector){List<T> result = new List<T>();HashSet<TKey> set = new HashSet<TKey>();foreach (var item in lst){var key = keySelector(item);if (!set.Contains(key)){set.Add(key);result.Add(item);}}return result;}
}

84a26677b92c9a80074d86c7de486d64.gif

在Demo.Core项目中添加文件夹CommonFunction用于存放通用方法,这里我们添加用于集合比对的ListCompare类如下:

using VI.Core.CommonExtension;namespace VI.Core.CommonFunction;/// <summary>
/// 集合比对
/// </summary>
public class ListCompare
{/** 调用实例:*  MutiCompare<Permission, string>(lst1, lst2, x => x.Code, (obj, isnew) =>*  {*      if (isnew)*      {*          Console.WriteLine($"新增项{obj.Id}");*      }*      else*      {*          Console.WriteLine($"已存在{obj.Id}");*      }*  }, out var lstNeedRemove);*//// <summary>/// 对比源集合和目标集合,处理已有项和新增项,并找出需要删除的项/// </summary>/// <param name="lstSource">源集合</param>/// <param name="lstDestination">目标集合</param>/// <param name="keySelector">集合比对关键字</param>/// <param name="action">新增或已有项处理方法,参数:(数据项, 是否是新增)</param>/// <param name="needRemove">需要删除的数据集</param>/// <typeparam name="TObject">集合对象数据类型</typeparam>/// <typeparam name="TKey">对比关键字数据类型</typeparam>public static void MutiCompare<TObject,TKey>(List<TObject> lstDestination,List<TObject> lstSource,Func<TObject, TKey> keySelector,Action<TObject, bool> action, out Dictionary<TKey, TObject> needRemove){//目标集合去重lstDestination.Distinct(keySelector);//将源集合存入字典,提高查询效率needRemove = new Dictionary<TKey, TObject>();foreach (var item in lstSource){needRemove.Add(keySelector(item),item);}//遍历目标集合,区分新增项及已有项//在字典中排除目标集合中的项,剩余的即为源集合中需删除的项foreach (var item in lstDestination){if (needRemove.ContainsKey(keySelector(item))){action(item, false);needRemove.Remove(keySelector(item));}else{action(item, true);}}}
}

774eda5f91fa5f9f23e62b8ce752335d.gif

在Demo.Identity.Application项目中添加Permissions文件夹。

316da11e90fa1461b08d804effc01b68.gif

在Demo.Identity.Application项目Permissions文件夹中添加PermissionProfileExtensions类用于定义对象映射关系如下:

using Demo.Identity.Permissions.Dto;
using Demo.Identity.Permissions.Entities;namespace Demo.Identity.Permissions;public static class PermissionProfileExtensions
{/// <summary>/// 创建权限领域相关实体映射关系/// </summary>/// <param name="profile"></param>public static void CreatePermissionsMap(this IdentityApplicationAutoMapperProfile profile){profile.CreateMap<SysPermission, PermissionTreeDto>();profile.CreateMap<SysPermission,SysPermissionDto>();profile.CreateMap<SysPermissionDto,SysPermission>();}
}

8bc6c8668e75a02b4f5c412b9542eba5.gif

在Demo.Identity.Application项目IdentityApplicationAutoMapperProfile类的IdentityApplicationAutoMapperProfile方法中添加如下代码:

this.CreatePermissionsMap();

75171c85c6a87c6f43a105900b24fdff.gif

在Demo.Identity.Application项目Permissions文件夹中添加PermissionTreeBuilder类,定义构造权限树形结构的通用方法如下:

using System.Collections.Generic;
using System.Linq;
using Demo.Identity.Permissions.Dto;namespace Demo.Identity.Permissions;/// <summary>
/// 权限建树帮助类
/// </summary>
public static class PermissionTreeBuilder
{/// <summary>/// 建立树形结构/// </summary>/// <param name="lst"></param>/// <returns></returns>public static List<PermissionTreeDto> Build(List<PermissionTreeDto> lst){var result = lst.ToList();for (var i = 0; i < result.Count; i++){if (result[i].ParentCode == null){continue;}foreach (var item in lst){item.Children ??= new List<PermissionTreeDto>();if (item.Code != result[i].ParentCode){continue;}item.Children.Add(result[i]);result.RemoveAt(i);i--;break;}}return result;}
}

030c797a2c1931482430a4de28c4d644.gif

之后我们在Demo.Identity.Application项目Permissions文件夹中添加权限管理实现类SysPermissionAppService如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Demo.Core.CommonFunction;
using Demo.Identity.Permissions.Dto;
using Demo.Identity.Permissions.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Identity;
using Demo.Core.CommonExtension;namespace Demo.Identity.Permissions
{/// <summary>/// 权限管理应用服务/// </summary>public class SysPermissionAppService : IdentityAppService, ISysPermissionAppService{#region 初始化private readonly IRepository<RolePermissions> _rolePermissionsRepository;private readonly IRepository<SysPermission> _sysPermissionsRepository;private readonly IRepository<IdentityUserRole> _userRolesRepository;public SysPermissionAppService(IRepository<RolePermissions> rolePermissionsRepository,IRepository<SysPermission> sysPermissionsRepository,IRepository<IdentityUserRole> userRolesRepository
){_rolePermissionsRepository = rolePermissionsRepository;_sysPermissionsRepository = sysPermissionsRepository;_userRolesRepository = userRolesRepository;}#endregion#region 按服务注册权限/// <summary>/// 按服务注册权限/// </summary>/// <param name="serviceName">服务名称</param>/// <param name="permissions">权限列表</param>/// <returns></returns>public async Task<bool> RegistPermission(string serviceName, List<SysPermissionDto> permissions){//根据服务名称查询现有权限var entities = await AsyncExecuter.ToListAsync( (await _sysPermissionsRepository.GetQueryableAsync()).Where(c => c.ServiceName == serviceName));var lst = ObjectMapper.Map<List<SysPermissionDto>, List<SysPermission>>(permissions);ListCompare.MutiCompare(lst, entities, x => x.Code, async (entity, isNew) =>{if (isNew){//新增await _sysPermissionsRepository.InsertAsync(entity);}else{//修改var tmp = lst.FirstOrDefault(x => x.Code == entity.Code);//调用权限判断方法,如果code和name相同就不进行添加if (!entity.Equals(tmp)&&tmp!=null){entity.SetId(tmp.Id);await _sysPermissionsRepository.UpdateAsync(entity);}}}, out var needRemove);foreach (var item in needRemove){//删除多余项await _sysPermissionsRepository.DeleteAsync(item.Value);}return true;}#endregion#region 按服务获取权限/// <summary>///     按服务获取权限/// </summary>/// <param name="serviceName">服务名称</param>/// <returns>查询结果</returns>public async Task<List<SysPermissionDto>> GetPermissions(string serviceName){var query = (await _sysPermissionsRepository.GetQueryableAsync()).Where(x => x.ServiceName == serviceName);//使用AsyncExecuter进行异步查询var lst = await AsyncExecuter.ToListAsync(query);//映射实体类到dtoreturn ObjectMapper.Map<List<SysPermission>, List<SysPermissionDto>>(lst);}#endregion#region 获取完整权限树/// <summary>/// 获取完整权限树/// </summary>/// <returns>查询结果</returns>public async Task<List<PermissionTreeDto>> GetPermissionTree(){var per = await _sysPermissionsRepository.ToListAsync();var lst = ObjectMapper.Map<List<SysPermission>, List<PermissionTreeDto>>(per);return PermissionTreeBuilder.Build(lst);}#endregion#region 获取用户权限码/// <summary>/// 获取用户权限码/// </summary>/// <param name="userId">用户编号</param>/// <returns>查询结果</returns>public async Task<List<string>> GetUserPermissionCode(Guid userId){var query = from user in (await _userRolesRepository.GetQueryableAsync()).Where(c => c.UserId == userId)join rp in (await _rolePermissionsRepository.GetQueryableAsync()) on user.RoleId equals rp.RoleIdjoin pe in (await _sysPermissionsRepository.GetQueryableAsync()) on rp.PermissionId equals pe.Idselect pe.Code;var permission = await AsyncExecuter.ToListAsync(query);return permission.Distinct(x=>x);}#endregion}
}

2e9636e35cd687e69a7bda0f414e2491.gif

添加角色权限关系管理实现类RolePermissionsAppService如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Demo.Identity.Permissions.Dto;
using Demo.Identity.Permissions.Entities;
using Volo.Abp.Domain.Repositories;namespace Demo.Identity.Permissions
{/// <summary>/// 角色管理应用服务/// </summary>public class RolePermissionsAppService : IdentityAppService, IRolePermissionsAppService{#region 初始化private readonly IRepository<RolePermissions> _rolePermissionsRepository;private readonly IRepository<SysPermission> _sysPermissionsRepository;public RolePermissionsAppService(IRepository<RolePermissions> rolePermissionsRepository,IRepository<SysPermission> sysPermissionsRepository
){_rolePermissionsRepository = rolePermissionsRepository;_sysPermissionsRepository = sysPermissionsRepository;}#endregion#region 获取角色所有权限/// <summary>/// 获取角色所有权限/// </summary>/// <param name="roleId">角色ID</param>/// <returns></returns>public async Task<List<PermissionTreeDto>> GetPermission(Guid roleId){var query = from rp in (await _rolePermissionsRepository.GetQueryableAsync()).Where(x => x.RoleId == roleId)join permission in (await _sysPermissionsRepository.GetQueryableAsync())on rp.PermissionId equals permission.Idselect permission;var permissions = await AsyncExecuter.ToListAsync(query);var lst = ObjectMapper.Map<List<SysPermission>, List<PermissionTreeDto>>(permissions);return PermissionTreeBuilder.Build(lst);}#endregion#region 设置角色权限/// <summary>/// 设置角色权限/// </summary>/// <param name="roleId">橘色编号</param>/// <param name="permissions">权限编号</param>/// <returns></returns>public async Task SetPermission(SetRolePermissionsDto dto){await _rolePermissionsRepository.DeleteAsync(x => x.RoleId == dto.RoleId);foreach (var permissionId in dto.Permissions){RolePermissions entity = new RolePermissions(){PermissionId = permissionId,RoleId = dto.RoleId,};await _rolePermissionsRepository.InsertAsync(entity);}}#endregion}
}

533db24a7e27c1df73b6eb3a7547613b.gif

 在Demo.Identity.EntityFrameworkCore项目IdentityDbContext类中加入以下属性:

public DbSet<SysPermission> SysPermissions { get; set; }
public DbSet<RolePermissions> RolePermissions { get; set; }

541fee81fe0e6e01440cada0ca593859.gif

在Demo.Identity.EntityFrameworkCore项目目录下启动命令提示符,执行以下命令分别创建和执行数据迁移:

dotnet-ef migrations add AddPermissions
dotnet-ef database update

7b7b9ffcbbf1363fbedb091b3b45329d.gif

在Demo.Identity.EntityFrameworkCore项目IdentityEntityFrameworkCoreModule类ConfigureServices方法中找到 options.AddDefaultRepositories(includeAllEntities: true); ,在其后面加入以下代码:

options.AddDefaultRepository<IdentityUserRole>();

51cc46674ce16b0cac6aa979d588dcf6.png

完成后运行身份管理服务,可正常运行和访问各接口,则基础服务层修改完成。后续操作请看下一篇

f8e71b4940c5412ab101fd7014ee2fb3.png

end

781c6fa3e12d116dbb90390fd14e7a12.png

cc34818bd82fcfd1bb2808e7b6dbe677.png

5b994a5a44dd2ca3f1d7d13201560b2a.png

更多精彩

关注我获得

4d2710f8e7134bc22784242e2f96ca64.png

这篇关于ABP vNext微服务架构详细教程——分布式权限框架(上)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/352041

相关文章

Vue项目的甘特图组件之dhtmlx-gantt使用教程和实现效果展示(推荐)

《Vue项目的甘特图组件之dhtmlx-gantt使用教程和实现效果展示(推荐)》文章介绍了如何使用dhtmlx-gantt组件来实现公司的甘特图需求,并提供了一个简单的Vue组件示例,文章还分享了一... 目录一、首先 npm 安装插件二、创建一个vue组件三、业务页面内 引用自定义组件:四、dhtmlx

MySQL zip安装包配置教程

《MySQLzip安装包配置教程》这篇文章详细介绍了如何使用zip安装包在Windows11上安装MySQL8.0,包括下载、解压、配置环境变量、初始化数据库、安装服务以及更改密码等步骤,感兴趣的朋... 目录mysql zip安装包配置教程1、下载zip安装包:2、安装2.1 解压zip包到安装目录2.2

Java集合中的List超详细讲解

《Java集合中的List超详细讲解》本文详细介绍了Java集合框架中的List接口,包括其在集合中的位置、继承体系、常用操作和代码示例,以及不同实现类(如ArrayList、LinkedList和V... 目录一,List的继承体系二,List的常用操作及代码示例1,创建List实例2,增加元素3,访问元

springboot的调度服务与异步服务使用详解

《springboot的调度服务与异步服务使用详解》本文主要介绍了Java的ScheduledExecutorService接口和SpringBoot中如何使用调度线程池,包括核心参数、创建方式、自定... 目录1.调度服务1.1.JDK之ScheduledExecutorService1.2.spring

Java使用Tesseract-OCR实战教程

《Java使用Tesseract-OCR实战教程》本文介绍了如何在Java中使用Tesseract-OCR进行文本提取,包括Tesseract-OCR的安装、中文训练库的配置、依赖库的引入以及具体的代... 目录Java使用Tesseract-OCRTesseract-OCR安装配置中文训练库引入依赖代码实

SpringBoot整合easy-es的详细过程

《SpringBoot整合easy-es的详细过程》本文介绍了EasyES,一个基于Elasticsearch的ORM框架,旨在简化开发流程并提高效率,EasyES支持SpringBoot框架,并提供... 目录一、easy-es简介二、实现基于Spring Boot框架的应用程序代码1.添加相关依赖2.添

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Goland debug失效详细解决步骤(合集)

《Golanddebug失效详细解决步骤(合集)》今天用Goland开发时,打断点,以debug方式运行,发现程序并没有断住,程序跳过了断点,直接运行结束,网上搜寻了大量文章,最后得以解决,特此在这... 目录Bug:Goland debug失效详细解决步骤【合集】情况一:Go或Goland架构不对情况二: