项目整体做法 88

这篇博客详细介绍了使用Spring Boot实现用户登录、用户首页跳转、左侧菜单列表展现、用户列表模块以及商品分类模块的功能。包括用户登录的pojo、SysResult对象创建、登录实现、用户状态修改、用户删除、密码加密存储、数据回显、商品分类列表的分页查询和状态修改等操作,全程使用MyBatis-Plus进行数据库操作,部分场景下利用MP的分页查询功能简化代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

接口文档:https://blog.youkuaiyun.com/qq_16804847/article/details/116671831?spm=1001.2014.3001.5502

一、用户登录模块 业务名user——构建user层级代码

1、用户登录实现

1)编辑pojo-根据接口文档和数据库表结构

package com.jt.pojo;


import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;


@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")//让pojo对象与表名一一映射
public class User extends BasePojo {
    @TableId(type = IdType.AUTO)//标识主键
    private Integer id;
   // @TableField("username") //如果属性与字段名称一致(包含驼峰规则),则注解可以省略
    private String username;
    @TableField("password")
    private String password;
    private String phone;
    private String email;
    private Boolean status;     //true 正常 false 停用
}

2)创建SysResult对象-vo层 全局响应用户的-根据接口文档

原因:用户点击登录后若是用户名和密码不正确要告诉/响应用户,不能让用户一直点;不论对错都要告知用户

写法:里面写静态方法:可以通过类名直接调用,方便返回给用户   注意重载

package com.jt.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class SysResult implements Serializable {
    private Integer status;//状态信息 200 成功  201 失败
    private String msg;//服务器返回的提示信息
    private Object data;/**注意:服务器返回值数据是Object类型*/

//响应数据类型是 SysResult对象.方法(),所以静态方法的返回值是 SysResult对象
 /**注意:方法的参数列表有参数,一定要在方法里return 如重载2和3*/
    /*一、登录失败*/
    public static SysResult fail(){
        return new SysResult(201,"业务调用失败!",null);//成功才返回业务数据,失败不返回
    }

    /*二、登录成功——用户登录成功的vo对象需要重载:方便按照用户不同的规则进行调用
     *    下面方法里的参数列表里写了传的参数(比如重载2和3),那么就要把传进来的参数写进return里return给用户*/
//重载1 用户需求:有的用户要求 不返回提示信息,不返回业务数据
    public static SysResult success(){
        return new SysResult(200,"业务调用成功!",null);
    }
//重载2  用户需求:有的用户要求 不返回提示信息,返回业务数据
    /*public static SysResult success(String msg){  //用户需求:有的用户要求 返回提示信息,不返回业务数据
        return new SysResult(200,msg,data);*///这个方法不能和下面的一个方法一起写,不构成重载Object类比String大且包含它
    public static SysResult success(Object data){
        return new SysResult(200,"业务调用成功!",data);
    }
//重载3 用户需求:有的用户要求 返回提示信息,同时要求返回业务数据
    public static SysResult success(String msg, Object data){
        return new SysResult(200,msg,data);
    }
}

3)用户登录实现

代码编辑

1>控制层 

用户登录业务逻辑

当用户完成登录时,前端系统会向后端进行访问.后端服务器经过数据库查询.如果查询正确 则返回token密钥信息. 如果查询失败 说明用户名和密码错误 返回null

在这里插入图片描述

    /**接收前端的参数传给service层*/
    @PostMapping("/login")
    public SysResult login(@RequestBody User user){
        /*由接口文档可知返回的业务数据是密钥token信息,所以
        由service层返回来的结果一定是String token,用String token接*/
        String token = userService.login(user);
    /**查询完service层传回来的数据 进行判断*/
        if(token == null){
            return SysResult.fail();
        }
        return SysResult.success(token);
    }

2>业务层

业务逻辑:

1、用户传过来的密码是明文,数据库里存的密码是密文,所以需要在业务层将用户传的明文变为密文--用各种算法(此处用的是md5加密算法)

2、根据用户名和密码查询数据库

3、判断数据库查询的返回值userDB是否有值,如果有值,说明用户名和密码正确,按要求生成并返回给控制层一个独一无二的token密钥信息--用UUID.randomUUID 并用toString将其转化为字符串

知识点:Maven如何保证安全性/防止被篡改/如何将明文密码转化为为密文?
可以将密码明文,转化为密文的几种算法:sha1/md5算法/md5hash
    常识性问题:
    1.如果数据相同 算法相同 结果必然相同.
    2.如果数据不同 算法相同 结果可能相同. hash碰撞问题.

md5加密算法:可以产生出一个128位(16字节)的散列值
          规则:md5加密算法的数据可以穷举查询.但是不可以被破解.

sha1算法:SHA-1可以生成一个被称为消息摘要的160位(20字节)散列值,散列值
        通常的呈现形式为40个十六进制数。

hash算法:1.对相同数据,采用相同的hash算法, 问: 结果是否相同? 答:必定相同
                     不同的数据,采用相同的hash算法, 问: 结果是否不同? 答:可能相同
                     会产生 hash碰撞问题!!!
        
                   关于hash碰撞说明:hash碰撞 不能避免,但可以有效地降低概率:比如密码需要大量

                   的数据才能碰撞破解成功,我们可以增大基数;或者减少次数-设置次数为3次数以内
               2、结论:结果相等,数据大概率没有被篡改;
                   结果不相等,数据必定被篡改了

