递归和内连接(左连接)两种方式
详细内容见文档笔记
api模块引入了service模块,并且两个模块都进行了nacos配置。由于api模块应用了service模块,所以在api的bootstrp中必须用extension-configs扩展配置文件 的方式引用service工程所用到的配置文件
nacos提供了shared-configs可以引入公用配置。
在content-api中配置了swagger,所有的接口工程 都需要配置swagger,这里就可以将swagger的配置定义为一个公用配置,哪个项目用引入即可。进入nacos的开发环境,添加swagger-dev.yaml公用配置。项目使用shared-configs可以引入公用配置。在接口工程的本地配置文件 中引入公用配置,如下:
配置示例
spring:
application:
name: content-api
cloud:
nacos:
server-addr: 192.168.101.65:8848
discovery:
namespace: dev
group: xuecheng-plus-project
config:
namespace: dev
group: xuecheng-plus-project
file-extension: yaml
refresh-enabled: true
extension-configs:
- data-id: content-service-${spring.profiles.active}.yaml
group: xuecheng-plus-project
refresh: true
shared-configs:
- data-id: swagger-${spring.profiles.active}.yaml
group: xuecheng-plus-common
refresh: true
- data-id: logging-${spring.profiles.active}.yaml
group: xuecheng-plus-common
refresh: true
profiles:
active: dev
nacos各配置文件 的优先级
项目应用名配置文件(nacos配置) > 扩展配置文件 > 共享配置文件 > 本地配置文件。
有时候我们在测试程序时直接在本地加一个配置进行测试,这时我们想让本地最优先,可以在nacos配置文件 中配置如下即可实现(nacos对应的项目应用名配置文件):
#配置本地优先
spring:
cloud:
config:
override-none: true
本项目主要用于路由转发
gateway本地bootstrap配置文件
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 192.168.101.65:8848
discovery:
namespace: ${spring.profiles.active}
group: xuecheng-plus-project
config:
namespace: ${spring.profiles.active}
group: xuecheng-plus-project
file-extension: yaml
refresh-enabled: true
shared-configs:
- data-id: logging-${spring.profiles.active}.yaml
group: xuecheng-plus-common
refresh: true
profiles:
active: dev
nacos中gateway的配置
server:
port: 63010 # 网关端口
spring:
cloud:
gateway:
# filter:
# strip-prefix:
# enabled: true
routes: # 网关路由配置
- id: content-api # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://content-api # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/content/** # 这个是按照路径匹配,只要以/content/开头就符合要求
# filters:
# - StripPrefix=1
- id: system-api
# uri: http://127.0.0.1:8081
uri: lb://system-api
predicates:
- Path=/system/**
# filters:
# - StripPrefix=1
- id: media-api
# uri: http://127.0.0.1:8081
uri: lb://media-api
predicates:
- Path=/media/**
# filters:
# - StripPrefix=1
此部分涉及IO流对文件的读取,新知识需要学习
分块代码:
public static void testChunk() throws IOException {
File sourceFile = new File("D:/data/videoTest.mp4");
String chunkPath = "D:/data/chunk/";
File chunkFolder = new File(chunkPath);
if (!chunkFolder.exists()) {
chunkFolder.mkdirs();
}
//分块大小
long chunkSize = 1024 * 1024 * 1;
//分块数量
long chunkNum = (long) Math.ceil(sourceFile.length() * 1.0 / chunkSize);
System.out.println("分块总数:"+chunkNum);
//缓冲区大小
byte[] b = new byte[1024*1024];
//使用RandomAccessFile访问文件
RandomAccessFile raf_read = new RandomAccessFile(sourceFile, "r");
//分块
for (int i = 0; i < chunkNum; i++) {
//创建分块文件
File file = new File(chunkPath + i);
if(file.exists()){
file.delete();
}
boolean newFile = file.createNewFile();
if (newFile) {
//向分块文件中写数据
RandomAccessFile raf_write = new RandomAccessFile(file, "rw");
int len = -1;
while ((len = raf_read.read(b)) != -1) {
raf_write.write(b, 0, len);
if (file.length() >= chunkSize) {
break;
}
}
raf_write.close();
System.out.println("完成分块"+i);
}
}
raf_read.close();
}
由于平时习惯使用文件输入输出流来实现IO的读写,所以可以将RandomAccessFile改为输入输出流。
RandomAccessFile随机流和IO流的原理是不同的。随机流的优点就是可以随意从指定的位置读取文件。
public static void testChunks() throws IOException{
// 要读取的文件
File sourceFile = new File("D:/data/videoTest.mp4");
// 分割文件的存储目录
String chunkPath = "D:/data/chunk/";
File chunkFolder = new File(chunkPath);
if(!chunkFolder.exists())
chunkFolder.mkdirs();
// 分块大小
long chunkSize = 1024 * 1024 * 1;
// 分块数量
long chunkNum = (long) Math.ceil(sourceFile.length() * 1.0 / chunkSize);
System.out.println("分块总数:"+chunkNum);
// 缓冲区大小
byte[] b = new byte[1024*1024];
InputStream stream_read = new FileInputStream(sourceFile);
for(int i = 0; i < chunkNum; i++){
// 创建分块文件对象
File file = new File(chunkPath + i);
if(file.exists())
file.delete();
// 创建分块文件
boolean newFile = file.createNewFile();
if(newFile){
int len = -1;
FileOutputStream stream_write = new FileOutputStream(file);
// 原理:输入流在第一次被读取使用之后,流的数据就消耗完了,所以想要重用的话,
// 需要将流数据存入容器,再从容器取出数据构造流
// 所以下面的while循环和if中的break可以保证每次都是写入规定大小的数据
while((len = stream_read.read(b)) != -1){
stream_write.write(b, 0, len);
if(file.length() > chunkSize)
break;
}
stream_write.close();
System.out.println("完成分块"+i);
}
}
stream_read.close();
}
合并分块文件
通过对合并后文件和源文件MD5值对比可以判断合并是否成功
public void testMerge() throws IOException {
//块文件目录
File chunkFolder = new File("D:/data/chunk/");
//原始文件
File originalFile = new File("D:/data/videoTest.mp4");
//合并文件
File mergeFile = new File("D:/data/videoTest1.mp4");
if (mergeFile.exists()) {
mergeFile.delete();
}
//创建新的合并文件
mergeFile.createNewFile();
//用于写文件
RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");
//指针指向文件顶端
raf_write.seek(0);
//缓冲区
byte[] b = new byte[1024];
//分块列表
File[] fileArray = chunkFolder.listFiles();
// 转成集合,便于排序
List<File> fileList = Arrays.asList(fileArray);
// 从小到大排序
Collections.sort(fileList, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return Integer.parseInt(o1.getName()) - Integer.parseInt(o2.getName());
}
});
//合并文件
for (File chunkFile : fileList) {
RandomAccessFile raf_read = new RandomAccessFile(chunkFile, "rw");
int len = -1;
while ((len = raf_read.read(b)) != -1) {
raf_write.write(b, 0, len);
}
raf_read.close();
}
raf_write.close();
//校验文件
try (
FileInputStream fileInputStream = new FileInputStream(originalFile);
FileInputStream mergeFileStream = new FileInputStream(mergeFile);
) {
//取出原始文件的md5
String originalMd5 = DigestUtils.md5Hex(fileInputStream);
//取出合并文件的md5进行比较
String mergeFileMd5 = DigestUtils.md5Hex(mergeFileStream);
if (originalMd5.equals(mergeFileMd5)) {
System.out.println("合并文件成功");
} else {
System.out.println("合并文件失败");
}
}
}
生成实体类对应的mapper和xml文件
定义MybatisPlusConfig,用于扫描mapper和配置分页拦截器
@MapperScan("com.xuecheng.content.mapper")
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
测试mapper功能
创建service接口,创建实现类,以查询课程信息为例:
@Service
public class CourseBaseInfoServiceImpl implements CourseBaseInfoService {
@Autowired
CourseBaseMapper courseBaseMapper;
@Override
public PageResult<CourseBase> queryCourseBaseList(PageParams pageParams, QueryCourseParamsDto queryCourseParamsDto) {
LambdaQueryWrapper<CourseBase> wrapper = new LambdaQueryWrapper<>();
// 构建查询条件,根据课程名称查询
wrapper.like(StringUtils.isNotEmpty(queryCourseParamsDto.getCourseName()),
CourseBase::getName, queryCourseParamsDto.getCourseName());
// 构建查询条件,根据审核状态查询
wrapper.eq(StringUtils.isNotEmpty(queryCourseParamsDto.getAuditStatus()),
CourseBase::getAuditStatus, queryCourseParamsDto.getAuditStatus());
// 构建查询条件,根据课程发布状态查询
wrapper.eq(StringUtils.isNotEmpty(queryCourseParamsDto.getPublishStatus()),
CourseBase::getAuditStatus, queryCourseParamsDto.getPublishStatus());
// 分页查询
Page<CourseBase> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
Page<CourseBase> selectPage = courseBaseMapper.selectPage(page, wrapper);
return new PageResult<CourseBase>(selectPage.getRecords(),
selectPage.getTotal(), pageParams.getPageNo(), pageParams.getPageSize());
}
}
存在问题
(1)由于本项目将controller和service分成了两个模块,controller放在了api模块,service和mapper放在了service模块。所以数据库连接配置写在了service的配置文件中。配置文件结尾必须是yaml,yml结尾会出错。
(2)最开始将配置文件放在了service模块的test目录下用于单元测试,导致api模块调用service中的服务时会出现数据库连接没有配置,这是因为模块之间相互引用只会引用main目录下的文件,test目录下的不会引用,需要将service中的配置文件放在main目录下的resource中。
(3)并且如果想在test中测试接口,也需要在test中的resource配置数据库连接。
(4)如果pom中添加个禁止过滤文件,也需要将yaml文件加入进去
src/main/java
**/*.properties
**/*.xml
**/*.yaml
false
src/main/resources
**/*.properties
**/*.xml
**/*.yaml
false