代码拉取完成,页面将自动刷新
同步操作将从 帝八哥/JavaBooks 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
这部分缓存,优化的时候采用了Redis的list,发现我用Jmeter测试并发的时候,发现了Redis中出现了非常多的异常数据,我当时还没有找到问题,暂时先采取以下方案,以下方案在并发时候,并没有出现数据异常。
if (redisUtils.hasKey(key)) {
// 如果缓存存在
Object obj = redisUtils.get(key);
log.warn("getCount->redis\n");
// 返回数据
return new ResponseUtil().setData(obj);
}
// 写
if (!redisUtils.hasKey(key)) {
// 如果缓存不存在,就写,注意与数据库数据一致性
redisUtils.set(key, response, RedisConstants.COUNTS_EXPIRE.getTime());
}
// 和上面一样
if (redisUtils.hasKey(key)) {
Object obj = redisUtils.get(key);
log.warn("getCountDetailById->redis\n");
return new ResponseUtil().setData(obj);
}
// 这里不判断了,上面已经判断过了
redisUtils.set(key, response, RedisConstants.COUNT_DETAIL_EXPIRE.getTime());
这里基本没调用过,它是获取班车人物信息的。所以分页查询即可
// MyBatis plus的分页查询
IPage<Bus> busIPage = new Page<>(request.getCurrentPage(), request.getPageSize());
busIPage = busMapper.selectPage(busIPage, null);
获取场次列表
// 分页插件,这里自定义分页查询
IPage<CountSimpleDto> countIPage = new Page<>(request.getCurrentPage(), request.getPageSize());
QueryWrapper<CountSimpleDto> queryWrapper = new QueryWrapper<>();
// 获取时间
String currHours = DateUtil.getHours();
String day = DateUtil.getDay();
System.out.println("当前时间:"+currHours);
System.out.println("当前日期:"+day);
// 判断条件
// 1. 找出符合当前天(比如,5.30)
// 2. 找出大于等于当前时间的场次(比如,数据库8点有一场,目前时间为7点,它就符合)
// 3. 找出状态为getBusStatus的场次,一般是还未发车的场次,(比如0,1)
queryWrapper
.eq("begin_date", day)
.ge("begin_time", currHours)
.eq("bus_status", request.getBusStatus())
.orderByAsc("begin_time");// 时间
countIPage = countMapper.selectCounts(countIPage, queryWrapper);
自定义分页CountMapper接口
public interface CountMapper extends BaseMapper<Count> {
/**
*
* @param page
* @param wrapper
* @return
*/
IPage<CountSimpleDto> selectCounts(IPage<CountSimpleDto> page, @Param(Constants.WRAPPER) Wrapper<CountSimpleDto> wrapper);
/**
*
* @param wrapper
* @return
*/
CountDetailDto selectCountDetailById(@Param(Constants.WRAPPER) Wrapper<CountDetailDto> wrapper);
}
xml
<select id="selectCounts" resultType="com.stylefeng.guns.rest.bus.dto.CountSimpleDto">
SELECT
sc.uuid ,
sc.begin_date ,
sc.begin_time ,
sc.bus_id ,
sc.bus_status,
sc.seat_status
FROM
sb_count sc
${ew.customSqlSegment}
</select>
查询场次详情信息
// 自定义查询,涉及到联表查询
QueryWrapper<CountDetailDto> wrapper = new QueryWrapper<>();
wrapper.eq("sc.uuid", request.getCountId());
xml
<select id="selectCountDetailById" resultType="com.stylefeng.guns.rest.bus.dto.CountDetailDto">
SELECT
sc.uuid ,
sc.bus_id ,
sc.bus_status ,
sc.begin_time ,
sc.begin_date ,
sc.selected_seats ,
sc.price,
sb.driver_name ,
sb.seats_number
FROM
sb_count sc
LEFT JOIN sb_bus sb ON
sc.bus_id = sb.uuid
${ew.customSqlSegment}
</select>
判断座位是否已重复
public boolean repeatSeats(String seats, Long coundId) {
// 查查数据库, 找到座位字段
boolean b = false; // false:不重复,true:重复
try {
Count count = countMapper.selectById(coundId);
// 比如,selectedSeats 是1,2
// dbSeats:"",
// dbSeats:"1,2,3",
// dbSeats: "4,5"
// 前端传来的selectedSeats, 前端判断是否为空,要不然后端也判断一下得了
if (seats.equals("")) {
return true;
}
if (count.getSelectedSeats().equals("")) {
return false;
}
String[] ss = seats.split(",");
String[] cs = count.getSelectedSeats().split(",");
// 这里考虑并发问题
HashSet<String> hashSet = new HashSet<>(Arrays.asList(cs));
for (String s : ss) {
if (hashSet.contains(s)) return true;
}
} catch (Exception e) {
e.printStackTrace();
log.error("selectedSeats", e);
return true; // 异常就算是重复
}
return b;
}
if (!StrUtil.isEmpty(selectedSeats)) {
// 这里可以优化,字符串拼接,这样的方式爆内存
// StringBuffer
newSelectedSeats = selectedSeats + "," + newSelectedSeats;
}
回退座位
Count count = countMapper.selectById(coundId);
String[] ss = seats.split(",");
String[] cs = count.getSelectedSeats().split(",");
// 并发问题,注意
HashSet<String> hashSet = new HashSet<>(Arrays.asList(cs));
for (String s : ss) {
if (hashSet.contains(s)) {
hashSet.remove(s);
}
}
if (hashSet.isEmpty()) {
count.setSelectedSeats("");
}
// 考虑了并发
StringBuffer sb = new StringBuffer();
for (String s : hashSet) {
sb.append(s);
sb.append(",");
}
// 上面的方案可以用String的replace的方法替换,遍历要回退的座位,依次替换即可
count.setSelectedSeats(sb.toString());
countMapper.updateById(count);
看一下String的replace的源码(1.8),感觉遍历还挺多,但的确不用自己去写了
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}
这里可以使用redis的延时队列或者RocketMQ的消息队列
/**
* 每天上午7点到晚上21点,每隔30分钟执行一次
*/
@Scheduled(cron = "0 0/30 7-21 * * ?")
private void schedulChangeBusStatus() {
log.warn("schedulChangeBusStatus执行");
busService.schedulChangeBusStatus();
}
看一下业务逻辑
public void schedulChangeBusStatus() {
// 获取时间
String currTime = DateUtil.getHours();
// 获取日期
String day = DateUtil.getDay();
log.warn("schedulChangeBusStatus->目前时间:" + currTime);
log.warn("schedulChangeBusStatus->目前时间:" + day);
System.out.println("目前时间:"+ currTime);
System.out.println("目前时间:"+ day);
QueryWrapper<Count> queryWrapper = new QueryWrapper<>();
// 先取出beingtime和now相等的表或者end_time和now相等到表
// 1. 取出当天
// 2. 取出开始时间或者结束时间符合当前时间
queryWrapper
.eq("begin_date", day) // 取出当天
.and(o -> o.eq("begin_time", currTime) // 当前时间
.or()
.eq("end_time", currTime));
List<Count> counts = countMapper.selectList(queryWrapper);
log.warn("schedulChangeBusStatus->查询到的:" + counts.toString());
// 开始作妖
for (Count count : counts) {
String busStatus = count.getBusStatus();
String beginTime = count.getBeginTime();
String endTime = count.getEndTime();
if (currTime.equals(beginTime)) {
if (busStatus.equals("0")) { // 沙河空闲
count.setBusStatus("2"); // 沙河->清水河
}
if (busStatus.equals("1")) { // 清水河空闲
count.setBusStatus("3"); // 清水河->沙河
}
count.setSelectedSeats(""); // 清空座位
}
if (currTime.equals(endTime)) {
if (busStatus.equals("2")) { // 沙河->清水河
count.setBusStatus("1"); // 清水河空闲
}
if (busStatus.equals("3")) { // 清水河->沙河
count.setBusStatus("0"); // 沙河空闲
}
}
System.out.println("修改的:" + count);
log.warn("schedulChangeBusStatus->修改的:" + count);
// 写入数据库
countMapper.updateById(count);
}
// 删缓存,这里非常重要...不删后果很严重
String key1 = RedisConstants.COUNTS_EXPIRE + "0";
String key2 = RedisConstants.COUNTS_EXPIRE + "1";
if (redisUtils.hasKey(key1)) {
redisUtils.del(key1);
}
if (redisUtils.hasKey(key2)) {
redisUtils.del(key2);
}
}
这里每隔30分钟扫库进行一次io,这里可以有优化... 假如我使用Redis的延迟队列
这个项目,没有后台,因此场次需要定时器添加即可
/**
* 每天凌晨0点2分执行
*/
@Scheduled(cron = "0 2 0 * * ? ")
private void addCounts(){
log.warn("addCounts执行");
busService.addCounts();
}
具体的业务逻辑:
public void addCounts() {
// 获取日期
String day = DateUtil.getDay();
// 获取前17个场次
QueryWrapper<Count> wrapper = new QueryWrapper<>();
wrapper.last("limit 17");
List<Count> counts = countMapper.selectList(wrapper);
// 开始修改 这里可以用java8 的特性, 还不是很熟悉,后期优化一下
for (Count count : counts) {
// 更改日期
count.setBeginDate(day);
// 更改uuid
count.setUuid(UUIDUtils.flakesUUID());
// 清空座位
count.setSelectedSeats("");
// 将走位状态清零
count.setSeatStatus("0");
// 插入
countMapper.insert(count);
}
// 删缓存,不删后果依然很严重
String key1 = RedisConstants.COUNTS_EXPIRE + "0";
String key2 = RedisConstants.COUNTS_EXPIRE + "1";
if (redisUtils.hasKey(key1)) {
redisUtils.del(key1);
}
if (redisUtils.hasKey(key2)) {
redisUtils.del(key2);
}
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。