首页 > 其他分享 >记一次对老服务改造

记一次对老服务改造

时间:2023-08-19 17:58:03浏览次数:50  
标签:一次 服务 改造 options auditInfo var new null public

关于现有老服务

使用的技术.net4.6.1+Nancy+Dapper,数据库是需要支持老版本MsSql + 新版本MySql、MsSql、DM,项目解决方案单层结构。看到这样的项目内心是...,好了再看内部实现,完全不想说话。

  if (connectionString.Contains("xx"))
  {
      //对应数据实现业务
  }
  else if (connectionString.Contains("xx"))
  {
      //对应数据实现业务
  }
  else
  {
      //对应数据实现业务
  }

每个接口都有一个如上的结构代码根据配置字符串判断数据库类型,每种数据库类型写一个实现,两个版本库结构三种不同数据库也就是一个业务要写四遍实现。
接口返回数据直接使用数据库表对象不管前端是否需要。吐血...血...
能用4.6.1版本创建项目算不上太老,这时间节点都支持.net standard2。完全不明白为什么要使用这样的技术栈组合去实现业务。

改造

项目只给了实现业务时间,但是在老项目上继续做下去已经难以为继。使用aspnetboilerplate进行改造(为啥不直接用vnext,因有些客户端使用prism自带了ioc容器,而老本abp是有IocManager方便融合把服务端中AppService也可直接在客户端中重用),考虑到几个改造点
1、使用aspnetboilerplate MultipleDbContext对不同数据库类型支持减少重复写不同数据库实现。
2、接口兼容现有返回数据结构减少客户端改动。
3、客户端构造http请求不统一,接收请求兼容form-data、queryString。
4、接口请求路径和现有服务保持一致。
5、审计日志记录。
6、请求与返回对象单独定义生成Swagger。

1、MultipleDbContext

示例

获取数据库连接字符串

public class MyConnectionStringResolver : DefaultConnectionStringResolver
{
    private readonly IConfigurationRoot _appConfiguration;

    public MyConnectionStringResolver(IAbpStartupConfiguration configuration, IHostingEnvironment hostingEnvironment)
        : base(configuration)
    {
        _appConfiguration =
            AppConfigurations.Get(hostingEnvironment.ContentRootPath, hostingEnvironment.EnvironmentName);
    }

    public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
    {
        if (args["DbContextConcreteType"] as Type == typeof(V4DbContext))
        {
            return _appConfiguration.GetConnectionString(YouApiConsts.V4ConnectionStringName);
        }

        return base.GetNameOrConnectionString(args);
    }
}

添加DbContext

        public override void PreInitialize()
        {
            //替换ConnectionStringResolver
            Configuration.ReplaceService<IConnectionStringResolver, MyConnectionStringResolver>();
            if (!SkipDbContextRegistration)
            {
                Configuration.Modules.AbpEfCore().AddDbContext<YouApiDbContext>(options =>
                {
                    if (options.ExistingConnection != null)
                    {
                        YouApiDbContextConfigurer.Configure(options.DbContextOptions, options.ExistingConnection);
                    }
                    else
                    {
                        YouApiDbContextConfigurer.Configure(options.DbContextOptions, options.ConnectionString);
                    }
                });

                //新增DbContext
                Configuration.Modules.AbpEfCore().AddDbContext<V4DbContext>(options =>
                {
                    if (options.ExistingConnection != null)
                    {
                        V4DbContextConfigurer.Configure(options.DbContextOptions, options.ExistingConnection);
                    }
                    else
                    {
                        V4DbContextConfigurer.Configure(options.DbContextOptions, options.ConnectionString);
                    }
                });
            }
        }

2、自定义返回数据结构

先找到AjaxResponseAjaxResponseBase被使用的相关对象AbpObjectActionResultWrapper、AbpJsonActionResultWrapper、AbpEmptyActionResultWrapper
都在AbpActionResultWrapperFactory : IAbpActionResultWrapperFactory中被使用,将Response与Wrapper稍微改造即可。

正常情况下返回

public override void PreInitialize()
{
    //替换默认的AbpActionResultWrapperFactory
    Configuration.ReplaceService<IAbpActionResultWrapperFactory, MyActionResultWrapperFactory>(DependencyLifeStyle.Transient);
}

