type
status
date
slug
summary
tags
category
titleIcon
password
icon
calloutIcon
🎨
堆放项目实现与学习过程中积累的代码模板
RabbitMQ消费者模板
notion image

java

package com.tianji.learning.mq; import com.tianji.api.dto.trade.OrderBasicDTO; import com.tianji.common.constants.MqConstants; import com.tianji.common.utils.CollUtils; import com.tianji.learning.service.ILearningLessonService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.ExchangeTypes; import org.springframework.amqp.rabbit.annotation.Exchange; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.QueueBinding; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; /** * @author CamelliaV * @since 2024/10/30 / 21:48 */ @Component @Slf4j @RequiredArgsConstructor public class LessonChangeListener { private final ILearningLessonService lessonService; @RabbitListener( bindings = @QueueBinding( value = @Queue(name = MqConstants.Queue.ORDER_PAY_QUEUE, durable = "true"), exchange = @Exchange(name = MqConstants.Exchange.ORDER_EXCHANGE, type = ExchangeTypes.TOPIC), key = MqConstants.Key.ORDER_PAY_KEY ) ) public void listenLessonPay(OrderBasicDTO dto) { // * 健壮性检查 if (dto == null || dto.getUserId() == null || CollUtils.isEmpty(dto.getCourseIds())) { log.error("MQ消息错误,订单数据为空"); return; } // * 为对应用户添加课程(信息均存放于dto中) lessonService.addLesson(dto); } @RabbitListener( bindings = @QueueBinding( value = @Queue(name = MqConstants.Queue.ORDER_REFUND_QUEUE, durable = "true"), exchange = @Exchange(name = MqConstants.Exchange.ORDER_EXCHANGE, type = ExchangeTypes.TOPIC), key = MqConstants.Key.ORDER_REFUND_KEY ) ) public void listenLessonRefund(OrderBasicDTO dto) { // * 健壮性检查 if (dto == null || dto.getUserId() == null || CollUtils.isEmpty(dto.getCourseIds())) { log.error("MQ消息错误,退款数据为空"); return; } // * 为对应用户删除课程 Long userId = dto.getUserId(); if (userId == null) { return; } Long courseId = dto.getCourseIds() .get(0); if (courseId == null) { return; } lessonService.deleteLessonByCourse(userId, courseId); } }
Java
Interface常量类模板
notion image

java

package com.tianji.common.constants; public interface MqConstants { interface Exchange { /*课程有关的交换机*/ String COURSE_EXCHANGE = "course.topic"; /*订单有关的交换机*/ String ORDER_EXCHANGE = "order.topic"; /*学习有关的交换机*/ String LEARNING_EXCHANGE = "learning.topic"; /*信息中心短信相关的交换机*/ String SMS_EXCHANGE = "sms.direct"; /*异常信息的交换机*/ String ERROR_EXCHANGE = "error.topic"; /*支付有关的交换机*/ String PAY_EXCHANGE = "pay.topic"; /*交易服务延迟任务交换机*/ String TRADE_DELAY_EXCHANGE = "trade.delay.topic"; /*点赞记录有关的交换机*/ String LIKE_RECORD_EXCHANGE = "like.record.topic"; } interface Queue { String ERROR_QUEUE_TEMPLATE = "error.{}.queue"; String ORDER_PAY_QUEUE = "learning.lesson.pay.queue"; String ORDER_REFUND_QUEUE = "learning.lesson.refund.queue"; } interface Key { /*课程有关的 RoutingKey*/ String COURSE_NEW_KEY = "course.new"; String COURSE_UP_KEY = "course.up"; String COURSE_DOWN_KEY = "course.down"; String COURSE_EXPIRE_KEY = "course.expire"; String COURSE_DELETE_KEY = "course.delete"; /*订单有关的RoutingKey*/ String ORDER_PAY_KEY = "order.pay"; String ORDER_REFUND_KEY = "order.refund"; /*积分相关RoutingKey*/ /* 写回答 */ String WRITE_REPLY = "reply.new"; /* 签到 */ String SIGN_IN = "sign.in"; /* 学习视频 */ String LEARN_SECTION = "section.learned"; /* 写笔记 */ String WRITE_NOTE = "note.new"; /* 笔记被采集 */ String NOTE_GATHERED = "note.gathered"; /*点赞的RoutingKey*/ String LIKED_TIMES_KEY_TEMPLATE = "{}.times.changed"; /*问答*/ String QA_LIKED_TIMES_KEY = "QA.times.changed"; /*笔记*/ String NOTE_LIKED_TIMES_KEY = "NOTE.times.changed"; /*短信系统发送短信*/ String SMS_MESSAGE = "sms.message"; /*异常RoutingKey的前缀*/ String ERROR_KEY_PREFIX = "error."; String DEFAULT_ERROR_KEY = "error.#"; /*支付有关的key*/ String PAY_SUCCESS = "pay.success"; String REFUND_CHANGE = "refund.status.change"; String ORDER_DELAY_KEY = "delay.order.query"; } }
Java
SpringTask定时任务模版
启动类
notion image

java

package com.tianji.learning; import lombok.extern.slf4j.Slf4j; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.core.env.Environment; import org.springframework.scheduling.annotation.EnableScheduling; import java.net.InetAddress; import java.net.UnknownHostException; @SpringBootApplication @EnableScheduling @MapperScan("com.tianji.learning.mapper") @Slf4j public class LearningApplication { public static void main(String[] args) throws UnknownHostException { SpringApplication app = new SpringApplicationBuilder(LearningApplication.class).build(args); Environment env = app.run(args).getEnvironment(); String protocol = "http"; if (env.getProperty("server.ssl.key-store") != null) { protocol = "https"; } log.info("--/\n---------------------------------------------------------------------------------------\n\t" + "Application '{}' is running! Access URLs:\n\t" + "Local: \t\t{}://localhost:{}\n\t" + "External: \t{}://{}:{}\n\t" + "Profile(s): \t{}" + "\n---------------------------------------------------------------------------------------", env.getProperty("spring.application.name"), protocol, env.getProperty("server.port"), protocol, InetAddress.getLocalHost().getHostAddress(), env.getProperty("server.port"), env.getActiveProfiles()); } }
Java
定时任务类
notion image

java

package com.tianji.learning.task; import com.tianji.learning.service.ILearningLessonService; import lombok.RequiredArgsConstructor; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * @author CamelliaV * @since 2024/11/8 / 21:30 */ @Component @RequiredArgsConstructor public class LessonExpireTask { private final ILearningLessonService lessonService; @Scheduled(cron = "0 0 2 * * ?") public void checkAndExpireLessons() { lessonService.checkAndExpireLessons(); } }
Java
DelayQueue单机延迟任务模板
DelayTask任务定义
notion image

java

package com.tianji.learning.task; import lombok.Data; import java.time.Duration; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; /** * @author CamelliaV * @since 2024/11/8 / 16:23 */ @Data public class DelayTask<D> implements Delayed { private D data; // * nanoseconds private long activeTime; public DelayTask(D data, Duration delayTime) { this.data = data; this.activeTime = System.nanoTime() + delayTime.toNanos(); } // * 返回任务执行剩余时间 @Override public long getDelay(TimeUnit unit) { return unit.convert(Math.max(0, activeTime - System.nanoTime()), TimeUnit.NANOSECONDS); } // * 排序 @Override public int compareTo(Delayed o) { long l = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS); if (l > 0) { return 1; } else if (l < 0) { return -1; } else { return 0; } } }
Java
notion image
notion image
notion image
notion image

java

package com.tianji.learning.task; import com.tianji.common.exceptions.DbException; import com.tianji.common.utils.CollUtils; import com.tianji.common.utils.JsonUtils; import com.tianji.common.utils.StringUtils; import com.tianji.learning.domain.po.LearningLesson; import com.tianji.learning.domain.po.LearningRecord; import com.tianji.learning.mapper.LearningRecordMapper; import com.tianji.learning.service.ILearningLessonService; import com.tianji.learning.service.ILearningRecordService; import lombok.Data; import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.SessionCallback; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.time.Duration; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.DelayQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.Collectors; @Slf4j @RequiredArgsConstructor @Component public class LearningRecordDelayTaskHandler { private final static String RECORD_KEY_TEMPLATE = "learning:record:{}"; // * 实际核心数,非逻辑处理器数 private final static int CPU_ACTUAL_CORES = 8; private static volatile boolean begin = true; private final StringRedisTemplate redisTemplate; private final DelayQueue<DelayTask<RecordTaskData>> queue = new DelayQueue<>(); private final LearningRecordMapper recordMapper; private final ILearningLessonService lessonService; // * 涉及循环依赖 修改了spring.main.allow-circular-references,使用autowired自动确定注入时机 // * 偷懒复用批量更新 @Autowired private final ILearningRecordService recordService; // * 仅为实现定时任务(不采用)添加,key为记录id,value为记录 private final Map<Long, LearningRecord> lastRecords; @PostConstruct public void init() { ExecutorService threadPool = Executors.newFixedThreadPool(CPU_ACTUAL_CORES); CompletableFuture.runAsync(this::handleDelayTask, threadPool); } @PreDestroy public void destroy() { log.debug("关闭学习记录处理的延迟任务"); begin = false; } /** * (仅实现,不采用)定时任务,每隔20秒检查Redis缓存是否有需要持久化的学习记录 */ @Scheduled(fixedRate = 20_000) private void checkAndPersistRecords() { log.debug("定时持久化学习记录开始"); // * 没有需要更新的数据(Redis数据为空) if (CollUtils.isEmpty(lastRecords)) { return; } List<LearningRecord> recordList = readRecordCacheBatch(); // * 健壮性检查,Redis中无数据,原则上lastRecords不为空Redis数据也不为空 if (CollUtils.isEmpty(recordList)) { return; } // * (可选)仅更新播放进度相比上次写入时没有变化的数据,但可能出现刚写入Redis就触发定时更新的情况,效果未必好 List<LearningRecord> recordsToUpdate = recordList.stream() .filter(record -> Objects.equals(record.getMoment(), lastRecords.get(record.getId()) .getMoment())) .collect(Collectors.toList()); // * finished不修改,此处必定为非第一次完成 recordsToUpdate.forEach(record -> record.setFinished(null)); // * 更新学习记录 boolean success = recordService.updateBatchById(recordsToUpdate); if (!success) { throw new DbException("定时任务更新学习记录失败"); } // * 更新课表 最近学习小节id与时间 // * Redis中数据只有三部分,需要使用更完全的数据 List<LearningLesson> lessons = recordsToUpdate.stream() .map(record -> { LearningRecord fullRecord = lastRecords.get(record.getId()); LearningLesson lesson = new LearningLesson(); lesson.setId(fullRecord.getLessonId()); lesson.setLatestLearnTime(LocalDateTime.now()); lesson.setLatestSectionId(fullRecord.getSectionId()); return lesson; }) .collect(Collectors.toList()); success = lessonService.updateBatchById(lessons); if (!success) { throw new DbException("定时任务更新课表失败"); } // * 更新结束,清空暂存区 lastRecords.clear(); log.info("定时持久化学习记录任务成功"); } private void handleDelayTask() { while (begin) { try { // 1.尝试获取任务 DelayTask<RecordTaskData> task = queue.take(); log.debug("获取到要处理的播放记录任务"); RecordTaskData data = task.getData(); // 2.读取Redis缓存 LearningRecord record = readRecordCache(data.getLessonId(), data.getSectionId()); if (record == null) { continue; } // 3.比较数据 if (!Objects.equals(data.getMoment(), record.getMoment())) { // 4.如果不一致,播放进度在变化,无需持久化 continue; } // 5.如果一致,证明用户离开了视频,需要持久化 // 5.1.更新学习记录 record.setFinished(null); recordMapper.updateById(record); // 5.2.更新课表 LearningLesson lesson = new LearningLesson(); lesson.setId(data.getLessonId()); lesson.setLatestSectionId(data.getSectionId()); lesson.setLatestLearnTime(LocalDateTime.now()); lessonService.updateById(lesson); log.debug("准备持久化学习记录信息"); } catch (Exception e) { log.error("处理播放记录任务发生异常", e); } } } // * 替换addLearningRecordTask采用定时任务方案 public void addLearningRecordTaskScheduled(LearningRecord record) { // 1.添加数据到Redis缓存 writeRecordCache(record); // 2.添加后续需要更新数据库的数据 lastRecords.putIfAbsent(record.getId(), record); } // * 定时方案使用 private List<LearningRecord> readRecordCacheBatch() { try { // * 1.批量读取Redis数据,因为没有根据lessonId聚类,这里只一次传输多条单field读取 // * 逆天API三个泛型只给传一个 List<Object> results = redisTemplate.executePipelined(new SessionCallback<Object>() { @Override public Object execute(RedisOperations operations) throws DataAccessException { for (Long recordKey : lastRecords.keySet()) { LearningRecord record = lastRecords.get(recordKey); String key = StringUtils.format(RECORD_KEY_TEMPLATE, record.getLessonId()); // * 完全虚假的类型安全 // * 18年的issue 2024还没close😅 // * https://github.com/spring-projects/spring-data-redis/issues/1431 //noinspection unchecked operations.opsForHash() .get(key, record.getSectionId() .toString()); } return null; } }); // * Redis中无数据,不进行更新 if (CollUtils.isEmpty(results)) { return null; } // * 反序列化Redis数据用于后续Service数据库数据更新 List<LearningRecord> recordList = results.stream() .map(record -> JsonUtils.toBean(record.toString(), LearningRecord.class)) .collect(Collectors.toList()); return recordList; } catch (Exception e) { log.error("缓存读取异常", e); return null; } } public void addLearningRecordTask(LearningRecord record) { // 1.添加数据到Redis缓存 writeRecordCache(record); // 2.提交延迟任务到延迟队列 DelayQueue queue.add(new DelayTask<>(new RecordTaskData(record), Duration.ofSeconds(20))); } public void writeRecordCache(LearningRecord record) { log.debug("更新学习记录的缓存数据"); try { // 1.数据转换 String json = JsonUtils.toJsonStr(new RecordCacheData(record)); // 2.写入Redis String key = StringUtils.format(RECORD_KEY_TEMPLATE, record.getLessonId()); redisTemplate.opsForHash() .put(key, record.getSectionId() .toString(), json); // 3.添加缓存过期时间 redisTemplate.expire(key, Duration.ofMinutes(1)); } catch (Exception e) { log.error("更新学习记录缓存异常", e); } } public LearningRecord readRecordCache(Long lessonId, Long sectionId) { try { // 1.读取Redis数据 String key = StringUtils.format(RECORD_KEY_TEMPLATE, lessonId); Object cacheData = redisTemplate.opsForHash() .get(key, sectionId.toString()); if (cacheData == null) { return null; } // 2.数据检查和转换 return JsonUtils.toBean(cacheData.toString(), LearningRecord.class); } catch (Exception e) { log.error("缓存读取异常", e); return null; } } public void cleanRecordCache(Long lessonId, Long sectionId) { // 删除数据 String key = StringUtils.format(RECORD_KEY_TEMPLATE, lessonId); redisTemplate.opsForHash() .delete(key, sectionId.toString()); } @Data @NoArgsConstructor private static class RecordCacheData { private Long id; private Integer moment; private Boolean finished; public RecordCacheData(LearningRecord record) { this.id = record.getId(); this.moment = record.getMoment(); this.finished = record.getFinished(); } } @Data @NoArgsConstructor private static class RecordTaskData { private Long lessonId; private Long sectionId; private Integer moment; public RecordTaskData(LearningRecord record) { this.lessonId = record.getLessonId(); this.sectionId = record.getSectionId(); this.moment = record.getMoment(); } } }
Java
Caffeine模板|自动装配
notion image
notion image
notion image

java

package com.tianji.api.cache; import com.github.benmanes.caffeine.cache.Cache; import com.tianji.api.client.course.CategoryClient; import com.tianji.api.dto.course.CategoryBasicDTO; import com.tianji.common.utils.CollUtils; import lombok.RequiredArgsConstructor; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; @RequiredArgsConstructor public class CategoryCache { private final Cache<String, Map<Long, CategoryBasicDTO>> categoryCaches; private final CategoryClient categoryClient; public Map<Long, CategoryBasicDTO> getCategoryMap() { return categoryCaches.get("CATEGORY", key -> { // 1.从CategoryClient查询 List<CategoryBasicDTO> list = categoryClient.getAllOfOneLevel(); if (list == null || list.isEmpty()) { return CollUtils.emptyMap(); } // 2.转换数据 return list.stream().collect(Collectors.toMap(CategoryBasicDTO::getId, Function.identity())); }); } public String getCategoryNames(List<Long> ids) { if (ids == null || ids.size() == 0) { return ""; } // 1.读取分类缓存 Map<Long, CategoryBasicDTO> map = getCategoryMap(); // 2.根据id查询分类名称并组装 StringBuilder sb = new StringBuilder(); for (Long id : ids) { sb.append(map.get(id).getName()).append("/"); } // 3.返回结果 return sb.deleteCharAt(sb.length() - 1).toString(); } public List<String> getCategoryNameList(List<Long> ids) { if (ids == null || ids.size() == 0) { return CollUtils.emptyList(); } // 1.读取分类缓存 Map<Long, CategoryBasicDTO> map = getCategoryMap(); // 2.根据id查询分类名称并组装 List<String> list = new ArrayList<>(ids.size()); for (Long id : ids) { list.add(map.get(id).getName()); } // 3.返回结果 return list; } public List<CategoryBasicDTO> queryCategoryByIds(List<Long> ids) { if (ids == null || ids.size() == 0) { return CollUtils.emptyList(); } Map<Long, CategoryBasicDTO> map = getCategoryMap(); return ids.stream() .map(map::get) .collect(Collectors.toList()); } public List<String> getNameByLv3Ids(List<Long> lv3Ids) { Map<Long, CategoryBasicDTO> map = getCategoryMap(); List<String> list = new ArrayList<>(lv3Ids.size()); for (Long lv3Id : lv3Ids) { CategoryBasicDTO lv3 = map.get(lv3Id); CategoryBasicDTO lv2 = map.get(lv3.getParentId()); CategoryBasicDTO lv1 = map.get(lv2.getParentId()); list.add(lv1.getName() + "/" + lv2.getName() + "/" + lv3.getName()); } return list; } public String getNameByLv3Id(Long lv3Id) { Map<Long, CategoryBasicDTO> map = getCategoryMap(); CategoryBasicDTO lv3 = map.get(lv3Id); CategoryBasicDTO lv2 = map.get(lv3.getParentId()); CategoryBasicDTO lv1 = map.get(lv2.getParentId()); return lv1.getName() + "/" + lv2.getName() + "/" + lv3.getName(); } }
Java
notion image

java

package com.tianji.api.config; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.tianji.api.cache.CategoryCache; import com.tianji.api.client.course.CategoryClient; import com.tianji.api.dto.course.CategoryBasicDTO; import org.springframework.context.annotation.Bean; import java.time.Duration; import java.util.Map; public class CategoryCacheConfig { /** * 课程分类的caffeine缓存 */ @Bean public Cache<String, Map<Long, CategoryBasicDTO>> categoryCaches(){ return Caffeine.newBuilder() .initialCapacity(1) // 容量限制 .maximumSize(10_000) // 最大内存限制 .expireAfterWrite(Duration.ofMinutes(30)) // 有效期 .build(); } /** * 课程分类的缓存工具类 */ @Bean public CategoryCache categoryCache( Cache<String, Map<Long, CategoryBasicDTO>> categoryCaches, CategoryClient categoryClient){ return new CategoryCache(categoryCaches, categoryClient); } }
Java
自动装配模板
notion image
notion image

java

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.tianji.api.config.RequestIdRelayConfiguration, \ com.tianji.api.config.RoleCacheConfig, \ com.tianji.api.config.FallbackConfig, \ com.tianji.api.config.CategoryCacheConfig
Java
Openfeign模板|自动装配
notion image

java

package com.tianji.api.config; import com.tianji.api.client.learning.fallback.LearningClientFallback; import com.tianji.api.client.promotion.fallback.PromotionClientFallback; import com.tianji.api.client.remark.fallback.RemarkClientFallback; import com.tianji.api.client.trade.fallback.TradeClientFallback; import com.tianji.api.client.user.fallback.UserClientFallback; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FallbackConfig { @Bean public LearningClientFallback learningClientFallback() { return new LearningClientFallback(); } @Bean public TradeClientFallback tradeClientFallback() { return new TradeClientFallback(); } @Bean public UserClientFallback userClientFallback() { return new UserClientFallback(); } @Bean public RemarkClientFallback remarkClientFallback() { return new RemarkClientFallback(); } @Bean public PromotionClientFallback promotionClientFallback() { return new PromotionClientFallback(); } }
Java
Spring Data Redis Pipeline模板
notion image

java

// * 批量查询对应业务id项用户点赞记录 List<Object> objects = redisTemplate.executePipelined(new RedisCallback<Object>() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { for (Long bizId : bizIds) { String allKey = RedisConstants.LIKES_ALL_KEY_PREFIX + bizType + ":" + bizId; String delKey = RedisConstants.LIKES_DEL_KEY_PREFIX + bizType + ":" + bizId; // * Redis中是否有 connection.sIsMember(allKey.getBytes(), user.getBytes()); // * 防止删除没同步时数据库有记录 connection.zScore(delKey.getBytes(), user.getBytes()); } return null; } });
Java
Spring Data Redis BitMap操作模板
notion image
notion image
notion image

java

package com.tianji.learning.service.impl; import com.tianji.common.autoconfigure.mq.RabbitMqHelper; import com.tianji.common.constants.MqConstants; import com.tianji.common.exceptions.BizIllegalException; import com.tianji.common.utils.BooleanUtils; import com.tianji.common.utils.CollUtils; import com.tianji.common.utils.UserContext; import com.tianji.learning.constants.RedisConstants; import com.tianji.learning.domain.vo.SignResultVO; import com.tianji.learning.mq.message.PointsMessage; import com.tianji.learning.service.ISignRecordService; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.connection.BitFieldSubCommands; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.List; /** * @author CamelliaV * @since 2024/11/18 / 15:48 */ @Service @RequiredArgsConstructor public class ISignRecordServiceImpl implements ISignRecordService { private final StringRedisTemplate redisTemplate; private final RabbitMqHelper mqHelper; /** * 签到 */ @Override public SignResultVO addSignRecord() { // * 使用bitmap实现签到,拼接用户id与年月作key,日计算offset,存入bitmap Long userId = UserContext.getUser(); LocalDate now = LocalDate.now(); String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId + ":" + now.format(DateTimeFormatter.ofPattern("yyyyMM")); int offset = now.getDayOfMonth() - 1; // * bitmap属于字符串 // * setBit返回之前的bit值 Boolean result = redisTemplate.opsForValue() .setBit(key, offset, true); if (BooleanUtils.isTrue(result)) { throw new BizIllegalException("不能重复签到"); } // * 统计连续签到天数 // * 获取到当天的本月签到详情 // * BITFIELD key GET u[dayOfMonth] 0 int signDays = 0; List<Long> results = redisTemplate.opsForValue() .bitField(key, BitFieldSubCommands.create() .get(BitFieldSubCommands.BitFieldType.unsigned(offset + 1)) .valueAt(0)); // * 返回为10进制数据,转二进制&处理 // * 计算连续签到天数 if (CollUtils.isNotEmpty(results)) { int num = results.get(0) .intValue(); while ((num & 1) == 1) { signDays++; num >>>= 1; } } // * 封装vo // * 填充连续签到天数与奖励积分 SignResultVO vo = new SignResultVO(); vo.setSignDays(signDays); int rewardPoints = 0; if (signDays == 7) { rewardPoints = 10; } else if (signDays == 14) { rewardPoints = 20; } else if (signDays == 28) { rewardPoints = 40; } vo.setRewardPoints(rewardPoints); // * 积分推送至mq mqHelper.send(MqConstants.Exchange.LEARNING_EXCHANGE, MqConstants.Key.SIGN_IN, PointsMessage.of(userId, vo.totalPoints())); return vo; } /** * 查询签到记录 */ @Override public Byte[] querySignRecords() { // * bitField查询签到详情 Long userId = UserContext.getUser(); LocalDate now = LocalDate.now(); int dayOfMonth = now.getDayOfMonth(); String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId + ":" + now.format(DateTimeFormatter.ofPattern("yyyyMM")); List<Long> results = redisTemplate.opsForValue() .bitField(key, BitFieldSubCommands.create() .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)) .valueAt(0)); if (CollUtils.isEmpty(results)) { return new Byte[0]; } int num = results.get(0) .intValue(); // * 从最末尾(日期最大)开始填充 Byte[] bytes = new Byte[dayOfMonth]; int pos = dayOfMonth; while (--pos >= 0) { bytes[pos] = (byte) (num & 1); num >>>= 1; } return bytes; } }
Java
Mybatis注解中多值SQL模板
notion image

java

/** * <p> * 点赞记录表 Mapper 接口 * </p> * * @author CamelliaV * @since 2024-11-13 */ public interface LikedRecordMapper extends BaseMapper<LikedRecord> { @Delete("<script>" + "DELETE FROM tj_remark.liked_record WHERE (biz_id, user_id, biz_type) IN " + "<foreach collection='records' item='record' open='(' separator=',' close=')'>" + "(#{record.bizId}, #{record.userId}, #{record.bizType})" + "</foreach>" + "</script>") int batchDeleteByUniqueKey(@Param("records") List<LikedRecord> recordsToUpdate); }
Java
Maven私服settings.xml模版
notion image
notion image
notion image

xml

<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> <!-- 本地仓库 --> <localRepository>E:\sl-express\apache-maven-3.6.3\repository</localRepository> <!-- 配置私服中deploy的账号 --> <servers> <server> <id>sl-releases</id> <username>deployment</username> <password>deployment123</password> </server> <server> <id>sl-snapshots</id> <username>deployment</username> <password>deployment123</password> </server> </servers> <!-- 使用阿里云maven镜像,排除私服资源库 --> <mirrors> <mirror> <id>mirror</id> <mirrorOf>central,jcenter,!sl-releases,!sl-snapshots</mirrorOf> <name>mirror</name> <url>https://maven.aliyun.com/nexus/content/groups/public</url> </mirror> </mirrors> <profiles> <profile> <id>sl</id> <!-- 配置项目deploy的地址 --> <properties> <altReleaseDeploymentRepository> sl-releases::default::http://maven.sl-express.com/nexus/content/repositories/releases/ </altReleaseDeploymentRepository> <altSnapshotDeploymentRepository> sl-snapshots::default::http://maven.sl-express.com/nexus/content/repositories/snapshots/ </altSnapshotDeploymentRepository> </properties> <!-- 配置项目下载依赖的私服地址 --> <repositories> <repository> <id>sl-releases</id> <url>http://maven.sl-express.com/nexus/content/repositories/releases/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>sl-snapshots</id> <url>http://maven.sl-express.com/nexus/content/repositories/snapshots/</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> </profile> </profiles> <activeProfiles> <!-- 激活配置 --> <activeProfile>sl</activeProfile> </activeProfiles> </settings>
XML
医学图像处理入门Bing&Google搜索引擎收录
Loading...
CamelliaV
CamelliaV
Java;CV;ACGN
最新发布
单例模式的四种写法
2025-4-24
体验MCP
2025-4-24
MetingJS使用自定义音乐源-CF+Huggingface部署
2025-4-2
博客访问站点测速分析与对比
2025-3-26
前端模块化
2025-3-16
Voxel2Mesh相关论文精读与代码复现
2025-3-15
公告
计划:
  • LLM相关
  • 支付业务 & 双token无感刷新
  • (线程池计算优惠方案)天机学堂Day09-Day12复盘-优惠劵业务
  • (业务复盘,技术汇总)天机学堂完结复盘
  • hot 100
 
2024-2025CamelliaV.

CamelliaV | Java;CV;ACGN


  1. 1 给予你的爱 Xi YuaN/Digital Vengeance/唢清
  2. 2 スペルビア帝国/夜 平松建治
  3. 3 Imagination QQHHh
  4. 4 virtues QQHHh
  5. 5 Tricolor (short ver.) Digital Vengeance/44
  6. 6 港口夜 - 四周年 月代彩
  7. 7 神よ、その黄昏よ 金﨑猛
  8. 8 絆炎 (English Ver) Katherine Eames
  9. 9 ラストエンゲージ~祈りの呪文 馬場泰久
  10. 10 an evening calm fripSide
  11. 11 フレスベルグの少女~風花雪月~ Caro
  12. 12 Answer 北原春希/小木曽雪菜
  13. 13 Kiss Kiss Kiss BENI
  14. 14 远航高歌 染音若蔡/阿南
  15. 15 Sentimental Blue Trident
  16. 16 目指す先にあるもの Falcom Sound Team J.D.K.
  17. 17 Night City r e l/Artemis Delta
  18. 18 Gimme×Gimme P*Light/Giga/初音ミク/鏡音リン
  19. 19 桃幻浪漫 Airots/Active Planets & AUGUST
  20. 20 DESIRE 美郷あき
  21. 21 镜花堂(feat.芬璃尔) 幻塔手游/Rux
  22. 22 she was sitting under the osmanthus tree 梶浦由記
给予你的爱 - Xi YuaN/Digital Vengeance/唢清
00:00 / 00:00