Software Engineering Final Work_xxx College Admissions Information Network (SpringBoot + Layui + shiro + Qadmin)

Software Engineering-A Brief Note on the Development Process of University Admissions Information Network

  • Development environment
  • development process
  • Login Jump Module
  • shiro permission control module
  • Background information display module
  • Click on the news headline at the front desk to display the content module
  • Admission query module
  • Message module
  • Project source code

1. Development environment

  • Development tools: IDEA, Hbuilder, Chrome
  • Technical selection: SpringBoot, Layui, Qadmin background template, Thymeleaf, Jquery, Shiro, MySQL, etc.
  • Development document reference: http://www.qadmin.net/, https://www.layui.com/doc/modules/layer.html#layer.alert

Second, the development process

1. Log in to the jump module

demand

  • The user enters the account number and password and clicks the login button of the information website to jump to the background.
  • The front-end information website uses the form component of layui, and the back-end uses the Qadmin template.
Question 1: url hides form parameters

Plus action found no response, because the project integrates Thymeleaf, so it needsth:method="post"

 <form class="layui-form layui-form-pane " th:method="post" th:action="@{/login}">

                            <div class="layui-form-item">
                                <label class="layui-form-label">账号</label>
                                <div class="layui-input-inline">
                                    <input type="text" name="userNumber" lay-verify="required" placeholder="请输入"
                                           autocomplete="off" class="layui-input">
                                </div>
                            </div>


                            <div class="layui-form-item">
                                <label class="layui-form-label">密码</label>
                                <div class="layui-input-inline">
                                    <input type="password" name="password" placeholder="请输入密码"
                                           autocomplete="off" class="layui-input">
                                </div>
                                <div class="layui-form-mid layui-word-aux">请务必填写用户名</div>
                            </div>


                            <div class="layui-form-item">
                                <button type="reset" class="layui-btn login-form-btn">重置</button>
                                <button class="layui-btn login-form-btn" type="submit"
                                >登录
                                </button>

                                <p th:text="${msg}">AAA</p>

                            </div>
                        </form>
Question 2: The night at the front desk does not have a style, and the front desk jumps to the background and the page cannot be found

The problems here are basically SpringMVC static resource interception and path problems

It is possible that the path introduced by the style is wrong. By default, pages are placed in the templates directory, and static resources (js, css, pictures) are placed in the static directory. If you put it directly in the static directory, you don't need to configure anything, and the style can be found directly. You can write and import directly, or you can use thymeleaf to import.

The configuration of these static resources can deal with special situations (for standard file locations). For example, if static resource files are placed in other static folders, the following configuration SpringMVC extensions are required.

// 2.   资源处理
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
    WebMvcConfigurer.super.addResourceHandlers(registry);
}
Question 3: The background page can only display the left navigation bar

Because the Qadmin background template used is a combination based on a mixture of vue + layui, there is no implementation of vue components, but it is divided into left navigation part + right page part

The right page part is to click the button herf in the left navigation part to go to another page, so it is not a whole html, the problem is:

  • The controller can only jump to one html page by default, that is, the left navigation part
  • The page shown on the right initiates a request for xxxx.html. You need to write a corresponding controller to process the request. You need to write as many mappings as there are pages.
  • You can add the corresponding service to the controller to get the data, and pass it to the page to be displayed through the model, because you need to re-render the data every time you open the page.
Question 4: Redirect redirect to the page in the static directory

The default forwarding is to the files in the templates directory

But the redirect is to find the html page in the static directory, which needs to be .htmlsuffixed

other problems

To solve the login failure, layui cooperates with the model to pop up the window. The idea is to judge whether the msg of the model is empty. If it is not empty, the window will pop up.

Solve jessionidthe problem of login failure when shiro integrates Thymeleaf for the first time redirection . Need to configure server.servlet.session.tracking-modes=COOKIE, refer to: Thymeleaf automatically adds ;jsessionid= after the URL, which causes the first login failure!

