修复协议错误
This commit is contained in:
+12
@@ -53,6 +53,10 @@ public class GroupCourseBooking extends BaseDomain {
|
||||
@Schema(description = "上课地点", example = "健身房A区")
|
||||
private String location;
|
||||
|
||||
//封面图URL(非DB字段,由 Service 从 GroupCourse 填充)
|
||||
@Schema(description = "封面图URL", example = "https://example.com/cover.jpg")
|
||||
private String coverImage;
|
||||
|
||||
public Long getCourseId() {
|
||||
return courseId;
|
||||
}
|
||||
@@ -132,4 +136,12 @@ public class GroupCourseBooking extends BaseDomain {
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public String getCoverImage() {
|
||||
return coverImage;
|
||||
}
|
||||
|
||||
public void setCoverImage(String coverImage) {
|
||||
this.coverImage = coverImage;
|
||||
}
|
||||
}
|
||||
+13
@@ -7,6 +7,7 @@ import cn.novalon.gym.manage.groupcourse.handler.BookingSagaHandler;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseBookingRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseBookingService;
|
||||
import cn.novalon.gym.manage.groupcourse.util.OSSUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
@@ -335,6 +336,18 @@ public class GroupCourseBookingService implements IGroupCourseBookingService {
|
||||
public Flux<GroupCourseBooking> getBookingsByMemberId(Long memberId) {
|
||||
logger.debug("查询会员预约记录:memberId={}", memberId);
|
||||
return bookingRepository.findByMemberId(memberId)
|
||||
.flatMap(booking -> {
|
||||
// 从关联的 GroupCourse 获取封面图并转为预签名URL
|
||||
if (booking.getCourseId() != null) {
|
||||
return courseRepository.findByIdAndDeletedAtIsNull(booking.getCourseId())
|
||||
.map(course -> {
|
||||
booking.setCoverImage(OSSUtil.toCoverPresignedUrl(course.getCoverImage()));
|
||||
return booking;
|
||||
})
|
||||
.defaultIfEmpty(booking);
|
||||
}
|
||||
return Mono.just(booking);
|
||||
})
|
||||
.doOnComplete(() -> logger.debug("查询完成:memberId={}", memberId));
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -5,6 +5,7 @@ import cn.novalon.gym.manage.groupcourse.domain.GroupCourseRecommend;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRecommendRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseRecommendService;
|
||||
import cn.novalon.gym.manage.groupcourse.util.OSSUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -134,6 +135,8 @@ public class GroupCourseRecommendService implements IGroupCourseRecommendService
|
||||
|
||||
return groupCourseRepository.findByIdAndDeletedAtIsNull(recommend.getCourseId())
|
||||
.map(course -> {
|
||||
// 将 OSS Key 转换为预签名URL,前端可直接加载
|
||||
course.setCoverImage(OSSUtil.toCoverPresignedUrl(course.getCoverImage()));
|
||||
recommend.setGroupCourse(course);
|
||||
return recommend;
|
||||
})
|
||||
|
||||
+37
-7
@@ -19,6 +19,7 @@ import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseTypeRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseService;
|
||||
import cn.novalon.gym.manage.groupcourse.util.QRCodeUtil;
|
||||
import cn.novalon.gym.manage.groupcourse.util.OSSUtil;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCard;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
|
||||
import cn.novalon.gym.manage.member.enums.MemberCardType;
|
||||
@@ -32,6 +33,7 @@ import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
@@ -153,7 +155,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
detail.setCurrentMembers(course.getCurrentMembers());
|
||||
detail.setStatus(course.getStatus());
|
||||
detail.setLocation(course.getLocation());
|
||||
detail.setCoverImage(course.getCoverImage());
|
||||
detail.setCoverImage(OSSUtil.toCoverPresignedUrl(course.getCoverImage()));
|
||||
detail.setDescription(course.getDescription());
|
||||
detail.setStoredValueAmount(course.getStoredValueAmount());
|
||||
detail.setQrCodePath(course.getQrCodePath());
|
||||
@@ -178,7 +180,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
try {
|
||||
GroupCourse groupCourse = objectMapper.readValue(cachedJson, GroupCourse.class);
|
||||
logger.info("缓存命中 - findById: id={}", id);
|
||||
return Mono.just(groupCourse);
|
||||
return Mono.just(fillCoverPresignedUrl(groupCourse));
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.warn("缓存解析失败,删除缓存 - id: {}, error: {}", id, e.getMessage());
|
||||
return redisUtil.delete(cacheKey).then(Mono.empty());
|
||||
@@ -188,6 +190,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
})
|
||||
.switchIfEmpty(
|
||||
groupCourseRepository.findByIdAndDeletedAtIsNull(id)
|
||||
.map(this::fillCoverPresignedUrl)
|
||||
.flatMap(groupCourse -> {
|
||||
try {
|
||||
String jsonData = objectMapper.writeValueAsString(groupCourse);
|
||||
@@ -205,16 +208,19 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
|
||||
@Override
|
||||
public Flux<GroupCourse> findAll() {
|
||||
return groupCourseRepository.findAll();
|
||||
return groupCourseRepository.findAll()
|
||||
.map(this::fillCoverPresignedUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<GroupCourse> findAll(boolean includeDeleted) {
|
||||
Flux<GroupCourse> flux;
|
||||
if(includeDeleted){
|
||||
return groupCourseRepository.findAll();
|
||||
flux = groupCourseRepository.findAll();
|
||||
}else{
|
||||
return groupCourseRepository.findByDeletedAtIsNull();
|
||||
flux = groupCourseRepository.findByDeletedAtIsNull();
|
||||
}
|
||||
return flux.map(this::fillCoverPresignedUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -234,6 +240,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
PageResponse<GroupCourse> pageResponse = objectMapper.readValue(cachedJson,
|
||||
objectMapper.getTypeFactory().constructParametricType(PageResponse.class, GroupCourse.class));
|
||||
logger.info("缓存命中 - findByPage: key={}", cacheKey);
|
||||
fillCoverPresignedUrl(pageResponse);
|
||||
return Mono.just(pageResponse);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.warn("缓存解析失败,删除缓存 - key: {}, error: {}", cacheKey, e.getMessage());
|
||||
@@ -254,6 +261,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
|
||||
return resultMono.flatMap(pageResponse -> {
|
||||
try {
|
||||
fillCoverPresignedUrl(pageResponse);
|
||||
String jsonData = objectMapper.writeValueAsString(pageResponse);
|
||||
return redisUtil.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS)
|
||||
.thenReturn(pageResponse)
|
||||
@@ -572,8 +580,11 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
query.getTimePeriod(), query.getPriceSort(), query.getRemainingMost());
|
||||
|
||||
return groupCourseRepository.searchGroupCourses(query)
|
||||
.doOnSuccess(result -> logger.info("多条件查询结果 - total={}, page={}, size={}",
|
||||
result.getTotalElements(), result.getCurrentPage(), result.getPageSize()))
|
||||
.doOnSuccess(result -> {
|
||||
fillCoverPresignedUrl(result);
|
||||
logger.info("多条件查询结果 - total={}, page={}, size={}",
|
||||
result.getTotalElements(), result.getCurrentPage(), result.getPageSize());
|
||||
})
|
||||
.doOnError(error -> logger.error("多条件查询失败 - error: {}", error.getMessage()));
|
||||
}
|
||||
|
||||
@@ -582,4 +593,23 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
.then(redisUtil.deleteByPattern(CACHE_KEY_ID_PREFIX + "*"))
|
||||
.then(redisUtil.deleteByPattern(CACHE_KEY_DETAIL_PREFIX + "*")).then();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将单个 GroupCourse 的 coverImage 从 OSS Key 转换为预签名URL
|
||||
*/
|
||||
private GroupCourse fillCoverPresignedUrl(GroupCourse course) {
|
||||
if (course != null) {
|
||||
course.setCoverImage(OSSUtil.toCoverPresignedUrl(course.getCoverImage()));
|
||||
}
|
||||
return course;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将分页结果中所有 GroupCourse 的 coverImage 从 OSS Key 转换为预签名URL
|
||||
*/
|
||||
private void fillCoverPresignedUrl(PageResponse<GroupCourse> pageResponse) {
|
||||
if (pageResponse != null && pageResponse.getContent() != null) {
|
||||
pageResponse.getContent().forEach(this::fillCoverPresignedUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-1
@@ -23,7 +23,7 @@ public class OSSUtil {
|
||||
private static final Logger logger = LoggerFactory.getLogger(OSSUtil.class);
|
||||
|
||||
// OSS配置信息
|
||||
private static final String ENDPOINT = "oss-cn-beijing.aliyuncs.com";
|
||||
private static final String ENDPOINT = "https://oss-cn-beijing.aliyuncs.com";
|
||||
private static final String ACCESS_KEY_ID = "LTAI5t9wHCiH68Xjxg64Xx4Y";
|
||||
private static final String ACCESS_KEY_SECRET = "isAfz1IFGAnV13LOIrVg19aPhY8aRq";
|
||||
private static final String BUCKET_NAME = "ycc-filesaver";
|
||||
@@ -37,6 +37,8 @@ public class OSSUtil {
|
||||
|
||||
// 预签名URL有效期(秒)
|
||||
private static final long PRESIGN_EXPIRE_SECONDS = 300;
|
||||
// 封面图预签名URL有效期(秒)- 1小时,确保前端列表页足够展示
|
||||
private static final long COVER_PRESIGN_EXPIRE_SECONDS = 3600;
|
||||
|
||||
/**
|
||||
* 上传文件到阿里云OSS(文件默认继承Bucket权限,不设置ACL)
|
||||
@@ -171,4 +173,21 @@ public class OSSUtil {
|
||||
public static String getPublicUrl(String ossKey) {
|
||||
return OSS_URL_PREFIX + ossKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将OSS Key(相对路径)转换为封面图预签名URL(1小时有效期)
|
||||
* 如果已经是HTTP(S)完整URL则直接返回
|
||||
*
|
||||
* @param ossKey OSS对象Key或完整URL
|
||||
* @return 可访问的预签名URL,ossKey为空时返回null
|
||||
*/
|
||||
public static String toCoverPresignedUrl(String ossKey) {
|
||||
if (ossKey == null || ossKey.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
if (ossKey.startsWith("http://") || ossKey.startsWith("https://")) {
|
||||
return ossKey;
|
||||
}
|
||||
return generatePresignedUrl(ossKey, COVER_PRESIGN_EXPIRE_SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
+1995
-72
File diff suppressed because it is too large
Load Diff
@@ -65,7 +65,7 @@ async function handleDelete(row: any) {
|
||||
inputType: 'password',
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
}).catch(() => {})
|
||||
}).catch(() => ({ value: '' }))
|
||||
if (!pwd) {
|
||||
ElMessage.warning('请输入验证密码')
|
||||
return
|
||||
@@ -94,7 +94,7 @@ async function handleSave() {
|
||||
inputType: 'password',
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
}).catch(() => {})
|
||||
}).catch(() => ({ value: '' }))
|
||||
if (!pwd) {
|
||||
ElMessage.warning('请输入验证密码')
|
||||
return
|
||||
@@ -132,7 +132,7 @@ async function handleAssignRoles() {
|
||||
inputType: 'password',
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
}).catch(() => {})
|
||||
}).catch(() => ({ value: '' }))
|
||||
if (!pwd) {
|
||||
ElMessage.warning('请输入验证密码')
|
||||
return
|
||||
|
||||
@@ -89,8 +89,8 @@ async function fetchData() {
|
||||
newMemberData[i] = res?.newMembers ?? 0
|
||||
activeMemberData[i] = res?.activeMembers ?? 0
|
||||
if (i === todayIdx) {
|
||||
statCards.value[0].value = res?.totalMembers ?? 0
|
||||
statCards.value[1].value = res?.newMembers ?? 0
|
||||
statCards.value[0]!.value = res?.totalMembers ?? 0
|
||||
statCards.value[1]!.value = res?.newMembers ?? 0
|
||||
}
|
||||
}).catch(() => {}),
|
||||
|
||||
@@ -98,14 +98,14 @@ async function fetchData() {
|
||||
bookingData[i] = res?.newBookings ?? 0
|
||||
cancelData[i] = res?.cancelBookings ?? 0
|
||||
if (i === todayIdx) {
|
||||
statCards.value[3].value = res?.newBookings ?? 0
|
||||
statCards.value[3]!.value = res?.newBookings ?? 0
|
||||
}
|
||||
}).catch(() => {}),
|
||||
|
||||
getSignInStatistics(range).then((res: any) => {
|
||||
signinData[i] = res?.totalSignIns ?? 0
|
||||
if (i === todayIdx) {
|
||||
statCards.value[2].value = res?.totalSignIns ?? 0
|
||||
statCards.value[2]!.value = res?.totalSignIns ?? 0
|
||||
}
|
||||
}).catch(() => {}),
|
||||
)
|
||||
@@ -116,7 +116,7 @@ async function fetchData() {
|
||||
getRevenueStatistics().then((res: any) => {
|
||||
const data = res?.data
|
||||
if (data) {
|
||||
statCards.value[4].value = (data.todayIncome ?? 0).toFixed(2)
|
||||
statCards.value[4]!.value = (data.todayIncome ?? 0).toFixed(2)
|
||||
}
|
||||
}).catch(() => {}),
|
||||
)
|
||||
|
||||
@@ -123,7 +123,7 @@ async function fetchData() {
|
||||
const cache: Record<number, LabelItem[]> = {}
|
||||
labelResults.forEach((result, i) => {
|
||||
if (result.status === 'fulfilled' && Array.isArray(result.value)) {
|
||||
cache[arr[i].id] = result.value as LabelItem[]
|
||||
cache[arr[i]!.id] = result.value as LabelItem[]
|
||||
}
|
||||
})
|
||||
labelCache.value = cache
|
||||
|
||||
@@ -147,7 +147,7 @@ function handleSearch() {
|
||||
function resetFilters() {
|
||||
keyword.value = ''
|
||||
genderFilter.value = null
|
||||
sortBy.value = sortOptions[0]
|
||||
sortBy.value = sortOptions[0]!
|
||||
currentPage.value = 1
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ async function handleDelete(row: any) {
|
||||
inputType: 'password',
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
}).catch(() => {})
|
||||
}).catch(() => ({ value: '' }))
|
||||
if (!pwd) {
|
||||
ElMessage.warning('请输入验证密码')
|
||||
return
|
||||
@@ -151,7 +151,7 @@ async function handleSave() {
|
||||
inputType: 'password',
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
}).catch(() => {})
|
||||
}).catch(() => ({ value: '' }))
|
||||
if (!pwd) {
|
||||
ElMessage.warning('请输入验证密码')
|
||||
return
|
||||
@@ -189,7 +189,7 @@ async function handleAssignPerms() {
|
||||
inputType: 'password',
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
}).catch(() => {})
|
||||
}).catch(() => ({ value: '' }))
|
||||
if (!pwd) {
|
||||
ElMessage.warning('请输入验证密码')
|
||||
return
|
||||
|
||||
@@ -6,6 +6,7 @@ import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
base: '/cuit/',
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
|
||||
Reference in New Issue
Block a user