前言:

本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作。

本系列文章主要参考资料:

微软文档:https://docs.microsoft.com/zh-cn/aspnet/core/getting-started/?view=aspnetcore-2.1&tabs=windows

《Pro ASP.NET MVC 5》、《锋利的 jQuery》

此系列皆使用 VS2017+C# 作为开发环境。如果有什么问题或者意见欢迎在留言区进行留言。

项目 github 地址:https://github.com/NanaseRuri/LibraryDemo

本章内容:自定义布局页、自定义 EditorFor 模板、EF 多对多数据的更新

一、自定义布局页 

在 ASP.NET 中,默认将 HTML 页面的 body 元素一部分抽出来,该部分称作 RenderBody ;然后将这部分放到一个布局即大体页面框架中即可完成对同一系列的页面进行精简的布局实现。

默认布局页为 _Layout.cshtml,可在视图文件夹中根目录或各个控制器视图目录的 _ViewStart.cshtml 修改默认布局页,或者在每个 Razor 页面的开头中指定布局页:

 @{
ViewData["Title"] = "EditLendingInfo";
Layout = "_LendingLayout";
}

之前一直使用的是 VS 的默认布局页,现在以该默认布局页为基础,添加自己所需要的信息:

 @using Microsoft.AspNetCore.Http.Extensions
@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - LibraryDemo</title>
<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-area="" asp-controller="BookInfo" asp-action="Index" class="navbar-brand">LibraryDemo</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a asp-area="" asp-controller="BookInfo" asp-action="Index">首页</a></li>
<li>
@if (User.Identity.IsAuthenticated)
{
<a asp-controller="BookInfo" asp-action="PersonalInfo">@User.Identity.Name</a>
}
else
{
<a asp-area="" asp-controller="StudentAccount" asp-action="Login"
asp-route-returnUrl="@(Context.Request.GetDisplayUrl())">登录</a>
}
</li>
<li><a asp-area="" asp-controller="BookInfo" asp-action="Recommend">推荐图书</a></li>
<li><a href="mailto:Nanase@cnblogs.com">联系我们</a></li>
@if (User.Identity.IsAuthenticated)
{
<li>
<a asp-action="Logout" asp-controller="StudentAccount" asp-route-returnUrl="@(Context.Request.GetDisplayUrl())">注销</a>
</li>
}
</ul>
</div>
</div>
</nav>
<div align="center">
<br />
<form action="@Url.Action("Search", "BookInfo")">
@Html.DropDownList("keyword", new List<SelectListItem>()
{
new SelectListItem("书名", "Name"),
new SelectListItem("ISBN", "ISBN"),
new SelectListItem("索书号", "FetchBookNumber"),
})
<input type="text" name="value"/>
<button type="submit"><span class="glyphicon glyphicon-search"></span></button>
</form> @if (TempData["message"] != null)
{
<br/>
<p class="text-success">@TempData["message"]</p>
<br/>
}
</div>
<partial name="_CookieConsentPartial" />
<div class="container body-content"> @RenderBody()
<hr />
</div> <div class="container" style="margin-top: 20px;">
<footer>
<p>&copy; - LibraryDemo</p>
</footer>
</div>
<environment include="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT"></script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"></script>
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>
@RenderSection("Scripts", required: false)
</body>
</html>

现在大体框架:

除了默认的 RenderBody 外,可以指定特定的部分放在页面的不同地方,在布局页中使用@RenderSection("SectionName"):

 @RenderSection("SectionName")

且在视图页中使用指定特定的节@section SectionName{  };

  @section SectionName{ 

   };

则该视图页中的 SectionName 部分会被提取出来放到布局页对应的位置。

二、管理员编辑借阅信息

动作方法:

在此对数据库的表格使用 Include 方法使 EF 应用其导航属性以获得 KeepingBooks 列表,否则使用 Student 对象 KeepingBooks 属性只会返回空。

         [Authorize(Roles = "Admin")]