String login(User user);
//一、用户登录实现
    @Override
    public String login(User user) {
        String password = user.getPassword();//利用传进来的user对象获取到用户输的明文密码

        byte[] bytes = password.getBytes();//DigestUtils.md5DigestAsHex(需要一个byte字节数组)
        //1.(利用工具API)将密码加密  因为数据库里密码都是密文,只有转换成密文才能进行查询业务
        String md5Password = DigestUtils.md5DigestAsHex(bytes);//或者将 password.getBytes()直接写入
        user.setPassword(md5Password);//将转变成的密文密码赋给user对象,去传给mapper层查询
        //2.根据用户名和密码查询数据库 ----结果只有一个,用User接收
        User userDB = userMapper.findUserByUP(user);
        //3.判断mapper层传回来的userDB是否有值,userDB不一定有值
        if(userDB == null){
            //用户名和密码查询错误,返回null
            return null;
        }
        //程序走到这里,说明用户名和密码正确 我们定义一个token并返回token字符串--
        String token = UUID.randomUUID().toString();
        return token;
    }

3>持久层

 User findUserByUP(User user);

映射文件:

<mapper namespace="com.jt.mapper.UserMapper">
    <select id="findUserByUP" resultType="User">
        select * from user    /*工作时不能写*,要全部写出来*/
            where username=#{username}
            and   password = #{password}
    </select>
</mapper>

效果图

2、用户首页跳转(路由)

修改路由信息两步:上下两步都不能少(路径:router/index.js中)

效果图

二、左侧菜单列表展现 业务名rights——构建rights层级代码

点此跳转   左侧菜单列表展现 总结

*左侧菜单列表展现 (有两级菜单 同表一对多 自关联-关联查询 各级菜单都在一个表里 )

1>编辑pojo (一对多,同表封装子级 为children)

package com.jt.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.util.List;

/**
 * @author 刘昱江
 * 时间 2021/2/18
 */
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@TableName("rights")
public class Rights extends BasePojo {
    @TableId(type = IdType.AUTO)
    private Integer id;//父级(一级)ID
    //@TableField("name")
    private String name;
    private Integer parentId;
    private String path;
    private Integer level;
    @TableField(exist = false)   //当前属性不参与MP操作
    private List<Rights> children; //List<Rights>父级  一对多 一个父级有多个子集列表
}

2>控制层

//三、左侧菜单列表展现
    @GetMapping("/getRightsList")
    public SysResult getRightsList(){
        //根据接口文档 得data返回权限List集合 所以用List集合接
        List<Rights> list = rightsService.getRightsList();
        return SysResult.success(list);
    }

3>业务层

List<Rights> getRightsList();
    @Override
    public List<Rights> getRightsList() {
        return rightsMapper.getRightsList();

    }

4>持久层

List<Rights> getRightsList();

映射文件:

规则:

要想查询谁的子级菜单,谁就要当父级id

原理说明: 左侧菜单划分为3级.但是显示时只显示2级.第三级在页面右半侧展示

数据库表中level

一级菜单获取: select * from rights where parent_id = 0

二级菜单获取: select * from rights where parent_id = 一级ID

三级菜单获取: select * from rights where parent_id = 二级ID

业务逻辑:

 思路: 获取1-2级的菜单信息.
     * 一级菜单: parent_id = 0
     * 二级菜单: parent_id = 一级ID
     * 将二级菜单数据,封装到一级菜单的children属性
     * 方案一:  通过代码 先查询一级数据,再查询二级数据,之后完成封装
     * 方案二:  通过关联查询,利用mybatis实现数据一对多的封装.

此处用方案二

自关联---各级列表都在一个表里

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.jt.mapper.RightsMapper">

<!--//三、左侧菜单列表展现 SQL:select * from rights p       父级
                                    left join rights c    子级
                                        on p.id = c.parent_id   到此 罗列出了所有的父子关系
                                            where p.parent_id =0   得出了一级二级的数据
                              (一张表看作两张表  进行多表查询)
                          或者SQL:select * from  (SELECT * FROM rights WHERE parent_id = 0一级菜单查询好了得出了一个结果,看成一个表p) p
                                         left join rights c  关联二级
                                              on p.id = c.parent_id 父id=子的parent_id   得出了一级二级的数据
                                    可以改成
                                    select * from  (SELECT * FROM rights WHERE parent_id = 0一级菜单查询好了得出了一个结果,看成一个表) p
                                         left join (select * from rights) c  关联二级
                                              on p.id = c.parent_id        父id=子的parent_id
                                              -->
    <select id="getRightsList" resultMap="rightsRM">
        /*SELECT * FROM
            (SELECT * FROM rights WHERE parent_id = 0) p
	    LEFT JOIN
            (SELECT id c_id,NAME c_name,parent_id c_parent_id,
                    path c_path,LEVEL c_level,created c_created,
                     updated c_updated FROM rights) c
            ON p.id = c.c_parent_id*/
            /*c_id 是起的别名,因为一级查出来的*/

          /*  select * from rights p
                                    left join rights c
                                        on p.id = c.parent_id
                                            where p.parent_id =0*/
          select p.*,c.id c_id,c.name c_name,c.parent_id c_parent_id,
               c.path c_path,c.level c_level,c.created c_created,
               c.updated c_updated
                             from rights p
                                    left join rights c
                                        on p.id = c.parent_id
                                            where p.parent_id =0
                                           /* 这是连接查询,今天day11老师的是笛卡尔积查询
                                           自关联要把两个表的字段名区分开,起别名方法 c.id  和c.id c_id区分*/
    </select>

    <resultMap id="rightsRM" type="Rights" autoMapping="true">
        <id column="id" property="id"/>

        <!--一对多封装-->
        <collection property="children" ofType="Rights" autoMapping="true" columnPrefix="c_"><!--columnPrefix="c_"指定字段映射的的前缀-->
            <id column="id" property="id"/>
         <!--  <result column="c_name" property="name"/>   &lt;!&ndash;&lt;!&ndash;这样下面都不用写了&ndash;&gt;&ndash;&gt;
            <result column="c_parent_id" property="parentId"/>
            <result column="c_path" property="path"/>
            <result column="c_level" property="level"/>
            <result column="c_created" property="created"/>
            <result column="c_updated" property="updated"/>-->
        </collection>
    </resultMap>

