Web项目实战 | 购物系统v2.0 | 开发记录(八)前后端分离初步思路 | JS处理URL参数实用函数 | AJAX 向后台传递Map类型数据 | MyBatis多表查询优化

" 常在河边走,哪能不湿鞋。" ——若发现文章内容有误,敬请指正,望不吝赐教,感谢!

以往记录


Web项目实战 | 购物系统v2.0 | 开发记录(一)需求分析 | 技术选型 | 系统设计 | 数据表设计 | SpringBoot、SSM、Thymeleaf、Bootstrap…

Web项目实战 | 购物系统v2.0 | 开发记录(二)搭建SpringBoot+SSM框架环境 | 配置Druid+MyBatis | 基于Bootstrap实现登陆页面| 图片验证码接口

Web项目实战 | 购物系统v2.0 | 开发记录(三)分页显示 | 根据商品名称进行模糊查询

Web项目实战 | 购物系统v2.0 | 开发记录(四)单个页面单个请求解决根据商品类型进行分页查询 | 使用省市区三级联动 | 使用JQuery 插件实现图片上传

Web项目实战 | 购物系统v2.0 | 开发记录(五)| 使用base64编码实现头像修改 | 用户个人信息修改 | JQuery动态提示

Web项目实战 | 购物系统v2.0 | 开发记录(六)商品详情页面 | 游客访问主页 | 运用Bootstrap4轻量级弹窗实现提示

Web项目实战 | 购物系统v2.0 | 开发记录(七)SpringBoot整合Shiro框架进行身份认证 | Shiro 加盐(MD5+Salt)验证登陆 | 数据表结构优化避免外键+设置中间表

一、使用JQuery设计工具函数处理URL


URL的作用非同小可,比如在百度搜索CSDN,观察地址栏可以发现有许多参数。
在这里插入图片描述
这些参数可以传输到SpringMVC的Controller层,后期有许多的用途,比如查询一个用户的映射地址为user/query,这时可以给它添加一个参数表示查询的用户信息,比如user/query?id=1,这时则表示查询id为1的用户,笔者通过URL的处理,实现了后台管理系统中用户信息多个条件搜索的功能。

JS实现代码:urlUtil.js

// 添加 或者 修改 url中参数的值
function clearURIExceptPageNow(){
    return document.location.href.split('&')[0]
}

function getUrlParam(thisURL, name) {
    let array = new RegExp(name + '=([^&]*)').exec(thisURL);
    return (array == null || array.length < 1)? '' : decodeURI(array[1]);
}

function updateUrlParam(thisURL, name, val) {
    thisURL = decodeURI(thisURL)
    // 如果 url中包含这个参数 则修改
    if (thisURL.indexOf(name+'=') > 0) {
        var v = getUrlParam(thisURL, name);
        if (v != null) {
            // 是否包含参数
            thisURL = thisURL.replace(name + '=' + v, name + '=' + val);
        }
        else {
            thisURL = thisURL.replace(name + '=', name + '=' + val);
        }

    } // 不包含这个参数 则添加
    else {
        if (thisURL.indexOf("?") > 0) {
            thisURL = thisURL + "&" + name + "=" + val;
        }
        else {
            thisURL = thisURL + "?" + name + "=" + val;
        }
    }
    return thisURL;
}
function updateCurrentUrlParam(name, val) {
    return updateUrlParam(document.location.href, name, val)
}
function getUrlParamToMap() {
    const params = window.location.toString().split('\?')[1]
    let map = new Map();
    if(params == null || params === '') return ''
    params.split('&').forEach(param =>{
        let kv = param.split('=');
        let k = kv[0]
        let v = kv[1]
        map.set(k,v)
    })
    return map
}
function getUrlParamToJSON() {
    let obj= Object.create(null)
    let map = getUrlParamToMap();
    if(map == null || map === '') return;
    for (let[k,v] of map) {
        k = decodeURI(k)
        v = decodeURI(v)
        obj[k] = v;
    }
    return JSON.stringify(obj)
}


功能一:直接获取替换参数值后的URL

比如当前地址栏的URL为:

user/query?id=1&roleName=普通用户

此时将查询用户ID为1,身份是普通用户的所有用户,当要切换查询的用户身份时,可以通过这个函数直接获取对应的URL

updateCurrentUrlParam('roleName', '管理员')