To solve the problem that the page for obtaining information asynchronously does not jump, the asynchronous request cooperates with the index switch of tabs to initiate the request asynchronously.

The page onload initiates an asynchronous request to render the information of the second part of the news

Mybatis configures multiple scan paths, the sub-files under resources/mapper need to be configured to ensure that mapper-locations: can be scanned classpath*:/mapper/*.xml,classpath*:/mapper/back/*.xml, pay attention toclasspath*

Thymeleaf traversal can get the index

<tr th:each="admin ,stat: ${admins}">
    <td th:text="${stat.index + 1}"></td>
</tr>

The entity class Admin inherits User, and the added user data passed in the form is incomplete, and the attributes inherited from the parent class do not match. The inheritance relationship needs to be cancelled, and all attributes need to be included in the Admin

About the request

News module

  • In the news tab area, it uses the monitoring iddex to initiate asynchronous requests, and then returns json according to the reverse order of date, displaying several items. Then directly add sub-elements to jquer to complete the addition.
  • In the second area of ​​news, onload is used to load and initiate an asynchronous request, and then return json according to the reverse order of date, displaying several items. Then directly add sub-elements to jquer to complete the addition.

User module

  • User display (thymeleaf), query user data in BackStageCobtroller, return the model, and render using thymeleaf to traverse
  • User deletion (thymeleaf), thymeleaf defines the request url in href, ?id=xxxand the backend RequestParam accepts it by passing the value.
  • User's update (thymeleaf), request the backend with id, and then find out the information and echo it back to the page, and [[${xxx}]]report undefined error through the failed name, and thymeleaf cooperates with the hidden input to succeed! ! jquery renders the data according to the id selector.
  • User's new addition (thymeleaf), form action directly post submission
  • Display of user roles (the database corresponds to admin, and the administrator needs to be displayed)
<td th:if="${admin.getRole() eq 'admin'}">管理员</td>
<td th:if="${admin.getRole() eq 'super_admin'}">超级管理员</td>
2. Shiro background permission control

The unlogged state intercepts the background

//设置未登录认证拦截器
filterMap.put("/back/**","authc");

Only the super administrator has the authority of user management. Roles have been added in userReleam, and they can be directly blocked by annotations,, @RequiresRoles("super_admin")or blocked by filter chains. You need to pay attention to the order of permission conditions, such as

//下面这些权限只能实现单资源单用户(因为Map的Key唯一),对于单资源多用户需要设置角色)

filterMap.put("/back/user_index.html","roles[super_admin]");
filterMap.put("/back/user_add.html","roles[super_admin]");
filterMap.put("/back/user_index","roles[super_admin]");
filterMap.put("/back/user_add","roles[super_admin]");
filterMap.put("/back/**","authc"); // 放在最后,前面的才能生效

Side menu display, page level control.

// 命名空间
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
    
// 在子菜单的遍历渲染中,控制到403页面
 <div shiro:hasRoles="xxx"></div>
3. Background information display module

The table is rendered and the content is too long. I started to prepare to use the folding panel for processing, but the folding panel is not good enough in td. Finally, the style of the table is set to overflow and hide the content. This has another problem, that is, you need to be able to click the button to display all the article content. . Use the button of layui, and then monitor the pop-up msg display. It's not easy to implement, and finally use if (this.offsetWidth < this.scrollWidth) {}it directly to implement the pop-up box.

<td  height="80px"  >
    <div th:text="${news.getContent()}" 
         // 设置长度过长隐藏
         style="width: 500px ;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;"> 
    </div>
</td>
  <script>
      layui.use(['element', 'layer'], function () {
          var element = layui.element;
          var layer = layui.layer;

          $(function() {
              $("td div").on("click", function() {
                  //js主要利用offsetWidth和scrollWidth判断是否溢出。
                  //在这里scrollWidth是包含内容的完全高度offsetWidth是当前表格单元格的宽度。
                  if (this.offsetWidth < this.scrollWidth) {
                      var that = this;
                      var text = $(this).text();

                      //示范一个公告层
                      layer.open({
                          type: 1
                          ,title: '详细信息' //不显示标题栏
                          ,closeBtn: false
                          ,area: '800px;'
                          ,shade: 0.8
                          ,id: 'LAY_layuipro' //设定一个id,防止重复弹出
                          ,btn: ['关闭']
                          ,btnAlign: 'c'
                          ,moveType: 0 //拖拽模式,0或者1
                          ,content: '<div style="padding: 20px ;"><pre>' + text + '</pre></div>'

                      });
                  }
              });
          })
        });

window.onload = function () {
	layer.msg("未全部显示的信息可以点击查看~", {icon: 6,title:'tips',anim: 5,offset: '100px'});

}
</script>

Delete: Click the button ?=xxxto delete directly by passing the id

Add: id selects and clicks the add button, a form dialog box pops up (start to set the display property of the form to none), and then directly initiate an asynchronous request.

Question 1: Error:, the {"timestamp":"2021-06-01T15:48:03.020+00:00","status":415,"error":"Unsupported Media Type","message":"","path":"/news/save"}reason is that the backend is added @RequestBody, this is the way of jquery, not ajax.

Question 2: Finally, some pages need to be refreshed to get the article information from the new one. window.location.href="/back/recruitment_dynamics_news.html";Be careful not to addparent

Question 3: To obtain the date, use jquery to encapsulate a getFormatDate() method to return the current time and submit it to the form.

Update: Click the modify button, the class selection pops up the form dialog (start to set the display property of the form to none), then the data is echoed, and the modification is completed and an asynchronous request is initiated.

Question 1: Data echo realization, click the modify button of the corresponding row, you need to echo the data id, articleTitle, content of this row to the form, mainly through the Jquery selector, the preferred class listens to the modification button click event, and then needs to be taken out Row data

var title = $(this).parent("td").prevAll().children(".title").text()
var id = $(this).parent("td").prevAll(".id").text()
var content = $(this).parent("td").prevAll().children(".content").text()

...

// 在成功打开弹出表单的回调事件中

...
// 数据回显
$('#title').val(title);
$('#content').val(content);


// 完整代码
<script>
    $('.update_btn').on('click', function(){
        /*
        var that = this;
        var text = $(this).text();

        var title = $(this).parents("tr").val().title
        var content = $(this).parents("tr").val().content
        */
        var title = $(this).parent("td").prevAll().children(".title").text()
        var id = $(this).parent("td").prevAll(".id").text()
        var content = $(this).parent("td").prevAll().children(".content").text()
        //页面层,弹出表单
        layer.open({
            type: 1 //Page层类型
            ,skin: 'layui-layer-molv'
            ,area: ['800px', '500px']
            ,title: ['更新招生新闻','font-size:18px']
            ,btn: ['确定', '取消']
            ,shadeClose: true
            ,shade: 0 //遮罩透明度
            ,maxmin: true //允许全屏最小化
            ,content:$("#form_div")
            ,success:	function(){

                // 数据回显
                $('#title').val(title);
                $('#content').val(content);

            }
            ,yes:function(){

                if($('#title').val().length == 0 || $('#content').val() == 0){
                    layer.msg("标题、内容不能为空!")
                }else{
                    $.post({
                        url: "/news/update",
                        dataType: 'json',
                        data: {
                            id:id,
                            articleTitle: $('#title').val(),
                            content: $('#content').val(),
                            date: getFormatDate(),
                        },
                        success: function (data) {

                            layer.alert(data.message);
                            window.location.href="/back/recruitment_dynamics_news.html";

                        },
                        error: function (error) {
                            layer.msg(error);
                        }
                    });

                }
            }


        });
    });