</mapper>

效果图:

三、用户列表模块 业务名user——构建user层级代码,——已经构建了

1、跳转到用户列表(路由嵌套)

规则:

1、如果路由中有父子的嵌套关系.则需要通过children属性标识

2、如果需要进行路由的嵌套. 则需要在路由定义的位置,指定路由的填充位 

业务说明
说明: 当用户点击用户列表之后,需要在跳转到本页右侧中,不覆盖原来的页面
url地址: /user
组件地址: user/user.vue组件

 2、修改路由信息(修改index.js中路由两步都不能少)
路径:router/index.js中

 效果图:

2、*用户列表  列表展现(携带分页对象PageResult)

返回值要求返回PageResult对象,那么 在控制层 从业务层返回来的数据我们就用PageResult对象接收一下,然后PageResult对象当作参数

 pageResult= userService.findUserList(pageResult);
        return SysResult.success(pageResult);

1>控制层

根据接口文档,控制层接收的参数是PageResult对象,响应参数需要携带分页对象PageResult

1>所以先编辑PageResult对象

package com.jt.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class PageResult implements Serializable {
    private String query;       //用户查询的数据
    private Integer pageNum;    //查询页数
    private Integer pageSize;   //查询条数
    private Long total;         //查询总记录数
    private Object rows;        //分页查询的结果
}

2>编辑controller层

    @GetMapping("/list")
    public SysResult findUserList(PageResult pageResult){
        //根据接口文档请求案例:接收到的PageResult只有用户
        // 传过来的query=查询关键字&pageNum=1&pageSize=10(三个)
        //query 输入框   pageNum用户要查询的页数  pageSize 每页的条数

        //因为要求返回的PageResult对象有五个参数,在业务层将(+2)
        //total rows这两个 先获取 再通过pageResult.set(total).set(rows)添加
       pageResult= userService.findUserList(pageResult);
        return SysResult.success(pageResult);
    }

2>业务层

PageResult findUserList(PageResult pageResult);

业务逻辑:

1)在业务层加上这两个前端未传过来的,却要返回给控制层用户的参数

totalLong查询总记录数不能为null
rowsObject分页查询的结果不能为null

2)将用户列表进行分页查询的结果rows和总记录数total 添加到PageResult并返回

分页查询原sql:

select *from emp limit 0,3--从第条开始,展示3条记录(3是条数)--前三条 

                                                                                写 0是从第0+1=1条开始

* 分页查询的Sql:  假如用户想查询: 每页十条 然后用户选择第几页
     *  SELECT * FROM USER LIMIT 起始位置,查询条数
     *  第一页: 每页10条
     *  SELECT * FROM USER LIMIT 0,10
     *  第二页: 每页10条
     *  SELECT * FROM USER LIMIT 10,10
     *  第N页:
     *  SELECT * FROM USER LIMIT (n-1)*10,10

    @Override
    public PageResult findUserList(PageResult pageResult) {
        //1、获取total 总记录数(+1)
        //select count(1)from emp
        Long total = userMapper.findTotal();//查询操作 查询总记录数


        //2、获取rows 分页查询的结果(+1)
        //将用户传过来的 查询的页数PageNum 查询的条数PageSize 和搜索框query获取
        //并当作参数传进获取rows的findUserList(,,)方法里
        //要得到分页查询的结果,需要起始位置 查询条数  SELECT * FROM USER LIMIT 起始位置,查询条数
        /*1)用户选了 每页/条和第几页后——我们可以获取到那页起始位置*/
        int start = (pageResult.getPageNum()-1)*pageResult.getPageSize();//start 每页的起始位置
        //pageResult.getPageNum()--n用户选择的查询第几页PageNum  pageResult.getPageSize()--用户选择的每页条数
        /*2)用户选择的每页条数 获取 PageSize */
        int size =pageResult.getPageSize();
        /*3)用户搜索框输入内容的获取  Query*/
        //注意;用户搜索框输入的可能有值,也可能是null或空串,写sql时注意动态sql
        String query=pageResult.getQuery();
        //并当作参数传进获取rows的findUserList(,,)方法里 获取rows
        List<User> rows = userMapper.findUserList(start,size,query);
        return pageResult.setTotal(total).setRows(rows);
    }

