参考地址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();
},
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传