dotnetcore+vue+elementUI 先後端分離架 二(後端篇)

前言

最近幾年先後端分離架構大行其道,並且各類框架也是層出不窮。本文經過dotnetcore +vue 來介紹 先後端分離架構實戰。html

涉及的技術棧

服務端技術

  • mysql

    本項目使用mysql 做爲持久化層
    本項目採用了 mysql 的示例 employees 數據庫, 須要的朋友能夠自行下載 。

http://www3.ntu.edu.sg/home/ehchua/programming/sql/SampleDatabases.html前端

  • orm

    dapper 短小精悍,被稱爲orm中的瑞士軍刀。做者以前使用EF 比較多,總感受 EF 對一些複雜查詢須要原生sql支持的不是那麼好,EF 生成sql 很差控制,涉及對性能要求比較高的地方,優化起來不夠理想。做者在最近的幾個項目中接觸到dapper,它對原生sql查詢支持的至關給力,使用起來也比較簡便,感受真是愛不釋手,嘿嘿。。。
    https://github.com/StackExchange/Dapper
  • 架構

    服務端總體架構採用倉儲模式
    Repository是一個獨立的層,介於領域層與數據映射層(數據訪問層)之間。它的存在讓領域層感受不到數據訪問層的存在,它提供一個相似集合的接口提供給領域層進行領域對象的訪問。Repository是倉庫管理員,領域層須要什麼東西只需告訴倉庫管理員,由倉庫管理員把東西拿給它,並不須要知道東西實際放在哪。
    此處照搬了dudu同窗對倉儲模式的理解。
    倉儲模式的好處:領域依賴接口與DB進行交互,解除了兩者之間的耦合,若是更換orm,DB層,採用這種模式,更換的代價最低。

前端技術

總體上SPA 結構vue

  • Vue:

    漸進式 JavaScript 框架.
    三大特色:易用,靈活, 高效。
    詳細信息見 https://cn.vuejs.org/node

  • VueRouter

    vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,適合用於構建單頁面應用。vue的單頁面應用是基於路由和組件的,路由用於設定訪問路徑,並將路徑和組件映射起來。傳統的頁面應用,是用一些超連接來實現頁面切換和跳轉的。在vue-router單頁面應用中,則是路徑之間的切換,也就是組件的切換。
  • axios

    axios 開始崛起, 是一個基於Promise 用於瀏覽器和 nodejs 的 HTTP 客戶端。
  • ElementUI

    網站快速成型工具
    Element,一套爲開發者、設計師和產品經理準備的基於 Vue 2.0 的桌面端組件庫
    詳細信息見 http://element-cn.eleme.io/#/zh-CNmysql

服務端項目結構

服務端代碼地址:https://github.com/hbb0b0/Hbb0b0.CMS/tree/master/Hbb0b0.CMS.WebAPIios

摘取部分類予以說明。git

CMS.Common

通用工具的封裝github

  • 配置工具類vue-router

    DB配置sql

    Cors配置

    WebApi配置

  • DB工具類

    DapperContext

    IRespository

    ResponsitoryBase

    包含獲取列表,獲取分頁列表,增刪改的通用操做的封裝

    SimpleCRUD

    github上對Dapper的進一步分裝

  • 消息

  • 數據轉換

    entity --> Dto

    DTObase

    PageList

CMS.DTO

返回給前端的數據對象,隔離DB model變化直接映射前端對象,使View與Model解耦。

CMS.Model

DB 中的表映射到 Model

CMS.IResponsity

DB 操做接口

CMS.Responsity

DB 操做接口的實現

CMS.IService

Serive接口,能夠供給第三方調用,也是第三方調用的規範,同時也約束着服務實現。
目前提供給API層調用。

CMS.Service

Serive接口的實現

CMS.API

以WebAPI 形式提供給前臺調用,並非嚴格按照 WebApi 規範 post,get,put,delete 調用.使用了 core中的 mvc 方式。

  • CMS.API 調用 IService 接口
  • CMS.Service 調用 IResponsitory接口
  • 各接口與實現之間經過Core中的構造函數注入方式聯接

