在前面几节中已经完成了service层和dao层,到目前为止只是后端的设计与编写,这节就要设计到前端的设计了。下面开始总结下这个秒杀业务前端有哪些要点:

1. 前端页面的流程

首先是列表页,点某个商品进入详情页,在这里会有个判断是否用户已登录的逻辑。如果已登录则进入详情页展示逻辑,如果用户未登录则让用户登录,将用户的信息写入cookie后再进入展示逻辑。对于详情页,它首先拿到系统的当前时间,将其与当前秒杀单的秒杀开始时间和秒杀结束时间作比较。若大于秒杀结束结束时间则显示秒杀已结束,若小于秒杀开始时间则显示秒杀未开始和倒计时。若在秒杀时间之内或者倒计时结束则显示商品秒杀地址,用户点击,执行秒杀,返回执行结果。

2. Restful接口设计

 具体什么是Restful呢?他是一种url设计规范,一种资源状态和资源状态的转移,关于Restful知识的具体讲解可以看这篇博文:我所理解的RESTful Web API

 业务的秒杀API设计如下:

Get/seckill/list     秒杀列表

Get/seckill/{id}/detail   详情页

Get/seckill/time/now  系统时间

Post/seckill/{id}/exposer  暴露秒杀

Post/seckill/{id}/execution  执行秒杀

其中:Get表示查询操作,Post表示添加/修改操作, Put表示修改操作,DELETE表示删除操作。通过不同的提交方式来表示它的动作,而后面的url设计遵循的规范是:/模块/资源/{标识}/集合1/...

3. SpringMVC框架

   这个应该是这个案例的重点知识点了吧,前面介绍了Mybatis和Spring的整合,现在来介绍Spring如何整合SpringMVC。首先是在web.xml中注册SpringMVC的核心控制器DispatcherServlet,并配置DispatcherServlet需要加载的配置文件。

web.xml

 <web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0"
metadata-complete="true">
<!--修改servlet版本为3.0--> <!-- 配置DispatcherServlet-->
<servlet>
<servlet-name>seckill-servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置springMVC需要加载的配置文件
spring-dao.xml, spring-service.xml, spring-web.xml
框架整合顺序:mybatis->spring->springMVC
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-*.xml</param-value>
</init-param>
</servlet> <servlet-mapping>
<servlet-name>seckill-servlet</servlet-name>
<!--默认匹配所有的请求-->
<url-pattern>/</url-pattern>
</servlet-mapping> </web-app> 

可以看到SpringMVC需要加载的文件分别是Spring的三个配置文件,spring-dao.xml,spring-service.xml,spring-web.xml。这三个配置文件分别配置了不同层上的东西,dao层,service层和web层。现在还没有spring-web.xml这个文件,在resource目录下的spring目录下新建这个文件用来配置web层的有关配置。

spring-web.xml

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--配置springMVC -->
<!--1:开启springMVC注解模式-->
<!--简化配置:
(1)自动注册DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter
(2)提供一系列:数据绑定,数字和日期format @NumberFormat,@DataTimeFormat,
xml,json默认读写支持-->
<mvc:annotation-driven/> <!-- 2:servlet-mapping 映射路劲:"/" -->
<!-- 静态资源默认servlet配置
(1).加入对静态资源的处理:js,gif,png
(2).允许使用"/"做整体映射
-->
<mvc:default-servlet-handler/> <!--3:配置jsp显示ViewResolver-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean> <!--4:扫描web相关的bean-->
<context:component-scan base-package="org.seckill.web"/>
</beans>

SpringMVC框架配置完成之后便开始编写业务的Controller,分发调度请求并调用Service层中相应的service来处理这些请求并返回模型和视图。在org.seckill目录下新建文件目录web用来存放Controller。新建SeckillController,负责对于Seckill这个资源模块的请求调度,代码如下:

SeckillController

 package org.seckill.web;

 import org.seckill.dao.SeckillResult;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.enums.SeckillStatEnum;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.exception.SeckillException;
