ABP应用开发(Step by Step)-下篇
测试 ProductAppService 类
ProductAppService
类的GetListAsync
方法写单元测试代码(构建自动化测试细节后续再议)。ProductAppService_Tests
类:using Shouldly;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Xunit;
namespace ProductManagement.Products
{
public class ProductAppService_Tests : ProductManagementApplicationTestBase
{
private readonly IProductAppService _productAppService;
public ProductAppService_Tests()
{
_productAppService =
GetRequiredService<IProductAppService>();
}
/* TODO: Test methods */
}
}
该类继承自ProductManagementApplicationTestBase
,它默认集成 ABP 框架和其他基础设施库,这样我们就可以直接使用内置的测试能力。另外,我们使用方法GetRequiredService
来解决测试代码中的依赖关系,而不是构造函数注入(这在测试中是不可能的)。
ProductAppService_Tests
类中添加如下代码:[Fact]
public async Task Should_Get_Product_List()
{
//Act
var output = await _productAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
//Assert
output.TotalCount.ShouldBe(3);
output.Items.ShouldContain(
x => x.Name.Contains("Acme Monochrome Laser Printer")
);
}
该方法调用该GetListAsync
方法并检查结果是否正确。如果您打开测试资源管理器窗口(在 Visual Studio 中的查看|测试资源管理器菜单下),您可以看到我们添加的测试方法。测试资源管理器用于显示和运行解决方案中的测试:

运行测试到检查它是否按预期工作。如果方法正常工作,将在测试方法名称的左侧看到一个绿色图标。
自动 API 控制器和 Swagger UI
/swagger
URL,如图所示:

我们没有创建ProductController接口。这个接口是如何出现的?
动态 JavaScript 代理