服務端代碼說明:

  • 配置
{
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Information"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Information"
      }
    }

  },
  "WebAPI": {
    "DB": {
      "ConnectionString": "server=localhost;database=employees;uid=root;pwd=sqlsa;charset='utf8';persistsecurityinfo=True;SslMode=none;Allow User Variables=True;",
      "ReadingConnectionString": "server=localhost;database=employees;uid=root;pwd=sqlsa;charset='utf8';persistsecurityinfo=True;SslMode=none;Allow User Variables=True;",
      "WritingConnectionString": "server=localhost;database=employees;uid=root;pwd=sqlsa;charset='utf8';persistsecurityinfo=True;SslMode=none;Allow User Variables=True;"

    },
    "Cors": {
      "Name": "CMSCorsConfig",
      "Original": "http://localhost:8080"
    }

  }
}

API
Startup

  • 配置讀取與接口實現配置
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CMS.Common;
using CMS.Common.Config;
using CMS.Common.DB;
using CMS.IRepository;
using CMS.IService;
using CMS.Repository;
using CMS.Service;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace CMS.WebApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc()
            //序列化設置
            .AddJsonOptions(options =>
             {
                 //忽略循環引用
                 options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                 //不使用駝峯樣式的key
                 //options.SerializerSettings.ContractResolver = new DefaultContractResolver();
                 //設置時間格式
                 options.SerializerSettings.DateFormatString = "yyyy-MM-dd hh:mm:ss"; 
             }
        );

            services.AddOptions();

            //讀取配置
            services.Configure<WebApiOption>(Configuration.GetSection("WebAPI"));

            IServiceProvider serviceProvider = services.BuildServiceProvider();
            //獲取配置
            WebApiOption config = serviceProvider.GetService<IOptions<WebApiOption>>().Value;
            //Cors配置
            AddCorsService(services,config);
            //DB Service
            AddDBService(services,config);
        

        }

        /// <summary>
        /// AddDBService
        /// </summary>
        /// <param name="services"></param>
        /// <param name="config"></param>
        private void AddDBService(IServiceCollection services,WebApiOption config)
        {
           
            //設置全局配置
            services.AddSingleton<IDapperContext>(_ => new DapperContext(
               config
               ));

            services.AddScoped<IDepartmentRep, DepartmentRep>();
            services.AddScoped<IDepartmentService, DepartmentService>();

            services.AddScoped<IEmployeeRep, EmployeeRep>();
            services.AddScoped<IEmployeeService, EmployeeService>();

            services.AddScoped<ITitleRep, TitleRep>();
            services.AddScoped<ISalaryRep, SalaryRep>();


        }


        /// <summary>
        /// AddCorsService
        /// </summary>
        /// <param name="services"></param>
        /// <param name="config"></param>
        private void AddCorsService(IServiceCollection services, WebApiOption config)
        {
          
            //添加cors 服務
            services.AddCors(options =>
                                     options.AddPolicy(WebApiOption.CORS_POLICY_NAME, p => p.WithOrigins(config.Cors.Original)
             .AllowAnyMethod().AllowAnyHeader()));
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc();


            app.UseMvc(routes =>
            {

                routes.MapRoute(
                    name: "default",
                    template: "{controller}/{action}/{no?}",
                    defaults: new { controller = "Home", action = "Index" });
            });
           
            //配置Cors
            app.UseCors(WebApiOption.CORS_POLICY_NAME);


        }
    }
}
  • ControllerBase
using CMS.Common.Config;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace CMS.WebApi.Common
{
    [Route("api/[controller]")]
    [EnableCors(WebApiOption.CORS_POLICY_NAME)]
    public abstract class BaseController: Controller
    {
        protected readonly ILogger m_Logger;

        public BaseController(ILogger<BaseController> logger)
        {
            m_Logger = logger;
        }
    }
}
  • EmployeeController
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CMS.Common;
using CMS.Common.Config;
using CMS.Common.Message;
using CMS.DTO;
using CMS.Entity;
using CMS.IService;
using CMS.WebApi.Common;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;

namespace CMS.WebApi.Controllers
{


    public class EmployeeController : BaseController
    {

        private DBOption m_CmsOptions;
        private IEmployeeService m_Service;