import org.seckill.service.SeckillService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*; import java.util.Date;
import java.util.List; /**
* Created by yuxue on 2016/10/17.
*/
@Controller
@RequestMapping("/seckill")// url:/模块/资源/{id}/细分 / seckill/list
public class SeckillController {
private final Logger logger= LoggerFactory.getLogger(this.getClass()); @Autowired
private SeckillService seckillService; @RequestMapping(value="/list", method = RequestMethod.GET )
public String list(Model model) {
//获取列表页
List<Seckill> list=seckillService.getSeckillList();
model.addAttribute("list",list);
//list.jsp+model=ModelAndView
return "list";
} @RequestMapping(value = "/{seckillId}/detail",method=RequestMethod.GET)
public String detail(@PathVariable("seckillId") Long seckillId,Model model){
if(seckillId==null){
return "redirect:/seckill/list";//请求重定向
}
Seckill seckill=seckillService.getById(seckillId);
if(seckill==null){
return "forward:/seckill/list"; //请求转发
}
model.addAttribute("seckill",seckill);
return "detail";
} //ajax json
/*
@ResponseBody表示该方法的返回结果直接写入HTTP response body中
一般在异步获取数据时使用,在使用@RequestMapping后,返回值通常解析为跳转路径,
加上@ResponseBody后返回结果不会被解析为跳转路径,而是直接写入HTTP response body中。 */
@RequestMapping(value = "/{seckillId}/exposer",method=RequestMethod.GET,
produces={"application/json;charset=UTF-8"})
@ResponseBody
public SeckillResult<Exposer> exposer(@PathVariable Long seckillId){
SeckillResult<Exposer> result;
try{
Exposer exposer=seckillService.exportSeckillUrl(seckillId);
result=new SeckillResult<Exposer>(true,exposer);
}catch(Exception e){
logger.error(e.getMessage(),e);
result=new SeckillResult<Exposer>(false,e.getMessage());
}
return result;
} /*
从客户端的Cookie中获得用户手机号码,将required属性设置为false,否则若浏览器的cookie中未保存
手机号码的话Spring会报错,这里设置为false,将对用户手机号的验证写在我们自己的判断逻辑中
*/
@RequestMapping(value="/{seckillId}/{md5}/execution",method=RequestMethod.POST,
produces = "application/json;charset=UTF-8")
@ResponseBody
public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId") Long seckillId,
@PathVariable("md5") String md5,
@CookieValue(value="killphone",required=false)Long phone){
if(phone==null){
return new SeckillResult<SeckillExecution>(false,"未注册");
}
SeckillResult<SeckillExecution> result;
try {
94 SeckillExecution execution = seckillService.executeSeckill(seckillId, phone, md5);
return new SeckillResult<SeckillExecution>(true,execution);
}catch (RepeatKillException e){
//logger.error(e.getMessage(),e);没有必要打印日志,因为是系统允许的异常
SeckillExecution seckillExecution=new SeckillExecution(seckillId, SeckillStatEnum.REPEAT_KILL);
return new SeckillResult<SeckillExecution>(false,seckillExecution);
}catch(SeckillCloseException e){
SeckillExecution seckillExecution=new SeckillExecution(seckillId, SeckillStatEnum.END);
return new SeckillResult<SeckillExecution>(false,seckillExecution);
}catch (Exception e){
logger.error(e.getMessage(),e);
SeckillExecution seckillExecution=new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
return new SeckillResult<SeckillExecution>(false,seckillExecution);
}
} @RequestMapping(value="/time/now", method = RequestMethod.GET)
    public SeckillResult<Long> time(){
Date now=new Date();
return new SeckillResult<Long>(true,now.getTime());
} }

分析:

1.return "redirect:/seckill/list"和return "forward:/seckill/list"  分别实现了请求重定向和请求转发。

2.关于注解@ResponseBody和@RequestMapping中的produces

 1)@ResponseBody

   该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。即我们的Controller执行后不需要跳转到别的什么页面,只需要返回某种格式(json,xml)的数据时候就可以用使用这个注解啦!而@RequestMapping中的produces="application/json;charset=UTF-8"便指定了返回的内容类型为json,编码格式为UTF-8。具体解释见这篇博文:Spring MVC之@RequestMapping 详解以及这篇@RequestBody, @ResponseBody 注解详解

3.@CookieValue注解: 从Http请求头中的Cookie提取指定的某个Cookie,这里required=false表示如果没有这个cookie的话不让SpringMVC报错,而是在我们自己写的逻辑中处理。

4.public SeckillResult<SeckillExecution> execute() 这个方法中用到了个泛型SeckillResult<T>,这个类也是个数据传输对象,在dto包下新建类SeckillResult

