接口文档: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"/> <!–<!–这样下面都不用写了–>–>
<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)在业务层加上这两个前端未传过来的,却要返回给控制层用户的参数
total | Long | 查询总记录数 | 不能为null |
rows | Object | 分页查询的结果 | 不能为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>品牌: <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);
}
文件/图片上传+删除操作
文件/图片上传操作_男人应该冷静,沸腾的水只会蒸发的博客-优快云博客