3>持久层

 @Select("select count(1) from user")
    Long findTotal();

    List<User> findUserList(@Param("start") int start, @Param("size") int size, @Param("query") String query);

映射文件:动态sql(模糊查询)___带limit的sql不能用条件构造器mybatis-plus替代,只能写映射文件

    <select id="findUserList" resultType="User">
        select * from user
        <where>
            <if test="query !=null and query !=''">username like "%"#{query}"%"</if>
        </where>
        limit #{start},#{size}
    </select>

效果图:

3、用户状态修改 (前端restFul风格传参 +注解修改)

业务逻辑 :

说明: 点击页面开关,修改数据表中的status
知识点讲解:
1. 数据库中没有boolean类型 可以使用数值类型代替 true 代替数值1, false 代替数值0
2. 数据库中可以使用tinyint类型代替boolean. 并且2种类型可以自动转化.

1>控制层

    @PutMapping("/status/{id}/{status}")
    public SysResult updateStatus(User user){
        userService.updateStatus(user);
        return SysResult.success();
    }

2>业务层

void updateStatus(User user);
    @Override
    public void updateStatus(User user) {
        user.setUpdated(new Date());
        userMapper.updateStatus(user);
    }

3>持久层(注解修改 无映射文件)

    @Update("update user set status=#{status}," +
            "updated=#{updated} where id=#{id}")
    void updateStatus(User user);

4、用户删除操作(前端restFul风格传参 +注解删除)

1>控制层

    @DeleteMapping("/{id}")
    public SysResult deleteUserById(@PathVariable Integer id){
        userService.deleteUserById(id);
        return SysResult.success();
    }

2>业务层

 void deleteUserById(Integer id);
    @Override
    public void deleteUserById(Integer id) {
        userMapper.deleteUserById(id);
    }

3>持久层(注解方式 无映射文件)

    @Delete("delete from user where id=#{id}")
    void deleteUserById(Integer id);

修改前端bug代码

5、需要注意--用户新增操作 用户的密码在业务层加密后才能往数据库存(增 前端封装成对象传+注解新增)

1>控制层

    @PostMapping("/addUser")
    public SysResult saveUser(@RequestBody User user){
        userService.saveUser(user);
        return SysResult.success();
    }

2>业务层

 void saveUser(User user);

业务逻辑:将用户输入的明文密码转变为密文才能存入数据库;

                新增操作必须在业务层实现类用对象.set().set().set()……入库

    @Override
    public void saveUser(User user) {
        //将密码加密处理
        String password = user.getPassword();//获取密码
        String md5Pass = DigestUtils.md5DigestAsHex(password.getBytes());//加密处理

        Date date = new Date();
        user.setPassword(md5Pass)
                .setStatus(true)
                .setCreated(date)
                .setUpdated(date);
        userMapper.saveUser(user);
    }

3>持久层(注解新增 无映射文件)

 @Insert("insert into user value (null,#{username},#{password},#{phone},#{email},#{status},#{created},#{updated})")
    void saveUser(User user);

6、需要注意--用户修改操作-修改哪个数据,哪个数据就应该被回显

1、数据回显 -根据ID查询用户信息,并要将查到的数据user返回 让用户看到

1>控制层

    @GetMapping("/{id}")
    public SysResult findUserById(@PathVariable Integer id){
        User user = userService.findUserById(id);
        return SysResult.success(user);//将查到的数据返回 让用户看到
    }

2>业务层

User findUserById(Integer id);
    @Override
    public User findUserById(Integer id) {

        return userMapper.findUserById(id);
    }

3>持久层(注解查询 无映射文件)

    @Select("select * from user where id=#{id}")
    User findUserById(Integer id);

2、用户修改-根据用户ID更新数据