public IActionResult EditLendingInfo(string barcode)
{
if (barcode == null)
{
return RedirectToAction("BookDetails");
}
Book book = _lendingInfoDbContext.Books.FirstOrDefault(b => b.BarCode == barcode);
return View(book);
} [HttpPost]
[Authorize(Roles = "Admin")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditLendingInfo([Bind("BarCode,ISBN,BorrowTime,KeeperId,AppointedLatestTime,State")]Book book)
{
if (ModelState.IsValid)
{
if (book.BorrowTime > DateTime.Now)
{
ModelState.AddModelError("", "请检查外借时间");
return View(book);
}
if (book.AppointedLatestTime.HasValue)
{
if (book.AppointedLatestTime < DateTime.Now)
{
ModelState.AddModelError("", "请检查预约时间");
return View(book);
} if (book.KeeperId == null)
{
ModelState.AddModelError("", "不存在该学生");
return View(book);
}
} StudentInfo student = await _lendingInfoDbContext.Students.Include(s => s.KeepingBooks).FirstOrDefaultAsync(s => s.UserName == book.KeeperId); Book addedBook = _lendingInfoDbContext.Books
.Include(b => b.Keeper).ThenInclude(k => k.KeepingBooks)
.FirstOrDefault(b => b.BarCode == book.BarCode);
if (addedBook == null)
{
return RedirectToAction("Books", new { isbn = book.ISBN });
} StudentInfo preStudent = addedBook.Keeper;
AppointmentOrLending targetLending =
preStudent?.KeepingBooks.FirstOrDefault(b => b.BookId == addedBook.BarCode); addedBook.AppointedLatestTime = book.AppointedLatestTime;
addedBook.State = book.State;
addedBook.BorrowTime = book.BorrowTime;
addedBook.MatureTime = null; preStudent?.KeepingBooks.Remove(targetLending); if (addedBook.BorrowTime.HasValue)
{
if (book.KeeperId == null)
{
ModelState.AddModelError("", "请检查借阅者");
return View(book);
} if (student == null)
{
ModelState.AddModelError("", "不存在该学生");
return View(book);
}
if (student != null)
{
if (student.KeepingBooks.Count >= student.MaxBooksNumber)
{
TempData["message"] = "该学生借书已超过上限";
} addedBook.State = BookState.Borrowed;
student.KeepingBooks.Add(new AppointmentOrLending()
{
BookId = addedBook.BarCode,
StudentId = student.UserName
});
addedBook.Keeper = student; }
addedBook.MatureTime = addedBook.BorrowTime + TimeSpan.FromDays();
} TempData["message"] = "保存成功";
await _lendingInfoDbContext.SaveChangesAsync();
return RedirectToAction("Books", new { isbn = book.ISBN });
}
return View(book);
}

将 BookState 枚举提取成分部视图 _BookStatePartial:

 @using LibraryDemo.Models.DomainModels
@model Book
<div class="form-group">
@Html.LabelFor(b => b.State)
@Html.DropDownListFor(b => b.State, Enum.GetValues(typeof(BookState)).Cast<Enum>().Select(state =>
{
string enumVal = Enum.GetName(typeof(BookState), state);
string displayVal;
switch (enumVal)
{
case "Normal":
displayVal = "可借阅";
break;
case "Readonly":
displayVal = "馆内阅览";
break;
case "Borrowed":
displayVal = "已借出";
break;
case "ReBorrowed":
displayVal = "被续借";
break;
case "Appointed":
displayVal = "被预约";
break;
default:
displayVal = "";
break;
}
return new SelectListItem()
{
Text = displayVal,
Value = enumVal,
Selected = Model.State.ToString() == enumVal
};
}))
</div>

Html.DisplayFor 方法是 ASP.NET 内置对各种属性进行展示的方法,可以在项目的 Views 文件夹中的 Shared 文件夹创建对应类型的 Editor 模板供其使用:

在此创建一个 DateTime.cshtml,于是我们使用 Html.DisplayFor 用于展示 DateTime 数据时只会显示年份/月份/天数:

 @model DateTime?

 @Model?.ToString("yyyy/M/dd")

视图中第 40 行使用 partial TagHelper 指定其 name 为 _BookStatePartial 以应用分部视图:

 @model LibraryDemo.Models.DomainModels.Book
@{
ViewData["Title"] = "EditLendingInfo";
Layout="_LendingLayout";
} <h2>@Model.BarCode</h2>
<h3>@Model.Name</h3>
<br/> <script>
window.onload = function() {
$("input").addClass("form-control");
}
window.onbeforeunload = function (event) {
return "您的数据未保存,确定退出?";
}
function removeOnbeforeunload() {
window.onbeforeunload = "";
}
</script> @Html.ValidationSummary(false,"",new{@class="text-danger"}) <form asp-action="EditLendingInfo" method="post">
@Html.HiddenFor(b => b.BarCode)
@Html.HiddenFor(b => b.ISBN)
<div class="form-group">
@Html.LabelFor(b => b.KeeperId)
@Html.EditorFor(b => b.KeeperId)
</div>
<div class="form-group">
@Html.LabelFor(b => b.BorrowTime)
@Html.EditorFor(b => b.BorrowTime)
</div>
<div class="form-group">
@Html.LabelFor(b => b.AppointedLatestTime)
@Html.EditorFor(b => b.AppointedLatestTime)
</div>
<partial model="@Model" name="_BookStatePartial"/>
<input type="submit" onclick="return removeOnbeforeunload()" class="btn-primary"/>
</form>

结果:

三、查看个人信息

这里通过 User.Identity.Name 获取当前登录人的信息以选定当前登录的学生:

         [Authorize]
public async Task<IActionResult> PersonalInfo()
{
StudentInfo student = await _lendingInfoDbContext.Students.Include(s => s.KeepingBooks).ThenInclude(k => k.Book)
.FirstOrDefaultAsync(s => s.UserName == User.Identity.Name);
decimal fine = ;
foreach (var book in student.KeepingBooks.Where(b => b.Book.MatureTime < DateTime.Now && !b.AppointingDateTime.HasValue))
{
fine += (DateTime.Now - book.Book.MatureTime.Value).Days * (decimal)0.2;
book.Book.State = book.Book.State == BookState.Appointed ? BookState.Appointed : BookState.Expired;
} student.Fine = fine;
PersonalInfoViewModel model = new PersonalInfoViewModel()
{
Student = student,
BookingBook = _lendingInfoDbContext.Books.FirstOrDefault(b => b.BarCode == student.AppointingBookBarCode)
};
return View(model);
}

视图:

 @model LibraryDemo.Models.PersonalInfoViewModel
@{
ViewData["Title"] = "PersonalInfo";
Layout = "_LendingLayout";
}
<link rel="stylesheet" href="~/css/BookInfo.css" />
<script>
function ensureCancel() {
if (confirm("确定取消预约?")) {
return true;
}
return false;
}
</script> <h2>@Model.Student.Name</h2>
<br />
@if (Model.Student.KeepingBooks.Any(b => b.Book.MatureTime < DateTime.Now))
{
<table>
<thead>
<tr>
<th colspan="">过期书籍</th>
</tr>
</thead>
<tr>
<th>书名</th>
<th>条形码</th>
<th>状态</th>
<th>到期时间</th>
<th>索书号</th>
</tr> @foreach (var matureBook in Model.Student.KeepingBooks.Where(b => b.Book.MatureTime < DateTime.Now && !b.AppointingDateTime.HasValue))
{
<tr>
<td>@matureBook.Book.Name</td>
<td>@matureBook.Book.BarCode</td>
<td>@Html.DisplayFor(b => matureBook.Book.State)</td>
<td>@matureBook.Book.MatureTime?.ToString("yyyy/MM/dd")</td>
<td>@matureBook.Book.FetchBookNumber</td>
</tr>
}
<tfoot><tr><td colspan="">罚款:@Model.Student.Fine</td></tr></tfoot>
</table>
}
<form asp-action="ReBorrow" method="post">
<table>
<tr>
<th>续借</th>
<th>书名</th>
<th>条形码</th>
<th>状态</th>
<th>到期时间</th>
<th>索书号</th>
</tr>
@if (!Model.Student.KeepingBooks.Any())
{
<tr>
<td colspan="" style="text-align: center">未借阅书本</td>
</tr>
}
else
{
foreach (var keepingBook in Model.Student.KeepingBooks.Where(b=>!b.AppointingDateTime.HasValue))
{
<tr>
<td><input type="checkbox" value="@keepingBook.Book.BarCode" name="barcodes"/></td>
<td>@keepingBook.Book.Name</td>
<td>@keepingBook.Book.BarCode</td>
<td>@Html.DisplayFor(b=>keepingBook.Book.State)</td>
<td>@keepingBook.Book.MatureTime?.ToString("yyyy/MM/dd")</td>
<td>@keepingBook.Book.FetchBookNumber</td>
</tr>
}
}
</table>
<br/>
<input type="submit" class="btn-primary btn" value="续借"/>
</form> <br />
@if (Model.BookingBook != null)
{
<form asp-action="CancelAppointing">
<table>
<tr>
<th>书名</th>
<th>条形码</th>
<th>状态</th>
<th>预约时间</th>
<th>索书号</th>
</tr>
<book-info book="@Model.BookingBook" is-booking-book="true"></book-info>
</table>
<br />
<input type="hidden" name="barcode" value="@Model.BookingBook.BarCode"/>
<input type="submit" value="取消预约" class="btn btn-danger" onclick="return ensureCancel()"/>
</form>
}

结果:

四、借阅书籍

由于暂时未有获取二维码的接口,仅通过直接访问 Lending 模拟借阅:

         [Authorize]
public async Task<IActionResult> Lending(string barcode)
{
Book targetBook=await _lendingInfoDbContext.Books.Include(b=>b.Appointments).FirstOrDefaultAsync(b => b.BarCode == barcode);
if (targetBook==null)
{
TempData["message"] = "请重新扫描书籍";
return RedirectToAction("PersonalInfo");
} if (targetBook.Appointments.Any(a=>a.AppointingDateTime.HasValue))
{
TempData["message"] = "此书已被预约";
return RedirectToAction("PersonalInfo");
} if (targetBook.State==BookState.Readonly)
{
TempData["message"] = "此书不供外借";
return RedirectToAction("PersonalInfo");
} targetBook.State = BookState.Borrowed;
targetBook.BorrowTime = DateTime.Now.Date;
targetBook.MatureTime = DateTime.Now.Date+TimeSpan.FromDays();
StudentInfo student =
await _lendingInfoDbContext.Students.Include(s=>s.KeepingBooks).FirstOrDefaultAsync(s => s.UserName == User.Identity.Name);
student.KeepingBooks.Add(new AppointmentOrLending()
{
BookId = targetBook.BarCode,
StudentId = student.UserName
});
await _lendingInfoDbContext.SaveChangesAsync();
TempData["message"] = "借书成功";
return RedirectToAction("PersonalInfo");
}

结果:

 六、续借书籍

动作方法:

         [Authorize]
[HttpPost]
public async Task<IActionResult> ReBorrow(IEnumerable<string> barcodes)
{
StringBuilder borrowSuccess = new StringBuilder();
StringBuilder borrowFail = new StringBuilder();
borrowSuccess.Append("成功续借书籍:");
borrowFail.Append("续借失败书籍:");
foreach (var barcode in barcodes)
{
Book reBorrowBook = _lendingInfoDbContext.Books.FirstOrDefault(b => b.BarCode == barcode);
if (reBorrowBook != null)
{
if (reBorrowBook.State == BookState.Borrowed && DateTime.Now-reBorrowBook.MatureTime?.Date<=TimeSpan.FromDays())
{
reBorrowBook.State = BookState.ReBorrowed;
reBorrowBook.BorrowTime = DateTime.Now.Date;
reBorrowBook.MatureTime = DateTime.Now.Date+TimeSpan.FromDays();
borrowSuccess.Append($"《{reBorrowBook.Name}》、");
}
else
{
borrowFail.Append($"《{reBorrowBook.Name}》、");
}
}
}
borrowSuccess.AppendLine(borrowFail.ToString());
await _lendingInfoDbContext.SaveChangesAsync();
TempData["message"] = borrowSuccess.ToString();
return RedirectToAction("PersonalInfo");
}

结果:

  

七、查询书籍

修改之前的 Search 方法使其通过当前用户的身份返回不同页面,以及在 _LendingInfoLayout 中添加搜索框部分:

19 行通过短路使未授权用户不用登录。

         public async Task<IActionResult> Search(string keyWord, string value)
{
BookDetails bookDetails = new BookDetails();
switch (keyWord)
{
case "Name":
bookDetails = await _lendingInfoDbContext.BooksDetail.AsNoTracking().FirstOrDefaultAsync(b => b.Name == value);
break;
case "ISBN":
bookDetails = await _lendingInfoDbContext.BooksDetail.AsNoTracking().FirstOrDefaultAsync(b => b.ISBN == value);
break;
case "FetchBookNumber":
bookDetails = await _lendingInfoDbContext.BooksDetail.AsNoTracking().FirstOrDefaultAsync(b => b.FetchBookNumber == value);
break;
} if (bookDetails != null)
{
if (User.Identity.IsAuthenticated&& User.IsInRole("Admin"))
{
return RedirectToAction("EditBookDetails", new { isbn = bookDetails.ISBN });
}
else
{
return RedirectToAction("Detail", new {isbn = bookDetails.ISBN});
}
} TempData["message"] = "找不到该书籍";
return RedirectToAction("BookDetails");
}

结果:

ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(六)学生借阅/预约/查询书籍事务的更多相关文章

  1. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(一) 基本模型以及数据库的建立

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  2. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(二)数据库初始化、基本登录页面以及授权逻辑的建立

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  3. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(七) 学生信息增删

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  4. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(五)外借/阅览图书信息的增删改查

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  5. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(四)图书信息的增删改查

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  6. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(三)密码修改以及密码重置

     前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/as ...

  7. 002.Create a web API with ASP.NET Core MVC and Visual Studio for Windows -- 【在windows上用vs与asp.net core mvc 创建一个 web api 程序】

    Create a web API with ASP.NET Core MVC and Visual Studio for Windows 在windows上用vs与asp.net core mvc 创 ...

  8. 在ASP.NET Core MVC中构建简单 Web Api

    Getting Started 在 ASP.NET Core MVC 框架中,ASP.NET 团队为我们提供了一整套的用于构建一个 Web 中的各种部分所需的套件,那么有些时候我们只需要做一个简单的 ...

  9. Pro ASP.NET Core MVC 第6版 第二章(前半章)

    目录 第二章 第一个MVC 应用程序 学习一个软件开发框架的最好方法是跳进他的内部并使用它.在本章,你将用ASP.NET Core MVC创建一个简单的数据登录应用.我将它一步一步地展示,以便你能看清 ...

随机推荐

  1. kibana dev tools快捷键

    kibana dev tools快捷键 ctrl+enter  提交请求 ctrl+i 自动缩进 ctrl+enter 提交请求 down 打开自动补全菜单 enter或tab 选中项自动补全 esc ...

  2. Version和Build的差别

    [1]概念 iOS的版本号号.一个叫做Version,一个叫做Build,这两个值都能够在Xcode 中选中target,点击"Summary"后看到. Version在plist ...

  3. Solidworks如何绘制标准螺纹线

    1 绘制螺旋线,螺距为0.5mm,圈数为15,起始角度为0°.   2                

  4. Intel Edison —— 控制GPIO接口,网页显示传感器数值

    前言 原创文章,转载引用务必注明链接. 因为是使用Typora(markdown)写好然后复制到论坛的,推荐直接访问我的网站以获得更好地阅读体验. Intel XDK IoT 开发环境很久之前就上手了 ...

  5. hihoCoder 1234 fractal

    #1234 : Fractal 时间限制:1000ms 单点时限:1000ms 内存限制:256MB 描述 This is the logo of PKUACM 2016. More specific ...

  6. Neutron配置Floating IP

    下图是由一个虚拟机vm1,一个路由器ext-router,两个网络ext-net及demo-net组成的拓扑结构.要达到的目的很简单,即vm1不仅能使用私网IP和内部的其它虚拟机进行通信,还可以通过e ...

  7. AOP和OOP的区别

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. AOP与OOP是面向不同领域的两种设计思想. ...

  8. JavaScript随机数区间限制

    在一段区间内的取某个数字 有一个通用的方法 主要使用了一下两个javascript函数 1.Math.floor() 方法可对一个数进行下舍入 2.Math.random() 方法可返回介于 0 ~ ...

  9. 基础才是重中之重~用好configSections让配置信息更规范

    对于小型项目来说,配置信息可以通过appSettings进行配置,而如果配置信息太多,appSettings显得有些乱,而且在开发人员调用时,也不够友好,节点名称很容易写错,这时,我们有几种解决方案 ...

  10. gradle in action 笔记

    原网址 https://lippiouyang.gitbooks.io/gradle-in-action-cn/content/