基于ASP.NET ZERO,开发SaaS版供应链管理系统

这篇具有很好参考价值的文章主要介绍了基于ASP.NET ZERO,开发SaaS版供应链管理系统。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

2018年下半年,公司决定开发一款SaaS版行业供应链管理系统,经过选型,确定采用ABP(ASP.NET Boilerplate)框架。为了加快开发效率,购买了商业版的 ASP.NET ZERO(以下简称ZERO),选择ASP.NET Core + Angular的SPA框架进行系统开发(ABP.IO届时刚刚起步,还很不成熟,因此没有选用)。

关于ABP与ZERO,园子里已经有诸多介绍,因此不再赘述。本文侧重介绍我们基于ZERO框架开发系统过程中进行的一些优化、调整、扩展部分的内容,方便有需要的园友们了解或者参考。

系统架构

系统在2020年7月发布上线(部署在阿里云上),目前有超过500家企业/团队/个人注册体验,感兴趣的园友可以在此系统的着陆网站 scm.plus 注册一个免费账号体验一下,欢迎大家的批评与建议。

基于ASP.NET ZERO,开发SaaS版供应链管理系统

ZERO框架总体上来说还是不错的,可以快速的上手,集成的通用功能(版本、租户、角色、用户、设置等)初期都可以直接使用,但还达不到直接发布使用的水准,需要经过诸多的优化调整扩展后才能发布上线。

A 后端(ASP.NET Core)部分

0、移除不需要的功能:Chat、SignalR、DynamicProperty、GraphQL、IdentityServer4。

基于系统功能定位,移除的这些不需要的功能,使系统尽可能的精简。

1、Migrations内移除Designer.cs。

在我们的开发环境内,经过测试与验证,使用mysql数据库时候,可以安全移除add-migration时候生成的庞大的Designer.cs文件。移除Designer.cs文件时候,需要把该文件内的DbContext与Migration声明语句移到对应的migration.cs文件内:文章来源地址https://www.toymoban.com/news/detail-695050.html

[DbContext(typeof(SCMDbContext))]
[Migration("20230811015119_Upgraded_To_Abp_8_3")]
public partial class Upgraded_To_Abp_8_3 : Migration
{
   //...
}

2、替换必要的功能包,确保系统后端可以部署到linux环境:

  • 使用SkiaSharp替换System.Drawing.Common;
  • 使用EPPlus替换NPOI。

3、停用系统默认的外部登录( Facebook、Google、Microsoft、Twitter等),添加微信扫码与小程序登录。

4、停用系统默认的支付选项( Paypal、Stripe等),添加支付宝(Alipay)支付。

5、Excel文件上传,ZERO默认没有实现,需要自行添加Excel文件的上传与导入功能:

  • Excel文件上传后先缓存该文件;
  • 创建一个后台Job(HangFire)执行Excel文件的读取、处理等;
  • Job发送执行后的结果(消息通知)。
[HttpPost]
[AbpMvcAuthorize(AppPermissions.Pages_Txxxs_Excel_Import)]
public async Task<JsonResult> ImportFromExcel()
{
    try
    {
        var jobArgs = await DoImportFromExcelJobArgs(AbpSession.ToUserIdentifier());

        var queueState = new EnqueuedState(GetJobQueueName());
        IBackgroundJobClient hangFireClient = new BackgroundJobClient();
        hangFireClient.Create<ImportTxxxsToExcelJob>(x => x.ExecuteAsync(jobArgs), queueState);

        return Json(new AjaxResponse(new { }));
    }
    catch (Exception ex)
    {
        return Json(new AjaxResponse(new ErrorInfo(ex.Message)));
    }
}

6、图片与文件上传存储,ZERO的默认实现是保存上传的图片文件到数据库内,需要改造存储到OSS中:

  • 使用MD5哈希前缀,生成OSS文件对象的名称(含path),提高OSS并发性能:
private static string GetOssObjName(int? tenantId, Guid id, bool isThumbnail)
{
    string tid = (tenantId ?? 0).ToString();
    string ext = isThumbnail ? "thu" : "ori"; //thu - 缩略图、ori - 原图/原文件
    string hashStr = BitConverter.ToString(MD5.HashData(Encoding.UTF8.GetBytes(tid)), 0).Replace("-", string.Empty).ToLower();

    return $"{hashStr[..4]}/{tid}/{id}.{ext}";
}
  • 若OSS未启用或者上传失败,则直接存储到数据库中:
public async Task SaveAsync(BinaryObject file)
{
    if (file?.Bytes == null) { return; }

    //1、OSS上传,成功后直接返回
    if (OssPutObject(file.TenantId, file.Id, file.Bytes, isThumbnail: false)) { return; } 

    //2、若OSS未启用或者上传失败,则直接上传到数据库中
    await _binaryObjectRepository.InsertAsync(file);
}
  • 获取时候遵循一样的逻辑:若OSS未启用或者获取不到,则直接自数据库中获取;自数据库获取成功后要同步数据库中记录到OSS中。

7、Webhook功能,需要改造支持推送数据到第三方接口,如:企业微信群、钉钉群、聚水潭API等:

  • 重写WebhookManager的SignWebhookRequest方法;
  • 重写DefaultWebhookSender的CreateWebhookRequestMessage、AddAdditionalHeaders、SendHttpRequest方法;
  • 缓存Webhook Subscription:
private SCMWebhookCacheItem SetAndGetCache(int? tenantId, string keyName = "SubscriptionCount")
{
   int tid = tenantId ?? 0; var cacheKey = $"{keyName}-{tid}";

   return _cacheManager.GetSCMWebhookCache().Get(cacheKey, () =>
   {
        int count = 0;
        var names = new Dictionary<string, List<WebhookSubscription>>();

        UnitOfWorkManager.WithUnitOfWork(() =>
        {
            using (UnitOfWorkManager.Current.SetTenantId(tenantId))
            {
                if (_featureChecker.IsEnabled(tid, "SCM.H"))            //Feature 核查
                {
                    var items = _webhookSubscriptionRepository.GetAllList(e => e.TenantId == tenantId && e.IsActive == true);
                    count = items.Count;

                    foreach (var item in items)
                    {
                        if (string.IsNullOrWhiteSpace(item.Webhooks)) { continue; }
                        var whNames = JsonHelper.DeserializeObject<string[]>(item.Webhooks); if (whNames == null) { continue; }
                        foreach (string whName in whNames)
                        {
                            if (names.ContainsKey(whName))
                            {
                                names[whName].Add(item.ToWebhookSubscription());
                            }
                            else
                            {
                                names.Add(whName, new List<WebhookSubscription> { item.ToWebhookSubscription() });
                            }
                        }
                    }
                }
            }
        });

        return new SCMWebhookCacheItem(count, names);
    });
}

8、在WebHostModule中设定只有一台Server执行后台Work,避免多台Server重复执行:

public override void PostInitialize()
{
    //...

    string defaultEndsWith = _appConfiguration["Job:DefaultEndsWith"];
    if (string.IsNullOrWhiteSpace(defaultEndsWith)) { defaultEndsWith = "01"; }
    if (AppVersionHelper.MachineName.EndsWith(defaultEndsWith))
    {
        var workManager = IocManager.Resolve<IBackgroundWorkerManager>();

        workManager.Add(IocManager.Resolve<SubscriptionExpirationCheckWorker>());
        workManager.Add(IocManager.Resolve<SubscriptionExpireEmailNotifierWorker>());
        workManager.Add(IocManager.Resolve<SubscriptionPaymentsCheckWorker>());
        workManager.Add(IocManager.Resolve<ExpiredAuditLogDeleterWorker>());
        workManager.Add(IocManager.Resolve<PasswordExpirationBackgroundWorker>());
    }

    //...
}

9、限流功能,ZERO默认没有实现,通过添加AspNetCoreRateLimit中间件集成限流功能:

  • 采用客户端ID(ClientRateLimiting)进行设置;
  • 重写RateLimitConfigurationRegisterResolvers方法,添加定制化的ClientIpHeaderResolveContributor:存在客户端ID则优先获取,反之获取客户端的IP:
    public class RateLimitConfigurationExtensions : RateLimitConfiguration  
    {
        //...
        public override void RegisterResolvers()
        {
            ClientResolvers.Add(new ClientIpHeaderResolveContributor(SCMConsts.TenantIdCookieName));
        }
    }

    public class ClientIpHeaderResolveContributor : IClientResolveContributor
    {
        private readonly string _headerName;

        public ClientIpHeaderResolveContributor(string headerName)
        {
            _headerName = headerName;     
        }

        public Task<string> ResolveClientAsync(HttpContext httpContext)
        {
            IPAddress clientIp = null;

            var headers = httpContext?.Request?.Headers;
            if (headers != null && headers.Count > 0)
            {
                if (headers.ContainsKey(_headerName))                               //0 scm_tid
                {
                    string clientId = headers[_headerName].ToString();
                    if (!string.IsNullOrWhiteSpace(clientId))
                    {
                        return Task.FromResult(clientId);
                    }
                }

                try
                {
                    if (headers.ContainsKey("X-Real-IP"))                           //1 X-Real-IP
                    {
                        clientIp = IpAddressUtil.ParseIp(headers["X-Real-IP"].ToString());
                    }
                    
                    if (clientIp == null && headers.ContainsKey("X-Forwarded-For")) //2 X-Forwarded-For
                    {
                        clientIp = IpAddressUtil.ParseIp(headers["X-Forwarded-For"].ToString());
                    }
                }
                catch {}

                clientIp ??= httpContext?.Connection?.RemoteIpAddress;             //3 RemoteIpAddress
            }

            return Task.FromResult(clientIp?.ToString());
        }
    }