1>控制层

    @PutMapping("/updateUser")
    public SysResult updateUser(@RequestBody User user){
        userService.updateUser(user);
        return SysResult.success();
    

2>业务层

void updateUser(User user);
    @Override
    public void updateUser(User user) {
        user.setUpdated(new Date());
        userMapper.updateUser(user);
    }

3>持久层(注解修改 无映射文件)

    @Update("update user set phone=#{phone},email=#{email},updated=#{updated} where id=#{id}")
    void updateUser(User user);

四、商品分类模块 业务名itemCat——构建构建itemcat层级代码--使用MP 不需要映射文件了

1、跳转到商品分类(路由嵌套)

业务说明
说明: 当用户点击商品分类之后,需要在跳转到本页右侧中,不覆盖原来的页面
url地址: /itemCat
组件地址: items /ItemCat.vue组件

在这里插入图片描述

在这里插入图片描述

效果图

2、 *商品分类  列表展现(有三级菜单  同表一对多)

***************************这里业务逻辑必须看懂,不然公司里没法写*********************************

1>编辑pojo (一对多,同表封装子级 为children)

package com.jt.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.util.List;

/**
 * @author 刘昱江
 * 时间 2021/3/26
 */
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@TableName("item_cat")
public class ItemCat extends BasePojo {

    @TableId(type = IdType.AUTO) //主键自增
    private Integer id;         //定义主键
    private Integer parentId;   //定义父级菜单 开启驼峰规则映射
    private String name;        //分类名称
    private Boolean status;     //分类状态 0 停用 1 正常
    private Integer level;      //商品分类等级  1 2 3
    @TableField(exist = false)   //当前属性不参与MP操作
    private List<ItemCat> children; //是业务数据 不是数据库字段
}

2>控制层     

    @GetMapping("/findItemCatList/{level}")
    public SysResult findItemCatList(@PathVariable Integer level){
        List<ItemCat> itemCatList = itemCatService.findItemCatList(level);
        return SysResult.success(itemCatList);
    }

3>业务层

 List<ItemCat> findItemCatList(Integer level);

业务逻辑:先查一级菜单-根据一级菜单查询二级菜单-根据二级菜单查询三级菜单

    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        long startTime = System.currentTimeMillis();
        QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
        /**查询一级菜单*/
        queryWrapper.eq("parent_id",0);
        List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
//***************************************************************************************
        for (ItemCat oneItemCat : oneList){
            //获取一级菜单的id
            int oneId=oneItemCat.getId();
            //清空多余的条件
            queryWrapper.clear();
            /**根据一级菜单查询二级菜单*/
            queryWrapper.eq("parent_id",oneId);
            List<ItemCat> twoList = itemCatMapper.selectList(queryWrapper);
//***************************************************************************************
            for (ItemCat twoItemCat : twoList){
                //获取二级菜单的id
                int twoId = twoItemCat.getId();
                //清空多余的条件
                queryWrapper.clear();
                /**根据二级菜单查询三级菜单*/
                queryWrapper.eq("parent_id",twoId);
                List<ItemCat> threeList = itemCatMapper.selectList(queryWrapper);
//***************************************************************************************
                /**将三级数据封装到二级数据中*/
                twoItemCat.setChildren(threeList);
            }
            /**将二级数据封装到一级数据中*/
            oneItemCat.setChildren(twoList);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("耗时:"+(endTime - startTime));
        return oneList;
    }

3>业务层代码优化---将数据保存到Map集合中

 List<ItemCat> findItemCatList(Integer level);

优化策略:

数据结构: Map<parentId, 当前父级下的子级>
例子:
Map<0, 所有的一级菜单>
Map<一级ID, 当前一级下的二级菜单>
Map<二级ID, 当前二级下的三级菜单>

设计的优势:
如果将数据保存到Map集合中,则可以有效的降低数据库的访问的次数. 提高查询效率.


                                        //优化业务层
/**一、封装Map集合*/
    /**
     * 思路:
     * 1.判断map集合中是否存在key(parentId)
     * 2.如果key(parentId) 不存在 准备一个新list集合,将自己作为第一个元素添加
     * 3.如果key(parentId) 存 在  获取list集合,将自己追加进去这个list集合
     */
    public Map<Integer, List<ItemCat>> getMap() {//数据结构: Map<parentId, 当前父级下的子级菜单>
        //新建new一个初始的map集合
        Map<Integer, List<ItemCat>> map = new HashMap<>();
        //查询数据库的所有的(各级)菜单--无条件,条件构造器就是写条件的,没有就不用new了,直接写null条件
        List<ItemCat> list = itemCatMapper.selectList(null);
        for (ItemCat itemCat : list) { //遍历所有的数据
            int key = itemCat.getParentId();
            if (map.containsKey(key)) {    //true 有数据
                map.get(key).add(itemCat);//3.获取list集合,将自己追加进去这个list集合 add(itemCat)
                //itemcat是循环中的变量
                //map.get(key)的结果是获取到了一个list集合,(V 		get(Object key) 根据指定的key返回对应的value,如果不存在,返回null)往list集合里添加元素用add
            } else {                        //false 没有数据
                List<ItemCat> value = new ArrayList<>();
                value.add(itemCat);//2.准备一个新list集合,将自己作为第一个元素添加 add(itemCat)
                map.put(key, value);
            }
        }
        return map;
    }
//*****************************************************************************************
/** 二、获取三个级别的菜单*/
    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        long startTime = System.currentTimeMillis();
        /*1.获取数据信息————上面封装的map集合里已经有数据,就不用查询数据库了*/
        //调用getMap()方法,即可获取到数据,数据返回的类型是map--Map <Integer,List<ItemCat>>接收
        Map<Integer, List<ItemCat>> map = getMap();
        /*2.判断level,获取各级菜单数据*/
        //get方法:V 		get(Object key) 根据指定的key返回对应的value,如果不存在,返回null
        if (level == 1) { //只获取一级菜单数据;一级的父级parentId是0 return map.get(0);
            return map.get(0);
        }
        if (level == 2) { //**获取1级和2级菜单数据
            return getTwoList(map);
        }
        //***获取1级和2级和3级菜单数据
        List<ItemCat> list = getThreeList(map);
        long endTime = System.currentTimeMillis();
        System.out.println("业务执行耗时:"+(endTime - startTime)+"毫秒");
        //获取1-2-3级数据
        return list;
    }

 //*****************************************************************************************
