583

后端项目

概述

  • 项目后端,包含业务逻辑、数据访问,根据服务接口动态生成WebApi
  • 项目SDKMicrosoft.NET.Sdk.Web
<Project Sdk="Microsoft.NET.Sdk.Web">
    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
    </PropertyGroup>
    <!--引用包,如果不采用Auto模式,可以删除前1个包-->
    <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.8" />
        <PackageReference Include="Coravel" Version="5.0.4" />  <!--后台定时任务包,不用可删除-->
        <PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.8" />
        <!--其他数据库提供者
        <PackageReference Include="System.Data.OleDb" Version="8.0.0" />
        <PackageReference Include="System.Data.SqlClient" Version="4.8.6" />
        <PackageReference Include="MySqlConnector" Version="2.3.7" />
        <PackageReference Include="Npgsql" Version="8.0.3" />
        -->
        <PackageReference Include="Known.Cells" Version="1.0.2" /> <!--Excel操作包,不用可删除-->
        <PackageReference Include="Known.Core" Version="2.0.10" />
        <ProjectReference Include="..\Sample.Client\Sample.Client.csproj" />
    </ItemGroup>
    <!--资源文件-->
    <ItemGroup>
        <EmbeddedResource Include="Resources\SQLite.sql" />
    </ItemGroup>
</Project>

业务逻辑

  • 业务逻辑服务类继承ServiceBase类和实现对应的服务接口,放在Services文件夹中。
  • 通常一个模块的增删改查导只需要设计3个接口,分别是查询、删除和保存。
  • 分页查询和导出共用一个接口,由查询条件参数来区分是查询和导出。
  • 删除单条和多条共用一个删除接口。
  • 新增和编辑共用一个保存接口。
  • 导入分同步和异步两种模式,对于数据量大的,建议使用异步导入模式。
// 服务实现类
class TestService(Context context) : ServiceBase(context), ITestService
{
    // 分页查询和导出数据
    public Task<PagingResult<TbTest>> QueryTestsAsync(PagingCriteria criteria)
    {
        return Database.QueryPageAsync<TbTest>(criteria);
    }

    // 删除数据
    public async Task<Result> DeleteTestsAsync(List<TbTest> models)
    {
        if (models == null || models.Count == 0)
            return Result.Error(Language.SelectOneAtLeast);
        // 无附件删除逻辑
        return await Database.TransactionAsync(Language.Delete, async db =>
        {
            foreach (var item in models)
            {
                // 如果有申请流程,则删除流程
                //await Platform.DeleteFlowAsync(db, item.Id);
                await db.DeleteAsync(item);
            }
        });
        // 带附件删除逻辑
        /*var oldFiles = new List<string>();
        var result = await Database.TransactionAsync(Language.Delete, async db =>
        {
            foreach (var item in models)
            {
                await Platform.DeleteFilesAsync(db, item.Id, oldFiles); // 删除附件
                await db.DeleteAsync(item);
            }
        });
        if (result.IsValid)
            Platform.DeleteFiles(oldFiles); // 删除服务器磁盘附件
        return result;*/
    }

    // 保存表单数据
    public async Task<Result> SaveTestAsync(TbTest model)
    {
        var vr = model.Validate(Context);
        if (vr.IsValid)
        {
            // 此处验证数据库数据,例如:商品编码已存在
        }
        if (!vr.IsValid)
            return vr;

        return await Database.TransactionAsync(Language.Save, async db =>
        {
            if (entity.IsNew)
            {
                // 如果有申请流程,则创建流程
                //await Platform.CreateFlowAsync(db, ApplyFlow.GetBizInfo(entity));
            }
            await db.SaveAsync(model);
        }, model);
    }

    // 保存带附件的表单
    public async Task<Result> SaveTestAsync(UploadInfo<TbTest> info)
    {
        var model = info.Model;
        var vr = model.Validate(Context);
        if (vr.IsValid)
        {
            // 此处验证数据库数据,例如:商品编码已存在
        }
        if (!vr.IsValid)
            return vr;

        var user = CurrentUser;
        var bizType = "TestFiles";
        var bizFiles = info.Files.GetAttachFiles(user, nameof(TbTest.Files), bizType);
        return await Database.TransactionAsync(Language.Save, async db =>
        {
            await Platform.AddFilesAsync(db, bizFiles, model.Id, bizType);
            model.Files = $"{model.Id}_{bizType}";
            await db.SaveAsync(model);
        }, model);
    }
}