B 前端(Angular)部分

0、类似后端,移除不需要的功能:Chat、SignalR、DynamicProperty等。

1、拆分精简service-proxies.ts文件:

  • ZERO使用NSwag生成前端的TypeScript代码文件service-proxies.ts,全部模块的都生成到一个文件内,导致该文件非常庞大,最终编译生成的main.js接近4MB;
  • 按系统执行层次,拆分service-proxies.ts为多个文件,精简其中的共用代码,调整module的调用、拆分、懒加载等,最终大幅度减少了main.js的大小(目前是587KB)。

2、优化表格组件primeng table,实现客户端表格使用状态的本地存储:表格列宽、列顺序、列显示隐藏、列固定、分页设定等。

3、实现客户端的卡片视图功能。

4、集成ng-lazyload-image,实现图片展示的懒加载。

5、集成ngx-markdown,实现markdown格式的在线帮助。

6、业务组件设置为独立组件,ChangeDetectionStrategy设置为OnPush:

@Component({
    changeDetection: ChangeDetectionStrategy.OnPush,
    templateUrl: './txxxs.component.html',
    standalone: true,
    imports: [...]
})
export class TxxxsComponent extends AppComponentBase {
    ...
    constructor(
        injector: Injector,
        changeDetector: ChangeDetectorRef,
    ) {
        super(injector);
        setInterval(() => { changeDetector.markForCheck(); }, AppConsts.ChangeDetectorMS);
    }
    ...
}

7、仪表盘升级为工作台,除了可以添加图表外,也可以添加业务组件(独立组件)。

8、路由直接链接业务组件,实现懒加载:

import { Route } from '@angular/router';
export default [
    { path: 'p120303/t12030301s', loadComponent: () => import('./t12030301s.component').then(c => c.T12030301sComponent), ... },
    { path: 'p120405/t12040501s', loadComponent: () => import('./t12040501s.component').then(c => c.T12040501sComponent), ... },
    { path: 'p120405/t12040502s', loadComponent: () => import('./t12040502s.component').then(c => c.T12040502sComponent), ... },
] as Route[];

9、通过webpackInclude,减少打包后的文件数量;使用webpackChunkName设定打包后的文件名:

function registerLocales(
    resolve: (value?: boolean | Promise<boolean>) => void,
    reject: any,
    spinnerService: NgxSpinnerService
) {
    if (shouldLoadLocale()) {
        let angularLocale = convertAbpLocaleToAngularLocale(abp.localization.currentLanguage.name);
        import(
            /* webpackInclude: /(en|en-GB|zh|zh-Hans|zh-Hant)\.mjs$/ */
            /* webpackChunkName: "angular-common-locales" */
            `/node_modules/@angular/common/locales/${angularLocale}.mjs`).then((module) => {
                registerLocaleData(module.default);
                resolve(true);
                spinnerService.hide();
            }, reject);
    } else {
        resolve(true);
        spinnerService.hide();
    }
}

C 小程序(Vue3)部分

后端部分已经实现小程序集成微信登录,后端输出的语言文本与API等小程序都可以直接调用,因此小程序的开发实现就相对比较容易,只需要实现必要的UI界面即可。

  • 小程序采用 uni-app(vue3) 框架进行开发,整体效率较高。
  • 有部分代码可以基于前端 Angular 的代码复制后稍加调整后即可使用。
  • 目前只输出了微信小程序,方便同企业微信群内的消息推送一体化集成。
  • 后端部分实现的Webhook功能,可以直接推送消息到企业微信群内,用户可以单击消息卡片,直接打开微信小程序内对应的页面,查看数据或者进行其他的维护操作。
  • 小程序中需要在onLaunch中进行路由守卫(登录拦截),以处理通过分享单独页面或者企业微信群内通过消息卡片直接打开小程序页面的权限核查。

总结

若没有优秀的工具框架支持,开发SaaS化系统并不是一件容易的事。基于ABP框架,使用ZERO工具,极大的降低了开发SaaS化系统的门槛,也促成了这套系统的实践与发布。

本文简要介绍了我们实现这套系统中的一些要点,供有需要的人了解参考,就算是抛砖引玉吧!

