diff --git a/README.md b/README.md index df9d7bb..78d595b 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ 2. 运行镜像 ```shell - sudo docker run -p 80:8080 -e MYSQL_URL=192.168.66.1:3306 -e MYSQL_USERNAME=root -e MYSQL_PASSWORD=root -it registry.cn-beijing.aliyuncs.com/itning/shw_server:latest + sudo docker run -p 80:8080 -e MYSQL_URL=192.168.66.1:3306 -e MYSQL_USERNAME=root -e MYSQL_PASSWORD=root -e REDIS_HOST=192.168.66.1 -e REDIS_PORT=6379 -it registry.cn-beijing.aliyuncs.com/itning/shw_server:latest ``` **其中MYSQL_URL参数值为MySql数据库服务器地址(带端口号)** diff --git a/pom.xml b/pom.xml index 49fa57c..ffcc518 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ top.yunshu shw_server - 1.6.6-RELEASE + 1.7.0-RELEASE shw_server Student HomeWork Management System @@ -50,6 +50,14 @@ org.springframework.boot spring-boot-starter-websocket + + org.springframework.boot + spring-boot-starter-cache + + + org.springframework.boot + spring-boot-starter-data-redis + mysql mysql-connector-java diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCache.java b/src/main/java/org/springframework/data/redis/cache/RedisCache.java new file mode 100644 index 0000000..457cbfe --- /dev/null +++ b/src/main/java/org/springframework/data/redis/cache/RedisCache.java @@ -0,0 +1,338 @@ +/* + * Copyright 2017-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.cache; + +import org.springframework.cache.support.AbstractValueAdaptingCache; +import org.springframework.cache.support.NullValue; +import org.springframework.cache.support.SimpleValueWrapper; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.data.redis.util.ByteUtils; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; +import top.yunshu.shw.server.config.SpringContextHelper; + +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.Set; +import java.util.concurrent.Callable; + +/** + * {@link org.springframework.cache.Cache} implementation using for Redis as underlying store. + *

+ * Use {@link RedisCacheManager} to create {@link RedisCache} instances. + * + * @author Christoph Strobl + * @author Mark Paluch + * @see RedisCacheConfiguration + * @see RedisCacheWriter + * @since 2.0 + */ +@SuppressWarnings("all") +public class RedisCache extends AbstractValueAdaptingCache { + private static final byte[] BINARY_NULL_VALUE = RedisSerializer.java().serialize(NullValue.INSTANCE); + + private final String name; + private final RedisCacheWriter cacheWriter; + private final RedisCacheConfiguration cacheConfig; + private final ConversionService conversionService; + private final RedisTemplate redisTemplate; + + { + redisTemplate = SpringContextHelper.getBean("redisTemplate", RedisTemplate.class); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + } + + /** + * Create new {@link RedisCache}. + * + * @param name must not be {@literal null}. + * @param cacheWriter must not be {@literal null}. + * @param cacheConfig must not be {@literal null}. + */ + protected RedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) { + + super(cacheConfig.getAllowCacheNullValues()); + + Assert.notNull(name, "Name must not be null!"); + Assert.notNull(cacheWriter, "CacheWriter must not be null!"); + Assert.notNull(cacheConfig, "CacheConfig must not be null!"); + + this.name = name; + this.cacheWriter = cacheWriter; + this.cacheConfig = cacheConfig; + this.conversionService = cacheConfig.getConversionService(); + } + + /* + * (non-Javadoc) + * @see org.springframework.cache.support.AbstractValueAdaptingCache#lookup(java.lang.Object) + */ + @Override + protected Object lookup(Object key) { + + byte[] value = cacheWriter.get(name, createAndConvertCacheKey(key)); + + if (value == null) { + return null; + } + + return deserializeCacheValue(value); + } + + /* + * (non-Javadoc) + * @see org.springframework.cache.Cache#getName() + */ + @Override + public String getName() { + return name; + } + + /* + * (non-Javadoc) + * @see org.springframework.cache.Cache#getNativeCache() + */ + @Override + public RedisCacheWriter getNativeCache() { + return this.cacheWriter; + } + + /* + * (non-Javadoc) + * @see org.springframework.cache.Cache#get(java.lang.Object, java.util.concurrent.Callable) + */ + @Override + @SuppressWarnings("unchecked") + public synchronized T get(Object key, Callable valueLoader) { + + ValueWrapper result = get(key); + + if (result != null) { + return (T) result.get(); + } + + T value = valueFromLoader(key, valueLoader); + put(key, value); + return value; + } + + /* + * (non-Javadoc) + * @see org.springframework.cache.Cache#put(java.lang.Object, java.lang.Object) + */ + @Override + public void put(Object key, @Nullable Object value) { + + Object cacheValue = preProcessCacheValue(value); + + if (!isAllowNullValues() && cacheValue == null) { + + throw new IllegalArgumentException(String.format( + "Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.", + name)); + } + + cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cache.Cache#putIfAbsent(java.lang.Object, java.lang.Object) + */ + @Override + public ValueWrapper putIfAbsent(Object key, @Nullable Object value) { + + Object cacheValue = preProcessCacheValue(value); + + if (!isAllowNullValues() && cacheValue == null) { + return get(key); + } + + byte[] result = cacheWriter.putIfAbsent(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), + cacheConfig.getTtl()); + + if (result == null) { + return null; + } + + return new SimpleValueWrapper(fromStoreValue(deserializeCacheValue(result))); + } + + /* + * (non-Javadoc) + * @see org.springframework.cache.Cache#evict(java.lang.Object) + */ + @Override + public void evict(Object key) { + if (key instanceof String) { + String keyStr = (String) key; + if (keyStr.startsWith("regex:")) { + String regex = keyStr.substring(6); + if ("".equals(regex)) { + throw new RuntimeException("must write regex"); + } + Set keys = redisTemplate.keys(name + "::" + regex); + redisTemplate.delete(keys); + return; + } + } + + cacheWriter.remove(name, createAndConvertCacheKey(key)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cache.Cache#clear() + */ + @Override + public void clear() { + byte[] pattern = conversionService.convert(createCacheKey("*"), byte[].class); + cacheWriter.clean(name, pattern); + } + + /** + * Get {@link RedisCacheConfiguration} used. + * + * @return immutable {@link RedisCacheConfiguration}. Never {@literal null}. + */ + public RedisCacheConfiguration getCacheConfiguration() { + return cacheConfig; + } + + /** + * Customization hook called before passing object to + * {@link org.springframework.data.redis.serializer.RedisSerializer}. + * + * @param value can be {@literal null}. + * @return preprocessed value. Can be {@literal null}. + */ + @Nullable + protected Object preProcessCacheValue(@Nullable Object value) { + + if (value != null) { + return value; + } + + return isAllowNullValues() ? NullValue.INSTANCE : null; + } + + /** + * Serialize the key. + * + * @param cacheKey must not be {@literal null}. + * @return never {@literal null}. + */ + protected byte[] serializeCacheKey(String cacheKey) { + return ByteUtils.getBytes(cacheConfig.getKeySerializationPair().write(cacheKey)); + } + + /** + * Serialize the value to cache. + * + * @param value must not be {@literal null}. + * @return never {@literal null}. + */ + protected byte[] serializeCacheValue(Object value) { + + if (isAllowNullValues() && value instanceof NullValue) { + return BINARY_NULL_VALUE; + } + + return ByteUtils.getBytes(cacheConfig.getValueSerializationPair().write(value)); + } + + /** + * Deserialize the given value to the actual cache value. + * + * @param value must not be {@literal null}. + * @return can be {@literal null}. + */ + @Nullable + protected Object deserializeCacheValue(byte[] value) { + + if (isAllowNullValues() && ObjectUtils.nullSafeEquals(value, BINARY_NULL_VALUE)) { + return NullValue.INSTANCE; + } + + return cacheConfig.getValueSerializationPair().read(ByteBuffer.wrap(value)); + } + + /** + * Customization hook for creating cache key before it gets serialized. + * + * @param key will never be {@literal null}. + * @return never {@literal null}. + */ + protected String createCacheKey(Object key) { + + String convertedKey = convertKey(key); + + if (!cacheConfig.usePrefix()) { + return convertedKey; + } + + return prefixCacheKey(convertedKey); + } + + /** + * Convert {@code key} to a {@link String} representation used for cache key creation. + * + * @param key will never be {@literal null}. + * @return never {@literal null}. + * @throws IllegalStateException if {@code key} cannot be converted to {@link String}. + */ + protected String convertKey(Object key) { + + TypeDescriptor source = TypeDescriptor.forObject(key); + if (conversionService.canConvert(source, TypeDescriptor.valueOf(String.class))) { + return conversionService.convert(key, String.class); + } + + Method toString = ReflectionUtils.findMethod(key.getClass(), "toString"); + + if (toString != null && !Object.class.equals(toString.getDeclaringClass())) { + return key.toString(); + } + + throw new IllegalStateException( + String.format("Cannot convert %s to String. Register a Converter or override toString().", source)); + } + + private byte[] createAndConvertCacheKey(Object key) { + return serializeCacheKey(createCacheKey(key)); + } + + private String prefixCacheKey(String key) { + + // allow contextual cache names by computing the key prefix on every call. + return cacheConfig.getKeyPrefixFor(name) + key; + } + + private static T valueFromLoader(Object key, Callable valueLoader) { + + try { + return valueLoader.call(); + } catch (Exception e) { + throw new ValueRetrievalException(key, valueLoader, e); + } + } +} diff --git a/src/main/java/top/yunshu/shw/server/ShwServerApplication.java b/src/main/java/top/yunshu/shw/server/ShwServerApplication.java index 7158054..00ad4c2 100644 --- a/src/main/java/top/yunshu/shw/server/ShwServerApplication.java +++ b/src/main/java/top/yunshu/shw/server/ShwServerApplication.java @@ -4,6 +4,7 @@ import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.scheduling.annotation.EnableScheduling; import springfox.documentation.swagger2.annotations.EnableSwagger2; @@ -16,6 +17,7 @@ @SpringBootApplication @EnableSwagger2 @EnableScheduling +@EnableCaching public class ShwServerApplication { private static final Logger logger = LoggerFactory.getLogger(ShwServerApplication.class); diff --git a/src/main/java/top/yunshu/shw/server/service/config/impl/ConfigServiceImpl.java b/src/main/java/top/yunshu/shw/server/service/config/impl/ConfigServiceImpl.java index 8d3e8f7..9885a67 100644 --- a/src/main/java/top/yunshu/shw/server/service/config/impl/ConfigServiceImpl.java +++ b/src/main/java/top/yunshu/shw/server/service/config/impl/ConfigServiceImpl.java @@ -1,6 +1,9 @@ package top.yunshu.shw.server.service.config.impl; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; import org.springframework.stereotype.Service; import top.itning.cas.CasProperties; import top.yunshu.shw.server.dao.ConfigDao; @@ -78,16 +81,22 @@ private void init() { } } + @Cacheable(cacheNames = "allConfigs") @Override public List getAllConfigs() { return configDao.findAll(); } + @Cacheable(cacheNames = "config", key = "#configKey.key") @Override public Optional getConfig(Config.ConfigKey configKey) { return configDao.findById(configKey.getKey()).map(Config::getValue); } + @Caching(evict = { + @CacheEvict(cacheNames = "allConfigs", allEntries = true), + @CacheEvict(cacheNames = "config", key = "#configKey.key") + }) @Override public void saveConfig(Config.ConfigKey configKey, String value) { Config config = new Config(); diff --git a/src/main/java/top/yunshu/shw/server/service/group/impl/GroupServiceImpl.java b/src/main/java/top/yunshu/shw/server/service/group/impl/GroupServiceImpl.java index 3a66ed9..a3b87d3 100644 --- a/src/main/java/top/yunshu/shw/server/service/group/impl/GroupServiceImpl.java +++ b/src/main/java/top/yunshu/shw/server/service/group/impl/GroupServiceImpl.java @@ -3,12 +3,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; -import top.yunshu.shw.server.dao.*; +import top.yunshu.shw.server.dao.GroupDao; +import top.yunshu.shw.server.dao.StudentGroupDao; +import top.yunshu.shw.server.dao.UploadDao; +import top.yunshu.shw.server.dao.WorkDao; import top.yunshu.shw.server.entity.Group; import top.yunshu.shw.server.entity.StudentGroup; import top.yunshu.shw.server.entity.Upload; @@ -47,6 +53,7 @@ public GroupServiceImpl(GroupDao groupDao, StudentGroupDao studentGroupDao, Work this.uploadDao = uploadDao; } + @Cacheable(cacheNames = "groupOfStudent", key = "#studentNumber+#pageable") @Override public Page findStudentAllGroups(String studentNumber, Pageable pageable) { List groupList = studentGroupDao.findAllByStudentNumber(studentNumber, pageable) @@ -65,21 +72,31 @@ public Page findStudentAllGroups(String studentNumber, Pageable pageable) return new PageImpl<>(groupList, pageable, studentGroupDao.countAllByStudentNumber(studentNumber)); } + @Cacheable(cacheNames = "groupOfTeacher", key = "#teacherNumber+#pageable") @Override public Page findTeacherAllGroups(String teacherNumber, Pageable pageable) { return groupDao.findByTeacherNumber(teacherNumber, pageable); } + @Caching(evict = { + @CacheEvict(cacheNames = "groupOfTeacher", key = "'regex:'+#teacherId+'*'"), + @CacheEvict(cacheNames = "isHaveAnyGroup", key = "#teacherId") + }) @Override public Group createGroup(String groupName, String teacherName, String teacherId) { String id = UUID.randomUUID().toString().replace("-", ""); return groupDao.save(new Group(id, groupName, teacherName, teacherId, id)); } + @Caching(evict = { + @CacheEvict(cacheNames = "groupOfStudent", key = "'regex:'+#studentId+'*'"), + //学生加入群组,教师作业详情缓存清空 + @CacheEvict(cacheNames = "workDetail", allEntries = true) + }) @Override public Group joinGroup(String code, String studentId) { if (!groupDao.existsAllByCode(code)) { - throw new NoSuchFiledValueException("群ID不存在", HttpStatus.NOT_FOUND); + throw new NoSuchFiledValueException("邀请码过期或不存在", HttpStatus.NOT_FOUND); } if (studentGroupDao.findByStudentNumberAndGroupID(studentId, code) != null) { throw new NoSuchFiledValueException("已加入过该群", HttpStatus.CONFLICT); @@ -90,6 +107,11 @@ public Group joinGroup(String code, String studentId) { return group; } + @Caching(evict = { + @CacheEvict(cacheNames = "groupOfStudent", key = "'regex:'+#studentId+'*'"), + //学生退出群组,教师作业详情缓存清空 + @CacheEvict(cacheNames = "workDetail", allEntries = true) + }) @Override public void dropOutGroup(String groupId, String studentId) { if (!groupDao.existsById(groupId)) { @@ -110,7 +132,13 @@ public void dropOutGroup(String groupId, String studentId) { uploadDao.deleteAll(willDeleteUploadList); } - + @Caching(evict = { + @CacheEvict(cacheNames = "groupOfStudent", allEntries = true), + @CacheEvict(cacheNames = "groupOfTeacher", key = "'regex:'+#teacherNumber+'*'"), + @CacheEvict(cacheNames = "isHaveAnyGroup", key = "#teacherNumber"), + @CacheEvict(cacheNames = "findGroupNameByGroupId", key = "#id"), + @CacheEvict(cacheNames = "findTeacherNameById", key = "#id") + }) @Override public void deleteGroup(String id, String teacherNumber) { Group group = groupDao.findById(id).orElseThrow(() -> new NoSuchFiledValueException("id: " + id + " not found", HttpStatus.NOT_FOUND)); @@ -127,6 +155,11 @@ public void deleteGroup(String id, String teacherNumber) { groupDao.delete(group); } + @Caching(evict = { + @CacheEvict(cacheNames = "groupOfStudent", allEntries = true), + @CacheEvict(cacheNames = "groupOfTeacher", key = "'regex:'+#teacherNumber+'*'"), + @CacheEvict(cacheNames = "findGroupNameByGroupId", key = "#id") + }) @Override public Group updateGroup(String id, String name, String teacherNumber) { Group group = groupDao.findById(id).orElseThrow(() -> new NoSuchFiledValueException("id: " + id + " not found ", HttpStatus.NOT_FOUND)); @@ -137,6 +170,7 @@ public Group updateGroup(String id, String name, String teacherNumber) { return groupDao.save(group); } + @Cacheable(cacheNames = "findGroupNameByGroupId", key = "#groupId") @Override public String findGroupNameByGroupId(String groupId) { if (!groupDao.existsById(groupId)) { @@ -145,6 +179,7 @@ public String findGroupNameByGroupId(String groupId) { return groupDao.findNameById(groupId); } + @Cacheable(cacheNames = "findTeacherNameById", key = "#groupId") @Override public String findTeacherNameById(String groupId) { if (!groupDao.existsById(groupId)) { @@ -153,6 +188,7 @@ public String findTeacherNameById(String groupId) { return groupDao.findTeacherNameById(groupId); } + @Cacheable(cacheNames = "isHaveAnyGroup", key = "#teacherNumber") @Override public boolean isHaveAnyGroup(String teacherNumber) { return !groupDao.findByTeacherNumber(teacherNumber).isEmpty(); diff --git a/src/main/java/top/yunshu/shw/server/service/upload/impl/UploadServiceImpl.java b/src/main/java/top/yunshu/shw/server/service/upload/impl/UploadServiceImpl.java index 13c7980..ef89b43 100644 --- a/src/main/java/top/yunshu/shw/server/service/upload/impl/UploadServiceImpl.java +++ b/src/main/java/top/yunshu/shw/server/service/upload/impl/UploadServiceImpl.java @@ -1,6 +1,8 @@ package top.yunshu.shw.server.service.upload.impl; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Caching; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -51,6 +53,11 @@ public Upload getUploadInfoByWorkId(String studentId, String workId) { return uploadDao.findUploadByStudentIdAndWorkId(studentId, workId); } + @Caching(evict = { + @CacheEvict(cacheNames = "studentDoneWork", allEntries = true), + @CacheEvict(cacheNames = "studentUndoneWork", allEntries = true), + @CacheEvict(cacheNames = "workDetail", allEntries = true) + }) @Override public void delUploadInfoByWorkId(String studentId, String workId) { if (!uploadDao.existsByStudentIdAndWorkId(studentId, workId)) { @@ -71,6 +78,11 @@ public void delUploadInfoByWorkId(String studentId, String workId) { uploadDao.delete(upload); } + @Caching(evict = { + @CacheEvict(cacheNames = "studentDoneWork", allEntries = true), + @CacheEvict(cacheNames = "studentUndoneWork", allEntries = true), + @CacheEvict(cacheNames = "workDetail", allEntries = true) + }) @Override public void uploadFile(MultipartFile file, String studentNumber, String workId) { String[] format = FileNameSpecificationUtils.safeGetStudentNameAndFileNameFormat(studentDao, workDao, studentNumber, workId); diff --git a/src/main/java/top/yunshu/shw/server/service/work/impl/WorkServiceImpl.java b/src/main/java/top/yunshu/shw/server/service/work/impl/WorkServiceImpl.java index 69adee3..37c51a6 100644 --- a/src/main/java/top/yunshu/shw/server/service/work/impl/WorkServiceImpl.java +++ b/src/main/java/top/yunshu/shw/server/service/work/impl/WorkServiceImpl.java @@ -5,6 +5,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; @@ -57,6 +60,7 @@ public WorkServiceImpl(WorkDao workDao, StudentGroupDao studentGroupDao, UploadD this.modelMapper = modelMapper; } + @Cacheable(cacheNames = "studentUndoneWork", key = "#studentId+#pageable") @Override public Page getStudentUnDoneWork(String studentId, Pageable pageable) { List workList = studentGroupDao.findGroupIdByStudentNumber(studentId) @@ -69,6 +73,7 @@ public Page getStudentUnDoneWork(String studentId, Pageable pageable) return getWorkModels(pageable, workList); } + @Cacheable(cacheNames = "studentDoneWork", key = "#studentId+#pageable") @Override public Page getStudentDoneWork(String studentId, Pageable pageable) { List workList = studentGroupDao.findGroupIdByStudentNumber(studentId) @@ -85,6 +90,7 @@ public Page getStudentDoneWork(String studentId, Pageable pageable) { return getWorkModels(pageable, workList); } + @Cacheable(cacheNames = "work", key = "#teacherNumber+#pageable") @Override public Page getTeacherAllWork(String teacherNumber, Pageable pageable) { List workList = groupDao.findByTeacherNumber(teacherNumber) @@ -96,6 +102,7 @@ public Page getTeacherAllWork(String teacherNumber, Pageable pageable return getWorkModels(pageable, workList); } + @Cacheable(cacheNames = "work", key = "#teacherNumber+#groupId+#pageable") @Override public Page getTeacherWork(String teacherNumber, String groupId, Pageable pageable) { Group group = groupDao.findById(groupId).orElseThrow(() -> new NoSuchFiledValueException("群ID: " + groupId + "不存在", HttpStatus.NOT_FOUND)); @@ -105,6 +112,10 @@ public Page getTeacherWork(String teacherNumber, String groupId, Page }.getType()), pageable, workPage.getTotalElements()); } + @Caching(evict = { + @CacheEvict(cacheNames = "studentUndoneWork", allEntries = true), + @CacheEvict(cacheNames = "work", allEntries = true) + }) @Override public Work createWork(String workName, String groupId, String format, boolean enabled) { if (!groupDao.existsById(groupId)) { @@ -119,6 +130,10 @@ public Work createWork(String workName, String groupId, String format, boolean e return workDao.save(work); } + @Caching(evict = { + @CacheEvict(cacheNames = "studentUndoneWork", allEntries = true), + @CacheEvict(cacheNames = "work", allEntries = true) + }) @Override public void changeEnabledWord(String workId, boolean enabled) { Work work = workDao.findById(workId).orElseThrow(() -> new NoSuchFiledValueException("作业ID: " + workId + "不存在", HttpStatus.NOT_FOUND)); @@ -126,6 +141,12 @@ public void changeEnabledWord(String workId, boolean enabled) { workDao.save(work); } + @Caching(evict = { + @CacheEvict(cacheNames = "studentDoneWork", allEntries = true), + @CacheEvict(cacheNames = "studentUndoneWork", allEntries = true), + @CacheEvict(cacheNames = "work", key = "'regex:'+#teacherNumber+'*'"), + @CacheEvict(cacheNames = "workDetail", key = "'regex:'+#workId+'*'") + }) @Override public void delWork(String workId, String teacherNumber) { Work work = workDao.findById(workId).orElseThrow(() -> new NoSuchFiledValueException("作业ID: " + workId + "不存在", HttpStatus.NOT_FOUND)); @@ -134,8 +155,10 @@ public void delWork(String workId, String teacherNumber) { throw new NoSuchFiledValueException("Forbidden", HttpStatus.FORBIDDEN); } workDao.delete(work); + uploadDao.deleteAll(uploadDao.findAllByWorkId(workId)); } + @Cacheable(cacheNames = "workDetail", key = "#workId+#teacherNumber+#pageable") @Override public Page getWorkDetailByWorkId(String teacherNumber, String workId, Pageable pageable) { Work work = workDao.findById(workId).orElseThrow(() -> new NoSuchFiledValueException("作业ID: " + workId + "不存在", HttpStatus.NOT_FOUND)); @@ -175,7 +198,6 @@ public Optional getOneWorkById(String workId) { * @return WorkModel */ private Page getWorkModels(Pageable pageable, List workList) { - //TODO 直接查所有分页意义不大,解决:Cache List //页数*每页条数 int to = (pageable.getPageNumber() + 1) * pageable.getPageSize(); int toIndex = to > workList.size() ? workList.size() : to; diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 470c1ac..e7589c8 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -8,6 +8,9 @@ spring: password: kingston jpa: show-sql: true + redis: + host: locahost + port: 6379 logging: level: debug level.top: debug diff --git a/src/main/resources/application-docker.yml b/src/main/resources/application-docker.yml index f4322f8..7b1a5f3 100644 --- a/src/main/resources/application-docker.yml +++ b/src/main/resources/application-docker.yml @@ -8,6 +8,9 @@ spring: password: ${MYSQL_PASSWORD} jpa: show-sql: false + redis: + host: ${REDIS_HOST} + port: ${REDIS_PORT} logging: level: warn level.top: warn diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index cb97e0e..b38580e 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -5,6 +5,9 @@ spring: url: jdbc:mysql://localhost:3306/shw2${datasource_url_end_str} username: root password: root + redis: + host: locahost + port: 6379 logging: level: error level.top: warn diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9e1b4d4..b602988 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -17,6 +17,20 @@ spring: multipart: max-file-size: 1024MB max-request-size: 1024MB + cache: + type: redis + redis: + jedis: + pool: + max-idle: 8 + min-idle: 0 + max-active: 8 + max-wait: -1 + database: 0 + data: + redis: + repositories: + enabled: false cas: server-url: http://login.greathiit.com login-url: http://login.greathiit.com/login diff --git a/src/test/java/top/yunshu/shw/server/test/CommonTest.java b/src/test/java/top/yunshu/shw/server/test/CommonTest.java new file mode 100644 index 0000000..f908c19 --- /dev/null +++ b/src/test/java/top/yunshu/shw/server/test/CommonTest.java @@ -0,0 +1,27 @@ +package top.yunshu.shw.server.test; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.test.context.junit4.SpringRunner; +import top.yunshu.shw.server.ShwServerApplication; + +import java.util.Set; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = ShwServerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class CommonTest { + @Autowired + private RedisTemplate redisTemplate; + + @Test + public void test1() { + redisTemplate.setDefaultSerializer(new StringRedisSerializer()); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + Set keys = redisTemplate.keys("work::00027*"); + redisTemplate.delete(keys); + } +}