SeckillResult

 package org.seckill.dao;

 /**
* Created by yuxue on 2016/10/18.
*/
//所有ajax请求返回类型,封装json结果
public class SeckillResult<T> { private boolean success; private T data; private String error; public SeckillResult(boolean success, T data) {
this.success = success;
this.data = data;
} public SeckillResult(boolean success, String error) {
this.success = success;
this.error = error;
} public boolean isSuccess() {
return success;
} public void setSuccess(boolean success) {
this.success = success;
} public T getData() {
return data;
} public void setData(T data) {
this.data = data;
} public String getError() {
return error;
} public void setError(String error) {
this.error = error;
}
}

分析:

一开始我不明白为什么要用这个类,打完代码后豁然醒悟:这个类其实封装了所有的请求返回类型!!!对于与Seckill有关的业务SeckillService,它里面有多个方法,一些方法要返回给前端数据,这里的问题便是数据类型可能会有很多种,比如暴露秒杀地址方法返回的是Exposer对象而execute方法返回的是SeckillExecution, 那么便用SeckillResult这个泛型来统一封装来自SeckillService的返回类型。前端要用到后端传来的数据时直接就从这里面去取相应的类型就好。这里是个很重要的设计思路:对于上层只提供统一的一致的接口,而底层复杂的细节则由这个接口封装起来,这些是我个人的理解,不知道对不对。

4. 基于Bootstrap开发页面

终于到了开发前端页面的时候了,老实说的话前端真的是不是很明白啊!感觉前端要记的东西太多了,各种杂七杂八的东西。这个案例是基于Bootstrap这个框架来开发前端页面的,Bootstrap其实帮你写好了前端控件的样式,拿过来直接用就可以开发出显示效果不错的前端页面了,自己不需要去写css样式。总共要写的页面有两个:list.jsp秒杀商品列表页和detail.jsp秒杀上屏详情页。

首先是编写list.jsp页面,为了使用Bootstrap框架,这个业务采用的是在线引入的方式所以必须要联网,总共要引入的文件有3个:Bootstrap的样式css文件,依赖的jQuery文件和核心javascript文件。关于Bootstrap的安装使用方式可以去Bootstrap中文网去查看一下。在webapp/WEB-INF目录下新建jsp目录来存放我们写的jsp文件

list.jsp

 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--引入jstl--%>
<%@include file="common/tag.jsp"%>
<!DOCTYPE html>
<head>
<title>秒杀列表页</title>
<%--jsp页面静态包含--%>
<%@include file="common/head.jsp"%>
</head>
<body>
<%--页面显示部分--%>
<div class="container">
<div class="panel panel-default">
<div clas="panel-heading text-center">
<h2>秒杀列表</h2>
</div>
<div class="panel-body">
<table class="table table-hover">
<thead>
<tr>
<th>名称</th>
<th>库存</th>
<th>开始时间</th>
<th>结束时间</th>
<th>创建时间</th>
<th>详情页</th>
</tr>
</thead>
<tbody>
<c:forEach var="sk" items="${list}">
<tr>
<td>${sk.name}</td>
<td>${sk.number}</td>
<td>
<fmt:formatDate value="${sk.startTime}" pattern="yyyy-MM-dd HH:mm:ss"/>
</td>
<td>
<fmt:formatDate value="${sk.endTime}" pattern="yyyy-MM-dd HH:mm:ss"/>
</td>
<td>
<fmt:formatDate value="${sk.createTime}" pattern="yyyy-MM-dd HH:mm:ss"/>
</td>
<td>
<%--target="_blank" 使得点击超链接后弹出的是新的页面--%>
<a class="btn btn-info" href="/seckill/${sk.seckillId}/detail" target="_blank">link</a>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
</div>
</body>
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="http://cdn.bootcss.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>
</html>

这里将页面要用到的jstl标签单独放到tag.jsp中

 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>

还有引入的head.jsp,作用是兼容平板,手机的页面显示,这也是bootstrap作为响应式布局的特点

 <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap -->
<link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css">

在webapp/WEB-INF目录下新建详情页面detail.jsp

detail.jsp

 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<title>秒杀详情页</title>