那么URL就会变成以下内容(其他的参数不会受影响)

user/query?id=1&roleName=管理员

功能二:清空URL除第一个以外的参数

比如当前地址栏请求的URL为:

user/query?pageNow=1&username=尤你&roleName=管理员

由于笔者固定第一个参数表示页数(分页查询),所以这里第一个参数不能清除,在clearURIExceptPageNow() 函数里保留了第一个参数,执行后的结果:

user/query?pageNow=1

功能三:封装URL的参数为Map/JSON类型

之前的函数都是基于URL内容上的修改,本质上都是字符串类型,主要用于修改当前的地址栏信息。

接下来是对其类型的封装,这样有助于通过AJAX请求向后台传递数据,直接将URL的参数传递到后台进行处理。

getUrlParamToJSON() 	// 将当前地址栏的参数值封装成JSON字符串(可传递到后台)

getUrlParamToMap()		// 将当前地址栏的参数封装成map类型的对象

二、JQuery AJAX 通过Map传输数据


有了之前的工具函数后,现在操作URL的参数变得特别灵活,以获取用户信息为例:

function loadData(){
    $.ajax({
        url: '/admin/user/query',
        type: 'POST',
        data: getUrlParamToJSON(),
        dataType: 'JSON',
        contentType: 'application/json;charset=UTF-8',
        error: (err) =>{
            console.error(err)
        },
        success: (data) =>{
	        console.error(data)
            //...
        }
    })
}

Controller层:

package com.uni.controller.admin;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.uni.base.RestResponse;
import com.uni.pojo.User;
import com.uni.service.UserService;
import com.uni.utils.PageUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/admin/user")
public class UserAdminController {
    @Autowired
    UserService userService;

    @PostMapping("/query")
    public String toAdminUserByPage(@RequestBody Map<String, String> paramMap) throws UnsupportedEncodingException {
        Map<String, String> map = new HashMap<>();
        for (String param : paramMap.keySet()) {
            paramMap.put(URLDecoder.decode(param, "utf-8"),
                    URLDecoder.decode(paramMap.get(param), "utf-8"));
        }
        // 获取请求的参数
        Integer pageNow = Integer.parseInt(map.getOrDefault("pageNow", "1"));
        PageHelper.startPage(pageNow, 5);

        List<User> usersInfo = userService.getUserByMapPojos(paramMap);
        PageInfo pageInfo = PageUtil.setPageInfo(pageNow, 5, usersInfo);
        return RestResponse.ok(new HashMap<String, Object>(){{
            put("usersInfo", usersInfo);
            put("pageInfo", pageInfo);
        }}).toJSONString();
    }
}

这里需要注意,在传递中文参数的时候,URL对其进行了转码,所以在后台接收到的数据默认是转码后的结果,比如在地址栏输入

http://localhost:8080/测试

然后手动复制这个地址,再粘贴后

http://localhost:8080/%E6%B5%8B%E8%AF%95

所以在Controller层接收的参数值就会是这个%E6%B5%8B%E8%AF%95,可以通过JavaEE原生的包java.net.URLDecoder里提供的decode方法进行解码,比如:(这里笔者将Map的KV都进行了转码):

paramMap.put(URLDecoder.decode(param, "utf-8"), URLDecoder.decode(paramMap.get(param), "utf-8"));

同样地,之前在前端的JS文件中也进行了解码(防止中文乱码)

thisURL = decodeURI(thisURL)

三、前后端分离思路:动态渲染Ajax请求返回的数据


在这个项目里,笔者一开始是使用Thymeleaf模板引擎渲染数据的,之前要么通过session获取数据,要么通过Controller层在Model对象里写入的数据,而现在则是通过JSON数据,关于数据库的页面就通过JS来渲染,比如渲染用户的信息:

function renderRoleInfo(roleInfo){

    $('#tbody-role').html('')		// 先清空内容
    $.each(roleInfo, (index, user) =>{
         $('#tbody-role').append(`
          <tr valign="baseline">
                <td><input type="checkbox" class="cb_role" data-code="${user['code']}"></td>
                <td>${user['code']}</td>
                <td>${user['role'][0]['roleName']}</td>
                <td>
                     <button class="btn btn-primary" onclick="updateRole('${user['code']}', 'up')">晋升</button>
                     <button class="btn btn-warning" onclick="updateRole('${user['code']}', 'down')">降级</button>
                     <button class="btn btn-danger">禁用</button>
                </td>
          </tr>`)
    })
}