/*    根据上面的getTwoList方法
     if(level == 2){ //获取1-2级数据
            return getTwoList(map);
        }
        生成*/
    private List<ItemCat> getTwoList(Map<Integer, List<ItemCat>> map) {
        //**目的:拿到一级和二级菜单数据**//
        //**1、先拿一级菜单数据**//
        List<ItemCat> oneList = map.get(0);
        //**再遍历得一级菜单id 查询二级菜单
        for (ItemCat oneItemCat : oneList) {
            //**获取一级菜单的id
            int key = oneItemCat.getId();  //id 是map中的key
            //**2、根据一级菜单查询二级菜单**//
            List<ItemCat> twoList = map.get(key);
            //**3、将二级数据封装到一级中**//
            oneItemCat.setChildren(twoList);
        }
        return oneList;//最终拿到的都是一个完整得包含二级菜单的 一级集合
    }

/* 根据上面的getThreeList方法
           if(level == 3){ //获取1级和2级和3级菜单数据
            return getThreeList(map);
        }
        生成*/
    private List<ItemCat> getThreeList(Map<Integer, List<ItemCat>> map) {
        //***目的:拿到一级和二级和三级菜单数据***//
        //***1、先拿一级和二级菜单数据***//
        List<ItemCat> oneList = getTwoList(map);
        //***再遍历  得一级菜单id 查询二级菜单;得二级菜单id 查询三级菜单
        for (ItemCat oneItemCat : oneList) {
            //***2、根据一级菜单查询二级菜单***//
            List<ItemCat> twoList = oneItemCat.getChildren();
            //二级菜单可能为空,那就没有三级id了;所以要进行判断
            if (twoList == null) { //说明 该一级下没有二级数据.所以跳过本次循环进入下一次
                continue;
            }
            for(ItemCat twoItemCat : twoList){
               //获取二级菜单的id
                int key = twoItemCat.getId();
                //***3、根据二级菜单查询三级菜单***//
                List<ItemCat> threeList = map.get(key);
                //***4、将三级数据封装到二级中***//
                twoItemCat.setChildren(threeList);
            }

        }
        return oneList;//最终拿到的都是一个完整得包含二级菜单和三级菜单的 一级集合
    }

4>持久层(用MP操作 持久层无代码)

效果图

3、商品分类状态修改操作

1>控制层

    @PutMapping("/status/{id}/{status}")
    public SysResult updateStatus1(ItemCat itemCat){
        itemCatService.updateStatus1(itemCat);
        return SysResult.success();
    }

2>业务层

 void updateStatus1(ItemCat itemCat);
@Override
@Transactional
public void updateStatus1(ItemCat itemCat) {
    itemCatMapper.updateById(itemCat);
}

3>持久层(使用了MP无代码)

 4、商品分类新增  操作

1>控制层

    @PostMapping("/saveItemCat")
    public SysResult saveItemCat(@RequestBody ItemCat itemCat){
        itemCatService.saveItemCat(itemCat);
        return SysResult.success();
    }

2>业务层

 void saveItemCat(ItemCat itemCat);
    @Override
    @Transactional
    public void saveItemCat(ItemCat itemCat) {
        itemCat.setStatus(true);//创建时间和修改时间已经自动填充了
        itemCatMapper.insert(itemCat);
    }

3>持久层(使用了MP无代码)

5、商品分类修改 操作

1>控制层

    @PutMapping("/updateItemCat")
    public SysResult updateItemCat(@RequestBody ItemCat itemCat){
        itemCatService.updateItemCat(itemCat);
        return SysResult.success();
    }

2>业务层

void updateItemCat(ItemCat itemCat);
    @Override
    @Transactional
    public void updateItemCat(ItemCat itemCat) {
        itemCatMapper.updateById(itemCat);
    }

3>持久层(使用了MP无代码)

6、需要注意-商品分类删除 操作

1>控制层

    @DeleteMapping("/deleteItemCat")
    public SysResult deleteItemCat(ItemCat itemCat){
        itemCatService.deleteItemCat(itemCat);
        return SysResult.success();
    }

2>业务层

void deleteItemCat(ItemCat itemCat);

业务逻辑:

当用户删除商品分类的时候,应该将它的子级数据全部删除.否则子级就永远在数据库中,没人调用

规则:

删除的思路: 1.判断是否为3级 如果是直接删除

                    2.判断是否为2级 如果是2级,先删除三级,再删除2级

                    3.如果是1级,先查询2级,再删除3级、2级和1级

    @Override
    @Transactional
    public void deleteItemCat(ItemCat itemCat) {
        //itemCatMapper.deleteById(itemCat);  看错误写法,注意删除其子集
        if (itemCat.getLevel() == 3) { //判断是否为3级 如果是直接删除
            itemCatMapper.deleteById(itemCat.getId());
            return;//return的作用:执行完 如果itemCat.getLevel() == 3 就不往下执行了
        }
        if (itemCat.getLevel() == 2) {//判断是否为2级 如果是2级,先删除三级,再删除2级
            int twoId = itemCat.getId();
            QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("parent_id", twoId);

            //List<ItemCat> threeList = itemCatMapper.selectList(queryWrapper); 查询三级数据
            itemCatMapper.delete(queryWrapper);    //删除三级数据
            itemCatMapper.deleteById(twoId);       //删除二级数据
            return;//return的作用:执行完 如果itemCat.getLevel() == 2 就不往下执行了
        }

        //执行到这里 只剩下一级菜单(执行到这里,要删除的菜单只能是一级菜单),不需要判断
        //1、应该先根据一级id查询二级id
        int oneId = itemCat.getId();
        QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id", oneId);
        List twoIdList = itemCatMapper.selectObjs(queryWrapper);//只查询parent_id是oneId的主键信息(即二级菜单的id)
        //2、对二级菜单进行判断
        if (twoIdList.size() == 0) {
            //如果没有二级数据,则直接删除一级信息
            itemCatMapper.deleteById(oneId);
        } else {
            //表示有二级,可以删除三级
            //思考: 如何快速删除 三级/二级/一级??
            //Sql: delete from item_cat where parent_id in (xx,xx,xx --二级id)  //删三级
            //     or id in (xxx,xxx,xx,--二级id)                                       //删二级
            //     or id  = oneId--一级id                                               //删一级
            queryWrapper.clear();
            queryWrapper.in("parent_id", twoIdList)  //三级菜单   可能有多个,所以用in
                    .or() //默认是and,所以我们要手动添加.or()
                    .in("id", twoIdList)   //二级菜单  可能有多个,所以用in
                    .or()
                    .eq("id", oneId);     //一级菜单   用eq,因为一级只有一个
            itemCatMapper.delete(queryWrapper);
        }
    }

