getSeckillList();
/**
* 查询单个秒杀记录
*
* @param seckillId
* @return
*/
Seckill getById(long seckillId);
/**
* 秒杀开启时输出秒杀接口地址,否则输出系统时间和秒杀时间
*
* @param seckillId
* @return
*/
Exposer exportSeckillUrl(long seckillId);
/**
* 执行秒杀操作
*
* @param seckillId
* @param userPhone
* @param md5
* @return
* @throws SeckillException
* @throws RepeatKillException
* @throws SeckillCloseException
*/
SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
throws SeckillException, RepeatKillException, SeckillCloseException;
/**
* 执行秒杀操作by存储过程
*
* @param seckillId
* @param userPhone
* @param md5
* @return
* @throws SeckillException
* @throws RepeatKillException
* @throws SeckillCloseException
*/
SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5)
throws SeckillException, RepeatKillException, SeckillCloseException;
}
================================================
FILE: seckill-core/.gitignore
================================================
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
================================================
FILE: seckill-core/pom.xml
================================================
seckill
com.imooc
0.0.1-SNAPSHOT
4.0.0
seckill-core
war
seckill-dubbo-consumer Maven Webapp
http://maven.apache.org
org.apache.maven.plugins
maven-compiler-plugin
1.8
1.8
com.imooc
seckill-model
0.0.1-SNAPSHOT
com.imooc
seckill-client
0.0.1-SNAPSHOT
com.alibaba
dubbo
2.5.7
spring
org.springframework
org.apache.zookeeper
zookeeper
3.5.3-beta
log4j
log4j
com.101tec
zkclient
0.8
================================================
FILE: seckill-core/src/main/java/com/imooc/aop/LogAOP.java
================================================
package com.imooc.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
/**
* @author yan
*
* 采用AOP的方式处理参数问题。
*/
@Component
@Aspect
public class LogAOP {
private final Logger LOG = LoggerFactory.getLogger(this.getClass());
@Pointcut("execution(* com.imooc.web.*.*(..))")
public void aopMethod() {
}
@Around("aopMethod()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String classType = joinPoint.getTarget().getClass().getName();
//运用反射的原理创建对象
Class> clazz = Class.forName(classType);
String clazzName = clazz.getName();
String clazzSimpleName = clazz.getSimpleName();
String methodName = joinPoint.getSignature().getName();
Logger logger = LoggerFactory.getLogger(clazzName);
logger.info("clazzName: " + clazzName + ", methodName:" + methodName);
long start = System.currentTimeMillis();
LOG.info("before method invoking!");
BindingResult bindingResult = null;
System.out.println("---------------");
System.out.println("调用类:" + clazzSimpleName);
System.out.println("调用方法:" + methodName);
return joinPoint.proceed();
}
}
================================================
FILE: seckill-core/src/main/java/com/imooc/web/SeckillController.java
================================================
package com.imooc.web;
import com.imooc.dto.SeckillExecution;
import com.imooc.exception.SeckillCloseException;
import com.imooc.dto.Exposer;
import com.imooc.dto.SeckillResult;
import com.imooc.entity.Seckill;
import com.imooc.enums.SeckillStateEnum;
import com.imooc.exception.RepeatKillException;
import com.imooc.client.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;
@Controller // @Service @Componet
@RequestMapping("/seckill") // url:/模块/资源/{id}/细分 /seckill/list
public class SeckillController {
//sb
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private SeckillService seckillService;
//http://localhost:8080/seckill/seckill/list
@RequestMapping(value = "/list", method = RequestMethod.GET)
public String list(Model model) {
// 获取列表页
List list = seckillService.getSeckillList();
model.addAttribute("list", list);
// list.jsp + model = ModelAndView
return "list";// WEB-INF/jsp/"list".jsp
}
@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
@RequestMapping(value = "/{seckillId}/exposer", method = RequestMethod.POST, produces = {
"application/json; charset=utf-8" })
@ResponseBody
public SeckillResult exposer(@PathVariable("seckillId") Long seckillId) {
SeckillResult result;
try {
Exposer exposer = seckillService.exportSeckillUrl(seckillId);
result = new SeckillResult(true, exposer);
} catch (Exception e) {
logger.error(e.getMessage(), e);
result = new SeckillResult(false, e.getMessage());
}
return result;
}
@RequestMapping(value = "/{seckillId}/{md5}/execution", method = RequestMethod.POST, produces = {
"application/json; charset=utf-8" })
@ResponseBody
public SeckillResult execute(@PathVariable("seckillId") Long seckillId,
@PathVariable("md5") String md5, @CookieValue(value = "killPhone", required = false) Long phone) {
// springmvc valid
if (phone == null) {
return new SeckillResult(false, "未注册");
}
try {
// 存储过程调用
SeckillExecution execution = seckillService.executeSeckillProcedure(seckillId, phone, md5);
return new SeckillResult(true, execution);
} catch (RepeatKillException e) {
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.REPEAT_KILL);
return new SeckillResult(true, execution);
} catch (SeckillCloseException e) {
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.END);
return new SeckillResult(true, execution);
} catch (Exception e) {
logger.error(e.getMessage(), e);
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
return new SeckillResult(true, execution);
}
}
@RequestMapping(value = "/time/now", method = RequestMethod.GET)
@ResponseBody
public SeckillResult time() {
Date now = new Date();
return new SeckillResult(true, now.getTime());
}
}
================================================
FILE: seckill-core/src/main/resources/logback.xml
================================================
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
================================================
FILE: seckill-core/src/main/resources/spring/applicationContext.xml
================================================
================================================
FILE: seckill-core/src/main/resources/spring/spring-dubbo-config.xml
================================================
================================================
FILE: seckill-core/src/main/resources/spring/spring-dubbo-consumer.xml
================================================
================================================
FILE: seckill-core/src/main/resources/spring/spring-web.xml
================================================
================================================
FILE: seckill-core/src/main/webapp/WEB-INF/jsp/common/head.jsp
================================================
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ path + "/";
pageContext.setAttribute("basePath",basePath);
%>
================================================
FILE: seckill-core/src/main/webapp/WEB-INF/jsp/common/tag.jsp
================================================
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
================================================
FILE: seckill-core/src/main/webapp/WEB-INF/jsp/detail.jsp
================================================
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@include file="common/head.jsp"%>
秒杀详情页
================================================
FILE: seckill-core/src/main/webapp/WEB-INF/jsp/list.jsp
================================================
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@include file="common/tag.jsp" %>
<%@include file="common/head.jsp"%>
秒杀列表页
秒杀列表
| 名称 |
库存 |
开始时间 |
结束时间 |
创建时间 |
详情页 |
| ${sk.name} |
${sk.number} |
|
|
|
link
|
================================================
FILE: seckill-core/src/main/webapp/WEB-INF/web.xml
================================================
seckill-dispatcher
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring/applicationContext.xml
seckill-dispatcher
/
contextConfigLocation
classpath:spring/applicationContext.xml
org.springframework.web.context.ContextLoaderListener
================================================
FILE: seckill-core/src/main/webapp/resources/js/seckill.js
================================================
// 存放主要交换逻辑js代码
// javascript 模块化
var seckill = {
// 封装秒杀相关ajax的url
URL : {
basePath : function() {
return $('#basePath').val();
},
now : function() {
return seckill.URL.basePath() + 'seckill/time/now';
},
exposer : function(seckillId) {
return seckill.URL.basePath() + 'seckill/' + seckillId + '/exposer';
},
execution : function(seckillId, md5) {
return seckill.URL.basePath() + 'seckill/' + seckillId + '/' + md5 + '/execution';
}
},
// 处理秒杀逻辑
handleSeckill : function(seckillId, node) {
// 获取秒杀地址,控制显示逻辑,执行秒杀
node.hide().html('');
console.log('exposerUrl=' + seckill.URL.exposer(seckillId));//TODO
$.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);//TODO
$('#killBtn').one('click', function() {
// 执行秒杀请求
// 1.先禁用按钮
$(this).addClass('disabled');
// 2.发送秒杀请求
$.post(killUrl, {}, function(result) {
if (result && result['success']) {
var killResult = result['data'];
var state = killResult['state'];
var stateInfo = killResult['stateInfo'];
// 3.显示秒杀结果
node.html('' + stateInfo + '');
}
});
});
node.show();
} else {
// 未开启秒杀
var now = exposer['now'];
var start = exposer['start'];
var end = exposer['end'];
// 重新计算计时逻辑
seckill.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 = $('#seckillBox');
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.handleSeckill(seckillId, seckillBox);
});
} else {
// 秒杀开始
seckill.handleSeckill(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('').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['reult:'] + result);
}
});
}
}
}
================================================
FILE: seckill-model/pom.xml
================================================
seckill
com.imooc
0.0.1-SNAPSHOT
4.0.0
seckill-model
================================================
FILE: seckill-model/src/main/java/com/imooc/dto/Exposer.java
================================================
package com.imooc.dto;
import java.io.Serializable;
/**
* 暴露秒杀接口DTO
*
* @author yan
*/
public class Exposer implements Serializable {
// 是否开启秒杀
private boolean exposed;
// 一种加密措施
private String md5;
// id
private long seckillId;
// 系统当前时间(毫秒)
private long now;
// 开启时间
private long start;
// 结束时间
private long end;
public Exposer(boolean exposed, String md5, long seckillId) {
this.exposed = exposed;
this.md5 = md5;
this.seckillId = seckillId;
}
public Exposer(boolean exposed, long seckillId, long now, long start, long end) {
this.exposed = exposed;
this.seckillId = seckillId;
this.now = now;
this.start = start;
this.end = end;
}
public Exposer(boolean exposed, long seckillId) {
this.exposed = exposed;
this.seckillId = seckillId;
}
public boolean isExposed() {
return exposed;
}
public void setExposed(boolean exposed) {
this.exposed = exposed;
}
public String getMd5() {
return md5;
}
public void setMd5(String md5) {
this.md5 = md5;
}
public long getSeckillId() {
return seckillId;
}
public void setSeckillId(long seckillId) {
this.seckillId = seckillId;
}
public long getNow() {
return now;
}
public void setNow(long now) {
this.now = now;
}
public long getStart() {
return start;
}
public void setStart(long start) {
this.start = start;
}
public long getEnd() {
return end;
}
public void setEnd(long end) {
this.end = end;
}
//重写toString方法
@Override
public String toString() {
return "Exposer [exposed=" + exposed + ", md5=" + md5 + ", seckillId=" + seckillId + ", now=" + now + ", start="
+ start + ", end=" + end + "]";
}
}
================================================
FILE: seckill-model/src/main/java/com/imooc/dto/SeckillExecution.java
================================================
package com.imooc.dto;
import com.imooc.entity.SuccessKilled;
import com.imooc.enums.SeckillStateEnum;
import java.io.Serializable;
/**
* 封装秒杀执行后结果
*
* @author yan
*/
public class SeckillExecution implements Serializable {
private Long seckillId;
// 秒杀执行结果状态
private Integer state;
// 状态标识
private String stateInfo;
// 秒杀成功对象
private SuccessKilled successKilled;
public SeckillExecution(Long seckillId, SeckillStateEnum stateEnum, SuccessKilled successKilled) {
this.seckillId = seckillId;
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
this.successKilled = successKilled;
}
public SeckillExecution(Long seckillId, SeckillStateEnum stateEnum) {
this.seckillId = seckillId;
if(stateEnum!=null){
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
}
}
public Long getSeckillId() {
return seckillId;
}
public void setSeckillId(Long seckillId) {
this.seckillId = seckillId;
}
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
public String getStateInfo() {
return stateInfo;
}
public void setStateInfo(String stateInfo) {
this.stateInfo = stateInfo;
}
public SuccessKilled getSuccessKilled() {
return successKilled;
}
public void setSuccessKilled(SuccessKilled successKilled) {
this.successKilled = successKilled;
}
@Override
public String toString() {
return "SeckillExecution [seckillId=" + seckillId + ", state=" + state + ", stateInfo=" + stateInfo
+ ", successKilled=" + successKilled + "]";
}
}
================================================
FILE: seckill-model/src/main/java/com/imooc/dto/SeckillResult.java
================================================
package com.imooc.dto;
//封装json结果
public class SeckillResult {
private boolean success;
private T data;
private String error;
public SeckillResult(boolean success, String error) {
this.success = success;
this.error = error;
}
public SeckillResult(boolean success, T data) {
this.success = success;
this.data = data;
}
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;
}
@Override
public String toString() {
return "SeckillResult [success=" + success + ", data=" + data + ", error=" + error + "]";
}
}
================================================
FILE: seckill-model/src/main/java/com/imooc/entity/Seckill.java
================================================
package com.imooc.entity;
import java.io.Serializable;
import java.util.Date;
/**
* 秒杀库存实体
*
* @author yan
*/
public class Seckill implements Serializable {
private long seckillId;
private String name;
private int number;
private Date startTime;
private Date endTime;
private Date createTime;
public long getSeckillId() {
return seckillId;
}
public void setSeckillId(long seckillId) {
this.seckillId = seckillId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Date getStartTime() {
return startTime;
}
public void setStartTime(Date startTime) {
this.startTime = startTime;
}
public Date getEndTime() {
return endTime;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Seckill [seckillId=" + seckillId + ", name=" + name + ", number=" + number + ", startTime=" + startTime
+ ", endTime=" + endTime + ", createTime=" + createTime + "]";
}
}
================================================
FILE: seckill-model/src/main/java/com/imooc/entity/SuccessKilled.java
================================================
package com.imooc.entity;
import java.io.Serializable;
import java.util.Date;
/**
* 成功秒杀实体
*
* @author yan
*/
public class SuccessKilled implements Serializable {
private long seckillId;
private long userPhone;
private short state;
private Date creteTime;
// 多对一的复合属性
private Seckill seckill;
public long getSeckillId() {
return seckillId;
}
public void setSeckillId(long seckillId) {
this.seckillId = seckillId;
}
public long getUserPhone() {
return userPhone;
}
public void setUserPhone(long userPhone) {
this.userPhone = userPhone;
}
public short getState() {
return state;
}
public void setState(short state) {
this.state = state;
}
public Date getCreteTime() {
return creteTime;
}
public void setCreteTime(Date creteTime) {
this.creteTime = creteTime;
}
public Seckill getSeckill() {
return seckill;
}
public void setSeckill(Seckill seckill) {
this.seckill = seckill;
}
@Override
public String toString() {
return "SuccessKilled [seckillId=" + seckillId + ", userPhone=" + userPhone + ", state=" + state
+ ", creteTime=" + creteTime + "]";
}
}
================================================
FILE: seckill-model/src/main/java/com/imooc/enums/SeckillStateEnum.java
================================================
package com.imooc.enums;
/**
* 使用枚举表述常量数据字典
*
* @author yan
*/
public enum SeckillStateEnum {
SUCCESS(1, "秒杀成功"), END(0, "秒杀结束"),
REPEAT_KILL(-1, "重复秒杀"),
INNER_ERROR(-2, "系统异常"),
DATA_REWRITE(-3, "数据篡改");
private Integer state;
private String stateInfo;
private SeckillStateEnum(int state, String stateInfo) {
this.state = state;
this.stateInfo = stateInfo;
}
public Integer getState() {
return state;
}
public String getStateInfo() {
return stateInfo;
}
public static SeckillStateEnum stateOf(Integer index) {
for (SeckillStateEnum state : values()) {
if (state.getState().equals(index)) {
return state;
}
}
return null;
}
}
================================================
FILE: seckill-model/src/main/java/com/imooc/exception/RepeatKillException.java
================================================
package com.imooc.exception;
/**
* 重复秒杀异常(运行期异常)
*
* @author yan
*/
public class RepeatKillException extends SeckillException {
public RepeatKillException(String message) {
super(message);
}
public RepeatKillException(String message, Throwable cause) {
super(message, cause);
}
}
================================================
FILE: seckill-model/src/main/java/com/imooc/exception/SeckillCloseException.java
================================================
package com.imooc.exception;
/**
* 秒杀关闭异常
*
* @author yan
*/
public class SeckillCloseException extends SeckillException {
public SeckillCloseException(String message) {
super(message);
}
public SeckillCloseException(String message, Throwable cause) {
super(message, cause);
}
}
================================================
FILE: seckill-model/src/main/java/com/imooc/exception/SeckillException.java
================================================
package com.imooc.exception;
/**
* 秒杀相关业务异常
*
* @author yan
*/
public class SeckillException extends RuntimeException {
public SeckillException(String message) {
super(message);
}
public SeckillException(String message, Throwable cause) {
super(message, cause);
}
}
================================================
FILE: seckill-service-provider/pom.xml
================================================
seckill
com.imooc
0.0.1-SNAPSHOT
4.0.0
seckill-service
war
seckill-dubbo-consumer Maven Webapp
http://maven.apache.org
org.apache.maven.plugins
maven-compiler-plugin
1.6
1.6
com.imooc
seckill-model
0.0.1-SNAPSHOT
com.imooc
seckill-client
0.0.1-SNAPSHOT
com.alibaba
dubbo
2.5.7
spring
org.springframework
org.apache.zookeeper
zookeeper
3.5.3-beta
log4j
log4j
com.101tec
zkclient
0.8
================================================
FILE: seckill-service-provider/src/main/java/com/imooc/dao/SeckillDao.java
================================================
package com.imooc.dao;
import com.imooc.entity.Seckill;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 秒杀库存DAO接口
*
* @author yan
*/
public interface SeckillDao {
/**
* 减库存
*
* @param seckillId
* @param killTime
* @return 如果影响行数等于>1,表示更新的记录行数
*/
int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime") Date killTime);
/**
* 根据id查询秒杀对象
*
* @param seckillId
* @return
*/
Seckill queryById(long seckillId);
/**
* 根据偏移量查询秒杀商品列表
*
* @param offset
* @param limit
* @return
*/
List queryAll(@Param("offset") int offset, @Param("limit") int limit);
/**
* 使用存储过程执行秒杀
*
* @param paramMap
*/
void killByProcedure(Map paramMap);
}
================================================
FILE: seckill-service-provider/src/main/java/com/imooc/dao/SuccessKilledDao.java
================================================
package com.imooc.dao;
import com.imooc.entity.SuccessKilled;
import org.apache.ibatis.annotations.Param;
public interface SuccessKilledDao {
/**
* 插入购买明细,可过滤重复
*
* @param seckillId
* @param userPhone
* @return 插入的行数
*/
int insertSuccessKilled(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);
/**
* 根据id查询SuccessKilled并携带秒杀产品对象实体
*
* @param seckillId
* @param userPhone
* @return
*/
SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);
}
================================================
FILE: seckill-service-provider/src/main/java/com/imooc/dao/cache/RedisDao.java
================================================
package com.imooc.dao.cache;
import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import com.imooc.entity.Seckill;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class RedisDao {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final JedisPool jedisPool;
private RuntimeSchema schema = RuntimeSchema.createFrom(Seckill.class);
public RedisDao(String ip, int port) {
jedisPool = new JedisPool(ip, port);
}
public Seckill getSeckill(long seckillId) {
// redis操作逻辑
try {
Jedis jedis = jedisPool.getResource();
try {
String key = "seckill:" + seckillId;
// 并没有实现内部序列化操作
// get -> byte[] -> 反序列化 -> object[Seckill]
// 采用自定义序列化
// protostuff : pojo.
byte[] bytes = jedis.get(key.getBytes());
if (bytes != null) {
Seckill seckill = schema.newMessage();
ProtostuffIOUtil.mergeFrom(bytes, seckill, schema);
// seckill被反序列化
return seckill;
}
} finally {
jedis.close();
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
public String putSeckill(Seckill seckill) {
// set Object(Seckill) -> 序列号 -> byte[]
try {
Jedis jedis = jedisPool.getResource();
try {
String key = "seckill:" + seckill.getSeckillId();
byte[] bytes = ProtostuffIOUtil.toByteArray(seckill, schema,
LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
// 超时缓存
int timeout = 60 * 60;
String result = jedis.setex(key.getBytes(), timeout, bytes);
return result;
} finally {
jedis.close();
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
}
================================================
FILE: seckill-service-provider/src/main/java/com/imooc/service/impl/App.java
================================================
package com.imooc.service.impl;
import com.imooc.client.SeckillService;
import com.imooc.entity.Seckill;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
import java.util.List;
public class App {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"spring/applicationContext.xml"});
context.start();
SeckillService seckillService = context.getBean("seckillService",SeckillService.class);
List getSeckillList = seckillService.getSeckillList();
System.out.println(getSeckillList);
System.out.println("press any key to exit.");
System.in.read();
}
}
================================================
FILE: seckill-service-provider/src/main/java/com/imooc/service/impl/SeckillServiceImpl.java
================================================
package com.imooc.service.impl;
import com.imooc.dao.SeckillDao;
import com.imooc.dao.SuccessKilledDao;
import com.imooc.dao.cache.RedisDao;
import com.imooc.dto.Exposer;
import com.imooc.dto.SeckillExecution;
import com.imooc.entity.Seckill;
import com.imooc.entity.SuccessKilled;
import com.imooc.enums.SeckillStateEnum;
import com.imooc.exception.RepeatKillException;
import com.imooc.exception.SeckillCloseException;
import com.imooc.exception.SeckillException;
import com.imooc.client.SeckillService;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
//@Componet @Service @Dao @Controller
@Service
public class SeckillServiceImpl implements SeckillService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
// 注入Service依赖
@Autowired
private SeckillDao seckillDao;
@Autowired
private SuccessKilledDao successKilledDao;
@Autowired
private RedisDao redisDao;
// md5盐值字符串,用于混淆MD5
private final String slat = "sdfsgsfjks;sf,lasmglksmg";
@Override
public List getSeckillList() {
return seckillDao.queryAll(0, 6);
}
@Override
public Seckill getById(long seckillId) {
return seckillDao.queryById(seckillId);
}
private String getMD5(long seckillId) {
String base = seckillId + "/" + slat;
String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
}
@Override
public Exposer exportSeckillUrl(long seckillId) {
// 优化点:缓存优化:超时的基础上维护一致性
// 1.访问redis
Seckill seckill = redisDao.getSeckill(seckillId);
if (seckill == null) {
// 2.访问数据库
seckill = seckillDao.queryById(seckillId);
if (seckill == null) {
return new Exposer(false, seckillId);
} else {
// 3.访问redis
redisDao.putSeckill(seckill);
}
}
if (seckill == null) {
return new Exposer(false, seckillId);
}
Date startTime = seckill.getStartTime();
Date endTime = seckill.getEndTime();
// 系统当前时间
Date nowTime = new Date();
if (nowTime.getTime() < startTime.getTime() || nowTime.getTime() > endTime.getTime()) {
return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(), endTime.getTime());
}
// 转化特定字符串的过程,不可逆
String md5 = getMD5(seckillId);
return new Exposer(true, md5, seckillId);
}
@Override
@Transactional
/**
* 使用注解控制事务方法的优点: 1.开发团队达成一致约定,明确标注事务方法的编程风格
* 2.保证事务方法的执行时间尽可能短,不要穿插其他网络操作,RPC/HTTP请求或者剥离到事务方法外部
* 3.不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制
*/
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
throws SeckillException, RepeatKillException, SeckillCloseException {
if (md5 == null || !md5.equals(getMD5(seckillId))) {
throw new SeckillException("seckill data rewrite");
}
// 执行秒杀逻辑:减库存 + 记录购买行为
Date now = new Date();
try {
// 记录购买行为
int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);
// 唯一:seckillId,userPhone
if (insertCount <= 0) {
// 重复秒杀
throw new RepeatKillException("seckill repeated");
} else {
// 减库存,热点商品竞争
int updateCount = seckillDao.reduceNumber(seckillId, now);
if (updateCount <= 0) {
// 没有更新到记录 rollback
throw new SeckillCloseException("seckill is closed");
} else {
// 秒杀成功 commit
SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, successKilled);
}
}
} catch (SeckillCloseException e1) {
throw e1;
} catch (RepeatKillException e2) {
throw e2;
} catch (Exception e) {
logger.error(e.getMessage(), e);
// 所有编译期异常转换为运行期异常
throw new SeckillException("seckill inner error:" + e.getMessage());
}
}
@Override
public SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) {
if (md5 == null || !md5.equals(getMD5(seckillId))) {
return new SeckillExecution(seckillId, SeckillStateEnum.DATA_REWRITE);
}
Date killTime = new Date();
Map map = new HashMap();
map.put("seckillId", seckillId);
map.put("phone", userPhone);
map.put("killTime", killTime);
map.put("result", null);
// 执行存储过程,result被赋值
try {
seckillDao.killByProcedure(map);
// 获取result
int result = MapUtils.getInteger(map, "result", -2);
if (result == 1) {
SuccessKilled sk = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, sk);
} else {
return new SeckillExecution(seckillId, SeckillStateEnum.stateOf(result));
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
}
}
}
================================================
FILE: seckill-service-provider/src/main/resources/dubbo.proerpties
================================================
dubbo.application.name=seckill-service
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.registry.check=false
dubbo.protocol.name=dubbo
================================================
FILE: seckill-service-provider/src/main/resources/jdbc.properties
================================================
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/seckill?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=root
#ʼӴС
jdbc.initialSize=0
#ӳ
jdbc.maxActive=20
#ӳС
jdbc.minIdle=1
#ȡȴʱ
jdbc.maxWait=60000
================================================
FILE: seckill-service-provider/src/main/resources/logback.xml
================================================
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
================================================
FILE: seckill-service-provider/src/main/resources/mapper/SeckillDao.xml
================================================
UPDATE seckill
SET number = number - 1
WHERE
seckill_id = #{seckillId}
AND start_time #{killTime}
AND end_time >= #{killTime}
AND number > 0
================================================
FILE: seckill-service-provider/src/main/resources/mapper/SuccessKilledDao.xml
================================================
INSERT ignore INTO success_killed (seckill_id, user_phone, state)
VALUES (#{seckillId}, #{userPhone}, 0)
================================================
FILE: seckill-service-provider/src/main/resources/mybatis-config.xml
================================================
================================================
FILE: seckill-service-provider/src/main/resources/spring/applicationContext.xml
================================================
================================================
FILE: seckill-service-provider/src/main/resources/spring/spring-dao.xml
================================================
================================================
FILE: seckill-service-provider/src/main/resources/spring/spring-dubbo-config.xml
================================================
================================================
FILE: seckill-service-provider/src/main/resources/spring/spring-dubbo-provider.xml
================================================
================================================
FILE: seckill-service-provider/src/main/resources/spring/spring-service.xml
================================================
================================================
FILE: seckill-service-provider/src/main/resources/spring/spring-web.xml
================================================
================================================
FILE: seckill-service-provider/src/main/webapp/WEB-INF/web.xml
================================================
seckill-dispatcher
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring/applicationContext.xml
seckill-dispatcher
/
contextConfigLocation
classpath:spring/applicationContext.xml
org.springframework.web.context.ContextLoaderListener
================================================
FILE: seckill-service-provider/src/main/webapp/index.jsp
================================================
Hello World!