<%--jsp页面静态包含--%>
<%@include file="common/head.jsp"%>
</head>
<body>
<div class="container">
<div class="panel panel-default text-center">
<div class="pannel-heading">
<h1>${seckill.name}</h1>
</div>
<div class="panel-body">
<h2 class="text-danger">
<%--显示time图标--%>
<span class="glyphicon glyphicon-time"></span>
<%--展示倒计时--%>
<span class="glyphicon" id="seckill-box"></span>
</h2>
</div>
</div>
</div>
<%--登录弹出层,输出电话号码--%>
<div id="killPhoneModal" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title text-center">
<span class="glyphicon glyphicon-phone"></span>
</h3>
</div>
<div class="modal-body">
<div class="row">
<div class="col-xs-8 col-xs-offset-2">
<input type="text" name="killphone" id="killPhoneKey"
placeholder="填手机号^o^" class="form-control" >
</div>
</div>
</div> <div class="modal-footer">
<!--验证信息-->
<span id="killPhoneMessage" class="glyphicon"></span>
<button type="button" id="killPhoneBtn" class="btn btn-success">
<span class="glyphicon glyphicon-phone"></span>
</button>
</div>
</div>
</div>
</div>
</body>
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="http://cdn.bootcss.com/bootstrap/3.3.0/js/bootstrap.min.js"></script> <%--使用CDN获取公共js http://www.bootcdn.cn/ --%>
<%--jQuery cookie操作插件--%>
<script src="http://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<%--jQuery countdown倒计时插件--%>
<script src="http://cdn.bootcss.com/jquery.countdown/2.1.0/jquery.countdown.min.js"></script>
<%--开始编写交互逻辑--%>
<script src="/resources/script/seckill.js" type="text/javascript"></script><%--老师说这里有个小坑,结尾必须写</script>这种形式,不能是/>,否则下面的js不会加载--%>
<script type="text/javascript">
$(function () {
//使用El表达式传入参数
seckill.detail.init({
seckillId:${seckill.seckillId},
startTime:${seckill.startTime.time},
endTime:${seckill.endTime.time}
});
});
</script>
</html> 

1.开启tomacat服务器,测试网页能否正常访问,跳转。这里出现了个小问题:list.jsp中的跳转的detail.jsp的超链接<a class="btn btn-info" href="/seckill/${sk.seckillId}/detail" target="_blank">link</a>我把其中的detail写成了detail.jsp结果Controller不能拦截到路径,查了下Controller中RequestMapping的配置路径为@RequestMapping(value = "/{seckillId}/detail",method=RequestMethod.GET),明白了,原来这个路径要“一模一样”,我之前认为只要在SpringMVC的DispatcherServlet里配置拦截路径为所有路径的话,那么就是拦截所有的html和jsp,所以后缀名什么的应该不重要,只要名字相同就可......傻叉了一回....,这个细节以后要注意。

2.关于<div id="killPhoneModal" class="modal fade">,这是个Bootstrap中的模态框插件,模态框(Modal)是覆盖在父窗体上的子窗体。通常,目的是显示来自一个单独的源的内容,可以在不离开父窗体的情况下有一些互动。子窗体可提供信息、交互等。在模态框中需要注意两点:第一是 .modal,用来把 <div> 的内容识别为模态框。第二是 .fade class。当模态框被切换时,它会引起内容淡入淡出。具体的使用见:http://www.runoob.com/bootstrap/bootstrap-modal-plugin.html

在webapp/resources目录下新建script目录,用来存放我们写的的页面交互脚本。新建seckill.js

 //存放主要交互逻辑js代码
