完结11章-技术大牛成长课,从0到1带你手写一个数据库系统

157 阅读3分钟

[完结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(); },