数据导入

  • 数据导入分同步和异步,导入处理逻辑通过导入类来实现。
  • 导入类继承ImportBase<T>类,放在Imports文件夹中。
  • 框架自动注入导入实现类,同步和异步导入时自动调用导入处理逻辑。

注意:导入类的名称命名规则,实体类名+Import,如:SysDictionaryImport。

class TbTestImport(ImportContext context) : ImportBase<TbTest>(context)
{
    // 初始化导入规范栏位
    public override void InitColumns()
    {
        AddColumn(c => c.Field1);
        AddColumn(c => c.Field2);
    }

    // 处理导入文件,实现导入逻辑
    public override async Task<Result> ExecuteAsync(SysFile file)
    {
        var models = new List<TbTest>();
        var result = ImportHelper.ReadFile<TbTest>(Context, file, item =>
        {
            var model = new TbTest
            {
                Field1 = item.GetValue(c => c.Field1),
                Field2 = item.GetValueT(c => c.Field2)
            };
            var vr = model.Validate(Context);
            if (!vr.IsValid)
                item.ErrorMessage = vr.Message;
            else
                models.Add(model);
        });

        if (!result.IsValid)
            return result;

        return await Database.TransactionAsync(Language.Import, async db =>
        {
            foreach (var item in models)
            {
                await db.SaveAsync(item);
            }
        });
    }
}

数据访问

  • 数据访问类为静态类,用于查询复杂SQL语句的数据源,放在Repositories文件夹中。
class TestRepository
{
    // 存在编码字段
    internal static async Task<bool> ExistsTestCodeAsync(Database db, TbTest model)
    {
        var sql = "select count(*) from TbTest where Id<>@Id and Code=@Code";
        return await db.ScalarAsync<int>(sql, new { model.Id, model.Code }) > 0;
    }
}

工作流

  • 内置工作流默认支持创建、提交、撤回、指派、审核(通过或退回)、重新申请。
  • 工作流继承BaseFlow类,可实现框架流程各环节操作前后调用业务系统数据。
  • 工作流在业务表单新增保存方法中创建。
  • 工作流提供日志插入和查询。
// 申请流程类
class ApplyFlow(Context context) : BaseFlow(context)
{
    private const string FlowCode = "ApplyFlow"; // 流程代码
    private const string FlowName = "申请流程";  // 流程名称

    // 获取流程信息
    internal static FlowBizInfo GetBizInfo(TbTest entity)
    {
        return new FlowBizInfo
        {
            FlowCode = FlowCode,
            FlowName = FlowName,
            BizId = entity.Id,
            BizName = entity.BizNo,
            BizUrl = "",
            BizStatus = FlowStatus.Save
        };
    }

    // 表单提交前
    public override async Task<Result> OnCommitingAsync(Database db, FlowFormInfo info)
    {
        // 表单提交前的校验
        var model = await db.QueryByIdAsync<TbTest>(info.BizId);
        var vr = model.Validate(Context);
        if (!vr.IsValid)
            return vr;

        return await base.OnCommitingAsync(db, info);
    }
    // 表单提交后
    public override Task OnCommitedAsync(Database db, FlowFormInfo info) => base.OnCommitedAsync(db, info);
    // 表单撤回前
    public override Task<Result> OnRevokingAsync(Database db, FlowFormInfo info) => base.OnRevokingAsync(db, info);
    // 表单撤回后
    public override Task OnRevokedAsync(Database db, FlowFormInfo info) => base.OnRevokedAsync(db, info);
    // 表单审核前
    public override Task<Result> OnVerifingAsync(Database db, FlowFormInfo info) => base.OnVerifingAsync(db, info);
    // 表单审核后
    public override Task OnVerifiedAsync(Database db, FlowFormInfo info)
    {
        // 表单审核通过后操作其他业务
        if (info.FlowStatus == FlowStatus.Over)
        {
        }
        return base.OnVerifiedAsync(db, info);
    }
    // 表单重启前
    public override Task<Result> OnRepeatingAsync(Database db, FlowFormInfo info) => base.OnRepeatingAsync(db, info);
    // 表单重启后
    public override Task OnRepeatedAsync(Database db, FlowFormInfo info) => base.OnRepeatedAsync(db, info);
    // 流程停止前
    public override Task<Result> OnStoppingAsync(Database db, FlowFormInfo info) => base.OnStoppingAsync(db, info);
    // 流程停止后
    public override Task OnStoppedAsync(Database db, FlowFormInfo info) => base.OnStoppedAsync(db, info);
}