参考地址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
- 图片支持拖拽、截图粘贴等方式上传