异常情况下返回处理
实现自定义MyExceptionFilter,按照原有实现稍微改造即可。

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient();
    //MVC
    services.AddControllersWithViews(
        options => { 
            options.Filters.Add(new AbpAutoValidateAntiforgeryTokenAttribute());
            //添加自定义异常拦截器MyExceptionFilter,指定order: 1执行优先生效
            options.Filters.AddService(typeof(MyExceptionFilter), order: 1);
        }
    ).AddNewtonsoftJson(options =>
    {
        options.SerializerSettings.ContractResolver = new AbpMvcContractResolver(IocManager.Instance)
        {
            NamingStrategy = new CamelCaseNamingStrategy()
        };
    });
    //...
}

3、接收请求兼容form-data、queryString

使用

    //MVC
    services.AddControllersWithViews(
        options => { 
            options.Filters.Add(new AbpAutoValidateAntiforgeryTokenAttribute());
            //添加自定义异常拦截器MyExceptionFilter,指定order: 1执行优先生效
            options.Filters.AddService(typeof(MyExceptionFilter), order: 1);
            //请求参数绑定兼容from-data
            options.ModelBinderProviders.InsertBodyOrDefaultBinding();
        }

接收不同请求类型参数json、form-data、queryString都可正常兼容

public class MyModelBinder : IModelBinder
    {
        private readonly IModelBinder _bodyBinder;
        private readonly IModelBinder _complexBinder;

        public MyModelBinder (IModelBinder bodyBinder, IModelBinder complexBinder)
        {
            _bodyBinder = bodyBinder;
            _complexBinder = complexBinder;
        }

        public async Task BindModelAsync(ModelBindingContext bindingContext)
        {
            await DefaultBindModel(bindingContext);
        }

        private async Task DefaultBindModel(ModelBindingContext bindingContext)
        {
            //两种对不同形式(json、form-data、queryString)请求参数绑定
            await _bodyBinder.BindModelAsync(bindingContext);

            if (bindingContext.Result.IsModelSet)
            {
                return;
            }

            bindingContext.ModelState.Clear();
            await _complexBinder.BindModelAsync(bindingContext);
        }
    }

    public class MyModelBinderProvider : IModelBinderProvider
    {
        private readonly BodyModelBinderProvider _bodyModelBinderProvider;
        private readonly ComplexObjectModelBinderProvider _complexDataModelBinderProvider;

        public MyModelBinderProvider(BodyModelBinderProvider bodyModelBinderProvider, ComplexObjectModelBinderProvider complexDataModelBinderProvider)
        {
            _bodyModelBinderProvider = bodyModelBinderProvider;
            _complexDataModelBinderProvider = complexDataModelBinderProvider;
        }

        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context.BindingInfo.BindingSource != null && context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Body))
            {
                var bodyBinder = _bodyModelBinderProvider.GetBinder(context);
                var complexBinder = _complexDataModelBinderProvider.GetBinder(context);
                return new MyModelBinder(bodyBinder, complexBinder);
            }
            return null;
        }
    }
    public static class MyModelBinderProviderSetup
    {
        public static void InsertBodyOrDefaultBinding(this IList<IModelBinderProvider> providers)
        {
            var bodyProvider = providers.OfType<BodyModelBinderProvider>().Single();
            var complexDataProvider = providers.OfType<ComplexObjectModelBinderProvider>().Single();
            providers.Insert(0, new MyModelBinderProvider(bodyProvider, complexDataProvider));
        }
    }

4、接口请求路径和现有服务保持一致

在Controller添加转换,时间不够原有老接口直接做转发,新接口和需要改造的接口直接在新项目中实现。

[ApiController]
[Route("api/[controller]/[action]")]

由于有不同数据库版本使用适配器模式获取不同实现调用

private readonly IYouService _youService;
public InventoryController(IAppConfigurationAccessor configurationAccessor, IHttpClientFactory httpClientFactory)
  {
      _appConfiguration = configurationAccessor.Configuration;
      _httpClientFactory = httpClientFactory;
      if (_dic.ContainsKey(_key))
          _youService = IocManager.Instance.IocContainer.Resolve<IInventory>(_dic[_key]);
  }

