需求描述
- 一个插件属于一个部门,一个部门存在多个插件
- 一个用户属于一个部门,一个部门存在多个用户
- 一个用户可以使用多个插件,一个插件可以给多个用户使用
- 用户是电脑的IP地址,实现制定某台电脑能用的效果
- 用户对插件的使用有时间上的限制
数据模型
我们系统的业务模块有:
- 部门模块
- 用户模块
- 插件模块
- 插件使用权限模块
- 插件使用记录模块
根据上述模块和需求的分析,我们得到如下模块之间关系图
数据表设计
根据上图,可以得出5个表,部门表(Department)、插件表(CADPlug)、成员表(User)、插件使用权限表(UserPlugAuthority)和插件使用记录表(UserLog)
部门表(Department)
- 继承EntityBase类,内含自增Id属性的设置
- Name属性:部门名字
- CADPlugList集合属性:是EFCore架构中附带的设置类,是插件多对1部门的集合类
- UserList集合属性:是EFCore架构中附带的设置类,是用户多对1部门的集合类
插件表(CADPlug)
- 继承EntityBase类,内含自增Id属性的设置
- Name属性:插件名字
- DepartmentId 和 Department 属性:EFCore架构中附带的设置类,是插件多对1部门的集合类,也是外键。
- UserPlugAuthorityList 集合属性:EFCore架构中附带的设置类,是插件使用权限类多对1插件的集合类
- UserList 集合属性:EFCore架构中附带的设置类,是插件类多对多用户的集合类
csharp
/// <summary>
/// 插件
/// </summary>
public class CADPlug: EntityBase
{
/// <summary>
/// 插件名字
/// </summary>
public string Name { get; set; }
/// <summary>
/// 插件所属部门ID
/// </summary>
public int DepartmentId { get; set; }
/// <summary>
/// 部门表
/// </summary>
public Department Department { get; set; }
/// <summary>
/// 插件使用权限中间表
/// </summary>
public ICollection<UserPlugAuthority> UserPlugAuthorityList { get; set; }
/// <summary>
/// 插件用户
/// </summary>
public ICollection<User> UserList { get; set; }
public CADPlug() { }
}
成员表(User)
- 继承Entity类,内置了Id、CreateTime、UpdateTime属性
- DepartmentId 和 Department 属性:EFCore架构中附带的设置类,是插件多对1部门的集合类,也是外键。
- UserPlugAuthorityList 集合属性:EFCore架构中附带的设置类,是插件使用权限类多对1用户的集合类
- CADPlugList 集合属性:EFCore架构中附带的设置类,是插件类多对多用户的集合类
csharp
/// <summary>
/// 用户表
/// </summary>
public class User:EntityBase,IEntityTypeBuilder<User>
{
public string IP { get; set; }
/// <summary>
/// 部门Id
/// </summary>
public int DepartmentId { get; set; }
/// <summary>
/// 部门
/// </summary>
public Department Department { get; set; }
/// <summary>
/// 多对多CAD插件表
/// </summary>
public ICollection<CADPlug> CADPlugList { get; set; }
/// <summary>
/// 用户权限使用中间表
/// </summary>
public ICollection<UserPlugAuthority> UserPlugAuthorityList { get; set; }
/// <summary>
/// 配置多对多的关系
/// </summary>
/// <param name="entityBuilder"></param>
/// <param name="dbContext"></param>
/// <param name="dbContextLocator"></param>
public void Configure(EntityTypeBuilder<User> entityBuilder, DbContext dbContext, Type dbContextLocator)
{
entityBuilder.HasMany(u => u.CADPlugList)
.WithMany(c => c.UserList)
.UsingEntity<UserPlugAuthority>(a => a.HasOne(b => b.CADPlug).WithMany(c => c.UserPlugAuthorityList).HasForeignKey(c => c.CADPlugId), a => a.HasOne(b => b.User).WithMany(u => u.UserPlugAuthorityList).HasForeignKey(c => c.UserId));
}
public User() { }
}
- IEntityTypeBuilder接口是对User类中多对多的关系进行配置。
插件使用权限表(UserPlugAuthority)
- 继承Entity类,内置了Id、CreateTime、UpdateTime属性
- UserId 和User 属性:EFCore架构中附带的设置类,是用户的外键。
- CADPlugId 和 CADPlug 属性:EFCore架构中附带的设置类,是插件的外键。
csharp
/// <summary>
/// 插件使用授权
/// </summary>
public class UserPlugAuthority : Entity
{
public UserPlugAuthority()
{
CreatedTime = DateTime.Now;
}
/// <summary>
/// 使用期限
/// </summary>
public DateTime UserDataTime { get; set; }
/// <summary>
/// 用户Id
/// </summary>
public int UserId { get; set; }
/// <summary>
/// 用户主表
/// </summary>
public User User { get; set; }
/// <summary>
/// 插件Id
/// </summary>
public int CADPlugId { get; set; }
/// <summary>
/// 插件信息
/// </summary>
public CADPlug CADPlug { get; set; }
}
插件使用记录表(UserLog)
- 这个类是用来记录插件使用的情况,不和其他表有主外键的关系。(这里没有设置验证用户登录的机制,单纯的记录)
- UserIp 属性:使用者电脑的IP
- CADPlugName 属性:使用者的插件名字
- PlugFunctiontName 属性:使用者插件的方法名
csharp
/// <summary>
/// 插件使用记录
/// </summary>
public class UserLog : Entity
{
public UserLog()
{
CreatedTime = DateTime.Now;
}
/// <summary>
/// 用户IP
/// </summary>
public string UserIp { get; set; }
/// <summary>
/// 插件方法名
/// </summary>
public string PlugFunctiontName { get; set; }
/// <summary>
/// 插件名
/// </summary>
public string CADPlugName { get; set; }
}
数据库初始化
- 在RightsManagementSystems.EntityFramework.Core项目中添加数据库上下文。
csharp
public class RightsManagementDbContext : AppDbContext<RightsManagementDbContext>
{
public RightsManagementDbContext(DbContextOptions<RightsManagementDbContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}
}
- 修改RightsManagementSystems.EntityFramework.Core项目中Startup类
csharp
public class Startup : AppStartup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDatabaseAccessor(options =>
{
string dbValue = App.Configuration["DataProvider"];
string connString = "";
string connDbProvider = "";
switch (dbValue)
{
case "Sqlite":
connString = App.Configuration["ConnectionString:SqliteConnection"];
connDbProvider = DbProvider.Sqlite;
break;
case "MySql":
connString = App.Configuration["ConnectionString:MySqlConnection"];
connDbProvider = $"{DbProvider.MySql}@8.0.22";
break;
}
options.AddDbPool<RightsManagementDbContext>(connDbProvider, connectionMetadata:connString);
}, "RightsManagementSystems.Database.Migrations");
}
}
- 会遇到多种数据库的使用配置,这里加了一个对配置的判断。
因此我们要去修改RightsManagementSystems.Web.Entry项目中appsettings.json文件
csharp
{
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Information"
}
},
"ConnectionString": {
"SqliteConnection": "Data Source = ./RightsManagementSystem.db",
"MySqlConnection": ""
},
"DataProvider": "Sqlite",
}
}
- 完成了数据库的配置后,需要去RightsManagementSystems.Database.Migrations 项目中做数据库的数据迁移(Furion操作步骤)
- 示例使用的是Sqlite数据库,迁移后可以直接看到数据库文件
接口设计
根据上诉模块,分别实现对应的接口类
- 部门接口
- 用户接口
- 插件接口
- 插件使用权限接口
- 插件使用记录接口
下面展示接口仅实现部门接口的类使用,其余可以去资源免费下载
部门接口
WebAPI类
csharp
/// <summary>
/// 部门操作类
/// </summary>
[DynamicApiController]
public class DepartmentAppService
{
private readonly IDepartmentService _departmentService;
public DepartmentAppService(IDepartmentService departmentService)
{
this._departmentService = departmentService;
}
/// <summary>
/// 新建部门
/// </summary>
/// <param name="departmentCreateDto"></param>
/// <returns></returns>
public async Task<IActionResult> PostDepartmentCreate([FromBody] DepartmentCreateDto departmentCreateDto) => await _departmentService.DepartmentCreateAsync(departmentCreateDto);
/// <summary>
/// 修改部门名字
/// </summary>
/// <param name="departmentId"></param>
/// <param name="departmentUpdateDto"></param>
/// <returns></returns>
public async Task<IActionResult> PutDepartmentUpdate(int departmentId,[FromBody]DepartmentUpdateDto departmentUpdateDto)=>await _departmentService.DepartmentUpdateAsync(departmentId, departmentUpdateDto);
/// <summary>
/// 删除部门
/// </summary>
/// <param name="departmentId"></param>
/// <returns></returns>
[HttpDelete]
public async Task<IActionResult> DepartmentDelete(int departmentId) => await _departmentService.DepartmentDeleteAsync(departmentId);
/// <summary>
/// 取得一个部门
/// </summary>
/// <param name="departmentId"></param>
/// <returns></returns>
public async Task<IActionResult> GetDepartment(int departmentId) => await _departmentService.DepartmentGetAsync(departmentId);
/// <summary>
/// 得到所有的部门信息
/// </summary>
/// <returns></returns>
public async Task<IActionResult> GetAllDepartment() => await _departmentService.DepartmentGetAllAsync();
}
通过IOC容器反向代理得到IDepartmentService类
部门api接口服务接口
部门api接口服务接口实现类
csharp
public class DepartmentService : ITransient, IDepartmentService
{
private readonly IRepository<Department> _departmentRepository;
public DepartmentService(IRepository<Department> departmentRepository)
{
this._departmentRepository = departmentRepository;
}
public async Task<IActionResult> DepartmentCreateAsync(DepartmentCreateDto departmentCreateDto)
{
try
{
if (departmentCreateDto == null) return ApiResponse.Error("null");
if (string.IsNullOrEmpty(departmentCreateDto.Name)) return ApiResponse.Error("部门名字为null");
Department department = new Department() { Name = departmentCreateDto.Name };
await _departmentRepository.InsertNowAsync(department);
return ApiResponse.OK("部门添加成功");
}
catch (Exception ex)
{
return ApiResponse.Error(ex.Message);
}
}
public async Task<IActionResult> DepartmentDeleteAsync(int departmentId)
{
try
{
if (departmentId < 0) return ApiResponse.Error("部门Id不存在");
await _departmentRepository.DeleteNowAsync(departmentId);
return ApiResponse.OK("删除成功");
}
catch (Exception ex)
{
return ApiResponse.Error(ex.Message);
}
}
public async Task<IActionResult> DepartmentGetAsync(int departmentId)
{
if (departmentId < 0) return ApiResponse.Error("部门Id不存在");
Department department = await _departmentRepository.Include(x => x.CADPlugList).Include(x => x.UserList).FirstOrDefaultAsync(x=>x.Id == departmentId);
return ApiResponse.OK(department);
}
public async Task<IActionResult> DepartmentGetAllAsync()
{
List<Department> allDepartmentList = await _departmentRepository.Include(x=>x.CADPlugList).Include(x=>x.UserList).AsQueryable().ToListAsync();
return ApiResponse.OK(allDepartmentList);
}
public async Task<IActionResult> DepartmentUpdateAsync(int departmentId, DepartmentUpdateDto departmentUpdateDto)
{
try
{
if (departmentUpdateDto == null) return ApiResponse.Error("null");
Department updateDepartment = await _departmentRepository.FindOrDefaultAsync(departmentId);
if (updateDepartment == null) return ApiResponse.Error("找不到部门");
if (await _departmentRepository.FirstOrDefaultAsync(x => x.Name.Equals(departmentUpdateDto.Name)) != null) return ApiResponse.Error("修改的名字已经存在");
if (!string.IsNullOrEmpty(departmentUpdateDto.Name)) updateDepartment.Name = departmentUpdateDto.Name;
var updateResult = await _departmentRepository.UpdateNowAsync(updateDepartment);
return ApiResponse.OK(updateResult.Entity);
}
catch (Exception ex)
{
return ApiResponse.Error(ex.Message);
}
}
}
- [DynamicApiController]是Furion动态WebAPI注入的其中一种方式
- ITransient是Furion框架的服务生存期接口,一共有三个接口
- ITransient:对应暂时/瞬时作用域服务生存期
- IScoped:对应请求作用域服务生存期
- ISingleton:对应单例作用域服务生存期
- IRepository < Department >是Furion框架内置的仓储类接口,可以实现对数据库中的数据进行操作,同时封装了很多对数据库使用的方法,详细可以去看文档(API文档)
- ApiResponse的静态方法是自己定义的一个返回值类,大家也可以自定义
运行查看swagger
需要注意的问题
- 在写业务代码时使用到Include在返回数据的时候出现Json文件套娃访问的问题,需要在RightsManagementSystems.Web.CoreStartup.cs类的ConfigureServices方法中添加以下代码
csharp
services.AddControllers().AddInjectWithUnifyResult().AddJsonOptions(options =>
{
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});