不难发现,这个和ThymeLeaf类似,使用 ` 符号标记HTML代码,然后可以在里面通过${}取JS文件里的变量值。

还有个问题就是它在什么时候调用?笔者采取的是在AJAX请求获取到数据的时候进行渲染

function loadData(){
    $.ajax({
        url: '/admin/user/query',
        type: 'POST',
        data: getUrlParamToJSON(),
        dataType: 'JSON',
        contentType: 'application/json;charset=UTF-8',
        error: (err) =>{
            console.error(err)
            open_toast('fail', "获取用户数据失败!")
        },
        success: (data) =>{
            console.log(data)
            if(data['code'] === 1){
                $('#userTbody').html('')
                // 渲染搜索的标签
                loadSearchTag()
                // 设置渲染延迟
                setTimeout(function (){
                    // 渲染用户信息
                    loadUserInfo(data['response']['usersInfo'])
                    // 渲染分页信息
                    loadPageInfo(data['response']['pageInfo'])
                    // 渲染完毕后关闭加载
                    $('#searchSpinnerPlaceholder').html('')
                    // 设置结果显示
                    $('#TextOfShowResultSum').text(`共有${data['response']['pageInfo'].total}条结果`)
                }, 200)
            }
        }
    })
}

四、两种方式实现筛选查询


思路一:代码限制查询的筛选条件

数据是通过AJAX获取的,而筛选的条件则显示在URL里,所以接下来可以有两种方式实现筛选的功能,一是通过代码修改Service层业务逻辑处理(繁琐),二是通过改写Mapper映射文件SQL语句(推荐),由于笔者是通过MyBatis逆向工程生成的DAO层接口以及实现的映射文件,它提供了原生的一些增删改查接口,所以没有自己写多表查询的SQL(使用的方法1),在Service层通过简单的SQL语句实现查询的条件限制,之后就用方法2进行了优化,这里都做一个记录吧…

实现效果:
在这里插入图片描述
JS关键代码:

/* 选择搜索的条件触发函数 */
$('.nav-search ul li a').click(function () {
    $wrapper = $('#searchWrapper')
    $wrapper.html('')
    const searchType = $(this).text();
    /* 添加地址 */
    if(searchType === '详细地址'){
        $wrapper.html(`<div class="pt-2">
            省份
            <select name="searchProv">
                <option value="*" data-code="*">请选择</option>
            </select>
            市区
            <select name="searchCity">
                <option value="*" data-code="*">请选择</option>
            </select>
            地区
            <select name="searchArea">
                <option value="*" data-code="*">请选择</option>
            </select>
            详细地址
            <input name="searchDetail">
        </div>`)
        loadSearchAddress()
    } else if(searchType === '名称'){
        $wrapper.html('<input id="input-search-text" type="text" name="searchUserName"' +
            ' class="bg-light w-auto mx-1" placeholder="支持正则表达式">')
    } else if(searchType === '性别'){
        $wrapper.html('<select name="searchGender">\n' +
            '<option value="男">男</option>\n' +
            '<option value="女">女</option>\n' +
            '</select>')
    } else if(searchType === '生日'){
        $wrapper.html('从&nbsp;' +
            '<input type="date" name="searchBirthdayBegin" value="2022-02-14">' +
            '&nbsp;至&nbsp;' +
            '<input type="date" name="searchBirthdayEnd" value="2022-02-14">')
    } else if(searchType === '电话'){
        $wrapper.html('<input id="input-search-text" type="text" name="searchPhone"' +
            ' class="bg-light w-auto mx-1" placeholder="支持正则表达式">')
    } else if(searchType === 'ID'){
        $wrapper.html('范围&nbsp;' +
            '<input type="number" name="searchIDBegin" value="1">' +
            '&nbsp;—&nbsp;</div>' +
            '<input type="number" name="searchIDEnd" value="1">\n')
    }
    $('#label-search-type').text(searchType)
})

/* 动态渲染搜索条件的标签 */
function ualert(searchType, searchText, alertStyle) {
    let wrapper = $('<div class="div-search-condition mx-3 col-md-3 pt-2 pb-1 fade show alert alert-' + alertStyle
        + ' alert-dismissible" role="alert" data-type="' + searchType + '" data-text="' + searchText +'">' +
        searchType + ' :' + searchText + '<button type="button" class="btn-close btn-sm pt-3 pb-1" data-bs-dismiss="alert" aria-label="Close">' +
        '</button></div>')

    $('#searchAlertPlaceholder').append(wrapper)
}

/* 添加筛选标签 */
$('#btn-add-search').click(function(){
    let count = $('.div-search-condition').length

    // 限制1: 筛选标签数量 <=3
    if(count >= 3) {
        open_toast('fail', '目前最多支持添加三个标签')
        return ;
    }
    let searchType = $('#label-search-type').text()
    let searchText = ''
    if(searchType === '名称'){
        searchText = $('input[name="searchUserName"]').val()
    } else if(searchType === '性别'){
        searchText = $('select[name="searchGender"]').val()
    } else if(searchType === '生日'){
        searchText = $('input[name="searchBirthdayBegin"]').val() + '到'
            $('input[name="searchBirthdayEnd"]').val()
    } else if(searchType === '详细地址'){
        searchText = $selectProv.val() + '-' + $selectCity.val() + '-'
            + $selectArea.val() + '-' + ($inputDetail.val() === '' ? '*' : $inputDetail.val())
    } else if(searchType === 'ID'){
        searchText = $('input[name="searchIDBegin"]').val() + '到' +
            $('input[name="searchIDEnd"]').val()
    } else if(searchType === '电话'){
        searchText = $('input[name="searchPhone"]').val()
    }
    // 限制2: 筛选内容不为空
    if(searchText == null || searchText === '')
        open_toast('warning', '请输入筛选的条件')
    // 限制3: 筛选类型不能重复
    else if(hasSearchType(searchType)) {
        open_toast('warning', '当前筛选的类型已存在!')
    } else
        ualert(searchType, searchText, 'success')
})

// 搜索功能
$('#btn-start-search').click(function(){

    // 显示加载图标
    let reqUrl = clearURIExceptPageNow()
    // 封装表单信息
    let reqData = []
    $('.div-search-condition').each(function(){
        reqData.push({
            name: $(this).data('type'),
            value: $(this).data('text')
        })
        reqUrl = updateUrlParam(reqUrl, $(this).data('type'), $(this).data('text'))
    })
    // 将页数默认为第一页
    reqUrl = updateUrlParam(reqUrl, 'pageNow', '1')
    // 更换地址
    window.location = reqUrl
    $.ajax({
        url: '/admin/user/search',
        type: 'POST',
        data: JSON.stringify(reqData),
        dataType: 'JSON',
        contentType: 'application/json;charset=UTF-8',
        error: (err) => {
            open_toast('fail', "请求失败!")
        },
        success: (data) =>{
            if(data['code'] === 1) {
                loadUserInfo(data['response']['usersInfo'])
                loadPageInfo(data['response']['pageInfo'])
                $('#searchSpinnerPlaceholder').html('')
            }
        }
    })
})

Controller层:

public String toAdminUserByPageBySome(@RequestBody Map<String, String> paramMap){
    int pageNow = Integer.parseInt(paramMap.getOrDefault("pageNow", "1"));
    PageHelper.startPage(pageNow, 5);
    List<User> usersInfo = userService.getUserByMapPojos(paramMap);
    PageInfo pageInfo = PageUtil.setPageInfo(pageNow, 5, usersInfo);
    return RestResponse.ok(new HashMap<String, Object>(){{
        put("usersInfo", usersInfo);
        put("pageInfo", pageInfo);
    }}).toJSONString();
}

Service层

 public List<User> getUserByMapPojos(Map<String, String> paramMap) {
        UserExample userExample = new UserExample();
        UserExample.Criteria criteria = userExample.createCriteria();
        boolean up = true;
        for (String param : paramMap.keySet()) {
            String value = paramMap.get(param);
            if("orderById".equals(param))
                up = "up".equals(value);
            if ("名称".equals(param)) {
                criteria.andUsernameLike("%" + value + "%");
            }
            else if ("生日".equals(param)) {
                System.out.println(value);
            }
            else if ("性别".equals(param)) {
                if ("男".equals(value))
                    criteria.andGenderEqualTo(2);
                else if ("女".equals(value))
                    criteria.andGenderEqualTo(1);
            }
            else if ("电话".equals(param)) {
                criteria.andPhoneLike("%" + value + "%");
            }
            else if ("详细地址".equals(param)) {
                criteria.andAddressLike("%" + value + "%");
            }
            else if ("ID".equals(param)) {
                String[] v = value.split("到");
                int begin = Integer.parseInt(v[0]);
                int end = Integer.parseInt(v[1]);
                criteria.andIdBetween((long) Math.max(1, begin), (long) Math.max(0, end));
            }
        }

        List<User> users = userMapper.selectByExampleWithBLOBs(userExample);
        if(!up){
            users.sort(new Comparator<User>() {
                @Override
                public int compare(User o1, User o2) {
                    if(o1.getId() < o2.getId())
                        return 1;
                    else if(o1.getId() > o2.getId())
                        return -1;
                    else
                        return 0;
                }
            });
        }
        return users;
    }

DAO层就不放代码了,是MyBatis逆向工程插件的原生代码,想了解其基本使用可参考文章【点击查看】

思路二:通过改写Mapper映射文件实现多表查询+条件限制

实现效果:
在这里插入图片描述
前端部分就不放了,和之前一样是通过AJAX请求数据的,搜索则是直接修改地址栏的URL

Controller层:

package com.uni.controller.admin;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.uni.base.RestResponse;
import com.uni.pojo.Role;
import com.uni.pojo.User;
import com.uni.service.RoleService;
import com.uni.service.UserService;
import com.uni.utils.PageUtil;
import com.uni.utils.ParamUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/admin/role")
public class RoleAdminController {
    @Autowired
    UserService userService;
    @Autowired
    RoleService roleService;
    @PostMapping("/query/all")
    public String getRoleInfo(@RequestBody(required = false) Map<String, String> paramMap) {
        ParamUtil.translate(paramMap);
        int pageNum = Integer.parseInt(paramMap.getOrDefault("pageNow", "1"));
        PageHelper.startPage(pageNum, 5);
        List<User> users = userService.getUserRoleByMapPojos(paramMap);
        List<Role> roles = roleService.queryAllRole();
        PageInfo pageInfo = PageUtil.setPageInfo(pageNum, 5, users);
        return RestResponse.ok(new HashMap<String, Object>(){{
            put("pageInfo", pageInfo);
            put("roleInfo", roles);
        }}).toJSONString();
    }
}

上述代码使用的 ParamUtil工具类是自定义的,就是之前URL编码的问题,内容如下:

package com.uni.utils;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Map;

public class ParamUtil {
    public static Map<String, String> translate(Map<String, String> map) {
        try {
            for (String key : map.keySet()) {
                map.put(URLDecoder.decode(key, "utf-8"),
                        URLDecoder.decode(map.get(key), "utf-8"));
            }
        }catch(UnsupportedEncodingException e){
            e.printStackTrace();
        }
        return map;
    }
}

Service层: 直接调用Mapper接口即可,相比之前的代码简化了许多

@Override
public List<User> getUserRoleByMapPojos(Map<String, String> paramMap) {
    return userMapper.selectCodeWithRole(paramMap);
}

DAO层:UserMapper.xml 目前笔者对角色只做了角色名称和用户账号进行了筛选

  <select id="selectCodeWithRole" parameterType="Map" resultMap="userWithRole">
    SELECT user.code, r.id, r.roleName
    FROM v2_user AS user
           LEFT JOIN v2_user_role AS ur
                     ON user.id = ur.userId
           LEFT JOIN v2_role AS r
                     ON ur.roleId = r.id
    <where>
          <foreach collection="map" item="value" index="key" separator="AND">
            <if test="key == 'roleName'">
                r.roleName = #{value}
            </if>
            <if test="key == 'code'">
                user.code LIKE CONCAT('%',#{value}, '%')
            </if>
          </foreach>
    </where>
  </select>

  <resultMap id="userWithRole" type="com.uni.pojo.User">
    <result column="code" property="code"/>
      <collection property="role" ofType="com.uni.pojo.Role">
        <result column="id" property="id"/>
        <result column="roleName" property="roleName"/>
      </collection>
  </resultMap>

这里用到了用户角色表(中间表)进行角色的查询,三张表的结构如下:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值