谷粒随享
学习目标:
随堂流程图:https://kdocs.cn/join/gpllwk8?f=101
项目地址:https://gitee.com/lvhonlgong/tingshu-1023.git
课件更新命令:
git pull origin master
如果修改过课件导致本地跟远端不一致,强制更新(远端覆盖本地)
git fetch --all
git reset --hard origin/master
git pull
随着智能手机和高速互联网的普及,人们开始寻求更便捷的方式来获取信息和娱乐。有声书的出现使得人们可以在旅途中、跑步时、做家务时等各种场景下,以更加灵活的方式享受阅读。
在过去,有声书主要是由专业的演员朗读,制作成录音带或CD。但随着数字化媒体的发展,听书软件应运而生,为用户提供了更多选择,包括自助出版的有声书和多样化的内容。
意义:
总的来说,听书软件的开发推动了阅读体验的数字化和个性化,为用户提供了更加便捷、多样化的阅读方式,也促进了作家和内容创作者的创作和传播。
内容创作者: 将书籍录制成有声书(音频)制作成专辑,在听书平台上发布专辑,可以达到盈利目的。
听书运营端:运营听书平台上所有的专辑,对专辑进行审核、发布、订单管理、支付管理。消费者消费(购买VIP会员,购买专辑、购买声音)可以达到盈利目的。
用户:收听平台上优质资源(知识付费),购买会员,购买专辑,购买声音
前端技术栈
参考听书软件环境安装.md
导入听书初始化项目资料中的tingshu-parent项目导入idea开发工具中即可!
第一步:
第二步:改NAT模式的子网IP:192.168.200.0
第三步:应用确定
第四步:启动虚拟机
第五步:登录虚拟机
IP:192.168.200.6
登录用户:root
登录密码:root
目前在虚拟机中安装以下容器服务都是开机自启动!
名 | URL | 账号密码 |
---|---|---|
Portainer | http://192.168.200.6:19000 | admin/admin1234567 |
MySQL | 192.168.200.6:3306 | root/root |
Redis | 192.168.200.6:6379 | |
Elasticsearch | http://192.168.200.6:9200 | elastic/111111 |
Kibana | http://192.168.200.6:5601 | elastic/111111 |
Logstash | 收集日志的后台进程,无需访问 | |
Zookeeper | 192.168.200.6:2181 | |
Kafka | 192.168.200.6:9092 | |
Kafdrop | http://192.168.200.6:9093 | |
Zipkin | http://192.168.200.6:9411 | |
Nacos | http://192.168.200.6:8848/nacos | nacos/nacos |
MinIO | http://192.168.200.6:9001 | admin/admin123456 |
YAPI | http://192.168.200.6:3000 | admin@admin.com/ymfe.org |
MongoDB | 192.168.200.6:27017 |
Tips:如果发现某些容器启动失败(说明该容器依赖其他容器,确保被依赖容器先正常启动),重启虚拟机
Nacos容器依赖MySQL容器-先启动MySQL容器
Kibana容器依赖ElasticSearch容器:先启动ES容器
Kafka容器依赖zookeeper容器:先启动zookeeper
Kafdrop容器依赖Kafka容器:先启动Kafka
YAPI容器依赖MongoDB容器:先启动MongoDB
如果发现Docker服务重启/启动报错。将Docker服务重启,所有容器开机自启
systemctl restart docker
配套资料\02-软件\找到安装微信开发者工具
每个同学注册申请微信小程序测试号(微信登录会使用到)测试账号申请入口https://mp.weixin.qq.com/wxamp/sandbox?doc=1
appId:测试号应用ID
appSecret:测试号对应秘钥
在微信开发者工具中导入,导入选择信任项目,注意:这里填写自己申请测试号应用ID
Mybatis 增强工具包 - 只做增强不做改变,简化CRUD
操作,
创建数据库:user_demo
初始化用户表
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '姓名',
`age` int DEFAULT NULL COMMENT '年龄',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `user` (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
创建demo工程mp_demo
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.atguigu</groupId>
<artifactId>mp_demo</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
<!-- 版本对应: https://start.spring.io/actuator/info -->
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<mysql.version>8.0.30</mysql.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
applicaton.yaml
server:
port: 8801
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.200.6:3306/user_demo?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=true
username: root
password: root
hikari:
connection-test-query: SELECT 1
connection-timeout: 60000
idle-timeout: 500000
max-lifetime: 540000
maximum-pool-size: 10
minimum-idle: 5
pool-name: GuliHikariPool
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*Mapper.xml
启动类
package com.atguigu;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author: atguigu
* @create: 2024-02-18 14:22
*/
@SpringBootApplication
public class MPDemoApp {
public static void main(String[] args) {
SpringApplication.run(MPDemoApp.class, args);
}
}
MyBatisPlus配置
package com.atguigu.demo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.atguigu.demo.mapper") //扫描持久层所在包全路径
public class MybatisPlusConfig {
/**
* 添加分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//如果配置多个插件,切记分页最后添加
//interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbType
return interceptor;
}
}
创建实体类
package com.atguigu.demo.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("user")
public class User {
//主键自增
@TableId(type = IdType.AUTO)
private Long id;
//普通列
@TableField("user_name")
private String name;
private Integer age;
private String email;
}
新增持久层Mapper继承BaseMapper
package com.atguigu.mp.mapper;
import com.atguigu.mp.model.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface UserMapper extends BaseMapper<User> {
}
新增业务层接口,继承 IService<实体类class>
package com.atguigu.mp.service;
import com.atguigu.mp.model.User;
import com.baomidou.mybatisplus.extension.service.IService;
public interface UserService extends IService<User> {
//继承MP父接口未实现方法
}
Service实现类
package com.atguigu.mp.service.impl;
import com.atguigu.mp.mapper.UserMapper;
import com.atguigu.mp.model.User;
import com.atguigu.mp.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* @author: atguigu
* @create: 2023-11-11 14:25
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
调用持久层或者业务层CURD方法。业务层提供方法都是Mapper持久层方法
package com.atguigu.mp.service.impl;
import com.atguigu.mp.mapper.UserMapper;
import com.atguigu.mp.model.User;
import com.atguigu.mp.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* @author: atguigu
* @create: 2023-12-06 13:58
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
测试类
package com.atguigu;
import com.atguigu.demo.mapper.UserMapper;
import com.atguigu.demo.model.User;
import com.atguigu.demo.service.UserService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.Year;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class MPDemoAppTest {
@Autowired
private UserMapper userMapper;
/**
* 测试MP提供持久层CURD方法
*/
@Test
public void testMapper(){
//System.out.println("xxxx");
//1.新增
//User user = new User();
//user.setName("张三");
//user.setAge(15);
//user.setEmail("zhangsan@qq.com");
//userMapper.insert(user);
//System.out.println(user);
//2.主键查询
//User user1 = userMapper.selectById(6);
//System.out.println(user1);
//3.查询所有
//List<User> userList = userMapper.selectList(null);
//System.out.println(userList);
//4.修改
//User user = new User();
//user.setId(6L);
//user.setName("zhangsan");
//userMapper.updateById(user);
//5.删除
//userMapper.deleteById(6L);
//6.条件查询 根据用户姓名+年龄等值查询列表
//6.1 创建Wrapper条件对象 弊端:有字段写错风险
//QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//6.2 封装查询条件 from user where user_name = 'Sandy'
//queryWrapper.eq("user_name", "Sandy");
//queryWrapper.eq("age", 21);
//支持链式编程
//queryWrapper.eq("user_name", "Sandy").eq("age", 21);
//7. 条件构建 采用Lambda形式
//LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
//// 通过方法引用获取类中数属性上注解中字段名称
//queryWrapper.eq(User::getName, "Sandy")
// .between(User::getAge, 21, 25);
//List<User> userList = userMapper.selectList(queryWrapper);
//System.out.println(userList);
//8.分页查询 MP自动拦截查询SQL生成分页sql limit ?(起始位置),?(页大小) 前提配置分页插件
Page<User> page = new Page<>(1, 2);
Page<User> userPage = userMapper.selectPage(page, null);
System.out.println("总页: " + userPage.getPages());
System.out.println("总记录数 = " + userPage.getTotal());
System.out.println("当前页记录 = " + userPage.getRecords());
}
@Autowired
private UserService userService;
/**
* 测试业务层CURD方法
*/
@Test
public void testService(){
//1.查询单个
//User user = userService.getById(5L);
//System.out.println(user);
//2.新增
//User user = new User();
//user.setName("李四");
//user.setAge(20);
//user.setEmail("lisi@qq.com");
//userService.save(user);
//3.查询所有
//List<User> list = userService.list();
//System.out.println(list);
//4.修改
//User user = new User();
//user.setId(7L);
//user.setName("lisi");
//userService.updateById(user);
//5.删除
//userService.removeById(7L);
//7.条件分页查询 模糊查询姓名,范围查询年龄
//7.1 构建分页对象
Page<User> page = new Page<>(1, 2);
//7.2 构建条件对象
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(User::getName, "a")
.ge(User::getAge, 10);
//7.3 执行分页查询
Page<User> userPage = userService.page(page, queryWrapper);
System.out.println(userPage);
}
}
功能入口:运行app项目-->我的-->创作中心-->专辑-->点击 + 添加专辑。提供给内容创作者/平台运营人员。使用(该功能必须登录才能访问),专辑经过审核(文字+封面)机制,审核通过内容创作者用户录制声音(音频文件-声音管理)新增专辑,将声音关联到专辑下。其他的普通用户(网民)可以在APP/小程序中进行获取内容资源(有声书等资源)
主要功能如下:
需求:在保存专辑需要为新增专辑关联分类,三级分类数据需要采用列表展示
创建视图:
# 需求:查询所有一级分类类别,某个一级分类下包含所有二级分类,所有二级分类下包含三级分类
# 实现方案:选择视图使用
# 视图:create [or replace] view 视图名称 as SQL; 随时可以替换视图
# 作用:视图是一张虚拟的表,本身不存放数据,视图中数据都来源于SQL对应原始表
# 1.屏蔽表中字段 视图新建SQL中指定部分字段;只给开发者用户分配视图读权限
# 2.视图可以封装数据来自于N张复杂关联查询,将来直接查询视图即可,简化开发
create or replace view v_album as select * from album_info;
create or replace view v_album as select id,album_title from album_info;
-- 查询原始表
select * from album_info;
-- 查询视图
select * from v_album;
#实现:将1,2,3级分类表进行关联查询,将1,2,3级分类相关信息都保存到视图中,将来应用程序查询视图
/*
SQL编写:
1.基于业务需求确定查询哪些表 base_category1,base_category2,base_category3
2.分析得到使用关联方式
3.使用关联字段
4.是否需要进行过滤条件
5.是否需要进行分组
6.是否需要进行排序
7.是否需要限制返回记录数
8.对SQL进行优化-建立索引
9.查看SQL的执行计划 结果中:type至少达到range级别
*/
select
bc3.id,
bc1.id category1_id,
bc1.name category1_name,
bc2.id category2_id,
bc2.name category2_name,
bc3.id category3_id,
bc3.name category3_name,
bc3.create_time,
bc3.update_time
from base_category1 bc1 left join base_category2 bc2 on bc2.category1_id = bc1.id
left join base_category3 bc3 on bc3.category2_id = bc2.id
where bc1.is_deleted = 0;
explain select
bc3.id,
bc1.id category1_id,
bc1.name category1_name,
bc2.id category2_id,
bc2.name category2_name,
bc3.id category3_id,
bc3.name category3_name,
bc3.create_time,
bc3.update_time
from base_category1 bc1 left join base_category2 bc2 on bc2.category1_id = bc1.id
left join base_category3 bc3 on bc3.category2_id = bc2.id
where bc1.is_deleted = 0;
create or replace view base_category_view as
select
bc3.id,
bc1.id category1_id,
bc1.name category1_name,
bc2.id category2_id,
bc2.name category2_name,
bc3.id category3_id,
bc3.name category3_name,
bc3.create_time,
bc3.update_time,
bc1.is_deleted
from base_category1 bc1 left join base_category2 bc2 on bc2.category1_id = bc1.id
left join base_category3 bc3 on bc3.category2_id = bc2.id
where bc1.is_deleted = 0;
涉及到的视图对象: base_category_view ,在这张视图中存储了所有的分类数据。展示分类数据的格式如下:
[
{
"categoryName":"音乐", #一级分类名称
"categoryId":1, #一级分类ID
"categoryChild":[ #当前一级分类包含的二级分类集合
{
"categoryName":"音乐音效", #二级分类名称
"categoryId":101, #二级分类ID
"categoryChild":[ #当前二级分类包含的三级分类集合
{
"categoryName": "催眠音乐",
"categoryId": 1001
},
{
"categoryName": "放松音乐",
"categoryId": 1002
},
{
"categoryName": "提神音乐",
"categoryId": 1003
}
]
}
]
},
{
"categoryName":"有声书",
"categoryId":2,
"categoryChild":[
{
"categoryName":"男频小说",
"categoryId":104,
"categoryChild":[
{
"categoryName":"军事小说",
"categoryId":1009
}
]
}
]
}
]
YAP接口地址:http://192.168.200.6:3000/project/11/interface/api/11
在service-album
模块中BaseCategoryApiController控制器编写
package com.atguigu.tingshu.album.api;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.service.BaseCategoryService;
import com.atguigu.tingshu.common.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Tag(name = "分类管理")
@RestController
@RequestMapping(value="/api/album")
@SuppressWarnings({"all"})
public class BaseCategoryApiController {
@Autowired
private BaseCategoryService baseCategoryService;
/**
* 查询所有分类(1、2、3级分类)
* @return
*/
@Operation(summary = "查询所有分类(1、2、3级分类)")
@GetMapping("/category/getBaseCategoryList")
public Result<List<JSONObject>> getBaseCategoryList(){
List<JSONObject> list = baseCategoryService.getBaseCategoryList();
return Result.ok(list);
}
}
接口与实现类
package com.atguigu.tingshu.album.service;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.model.album.BaseCategory1;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface BaseCategoryService extends IService<BaseCategory1> {
/**
* 查询所有分类(1、2、3级分类)
* @return
*/
List<JSONObject> getBaseCategoryList();
}
package com.atguigu.tingshu.album.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.mapper.BaseCategory1Mapper;
import com.atguigu.tingshu.album.mapper.BaseCategory2Mapper;
import com.atguigu.tingshu.album.mapper.BaseCategory3Mapper;
import com.atguigu.tingshu.album.mapper.BaseCategoryViewMapper;
import com.atguigu.tingshu.album.service.BaseCategoryService;
import com.atguigu.tingshu.model.album.BaseCategory1;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@SuppressWarnings({"all"})
public class BaseCategoryServiceImpl extends ServiceImpl<BaseCategory1Mapper, BaseCategory1> implements BaseCategoryService {
@Autowired
private BaseCategory1Mapper baseCategory1Mapper;
@Autowired
private BaseCategory2Mapper baseCategory2Mapper;
@Autowired
private BaseCategory3Mapper baseCategory3Mapper;
@Autowired
private BaseCategoryViewMapper baseCategoryViewMapper;
/**
* 查询所有分类(1、2、3级分类)
* TODO :每次查询都需要查询DB,将来将读多写少数据放入缓存Redis中
* @return
*/
@Override
public List<JSONObject> getBaseCategoryList() {
//1.获取分类视图中所有分类数据 共计419条记录
List<BaseCategoryView> list = baseCategoryViewMapper.selectList(null);
//2.处理一级分类
//2.1 构建一级分类JSON对象集合
List<JSONObject> jsonObjectList = new ArrayList<>();
//2.2 对所有分类集合进行分组:按照1分类ID进行分组
Map<Long, List<BaseCategoryView>> category1Map = list
.stream()
.collect(Collectors.groupingBy(BaseCategoryView::getCategory1Id));
//2.3 构建一级分类JOSN对象
for (Map.Entry<Long, List<BaseCategoryView>> entry1 : category1Map.entrySet()) {
JSONObject jsonObject1 = new JSONObject();
Long category1Id = entry1.getKey();
String category1Name = entry1.getValue().get(0).getCategory1Name();
//2.3.1 封装一级分类ID
jsonObject1.put("categoryId", category1Id);
//2.3.2 封装一级分类名称
jsonObject1.put("categoryName", category1Name);
//3.处理二级分类
//3.1 对1级分类集合进行分组:按照2级分类ID进行分组-得到二级分类Map
Map<Long, List<BaseCategoryView>> category2Map = entry1.getValue()
.stream()
.collect(Collectors.groupingBy(BaseCategoryView::getCategory2Id));
//3.2 创建二级分类JSON对象集合
List<JSONObject> jsonObject2List = new ArrayList<>();
//3.3 遍历二级分类Map 封装二级分类对象
for (Map.Entry<Long, List<BaseCategoryView>> entry2 : category2Map.entrySet()) {
//3.3.1 构建二级分类JSON对象
JSONObject jsonObject2 = new JSONObject();
//3.3.2 封装二级分类ID
Long category2Id = entry2.getKey();
jsonObject2.put("categoryId", category2Id);
//3.3.2 封装二级分类名称
String category2Name = entry2.getValue().get(0).getCategory2Name();
jsonObject2.put("categoryName", category2Name);
//3.4 将二级分类对象加入到二级分类JOSN集合中
jsonObject2List.add(jsonObject2);
//4.处理三级分类
//4.1 创建三级分类JSON对象集合
List<JSONObject> jsonObject3List = new ArrayList<>();
//4.2 遍历二级分类对象
for (BaseCategoryView baseCategoryView : entry2.getValue()) {
//4.2.1 构建三级分类JSON对象
JSONObject jsonObject3 = new JSONObject();
//4.2.2 封装三级分类ID
jsonObject3.put("categoryId", baseCategoryView.getCategory3Id());
//4.2.3 封装三级分类名称
jsonObject3.put("categoryName", baseCategoryView.getCategory3Name());
//4.3 将三级分类JSON对象放入三级分类JSON集合中
jsonObject3List.add(jsonObject3);
//4.2 将三级分类JSON集合放入二级分类对象中“categoryChild”属性中
jsonObject2.put("categoryChild", jsonObject3List);
}
//3.5 将二级分类JOSN集合加入到一级分类对象中“categoryChild”属性中
jsonObject1.put("categoryChild", jsonObject2List);
}
//2.4 将一级分类JOSN对象放入到一级分类JSON对象集合
jsonObjectList.add(jsonObject1);
}
//5.返回所有一级分类JSON对象集合
return jsonObjectList;
}
}
YAPI接口地址: http://192.168.200.6:3000/project/11/interface/api/15
在BaseCategoryApiController控制器中添加代码
/**
* 根据一级分类Id获取分类关联标签名,标签值 列表
* @param category1Id 一级分类ID
* @return
*/
@Operation(summary = "根据一级分类Id获取分类关联标签名,标签值 列表")
@GetMapping("/category/findAttribute/{category1Id}")
public Result<List<BaseAttribute>> getAttributesByCategory1Id(@PathVariable Long category1Id){
List<BaseAttribute> list = baseCategoryService.getAttributesByCategory1Id(category1Id);
return Result.ok(list);
}
BaseCategoryService
/**
* 根据一级分类Id获取分类关联标签名,标签值 列表
* @param category1Id 一级分类ID
* @return
*/
List<BaseAttribute> getAttributesByCategory1Id(Long category1Id);
BaseCategoryServiceImpl
@Autowired
private BaseAttributeMapper baseAttributeMapper;
/**
* 根据一级分类Id获取分类关联标签名,标签值 列表
* @param category1Id 一级分类ID
* @return
*/
@Override
public List<BaseAttribute> getAttributesByCategory1Id(Long category1Id) {
return baseAttributeMapper.getAttributesByCategory1Id(category1Id);
}
在BaseAttributeMapper中添加方法
package com.atguigu.tingshu.album.mapper;
import com.atguigu.tingshu.model.album.BaseAttribute;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface BaseAttributeMapper extends BaseMapper<BaseAttribute> {
/**
* 根据一级分类Id获取分类关联标签名,标签值 列表
* @param category1Id
* @return
*/
List<BaseAttribute> getAttributesByCategory1Id(@Param("category1Id") Long category1Id);
}
动态SQL:
#根据一级分类Id获取分类关联标签名,标签值 列表
select
ba.id,
ba.category1_id,
ba.attribute_name,
bav.id base_attribute_value_id,
bav.attribute_id,
bav.value_name
from base_attribute ba inner join base_attribute_value bav
on bav.attribute_id = ba.id
where ba.category1_id = 2 and ba.is_deleted = 0;
# 查询执行计划 ref级别
explain select
ba.id,
ba.category1_id,
ba.attribute_name,
bav.id base_attribute_value_id,
bav.attribute_id,
bav.value_name
from base_attribute ba inner join base_attribute_value bav
on bav.attribute_id = ba.id
where ba.category1_id = 2 and ba.is_deleted = 0;
在resources
目录下创建mapper目录并添加配置文件BaseAttributeMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.atguigu.tingshu.album.mapper.BaseAttributeMapper">
<!--自定义结果集resultMap 完成一对多封装-->
<resultMap id="baseAttributeValueMap" type="com.atguigu.tingshu.model.album.BaseAttribute" autoMapping="true">
<!--封装一方的列-->
<id column="id" property="id"></id>
<!--封装多方属性列表-->
<collection property="attributeValueList" ofType="com.atguigu.tingshu.model.album.BaseAttributeValue" autoMapping="true">
<id column="base_attribute_value_id" property="id"/>
</collection>
</resultMap>
<!--根据一级分类Id获取分类关联标签名(一方),标签值(多方) 列表-->
<select id="getAttributesByCategory1Id" resultMap="baseAttributeValueMap">
select
ba.id,
ba.category1_id,
ba.attribute_name,
bav.id base_attribute_value_id,
bav.attribute_id,
bav.value_name
from base_attribute ba inner join base_attribute_value bav
on bav.attribute_id = ba.id
where ba.category1_id = #{category1Id} and ba.is_deleted = 0
</select>
</mapper>
MinIO 是一个基于Apache License v3.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。
特点
高性能:作为高性能对象存储,在标准硬件条件下它能达到55GB/s的读、35GB/s的写速率
可扩容:不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并跨越多个数据中心
云原生:容器化、基于K8S的编排、多租户支持
Amazon S3兼容:Minio使用Amazon S3 v2 / v4 API。可以使用Minio SDK,Minio Client,AWS SDK和AWS CLI访问Minio服务器。
可对接后端存储: 除了Minio自己的文件系统,还支持DAS、 JBODs、NAS、Google云存储和Azure Blob存储。
SDK支持: 基于Minio轻量的特点,它得到类似Java、Python或Go等语言 的sdk支持
Lambda计算: Minio服务器通过其兼容AWS SNS / SQS的事件通知服务触发Lambda功能。支持的目标是消息队列,如Kafka,NATS,AMQP,MQTT,Webhooks以及Elasticsearch,Redis,Postgres和MySQL等数据库。
有操作页面
功能简单: 这一设计原则让MinIO不容易出错、更快启动
支持纠删码:MinIO使用纠删码、Checksum来防止硬件错误和静默数据污染。在最高冗余度配置下,即使丢失1/2的磁盘也能恢复数据!
存储机制
Minio使用纠删码erasure code和校验和checksum。 即便丢失一半数量(N/2)的硬盘,仍然可以恢复数据。纠删码是一种恢复丢失和损坏数据的数学算法。
docker pull minio/minio
docker run \ -p 9000:9000 \ -p 9001:9001 \ --name minio \ -d --restart=always \ -e "MINIO_ROOT_USER=admin" \ -e "MINIO_ROOT_PASSWORD=admin123456" \ -v /home/data:/data \ -v /home/config:/root/.minio \ minio/minio server /data --console-address ":9001"
浏览器访问:http://192.168.200.6:9001/minio/login,如图:
登录账户说明:安装时指定了登录账号:admin/admin123456
注意:文件上传时,需要调整一下linux 服务器的时间与windows 时间一致!
> 第一步:安装ntp服务 > yum -y install ntp > 第二步:开启开机启动服务 > systemctl enable ntpd > 第三步:启动服务 Tips:联网正常前提下如果定时同步失败,先停止服务,再启动 > systemctl stop ntpd > systemctl start ntpd > 第四步:更改时区 > timedatectl set-timezone Asia/Shanghai > 第五步:启用ntp同步 > timedatectl set-ntp yes > 第六步:同步时间 > ntpq -p > ``` ### 2.3.3 专辑图片上传 MinIO-JavaAPI:https://min.io/docs/minio/linux/developers/java/API.html Tomcat默认限制上传文件大小:1MB 通过修改配置更改:
yaml spring: servlet:
multipart: max-file-size: 10MB #单个文件最大限制 max-request-size: 20MB #多个文件最大限制
**业务需求**:在新增专辑前需要为专辑设置专辑封面,选中本机图片文件后将文件上传到MInIO,且返回上传后文件在线地址,方便用户进行预览。效果如下: ![image-20231001112153675](assets/image-20231001112153675.png) > YAPI文档地址:http://192.168.200.6:3000/project/11/interface/api/13
java package com.atguigu.tingshu.album.api;
import cn.hutool.core.util.IdUtil; import com.atguigu.tingshu.album.service.FileUploadService; import com.atguigu.tingshu.common.result.Result; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile;
@Tag(name = "上传管理接口") @RestController @RequestMapping("api/album") public class FileUploadApiController {
@Autowired
private FileUploadService fileUploadService;
/**
* 专辑封面,声音封面,主播头像进行文件图片上传
*
* @param file 文件对象 跟前段提交文件参数名称一致:file
* @return
*/
@Operation(summary = "文件(图片)上传接口")
@PostMapping("/fileUpload")
public Result<String> fileUpload(MultipartFile file) {
String fileUrl = fileUploadService.fileUpload(file);
return Result.ok(fileUrl);
}
}
**配置MinIO客户端对象**
java package com.atguigu.tingshu.album.config;
import io.minio.MinioClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
/**
@create: 2024-02-19 10:37 */ @Configuration public class MinIOConfig {
@Autowired private MinioConstantProperties prop;
/**
package com.atguigu.tingshu.album.service;
import org.springframework.web.multipart.MultipartFile;
public interface FileUploadService {
/**
* 专辑封面,声音封面,主播头像进行文件图片上传
*
* @return 上传后MinIO文件在线地址
*/
String fileUpload(MultipartFile file);
}
**FileUploadServiceImpl**
java package com.atguigu.tingshu.album.service.impl;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.file.FileNameUtil; import cn.hutool.core.util.IdUtil; import com.atguigu.tingshu.album.config.MinioConstantProperties; import com.atguigu.tingshu.album.service.FileUploadService; import com.atguigu.tingshu.common.execption.GuiguException; import io.minio.MinioClient; import io.minio.PutObjectArgs; import io.minio.errors.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException;
/**
@create: 2024-04-02 10:28 */ @Slf4j @Service public class FileUploadServiceImpl implements FileUploadService {
@Autowired private MinioConstantProperties properties;
@Autowired private MinioClient minioClient;
/**
@return 上传后MinIO文件在线地址 */ @Override public String fileUpload(MultipartFile file) { //1.业务校验-验证上传文件是否为图片,TODO 图片大小 try {
BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
if (bufferedImage == null) {
throw new GuiguException(400, "图片格式非法!");
}
//2.调用MinIO客户端对象上传文件方法上传到MinIO
//2.1 文件名称 形式 日期/UUID.后缀
String extName = FileNameUtil.extName(file.getOriginalFilename());
String objectName = "/" + DateUtil.today() + "/" + IdUtil.randomUUID() + "." + extName;
minioClient.putObject(
PutObjectArgs.builder().bucket(properties.getBucketName())
.object(objectName).stream(
file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build());
//3.拼接上传后文件在线地址
return properties.getEndpointUrl() + "/" + properties.getBucketName() + objectName;
} catch (Exception e) {
log.error("[专辑服务]文件上传接口异常:", e);
throw new RuntimeException("文件上传接口异常" + e);
} } }
## 2.4 保存专辑
设为私密:表示不发布的意思,后续可以通过这个按钮选项实现专辑的上架-下架操作
涉及的表:
- **album_info** 专辑表
- 初始化userId 默认值1 为了后续能查到数据
- 并设置初始化状态为审核通过
- 如果是付费专辑则设置前五集为免费试看
- **album_attribute_value** 专辑属性值表
- 设置专辑Id
- **album_stat** 专辑统计表
- 初始化统计数目为0
### 2.4.1 控制层
> YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/17
**Tips**:新增的专辑需要将专辑关联到主播用户,但由于还未完成登录功能,故在获取用户工具类`AuthContextHolder`中**getUserId方法中**将获取用户ID的返回值写为固定。
![image-20231002093707199](assets/image-20231002093707199.png)
在`service-album`模块中**AlbumInfoApiController**
java
package com.atguigu.tingshu.album.api;
import com.atguigu.tingshu.album.service.AlbumInfoService; import com.atguigu.tingshu.common.result.Result; import com.atguigu.tingshu.common.util.AuthContextHolder; import com.atguigu.tingshu.model.album.AlbumInfo; import com.atguigu.tingshu.vo.album.AlbumInfoVo; import com.tencentcloudapi.cat.v20180409.models.AlarmInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
@Tag(name = "专辑管理") @RestController @RequestMapping("api/album") @SuppressWarnings({"all"}) public class AlbumInfoApiController {
@Autowired private AlbumInfoService albumInfoService;
/**
* TODO:该接口必须要求用户登录后才能调用,获取用户ID关联到专辑 暂时将当前用户ID固定为:1
* 保存专辑
*
* @param albumInfoVo
* @return
*/
@Operation(summary = "保存专辑")
@PostMapping("/albumInfo/saveAlbumInfo")
public Result saveAlbumInfo(@RequestBody AlbumInfoVo albumInfoVo) {
//1.获取当前登录用户ID
Long userId = AuthContextHolder.getUserId();
//2.调用业务逻辑保存专辑
albumInfoService.saveAlbumInfo(userId, albumInfoVo);
return Result.ok();
}
}
### 2.4.2 业务层
**AlbumInfoService接口**
java package com.atguigu.tingshu.album.service;
import com.atguigu.tingshu.model.album.AlbumInfo; import com.atguigu.tingshu.vo.album.AlbumInfoVo; import com.baomidou.mybatisplus.extension.service.IService;
public interface AlbumInfoService extends IService {
/**
* 保存专辑
* @param userId 用户ID
* @param albumInfoVo 专辑信息
*/
void saveAlbumInfo(Long userId, AlbumInfoVo albumInfoVo);
/**
* 保存专辑统计信息
* @param albumId 专辑ID
* @param statType 统计类型 0401-播放量 0402-订阅量 0403-购买量 0403-评论数
* @param num 数值
*/
void saveAlbumStat(Long albumId, String statType, Integer num);
}
**AlbumInfoServiceImpl实现类**
java package com.atguigu.tingshu.album.service.impl;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollectionUtil; import com.atguigu.tingshu.album.mapper.AlbumAttributeValueMapper; import com.atguigu.tingshu.album.mapper.AlbumInfoMapper; import com.atguigu.tingshu.album.mapper.AlbumStatMapper; import com.atguigu.tingshu.album.service.AlbumInfoService; import com.atguigu.tingshu.common.constant.SystemConstant; import com.atguigu.tingshu.model.album.AlbumAttributeValue; import com.atguigu.tingshu.model.album.AlbumInfo; import com.atguigu.tingshu.model.album.AlbumStat; import com.atguigu.tingshu.model.search.AlbumInfoIndex; import com.atguigu.tingshu.vo.album.AlbumAttributeValueVo; import com.atguigu.tingshu.vo.album.AlbumInfoVo; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Slf4j @Service @SuppressWarnings({"all"}) public class AlbumInfoServiceImpl extends ServiceImpl implements AlbumInfoService {
@Autowired
private AlbumInfoMapper albumInfoMapper;
@Autowired
private AlbumAttributeValueMapper albumAttributeValueMapper;
@Autowired
private AlbumStatMapper albumStatMapper;
/**
* 保存专辑
* 1.将提交专辑信息封装专辑对象AlbumInfo对象,向专辑表中增加一条记录
* 2.将提交专辑标签封装专辑标签集合对象,向专辑标签关系表中增加若干条记录
* 3.固定为专辑保存四条统计信息
*
* @param userId 用户ID
* @param albumInfoVo 专辑信息
*/
@Override
@Transactional(rollbackFor = Exception.class) //默认事务注解当发生RuntimeException或者ERROR会进行事务回滚
public void saveAlbumInfo(Long userId, AlbumInfoVo albumInfoVo) {
//1.将提交专辑信息封装专辑对象AlbumInfo对象,向专辑表中增加一条记录
//1.1 将提交专辑VO转为专辑PO对象
AlbumInfo albumInfo = BeanUtil.copyProperties(albumInfoVo, AlbumInfo.class);
//1.2 为专辑中属性赋值 用户ID,包含声音数量、免费试听集数、状态等
albumInfo.setUserId(userId);
albumInfo.setIncludeTrackCount(0);
albumInfo.setTracksForFree(5);
albumInfo.setStatus(SystemConstant.ALBUM_STATUS_PASS);
//1.3 保存专辑 得到专辑ID
albumInfoMapper.insert(albumInfo);
Long albumId = albumInfo.getId();
//2.将提交专辑标签封装专辑标签集合对象,向专辑标签关系表中增加若干条记录
//2.1 获取专辑标签VO集合 转为专辑标签PO集合
List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
//2.2 专辑标签关联专辑ID,保存
albumAttributeValueVoList
.stream()
.forEach(albumAttributeValueVo -> {
//将VO转为PO
AlbumAttributeValue albumAttributeValue = BeanUtil.copyProperties(albumAttributeValueVo, AlbumAttributeValue.class);
albumAttributeValue.setAlbumId(albumId);
//保存专辑标签
albumAttributeValueMapper.insert(albumAttributeValue);
});
}
//3.固定为专辑保存四条统计信息
this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_PLAY, 0);
this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_SUBSCRIBE, 0);
this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_BUY, 0);
this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_COMMENT, 0);
}
/**
* 保存专辑统计信息
*
* @param albumId 专辑ID
* @param statType 统计类型 0401-播放量 0402-订阅量 0403-购买量 0403-评论数
* @param num 数值
*/
@Override
public void saveAlbumStat(Long albumId, String statType, Integer num) {
AlbumStat albumStat = new AlbumStat();
albumStat.setAlbumId(albumId);
albumStat.setStatType(statType);
albumStat.setStatNum(num);
albumStatMapper.insert(albumStat);
}
}
# 3、查看专辑列表
需求:创作者中心-->查询**当前登录用户**发布的专辑列表,每个专辑包含:专辑ID,专辑封面图片、专辑名称、专辑包含声音个数、创建时间、**播放量、购买量、订阅数、评论数量**等 ;根据专辑审核状态及任意关键字进行模糊查询。
![](assets/image-20231114083625117.png)
> YAPI接口文档:http://192.168.200.6:3000/project/11/interface/api/19
## 3.1 控制层
**AlbumInfoApiController控制器**
查询数据的时候,我们将页面渲染的数据封装到一个实体类中AlbumListVo,只需要返回这个类的集合即可!
java /**
@return */ @Operation(summary = "查询当前登录用户发布专辑分页列表") @PostMapping("/albumInfo/findUserAlbumPage/{page}/{limit}") public Result> getUserAlbumPage(
@PathVariable int page,
@PathVariable int limit,
@RequestBody AlbumInfoQuery albumInfoQuery
) { //1.通过工具类从ThreadLocal中获取用户ID 暂固定:1 Long userId = AuthContextHolder.getUserId(); albumInfoQuery.setUserId(userId); //2.调用业务逻辑进行查询分页 //2.1 创建分页对象交给持久层分页使用:封装页码,页大小 Page pageInfo = new Page<>(page, limit); //2.2 调用业务层->持久层 封装:总记录,总页数,当前页数据 pageInfo = albumInfoService.getUserAlbumPage(albumInfoQuery, pageInfo); return Result.ok(pageInfo); }
## 3.2 业务层
**AlbumInfoService接口**
java
/**
@return */ Page getUserAlbumPage(AlbumInfoQuery albumInfoQuery, Page pageInfo);
**AlbumInfoServiceImpl实现类**
java
/**
@return */ @Override public Page getUserAlbumPage(AlbumInfoQuery albumInfoQuery, Page pageInfo) { return albumInfoMapper.getUserAlbumPage(pageInfo, albumInfoQuery); }
## 3.3 持久层
SQL演练:
sql
#1. 进行关联查询 select * from album_info ai inner join album_stat stat on stat.album_id = ai.id where user_id = 1 and ai.is_deleted = 0
#2. 指定查询列 select
ai.id,
ai.album_title,
ai.cover_url,
ai.include_track_count,
stat.stat_type,
stat.stat_num
from album_info ai inner join album_stat stat on stat.album_id = ai.id where user_id = 1 and ai.is_deleted = 0
#3. 将专辑四项统计信息封装到一个专辑对象中四个属性 核心诉求:将统计四条行记录转为列 select
ai.id,
ai.album_title,
ai.cover_url,
ai.include_track_count,
stat.stat_type,
stat.stat_num
from album_info ai inner join album_stat stat on stat.album_id = ai.id where user_id = 1 and ai.is_deleted = 0;
#4. 对专辑(统计)四条记录进行分组:按照转计划ID分组 MySQL默认严格模式,违背only_full_group_by:select只能出现groupby中分组字段或者聚合函数 select
ai.id,
ai.album_title,
ai.cover_url,
ai.include_track_count,
stat.stat_type,
stat.stat_num
from album_info ai inner join album_stat stat on stat.album_id = ai.id where user_id = 1 and ai.is_deleted = 0 group by ai.id;
#5. 使用聚合函数+判断 select if(1=2, 'a', 'b');
select
ai.id,
ai.album_title,
ai.cover_url,
ai.include_track_count,
max(if(stat.stat_type='0401', stat.stat_num, 0)) playStatNum,
max(if(stat.stat_type='0402', stat.stat_num, 0)) subscribeStatNum,
max(if(stat.stat_type='0403', stat.stat_num, 0)) buyStatNum,
max(if(stat.stat_type='0404', stat.stat_num, 0)) commentStatNum
from album_info ai inner join album_stat stat on stat.album_id = ai.id where user_id = 1 and ai.is_deleted = 0 group by ai.id;
#6. 加入过滤条件:审核状态,关键字模糊查询 select
ai.id albumId,
ai.album_title,
ai.cover_url,
ai.include_track_count,
max(if(stat.stat_type='0401', stat.stat_num, 0)) playStatNum,
max(if(stat.stat_type='0402', stat.stat_num, 0)) subscribeStatNum,
max(if(stat.stat_type='0403', stat.stat_num, 0)) buyStatNum,
max(if(stat.stat_type='0404', stat.stat_num, 0)) commentStatNum
from album_info ai inner join album_stat stat on stat.album_id = ai.id where user_id = ? and ai.status = ? and ai.album_title like '%经典%' and ai.is_deleted = 0 group by ai.id order by ai.id desc
**AlbumInfoMapper **
java package com.atguigu.tingshu.album.mapper;
import com.atguigu.tingshu.model.album.AlbumInfo; import com.atguigu.tingshu.query.album.AlbumInfoQuery; import com.atguigu.tingshu.vo.album.AlbumListVo; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param;
@Mapper public interface AlbumInfoMapper extends BaseMapper {
/**
* 分页查询专辑列表列表(包含统计信息)
* @param pageInfo MP会自动进行分页 动态SQL拼接 limit ?,?
* @param albumInfoQuery 查询条件
* @return
*/
Page<AlbumListVo> getUserAlbumPage(Page<AlbumListVo> pageInfo, @Param("vo") AlbumInfoQuery albumInfoQuery);
}
**AlbumInfoMapper.xml 实现**
xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!--分页查询专辑列表列表(包含统计信息)-->
<select id="getUserAlbumPage" resultType="com.atguigu.tingshu.vo.album.AlbumListVo">
select
ai.id albumId,
ai.album_title,
ai.cover_url,
ai.include_track_count,
max(if(stat.stat_type='0401', stat.stat_num, 0)) playStatNum,
max(if(stat.stat_type='0402', stat.stat_num, 0)) subscribeStatNum,
max(if(stat.stat_type='0403', stat.stat_num, 0)) buyStatNum,
max(if(stat.stat_type='0404', stat.stat_num, 0)) commentStatNum
from album_info ai inner join album_stat stat on stat.album_id = ai.id
<where>
<if test="vo.userId != null">
ai.user_id = #{vo.userId}
</if>
<if test="vo.status != null and vo.status != ''">
and ai.status = #{vo.status}
</if>
<if test="vo.albumTitle != null and vo.albumTitle != ''">
and ai.album_title like concat('%', #{vo.albumTitle}, '%')
</if>
and ai.is_deleted = 0
</where>
group by ai.id
order by ai.id desc
</select>
# 4、删除专辑
在本项目中所有删除都采用逻辑删除,利用MybatisPlus提供逻辑删除。在表中提供逻辑删除字段:is_deleted
在Java实体类中映射逻辑删除字段,属性使用@TableLogic,调用MP提供持久层或者业务层删除方法时候,自动实现逻辑删除(修改操作)
> YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/21
## 4.1 控制层
**AlbumInfoApiController控制器**
java /**
@return */ @Operation(summary = "删除专辑") @DeleteMapping("/albumInfo/removeAlbumInfo/{id}") public Result removeAlbumInfo(@PathVariable Long id){ albumInfoService.removeAlbumInfo(id); return Result.ok(); }
## 4.2 业务层
**AlbumInfoService接口**
java
/**
@Autowired private TrackInfoMapper trackInfoMapper;
/**
@return */ @Override @Transactional(rollbackFor = Exception.class) public void removeAlbumInfo(Long id) { //1.判断该专辑下是否包含声音如果包含则拒绝删除 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(TrackInfo::getAlbumId, id); Long count = trackInfoMapper.selectCount(queryWrapper); if (count > 0) {
throw new GuiguException(400, "该专辑下仍然关联" + count + "个声音");
} //2.根据主键删除专辑表(逻辑删除) albumInfoMapper.deleteById(id);
//3.根据专辑ID删除专辑标签记录(逻辑删除) LambdaQueryWrapper attributeValueLambdaQueryWrapper = new LambdaQueryWrapper<>(); attributeValueLambdaQueryWrapper.eq(AlbumAttributeValue::getAlbumId, id); albumAttributeValueMapper.delete(attributeValueLambdaQueryWrapper);
//4.根据专辑ID删除专辑统计记录(逻辑删除) LambdaQueryWrapper albumStatLambdaQueryWrapper = new LambdaQueryWrapper<>(); albumStatLambdaQueryWrapper.eq(AlbumStat::getAlbumId, id); albumStatMapper.delete(albumStatLambdaQueryWrapper); }
# 5、专辑修改
## 5.1 回显数据
1. 需要根据专辑id获取到对应的回显数据,需要回显专辑与属性数据,不需要回显统计数据!
2. 根据修改内容保存最新数据
> YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/23
**AlbumInfoApiController控制器**
java
/**
@return */ @Operation(summary = "根据专辑ID查询专辑信息(包含专辑标签列表)") @GetMapping("/albumInfo/getAlbumInfo/{id}") public Result getAlbumInfo(@PathVariable Long id) { AlbumInfo albumInfo = albumInfoService.getAlbumInfo(id); return Result.ok(albumInfo); }
**AlbumInfoService接口**
java
/**
@return */ AlbumInfo getAlbumInfo(Long id);
**AlbumInfoServiceImpl实现类**
java
/**
@return */ @Override public AlbumInfo getAlbumInfo(Long id) { //1.根据专辑主键ID查询专辑信息 AlbumInfo albumInfo = albumInfoMapper.selectById(id); Assert.notNull(albumInfo, "专辑信息:{}不存在", id);
//2.根据专辑ID查询专辑标签列表,将标签列表封装到专辑对象中 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(AlbumAttributeValue::getAlbumId, id); List albumAttributeValueList = albumAttributeValueMapper.selectList(queryWrapper); if (CollectionUtil.isNotEmpty(albumAttributeValueList)) {
albumInfo.setAlbumAttributeValueVoList(albumAttributeValueList);
} return albumInfo; }
## 5.2 保存修改后数据
涉及的表:
- album_info 根据主键进行更新
- album_attribute_value 先删除所有数据,再新增数据
> YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/25
**AlbumInfoApiController控制器**
java
/**
@return */ @Operation(summary = "更新专辑信息") @PutMapping("/albumInfo/updateAlbumInfo/{id}") public Result updateAlbumInfo(@PathVariable Long id, @RequestBody AlbumInfo albumInfo){ albumInfo.setId(id); albumInfoService.updateAlbumInfo(albumInfo); return Result.ok(); }
**AlbumInfoService接口**
java
/**
@param albumInfo */ void updateAlbumInfo(AlbumInfo albumInfo);
**AlbumInfoServiceImpl实现类**
java
/**
@param albumInfo */ @Override @Transactional(rollbackFor = Exception.class) public void updateAlbumInfo(AlbumInfo albumInfo) { //1.修改专辑表信息 this.updateById(albumInfo);
//2.修改专辑标签关系记录 //2.1 根据专辑ID删除“旧”的专辑标签记录 LambdaQueryWrapper albumAttributeValueLambdaQueryWrapper = new LambdaQueryWrapper<>(); albumAttributeValueLambdaQueryWrapper.eq(AlbumAttributeValue::getAlbumId, albumInfo.getId()); albumAttributeValueMapper.delete(albumAttributeValueLambdaQueryWrapper);
//2.2 新增专辑标签关系记录 List albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList(); if(CollectionUtil.isNotEmpty(albumAttributeValueVoList)){
//2.1 将集合类型AlbumAttributeValueVo转为AlbumAttributeValue 关联专辑ID
for (AlbumAttributeValue albumAttributeValue : albumAttributeValueVoList) {
albumAttributeValue.setAlbumId(albumInfo.getId());
//2.2 保存专辑标签关系
albumAttributeValueMapper.insert(albumAttributeValue);
}
} } ```