</script>
4. Click on the title at the front desk to display the content of the article

Realization principle

First of all, the length of the li title needs to be displayed as a limit. When testing, the length of the database field is too small. It also needs to be increased.

li{
    white-space:nowrap;
    overflow: hidden;
    text-overflow:ellipsis;

}

Then put the mouse on it, you need to display all the title tips

// 鼠标悬浮显示缩略全部信息
$(function() {
    $(document).on('mouseenter',"li.each_news",function(){
        //layer.alert('hello');

        if (this.offsetWidth < this.scrollWidth) {

            var that = this
            var title = $(this).children("a.each_title").eq(0).text()
            var date = $(this).children("span.each_date").eq(0).text()

            layer.tips(title + "日期:" + date, that, {
                tips: 1,
                time: 2000
            });

        }
    })
}),

Click on the title to display the content of the article

// 监听点击的标题,显示新闻内容
$(document).on('click',"li.each_news",function(){
   // layer.alert('hello');
    var content = $(this).children("span.each_content").eq(0).text()
    var title = $(this).children("a.each_title").eq(0).text()
    var date = $(this).children("span.each_date").eq(0).text()

    //示范一个公告层
    layer.open({
        type: 1
        , title:   '发布日期:' + date //标题栏
        , closeBtn: false
        , area: '800px;'
        , shade: 0.8
        , id: 'LAY_layuipro' //设定一个id,防止重复弹出
        , btn: ['关闭']
        , btnAlign: 'c'
        , moveType: 0 //拖拽模式,0或者1
        , content: '<div style="padding: 20px ;"> <pre>' + content + '</pre></div>'

    });

});
5. Admission module