getList
的,您可以定位到/Abp/ServiceProxyScript
地址,查看由 ABP 框架动态创建的 JavaScript 代理函数。产品列表
Index.cshtml
。下图显示了我们添加的页面的位置:
编辑内容,Index.cshtml
如下代码块所示:
@page
@using ProductManagement.Web.Pages.Products
@model IndexModel
<h1>Products Page</h1>
在这里,我放置一个h1
元素作为页眉。接下来我们在主菜单中添加一个菜单来打开这个页面。
添加菜单项
ProductManagementMenuContributor
类,并在ConfigureMainMenuAsync
方法末尾添加以下代码:context.Menu.AddItem(
new ApplicationMenuItem(
"ProductManagement",
l["Menu:ProductManagement"],
icon: "fas fa-shopping-cart"
).AddItem(
new ApplicationMenuItem(
"ProductManagement.Products",
l["Menu:Products"],
url: "/Products"
)
)
);
此代码添加了一个产品管理主菜单,其中包含产品菜单项。里面的l["…"]
语法是用来获取本地化的值。
en.json
文件,并将以下代码添加到该texts
部分的末尾:Menu:
作为菜单的本地化键的前缀,例如Menu:Products
。我们将在[第 8 章] 使用 ABP 的功能和服务中探讨本地化主题。
创建产品数据表
Index.cshtml
页面(在Pages/Products文件夹),并将其内容更改为以下内容:@page
@using ProductManagement.Web.Pages.Products
@using Microsoft.Extensions.Localization
@using ProductManagement.Localization
@model IndexModel
@inject IStringLocalizer<ProductManagementResource> L
@section scripts
{
<abp-script src="/Pages/Products/Index.cshtml.js" />
}
<abp-card>
<abp-card-header>
<h2>@L["Menu:Products"]</h2>
</abp-card-header>
<abp-card-body>
<abp-table id="ProductsTable" striped-rows="true" />
</abp-card-body>
</abp-card>
abp-script
是一个 ABP 标签助手,用于将脚本文件添加到页面,并具有自动捆绑、压缩和版本控制功能。abp-card
是另一个标签助手,以一种类型安全且简单的方式渲染 Card 组件。
我们可以使用标准的 HTML 标签。但是,ABP 标签助手极大地简化了 MVC/Razor 页面中的 UI 创建。此外,它们支持智能感知和编译时错误类型检查。我们将在[第 12 章] 使用 MVC/Razor 页面中研究标签助手。
Index.cshtml.js
,内容如下:$(function () {
var l = abp.localization.getResource('ProductManagement');
var dataTable = $('#ProductsTable').DataTable(
abp.libs.datatables.normalizeConfiguration({
serverSide: true,
paging: true,
order: [[0, "asc"]],
searching: false,
scrollX: true,
ajax: abp.libs.datatables.createAjax(
productManagement.products.product.getList),
columnDefs: [
/* TODO: Column definitions */
]
})
);
});
ABP 简化了数据表配置并提供了内置集成:
abp.localization.getResource
返回一个本地化对象,ABP 允许您在 JS中重用服务器端定义的本地化。abp.libs.datatables.normalizeConfiguration
是 ABP 框架定义的辅助函数。它通过为缺失选项提供常规默认值来简化数据表的配置。abp.libs.datatables.createAjax
使 ABP 的动态 JS 客户端代理来适配数据表的参数格式。productManagement.products.product.getList
是动态JS代理方法。
columnDefs
数组用于定义数据表中的列:{
title: l('Name'),
data: "name"
},
{
title: l('CategoryName'),
data: "categoryName",
orderable: false
},
{
title: l('Price'),
data: "price"
},
{
title: l('StockState'),
data: "stockState",
render: function (data) {
return l('Enum:StockState:' + data);
}
},
{
title: l('CreationTime'),
data: "creationTime",
dataFormat: 'date'
}
通常,列有一个title
字段和一个data
字段。data
字段匹配ProductDto
类中的属性名称,格式为驼峰式(一种命名风格,其中每个单词的第一个字母大写,第一个单词除外;它是JavaScript 语言中常用的命名风格)。
render
选项用于精细控制如何显示列数据。en.json
文件,并在该部分的末尾添加以下条目texts
:"Name": "Name",
"CategoryName": "Category name",
"Price": "Price",
"StockState": "Stock state",
"Enum:StockState:0": "Pre-order",
"Enum:StockState:1": "In stock",
"Enum:StockState:2": "Not available",
"Enum:StockState:3": "Stopped",
"CreationTime": "Creation time"
看一下实际的产品数据表:

创建产品
定义新的应用服务方法来获取类别和创建产品。
- 定义应用服务的获取类别和创建产品方法。
- 在 UI 部分,使用 ABP 的动态表单功能,基于 C# 类自动生成产品创建表单。
定义应用接口
IProductAppService
接口添加两个新方法开始:GetCategoriesAsync
方法获取产品类别的下拉数据。我们定义了两个新的 DTO。CreateUpdateProductDto
用于创建和更新产品(我们将在编辑产品时候重复使用它)。我们在ProductManagement.Application.Contracts项目的Products文件夹中定义它:using System;
using System.ComponentModel.DataAnnotations;
namespace ProductManagement.Products
{
public class CreateUpdateProductDto
{
public Guid CategoryId { get; set; }
[Required]
[StringLength(ProductConsts.MaxNameLength)]
public string Name { get; set; }
public float Price { get; set; }
public bool IsFreeCargo { get; set; }
public DateTime ReleaseDate { get; set; }
public ProductStockState StockState { get; set; }
}
}
接下来,在ProductManagement.Application.Contracts项目的Categories文件夹中定义一个CategoryLookupDto
类:
using System;
namespace ProductManagement.Categories
{
public class CategoryLookupDto
{
public Guid Id { get; set; }
public string Name { get; set; }
}
}
定了接口相关类,现在我们可以在应用层实现接口了。
实现应用服务
ProductAppService
中实现CreateAsync
和GetCategoriesAsync
方法(ProductManagement.Application项目中),如下代码块:public async Task CreateAsync(CreateUpdateProductDto input)
{
await _productRepository.InsertAsync(
ObjectMapper.Map<CreateUpdateProductDto, Product>(input)
);
}
public async Task<ListResultDto<CategoryLookupDto>> GetCategoriesAsync()
{
var categories = await _categoryRepository.GetListAsync();
return new ListResultDto<CategoryLookupDto>(
ObjectMapper.Map<List<Category>, List<CategoryLookupDto>>(categories)
);
}
这里,_categoryRepository
属于IRepository<Category, Guid>
服务类型,通过构造函数注入,方法实现很简单,无需解释。
ProductManagementApplicationAutoMapperProfile.cs
文件(在ProductManagement.Application项目中),添加以下代码:CreateMap<CreateUpdateProductDto, Product>();
CreateMap<Category, CategoryLookupDto>();
用户界面
CreateProductModal.cshtml
Razor 页面。打开CreateProductModal.cshtml.cs
文件,更改CreateProductModalModel
代码:using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using ProductManagement.Products;
namespace ProductManagement.Web.Pages.Products
{
Public class CreateProductModalModel:ProductManagementPageModel
{
[BindProperty]
public CreateEditProductViewModel Product { get; set; }
public SelectListItem[] Categories { get; set; }
private readonly IProductAppService _productAppService; public CreateProductModalModel(IProductAppService productAppService)
{
_productAppService = productAppService;
}
public async Task OnGetAsync()
{
// TODO
}
public async Task<IActionResult> OnPostAsync()
{
// TODO
}
}
}
这里的ProductManagementPageModel
是基类。你可以继承它来创建PageModel
类。[BindProperty]
是一个标准的 ASP.NET Core 属性,在HTTP Post 请求时,会将数据绑定到Product
属性。Categories
将用于显示下拉列表中的类别。我们通过注入IProductAppService
接口以使用之前定义的方法。
CreateEditProductViewModel
还没定义,我们将其定义在与CreateProductModal.cshtml
相同的文件夹下:using ProductManagement.Products;
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
namespace ProductManagement.Web.Pages.Products
{
public class CreateEditProductViewModel
{
[SelectItems("Categories")]
[DisplayName("Category")]
public Guid CategoryId { get; set; }
[Required]
[StringLength(ProductConsts.MaxNameLength)]
public string Name { get; set; }
public float Price { get; set; }
public bool IsFreeCargo { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public ProductStockState StockState { get; set; }
}
}
SelectItems
告诉我们CategoryId
属性将从Categories
列表中选择。我们将在编辑模式对话框中重用此类。这就是我为什么命名它为CreateEditProductViewModel
。DTO 与 ViewModel
CreateEditProductViewModel
似乎没有必要,因为它与 CreateUpdateProductDto
DTO非常相似。当然你也可以在视图里复用DTO。但是,考虑到这些类具有不同的用途,并且随着时间的推移会向不同的方向发展,所更推荐的办法是将每个关注点分开。例如,[SelectItems("Categories")]
属性指向 Razor Page 模型,它在应用层没有任何意义。CreateProductModalModel
类中实现OnGetAsync
方法:public async Task OnGetAsync()
{
Product = new CreateEditProductViewModel
{
ReleaseDate = Clock.Now,
StockState = ProductStockState.PreOrder
}; var categoryLookup = await _productAppService.GetCategoriesAsync();
Categories = categoryLookup.Items.Select(x => new SelectListItem(x.Name, x.Id.ToString())).ToArray();
}
我们使用默认值创建Product
类,然后使用产品应用服务填充Categories
列表。Clock
是 ABP 框架提供的服务,用于获取当前时间(在不处理时区和本地/UTC 时间的情况下),这里我们不再使用DateTime.Now
。具体内容这将在[第 8 章] 使用 ABP 的功能和服务中进行解释。
OnPostAsync
代码块:public async Task<IActionResult> OnPostAsync()
{
await _productAppService.CreateAsync(
ObjectMapper.Map<CreateEditProductViewModel,CreateUpdateProductDto> (Product)
);
return NoContent();
}
由于我们要映射CreateEditProductViewModel
到CreateProductDto
,所以需要定义映射配置。我们在ProductManagement.Web项目中打开ProductManagementWebAutoMapperProfile
类,并更改以下代码块内容:
public class ProductManagementWebAutoMapperProfile : Profile
{
public ProductManagementWebAutoMapperProfile()
{
CreateMap<CreateEditProductViewModel, CreateUpdateProductDto>();
}
}
我们已经完成了产品创建 UI 的 C# 端,接下来可以开始构建 UI 和 JavaScript 代码。打开CreateProductModal.cshtml
文件,并将内容更改如下:
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using ProductManagement.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model ProductManagement.Web.Pages.Products.CreateProductModalModel
@inject IHtmlLocalizer<ProductManagementResource> L
@{
Layout = null;
}
<abp-dynamic-form abp-model="Product" asp-page="/Products/CreateProductModal">
<abp-modal>
<abp-modal-header title="@L["NewProduct"].Value"></abp-modal-header>
<abp-modal-body>
<abp-form-content />
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
</abp-modal>
</abp-dynamic-form>
在这里,abp-dynamic-form
会根据 C# 模型类自动创建表单元素。abp-form-content
是呈现表单元素的地方。abp-modal
用于创建模态对话框。
Index.cshtml
文件,然后将abp-card-header
部分更改如下:<abp-card-header>
<abp-row>
<abp-column size-md="_6">
<abp-card-title>@L["Menu:Products"]</abp-card-title>
</abp-column>
<abp-column size-md="_6" class="text-end">
<abp-button id="NewProductButton"
text="@L["NewProduct"].Value"
icon="plus"
button-type="Primary"/>
</abp-column>
</abp-row>
</abp-card-header>
我添加了 2 列,其中每列都有一个size-md="_6"
属性(即 12 列 Bootstrap 网格的一半)。左侧设置卡片标题,右侧放置了一个按钮。
Index.cshtml.js
文件末尾(在})
之前):var createModal = new abp.ModalManager(abp.appPath + 'Products/CreateProductModal');
createModal.onResult(function () {
dataTable.ajax.reload();
});
$('#NewProductButton').click(function (e) {
e.preventDefault();
createModal.open();
});
abp.ModalManager
用于在客户端管理模式对话框。在内部,它使用 Twitter Bootstrap 的标准模态组件,封装了很多细节,并提供了一个简单的 API。当模型触发保存时会返回一个回调函数createModal.onResult()
。createModal.open()
用于打开模态对话框。
en.json
文件中定义一些本地化文本(.Domain.Shared项目的Localization/ProductManagement 文件夹下):"NewProduct": "New Product",
"Category": "Category",
"IsFreeCargo": "Free Cargo",
"ReleaseDate": "Release Date"
再次运行 Web 尝试创建新产品