        public EmployeeController(IOptions<DBOption> option, IEmployeeService employeeService, ILogger<EmployeeController> logger) : base(logger)
        {
            this.m_CmsOptions = option.Value;
            m_Service = employeeService;
        }


        [Route("[action]/{pageNumber}/{rowsPerPage}")]
        [HttpGet]
        public ReponseMessage<PagedList<EmployeeDTO>> GetPagedList(int pageNumber, int rowsPerPage)
        {
            var result = m_Service.GetPagedList(pageNumber, rowsPerPage);

            return m_Service.GetPagedList(pageNumber, rowsPerPage);
        }

        [Route("[action]")]
        [HttpPost]
        public ReponseMessage<PagedList<EmployeeDTO>> Query([FromBody]QueryCondition<EmployeeQuery> condition)
        {
            var result = m_Service.Query(condition);
            m_Logger.LogInformation("GetPagedList:{0}", JsonConvert.SerializeObject(result));
            return result;
        }

        [Route("[action]/{no}")]
        [HttpGet]
        public ReponseMessage<EmployeeDetailDto> GetDetail(string no)
        {
            var result = m_Service.GetDetail(no);

            return result;
        }

        [Route("[action]")]
        [HttpPost]
        public ReponseMessage<int> Add([FromBody]EmployeeDTO dto)
        {
            var result = m_Service.Add(dto);

            return result;
        }

        [Route("[action]/{empNo}")]
        [HttpPost]
        public ReponseMessage<int> Delete(int empNo)
        {
            var result = m_Service.Delete(empNo);

            return result;
        }

        [Route("[action]/")]
        [HttpPost]
        public ReponseMessage<int> Update([FromBody]EmployeeDTO dto)
        {
            var result = m_Service.Update(dto);

            return result;
        }
    }


}
  • IService
using CMS.Common;
using CMS.Common.Message;
using CMS.DTO;
using CMS.Entity;
using CMS.Model;
using System;
using System.Collections.Generic;

namespace CMS.IService
{
    public interface IEmployeeService
    {

        ReponseMessage<PagedList<EmployeeDTO>> GetPagedList(int pageNumber, int rowsPerPage);

        ReponseMessage<PagedList<EmployeeDTO>> Query(QueryCondition<EmployeeQuery> condition);
        ReponseMessage<EmployeeDetailDto> GetDetail(string emp_no);

        ReponseMessage<int> Add(EmployeeDTO dto);

        ReponseMessage<int> Update(EmployeeDTO dto);

        ReponseMessage<int> Delete(int emp_No);
    }
}
  • Service 實現
using CMS.Common;
using CMS.Common.Message;
using CMS.DTO;
using CMS.Entity;
using CMS.IRepository;
using CMS.IService;
using CMS.Model;
using CMS.Utility;
using Dapper;
using System;
using System.Collections.Generic;
using System.Linq;
namespace CMS.Service
{
    public class EmployeeService : IEmployeeService
    {
        private IEmployeeRep m_Rep;
        private ITitleRep m_TitleRep;
        private ISalaryRep m_SalaryRep;
        public EmployeeService(IEmployeeRep rep,ITitleRep titleRep, ISalaryRep salaryRep)
        {
            m_Rep = rep;
            m_TitleRep = titleRep;
            m_SalaryRep = salaryRep;
        }

      
        public ReponseMessage<EmployeeDetailDto> GetDetail(string emp_no)
        {
            ReponseMessage<EmployeeDetailDto> result = new ReponseMessage<EmployeeDetailDto>();
            EmployeeDetailDto data = new EmployeeDetailDto();
            var emp = m_Rep.Get(emp_no);
            data.Info = emp.JTransformTo<EmployeeDTO>();
            var titleList = m_TitleRep.GetList(string.Format($"where emp_no = {emp_no}"));
            var salaryList = m_SalaryRep.GetList(string.Format($"where emp_no = {emp_no} order by from_date "));
            result.Data = data;
            result.IsSuccess = true;
            if (titleList != null)
            {
                result.Data.TitleList = titleList.JTransformTo<TitleDto>();
            }

            if (salaryList!=null)
            {
                result.Data.SalaryList = salaryList.JTransformTo<SalaryDto>();
            }
            return result;

        }