转发到老服务请求

        private async Task<TO> GetExecute<TO>(string url)
        {
            TO result = Activator.CreateInstance<TO>();

            var httpClient = _httpClientFactory.CreateClient();
            var response = await httpClient.GetAsync($"{_baseUrl}{url}");

            // 处理响应
            if (response.IsSuccessStatusCode)
            {
                var responseData = await response.Content.ReadAsStringAsync();
                var resData = JsonConvert.DeserializeObject<ResParameter>(responseData);
                if (resData.code == ResponseCode.success)
                {
                    if (resData.data != null)
                        result = JsonConvert.DeserializeObject<TO>(resData.data.ToString());
                    else
                        result = default(TO);
                }
                else
                {
                    throw new UserFriendlyException(resData.info);
                }
            }
            return result;
        }

        private async Task<TO> PostExecute<TO, TI>(string url, TI input)
        {
            TO result = Activator.CreateInstance<TO>();

            var httpClient = _httpClientFactory.CreateClient();
            HttpContent httpContent = new StringContent(JsonConvert.SerializeObject(input), System.Text.Encoding.UTF8, "application/json");
            var response = await httpClient.PostAsync($"{_baseUrl}{url}", httpContent);

            // 处理响应
            if (response.IsSuccessStatusCode)
            {
                var responseData = await response.Content.ReadAsStringAsync();
                var resData = JsonConvert.DeserializeObject<ResParameter>(responseData);
                if (resData.code == ResponseCode.success)
                {
                    if (resData.data != null)
                        result = JsonConvert.DeserializeObject<TO>(resData.data.ToString());
                    else
                        result = default(TO);
                }
                else
                {
                    throw new UserFriendlyException(resData.info);
                }
            }
            return result;
        }

5、审计日志记录