(自己拓展递归写法怎么写)

    public int aa(int a){
    if(a > 100){
        return a;
    }else{
        return aa(a++);
    }
 }

3>持久层(使用了MP无代码)

五、商品列表模块 业务名item——构建构建item层级代码--使用MP 不需要映射文件了     

商品列表的表item里 里面还包含了item_desc表 所以还要多写一个pojo对象和ItemDescMapper接口,并注入ItemServiceImpl.

编辑ItemDesc

在这里插入图片描述

表关系: 一个商品对应一个商品详情, item.id = item_desc.id 商品表的Id和详情表的ID是一致的.

在这里插入图片描述

 在这里插入图片描述

1、跳转到商品列表(路由嵌套)

 效果图

2、跳转到商品新增(路由嵌套)

效果图

3、*商品列表  列表展现(MP分页查询)(携带分页对象PageResult)

 返回值要求返回PageResult对象,那么 在控制层 从业务层返回来的数据我们就用PageResult对象接收一下,然后PageResult对象当作参数

 pageResult= userService.findUserList(pageResult);
        return SysResult.success(pageResult);

1>控制层

package com.jt.controller;

import com.jt.service.ItemService;
import com.jt.vo.PageResult;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@CrossOrigin
@RequestMapping("/item")
public class ItemController {

    @Autowired
    private ItemService itemService;

    /**
     * 业务需求: 商品列表展现
     * URL地址: /item/getItemList?query=&pageNum=1&pageSize=10
     * 参数:    pageResult对象
     * 返回值:  SysResult(pageResult对象)——总的SysResult对象包裹着pageResult对象返回
     */
    @GetMapping("/getItemList")
    public SysResult getItemList(PageResult pageResult){//3个参数
//传进来一个PageResult,返回来的依然是个PageResult——传进来的是三个参数的PageResult,返回的是加了两个参数的PageResult
        pageResult = itemService.getItemList(pageResult); //3+2     业务层
        return SysResult.success(pageResult);
    }

}

2>业务层(mybatise-plus 分页的规则  固定API)

PageResult getItemList(PageResult pageResult);
package com.jt.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jt.mapper.ItemDescMapper;
import com.jt.mapper.ItemMapper;
import com.jt.pojo.Item;
import com.jt.vo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.List;

@Service
public class ItemServiceImpl implements ItemService{

    @Autowired
    private ItemMapper itemMapper;
    @Autowired
    private ItemDescMapper itemDescMapper;

    /**
     * Sql: select * from item limit 起始位置,显示条数
     * 参数说明:
     *      1.page 用来封装分页参数 第几页/每页多少条
     *      2.queryWrapper 查询的条件构造器
     * @param pageResult
     * @return
     */
    @Override
    public PageResult getItemList(PageResult pageResult) {
        //1.构建模糊查询
        boolean flag = StringUtils.hasLength(pageResult.getQuery());//判断用户传过来的是否为空,如果为空,下面的条件构造器就无作用
        QueryWrapper<Item> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(flag,"title", pageResult.getQuery());

//mybatise-plus 分页的规则  固定API
        //2.定义分页对象   2
        IPage<Item> page = new Page<>(pageResult.getPageNum(),pageResult.getPageSize());
        //pageResult.getPageNum()页数    pageResult.getPageSize()条数
        //page的参数由原来的页数/条数 经过业务调用添加了 总记录数和分页的结果
        page = itemMapper.selectPage(page,queryWrapper);//2+2
        long total = page.getTotal();   //获取总数
        List<Item> rows = page.getRecords(); //获取分页的结果
//通过page把我们要的数据先获取到,然后传给我们

        return pageResult.setTotal(total).setRows(rows);
    }
}

3>编辑MP分页查询使用需要的插件(分页配置类)

说明: MybatisPlus可以实现跨数据库. 用户操作的是对象,但是由MP动态的生成对应的Sql语句.
         如果需要使用分页,则需要额外的指定数据库版本. 需要编辑配置类.

//作用:告诉mybatis-plus现在  数据库的类型

