SpringBoot actual development background management - architecture description and development
1. Architecture mode
-
Monomer architecture
-
technical structure
-
springboot + mybatisplus + mysql
-
Front end technology: jquery / layui
-
Font Icon Library: iconfont
2. What are the architectural patterns of background development?
- Pure enterprise development (all internal design of the company, backstage page and function control, animation, js and css preparation)
- Use some open source development patterns (layui, bootstrap, jui, extjs), etc. What are the benefits? Fast and convenient, which provides a large number of components and modules. For example: date component, table, form, button, pop-up window, etc. 95%.
- New background development modes: Vue admin element, elementui, antd, etc. are all front-end and back-end separated architecture modes based on Vue client architecture. (95%)
3. Rendering of menu navigation developed in the background
There are three modes of comparing programs:
1. Iframe based (compare traditional asynchronous + dynamic page rendering)
2. Pure asynchronous (js is used for dynamic splicing and rendering) (compared with traditional asynchronous + dynamic page rendering)
3. Based on route jump (vue scaffold) - vue router
4. Rendering problems with menu navigation
First, manage and control the navigation bar
Thinking: navigation needs to exist in every page, so we can split the public part with the technology contained in the page, and then import it in each page with the defined syntax. that will do
The advantage is that it is convenient for us to unify maintenance and subsequent upgrade and control.
There are such technologies included in freemaker or jsp or thymeleaf. Take thymeleaf as an example:
Create a new common and leftnav The html is as follows
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <body> <aside id="asideapp" th:fragment="asidebar" class="byte-layout-sider byte-layout-sider-light mp-main-sider animated fadeInLeft" style="width: 246px;"> <div class="byte-layout-sider-children"> <div class="mp-menu-wrapper f-min-scroll f-hover-scroll mp-menu-wrapper-can-scroll"> <div class="byte-menu garr-menu"> <div class="byte-menu-inline base_creation_tab"> <div class="byte-menu-inline-header"> <a href="/admin/index" class=""> <span style="padding-left: 0px; display: block;"> <span title="Console" class="ksd-icon-sp iconfont fz20 mr-2 iconhome"></span> <span>Console</span> </span> </a> </div> <div class="byte-menu-inline-content animated fadeIn" style="height: auto; display: none;"></div> </div> <div class="byte-menu-inline base_creation_tab"> <div class="byte-menu-inline-header"> <a href="javascript:void(0);" class=""> <span style="padding-left: 0px;"> <span title="user management " class="ksd-icon-sp iconfont fz20 mr-2 iconiconzh1"></span> <span>user management </span> </span> <span class="byte-menu-icon-suffix"> <svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor" class="byte-icon byte-icon-down"> <path d="M512 613.12L858.88 265.6a21.12 21.12 0 0130.08 0l30.08 30.72a21.12 21.12 0 010 30.08L542.08 704a42.24 42.24 0 01-60.16 0L104.96 326.4a21.12 21.12 0 010-30.08l30.08-30.72a21.12 21.12 0 0130.08 0z"></path> </svg> </span> </a> </div> <div class="byte-menu-inline-content animated fadeIn" style="display: block;"> <div title="user management " data-href="/admin/user/list" class="byte-menu-item"> <span style="padding-left: 24px; display: block;"> <span class="ksd-icon-sp selected-border-right iconfont fz20 mr-2 iconiconzh1"></span> <a href="javascript:void(0);">user management </a> </span> </div> <div title="Statistical module" data-href="/admin/state/list" class="byte-menu-item"> <span style="padding-left: 24px; display: block;"> <span class="ksd-icon-sp selected-border-right iconfont fz20 mr-2 iconiconzh1"></span> <a href="javascript:void(0);">Statistical module</a> </span> </div> </div> </div> <div class="byte-menu-inline base_creation_tab"> <div class="byte-menu-inline-header"> <a href="javascript:void(0);" class=""> <span style="padding-left: 0px; display: block;"> <span title="Role management" class="ksd-icon-sp iconfont fz20 mr-2 iconorder1"></span> <span>Notification management</span> </span> <span class="byte-menu-icon-suffix is-open"> <svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor" class="byte-icon byte-icon-down"> <path d="M512 613.12L858.88 265.6a21.12 21.12 0 0130.08 0l30.08 30.72a21.12 21.12 0 010 30.08L542.08 704a42.24 42.24 0 01-60.16 0L104.96 326.4a21.12 21.12 0 010-30.08l30.08-30.72a21.12 21.12 0 0130.08 0z"></path> </svg> </span> </a> </div> <div class="byte-menu-inline-content animated fadeIn" style="height: auto; display: none;"> <div title="Role list" data-href="/admin/permission/list" class="byte-menu-item"> <span style="padding-left: 24px; display: block;"> <span class="ksd-icon-sp selected-border-right iconfont fz20 mr-2 iconorder1"></span> <a href="javascript:void(0);">Role list</a> </span> </div> <div title="Permission list" data-href="/admin/role/list" class="byte-menu-item"> <span style="padding-left: 24px; display: block;"> <span class="ksd-icon-sp selected-border-right iconfont fz20 mr-2 iconorder1"></span> <a href="javascript:void(0);">Permission list</a> </span> </div> <div title="System notification" data-href="/admin/notice/system" class="byte-menu-item"> <span style="padding-left: 24px; display: block;"> <span class="ksd-icon-sp selected-border-right iconfont fz20 mr-2 iconorder1"></span> <a href="javascript:void(0);">System notification</a> </span> </div> <div title="Course notice" data-href="/admin/notice/course" class="byte-menu-item"> <span style="padding-left: 24px; display: block;"> <span class="ksd-icon-sp selected-border-right iconfont fz20 mr-2 iconorder1"></span> <a href="javascript:void(0);">Course notice</a> </span> </div> </div> </div> <div class="byte-menu-inline base_creation_tab"> <div class="byte-menu-inline-header"> <a href="javascript:void(0);" class=""> <span style="padding-left: 0px; display: block;"> <span title="system management" class="ksd-icon-sp iconfont fz20 mr-2 iconshangjia2"></span> <span>system management</span> </span> <span class="byte-menu-icon-suffix is-open"> <svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor" class="byte-icon byte-icon-down"> <path d="M512 613.12L858.88 265.6a21.12 21.12 0 0130.08 0l30.08 30.72a21.12 21.12 0 010 30.08L542.08 704a42.24 42.24 0 01-60.16 0L104.96 326.4a21.12 21.12 0 010-30.08l30.08-30.72a21.12 21.12 0 0130.08 0z"></path> </svg> </span> </a> </div> <div class="byte-menu-inline-content animated fadeIn" style="height: auto; display: none;"> <div title="Digital account generation" data-href="/admin/system/userNum" class="byte-menu-item"> <span style="padding-left: 24px; display: block;"> <span class="ksd-icon-sp selected-border-right iconfont fz20 mr-2 iconshangjia2"></span> <a href="javascript:void(0);">Digital account generation</a> </span> </div> <div title="Navigation friend chain management" data-href="/admin/system/friendLink" class="byte-menu-item"> <span style="padding-left: 24px; display: block;"> <span class="ksd-icon-sp selected-border-right iconfont fz20 mr-2 iconshangjia2"></span> <a href="javascript:void(0);">Navigation friend chain management</a> </span> </div> <div title="Member price management" data-href="/admin/system/vipPrice" class="byte-menu-item"> <span style="padding-left: 24px; display: block;"> <span class="ksd-icon-sp selected-border-right iconfont fz20 mr-2 iconshangjia2"></span> <a href="javascript:void(0);">Member price management</a> </span> </div> <div title="SEO Administration" data-href="/admin/system/seo" class="byte-menu-item"> <span style="padding-left: 24px; display: block;"> <span class="ksd-icon-sp selected-border-right iconfont fz20 mr-2 iconshangjia2"></span> <a href="javascript:void(0);">SEO Administration</a> </span> </div> <div title="Site settings" data-href="/admin/system/params" class="byte-menu-item"> <span style="padding-left: 24px; display: block;"> <span class="ksd-icon-sp selected-border-right iconfont fz20 mr-2 iconshangjia2"></span> <a href="javascript:void(0);">Site settings</a> <span title="To be completed" class="layui-badge-dot" style="position: absolute; top: 12px; right: 50px;"></span> </span> </div> <div title="OSS Administration" data-href="/admin/system/oss" class="byte-menu-item"> <span style="padding-left: 24px; display: block;"> <span class="ksd-icon-sp selected-border-right iconfont fz20 mr-2 iconshangjia2"></span> <a href="javascript:void(0);">OSS Administration</a> <span title="To be completed" class="layui-badge-dot" style="position: absolute; top: 12px; right: 50px;"></span> </span> </div> </div> </div> </div> </div> </div> </aside> </body> </html>
Core code
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <body> <aside th:fragment="asidebar"></aside>
Th: fragment = "aside bar" give the specific location of the page template a name: aside bar. This name is introduced in the pages to be included as follows:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="IE=Edge"> <meta http-equiv="Pragma" content="no-cache"> <meta http-equiv="Cache-Control" content="no-cache"> <meta http-equiv="Expires" content="0"> <title>back-stage management</title> <meta name="keywords" content="HTML, CSS, XML, XHTML, JavaScript"> <meta name="description" content="franco web Technical course"> <!-- Background public css js The file storage page contains --> <div th:replace="~{commons/header::scriptbar}"></div> </head> <body data-ext-version="3.1" style=""> <!-- Head menu navigation --> <div th:replace="~{commons/nav::navbar}"></div> <!-- Content area --> <div id="root" class="ksd-main"> <div class="pgc-wrapper pgc-index index-wrapper"> <div class="pgc-content"> <section class=" mp-main-contain byte-layout-has-sider"> <!-- Left menu navigation --> <div th:replace="~{commons/leftnav::asidebar}"></div> <!-- Content table --> <div id="ksd-mainbox" class="animated fadeIn pr"></div> </section> </div> </div> </div> <!-- bottom --> <div th:replace="~{commons/footer::footerbar}"></div> <!-- Page script --> <script th:src="@{/js/admin/index.js}"></script> </body> </html>
Core code:
<div th:replace="~{commons/leftnav::asidebar}"></div>
th:replace stands for commons / leftnav The specific code block name in html is: th:fragment = "asidebar". Take it out and replace the div.
$(function () { // Initialize menu navigation adminAside.init(); // Page loading tips adminLoading.init(); }) // Control left menu navigation var adminAside = { init:function () { this.animate(); this.menuEvent(); }, // Controls the folding and unfolding of the left menu navigation animate:function () { //Click menu binding click event, and then control the menu below to expand and collapse $("#asideapp").find(".byte-menu-inline-header").on("click", function () { // Control exclusion $(this).parents(".byte-menu-inline").siblings().find(".byte-menu-inline-content").hide(); $(this).parents(".byte-menu-inline").siblings().find(".byte-menu-icon-suffix").removeClass("is-open"); $(this).next().toggle(); $(this).find(".byte-menu-icon-suffix").toggleClass("is-open"); }) }, // From the control menu, click render right template menuEvent:function () { $("#asideapp").find(".byte-menu-item").on("click", function () { var href = $(this).data("href"); // $.get(href, function (res) { // $("#ksd-mainbox").html(res); // }) $("#ksd-mainbox").load(href); }) // Trigger the first element as the default active item $("#asideapp").find(".byte-menu-item").eq(0).trigger("click"); } } // Page loading tips var adminLoading = { init:function () { this.animate(); }, animate:function () { } }
5. Login
Note: login and logout are handled in the background development, and login is not required. Other background access pages require login interception to enter. Therefore, one of the first issues we consider when doing background work: login interception
5.1 define and register login interceptors
package com.example.config; import com.example.hander.LoginInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @Auther: giraffe * @Date: 2021/07/26/16:43 * @Description: */ @Configuration public class WebMvcConfiguration implements WebMvcConfigurer { @Bean public LoginInterceptor getLoginInterceptor() { return new LoginInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { // Interceptor registration registry.addInterceptor(getLoginInterceptor()) // Exclude login and exit .excludePathPatterns("/admin/login", "/admin/logout", "/admin/toLogin") // Intercept all background requests .addPathPatterns("/admin/**"); } }
5.2 exclude login and exit routes in the interceptor
@Override public void addInterceptors(InterceptorRegistry registry) { // Interceptor registration registry.addInterceptor(getLoginInterceptor()) // Exclude login and exit .excludePathPatterns("/admin/login", "/admin/logout", "/admin/toLogin") // Intercept all background requests .addPathPatterns("/admin/**"); }
5.3 logged in, jump to the home page
package com.example.controller; import com.example.common.constant.RConstants; import com.example.common.exception.ResultCodeEnum; import com.example.common.exception.ValidationException; import com.example.entity.User; import com.example.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpSession; /** * @Auther: giraffe * @Date: 2021/07/25/11:07 * @Description: */ @Controller @Slf4j public class LoginController extends BaseController { @Autowired private UserService userService; @GetMapping("/login") public String login(HttpSession session){ // First, judge whether the user has logged in User sessionUser = (User) session.getAttribute(RConstants.SESSION_USER); // If you have logged in, go directly to the home page if (sessionUser != null) { return "redirect:/admin/index"; } // If you don't log in, go directly to the login page return "login"; } @PostMapping("/toLogin") @ResponseBody public String toLogin(HttpSession session, String nickname, String password){ log.info("Current user:{},password:{}", nickname, password); // Query whether the user exists according to the nickname entered by the user User user = userService.getByUserName(nickname); // If it does not exist, it returns fail, which means that the user cannot find it if(user == null){ throw new ValidationException(ResultCodeEnum.NICKNAME_NO_EXISTENCE); } // Judge whether the password entered by the current user is consistent with the database password. If the consistent login is successful if (user != null && !user.getPassword().equalsIgnoreCase(password)) { throw new ValidationException(ResultCodeEnum.PASSWORD_ERROR); } // Write the login successful user information into the session session.setAttribute(RConstants.SESSION_USER, user); // success is returned after successful login return "success"; } }
5.4. Log out
@GetMapping("/logout") public String logout(HttpSession session){ // Log out session.invalidate(); // Exit successfully, redirected to login page return "redirect:/admin/login"; }
<a th:href="@{/admin/logout}" style="width: 86px;"><i class="iconfont iconai-out"></i>Log out</a>
5.5 background setting of login page
.bgpic{ background:url("xxxx.jpg"); position:fixed; top:0; left:0; bottom:0; right:0; filter:blur(1px); background-size:cover; }
6. Complete list query, search and pagination
6.1 configuration of paging interceptor
package com.example.config; import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.annotation.EnableTransactionManagement; /** * @Auther: giraffe * @Date: 2021/07/30/14:53 * @Description: */ @Configuration @EnableTransactionManagement public class MyBatisPlusConfig { // Paging plug-in @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } }
6.2 define paging logic
package com.example.controller.state; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.example.controller.common.BaseController; import com.example.entity.State; import com.example.service.state.StateService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; /** * @Auther: giraffe * @Date: 2021/07/28/15:27 * @Description: */ @Controller public class StateController extends BaseController { @Autowired private StateService stateService; @GetMapping("state/list") public String list(ModelMap modelMap){ Page<State> pageState = stateService.pageList(0, 10); // 2: Put data into scope // Specific data displayed on each page modelMap.put("stateList", pageState.getRecords()); // Total records modelMap.put("total", pageState.getTotal()); //pageSize is how many pages are displayed per page modelMap.put("pageSize", pageState.getSize()); // pageNo current page modelMap.put("pageNo", pageState.getCurrent()); // How many pages are there in the pages modelMap.put("pages", pageState.getPages()); // 3: Rendered view template return "state/template"; } @GetMapping("state/listTemplate") public String listTemplate(ModelMap modelMap, @RequestParam(name = "pageNo", defaultValue = "1")Integer pageNo, @RequestParam(name = "pageSize", defaultValue = "10")Integer pageSize){ Page<State> pageState = stateService.pageList(pageNo,pageSize); // 2: Put data into scope // Specific data displayed on each page modelMap.put("stateList", pageState.getRecords()); // Total records modelMap.put("total", pageState.getTotal()); //pageSize is how many pages are displayed per page modelMap.put("pageSize", pageState.getSize()); // pageNo current page modelMap.put("pageNo", pageState.getCurrent()); // How many pages are there in the pages modelMap.put("pages", pageState.getPages()); // 3: Rendered view template return "state/listTemplate"; } }
package com.example.service.state; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.entity.State; import com.example.mapper.StateMapper; import org.springframework.stereotype.Service; /** * @Auther: giraffe * @Date: 2021/07/30/13:33 * @Description: */ @Service public class StateServiceImpl extends ServiceImpl<StateMapper, State> implements StateService { @Override public Page<State> pageList(int pageNo, int pageSize) { Page<State> page = new Page<>(pageNo, pageSize); // Query paging QueryWrapper<State> queryWrapper = new QueryWrapper<>(); Page<State> pageState = this.page(page, queryWrapper); return pageState; } }
package com.example.service.state; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import com.example.entity.State; /** * @Auther: giraffe * @Date: 2021/07/30/13:32 * @Description: */ public interface StateService extends IService<State> { Page<State> pageList(int pageNo, int pageSize); }
6.3 page initialization paging
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <body> <main id="appbox" class="byte-layout-content garr-container" style="background: rgb(255, 255, 255);"> <div class="layui-tab-item layui-show"> <div id="LAY_preview"> <div class="layui-border-box"> <div class="layui-table-tool"> <span class="mr-4 fl">11 articles in total</span> <div class="layui-table-tool-selfc ml-2 fr"> <a href="https://www.kuangstudy.com/bbs" target="_blank" class="layui-btn layui-btn-sm"> <span class="iconfont iconhome mr-2 fz12"></span> Visit the home page </a> </div> <div class="layui-table-tool-selfc ml-2 fr"> <button class="layui-btn layui-btn-sm"> <i class="iconfont iconadd mr-2"></i> Add first level classification </button> </div> <div class="layui-table-tool-selfc fr"> <div class="layui-inline"> <input title="knock enter Keyboard can also search!" maxlength="100" autocomplete="off" placeholder="Please enter a category title..." class="layui-input" style="width: 320px; height: 32px; line-height: 32px;"> </div> <button class="layui-btn layui-btn-sm">search</button> </div> </div> </div> <div class="layui-table-box"> <div class="layui-table-body layui-table-main"> <table cellspacing="0" cellpadding="0" border="0" class="layui-table"> <thead> <tr> <th class="layui-table-col-special"> <div class="layui-table-cell laytable-cell-numbers"> <span>ID</span> </div> </th> <th> <div class="layui-table-cell"><span>title</span></div> </th> <th> <div class="layui-table-cell"><span>Number of participants</span></div> </th> <th> <div class="layui-table-cell"><span>Creation time</span></div> </th> <th> <div class="layui-table-cell"><span>Create user</span></div> </th> <th> <div class="layui-table-cell"><span>state</span></div> </th> <th> <div class="layui-table-cell"><span>operation</span></div> </th> </tr> </thead> <tbody id="state-tbody" th:data-total="${total}" th:data-pages="${pages}" th:data-pageSize="${pageSize}"> <div th:replace="~{state/listTemplate::stateList}"></div> </tbody> </table> </div> <!-- Turn pages --> <div class="layui-table-page" id="state-page" style="height: 60px; text-align: center; margin-top: 50px;"></div> </div> </div> </div> <script> // 1: How to initialize and get the page number of each click // 2: How to query the corresponding data according to the paging query and return the corresponding data to the specified location var state = { page:function (total) { var that = this; layui.use(['laypage', 'layer'], function() { var layPage = layui.laypage , layer = layui.layer; //The total number of pages is greater than the total number of pages layPage.render({ elem: 'state-page' , count: total //Total data , jump: function (obj) { // Get the current user click the current page var currentPageNo = obj.curr; // if(currentPageNo > 1){ // Click on each page of rendered data that.loadData(currentPageNo); // } } }); }); }, // Load background data // loadData:function (pageNo) { // $.get("/admin/state/listTemplate", {pageNo:pageNo}, function (res) { // //Find the content of the rendered tbody on the second page // var tbodyHtml = $(res).find("#state-tbody").html(); // //Put the found content into the current tbody // $("#state-tbody").html(tbodyHtml); // }) // } loadData:function(pageNo){ $.get("/admin/state/listTemplate",{pageNo:pageNo},function(res){ $("#state-tbody").html(res); }) } }; $(function () { // Get total records var total = $("#state-tbody").data("total"); // Initialize paging based on total state.page(total); }) </script> </main> </body> </html>
Core code
<tbody id="ksd-tbody" th:data-total="${total}" th:data-pages="${pages}">
1. The above code takes out the data in the background scope through thymeleaf's template rendering technology.
// Specific data displayed on each page modelMap.put("stateList", pageState.getRecords()); // Total records modelMap.put("total", pageState.getTotal()); //pageSize is how many pages are displayed per page modelMap.put("pageSize", pageState.getSize()); // pageNo current page modelMap.put("pageNo", pageState.getCurrent()); // How many pages are there in the pages modelMap.put("pages", pageState.getPages());
2. Then it is rendered to the left position through ajax, and then JS is loaded, the total number of records is de duplicated through jQuery's data syntax, and then paging can be initialized.
```// The entry of iframe and iframe is not triggered immediately, including all the functions of the current node $(function(){ // 1. Get the total number of records from the KSD tbody attribute var total = $("#ksd-tbody").data("total"); // 2. Just initialize paging according to total ksdState.page(total); })
Core code
<tbody id="ksd-tbody" th:data-total="${total}" th:data-pages="${pages}">
1. The above code takes out the data in the background scope through thymeleaf's template rendering technology.
// Specific data displayed on each page modelMap.put("stateList", pageState.getRecords()); // Total records modelMap.put("total", pageState.getTotal()); //pageSize is how many pages are displayed per page modelMap.put("pageSize", pageState.getSize()); // pageNo current page modelMap.put("pageNo", pageState.getCurrent()); // How many pages are there in the pages modelMap.put("pages", pageState.getPages());
2. Then it is rendered to the left position through ajax, and then JS is loaded, the total number of records is de duplicated through jQuery's data syntax, and then paging can be initialized.
// All current nodes, excluding the entry function triggered immediately after image and iframe are loaded $(function(){ // 1. Get the total number of records from the KSD tbody attribute var total = $("#ksd-tbody").data("total"); // 2. Just initialize paging according to total ksdState.page(total); })