Front desk query

  • Click the button, Jquery listens to the event according to the id, and the layer pop-up box contains the form (set the display of the form to none)
  • After filling in the information, click the confirm query button to directly initiate a post request query, and respond according to the res pop-up box.
// 录取查询按钮监听事件
$(document).on('click',"#enroll_btn",function(){
    //layer.alert('debug')
    //页面层,弹出表单
    layer.open({
        type: 1 //Page层类型
        ,skin: 'layui-layer-molv'
        ,area: ['800px', '500px']
        ,title: ['录取查询','font-size:18px']
        ,btn: ['确定', '取消']
        ,shadeClose: true
        ,shade: 0 //遮罩透明度
        ,maxmin: true //允许全屏最小化
        ,content:$("#form_div")
        ,success:  function(){
           //layer.msg($('#userNumber').val())
            //数据回显用不到
        }
        ,yes:function(){

            if($('#userNumber').val().length == 0 || $('#name').val() == 0){
                layer.msg("准考证号、姓名不能为空!")
            }else{
                $.post({
                    url: "/enroll/find-one",
                    data: {
                        userNumber: $('#userNumber').val(),
                        name: $('#name').val(),

                    },
                    success: function (data) {

                        layer.alert(data.message);


                    },
                    error: function (error) {
                        layer.msg(error);
                    }
                });

            }
        }


    });


})

Backstage management

  • New: Similar to the user management module, click the new button, and a form containing a dialog box will pop up. After filling in the data, Jquery initiates a request through a post.
  • Delete: Similar to the user management module, click the delete button in the corresponding row and request the backend with the id.
6. Message module (the front and back interfaces are all in one)

Front display

The tabs page shows: The maximum number of messages is 10, which can be checked in chronological order, and they have been replied.

New message: Click the message button to jump to the page for filling in the information, click the button on the page to pop up a form, and initiate a request after filling in the message.

Message page display: display all messages, and use layui's data table for rendering.

// 前面html
<table id="answered" lay-filter="answered_message"></table>


// 自动渲染js

<script>
    layui.use('table', function(){
        var table = layui.table;

        //第一个实例,已经回复的留言
        table.render({
             elem: '#answered'
            ,height: 600
            ,url: '/get-all-message' //数据接口
            ,page: true //开启分页
            ,cols: [
                [
                    {field: 'id', title: 'ID', width:80, sort: true, fixed: 'left'}
                    ,{field: 'problem', title: '留言问题', width:180}
                    ,{field: 'answer', title: '管理员回答', width:180, sort: true}
                    ,{field: 'examType', title: '考试类型', width:80}
                    ,{field: 'examScore', title: '考试分数', width: 80}
                    ,{field: 'studentLocation', title: '城市', width: 80, sort: true}
                    ,{field: 'highSchool', title: '毕业高中', width: 80, sort: true}
                    ,{field: 'questTime', title: '留言时间', width: 80}
                ]
            ]
        });



    });