        public ReponseMessage<PagedList<EmployeeDTO>> GetPagedList(int pageNumber, int rowsPerPage)
        {
            int total = 0;
            ReponseMessage<PagedList<EmployeeDTO>> result = new ReponseMessage<PagedList<EmployeeDTO>>();
            result.Data = new PagedList<EmployeeDTO>();
            var modelResult= m_Rep.GetPagedList(pageNumber, rowsPerPage, null, null, out total);
            result.Data.TotalCount = total;
            result.Data.Items=modelResult.Items.JTransformTo<EmployeeDTO>();
            result.IsSuccess = true;

            return result;
        }
        public ReponseMessage<PagedList<EmployeeDTO>> Query(QueryCondition<EmployeeQuery> condition)
        {
            ReponseMessage<PagedList<EmployeeDTO>> result = new ReponseMessage<PagedList<EmployeeDTO>>();
            result.Data = new PagedList<EmployeeDTO>();
            var modelResult = m_Rep.Query(condition);
            result.Data.TotalCount = modelResult.TotalCount;
            if (modelResult.TotalCount > 0)
            {
                result.Data.Items = modelResult.TotalCount>0 ? modelResult.Items.JTransformTo<EmployeeDTO>():new List<EmployeeDTO>();
            }
            result.IsSuccess = true;

            return result;
        }

        public ReponseMessage<int> Add(EmployeeDTO dto)
        {
            ReponseMessage<int> result = new ReponseMessage<int>();
            if (dto==null )
            {
                result.IsSuccess = false;
                result.MessageInfo = new MessageInfo() { ErrorCode = -1, Message ="無效的參數" };
            }

            EmployeeModel entitity = dto.JTransformTo<EmployeeModel>();

            int? opResult =  m_Rep.CustomerAdd(entitity);

            if(opResult.HasValue && opResult>0)
            {
                result.IsSuccess = true;
                result.Data = opResult.Value;
            }
            
            return result;
        }

        public ReponseMessage<int> Update(EmployeeDTO dto)
        {
            ReponseMessage<int> result = new ReponseMessage<int>();
            if (dto == null)
            {
                result.IsSuccess = false;
                result.MessageInfo = new MessageInfo() { ErrorCode = -1, Message = "無效的參數" };
            }

            EmployeeModel entitity = dto.JTransformTo<EmployeeModel>();

            int? opResult = m_Rep.Update(entitity);

            if (opResult.HasValue && opResult > 0)
            {
                result.IsSuccess = true;
                result.Data = opResult.Value;
            }

            return result;
        }

        public ReponseMessage<int> Delete(int empNo)
        {
            ReponseMessage<int> result = new ReponseMessage<int>();

            int? opResult = m_Rep.Delete(empNo);

            if (opResult.HasValue && opResult ==1)
            {
                result.IsSuccess = true;
                result.Data = opResult.Value;
            }

            return result;
        }
    }
}
  • IResponsitory
using CMS.Common;
using CMS.Common.DB;
using CMS.DTO;
using CMS.Entity;
using CMS.Model;
using System;
using System.Collections.Generic;
using System.Text;

namespace CMS.IRepository
{
    public interface IEmployeeRep: IRepository<EmployeeModel>
    {
        PagedList<EmployeeDTO> Query(QueryCondition<EmployeeQuery> query);

        int? Add(EmployeeModel entity);

        int? CustomerAdd(EmployeeModel entity);

        int? Delete(int  empNo);

        int? Update(EmployeeModel entity);

    }
}

-Responsitory 實現

using CMS.Common;
using CMS.Common.DB;
using CMS.DTO;
using CMS.IRepository;
using CMS.Model;
using System;
using System.Collections.Generic;
using System.Text;
using Dapper;
namespace CMS.Repository
{
public class EmployeeRep : ReponsitoryBase , IEmployeeRep
{
private static object m_sync_Object = new object();
public EmployeeRep(IDapperContext dapper) : base(dapper)
{

}

