mirror of
https://gitee.com/hhyykk/ipms-sjy.git
synced 2025-07-24 16:05:08 +08:00
Merge remote-tracking branch 'origin/master' into feature/springdoc
# Conflicts: # README.md # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyValueController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/ProductPropertyViewRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyAndValueRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyCreateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyListReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyPageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyUpdateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueCreateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValuePageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueUpdateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuCreateOrUpdateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuDetailRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuPageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuRespVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/property/AppProductPropertyValueDetailRespVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderPageReqVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/TradeOrderItemRespVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/TradeOrderRespVO.java # yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java # yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/vo/PayNotifyOrderReqVO.java # yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/vo/PayRefundOrderReqVO.java # yudao-server/pom.xml # yudao-server/src/main/java/cn/iocoder/yudao/module/shop/controller/app/AppShopOrderController.java
This commit is contained in:
@ -1,11 +1,14 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.mysql.file;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.file.core.client.db.DBFileContentFrameworkDAO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileContentDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public class FileContentDAOImpl implements DBFileContentFrameworkDAO {
|
||||
@ -27,9 +30,11 @@ public class FileContentDAOImpl implements DBFileContentFrameworkDAO {
|
||||
|
||||
@Override
|
||||
public byte[] selectContent(Long configId, String path) {
|
||||
FileContentDO fileContentDO = fileContentMapper.selectOne(
|
||||
buildQuery(configId, path).select(FileContentDO::getContent));
|
||||
return fileContentDO != null ? fileContentDO.getContent() : null;
|
||||
List<FileContentDO> list = fileContentMapper.selectList(
|
||||
buildQuery(configId, path).select(FileContentDO::getContent).orderByDesc(FileContentDO::getId));
|
||||
return Optional.ofNullable(CollUtil.getFirst(list))
|
||||
.map(FileContentDO::getContent)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<FileContentDO> buildQuery(Long configId, String path) {
|
||||
|
@ -18,6 +18,8 @@ import org.springframework.stereotype.Component;
|
||||
import java.util.*;
|
||||
|
||||
import static cn.hutool.core.text.CharSequenceUtil.*;
|
||||
import static cn.hutool.core.util.RandomUtil.randomEle;
|
||||
import static cn.hutool.core.util.RandomUtil.randomInt;
|
||||
|
||||
/**
|
||||
* 代码生成器的 Builder,负责:
|
||||
@ -128,6 +130,7 @@ public class CodegenBuilder {
|
||||
// 初始化 Column 列的默认字段
|
||||
processColumnOperation(column); // 处理 CRUD 相关的字段的默认值
|
||||
processColumnUI(column); // 处理 UI 相关的字段的默认值
|
||||
processColumnExample(column); // 处理字段的 swagger example 示例
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
@ -169,4 +172,42 @@ public class CodegenBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理字段的 swagger example 示例
|
||||
*
|
||||
* @param column 字段
|
||||
*/
|
||||
private void processColumnExample(CodegenColumnDO column) {
|
||||
// id、price、count 等可能是整数的后缀
|
||||
if (StrUtil.endWithAnyIgnoreCase(column.getJavaField(), "id", "price", "count")) {
|
||||
column.setExample(String.valueOf(randomInt(1, Short.MAX_VALUE)));
|
||||
return;
|
||||
}
|
||||
// name
|
||||
if (StrUtil.endWithIgnoreCase(column.getJavaField(), "name")) {
|
||||
column.setExample(randomEle(new String[]{"张三", "李四", "王五", "赵六", "芋艿"}));
|
||||
return;
|
||||
}
|
||||
// status
|
||||
if (StrUtil.endWithAnyIgnoreCase(column.getJavaField(), "status", "type")) {
|
||||
column.setExample(randomEle(new String[]{"1", "2"}));
|
||||
return;
|
||||
}
|
||||
// url
|
||||
if (StrUtil.endWithIgnoreCase(column.getColumnName(), "url")) {
|
||||
column.setExample("https://www.iocoder.cn");
|
||||
return;
|
||||
}
|
||||
// reason
|
||||
if (StrUtil.endWithIgnoreCase(column.getColumnName(), "reason")) {
|
||||
column.setExample(randomEle(new String[]{"不喜欢", "不对", "不好", "不香"}));
|
||||
return;
|
||||
}
|
||||
// description、memo、remark
|
||||
if (StrUtil.endWithAnyIgnoreCase(column.getColumnName(), "description", "memo", "remark")) {
|
||||
column.setExample(randomEle(new String[]{"你猜", "随便", "你说的对"}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package cn.iocoder.yudao.module.infra.service.file;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
@ -20,7 +19,6 @@ import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
|
||||
import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -79,20 +77,36 @@ public class FileConfigServiceImpl implements FileConfigService {
|
||||
@Resource
|
||||
private Validator validator;
|
||||
|
||||
@Resource
|
||||
@Lazy // 注入自己,所以延迟加载
|
||||
private FileConfigService self;
|
||||
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initFileClients() {
|
||||
// 获取文件配置,如果有更新
|
||||
List<FileConfigDO> configs = loadFileConfigIfUpdate(maxUpdateTime);
|
||||
if (CollUtil.isEmpty(configs)) {
|
||||
initLocalCacheIfUpdate(null);
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||
public void schedulePeriodicRefresh() {
|
||||
initLocalCacheIfUpdate(this.maxUpdateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新本地缓存
|
||||
*
|
||||
* @param maxUpdateTime 最大更新时间
|
||||
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||
*/
|
||||
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||
if (maxUpdateTime != null
|
||||
&& fileConfigMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||
return;
|
||||
}
|
||||
List<FileConfigDO> configs = fileConfigMapper.selectList();
|
||||
log.info("[initLocalCacheIfUpdate][缓存文件配置,数量为:{}]", configs.size());
|
||||
|
||||
// 创建或更新支付 Client
|
||||
// 第二步:构建缓存。创建或更新文件 Client
|
||||
configs.forEach(config -> {
|
||||
fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig());
|
||||
// 如果是 master,进行设置
|
||||
@ -101,35 +115,8 @@ public class FileConfigServiceImpl implements FileConfigService {
|
||||
}
|
||||
});
|
||||
|
||||
// 写入缓存
|
||||
maxUpdateTime = CollectionUtils.getMaxValue(configs, FileConfigDO::getUpdateTime);
|
||||
log.info("[initFileClients][初始化 FileConfig 数量为 {}]", configs.size());
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||
public void schedulePeriodicRefresh() {
|
||||
self.initFileClients();
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果文件配置发生变化,从数据库中获取最新的全量文件配置。
|
||||
* 如果未发生变化,则返回空
|
||||
*
|
||||
* @param maxUpdateTime 当前文件配置的最大更新时间
|
||||
* @return 文件配置列表
|
||||
*/
|
||||
private List<FileConfigDO> loadFileConfigIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 第一步,判断是否要更新。
|
||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
||||
log.info("[loadFileConfigIfUpdate][首次加载全量文件配置]");
|
||||
} else { // 判断数据库中是否有更新的文件配置
|
||||
if (fileConfigMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
return null;
|
||||
}
|
||||
log.info("[loadFileConfigIfUpdate][增量加载全量文件配置]");
|
||||
}
|
||||
// 第二步,如果有更新,则从数据库加载所有文件配置
|
||||
return fileConfigMapper.selectList();
|
||||
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||
this.maxUpdateTime = CollectionUtils.getMaxValue(configs, FileConfigDO::getUpdateTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<vxe-grid ref="xGrid" v-bind="gridOptions" class="xtable-scrollbar">
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:新增 -->
|
||||
<XButton
|
||||
@ -43,7 +43,7 @@
|
||||
@click="handleDelete(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<!-- 弹窗 -->
|
||||
<XModal id="${classNameVar}Model" :loading="modelLoading" v-model="modelVisible" :title="modelTitle">
|
||||
@ -79,8 +79,7 @@
|
||||
import { ref, unref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { useVxeGrid } from '@/hooks/web/useVxeGrid'
|
||||
import { VxeGridInstance } from 'vxe-table'
|
||||
import { useXTable } from '@/hooks/web/useXTable'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
// 业务相关的 import
|
||||
import { rules, allSchemas } from './${classNameVar}.data'
|
||||
@ -90,8 +89,7 @@ const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
// 列表相关的变量
|
||||
const xGrid = ref<VxeGridInstance>() // 列表 Grid Ref
|
||||
const { gridOptions, getList, deleteData, exportList } = useVxeGrid<${simpleClassName}Api.${simpleClassName}VO>({
|
||||
const [registerTable, { reload, deleteData, exportList }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: ${simpleClassName}Api.get${simpleClassName}PageApi,
|
||||
deleteApi: ${simpleClassName}Api.delete${simpleClassName}Api,
|
||||
@ -123,7 +121,7 @@ const handleCreate = () => {
|
||||
|
||||
// 导出操作
|
||||
const handleExport = async () => {
|
||||
await exportList(xGrid, '${table.classComment}.xls')
|
||||
await exportList('${table.classComment}.xls')
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
@ -145,7 +143,7 @@ const handleDetail = async (rowId: number) => {
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (rowId: number) => {
|
||||
await deleteData(xGrid, rowId)
|
||||
await deleteData(rowId)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
@ -169,7 +167,7 @@ const submitForm = async () => {
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
// 刷新列表
|
||||
await getList(xGrid)
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -79,7 +79,7 @@ public class FileServiceTest extends BaseDbUnitTest {
|
||||
FileClient client = mock(FileClient.class);
|
||||
when(fileConfigService.getMasterFileClient()).thenReturn(client);
|
||||
String url = randomString();
|
||||
when(client.upload(same(content), same(path), same("image/jpeg"))).thenReturn(url);
|
||||
when(client.upload(same(content), same(path), eq("image/jpeg"))).thenReturn(url);
|
||||
when(client.getId()).thenReturn(10L);
|
||||
String name = "单测文件名";
|
||||
// 调用
|
||||
|
@ -9,7 +9,7 @@ spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
name: ruoyi-vue-pro
|
||||
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
|
||||
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
|
||||
driver-class-name: org.h2.Driver
|
||||
username: sa
|
||||
password:
|
||||
|
Reference in New Issue
Block a user