今日任务
课程的CRUD
关于数据库的存储
有两种方案
方案一:
像课程详情 课程图片 都是数据课程的信息 可知直接放入t_course 就ok
方案二:
有时候我们只需要查询到课程的基本信息 不需要显示图片和详情 这个时候我们就会用到垂直分表
垂直分表:一些字段我们一般不需要直接查询 就把这些字段单独放一个表 通过外键关联 正常情况下 不需要查询关联表 如果需要 就通过外键查询就ok 这样能大大提高数据库的效率
生成代码
生成后测试是否已经加入
然后从我们的资料中拷贝Course.vue界面
在这之前 我们需要集成一个支持Vue的富文本编辑器
直接在终端运行
npm install quill --save
npm install --save vue-quill-editor
然后把list一系列的修改了 和之前一样
然后启动前后端服务
然后可能会报一个这样的错
别担心 这是因为你的redis没有启动起来 将redis启动就行了
这与页面就能出来了
接下来我们来做新增操作
页面都已经写好 我们只需要拷贝下来用就是了 因为我不是专业的前端 所以我们就直接用别人写好的就行了
后台:
我们需要修改的地方是 覆写service中的插入和修改方法 因为我们这个是有关联对象的 所以自带的方法已经满足不了我们了
@Override
public boolean insert(Course entity) {
//要添加三张表 课程表 详情表 市场信心表
//tenantId tenantName userId userName
System.out.println(entity);
entity.setStatus(0);
courseMapper.insert(entity);
Long courseId = entity.getId();
//同时保存详情和市场信心
CourseDetail courseDetail = entity.getCourseDetail();
courseDetail.setCourseId(courseId);
courseDetailMapper.insert(courseDetail);
CourseMarket courseMarket = entity.getCourseMarket();
courseMarket.setCourseId(courseId);
courseMarketMapper.insert(courseMarket);
return true;
}
对于Controller 在我么登录之后 需要传递一些信息进去 但是我们现在还用不到 所以就先注释掉
@PutMapping
public AjaxResult addOrUpdate(@RequestBody Course course){
try {
// @TODO 以后登录成功都能获取,现在使用holder来模拟
//登录成功后设置到UserInfoHolder,以后所有模块要使用都直接使用UserInfoHolder
// course.setTenantId(UserInfoHolder.getTenant().getId());
// course.setTenantName(UserInfoHolder.getTenant().getCompanyName());
// course.setUserId(UserInfoHolder.getLoginUser().getId());
// course.setUserName(UserInfoHolder.getLoginUser().getUsername());
if(course.getId()!=null){
courseService.updateById(course);
}else{
courseService.insert(course);
}
return AjaxResult.me();
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setMessage("保存对象失败!"+e.getMessage());
}
}
修改和删除我就不在这展示了 和之前的都大同小异
课程的上线和下线
业务说明
上线:
现实生活中:
当培训机构研发出新的课程后,准备招生时,要先把它添加到数据中。但是期望暂时不需要用户能够搜索到。所以需要上线才能操作
系统中:
在系统中,我们添加了一个课程,用户不立即解就搜索到,需要上线以后才行.
下线:
当某个课程不想卖的时候,就要下线. 当课程下线后,用户不能搜索到,但是数据库是还有的.
方案-用es查询代替数据库查询
上线后,修改状态为”上线”,前台用户搜索时只能搜索到上线状态的.如果不想卖了,执行下线时,修改状态下线就ok.----------垃圾(每次都要操作数据库.)
如果有1000W的并发来查询课程,要高并发访问数据库,效率低下
上线时把课程数据同步到es,用户查询直接从es查询.也就意味着没有上线的课程用户查询不到,因为没有放到es库.
下线时把es库课程数据删除掉. 用户就查询不到了. —NB(以基于索引搜索代替数据库查询)
好处:
降低数据库压力
提高了查询速度,增强用户体验-基于索引搜索,效率远远数据库搜索
ES java操作
准备es环境
Es服务端
方案选择
- 原生ESTransport Client方式 —> 就相当于写jdbc代码,非常麻烦.
- Springboot spring data es
spring data
是spring对数据访问抽象.这些数据可以放入db,index,nosql等包含以下:
spring data jpa spring对关系型数据库访问支持
spring data ES spring对es数据访问支持
spring data redis等 spring对redis数据访问支持
spring data …
Springboot spring data es —>对 spring data es简化了配置
安装和使用
如果你是第一次安装elasticsearch 需要修改一下配置
将
这里面的所需的运存改为1g 当然 你电脑如果运存的大的话也可以不换
然后是启动 双击这个文件夹就行
直接就启动了
他有两个端口 一个是9200 一个是9300 9300是代码访问的客户端 9200是http的
然后再浏览器中访问
http://localhost:9200/
如果能看到这个界面 就说明是成功了
接下来我们使用kibana来访问她 首先要启动kibana的服务
首先修改一下配置 指定端口
然后启动(尽量使用管理员权限启动)
启动成功后
我们在浏览器中访问
http://localhost:5601/
我们用的最多的是
其他的喜欢研究的可以自己去研究一下
课程上下线实现
技术架构
管理员:(千级)
添加:直接操作数据库
删除和修改: 同步操作方案
分页查询:直接操作数据.并发量小.
上线:同步信息到ES库.
下线:删除ES库数据
用户:(千万级)
搜索课程:直接从ES
实现
写的服务不具备通用性,没必要单独搞一个服务
关于数据库的设计
需要值得注意的是 :这里我们的数据库采用的是反3NF的设计
准备环境 -在自己模块
导包
<!--springboot 对spring data es支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
配置文件
spring:
data:
elasticsearch:
cluster-name: elasticsearch
cluster-nodes: 127.0.0.1:9300 #9200是图形界面端,9300代码端
入口类
@SpringBootApplication
public class ESApplication {
public static void main(String[] args) {
SpringApplication.run(ESApplication.class);
}
}
EsCourse.java
//包含了前台展示的字段和要添加到索引库都要写到这儿
@Document(indexName = "hrm",type = "course")
public class EsCourse {
@Id
private Long id;
private String name;
private String users;
private Long courseTypeId;
private String courseTypeName;
private Long gradeId;
private String gradeName;
private Integer status;
private Long tenantId;
private String tenantName;
private Long userId;
private String userName;
private Date startTime;
private Date endTime;
private String intro;
private String resources; //图片
private Date expires; //过期时间
private BigDecimal priceOld; //原价
private BigDecimal price; //原价
private String qq; //原价
@Field(type = FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
private String all;
//getter和setter
//toString
public String getAll() {
String tmp = name
+" "+ users
+" "+ courseTypeName
+" "+ gradeName
+" "+ tenantName
+" "+ userName
+" "+ intro;
return tmp;
}
EsCourseRepository.java
import org.leryoo.index.doc.EsCourse;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
//在service通过注入它就可以完成索引库操作了
public interface EsCourseRepository extends ElasticsearchRepository<EsCourse,Long> {
}
接下来什么地方要用到就直接调用就行了 和操作数据库的方法都差不多
Service层 业务逻辑
上线
@Override
public AjaxResult onLine(Long[] ids) {
try
{
//1添加索引库
// 通过id查询数据库里面课程
List<Course> courses = courseMapper
.selectBatchIds(Arrays.asList(ids));
//转换为doc
List<EsCourse> esCourses = courses2EsCourses(courses);
//批量添加就ok
repository.saveAll(esCourses);
//2 修改数据库状态和上线时间 - ids,time
Map<String,Object> params = new HashMap<>();
params.put("ids",ids);
params.put("onLineTime",new Date());
// 修改状态和上线时间
//update t_couse set status=1 and start_time={} where id in (1,2,3,4)
courseMapper.onLine(params);
return AjaxResult.me();
}catch (Exception e){
e.printStackTrace();
return AjaxResult.me().setSuccess(false).setMessage("上线失败!"+e.getMessage());
}
}
private List<EsCourse> courses2EsCourses(List<Course> courses) {
//1 声明要返回
List<EsCourse> result = new ArrayList<>();
//2 转换
for (Course course : courses) {
result.add(course2EsCourse(course));
}
//3返回
return result;
}
//把一个course转换为EsCourse
private EsCourse course2EsCourse(Course course) {
//1 声明要返回
EsCourse result = new EsCourse();
// 2 转换
result.setId(course.getId());
result.setName(course.getName());
result.setUsers(course.getUsers());
result.setCourseTypeId(course.getCourseTypeId());
//type-同库-没有做关联查询
if (course.getCourseType() != null)
result.setCourseTypeName(course.getCourseType().getName());
result.setGradeId(course.getGrade());
result.setGradeName(course.getGradeName());
result.setStatus(course.getStatus());
result.setTenantId(course.getTenantId());
result.setTenantName(course.getTenantName());
result.setUserId(course.getUserId());
result.setUserName(course.getUserName());
result.setStartTime(course.getStartTime());
result.setEndTime(course.getEndTime());
//Detail
result.setIntro(null);
//resource
result.setResources(null);
//market
result.setExpires(null);
result.setPrice(null);
result.setPriceOld(null);
result.setQq(null);
// 3返回
return result;
}
下线:
@Override
public AjaxResult offLine(Long[] ids) {
try
{
//1 删除索引库里面的内容
// List<Course> courses = courseMapper
// .selectBatchIds(Arrays.asList(ids));
//转换为doc
// List<EsCourse> esCourses = courses2EsCourses(courses);
// repository.deleteAll(esCourses);
for (Long id : ids) {
repository.deleteById(id);
}
//2 修改数据库状态 status=0 end_time
Map<String,Object> params = new HashMap<>();
params.put("ids",ids);
params.put("offLineTime",new Date());
// 修改状态和下线时间
//update t_couse set status=0 and start_time={} where id in (1,2,3,4)
courseMapper.offLine(params);
return AjaxResult.me();
}catch (Exception e){
e.printStackTrace();
return AjaxResult.me().setSuccess(false).setMessage("下线失败!"+e.getMessage());
}
}
SQL语句
<!-- 上线-->
<!-- void onLine(Map<String, Object> params);-->
<update id="onLine" parameterType="map">
update t_course set status=1,start_time=#{onLineTime} where id in
<foreach collection="ids" separator="," open="(" close=")" item="id">
#{id}
</foreach>
</update>
<!-- void offLine(Map<String, Object> params);-->
<update id="offLine" parameterType="map">
update t_course set status=0 , end_time=#{offLineTime} where id in
<foreach collection="ids" separator="," open="(" close=")" item="id">
#{id}
</foreach>
</update>
注意前端传参
和封装参数
和选中行时往sels里面传值
需要注意的是 传值的时候 Controller层需要加上
然后编写SQL语句的时候
这里直接写参数
自此 课程模块就已经写完了
今天的内容就到这啦