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

feilipu2023nui · · 1220 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。
参考地址1:https://pan.baidu.com/s/1N-x48vz9Z0peZdjOU5Gh-g 提取码: nxts 参考地址2:https://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-form :inline="true" :model="dataForm" :rules="dataRule" ref="dataForm"> <el-form-item prop="name"> <el-input v-model="dataForm.name" placeholder="会议室名称" size="medium" class="input" clearable="clearable" /> </el-form-item> <el-form-item> <el-select v-model="dataForm.canDelete" class="input" placeholder="条件" size="medium"> <el-option label="全部" value="all" /> <el-option label="可删除" value="true" /> <el-option label="不可删除" value="false" /> </el-select> </el-form-item> <el-form-item> <el-button size="medium" type="primary" @click="searchHandle()">查询</el-button> <el-button size="medium" type="primary" :disabled="!isAuth(['ROOT', 'MEETING_ROOM:INSERT'])" @click="addHandle()" > 新增 </el-button> <el-button size="medium" type="danger" :disabled="!isAuth(['ROOT', 'MEETING_ROOM:DELETE'])" @click="deleteHandle()" > 批量删除 </el-button> </el-form-item> </el-form> 打开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-form :inline="true" :model="dataForm" :rules="dataRule" ref="dataForm"> <el-form-item prop="name"> <el-input v-model="dataForm.name" placeholder="姓名" size="medium" class="input" clearable="clearable" /> </el-form-item> <el-form-item> <el-select v-model="dataForm.deptId" class="input" placeholder="部门" size="medium" clearable="clearable" > <el-option v-for="one in deptList" :label="one.deptName" :value="one.id" /> </el-select> </el-form-item> <el-form-item> <el-select v-model="dataForm.typeId" class="input" placeholder="罚款类型" size="medium" clearable="clearable" > <el-option v-for="one in amectTypeList" :label="one.type" :value="one.id" /> </el-select> </el-form-item> <el-form-item> <el-date-picker v-model="dataForm.date" type="daterange" range-separator="~" start-placeholder="开始日期" end-placeholder="结束日期" size="medium" ></el-date-picker> </el-form-item> <el-form-item> <el-select v-model="dataForm.status" class="input" placeholder="状态" size="medium" clearable="clearable" > <el-option label="未缴纳" value="1" /> <el-option label="已缴纳" value="2" /> </el-select> </el-form-item> <el-form-item> <el-button size="medium" type="primary" @click="searchHandle()">查询</el-button> <el-button size="medium" type="primary" :disabled="!isAuth(['ROOT', 'AMECT:INSERT'])" @click="addHandle()" > 新增 </el-button> <el-button size="medium" type="danger" :disabled="!isAuth(['ROOT', 'AMECT:DELETE'])" @click="deleteHandle()" > 批量删除 </el-button> <el-button size="medium" type="warning" :disabled="!isAuth(['ROOT', 'AMECT:SELECT'])" @click="reportHandle()" > 查看报告 </el-button> </el-form-item> </el-form> 页面表格内容也不复杂,而且折叠面板的内容,我们直接把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> </el-table> 向客户端发送消息,需要使用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(); },
1220 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传