文章目录
DAO 层编码思考
DAO 层工作演变为:接口设计 + SQL 编写 (DAO: 数据访问层的简称)
好处:
- 代码和 SQL 的分离,方便 Review
- DAO 拼接等业务逻辑在 Service 层完成
1. 秒杀 Service 接口设计
main > org.seckill > 新建以下包
service
:存放 service 接口和实现类
exception
:存放 service 接口所需要的一些异常,如 重复秒杀、秒杀已关闭
dto(数据传输层)
:存放表示数据的一些类型(有点像 entity,不过 entity 是业务的封装 如 秒杀、 秒杀成功)关注 web 和 service 之间的数据传递
1.1 service 接口
service 包下 > 新建 SeckillService 接口
package org.seckill.service;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.exception.SeckillException;
import java.util.List;
/**
* 业务接口:站在“使用者”角度设计接口
* 三个方面:方法定义粒度,参数(越简练越直接传递越好),返回类型(return 类型/异常)
* <p>
* Created by dixinkk on 2022/01/03.
*/
public interface SeckillService {
/**
* 查询所有秒杀记录
* @return
*/
List<Seckill> getSeckillList();
/**
* 查询单个秒杀记录
* @param seckillId
* @return
*/
Seckill getById(long seckillId);
/**
* 秒杀开启时输出秒杀接口地址
* 否则输出系统时间和秒杀时间
*
* @param seckillId
*/
Exposer exportSeckillUrl(long seckillId);
/**
* 执行秒杀操作
* @param seckillId
* @param userPhone
* @param md5
*/
SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
throws SeckillException, RepeatKillException, SeckillCloseException;
}
1.2 dto 数据传输层
dto包下 > 新建 Exposer
类
1.2.1 Exposer 暴露秒杀地址 DTO
package org.seckill.dto;
/**
* 暴露秒杀地址 DTO
* Created by dixinkk on 2022/01/03.
*/
public class Exposer {
// 是否开启秒杀
private boolean exposed;
// 一种加密措施
private String md5;
// id
private long seckillId;
// 系统当前时间(毫秒)
private long now;
// 开启时间
private long start;
// 结束时间
private long end;
@Override
public String toString() {
return "Exposer{" +
"exposed=" + exposed +
", md5='" + md5 + '\'' +
", seckillId=" + seckillId +
", now=" + now +
", start=" + start +
", end=" + 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;
}
}
1.2.2 SeckillExecution 秒杀成功 DTO
package org.seckill.dto;
import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStateEnum;
/**
* 封装秒杀执行后的结果
* Created by dixinkk on 2022/01/03.
*/
public class SeckillExecution {
private long seckillId;
// 秒杀执行结果状态
private int state;
// 状态表示
private String stateInfo;
// 秒杀成功对象
private SuccessKilled successKilled;
@Override
public String toString() {
return "SeckillExecution{" +
"seckillId=" + seckillId +
", state=" + state +
", stateInfo='" + stateInfo + '\'' +
", 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;
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
}
public long getSeckillId() {
return seckillId;
}
public void setSeckillId(long seckillId) {
this.seckillId = seckillId;
}
public int getState() {
return state;
}
public void setState(int 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;
}
}
1.3 exception
1.3.1 RuntimeException
package org.seckill.exception;
/**
* 重复秒杀异常(运行期异常)
* Created by dixinkk 2022/01/03.
*/
public class RepeatKillException extends SeckillException {
public RepeatKillException(String message) {
super(message);
}
public RepeatKillException(String message, Throwable cause) {
super(message, cause);
}
}
1.3.2 SeckillCloseException
package org.seckill.exception;
/**
* 秒杀关闭异常(运行期异常)
* Created by dixinkk 2022/01/03.
*/
public class SeckillCloseException extends SeckillException {
public SeckillCloseException(String message) {
super(message);
}
public SeckillCloseException(String message, Throwable cause) {
super(message, cause);
}
}
1.3.3 SeckillException
package org.seckill.exception;
/**
* 秒杀相关业务异常(运行期异常)
* Created by dixinkk 2022/01/03.
*/
public class SeckillException extends RuntimeException {
public SeckillException(String message) {
super(message);
}
public SeckillException(String message, Throwable cause) {
super(message, cause);
}
}
2. 秒杀 Service 接口实现
service > impl >
2.1 SeckillServiceImpl 实现类
package org.seckill.service.impl;
import org.seckill.dao.SeckillDao;
import org.seckill.dao.SuccessKilledDao;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStateEnum;
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.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import java.util.Date;
import java.util.List;
/**
* Created by dixinkk on 2022/01/04.
*/
// @Component @Service @Dao @Controller
@Service
public class SeckillServiceImpl implements SeckillService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
// 注入 Service 依赖 @Autowired @Resource @Inject
@Autowired
private SeckillDao seckillDao;
@Autowired
private SuccessKilledDao successKilledDao;
// md5 盐值字符串,用于混淆 md5
private final String slat = "fgasdgsdahgdsfhjsdffsdgkgfodkgerl;s&*&^^&^%$$@!1345463";
@Override
public List<Seckill> getSeckillList() {
return seckillDao.queryAll(0, 4);
}
@Override
public Seckill getById(long seckillId) {
return seckillDao.queryById(seckillId);
}
@Override
public Exposer exportSeckillUrl(long seckillId) {
Seckill seckill = seckillDao.queryById(seckillId);
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);
}
// exportSeckillUrl 和 executeSeckill 对 MD5 有重用,所以抽象出一个方法来
private String getMD5(long seckillId) {
String base = seckillId + "/" + slat;
String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
}
@Override
@Transactional
/**
* 使用注解控制事务方法的优点:
* 1.开发团队达成一致约定,明确标注事务方法的编程风格
* 2.保证事务方法的执行时间尽可能短,不要穿插其他的网络操作 -> RPC(缓存,如 Redis)/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 nowTime = new Date();
try {
// 减库存
int updateCount = seckillDao.reduceNumber(seckillId, nowTime);
if (updateCount <= 0) {
// 没有更新到记录,秒杀结束
throw new SeckillCloseException("seckill closed");
} else {
// 记录购买行为
int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);
// 唯一:seckillId, userPhone
if (insertCount <= 0) {
// 重复秒杀
throw new RepeatKillException("seckill repeated");
} else {
// 秒杀成功
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());
}
}
}
2.2 SeckillStateEnum 使用枚举表示常量数据字段
package org.seckill.enums;
/**
* 使用枚举表述常量数据字段(养成习惯:把数据字典放在枚举中)
* Created by dixinkk on 2022/01/06.
*/
public enum SeckillStateEnum {
SUCCESS(1, "秒杀成功"),
END(0, "秒杀结束"),
REPEAT_KILL(-1, "重复秒杀"),
INNER_ERROR(-2, "系统异常"),
DATA_REWRITE(-3,"数据篡改");
private int state;
private String stateInfo;
SeckillStateEnum(int state, String stateInfo) {
this.state = state;
this.stateInfo = stateInfo;
}
public int getState() {
return state;
}
public String getStateInfo() {
return stateInfo;
}
public static SeckillStateEnum stateOf(int index) {
for (SeckillStateEnum state : values()) {
if (state.getState() == index) {
return state;
}
}
return null;
}
}