    public int? Add(EmployeeModel entity)
    {
        return this.Insert(entity);
    }
    new public int? Update(EmployeeModel entity)
    {
        return base.Update(entity);
    }
    public int? CustomerAdd(EmployeeModel entity)
    {
        int cmdResult = -1;
        using (var connection = this.GetWritingConnection())
        {
            var sql = new StringBuilder(" set @maxNo=(select max(emp_no)+1 from employees) ;");
            sql.AppendLine("insert into employees(emp_no,first_name,last_name,gender,birth_date,hire_date) values(@maxNo,@first_name,@last_name,@gender,@birth_date,@hire_date); ");
            sql.AppendLine("select @maxNo;");
            cmdResult = connection.ExecuteScalar<int>(sql.ToString(), entity);
        }

        return cmdResult;
    }

    public int? Delete(int empNo)
    {
        int cmdResult = -1;
        using (var connection = this.GetWritingConnection())
        {
            cmdResult = connection.Execute("delete from employees where emp_no = @emp_no ;", new { emp_no = empNo });
        }

        return cmdResult;
    }

    public PagedList<EmployeeDTO> Query(QueryCondition<EmployeeQuery> query)
    {


        PagedList<EmployeeDTO> pagedList = new PagedList<EmployeeDTO>();
        #region sql

        var sql = new StringBuilder("SELECT SQL_CALC_FOUND_ROWS * from employees ");

        #endregion

        sql.AppendLine(" Where 1=1");


        if (!string.IsNullOrEmpty(query.Param.First_Name))
        {
            sql.AppendLine(string.Format(" and First_Name like '{0}'", query.GetLikeValue(query.Param.First_Name)));
        }

        if (!string.IsNullOrEmpty(query.Param.Last_Name))
        {
            sql.AppendLine(string.Format(" and last_Name like '{0}'", query.GetLikeValue(query.Param.Last_Name)));
        }

        if (!string.IsNullOrEmpty(query.Param.Emp_No))
        {
            sql.AppendLine(string.Format(" and emp_no = @Emp_No"));
        }

        if (!string.IsNullOrEmpty(query.Param.Gender))
        {
            sql.AppendLine(string.Format(" and gender = @Gender"));
        }

        DateTime? hire_date_start = null;
        DateTime? hire_date_end = null;
        if (query.Param.Hire_Date_Range != null)
        {
            if (query.Param.Hire_Date_Range[0].HasValue)
            {
                hire_date_start = query.Param.Hire_Date_Range[0];
                sql.AppendLine(string.Format(" and hire_date >= @Hire_Date_Range_Start"));
            }
            if (query.Param.Hire_Date_Range[1].HasValue)
            {
                hire_date_end = query.Param.Hire_Date_Range[1];
                sql.AppendLine(string.Format(" and hire_date <= @Hire_Date_Range_End"));
            }
        }

        DateTime? birth_date_start = null;
        DateTime? birth_date_end = null;
        if (query.Param.Birth_Date_Range != null)
        {
            if (query.Param.Birth_Date_Range[0].HasValue)
            {
                birth_date_start = query.Param.Birth_Date_Range[0];
                sql.AppendLine(string.Format(" and birth_date >= @Birth_Date_Range_Start"));
            }
            if (query.Param.Birth_Date_Range[1].HasValue)
            {
                birth_date_end = query.Param.Birth_Date_Range[1];
                sql.AppendLine(string.Format(" and birth_date <= @Birth_Date_Range_End"));
            }
        }

        sql.AppendLine(" order by emp_no desc");
        sql.AppendLine($" LIMIT {(query.pageInfo.PageIndex - 1) * query.pageInfo.PageSize},{query.pageInfo.PageSize}");
        sql.Append(";");
        using (var connection = this.GetReadingConnection())
        {
            var result = connection.Query<EmployeeDTO>(sql.ToString(),
                new
                {
                    Emp_No = query.Param.Emp_No,
                    Hire_Date_Range_Start = hire_date_start,
                    Hire_Date_Range_End = hire_date_end,
                    Birth_Date_Range_Start = birth_date_start,
                    Birth_Date_Range_End = birth_date_end,
                    Gender = query.Param.Gender

                }).AsList();
            pagedList.Items = result;
            pagedList.TotalCount = connection.ExecuteScalar<int>("SELECT FOUND_ROWS();");
        }
        return pagedList;
    }

}

}

目前測試 empoyee 30萬條分頁數據大體在400ms左右

相關文章
相關標籤/搜索