ABP基于 C# 类模型自动创建表单字段。本地化和验证也可以通过读取属性和使用约定来自动工作。我们将在[第 12 章] 使用 MVC/Razor 页面 中更详细地介绍验证和本地化主题。
编辑产品
定义应用接口
IProductAppService
接口定义两个新方法:Task<ProductDto> GetAsync(Guid id);
Task UpdateAsync(Guid id, CreateUpdateProductDto input);
第一种方法用于通过ID获取产品。我们在UpdateAsync
方法中重用之前定义的CreateUpdateProductDto
。
实现应用接口
ProductAppService
类中:public async Task<ProductDto> GetAsync(Guid id)
{
return ObjectMapper.Map<Product, ProductDto>(
await _productRepository.GetAsync(id)
);
}
public async Task UpdateAsync(Guid id, CreateUpdateProductDto input)
{
var product = await _productRepository.GetAsync(id);
ObjectMapper.Map(input, product);
}
GetAsync
方法用于从数据库中获取产品,并将其映射到ProductDto
对象后进行返回。UpdateAsync
方法获取到一个产品后,将给定的DTO输入映射到产品。通过这种方式,我们用新值覆盖产品。
_productRepository.UpdateAsync
,因为 EF Core有一个变更跟踪系统。ABP 的工作单元如果没有抛出异常,则在请求结束时会自动保存更改。我们将在[第 6 章] *使用数据访问基础架构”*中介绍工作单元系统。用户界面
EditProductModal.cshtml
Razor 页面(ProductManagement.Web项目的 Pages/Products文件夹下)。打开EditProductModal.cshtml.cs
,代码更改如下:using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using ProductManagement.Products;
namespace ProductManagement.Web.Pages.Products
{
public class EditProductModalModel : ProductManagementPageModel
{
[HiddenInput]
[BindProperty(SupportsGet = true)]
public Guid Id { get; set; }
[BindProperty]
public CreateEditProductViewModel Product { get; set; }
public SelectListItem[] Categories { get; set; }
private readonly IProductAppService _productAppService; public EditProductModalModel(IProductAppService productAppService)
{
_productAppService = productAppService;
}
public async Task OnGetAsync()
{
// TODO
}
public async Task<IActionResult> OnPostAsync()
{
// TODO
}
}
}
表单中Id
字段将被隐藏。
Product
和Categories
属性类似于创建产品。我们还将
IProductAppService
接口注入到构造函数。OnGetAsync
方法,如下代码块所示:public async Task OnGetAsync()
{
var productDto = await _productAppService.GetAsync(Id);
Product = ObjectMapper.Map<ProductDto, CreateEditProductViewModel>(productDto); var categoryLookup = await _productAppService.GetCategoriesAsync();
Categories = categoryLookup.Items
.Select(x => new SelectListItem(x.Name, x.Id.ToString()))
.ToArray();
}
首先,我们要先获取一个产品 ( ProductDto
),再将其转换为CreateEditProductViewModel
,使用它在 UI 上来创建编辑表单。然后,我们在表单上选择产品类别。
ProductDto
到CreateEditProductViewModel
,所以我们需要在ProductManagementWebAutoMapperProfile
类中定义配置映射(ProductManagement.Web项目中),这和我们之前操作是一样的:CreateMap<ProductDto, CreateEditProductViewModel>();
我们再看下OnPostAsync()
方法:
public async Task<IActionResult> OnPostAsync()
{
await _productAppService.UpdateAsync(Id,
ObjectMapper.Map<CreateEditProductViewModel, CreateUpdateProductDto>(Product)
);
return NoContent();
}
OnPostAsync
方法很简单,把CreateEditProductViewModel
转换为CreateUpdateProductDto
。
EditProductModal.cshtml
,内容更改如下:@page
@using Microsoft.AspNetCore.Mvc.Localization
@using ProductManagement.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model ProductManagement.Web.Pages.Products.EditProductModalModel
@inject IHtmlLocalizer<ProductManagementResource> L
@{
Layout = null;
}
<abp-dynamic-form abp-model="Product" asp-page="/Products/EditProductModal">
<abp-modal>
<abp-modal-header title="@Model.Product.Name"></abp-modal-header>
<abp-modal-body>
<abp-input asp-for="Id" />
<abp-form-content/>
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
</abp-modal>
</abp-dynamic-form>
页面与CreateProductModal.cshtml
非常相似。我刚刚将Id
字段作为隐藏字段添加到表单,用来存储Id
编辑的产品的属性。
Index.cshtml.js
文件,并在dataTable
代码的头部添加一个ModalManager
对象:var editModal = new abp.ModalManager(abp.appPath + 'Products/EditProductModal');
然后,在dataTable
内部的columnDefs
数组中定义一个列(第一项):
{
title: l('Actions'),
rowAction: {
items:
[
{
text: l('Edit'),
action: function (data) {
editModal.open({ id: data.record.id });
}
}
]
}
},
此代码向数据表添加了一个新的Actions列,并添加了一个Edit操作按钮,单击即可打开编辑窗口。rowAction
是 ABP Framework 提供的一个特殊选项。它用于在表中的一行添加一个或多个操作按钮。
dataTable
初始化代码后添加如下:editModal.onResult(function () {
dataTable.ajax.reload();
});
在保存产品编辑对话框后刷新数据表,确保我们可以看到表上的最新数据。最终的 UI 类似于下图:

我们现在可以查看、创建和编辑产品了。最后一部分将实现删除产品。
删除产品
IProductAppService
接口中添加一个新方法:Task DeleteAsync(Guid id);
然后,在ProductAppService
类中实现它:
public async Task DeleteAsync(Guid id)
{
await _productRepository.DeleteAsync(id);
}
现在向产品列表添加一个新删除按钮。打开Index.cshtml.js
,并在Edit操作之后添加以下定义(在rowAction.items
数组中):
{
text: l('Delete'),
confirmMessage: function (data) {
return l('ProductDeletionConfirmationMessage',data.record.name);
},
action: function (data) {
productManagement.products.product
.delete(data.record.id)
.then(function() {
abp.notify.info(l('SuccessfullyDeleted'));
dataTable.ajax.reload();
});
}
}
confirmMessage
用于在删除之前获得用户确认。productManagement.products.product.delete
函数由 ABP 框架动态创建。通过这种方式,可以直接在 JS 代码中调用服务器端方法。我们只需传递当前记录的 ID。then
函数传递一个回调函数,用于删除之后的操作。最后,我们使用abp.notify.info
通知用户,最后刷新数据表。
en.json
文件中添加以下代码:
因为现在有两个操作按钮,所以编辑按钮会自动变成一个下拉选项。当您单击删除操作时,您会收到一条确认消息:
Product
实体派生于FullAuditedAggregateRoot
,所以它使用了软删除。删除产品后检查数据库,您会看到它并没有真正删除,但是IsDeleted
字段已经设置为true
(逻辑删除不是物理删除)。下次查询商品时,已删除的商品会自动过滤掉,不包含在查询结果中。这是由 ABP 框架的数据过滤系统完成的。概括
(构建自动化测试细节后续再议)
ABP应用开发(Step by Step)-下篇的更多相关文章
- Step by Step: 基于MFC下的COM组件开发-Helloworld
http://blog.csdn.net/sybifei/article/details/45008745 [这篇文章有问题, 仅供参考] http://blog.csdn.net/define_us ...
- Step by step Dynamics CRM 2011升级到Dynamics CRM 2013
原创地址:http://www.cnblogs.com/jfzhu/p/4018153.html 转载请注明出处 (一)检查Customizations 从2011升级到2013有一些legacy f ...
- Step by step Install a Local Report Server and Remote Report Server Database
原创地址:http://www.cnblogs.com/jfzhu/p/4012097.html 转载请注明出处 前面的文章<Step by step SQL Server 2012的安装 &g ...
- WPF Step By Step 系列-Prism框架在项目中使用
WPF Step By Step 系列-Prism框架在项目中使用 回顾 上一篇,我们介绍了关于控件模板的用法,本节我们将继续说明WPF更加实用的内容,在大型的项目中如何使用Prism框架,并给予Pr ...
- WPF Step By Step 完整布局介绍
WPF Step By Step 完整布局介绍 回顾 上一篇,我们介绍了基本控件及控件的重要属性和用法,我们本篇详细介绍WPF中的几种布局容器及每种布局容器的使用场景,当 然这些都是本人在实际项目中的 ...
- WPF Step By Step 控件介绍
WPF Step By Step 控件介绍 回顾 上一篇,我们主要讨论了WPF的几个重点的基本知识的介绍,本篇,我们将会简单的介绍几个基本控件的简单用法,本文会举几个项目中的具体的例子,结合这些 例子 ...
- WPF Step By Step 系列 - 开篇 ·
WPF Step By Step 系列 - 开篇 公司最近要去我去整理出一个完整的WPF培训的教程,我刚好将自己学习WPF的过程和经验总结整理成笔记的方式来讲述,这里就不按照书上面的东西来说了,书本上 ...
- WinForm RDLC SubReport Step by step
最近在做的一个PO管理系统,因为要用到订单打印,没有用水晶报表,直接使用VS2010的Reporting.参考了网上的一些文章,但因为找到的数据是用于WebForm的,适配到WinForm有点区别,竟 ...
- Struts2+Spring+Hibernate step by step 11 ssh拦截验证用户登录到集成
注意:该系列文章从教师王健写了一部分ssh集成开发指南 引言: 之前没有引入拦截器之前,我们使用Filter过滤器验证用户是否登录,在使用struts2之后,全然能够使用拦截器,验证用户是否已经登录, ...
- 持续交付工具ThoughtWorks Go部署step by step
持续交付工具ThoughtWorks Go部署step by step http://blogs.360.cn/360cloud/2014/05/13/%E6%8C%81%E7%BB%AD%E4%BA ...
随机推荐
- CentOS6跟CentOS7的区别
1.CentOS6在/etc/profile配置环境变量是JAVAHOME,CentOS7是{JAVA_HOME} 2.
- docker安装部署、fastDFS文件服务器搭建与springboot项目接口
一.docker安装部署 1.更新yum包:sudo yum update 2.安装需要的软件包,yum-util 提供yum-config-manager功能,另外两个是devicemapper驱动 ...
- C语言之数据类型(知识点8)
一.数据类型 1.数据基本类型 (1)整数 ①有符号整形 有符号短整型 short 有符号基本整形 int 有符号长整形 long ②无符号整形 无符号基本整形 无符号短整型 无符号长整型 (2) ...
- C语言之main方法解析(知识点1)
1.注释 /*自带注释*/2.引包 #include <stdio.h>3.主方法 void main{}4.执行体 printf("打印& ...
- catkin编译系统
引言 最近项目中遇到一个需求:将 C++ 程序 (不是 ROS node,只是普通的 C++ 程序)中的变量发布到 ROS topic 上,以便 ROS 中的其他 node 进行后续处理. 原 C++ ...
- 【STM32】MDK中寄存器地址名称映射分析
对于MCU,一切底层配置,最终都是在配置寄存器 51单片机访问地址 51单片机经常会引用一个reg51.h的头文件.下面看看它是怎么把名字和寄存器联系在一起的: 1 sfr p0=0x80; 2 p0 ...
- Numpy非常重要有用的数组合并操作
Numpy非常重要有用的数组合并操作 背景:在给机器学习准备数据的过程中,经常需要进行不同来源的数据合并的操作. 两类场景: 给已有的数据添加多行,比如增添一些样本数据进去: 给已有的数据添加多列,比 ...
- 开发一个自己的 CSS 框架(四)
这一节,我们来讲规矩,谈网格,做人可以不要脸,不讲规矩,不讲道理(特指傲娇兽),但底线还是要有的,如同网格一样,不能超出. jeet 这里我们别人封装好的模块,不过呢,我们也会详细介绍一下原理.首先我 ...
- Day 19: EmberJS 入门指南
编者注:我们发现了有趣的系列文章<30天学习30种新技术>,正在翻译,一天一篇更新,年终礼包.下面是第19天的内容. 到目前为止,我们这一系列文章涉及了Bower.AngularJS.Gr ...
- 前端每日实战:133# 视频演示如何用 CSS 和 GSAP 创作有多个关键帧的连续动画
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/eLMKJG 可交互视频 此视频是可 ...