由于现有数据库是非aspnetboilerplate格式的数据库审计日志替换成`MySimpleLogAuditingStore`
```c#
/// <summary>
    /// Implements <see cref="IAuditingStore"/> to simply write audits to logs.
    /// </summary>
    public class MySimpleLogAuditingStore : IAuditingStore
    {
        /// <summary>
        /// Singleton instance.
        /// </summary>
        public static SimpleLogAuditingStore Instance { get; } = new SimpleLogAuditingStore();

        public ILogger Logger { get; set; }

        public MySimpleLogAuditingStore()
        {
            Logger = NullLogger.Instance;
        }

        public Task SaveAsync(AuditInfo auditInfo)
        {
            if (auditInfo.Exception == null)
            {
                Logger.Info(auditInfo.LogToString());
                //Logger.Info(auditInfo.ToString());
            }
            else
            {
                Logger.Warn(auditInfo.LogToString());
                //Logger.Warn(auditInfo.ToString());
            }

            return Task.FromResult(0);
        }

        public void Save(AuditInfo auditInfo)
        {
            if (auditInfo.Exception == null)
            {
                Logger.Info(auditInfo.LogToString());
                //Logger.Info(auditInfo.ToString());
            }
            else
            {
                Logger.Warn(auditInfo.LogToString());
                //Logger.Warn(auditInfo.ToString());
            }
        }
    }

    public static class SimpleLogAuditingStoreExtensions
    {
        public static string LogToString(this AuditInfo auditInfo)
        {
            var loggedUserId = auditInfo.UserId.HasValue
                                   ? "user " + auditInfo.UserId.Value
                                   : "an anonymous user";

            var exceptionOrSuccessMessage = auditInfo.Exception != null
            ? "exception: " + auditInfo.Exception.Message
            : "succeed";

            return @$"AUDIT LOG: {auditInfo.ServiceName}.{auditInfo.MethodName} is executed by {loggedUserId} in {auditInfo.ExecutionDuration} ms 
                    from {auditInfo.ClientIpAddress} IP address 
                    param {auditInfo.Parameters} 
                    with {exceptionOrSuccessMessage}.";
        }
    }

6、请求与返回对象单独定义生成Swagger

原有的Nancy是没办法生成Swagger文档前端也不知道请求路径参数与返回徒增沟通成本,且原有接收参数很多是直接写死字符串接收。将原有请求入参与返回单独定义对象也能在对象中使用ICustomValidate做一些简单效验。
由于返回结果也是直接使用老系统定义的数据结构导致很多非必要的xx:null数据返回,为了避免混淆视听添加全局忽略

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient();
    //MVC
    services.AddControllersWithViews(
       //...
    ).AddNewtonsoftJson(options =>
    {
        options.SerializerSettings.ContractResolver = new AbpMvcContractResolver(IocManager.Instance)
        {
            NamingStrategy = new CamelCaseNamingStrategy(),
        };
        //全局忽略返回null
        options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
    });
    //...
}

最后

吐...血...。止住了。

标签:一次,服务,改造,options,auditInfo,var,new,null,public
From: https://www.cnblogs.com/ddrsql/p/17642692.html

相关文章

  • CHAPTER 7 Linux Operating System Services linux 系统服务
     /usr/include/asm-generic/unistd.h  /usr/include/errno.h/usr/include/asm-generic/errno.h /usr/include/asm-generic/errno-base.h  ......
  • 【逍遥西游中变】Linux手工服务端+安卓苹果双端
    5月整理大话回合手游【逍遥西游中变】Linux手工服务端+充值后台+安卓苹果双端压缩包内有详细的视频搭建教程,亲测有效,点击下面链接  提取码:8co9......
  • 【21.0】结合celery改造接口
    【一】引入所有接口都可以改造,尤其是查询所有的这种接口,如果加入缓存,会极大的提高查询速度首页轮播图接口:获取轮播图数据,加缓存---》咱们只是以它为例【二】改造轮播图接口luffyCity\luffyCity\apps\home\views.pyclassBannerView(GenericViewSet,CommonListMod......
  • 文件服务器迁移
    说明:文件服务器迁移共享文件,从172.21.44.X文件服务器迁移另外一台服务器操作:1、在新文件服务器以管理身份运行CMD输入以下命令robocopy\\172.21.44.xx\e$\spcdataD:\spcdata/e/j/copyall/mt:12/mot:10/mon:1 /mir命令说明:/e 复制子目录/j 复制时使用未缓冲......
  • 配置snmptrap服务器写入日志并通过邮件报警
    配置snmptrap服务器写入日志并通过邮件报警安装相关软件包yuminstallnet-snmpnet-snmp-utilsmailx修改snmptrapd配置文件/etc/snmp/snmptrapd.confdisableAuthorizationyesauthCommunitylog,execute,netpublictraphandledefault/usr/local/bin/traplog.sh......
  • 【故障公告】多年的故障老朋友又来了:数据库服务器 CPU 100%
    数据库服务器CPU100%问题几乎每年都要来几次,从来都不事先打一声招呼,今年的第2次在我们正忙着会员救园的时候来了。今天13:35首先收到我们自己的异常告警通知:ExecutionTimeoutExpired.Thetimeoutperiodelapsedpriortocompletionoftheoperationortheserver......
  • nodejs轻量服务器后端
    nodejs轻量服务器后端搭建思路server.js主函数+mine.js配置文件+index.html测试网页server.js文件varPORT=8080;//端口varDIR='test1';//用于存放html的目录varhttp=require('http');varurl=require('url');varfs=require('fs');var......
  • 服务器架设
    opencv安装pipinstallopencv-python依赖安装#libglvnd-1:1.3.2-1.tl3.x86_64#libglvnd-glx-1:1.3.2-1.tl3.x86_64#libxshmfence-1.3-2.tl3.x86_64#mesa-libGL-20.3.3-2.tl3.x86_64#mesa-libglapi-20.3.3-2.tl3.x86_64yuminstalllibGLstep1.准备环境re......
  • linux云服务器状态上报
    统计某文件夹下文件的个数ls-l|grep “^-”|wc-l统计某文件夹下目录的个数ls-l|grep“^d”|wc-l统计文件夹下文件的个数,包括子文件夹里的。ls-lR|grep“^-”|wc-l统计文件夹下目录的个数,包括子文件夹里的。ls-lR|grep“^d”|wc-l说明:ls-l长列表输出该目录下文件信息(......
  • Squid代理服务之反向代理模式
    为公网用户做加速访问1)实验拓扑2)web服务器配置参考传统模式中的web服务器配置,这里不再重复记录操作。网关需要指向squid服务器的内网ipecho"GATEWAY=10.10.10.13">>/etc/sysconfig/network-scripts/ifcfg-ens333)安装squid服务安装步骤这里不再赘述,参考传统模式中的安装步骤。(1)开......