[完结11章]技术大牛成长课,从0到1带你手写一个数据库系统 参考地址1:pan.baidu.com/s/1N-x48vz9… 提取码: nxts 参考地址2:share.weiyun.com/Tp6ewDIJ 密码:6crcwd
关于数据库系统的开发一直以来都是一个难点,它的流程复杂,涉及到的技术点众多,特别在部署这块尤为重要,今天就带着大家手把手去实现这样一个数据库系统项目。
我将从理论结合实际场景综合性落地,让大家轻松吃透核心技术底层原理。 首先是应用场景这块:复用到日常开发场景中,如何运用高级数据结构、算法和设计模式,如何正确面对高并发进行编程,如何进行数据库的优化,如何理解数据库的执行计划分析慢SQL的原因等; 其次是原理剖析: 深度剖析数据库系统原理,将数据库几十年发展精髓拆解并呈现,端到端解析数据库系统中的各种工程trick,结合具体实现案例(MySQL/PostgreSQL/SQLite)展现系统级实现方案
到最后的源码实战: 手把手实现每一行代码,掌握每行代码的原理,实现代码规模巨大的数据库系统原型,开发、debug过程演示真实传授解bug的核心方法论,探讨各种工程技巧、可优化的空间,引发深层思考
下面就开始我们的源码项目:
我们首先看一下模型层定义的变量,除了有查询条件之外,还有与分页相关的变量,以及保存勾选的会议室记录的变量,最后是表单验证的规则。总体上来看,跟用户管理页面的模型层差不多。 data: function() { return { dataForm: { name: null, canDelete: null }, dataList: [], pageIndex: 1, pageSize: 10, totalCount: 0, dataListLoading: false, dataListSelections: [], addOrUpdateVisible: false, dataRule: { name: [{ required: false, pattern: '^[a-zA-Z0-9\u4e00-\u9fa5]{1,20}$', message: '会议室名称格式错误' }] } }; },
接下来我们看看前端页面的表单控件是怎么定义的。当用户点击查询按钮的时候,触发点击事件对应的回调函数是searchHandle(),这个函数是我们一会儿要声明的。 <el-button size="medium" type="primary" @click="searchHandle()">查询 <el-button size="medium" type="primary" :disabled="!isAuth(['ROOT', 'MEETING_ROOM:INSERT'])" @click="addHandle()" > 新增 <el-button size="medium" type="danger" :disabled="!isAuth(['ROOT', 'MEETING_ROOM:DELETE'])" @click="deleteHandle()" > 批量删除 打开amect.vue文件,还是老规矩,看视图层标签之前,咱们先来看看模型层都定义了哪些变量。 data: function() { return { dataForm: { name: null, deptId: null, typeId: null, status: null, date: null }, deptList: [], amectTypeList: [], dataList: [], pageIndex: 1, pageSize: 10, totalCount: 0, dataListLoading: false, dataListSelections: [], dataRule: { name: [{ required: false, pattern: '^[\u4e00-\u9fa5]{1,10}$', message: '姓名格式错误' }] }, addOrUpdateVisible: false, payVisible: false }; }, 视图层里面的查询条件较多,部门列表、违纪类型列表的数据,都是要通过Ajax查询出来的,所以建议大家可以看看loadDeptList()和loadAmectTypeList()函数。 <el-button size="medium" type="primary" @click="searchHandle()">查询 <el-button size="medium" type="primary" :disabled="!isAuth(['ROOT', 'AMECT:INSERT'])" @click="addHandle()" > 新增 <el-button size="medium" type="danger" :disabled="!isAuth(['ROOT', 'AMECT:DELETE'])" @click="deleteHandle()" > 批量删除 <el-button size="medium" type="warning" :disabled="!isAuth(['ROOT', 'AMECT:SELECT'])" @click="reportHandle()" > 查看报告
页面表格内容也不复杂,而且折叠面板的内容,我们直接把tb_amect数据表中的reason字段值写上去就可以了,不需要展开的时候发送Ajax请求。 <el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle" cell-style="padding: 4px 0" style="width: 100%;" size="medium"
<el-table-column
type="selection"
:selectable="selectable"
header-align="center"
align="center"
width="50"
/>
<el-table-column width="40px" prop="reason" header-align="center" align="center" type="expand">
<template #default="scope">
罚款原因:{{ scope.row.reason }}
</template>
</el-table-column>
<el-table-column type="index" header-align="center" align="center" width="100" label="序号">
<template #default="scope">
<span>{{ (pageIndex - 1) * pageSize + scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column prop="type" header-align="center" align="center" label="罚款类型" />
<el-table-column prop="name" header-align="center" align="center" label="当事人" />
<el-table-column prop="deptName" header-align="center" align="center" label="所属部门" />
<el-table-column header-align="center" align="center" label="罚款金额">
<template #default="scope">
<span>{{ scope.row.amount }}元</span>
</template>
</el-table-column>
<el-table-column prop="status" header-align="center" align="center" label="状态" />
<el-table-column prop="createTime" header-align="center" align="center" label="日期时间" />
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
<template #default="scope">
<el-button
type="text"
size="medium"
:disabled="!(isAuth(['ROOT', 'AMECT:UPDATE']) && scope.row.status != '已缴纳')"
@click="updateHandle(scope.row.id)"
>
修改
</el-button>
<el-button
type="text"
size="medium"
:disabled="!(isAuth(['ROOT', 'AMECT:DELETE']) && scope.row.status != '已缴纳')"
@click="deleteHandle(scope.row.id)"
>
删除
</el-button>
<el-button
type="text"
size="medium"
:disabled="!(scope.row.mine == 'true' && scope.row.status == '未缴纳')"
@click="payHandle(scope.row.id)"
>
交款
</el-button>
</template>
</el-table-column>
向客户端发送消息,需要使用Session对象。但是这些生命周期函数都由于客户端某种操作,而触发执行的。如果客户端不触发操作,那么后端是无法主动给客户端发送消息的。所以我们要把Session对象缓存起来。需要的时候,我们提取缓存的Session,主动向客户端发送消息。
因为后端的WebSocket服务类是多例的,所以我们想要全局共享缓存,要么用Redis,要么声明静态的HashMap对象。如果选用Redis,那么保存Session对象要用到序列化,会消耗一定的时间,所以不建议使用。如果全局共享使用HashMap,又会存在并发读写的问题,最终我们选择ConcurrentHashMap类 @Slf4j @ServerEndpoint(value = "/socket") @Component public class WebSocketService {
//用于保存WebSocket连接对象
public static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session) {
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(Session session) {
Map map = session.getUserProperties();
if (map.containsKey("userId")) {
String userId = MapUtil.getStr(map, "userId");
sessionMap.remove(userId);
}
}
/**
* 接收消息
*
* @param message
* @param session
*/
@OnMessage
public void onMessage(String message, Session session) {
//把字符串转换成JSON
JSONObject json = JSONUtil.parseObj(message);
String opt = json.getStr("opt");
if("ping".equals(opt)){
return;
}
//从JSON中取出Token
String token = json.getStr("token");
//从Token取出userId
String userId = StpUtil.stpLogic.getLoginIdByToken(token).toString();
//取出Session绑定的属性
Map map = session.getUserProperties();
//如果没有userId属性,就给Session绑定userId属性,关闭连接的时候会用到
if (!map.containsKey("userId")) {
map.put("userId", userId);
}
//把Session缓存起来
if (sessionMap.containsKey(userId)) {
//替换缓存中的Session
sessionMap.replace(userId, session);
} else {
//向缓存添加Session
sessionMap.put(userId, session);
}
sendInfo("ok",userId);
}
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误", error);
}
/**
* 发送消息给客户端
*/
public static void sendInfo(String message, String userId) {
if (StrUtil.isNotBlank(userId) && sessionMap.containsKey(userId)) {
//从缓存中查找到Session对象
Session session = sessionMap.get(userId);
//发送消息
sendMessage(message, session);
}
}
/**
* 封装发送消息给客户端
*/
private static void sendMessage(String message, Session session) {
try {
session.getBasicRemote().sendText(message);
} catch (Exception e) {
log.error("执行异常", e);
}
}
} 我们首先看一下模型层定义的变量,除了有查询条件之外,还有与分页相关的变量,以及保存勾选的罚款类别记录的变量,最后是表单验证的规则。总体上来看,跟用户管理页面的模型层差不多。 data: function() { return { dataForm: { type: null }, dataList: [], pageIndex: 1, pageSize: 10, totalCount: 0, dataListLoading: false, dataListSelections: [], addOrUpdateVisible: false, dataRule: { type: [{ required: false, pattern: '^[a-zA-Z0-9\u4e00-\u9fa5]{1,10}', message: '类型名称格式错误' }] } }; }, 归档任务对应的弹窗页面是archive.vue,我们先来熟悉一下这个页面。 <el-dialog title="执行归档" width="500px" :close-on-click-modal="false" v-model="visible" :show-close="false"> <el-upload ref="upload" :action="url" list-type="picture-card" accept=".jpg,.jpeg,.png" with-credentials="true" :before-upload="beforeUploadHandle" :on-success="successHandle" :on-remove="removeHandle" > <i class="el-icon-plus"></i> </el-upload> <template #footer> <span class="dialog-footer"> <el-button size="medium" @click="cancel()">取消</el-button> <el-button type="primary" @click="archive()" size="medium" :disabled="disableBtn">{{ btn }}</el-button> </span> </template> </el-dialog> 页面模型层的代码也不复杂。由于请求既要上传文件,又要上传普通数据,容易产生干扰。所以我把type参数放在URL传递,请求体中只有上传的文件。 cancel: function() { let that = this; if (Object.keys(that.picList).length > 0) { let pathes = Object.values(that.picList); that.http('cos/deleteCosFile', 'POST', { pathes: pathes }, true, function(resp) { that.picList = {}; }); } that.visible = false; that.$refs['upload'].clearFiles(); },