@Configuration  //标识配置类
public class MybatisPlusConfig {
    //将自定义的对象交给Spring容器管理 告诉MP 使用的mysql/mariadb数据库
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //自己定义了一个拦截器
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor
                (new PaginationInnerInterceptor(DbType.MARIADB));//MARIADB数据库版本
      /*interceptor.addInnerInterceptor
                (new PaginationInnerInterceptor(DbType.ORACLE));//ORACLE数据库版本*/
      /*interceptor.addInnerInterceptor
                (new PaginationInnerInterceptor(DbType.ORACLE_12C));//ORACLE_12C数据库版本*/
      /*interceptor.addInnerInterceptor
                (new PaginationInnerInterceptor(DbType.SQL_SERVER));//SQL_SERVER数据库版本*/
        return interceptor;
    }
}

效果图

 变成

4、商品新增实现

关于商品信息说明

说明: item的基本信息与ItemDesc的详情信息 可以采用对象的方式进行包裹.
例如: {item: item的数据信息, itemDesc: itemDesc的数据信息}

在这里插入图片描述

 关于商品新增接口文档说明

  • 请求路径: http://localhost:8091/item/saveItem
  • 请求类型: post
  • 前端传递参数分析
	{
		item: {
			images: "/2021/05/20/da0c1d4781c1499399f090da8b60f359.jpg,/2021/05/20/2ac1c34776a7465887eb019655354c3c.jpg"
			itemCatId: 560
			num: "100"
			price: 718800
			sellPoint: "【华为官方直供,至高12期免息0首付,原装正品】送华为原装无线充+运动蓝牙耳机+蓝牙音箱+三合一多功能数据线+钢化膜等!"
			title: "华为P40 Pro 5G手机【12期免息可选送豪礼】全网通智能手机"
		},
		itemDesc: {
				itemDesc: "<ul><li>品牌:&nbsp;<a href=https://list.jd.com/list.html".......      "
		},
		itemParam: {
			dynamicArray: [
							{paramId: 1, paramVals: "亮黑色,釉白色"}, 
							{paramId: 2, paramVals: "8GB+128GB,8GB+256GB"}
						   ],
			staticArray: [
				 	{"paramId": 3,"paramVals": "华为Mate 40 Pro"},
				    {"paramId": 4,"paramVals": "0.575kg"}.....
			 		]
		}
	}
  • 请求参数: 使用ItemVO对象接收

 封装ItemVO对象

@Data
@Accessors(chain = true)
public class ItemVO implements Serializable {
    private Item item;
    private ItemDesc itemDesc;
}

 修改表字段类型

说明: 文本操作需要大量的存储空间,所以改为mediumtext

在这里插入图片描述

 页面参数传递

在这里插入图片描述

 1>控制层  编辑ItemController

  /**
     * 实现商品新增操作
     * URL地址: /item/saveItem
     * 请求类型: post请求
     * 参数信息: itemVO对象 JSON
     */
    @PostMapping("/saveItem")
    public SysResult saveItem(@RequestBody ItemVO itemVO){//Item/ItemDesc

        itemService.saveItem(itemVO);
        return SysResult.success();
    }

2>业务层   编辑ItemService

void saveItem(ItemVO itemVO);
 /**
     * 需求: 完成2部分入库操作
     * 步骤1: 完成Item入库操作
     * 步骤2: 完成ItemDesc入库操作 item.id=itemDesc.id
     * mybatis 知识讲解
     *  <insert id="xxxx" useGeneratedKeys="true"
     *             keyColumn="id"
     *             keyProperty="id">
     *         新增sql
     *  </insert>
     *  MP知识讲解:
     *      MP基于对象的方式操作数据,如果实现数据的入库操作,
     *      则数据都会与对象绑定,动态回显.
     *  难点知识: 如何实现数据回显!!!!!!
     * @param itemVO
     */
    @Override
    @Transactional
    public void saveItem(ItemVO itemVO) {
        //1.步骤1 实现Item对象入库
        Item item = itemVO.getItem().setStatus(true);
        //刚开始id为null,入库操作时候,id在数据库中会自动赋值
        //赋值之后,对象中的ID依然为null
        itemMapper.insert(item);
        //2.步骤2 实现ItemDesc对象入库
        ItemDesc itemDesc = itemVO.getItemDesc();
        itemDesc.setId(item.getId());
        itemDescMapper.insert(itemDesc);
    }

5、商品删除操作

 1>控制层  编辑ItemController

/**
     * 业务: 实现商品删除操作
     * URL: /item/deleteItemById?id=xxx
     * 参数: id
     * 返回值: SysResult对象
     */
    @DeleteMapping("/deleteItemById")
    public SysResult deleteItemById(Integer id){

        itemService.deleteItemById(id);
        return SysResult.success();
    }

2>业务层

void deleteItemById(Integer id);
@Override
    @Transactional
    public void deleteItemById(Integer id) {
        itemMapper.deleteById(id);
        itemDescMapper.deleteById(id);
    }

文件/图片上传+删除操作

文件/图片上传操作_男人应该冷静,沸腾的水只会蒸发的博客-优快云博客

Nginx反向代理实现图片回显功能/图片预览操作

Nginx反向代理实现图片回显功能/图片预览操作_男人应该冷静,沸腾的水只会蒸发的博客-优快云博客

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值