</script>

problem appear

When the data table is rendered, an error is reported:, the org.thymeleaf.exceptions.TemplateProcessingException: Could not parse as expression: "reason is [[ ]]that the inline expression after cols becomes thymeleaf. The solution is to add a newline.

Row index problem: the id of the database may not be continuous, you need to customize the template to display

  cols: [[
    {field:'id', title: '文章标题', width: 200, templet: function(d) {
        return d.LAY_INDEX;
    }} //这里的templet值是模板元素的选择器
    ,
  ]]
});

Background module

Display: layui data table rendering, calling the same interface as the foreground.

Delete: The toolBar of the layui data table displays the delete button, click to get the current row object, and then hold the id to initiate an ajax request.

Reply: Click the corresponding button on the toolbar of layui to get the current row object. First, the form will pop up to display the data, and after filling out the reply, initiate a request for update reply.

// 2. 监听行操作
table.on('tool(messages)', function(obj){ //注:tool 是工具条事件名,test 是 table 原始容器的属性 lay-filter="对应的值"
    var data = obj.data; //获得当前行数据
    var layEvent = obj.event; //获得 lay-event 对应的值(也可以是表头的 event 参数对应的值)
    var tr = obj.tr; //获得当前行 tr 的 DOM 对象(如果有的话)

    if(layEvent === 'detail'){ //查看
        //do somehing
    }
    else if(layEvent === 'del'){ //删除

        //console.log(obj)

        layer.confirm('确认要删除么', function(index){

            obj.del(); //删除对应行(tr)的DOM结构,并更新缓存
            layer.close(index);
            //向服务端发送删除指令

            var id = obj.data.id

            $.ajax({
                url: "/delete-message",
                data: {id: id},
                type: "get",
                dataType: "json",
                success: function(data) {
                    // data = jQuery.parseJSON(data);  //dataType指明了返回数据为json类型,故不需要再反序列化
                    layer.alert(data.message)

                }
            });





        });
    }
    else if(layEvent === 'edit'){ //编辑
        //do something

        //console.log(obj)
        //页面层,弹出表单
        layer.open({
            type: 1 //Page层类型
            ,skin: 'layui-layer-molv'
            ,area: ['1000px', '600px']
            ,title: ['回复留言','font-size:18px']
            ,btn: ['确定', '取消']
            ,shadeClose: true
            ,shade: 0 //遮罩透明度
            ,maxmin: true //允许全屏最小化
            ,content:$("#form_div")
            ,success:  function(){
                // 数据回显
                $('#problem').val(obj.data.problem);
                // $('#content').val("");
                $('#phone').val(obj.data.phone);
                $('#studentLocation').val(obj.data.studentLocation);
                $('#examType').val(obj.data.examType);
                $('#examScore').val(obj.data.examScore);
                $('#highSchool').val(obj.data.highSchool);


            }
            ,yes:function(){
                //layer.msg("点击了确定!")


                if($('#answer').val() == 0){
                    layer.msg("回复不能为空!")
                }
                else{
                    $.post({
                        url: "/update-message",
                        dataType: 'json',
                        data: {
                            id: obj.data.id,
                            answer: $('#answer').val()
                        },
                        success: function (data) {
                            layer.open({
                                content: data.message,
                                yes: function(index, layero){
                                    //do something
                                    location.reload()
                                    layer.close(index); //如果设定了yes回调,需进行手工关闭
                                }
                            });



                        },
                        error: function (error) {
                            layer.msg(error);
                        }
                    });

                }
            }


        });




        //同步更新缓存对应的值
        obj.update({
           
        });
    }

});

Third, the project source code

github address: https://github.com/GitHubSi/enrollment-infomation_springboot-layui-qadmin-shiro

Insert picture description here