//javascript 模块化
var seckill={
//封装秒杀相关ajax的url
URL:{
now:function () {
return '/seckill/time/now';
},
exposer:function (seckillId) {
return '/seckill/'+seckillId+'/exposer';
},
execution:function(seckillId,md5){
return 'seckill/'+seckillId+'/'+md5+'/execution';
}
},
handleSeckillkill:function (seckillId,node) {
//处理秒杀逻辑,控制现实逻辑,执行秒杀
node.hide().html('<button class="btn btn-primary btn-lg" id="killBtn">开始秒杀</button>');
$.post(seckill.URL.exposer(seckillId),{},function (result) {
//在回调函数中,执行交互流程
if(result&&result['success']){
var exposer=result['data'];
if(exposer['exposed']){
//开启秒杀
var md5=exposer['md5'];
var killUrl=seckill.URL.execution(seckillId,md5);
console.log("killUrl:"+killUrl);
//绑定一次点击事件
$('#killBtn').one('click',function () {
//执行秒杀请求
//1.先禁用按钮
$(this).addClass('disabled');
//2.发送秒杀请求执行秒杀
$.post(killUrl,{},function (result) {
if(result&&result['success']){
var killResult=result['data'];
var state=killResult['data'];
var stateInfo=killResult['stateInfo'];
//3.显示秒杀结果
node.html('<span class="label label-success">'+stateInfo+'</span>');
}
});
});
node.show();
}else{
//未开启秒杀
var now=exposer['now'];
var start=exposer['start'];
var end=exposer['end'];
//重新计算计时逻辑
seckillId.countdown(seckillId,now,start,end);
}
}else{
console.log('result:'+result);
}
});
},
//验证手机号
validatePhone: function (phone) {
if (phone && phone.length == 11 && !isNaN(phone)) {
return true;
} else {
return false;
}
},
countdown:function (seckillId,nowTime,startTime,endTime) {
var seckillBox=$('#seckill-box');
//时间判断
if(nowTime>endTime){
//秒杀结束
seckillBox.html('秒杀结束!');
}else if(nowTime<startTime){
//秒杀未开始,计时事件绑定
var killTime=new Date(startTime+1000);
seckillBox.countdown(killTime,function(event){
//时间格式
var format=event.strftime('秒杀倒计时:%D天 %H时 %M分 %S秒');
seckillBox.html(format);
/*时间完成后回调事件*/
}).on('finish.countdown',function () {
//获取秒杀地址,控制现实逻辑,执行秒杀
seckill.handleSeckillkill(seckillId,seckillBox);
});
}else{
//秒杀开始
seckill.handleSeckillkill(seckillId,seckillBox);
}
},
//详情秒杀页逻辑
detail: {
//详情页初始化
init: function (params) {
//手机验证和登陆,计时交互
//规划我们的交互流程
//在cookie中查找手机号
var killPhone = $.cookie('killPhone');
var startTime = params['startTime'];
var endTime = params['endTime'];
var seckillId = params['seckillId'];
//验证手机号码
if (!seckill.validatePhone(killPhone)){
//绑定phone
//控制输出
var killPhoneModal=$('#killPhoneModal');
//显示弹出层
killPhoneModal.modal({
show:true,//显示弹出层
backdrop:'static',//禁止位置关闭
keyboard:false//关闭键盘事件
});
$('#killPhoneBtn').click(function(){
var inputPhone=$('#killPhoneKey').val();
console.log('inputPhone='+inputPhone);//TODO
if(seckill.validatePhone(inputPhone)){
//电话写入cookie
$.cookie('killPhone',inputPhone,{expires:7,path:'/seckill'});
//刷新页面
window.location.reload();
}else{
$('#killPhoneMessage').hide().html('<label class="label label-danger">手机号错误!</label>').show(300);
}
});
}
//已经登录
var startTime = params['startTime'];
var endTime = params['endTime'];
var seckillId = params['seckillId'];
$.get(seckill.URL.now(),{},function(result){
if(result&&result['success']){
var nowTime=result['data'];
//时间判断,计时交互
seckill.countdown(seckillId,nowTime,startTime,endTime);
}else{
console.log('result:'+result);
}
}
);
}
}
}

还是吐槽一句:前端真心不熟啊。。。。js,jQuery什么的,要学的东西还有很多啊。

到现在前端的编写算是完成了,下一节讲的是怎么优化这个业务让其能承受更多的并发量,呼呼呼,写到这里不容易啊,哪里搞错了的望指正,谢谢。

最新文章

  1. android/java 根据当前时间判断股票交易状态(未开盘 交易中 休市中 已收盘)
  2. python中的模块
  3. C++的那些事:函数全解析
  4. 使用VS2013调试FluorineFx程序
  5. 【暑假】[实用数据结构]UVAlive 3027 Corporative Network
  6. JSTL之迭代标签库
  7. Java model 对象处理
  8. juce中的引用计数
  9. linux之sed
  10. 怎么给没链接的flash加超链接
  11. Java应用开发中的字符集与字符编码
  12. nyoj 非洲小孩
  13. 野路子码农系列(1) 创建Web API
  14. Jmeter选项含义
  15. UISplitViewController使用
  16. spring开发需要的配置文件
  17. 学习笔记TF023:下载、缓存、属性字典、惰性属性、覆盖数据流图、资源
  18. nginx升级教程
  19. plsql 用法和技巧
  20. 查看eclipse版本信息

热门文章

  1. js match() 方法
  2. Bootstrap modal被sliverlight掩盖。
  3. vue.js入门(3)——组件通信
  4. FingerGestures 屏蔽NGUI的方法
  5. This application is modifying the autolayout engine from a background threa-线程错误
  6. java 8增强的包装类
  7. Boadload和Image$$??$$Limit含义
  8. Egret官方案例学习笔记
  9. 2016年上半年金融类App成绩单,手机银行优势尽显! (转自Analysys易观(ID:enfodesk))
  10. Spring中的事物管理,基于spring的bean的配置