到了这里,关于基于ASP.NET ZERO,开发SaaS版供应链管理系统的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SCM供应链管理系统解决方案:实现供应可视化、管理信息化,成本可控化

    SCM供应链管理系统 是基于协同供应链的管理思想,加上配合各实体行业的多样化业务需求,让企业管理过程、操作流程和信息系统紧密配合,轻松做到业务各环节无缝链接,形成集齐物流、信息流、单证流、商流和资金流五流合一的供应链SCM管理系统。供应链围绕核心企业对

    2024年02月02日
    浏览(37)
  • 微信小程序的物料产品采购供应链管理系统

    本论文主要论述了如何使用小程序与java语言开发一个微信小程序的供应链管理系统,本系统将严格按照软件开发流程进行各个阶段的工作,后台采用mysql数据库,面向对象编程思想进行产品开发。在引言中,系统将论述微信小程序的供应链管理系统的当前背景以及系统开发的

    2024年02月21日
    浏览(33)
  • 数商云:SCM系统助力医疗器械企业紧跟时代变局 搭建供应链管理平台

    医疗器械产业是生物工程、电子信息和医学影像等高新技术领域复合交叉的知识密集型、资金密集型产业。作为关系到人类生命健康的战略性新兴产业,在庞大而稳定的市场需求下,全球医疗器械产业长期以来一直保持着良好的增长势头,2020年全球医疗器械规模突破5000亿美

    2024年02月06日
    浏览(43)
  • 基于区块链技术改进供应链管理

    目录 一、简介 二、编写智能合约 三、部署智能合约 四、开发后端服务器 五、创建前端界面 六、集成物联网设备(可选) 在本文中,我们将探讨如何利用区块链技术改进供应链管理。我们将通过一个简化的示例项目,展示如何构建一个基于以太坊的供应链管理系统。我们将

    2023年04月21日
    浏览(41)
  • 基于区块链技术的供应链金融平台

    目录 一、项目背景 1 1.1 供应链+金融场景 1 1.2 功能需求分析 2 二、方案设计 2 2.1 存储设计 2 我们使用一个列表来保存所有的债权凭证 3 2.2 基本功能 3 企业注册(该方法仅限部署该合约的认证机构调用) 4 2.3 核心功能 9 2.4 前端实现 13 2.5后端实现 16 实现智能合约方法调用接口

    2023年04月10日
    浏览(30)
  • 基于区块链技术的智慧供应链创新应用

    随着区块链技术的不断成熟,智慧供应链得到了长足的发展,通过基于华为云的几个智慧供应链和物流行业的应用创新项目,诠释在实际应用场景中如何通过区块链和物流面临的调整和困难,达到优化流程、提高效率、降低成本的效果。 随着区块链技术的不断成熟,智慧供应

    2024年02月12日
    浏览(37)
  • 基于计算机视觉的物流和供应链管理

    作者:禅与计算机程序设计艺术 物流、供应链是一个非常重要的现代经济活动,许多企业都面临着如何提高效率,降低成本,改善供应链服务质量的问题。目前,人们已经在探索如何通过人工智能、物联网等新兴技术,实现自动化运输过程和管理。基于计算机视觉技术的物流

    2024年02月10日
    浏览(29)
  • 供应链 | 大数据报童模型:基于机器学习的实践见解

    论文解读:李欣 马玺渊 作者:Gah-Yi Ban, Cynthia Rudin 引用:Ban, Gah-Yi and Cynthia Rudin. The big data newsvendor: Practical insights from machine learning. Operations Research 67.1 (2019): 90-108. 文章链接:https://doi.org/10.1287/opre.2018.1757 文章研究了大规模数据驱动的报童问题(包括 p p p 个关于需求的特征和

    2024年02月12日
    浏览(27)
  • 开发安全、软件供应链安全及开源软件安全的概念差异

    开发安全、软件供应链安全和开源软件安全是在软件生命周期中不同阶段涉及到的安全概念,它们有着一些共同点,同时也存在一些显著的差异。以下是它们之间的比较总结: 范围 : 开发安全 :关注于整个软件开发过程中的安全性,包括代码编写、测试、部署等环节。 软

    2024年03月14日
    浏览(81)
  • 基于Java实现的区块链供应链金融系统平台设计

    中小微企业融资难、融资贵是长久以来我国金融发展过程中需要解决的问题。世界银行、中小企业金融论坛、国际金融公司联合发布的《中小微企业融资缺口:对新兴市场微型、小型和中型企业融资不足与机遇的评估》报告中表示,中国 40%的中小微企业存在信贷困难,或是完

    2024年01月17日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包