JT项目学习

京淘项目

文章目录

一. 项目调用流程图

关于前后端调用说明
前端服务器
说明1: 前端服务器通过脚手架的工具 启动了tomcat服务器. 端口号默认为8080/8081 该服务器中只部署前端项目.
说明2: 前端的数据从后端服务器中获取的. http://localhost:8091 2个不同类型的服务器.
性能问题:
前端和后端谁对服务器性能(处理速度更快)要求更好: 后端服务器.
前端和后端谁对服务器并发能力要求更好 前端服务器

2.1 后端项目搭建
2.1.1 创建项目

2.1.2 编辑pom.xml文件
 <!--mybatis依赖包-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

        <!--jdbc依赖包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!--添加lombok的包-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

2.1.3 按照规则下载项目

说明: 根据码云中的代码, 粘贴复制pom.xml 和src文件 即可

2.1.4 代码启动测试

2.2 搭建前端项目
2.2.1 检查node.js是否正确

2.2.2 安装vue客户端工具

命令: npm install -g @vue/cli --force

2.2.3 启动客户端工具

命令: vue ui

浏览器效果:

2.2.4 前端项目导入

1.去码云中下载指定的前端项目

  1. 在IDEA的工作目录中解压

  1. 利用UI工具导入项目

  1. 项目启动

2.3 关于脚手架目录结构说明
2.3.1 关于VUE知识讲解

目录结构说明:

  1. assets 引入第三方js/css

  2. components 组件定义的目录 组件: 将html/css/js统一封装到一起,可以提高前端代码的扩展性.

  3. plugins 引入elementUI.js的文件

  4. router 代表路由

  5. App.vue 代表整个项目的根组件

  6. main.js 项目中核心JS文件

2.3.2 使用Axios的说明
  1. 定义共同 的请求前缀
	/* 设定axios的请求根目录 */
	axios.defaults.baseURL = 'http://localhost:8091/'
  1. 父子组件之间参数传递
/* 向vue对象中添加全局对象 以后发送ajax请求使用$http对象 */
/* 父组件将参数传递给子组件 需要声明 prototype.key= xxx */
Vue.prototype.$http = axios

2.4 关于Vue 路由说明
	//使用路由机制
	Vue.use(VueRouter)
	const routes = [
	  {path: '/', redirect: '/login'},
	  {path: '/login', component: Login},
	  {path: '/elementUI', component: ElementUI}
	]

三. 用户登录实现

3.1 表设计

1.表结构定义

2.编辑POJO

package com.jt.pojo;

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
public class User extends BasePojo{
    private Integer id;
    private String username;
    private String password;
    private String phone;
    private String email;
    private Boolean status;     //true 正常 false 停用
}
3.2 业务接口文档
  • 请求路径: /user/login
  • 请求方式: POST
  • 请求参数
参数名称参数说明备注
username用户名不能为空
password密码不能为空
  • 响应数据 SysResult对象
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据返回密钥token信息

返回值格式如下:

	{"status":200,"msg":"服务器调用成功!","data":"1e893a97634847b3a8b499b173bea620"}
3.3 创建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;   //服务器返回值数据

    public static SysResult fail(){

        return new SysResult(201,"业务调用失败!!",null);
    }

    public static SysResult success(){

        return new SysResult(200,"业务调用成功!!",null);
    }

    public static SysResult success(Object data){

        return new SysResult(200,"业务调用成功!!",data);
    }

    public static SysResult success(String msg,Object data){

        return new SysResult(200,msg,data);
    }
}
3.4 关于登录业务流程

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

3.5 页面Ajax请求路径说明
  1. URL地址

  1. 参数说明

3.6 编辑UserController
package com.jt.controller;

import com.jt.pojo.User;
import com.jt.service.UserService;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
@CrossOrigin  //前后端跨域的操作
public class UserController {

    @Autowired
    private UserService userService;
    
    /*
    * 业务:完成用户登录的操作
    * URL:user/login
    * 请求参数:username/password json串
    * 返回值:SysResult对象 token密钥
    * */
    @PostMapping("/login")
    public SysResult login(@RequestBody User user){
       //业务逻辑:根据u/p查询数据库 true:用户登陆正常 token  false:null
       String token=userService.login(user);
       if(token==null){
           //表示后端查询失败,返回用户201
           return SysResult.fail();
       }
       //表示有数据,返回值为200
       return SysResult.success(token);
    }

}
3.7 md5加密算法

MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。MD5由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于1992年公开,用以取代MD4算法。这套算法的程序在 RFC 1321 标准中被加以规范。1996年后该算法被证实存在弱点,可以被加以破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。2004年,证实MD5算法无法防止碰撞(collision),因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。

常识性问题:
1.如果数据相同 算法相同 结果必然相同.
2.如果数据不同 算法相同 结果可能相同. hash碰撞问题.

规则:
数据可以穷举查询.但是不可以被破解.

3.8 编辑UserService
/**
     * 思路:
     *  user数据:User(id=null, username=abc, password=123, phone=null, email=null, status=null)
     *  1.将密码明文,转化为密文   sha1/md5算法/md5hash
     *  2.根据用户名和密码查询用户信息
     *  3.有结果:   返回token  UUID
     *  4.没有结果: 返回null
     * @param user
     * @return
     */
    @Override
    public String login(User user) {
        String password = user.getPassword();
        byte[] bytes = password.getBytes();
        //1.将密码加密
        String md5Password = DigestUtils.md5DigestAsHex(bytes);
        user.setPassword(md5Password);
        //2.根据用户名和密码查询数据库
        User userDB = userMapper.findUserByUP(user);
        //3.判断userDB是否有值
        if(userDB == null){
            //用户名和密码查询错误
            return null;
        }
        //程序走到这里,说明用户名和密码正确 返回token
        String token = UUID.randomUUID().toString();
        return token;
    }
3.9 编辑UserMapper
  1. 编辑Mapper接口
package com.jt.mapper;

import com.jt.pojo.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;
//@Mapper 已经在主启动类中配置了 @MapperScan("com.jt.mapper") 注解//将该路径下的mapper接口交给容器管理
public interface UserMapper {

    User findUserByUP(User user);
}
  1. 编辑Mapper映射文件
<?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.UserMapper">

    <select id="findUserByUP" resultType="User">
        select username,password from user where username=#{username} and password = #{password}
    </select>

</mapper>
3.10 关于报错解决方案
3.10.1 F12开发者工具查询请求路径
3.10.2 根据报错信息检查路径
310.3 检查返回值问题

通过response选项 检查返回值 是否与预期一致.

3.11 用户首页跳转
3.11.1 业务说明

说明: 当用户登录成功之后,需要跳转到系统首页中.
url地址: /home
组件地址: Home.vue组件

前端login.vue组件局部代码

 //获取表单对象之后进行数据校验
      //valid 表示校验的结果 true表示通过  false表示失败
      this.$refs.loginFormRef.validate(async valid => {
         //如果没有完成校验则直接返回
         if(!valid) return

         //如果校验成功,则发起ajax请求
        const {data: result} = await this.$http.post('/user/login',this.loginForm)
        if(result.status !== 200) return this.$message.error("用户登录失败")
        this.$message.success("用户登陆成功")

        //获取用户token信息
        let token = result.data
        window.sessionStorage.setItem("token",token)

        //用户登录成功之后,跳转到home页面
        this.$router.push("/home")
3.11.2 修改路由信息

路径:router/index.js中

易错项说明: 如果修改代码之后,没有任何的效果。则需要检查操作的代码与脚手架中运行的代码是否为同一个。

四. 页面布局

4.1 左侧菜单列表展现
4.1.1 表设计

原理说明: 左侧菜单划分为3级.但是显示时只显示2级.
一级菜单获取: select * from rights where parent_id = 0
二级菜单获取: select * from rights where parent_id = 一级ID
三级菜单获取: select * from rights where parent_id = 二级ID

4.2 编辑POJO
@Data
@Accessors(chain = true)
public class Rights extends BasePojo{
    private Integer id;
    private String name;
    private Integer parentId;
    private String path;
    private Integer level;
    private List<Rights> children; //不是表格固有属性
}
4.3 构建层级代码

Mapper/Service/Controller 2-2-1

4.4 左侧菜单列表业务接口
  • 请求路径 /rights/getRightsList
  • 请求类型 GET
  • 请求参数 无
  • 响应数据 SysResult对象
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据返回权限List集合

响应数据如图所示

4.5 编辑RightsController
package com.jt.controller;

import com.jt.pojo.Rights;
import com.jt.service.RightsService;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@CrossOrigin
@RequestMapping("/rights")
public class RightsController {

    @Autowired
    private RightsService rightsService;

    /**
     * URL: /rights/getRightsList
     * 参数: 无
     * 类型: get类型
     * 返回值: SysResult对象(list)集合
     */
    @GetMapping("/getRightsList")
    public SysResult getRightsList(){
        List<Rights> list = rightsService.getRightsList();
        return SysResult.success(list);
    }
}
4.6 Sql查询语法
SELECT * FROM 
	rights p
		LEFT JOIN
	rights c
		ON
	p.id = c.parent_id
	WHERE p.parent_id = 0

二选一即可
SELECT * FROM 
(SELECT * FROM rights WHERE parent_id = 0)p  
	LEFT JOIN
		rights c                     
	ON p.id = c.parent_id
	
能在Mybatis使用版本:
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=0SELECT * 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

4.7 编辑RightsService
package com.jt.service;

import com.jt.mapper.RightsMapper;
import com.jt.pojo.Rights;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class RightsServiceImpl implements RightsService{

    @Autowired
    private RightsMapper rightsMapper;

    /**
     * 思路: 获取1-2级的菜单信息.
     * 一级菜单: parent_id = 0
     * 二级菜单: parent_id = 一级ID
     * 将二级菜单数据,封装到一级菜单的children属性
     * 方案一:  通过代码 先查询一级数据,再查询二级数据,之后完成封装
     * 方案二:  通过关联查询,利用mybatis实现数据一对多的封装.
     * @return
     */
    @Override
    public List<Rights> getRightsList() {
        //只获取1级和2级数据
        return rightsMapper.getRightsList();
    }
}
4.8 编辑RightsMapper
  1. RightsMapper接口
public interface RightsMapper {
    List<Rights> getRightsList();
}
  1. 添加RightsMapper.xml映射文件
<?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">

   <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
    </select>

    <resultMap id="rightsRM" type="Rights" autoMapping="true">
        <id column="id" property="id"/>
        <!--一对多-->
        <collection property="children" ofType="Rights">
            <id column="c_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>	

补充一个三表联查

 <select id="getRightsList" resultMap="rightsRM">
        SELECT * FROM
(SELECT p.id p_id,p.name p_name,p.parent_id p_parent_id,p.path p_path,p.level p_level,p.created p_created, p.updated p_updated FROM rights p) p
RIGHT JOIN
(SELECT * FROM rights WHERE parent_id = 0) g
ON g.id=p.p_parent_id
LEFT JOIN
(SELECT 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 c) c
ON p.p_id=c.c_parent_id
    </select>
    <resultMap id="rightsRM" type="Rights" autoMapping="true">
        <id column="id" property="id"></id>
        <collection property="children" ofType="Rights" autoMapping="true">
            <id column="p_id" property="id"></id>
            <result column="p_name" property="name"></result>
            <result column="p_parent_id" property="parentId"></result>
            <result column="p_path" property="path"></result>
            <result column="p_level" property="level"></result>
            <result column="p_created" property="created"></result>
            <result column="p_updated" property="updated"></result>
            <collection property="children" ofType="Rights" autoMapping="true">
                <id column="c_id" property="id"></id>
                <result column="c_name" property="name"></result>
                <result column="c_parent_id" property="parentId"></result>
                <result column="c_path" property="path"></result>
                <result column="c_level" property="level"></result>
                <result column="c_created" property="created"></result>
                <result column="c_updated" property="updated"></result>
            </collection>
        </collection>
    </resultMap>
4.9 页面效果展现

五. 用户模块管理

5.1 用户列表展现
5.1.1 vue中路由嵌套问题
5.1.1.1 路由入门案例

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

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

5.1.2 京淘后台父子组件跳转

说明: 编辑router/index.js文件 通过children属性实现父子组件的嵌套

页面效果展现:

5.1.3 业务接口文档
  • 请求路径: /user/list
  • 请求类型: GET
  • 请求参数: 后台使用PageResult对象接收
  • 请求案例: http://localhost:8091/user/list?query=查询关键字&pageNum=1&pageSize=10
参数名称参数说明备注信息
query用户查询的数据可以为null
pageNum分页查询的页数必须赋值不能为null
pageSize分页查询的条数必须赋值不能为null
  • 响应参数: SysResult对象 需要携带分页对象 PageResult
参数名称参数说明备注信息
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据返回值PageResult对象
  • PageResult 对象介绍
参数名称参数类型参数说明备注信息
queryString用户查询的数据可以为null
pageNumInteger查询页数不能为null
pageSizeInteger查询条数不能为null
totalLong查询总记录数不能为null
rowsObject分页查询的结果不能为null
  • 返回值效果
{"status":200,
  "msg":"服务器调用成功!",
  "data":
	{"query":"",
	"pageNum":1,
	"pageSize":2,
	"total":4,
	"rows":[
		{"created":"2021-02-18T11:17:23.000+00:00",
		 "updated":"2021-03-26T06:47:20.000+00:00",
		 "id":1,
		 "username":"admin",
		 "password":"a66abb5684c45962d887564f08346e8d",
		 "phone":"13111112222",
		 "email":"1235678@qq.com",
		 "status":true,
		 "role":null
		 },
		{"created":"2021-02-18T11:17:23.000+00:00",
		"updated":"2021-03-13T08:50:30.000+00:00",
		"id":2,
		"username":"admin123",
		"password":"a66abb5684c45962d887564f08346e8d",
		"phone":"13111112223",
		"email":"1235678@qq.com",
		"status":false,
		"role":null
		}
		]
	}
}
5.1.4 编辑PageResult对象
package com.jt.vo;

import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

@Data
@Accessors(chain = true)
public class PageResult implements Serializable {
    private String query;       //用户查询的数据
    private Integer pageNum;    //查询页数
    private Integer pageSize;   //查询条数
    private Long total;         //查询总记录数
    private Object rows;        //分页查询的结果
}
5.1.5 编辑UserController
 /**
     * 需求: 将用户列表进行分页查询
     * URL:  /user/list
     * 参数: query=查询关键字&pageNum=1&pageSize=10
     * 请求类型: get
     * 返回值: SysResult(pageResult)
     */
    @GetMapping("/list")
    public SysResult findUserList(PageResult pageResult){//3
        pageResult = userService.findUserList(pageResult);//+2
        return SysResult.success(pageResult);
    }
5.1.6 编辑UserService
  1. 编辑业务接口
public interface UserService {

    PageResult findUserList(PageResult pageResult);
}
  1. 编辑UserServiceImpl实现类
/**
     * 分页查询的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
     * @param pageResult
     * @return
     */
    @Override
    public PageResult findUserList(PageResult pageResult) {
        //1.总记录数
        long total = userMapper.findTotal();
        //2.获取分页查询的结果
        int start = (pageResult.getPageNum() - 1) * pageResult.getPageSize();
        int size = pageResult.getPageSize();
        String query = pageResult.getQuery();
        List<User> rows = userMapper.findUserList(start,size,query);
        return pageResult.setTotal(total).setRows(rows);
    }
5.1.7 编辑UserMapper
  1. 编辑Mapper接口
  //只支持单值传参 封装为map集合
 List<User> findUserList(@Param("start") int start,@Param("size") int size,@Param("query") String query);
  1. 编辑UserMapper.xml映射文件
	<!--表名必须小写 简化操作全部小写 ctrl+shift+y/u
        业务需求: query属性可能有值/可能没有值 形成动态sql
    -->
    <select id="findUserList" resultType="User">
        select * from user
            <where>
                <if test="query !=null and query !=''">username like "%"#{query}"%"</if>
            </where>
            limit #{start},#{size}
    </select>

5.2 用户状态修改
5.2.1 业务说明

说明: 点击页面开关,修改数据表中的status
知识点讲解:

  1. 数据库中没有boolean类型 可以使用数值类型代替 true 代替数值1, false 代替数值0
  2. 数据库中可以使用tinyint类型代替boolean. 并且2种类型可以自动转化.
5.2.2 业务接口文档
  • 请求路径 /user/status/{id}/{status}
  • 请求类型 PUT
  • 请求参数: 用户ID/状态值数据
参数名称参数类型参数说明备注信息
idInteger用户ID号不能为null
statusboolean参数状态信息不能为null
  • 返回值结果: SysResult对象
{"status":200,"msg":"服务器调用成功!","data":null}
5.2.3 编辑UserController
/**
     * 需求: 实现状态修改
     * URL: /user/status/{id}/{status}
     * 参数: id/status
     * 类型: put
     * 返回值: SysResult对象
     */
    @PutMapping("/status/{id}/{status}")
    public SysResult updateStatus(User user){

        userService.updateStatus(user);
        return SysResult.success();
    }
5.2.4 编辑UserService
  1. 编辑接口
void updateStatus(User user);
  1. 编辑Service实现类
/**
     * 需求: 修改状态信息
     * 参数: id/status/updated修改时间
     * @param user
     */
    @Override
    public void updateStatus(User user) {
        user.setUpdated(new Date()); //设定当前时间
        userMapper.updateStatus(user);
    }
5.2.5 编辑UserMapper接口
 @Update("update user set status=#{status},updated=#{updated} where id=#{id}")
    void updateStatus(User user);
5.3 用户删除操作
5.3.1 业务接口说明
  • 请求路径: /user/{id}
  • 请求类型: delete
  • 请求参数:
参数名称参数说明备注
ID用户ID号不能为null
  • 返回值: SysResult对象
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据null
5.3.2 编辑UserController
  /**
     * 业务说明:  实现用户数据删除操作
     *  URL: /user/{id}
     *  参数: id
     *  返回值: SysResult对象
     */
    @DeleteMapping("/{id}")
    public SysResult deleteUserById(@PathVariable Integer id){

        userService.deleteUserById(id);
        return SysResult.success();
    }
5.3.3 编辑UserService
1.接口方法
void deleteUserById(Integer id);

2.实现类方法
@Override
 public void deleteUserById(Integer id) {

        userMapper.deleteUserById(id);
 }
5.3.4 编辑UserMapper
	@Delete("delete from user where id=#{id}")
    void deleteUserById(Integer id);
5.3.5 修改页面JS

5.4 用户新增
5.4.1 业务接口文档
  • 请求路径 /user/addUser
  • 请求类型 POST
  • 请求参数: 整个form表单数据封装为js对象进行参数传递
参数名称参数类型参数说明备注信息
usernameString用户名不能为null
passwordString密码不能为null
phoneString电话号码不能为null
emailString密码不能为null
  • 返回值结果: SysResult对象
{"status":200,"msg":"服务器调用成功!","data":null}
5.4.2 编辑UserController
/**
     * 需求: 完成用户新增操作
     * URL地址: /user/addUser
     * 参数:    使用对象接收
     * 返回值:  SysResult
     * 类型: post
     */
    @PostMapping("/addUser")
    public SysResult saveUser(@RequestBody User user){

        userService.saveUser(user);
        return SysResult.success();
    }
5.4.3 编辑UserService
  1. 编辑UserService接口
 void saveUser(User user);
  1. 编辑UserServiceImpl实现类
/**
     * 业务:
     *     1.密码进行加密处理
     *     2.设定状态码信息
     *     3.设定创建时间和修改时间
     * @param user
     */
    @Override
    public void saveUser(User user) {
        //将密码加密处理
        String password = user.getPassword();
        Date date = new Date();
        String md5Pass = DigestUtils.md5DigestAsHex(password.getBytes());
        user.setPassword(md5Pass)
                .setStatus(true)
                .setCreated(date)
                .setUpdated(date);
        userMapper.saveUser(user);
    }
5.4.4 编辑Mapper/映射文件
  1. 编辑Mapper接口
void saveUser(User user);
  1. 编辑Mapper映射文件
 <insert id="saveUser">
        insert into user value (null,#{username},#{password},#{phone},
        #{email},#{status},#{created},#{updated})
    </insert>
5.5 用户修改-数据回显
5.5.1 业务接口文档说明
  • 请求路径: /user/{id}
  • 请求类型: GET
  • 返回值: SysResult对象
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据返回user对象
  • JSON格式如下:
{
 "status":200,
 "msg":"服务器调用成功!",
 "data":{
	 "created":"2021-02-18T11:17:23.000+00:00",
	 "updated":"2021-05-17T11:33:46.000+00:00",
	 "id":1,
	 "username":"admin",
	 "password":"a66abb5684c45962d887564f08346e8d",
	 "phone":"13111112222",
	 "email":"1235678@qq.com",
	 "status":true,
	 "role":null
	 }
 }
5.5.2 编辑UserController
/**
     * 业务说明: 根据ID查询数据
     * URL:    /user/{id}
     * 参数:    id值
     * 类型:   get
     * 返回值:  SysResult(user对象)
     */
    @GetMapping("/{id}")
    public SysResult findUserById(@PathVariable Integer id){

        User user = userService.findUserById(id);
        return SysResult.success(user);
    }
5.5.3 编辑UserService
1.编辑业务接口代码
User findUserById(Integer id);

2.编辑实现类代码
@Override
    public User findUserById(Integer id) {

        return userMapper.findUserById(id);
    }
5.5.4 编辑UserMapper
@Select("select * from user where id=#{id}")
    User findUserById(Integer id);
5.6 用户修改-数据提交
5.6.1 业务接口说明
  • 请求路径: /user/updateUser
  • 请求类型: PUT
  • 请求参数: User对象结构
参数名称参数说明备注
ID用户ID号不能为null
phone手机信息不能为null
email邮箱地址不能为null
  • 返回值: SysResult对象
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据null
  • JSON格式如下:
{
 "status":200,
 "msg":"服务器调用成功!",
 "data":{}
 }
5.6.2 编辑UserController
/**
     * 业务: 实现用户的修改操作
     * URL地址: /user/updateUser
     * 参数:    使用对象User接收
     * 返回值:  SysResult
     * 类型:
     */
    @PutMapping("/updateUser")
    public SysResult updateUser(@RequestBody User user){
        userService.updateUser(user);
        return SysResult.success();
    }
5.6.3 编辑UserService
1.编辑接口
 void updateUser(User user);

2.编辑实现类
 /**
     * 说明: 实现用户的修改
     * 数据: 添加更新时间操作
     * @param user
     */
    @Override
    public void updateUser(User user) {
        user.setUpdated(new Date());
        userMapper.updateUser(user);
    }
5.6.4 编辑UserMapper
1.编辑UserMapper接口
void updateUser(User user);

2. 编辑Mapper映射文件
<!--完成用户的更新操作-->
    <update id="updateUser">
        update user set
            phone=#{phone},
            email=#{email},
            updated=#{updated}
        where id=#{id}
    </update>

六. 业务辅助功能

6.1 事务控制
6.1.1 现象说明

说明: 业务逻辑在执行的过程中,如果中间发生了异常,应该保证事务的一致性.事务应该回滚.但是经过测试,发现执行报异常,但是数据可以正常的入库. 说明方法没有事务的控制.

6.1.2 关于注解说明

名称: @Transactional //事务的注解
特点:
1.Spring中默认对事务进行支持.
2.Spring中默认控制的是运行时异常. 如果是检查异常 Spring不负责处理.

检查异常:try catch

核心原理: AOP

常见案例:
@Transactional(rollbackFor = Exception.class) 只要有异常,则全部回滚.

问题: 如果采用上述的代码,则AOP拦截所有的异常,运行效率低. 所以一般只拦截运行时异常.检查异常由程序员手动控制.

   /*
    * 业务:
    *   1.密码进行加密处理
    *   2.设定状态码信息
    *   3.设定创建时间和修改时间
    * */
    @Override
//    @Transactional(rollbackFor = SQLException.class) //事物的注解
    @Transactional
    public void addUser(User user) {
        byte[] bytes = user.getPassword().getBytes();
        String md5Password = DigestUtils.md5DigestAsHex(bytes);
        user.setPassword(md5Password).setStatus(true).setCreated(new Date()).setUpdated(user.getCreated());
        userMapper.addUser(user);
    }

总结: 以后操作数据库时 ,新增/删除/修改操作 需要事务控制.

6.2 全局异常处理机制
6.2.1 业务说明

当用户在进行业务操作时,不管后端服务器执行是否正确,都应该给用户一个正向的回馈!!!
解决方案:

 @PutMapping("/status/{id}/{status}")
    public SysResult updateStatus(User user){
        try {
            userService.updateStatus(user);
            return SysResult.success();
        }catch (Exception e){
            e.printStackTrace();
            return SysResult.fail();
        }
    }

暴露问题: 由于在每个方法中都需要添加try-catch代码 必然会导致代码的可读性低.并且代码繁琐.不便于扩展.

6.2.2 全局异常处理机制实现
package com.jt.aop;

import com.jt.vo.SysResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice //advice 通知!!!
//全局异常处理机制,只拦截Controller层
public class SystemException {
    
    //指定异常的类型进行拦截.
    @ExceptionHandler(RuntimeException.class)
    public SysResult exception(Exception e){
        e.printStackTrace();//控制台打印异常
        return SysResult.fail();//201
    }
}

七. MybatisPlus(MP)

7.1 MP介绍

MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

7.2 MP特点

无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

7.3 ORM思想
7.3.1 ORM说明

对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。如今已有很多免费和付费的ORM产品,而有些程序员更倾向于创建自己的ORM工具。

总结:以面向对象的方式操作数据库

7.3.2 ORM特点
  1. 对象与数据库中的表什么关系? 一一映射
  2. 对象中的属性与数据库中的字段什么关系? 一一映射
  3. 以面向对象的方式操作数据库。由框架动态生成Sql语句。实现了跨数据库的操作.
7.4 MP入门案例
7.4.1 导入jar包
<!--spring整合mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>
7.4.2 编辑POJO对象
package com.jt.pojo;

@Data
@Accessors(chain = true)

@TableName("user")      //让对象与表名一一映射

public class User extends BasePojo{
    
    @TableId(type = IdType.AUTO/*主键自增*/)    //标识主键
    
    private Integer id;
    
//    @TableField("username")  如果属性与字段名称一致(包含驼峰规则),那么注解可以省略不写
    
    private String username;
    private String password;
    private String phone;
    private String email;
    private Boolean status;     
}
7.4.3 编辑Mapper接口

说明: MP将常用的CURD操作进行封装.以后用户需要使用,只需要继承公共的业务接口即可.

//继承时,必须要添加泛型,并且该泛型必须与表关联
//MP提供了强大的单表的CRUD操作,多表自己写
//此处BaseMapper<User>中的泛型限制了UserMapper的()不管是实现类还是代理类实例对象调用的构造器泛型都必须是User;
public interface UserMapper extends BaseMapper<User> {}
7.4.4 编辑YML文件

7.4.5 入门案例
package com.jt;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Date;
import java.util.List;

@SpringBootTest
class JtApplicationTests {
    @Autowired
    private UserMapper userMapper;

    /*
    * 实现数据的新增
    * */
    @Test
    void contextLoads() {
        User user = new User();
        user.setUsername("admin888").setPassword("123123").setEmail("11@qq.com")               .setPhone("13412345678").setStatus(true).setCreated(newDate()).setUpdated(user.getCreated());
        int rows = userMapper.insert(user);
        System.out.println("影响的行数:"+rows);
    }

    }
}
7.5 MP案例
7.5.1 根据ID查询
	/**
     * 案例1: 根据ID进行查询
     */
    @Test
    public void test01(){
        int id = 1;
        User user = userMapper.selectById(id);
        System.out.println(user);
    }
7.5.2 对象封装数据
/*
 * 案例2:根据其它属性查询数据
 * 根据username="admin888"进行查询
 * 组件:  条件构造器  用来封装where条件的
 * select * from user where username="admin888"
 * 如果可以保证查询的结果为一个,则用对象接
 * 方式一:利用对象去封装数据  只能满足=号逻辑运算符
 * User user = userMapper.selectOne(queryWrapper);
 * 如果不能可以保证查询的结果为一个,则用集合接
 * List<User> userList = userMapper.selectList(null);
 *
 * */
@Test
public void test2(){
    User temp =new User();
    temp.setUsername("admin888");//=号拼接
    //条件构造器,会根据对象中不为null的元素动态拼接sql  注意此处要传入temp对象,否则就会把所有的值都查询出来
    QueryWrapper<User> queryWrapper =new QueryWrapper<>(temp);
    /*如果可以保证查询的结果为一个,则用对象接*/
    User user = userMapper.selectOne(queryWrapper);
    System.out.println(user);
}
7.5.3 转译字符方式
	/*
     * 案例3:查询Id>5de的数据
     * select * from user where id >5;
     * 转义字符  > &gt;  < &lt;  =  eq;  >= &ge;  <= le;  != ne;
     * 利用逻辑运算符  实现sql操作
     * */
    @Test
    public void test3(){
        QueryWrapper<User>  queryWrapper = new QueryWrapper<>();
        queryWrapper.gt("id",5);
        List<User> userList = userMapper.selectList(queryWrapper);
        System.out.println(userList);

    }
7.5.4 模糊查询
	 /*
     * 案例4:查询username 包含admin的数据,并且按照id降序排列
     * select * from user where username like "%admin%" order by id desc;
     * 知识点:
     *      1.like       "%admin%"   左右都有%号
     *      2.likeLeft   "%admin"    左边有%号
     *      3.likeRight  "admin%"    右边有%号
     * */
    @Test
    public void test4(){
        QueryWrapper<User>  queryWrapper = new QueryWrapper<>();
        queryWrapper.like("username","admin").orderByDesc("id");
        List<User> userList = userMapper.selectList(queryWrapper);
        System.out.println(userList);

    }
7.5.5 in关键字
/*
 * 案例5:查询id=2,4,5,7,8的数据
 * select * from user where id in(2,4,5,7,8);
 * 注意事项:在使用in关键字时候,最好使用包装类型.
 * */
@Test
public void test5(){
    //int[] ids={2,4,5,7,8}
    Integer[] ids={2,4,5,7,8};
    QueryWrapper<User>  queryWrapper = new QueryWrapper<>();
    queryWrapper.in("id",ids);
    //方式1:利用条件构造器查询
    List<User> userList = userMapper.selectList(queryWrapper);
    System.out.println(userList);
    //方式2:利用API查询
    List<Integer> list = Arrays.asList(ids);
    List<User> userList1 = userMapper.selectBatchIds(list);
    System.out.println(userList1);


    Integer[] integers = (Integer[]) list.toArray();
    System.out.println(Arrays.toString(integers));
}
7.5.6 动态Sql查询
 /*
     * 案例6:动态查询数据,利用电话/邮箱查询数据
     * MP特点 根据对象(pojo对象,而不是QueryWrapper条件构造器对象)中不为null的属性充当条件
     *
     * */
    @Test
    public void test6(){
        //空串和空不一样,空串是有值只不过是空串(长度为0),调用API不会出现空指针情况,空则会出现空指针情况,所以判断条件phone!=null && phone!=""
       String phone = "13412345678";
       String email = "11@qq.com";
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//        queryWrapper.eq("phone", phone).eq("email", email); 非动态查询
        //动态查询
//        queryWrapper.eq(phone!=null && phone!="","phone", phone).eq(email!=null && email!="","email", email);
        queryWrapper.eq(StringUtils.hasLength(phone),"phone", phone).eq(StringUtils.hasLength(email),"email", email);
        List<User> userList = userMapper.selectList(queryWrapper);
        System.out.println(userList);
    }
7.5.7 根据主键查询
/*
     * 案例7:只查询主键信息
     * API: selectObjs: 获取主键信息
     * 实际用途: 关联查询
     * */
    @Test
    public void test7(){

        //查询所有:select * from user
        List<User> userList = userMapper.selectList(null);
        System.out.println(userList);
        //只查询主键信息  构造器设置为空,c
        List<Object> ids = userMapper.selectObjs(null);
        System.out.println(ids);
    }
7.5.8 根据主键更新
/*
 * 案例8:用户的修改操作
 * 1.根据id修改数据
 *      将id=19 的name 改为 "admin999",phone="110110"
 *      原理:Id当作唯一的where条件,其他的不为null的属性当作set条件
 *      userMapper.updateById(user);
 * */
@Test
public void test8(){
    User user = new User();
    user.setId(19).setUsername("admin999").setPhone("110110");
    int i = userMapper.updateById(user);
    System.out.println("更新成功,影响行数"+i);

}
7.5.9 根据其它条件更新
 /*
     * 案例9:用户的修改操作
     * 1.根据id修改数据
     *      将password=123456 的phone 改为 "10086",email="10086@qq.com"
     * API: userMapper.update(xxx, 条件构造器)
     * 参数说明:
     *      1.entity: 主要的目的是封装set条件
     *      2.updateWrapper: 封装修改条件的条件构造器
     * */
    @Test
    public void test9(){
        User user = new User();
        user.setPhone("10086").setEmail("10086@qq.com");
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("password", "123123");
        int i = userMapper.update(user, updateWrapper);
        System.out.println("修改成功,影响行数"+i);
    }

八. 商品模块管理

8.1 查询3级商品分类信息
8.1.1 路由跳转页面

需求: 当用户点击商品分类时,应该跳转到商品分类页面中.

编辑路由文件

8.1.2 商品分类层级代码搭建

编辑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.Data;
import lombok.experimental.Accessors;

import java.util.List;


@Data
@Accessors(chain = true)
@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
    /*children是业务数据,并不是数据库字段*/
    @TableField(exist = false)  //标识当前属性不参与MP操作
    private List<ItemCat> children;
}
8.1.2 搭建层级代码

8.1.4 商品分类业务说明(难点知识!!!)

表设计

8.1.5 数据结构设计

一级/二级一定有children/ 三级的children为null

@Data
@Accessors(chain = true)
@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
   
    /*children是业务数据,并不是数据库字段*/
    @TableField(exist = false)  //标识当前属性不参与MP操作
    private List<ItemCat> children;
}
8.1.6 商品分类列表展现
8.1.6.1 业务接口说明
  • 请求路径: /itemCat/findItemCatList/{level}
  • 请求类型: get
  • 请求参数: level
参数名称参数说明备注
level查询级别1查询一级分类 2查询1-2 级商品分类 3查询1-2-3级商品分类
  • 业务说明: 查询3级分类菜单数据 要求三层结构嵌套
  • 返回值: SysResult对象
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据3级商品分类信息
8.1.6.2 页面URL说明

8.1.6.3 编辑ItemCatController
package com.jt.controller;

import com.jt.pojo.ItemCat;
import com.jt.service.ItemCatService;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@CrossOrigin
@RequestMapping("/itemCat")
public class ItemCatController {
    @Autowired
    private ItemCatService itemCatService;

    /*
    * 需求:查询三级商品分类信息
    * URL:/itemCat/findItemCatList/{level}
    * 参数: level 1/2/3
    * 返回值: SysResult(三级菜单)
    * */
    @GetMapping("/findItemCatList/{level}")
    public SysResult findItemCatList(@PathVariable Integer level){
        List<ItemCat> itemCatList = itemCatService.findItemCatList(level);
        return SysResult.success(itemCatList);
    }
}
8.1.6.4 升级MPjar包
<!--spring整合mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
8.1.6.5 编辑ItemCatService
package com.jt.service;

import com.jt.pojo.ItemCat;

import java.util.List;

public interface ItemCatService {
    List<ItemCat> findItemCatList(Integer level);
}
package com.jt.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.ItemCatMapper;
import com.jt.pojo.ItemCat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ItemCatServiceImpl implements ItemCatService {
    @Autowired
    private ItemCatMapper itemCatMapper;

    /*
    * 只查询一级菜单  parent_id=0或level=1
    * 查询二级菜单  parent_id = 一级Id
    * */
    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        long start = System.currentTimeMillis();
        QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id", 0);
        List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
        for(ItemCat oneItemCat : oneList){
            Integer oneId = oneItemCat.getId();
            //清空多余的条件
            queryWrapper.clear();
            //查询二级数据
            queryWrapper.eq("parent_id", oneId);
            List<ItemCat> twoList = itemCatMapper.selectList(queryWrapper);
            //将二级封装到一级对象中
            oneItemCat.setChildren(twoList);
            for (ItemCat twoItemCat : twoList){
                Integer twoId = twoItemCat.getId();
                queryWrapper.clear();
                queryWrapper.eq("parent_id",twoId);
                List<ItemCat> threeList = itemCatMapper.selectList(queryWrapper);
                twoItemCat.setChildren(threeList);
            }
        }
        long end = System.currentTimeMillis();
        System.out.println(end-start);
        return  oneList;
    }
}
8.1.6.6 编辑ItemCatMapper
package com.jt.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jt.pojo.ItemCat;

import java.util.List;

public interface ItemCatMapper extends BaseMapper<ItemCat> {
}
8.1.6.7 页面效果展现

8.1.7 商品分类实现优化
8.1.7.1 优化策略

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

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

8.1.7.2 封装Map集合
/**
     * 思路:
     *      1.判断map集合中是否存在key
     *      2.如果key 不存在 准备一个新list集合,将自己作为第一个元素添加
     *      3.如果key 存 在  获取list集合,将自己追加.
     * @return
     */
    public Map<Integer,List<ItemCat>> getMap(){
        Map<Integer,List<ItemCat>> map = new HashMap<>();
        //1.查询数据库的所有的记录
        List<ItemCat> list = itemCatMapper.selectList(null);
        for(ItemCat itemCat : list){
            int key = itemCat.getParentId();
            if(map.containsKey(key)){ //true 有数据
                map.get(key).add(itemCat);
            }else{  //false 没有数据
                List<ItemCat> childrenList = new ArrayList<>();
                childrenList.add(itemCat);
                map.put(key,childrenList);
            }
        }
        return map;
    }
8.1.7.3 商品分类列表实现完整代码
package com.jt.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.ItemCatMapper;
import com.jt.pojo.ItemCat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class ItemCatServiceImpl implements ItemCatService {
    @Autowired
    private ItemCatMapper itemCatMapper;

    /*
    * 思路:
    *       1.判断map集合中是否存在key
    *       2.如果不存在,准备一个新的list集合,将自己作为第一个元素添加
    *       3.如果key存在,获取list集合,将自己追加进去;
    *
    * */
    public Map<Integer,List<ItemCat>> getMap(){
        Map<Integer,List<ItemCat>> map =new HashMap<>();
        //1.查询数据库的所有记录
        List<ItemCat> list = itemCatMapper.selectList(null);
        for(ItemCat itemCat : list){ //true 有数据 将自己添加
            int key= itemCat.getParentId();
            if(map.containsKey(key)){
                map.get(key).add(itemCat);
            }else {//false 没有数据  准备新集合,添加数据
                List<ItemCat> childrenList =new ArrayList<>();
                childrenList.add(itemCat);
                map.put(key, childrenList);
            }
        }
        return map;
    }

    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        //1.获取数据信息
        Map<Integer,List<ItemCat>> map =getMap();
        if(level==1){//值获取一级菜单数据
            return map.get(0);
        }
        if(level==2){//获取一级和二级数据
           return getTwoList(map);
        }
        return getThreeList(map);
    }

    private List<ItemCat> getThreeList(Map<Integer, List<ItemCat>> map) {
        List<ItemCat> oneList = getTwoList(map);
        for(ItemCat oneItemCat: oneList){
            List<ItemCat> twoList = oneItemCat.getChildren();
            if(twoList==null){
                //说明该一级下里面没有二级的数据,所以跳过本次的循环,进入下一次
                continue;
            }
            for(ItemCat twoItemCat : twoList){
                //使用二级的Id查询三级数据
                Integer key = twoItemCat.getId();
                List<ItemCat> threeList = map.get(key);
                twoItemCat.setChildren(threeList);
            }
        }
        return oneList;
    }

    private List<ItemCat> getTwoList(Map<Integer, List<ItemCat>> map) {
        List<ItemCat> oneList = map.get(0);
        for(ItemCat oneItemCat : oneList){
            //根据一级Id去查二级集合
            Integer /*oneId*/key = oneItemCat.getId();//id map中的key
            List<ItemCat> twoList = map.get(key);
            //将二级数据封装到一级中
            oneItemCat.setChildren(twoList);
        }
        return oneList;
    }

    /*
     * 只查询一级菜单  parent_id=0或level=1
     * 查询二级菜单  parent_id = 一级Id
     * */
//    @Override
//    public List<ItemCat> findItemCatList(Integer level) {
//        long start = System.currentTimeMillis();
//        QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
//        queryWrapper.eq("parent_id", 0);
//        List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
//        for(ItemCat oneItemCat : oneList){
//            Integer oneId = oneItemCat.getId();
//            //清空多余的条件
//            queryWrapper.clear();
//            //查询二级数据
//            queryWrapper.eq("parent_id", oneId);
//            List<ItemCat> twoList = itemCatMapper.selectList(queryWrapper);
//            //将二级封装到一级对象中
//            oneItemCat.setChildren(twoList);
//            for (ItemCat twoItemCat : twoList){
//                Integer twoId = twoItemCat.getId();
//                queryWrapper.clear();
//                queryWrapper.eq("parent_id",twoId);
//                List<ItemCat> threeList = itemCatMapper.selectList(queryWrapper);
//                twoItemCat.setChildren(threeList);
//            }
//        }
//        long end = System.currentTimeMillis();
//        System.out.println(end-start);
//        return  oneList;
//    }
}
8.1.7.4 递归代码
@Override
public List<ItemCat> findItemCatList(Integer level) {
    Map<Integer, List<ItemCat>> map = getMap();
    if(level==1) return map.get(0);
    level--;
    List<ItemCat> itemCatList = findItemCatList(level-);
    for(ItemCat itemCat : itemCatList){
        List<ItemCat> childrenList = itemCat.getChildren();
        /*此处还可以分开用判定再优化,感兴趣的可以优化一下*/
        if(childrenList==null){
            Integer id = itemCat.getId();
            itemCat.setChildren(map.get(id));
            continue;
        }
        for(ItemCat childrenItemCat : childrenList){
            Integer id = childrenItemCat.getId();
            childrenItemCat.setChildren(map.get(id));
        }
    }
    return itemCatList;
}
8.2 商品分类新增
8.2.1 业务接口说明
  • 请求路径: /itemCat/saveItemCat
  • 请求类型: post
  • 请求参数: 表单数据
参数名称参数说明备注
name商品分类名称不能为null
parentId用户父级ID不能为null
level分类级别1 2 3 商品分类级别
  • 返回值: SysResult对象
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据可以为null
8.2.2 前端页面URL请求

1.页面请求

2.页面传递的JSON串

8.2.3 编辑ItemCatController
 /**
     * 业务需求:
     *   实现商品分类新增操作
     *   url地址: /itemCat/saveItemCat
     *   参数:    JSON串
     *   提交方式: post
     *   返回值:  SysResult对象
     */
    @PostMapping("/saveItemCat")
    public SysResult saveItemCat(@RequestBody ItemCat itemCat){

        itemCatService.saveItemCat(itemCat);
        return SysResult.success();
    }
8.2.4 编辑ItemCatService
void saveItemCat(ItemCat itemCat);
/**
     * 有些数据应该提前填充 status/创建时间/修改时间
     * @param itemCat
     */
    @Override
    @Transactional  //事务注解
    public void saveItemCat(ItemCat itemCat) {
        itemCat.setStatus(true)
                .setCreated(new Date())
                .setUpdated(itemCat.getCreated());
        itemCatMapper.insert(itemCat);
    }
8.3 数据自动填充
8.3.1 官网介绍

8.3.2 编辑自动填充项
//pojo基类,完成2个任务,2个日期,实现序列化
@Data
@Accessors(chain=true)
public class BasePojo implements Serializable{
   //只有公共字段的属性参可以开启自动填充
   @TableField(fill = FieldFill.INSERT)
   private Date created;  //表示入库时需要赋值
   @TableField(fill = FieldFill.INSERT_UPDATE)
   private Date updated;  //表示入库/更新时赋值.
}
8.3.3 配置自动填充功能

说明: 在com.jt.config 中编辑配置类

//将该类交给Spring容器管理(this只在运行时候有效,指当前对象而不是当前类)
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        Date date = new Date();
        this.setFieldValByName("created",date,metaObject);
        this.setFieldValByName("updated",date,metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updated",new Date(),metaObject);
    }
}
8.3.4 优化商品分类新增service层
@Override
@Transactional
public void saveItemCat(ItemCat itemCat) {
    //已开启自动填充创建时间和更新时间
    itemCat.setStatus(true);
    itemCatMapper.insert(itemCat);
}
8.4 修改商品分类状态
8.4.1 业务接口说明
  • 请求路径: /itemCat/status/{id}/{status}
  • 请求类型: put
  • 请求参数:
参数名称参数说明备注
id用户ID值不能为null
status用户的状态信息不能为null
  • 返回值: SysResult对象
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据可以为null
8.4.2 编辑ItemCatController
/*
* 需求:修改商品分类状态
* 请求路径: /itemCat/status/{id}/{status}
* 请求类型: put
* 请求参数: id/status
* 返回值: SysResult对象
* */
@PutMapping("/status/{id}/{status}")
public SysResult updateStatus(@PathVariable Integer id,@PathVariable Boolean status){
    itemCatService.updateStatus(id,status);
    return SysResult.success();
}
8.4.3 编辑ItemCatService
void updateStatus(Integer id, Boolean status);
  @Override
    @Transactional
    public void updateStatus(Integer id, Boolean status) {
        ItemCat itemCat = new ItemCat();
//        itemCat.setStatus(status);
//        UpdateWrapper<ItemCat> updateWrapper = new UpdateWrapper<>();
//        updateWrapper.eq("id", id);
//        itemCatMapper.update(itemCat, updateWrapper);
        itemCat.setId(id).setStatus(status);
        itemCatMapper.updateById(itemCat);
    }
8.5 商品分类修改
8.5.1 业务接口说明
  • 请求路径: /itemCat/updateItemCat
  • 请求类型: put
  • 请求参数: 表单数据 ItemCat对象
  • 返回值: SysResult对象
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据可以为null
8.5.2 编辑ItemCatController
/*
* 需求:商品分类修改
* 请求路径: /itemCat/updateItemCat
* 请求类型: put
* 请求参数: 表单数据 ItemCat对象
* 返回值: SysResult对象
* */
@PutMapping("/updateItemCat")
public SysResult updateItemCat(@RequestBody ItemCat itemCat){
    itemCatService.updateItemCat(itemCat);
    return SysResult.success();
}
8.5.3 编辑ItemCatService
void updateItemCat(ItemCat itemCat);
@Override
@Transactional
public void updateItemCat(ItemCat itemCat) {
    itemCatMapper.updateById(itemCat);
}
8.6 商品分类删除
8.6.1 业务说明

当用户删除商品分类的时候,应该将它的子级数据全部删除.

  • 请求路径: /itemCat/deleteItemCat
  • 请求类型: delete
  • 业务描述: 当删除节点为父级时,应该删除自身和所有的子节点
  • 请求参数:
参数名称参数说明备注
id分类ID号不能为null
level商品分类级别 一级,二级,三级
  • 返回值结果 SysResult对象
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据可以为null
8.6.2 编辑ItemCatController
/*
 * 需求:商品分类删除
 * 请求路径: /itemCat/deleteItemCat
 * 请求类型: delete
 * 请求参数: id/level
 * 返回值: SysResult对象
 * */
@DeleteMapping("/deleteItemCat")
public SysResult deleteItemCat(ItemCat itemCat){
    itemCatService.deleteItemCat(itemCat);
    return SysResult.success();
}
8.6.3 编辑ItemCatService
/*
* 删除思路:
*   1.判断是否为3级  如果是,直接删除
*   2.判断是否为2级  如果是2级,先删除3级,再删除2级
*   3.如果是1级,先查询2级,再删除3级/2级/1级
* */
@Override
public void deleteItemCat(ItemCat itemCat) {
    if(itemCat.getLevel()==3){
        itemCatMapper.deleteById(itemCat.getId());
        return;
    }
    if (itemCat.getLevel()==2){
        Integer twoId = itemCat.getId();
        QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id",twoId);
        itemCatMapper.delete(queryWrapper);
        itemCatMapper.deleteById(twoId);
        return;
    }
    /*
    * 删除一级菜单
    * 1.应该先查询二级id
    * */
    Integer oneId = itemCat.getId();
    QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("parent_id", oneId);
    /*返回值有可能是空集合,但不可能为null*/
    List<Object> twoIdList =itemCatMapper.selectObjs(queryWrapper);
    if(twoIdList.size()==0){
        //如果没有二级数据,则直接删除一级信息
        itemCatMapper.deleteById(oneId);
    }else {
        //表示有二级数据,如何快速删除1级2级3级数据(3级无需判断是否有值,大不了影响行数为0,sql语句不会报错)
        //sql : delete from item_cat where parent_id in (x,x,x,x) or id in (x,x,x,x) or id = oneId;
        queryWrapper.clear();
        queryWrapper.in("parent_id", twoIdList).or().in("id", twoIdList).or().eq("id", oneId);
        itemCatMapper.delete(queryWrapper);
    }
}
8.7 商品列表展现
8.7.1 表设计

1.商品表设计(item)

2.商品详情表设计(item_desc)

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

8.7.2 POJO设计
8.7.2.1 编辑Item类
package com.jt.pojo;

@Data
@Accessors(chain = true)
@TableName("item")
public class Item extends BasePojo{
    @TableId(type = IdType.AUTO)
    private Integer id;         //商品Id号
    private String title;       //商品标题信息
    private String sellPoint;   //卖点信息
    private Integer price;      //商品价格
    private Integer num;        //商品数量
    private String images;       //商品图片
    private Integer itemCatId;  //商品分类ID号
    private Boolean status;     //状态信息    0 下架 1 上架

}
8.7.2.2 编辑ItemDesc
package com.jt.pojo;

@Data
@Accessors(chain = true)
@TableName("/item_desc")
public class ItemDesc extends BasePojo{
    @TableId  //只标识主键即可
    private Integer id;      //item.id一致
    private String itemDesc; //html代码片段
}
8.7.3 商品页面跳转
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../components/Login.vue'
import ElementUI from '../components/ElementUI.vue'
import Home from '../components/Home.vue'
import User from '../components/user/user.vue'
import ItemCat from '../components/items/ItemCat.vue'
import Item from '../components/items/Item.vue'
import AddItem from '../components/items/addItem.vue'
//使用路由机制
Vue.use(VueRouter)
const routes = [
  {path: '/', redirect: '/login'},
  {path: '/login', component: Login},
  {path: '/elementUI', component: ElementUI},
  {path: '/home', component: Home, children: [
    {path: '/user', component: User},
    {path: '/itemCat', component: ItemCat},
    {path: '/item', component: Item},
    {path: '/item/addItem', component: AddItem}
  ]},
]

8.7.4 商品列表展现
8.7.4.1 业务接口说明
  • 请求路径: /item/getItemList?query=&pageNum=1&pageSize=10
  • 请求类型: get
  • 请求参数: 使用pageResult对象接收
参数名称参数说明备注信息
query用户查询的数据可以为null
pageNum分页查询的页数必须赋值不能为null
pageSize分页查询的条数必须赋值不能为null
  • 返回值结果:
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据商品分页对象
8.7.4.2 编辑ItemController
@CrossOrigin
@RestController
@RequestMapping("/item")
public class ItemController {
    @Autowired
    private ItemService itemService;
    /*
    * 需求:商品列表展现
    * 请求路径: /item/getItemList?query=&pageNum=1&pageSize=10
    * 请求类型: get
    * 请求参数: 使用pageResult对象接收
    * 返回值结果:SysResult(pageResult)
    * */
    @GetMapping("/getItemList")
    public SysResult getItemList(PageResult pageResult){//3个参数
        pageResult=itemService.getItemList(pageResult);//3+2
        return SysResult.success(pageResult);
    }
}
8.7.4.3 编辑ItemService
package com.jt.service;

import com.jt.vo.PageResult;

public interface ItemService {
    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 where 条件 limit 起始位置,显示条数
    *   参数说明:
    *       1.page:用来封装分页参数  第几页/每页多少条
    *       2.queryWrapper 查询的条件构造器
    * */
    @Override
    public PageResult getItemList(PageResult pageResult) {
        //构建模糊查询
        QueryWrapper<Item> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StringUtils.hasLength(pageResult.getQuery()),"title", pageResult.getQuery());
       //2定义分页对象
//        IPage<Item> page = new Page<>(pageResult.getPageNum(), pageResult.getPageSize());
        /*用这个也可以,Page<>实现了IPage<>接口*/
        Page<Item> page = new Page<>(pageResult.getPageNum(), pageResult.getPageSize());
        //page的参数由原来的页数/条数,经过业务添加了 总记录数和分页的结果
        page = itemMapper.selectPage(page, queryWrapper);  //2+2
        long total = page.getTotal(); //获取总数
        List<Item> rows = page.getRecords(); //获取分页结果
        return pageResult.setTotal(total).setRows(rows);
    }
}
8.7.4.4 编辑分页配置类

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

package com.jt.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {
    //将自定义对象交给Spring容器管理,告诉MP,使用的是MySql和MariaDB数据库
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}
8.8 商品新增实现
8.8.1 关于商品信息说明

说明: item的基本信息与ItemDesc的详情信息 可以采用对象的方式进行包裹.

例如: {item: item的数据信息, itemDesc: itemDesc的数据信息}

8.8.2 关于商品新增接口说明
  • 请求路径: 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".......      "
		}
	}
  • 请求参数: 使用ItemVO对象接收
参数名称参数类型参数说明备注
itemItem商品基本信息对象封装不能为null
itemDescItemDesc商品详情信息不能为null
  • ItemVO参数详解:
  • Item对象
参数名称参数类型参数说明备注
titleString商品标题信息不能为null
sellPointString商品卖点信息不能为null
priceInteger商品价格信息不能为null 需要将数据扩大100倍
numInteger商品数量信息不能为null
imagesString商品图片地址信息不能为null
itemCatIdInteger商品父级分类ID不能为null
statusBoolean商品状态信息不能为null
  • itemDesc 对象
 	为了降低商品提交代码的耦合性,将大字段信息详情,采用ItemDesc对象进行封装
参数名称参数类型参数说明备注
idInteger商品Id信息因为Item和ItemDesc是一对一关系 所以需要依赖Item对象的Id值
itemDescString商品详情信息内部包含了大量的html语句
  • 返回值结果:
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据可以为null
8.8.3 封装ItemVO对象
package com.jt.pojo;

import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

@Data
@Accessors(chain = true)
public class ItemVO implements Serializable {
    private Item item;
    private ItemDesc itemDesc;
}
8.8.4 修改表字段类型

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

8.8.5 页面参数传递

8.5.6 编辑ItemCatController
/*
* 商品新增业务接口
* 请求路径: http://localhost:8091/item/saveItem
* 请求类型: post
* 前端传递参数分析 ItemVO
* */
@PostMapping("/saveItem")
public SysResult saveItem(@RequestBody ItemVO itemVO){//Item/ItemDesc
    itemService.saveItem(itemVO);
    return SysResult.success();
}
8.8.7 编辑ItemCatService
void saveItem(ItemVO itemVO);
/*
* 需求:完成两部分的入库操作
* 步骤一:完成Item入库操作
* 步骤二:完成ItemDesc入库操作Item.id=ItemDesc.id
* mybatis 知识讲解:
*   xml映射文件中配置主键或者数据的回显
*   <insert id="xxxx" useGeneratedKeys="true"
*             keyColumn="id"
*             keyProperty="id">
*         新增sql
*  </insert>
* MP的知识点讲解:
*   MP基于对象的方式操作数据,如果实现了数据的入库操作
*   则数据库里的数据会与对象进行绑定,动态回显
* 难点知识:如何实现数据的回显!!!!
* */
@Override
@Transactional
public void saveItem(ItemVO itemVO) {
    //1.实现Item对象的入库
    Item item = itemVO.getItem().setStatus(true);
    //主键自增:刚开始id为空,但入库之后,id在数据库中会自动赋值
    //那么赋值之后,对象中的id是否有值?  没有
    itemMapper.insert(item);
    //2.实现ItemDesc对象入库
    ItemDesc itemDesc = itemVO.getItemDesc();
    itemDesc.setId(item.getId());
    itemDescMapper.insert(itemDesc);
}
8.9 商品删除操作
8.9.1 商品删除业务接口
  • 请求路径: /item/deleteItemById
  • 请求类型: delete
  • 请求参数:
参数名称参数说明备注
id商品id不能为null
  • 返回值结果:
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据可以为null
8.9.2 编辑ItemController
/*
* 商品数据删除
* 请求路径: /item/deleteItemById
* 请求类型: delete
* 请求参数:id
* 返回值结果:SysResult
* */
@DeleteMapping("/deleteItemById")
public SysResult deleteItemById(Integer id){
    itemService.deleteItemById(id);
    return SysResult.success();
}
8.9.3 编辑ItemService
void deleteItemById(Integer id);
@Override
@Transactional
public void deleteItemById(Integer id) {
    itemMapper.deleteById(id);
    itemDescMapper.deleteById(id);
}
8.10 商品状态修改
8.10.1 商品删除业务接口
  • 请求路径: /item/updateItemStatus
  • 请求类型: put
  • 请求参数: 使用对象接收
参数名称参数说明备注
id商品id不能为null
status状态信息不能为null
  • 返回值结果:
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据可以为null
8.10.2 编辑ItemController
/*
*  商品状态修改
*  请求路径: /item/updateItemStatus
*  请求参数: 使用对象接收 /id/status
*  返回值结果: SysResult
* */
@PutMapping("/updateItemStatus")
public SysResult updateItemStatus(@RequestBody Item item){
    itemService.updateItemStatus(item);
    return SysResult.success();
}
8.10.3 编辑ItemService
void updateItemStatus(Item item);
@Override
@Transactional
public void updateItemStatus(Item item) {
    itemMapper.updateById(item);
}
8.11 文件上传操作
8.11.1 文件上传业务说明

说明:当用户选择多张图片时,则是一张一张的传输.

  • 请求路径: http://localhost:8091/file/upload
  • 请求类型: post
  • 请求参数:
参数名称参数说明备注
file文件上传的参数名称file中携带的是二进制信息
  • 返回值结果:
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据返回ImageVO对象
  • ImageVO对象说明
参数名称参数类型参数说明备注
virtualPathString图片实际路径 不包含磁盘信息例如: 2021/11/11/a.jpg 不需要写磁盘地址
urlPathString图片url访问地址http://image.jt.com/2021/11/11/a.jpg 需要指定域名地址
fileNameString文件上传后的文件名称UUID.type
8.11.2 ImageVO对象封装
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 ImageVO implements Serializable {
    private String virtualPath; //动态变化的路径
    private String urlPath;     //图片url访问地址  网络地址
    private String fileName;    //图片名称
}
8.11.3 文件上传入门案例
package com.jt.controller;

import com.jt.service.FileService;
import com.jt.vo.ImageVO;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {
    /*
    * URL:/file/upload
    * 请求类型: post
    * 请求参数:file
    * 返回值:SysResult(ImageVo)
    * 知识点:
    *   SpringMVC针对与IO操作开发了MultipartFile
    *   底层实现就是常规的IO流,简化了用户的操作流程,无需手动关流
    *   SpringMVC中默认支持大小是1M
    *  步骤:
    *       1.获取文件名
    *       2.准备文件路径
    *       3.准备文件上传的全路径(1+2)
    *       4.实现文件上传的操作
    * */
    @PostMapping("/upload")
    public SysResult upload(MultipartFile file) throws IOException {//file 名不能乱写,要与传过来的变量名对应
        //获取文件名称 a.jpg
        String filename = file.getOriginalFilename();
        //准备文件的目录
        String dir = "E:/StudyPractise/images";
        File dirFile = new File(dir);
        if(!dirFile.exists()){//判断目录是否存在
            dirFile.mkdirs(); //创建多级目录
        }
        String path = dir+"/"+filename;
        //实现文件的上传
        file.transferTo(new File(path));//此处异常属于检查异常
        return SysResult.success();
    }
}
8.11.4 文件上传完整代码
8.11.4.1 编辑FileController
package com.jt.controller;

import com.jt.service.FileService;
import com.jt.vo.ImageVO;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {
    @Autowired
    private FileService fileService;

    @PostMapping("/upload")
    public SysResult upload(MultipartFile file){
        ImageVO imageVO = fileService.upload(file);
        if(imageVO==null){
            return SysResult.fail();
        }
        return SysResult.success(imageVO);
    }
}
8.11.4.2 编辑FileService
package com.jt.service;

import com.jt.vo.ImageVO;
import org.springframework.web.multipart.MultipartFile;

public interface FileService {
    ImageVO upload(MultipartFile file);
}
package com.jt.service;

import com.jt.vo.ImageVO;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

@Service
public class FileServiceImpl implements FileService{
    //定义文件根目录
    private String rootDir="E:/StudyPractise/images";
    //定义图片网络服务器地址
    private String rootURL="http://image.jt.com";
    /*
    * 思路:
    *   1.校验文件是否为图片
    *   2.防止文件为恶意程序  木马.exe.jpg
    *   3.分目录存储       按照时间维度划分
    *   4.防止文件重名     UUID
    * */
    @Override
    public ImageVO upload(MultipartFile file) {
        //a.jpg  jpg/png/gif
        //获取图片名称全部转小写(windows系统中不区分大小写,所以当用户传的是A.JPG时就会出现Bug(文件正确却传不了),所以得转小写)
        String filename = file.getOriginalFilename().toLowerCase();

        if(!filename.matches("^.+\\.(jpg|png|gif)$")){
            return null;
        }
        //2.通过校验宽度和高度判断是否为图片  bufferedImage代表图片的包装对象
        try {
            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            int width = bufferedImage.getWidth();
            int height = bufferedImage.getHeight();
            if(width==0 || height==0){
                return null;
            }
            //实现分目录的存储
            String datePath = new SimpleDateFormat("/yyyy/MM/dd/").format(new Date());
            //E:/StudyPractise/images/2025/12/12/
            String fileDir = rootDir + datePath;
            File dirFile = new File(fileDir);
            if(!dirFile.exists()){
                dirFile.mkdirs();
            }
            //4.动态生成UUID
            String uuid = UUID.randomUUID().toString();
            int index = filename.lastIndexOf(".");
            String type = filename.substring(index);
            filename=uuid+type;
            //5.实现文件上传 准备文件的全路径
            String path = fileDir + filename;
            file.transferTo(new File(path));
            //6.准备ImageVO对象并返回
            //  /2025/12/12/uuid.png
            String virtualPath = datePath+filename;
//            String urlPath = "https://gitee.com/mr-sun-drawing-bed/gallery/raw/master/study/tedu/cgb/Phase3/a.png";
            //http://image.jt.com/2021/11/11/uuid.png
            String urlPath = rootURL+virtualPath;
            System.out.println(urlPath);
            ImageVO imageVO = new ImageVO(virtualPath, urlPath, filename);
            return imageVO;
        } catch (IOException e) {
            /*
            * 一般的条件下,为了不影响代码得结构,将检查异常转化为运行时异常 throw new RuntimeException(e);
            *
            * 解释 : 如果直接向上抛出,会在接口和controller的方法上添加异常改变了代码结构
            *        如果直接tryCatch,只有后端捕获异常,不去上报,没人知道,所以转成运行时异常由aop捕获,再由AOP返回SysResult.fail();
            *        tryCatch一般获取的是检查异常,会一直向上抛出,导致就是谁调用谁处理.
            * */
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    //删除文件  需要文件的全路径
    @Override
    public void delete(String virtualPath) {
        String localPtah=rootDir+virtualPath;
        File file = new File(localPtah);
        if(file.exists()){
            file.delete();
        }
    }
}
8.12 文件删除操作
8.12.1 业务接口说明
  • 请求路径: http://localhost:8091/file/deleteFile
  • 请求类型: delete
  • 请求参数:
参数名称参数说明备注
virtualPath文件上传的虚拟的路径删除时需要磁盘路径一起删除
  • 返回值结果:
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据可以为null

前端请求参数说明:

8.12.2 编辑FileController
 /**
     * 完成图片的删除操作
     * URL: http://localhost:8091/file/deleteFile
     * 参数: virtualPath
     * 返回值: SysResult对象
     */
    @DeleteMapping("/deleteFile")
    public SysResult deleteFile(String virtualPath){

        fileService.deleteFile(virtualPath);
        return SysResult.success();
    }
8.12.3 编辑FileService
 //删除文件  需要文件全路径
    @Override
    public void deleteFile(String virtualPath) {
        String localPath = rootDir + virtualPath;
        File file = new File(localPath);
        if(file.exists()){ //如果文件存在,则删除
            file.delete();
        }
    }

九. 正反向代理

9.1 业务需求

需求说明: 当用户完成图片上传之后 会根据网络地址访问图片,但是查找的图片一定存在于磁盘中.
URL地址: http://image.jt.com/2021/12/16/f9981c76-e6a7-49fa-88d9-3be0851dbf50.jpg
磁盘地址: D:/project3/images/2021/12/16/f9981c76-e6a7-49fa-88d9-3be0851dbf50.jpg

技术难点:
1.当用户访问URL网络地址时,应该按照磁盘地址进行查找!
如何将网络地址转化为磁盘地址?

9.2 反向代理

反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源。同时,用户不需要知道目标服务器的地址,也无须在用户端作任何设定。反向代理服务器通常可用来作为Web加速,即使用反向代理作为Web服务器的前置机来降低网络和服务器的负载,提高访问效率。

3.2.1 知识铺垫

如果网站将真实地址直接告知用户.导致整个服务器不安全. 所以需要采用代理的技术规避该问题.

3.2.2 反向代理特点
  1. 反向代理服务器介于用户和目标服务器之间
  2. 用户从反向代理服务器获取资源, 用户以为反向代理服务器就是目标服务器.
  3. 用户不清楚真实的服务器到底是谁, 保护了真实服务器的信息. 也称之为 "服务器端代理"
9.3 正向代理
3.3.1 正向代理介绍

正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。

3.3.2 正向代理特点
  1. 正向代理服务器介于客户端和原始服务器之间.
  2. 用户访问正向代理服务器,并且指定目标服务器地址.
  3. 服务器端不清楚到底是谁访问的,以为是正向代理服务器访问的. 保护了用户信息. 也称之为客户端代理.

9.4 nginx
9.4.1 Nginx介绍

Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的,第一个公开版本0.1.0发布于2004年10月4日。
其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、简单的配置文件和低系统资源的消耗而闻名。2011年6月1日,nginx 1.0.4发布。
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。

9.4.2 Nginx特点
  1. nginx是一块开源免费的 轻量级的反向代理服务器/web服务器.
  2. nginx并发能力强 理论值: 5万次/秒 实际值: 3万次/秒
    tomcat: 150-220 —>1000个/秒 内存占用200M
  3. nginx占用内存少 不超过2M
9.4.3 Nginx下载

URL地址: http://nginx.org/en/download.html

9.4.4 Nginx 安装和使用

说明:
1.nginx启动会占用80端口!!
2.nginx启动路径不要有中文/空格/特殊字符 底层开发语言:C语言

访问测试:

9.4.5 关于nginx 80端占用问题说明
  1. 检查端口号占用

  1. 打开任务管理器

  1. 关于80端口 被PID=4占用说明

9.4.6 关于nginx 启动项说明

说明: nginx的启动每次都会启动2个进程项.
主进程: 主要提供反向代理服务. 占用内存大的
守护进程: 防止主进程意外关闭. 占用内存小的

9.4.7 nginx 命令(熟练掌握)

说明: nginx的命令需要在根目录中运行 nginx.exe 所在的路径就是根目录
命令:

  1. 启动nginx start nginx
  2. 重启nginx nginx -s reload
  3. 关闭nginx nginx -s stop
9.5 Nginx反向代理机制
http {
		 #每个反向代理服务,就是一个server
		 server {
		 	#nginx默认监听端口号 默认都是80
			listen       80;
			#nginx要拦截的域名
			server_name  localhost;
			
			#拦截所有的请求
			location / {
				# root 代表代理的是一个目录 
            	root   html;
            	# 配置默认访问的首页
            	index  index.html index.htm;
        	}
		 }	
	}

9.5.1 Nginx实现图片代理

URL地址: http://image.jt.com/2021/12/16/f9981c76-e6a7-49fa-88d9-3be0851dbf50.jpg
磁盘地址: D:/project3/images/2021/12/16/f9981c76-e6a7-49fa-88d9-3be0851dbf50.jpg
代理机制:
域名: http://image.jt.com:80

代理为:
E:/StudyPractise/images

配置图片代理:

#配置图片代理
	server {
		#监听端口号
		listen 80;
		#服务名称
		server_name  image.jt.com;
		#
		location / {
			#根目录
			root E:/StudyPractise/images;
		}
	}

编辑完成之后,重启nginx

9.5.2 图片回显流程图

9.5.3 修改hosts文件

文件位置: C:\Windows\System32\drivers\etc

9.5.4 修改hosts文件
127.0.0.1       localhost
127.0.0.1		image.jt.com
127.0.0.1		manage.jt.com
127.0.0.1		web.jt.com

十.Windows 项目发布

10.1 项目发布检查

1.检查所有的sql中的表名是否存在大小写问题. 检查注解 检查Sql
2.根据码云 检查POM.xml文件内容
3.将项目打包 install 命令
4.前端项目打包

检查是否生成dist目录

)

10.2 前端发布准备
  1. 修改main.js的路径

  1. 编辑AddItem.vue文件

  1. 将前端项目进行编译

如果将上述的操作修改完成,之后需要将程序编译。如图所示

10.3 前端项目发布
10.3.1 业务说明

将编译之后的dist目录 复制到nginx的根目录中。

10.3.2 前端项目发布

需求: 用户通过http://web.jt.com 访问 dist/index.html

#配置前端代理
	server {
		listen 80;
		server_name  web.jt.com;

		location / {
			root dist;
			index index.html;
		}
	}

重启nginx

10.3.3 前端代码测试

10.4 后端项目发布
10.4.1 动态获取端口号
package com.jt.controller;

import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin
public class PortController {

    //动态获取端口号
    @Value("${server.port}")
    private Integer port;

    @GetMapping("/getPort")
    public String getPort(){

        return "当前端口号:"+port;
    }
}
10.4.2 项目发布

命令: java -jar 8091.jar

问题说明: 如果启动失败 检查pom.xml文件 或者使用我的jar包

10.4.3 nginx完成后端发布
#配置后端代理
	server {
		listen 80;
		server_name  manage.jt.com;

		location / {
			#proxy_pass 映射的是请求的地址
			proxy_pass http://localhost:8091;
		}
	}
10.4.4 后端项目测试

通过:http://manage.jt.com/itemCat/findItemCatList/3 测试后端域名是否可用

10.5 集群部署
10.5.1 集群部署流程图

10.5.2 部署集群

说明: 分别准备3台tomcat服务器 端口号分别为8091/8092/8093

10.5.3 启动多个进程

说明: 勾选其中的选项 ,可以平行运行多个进程

启动3台服务器 完成测试。

10.5.4 部署集群
#配置后端代理
	server {
		listen 80;
		server_name  manage.jt.com;

		location / {
			#proxy_pass 映射的是请求的地址
			#proxy_pass http://localhost:8091;
			#访问集群
			proxy_pass http://tomcats;
		}
	}

	#配置tomcat服务器集群 1.轮询策略
	upstream tomcats {
		server  127.0.0.1:8091;
		server  127.0.0.1:8092;
		server  127.0.0.1:8093;
	}
10.6 nginx负载均衡策略
10.6.1 轮询策略

说明: 按照配置文件的顺序 依次访问

#配置tomcat服务器集群 1.轮询策略
	upstream tomcats {
		server  127.0.0.1:8091;
		server  127.0.0.1:8092;
		server  127.0.0.1:8093;
	}
10.6.2 权重策略

说明: 可以根据服务器性能,灵活的设置配比

#配置tomcat服务器集群 1.轮询策略  2.权重策略
	upstream tomcats {
		server  127.0.0.1:8091 weight=6;
		server  127.0.0.1:8092 weight=3;
		server  127.0.0.1:8093 weight=1;
	}
10.6.3 IP_HASH策略

说明: 如果需要让用户与服务器进行绑定. 则使用IPHASH

#配置tomcat服务器集群 1.轮询策略  2.权重策略 3.iphash策略
	upstream tomcats {
		ip_hash;
		server  127.0.0.1:8091 weight=6;
		server  127.0.0.1:8092 weight=3;
		server  127.0.0.1:8093 weight=1;
	}
10.6.4 nginx常见属性
  1. down属性 如果down属性标识了服务器,则nginx不会再次访问该服务器

  2. backup属性 设置备用机, 正常情况下,备用机不会被访问,但是当主机遇忙时/或者宕机时,备用机才会被访问.

#配置tomcat服务器集群 1.轮询策略  2.权重策略 3.iphash策略
	upstream tomcats {
		#ip_hash;
		server  127.0.0.1:8091 weight=6 down;
		server  127.0.0.1:8092 weight=3 down;
		server  127.0.0.1:8093 weight=1 backup;
	}
10.6.5 项目如何实现在线部署

说明:
1.首先需要制定上线计划 分批次上线部署.
2.首先可以先将一部分服务器down掉,之后替换新的jar包,重启服务器,测试通过之后,nginx正式接通.
3.重复执行多次,直至项目部署完成为止.
nginx启动的速度很快,所以几乎对用户没有影响.

十一. Linux项目发布

11.1 安装JDK
11.1.1 上传安装包

说明: 将windows中的tar.gz的文件上传到Linux系统中,如图所示

11.1.2 解压JDK

命令: tar -xvf jdk-8u51-linux-x64.tar.gz

删除/修改文件名称:

11.1.3 检查JDK是否有效

11.1.4 编辑JDK 环境配置

编辑:linux 环境配置 vim /etc/profile
环境变量生效:

  1. 重启Linux服务器。
  2. 刷新环境变量 source /etc/profile
#设定jdk环境
export JAVA_HOME=/usr/local/src/jdk1.8
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib

11.2. Linux 安装mariadb数据库
11.2.1 项目发布流程

11.2.2 Linux安装MariaDB数据库

Linux安装MariaDB数据库

11.2.3 导入数据库

11.2.4 Linux项目发布指南
  1. 修改文件上传的目录

  1. 检查Linux mysql密码是否正确

11.3 tomcat服务器发布
11.3.1 项目打包

说明:Linux项目部署准备2台tomcat服务器, 分别是8091/8092

11.3.2 jar包文件上传

上传jar包文件

11.3.3 启动项目
11.3.3.1 前端运行

命令: java -jar 8091.jar

测试启动是否正常
说明:通过IP地址+端口+测试路径 判断后端服务器发布是否正常

前端运行的弊端:

  1. 如果采用上述的指令,则当前的终端与tomcat服务器绑定。如果终端关闭或者退出。tomcat服务器也会关闭
  2. 可以通过末尾添加 &号的方式 缓解终端与tomcat绑定的问题。但是不能解决
11.3.3.2 后端运行

命令: nohup java -jar 8091.jar => 8091.log &
命令说明: 将运行的方式改为后端运行,所有的日志输出端到8091.log日志中。通过cat 8091.log 检查日志

检索java进程命令:jps

关闭进程:

  1. kill PID号 普通的关闭
  2. kill -15 PID号 较为强硬的关闭
  3. kill -9 PID号 强制关闭
11.3.3.3 脚本运行(了解)

说明:Linux中常见的脚本 shell脚本 以xxx.sh结尾
编辑脚本: vim start.sh

启动脚本: sh start.sh

11.4 Nginx安装步骤
11.4.1 官网介绍

http://nginx.org/en/download.html

11.4.2 上传安装包

上传到指定目录中 /usr/local/src

11.4.3 解压Nginx 压缩文件

1.删除安装文件

 rm -f nginx-1.21.3.tar.gz

2.修改文件名称

 mv nginx-1.19.4   nginx-source

改完名称之后的结果:

11.4.5 安装nginx服务器

说明:在nginx-source的根目录中执行如下命令

需要提前安装nginx的依赖项

 yum -y install gcc pcre-devel zlib-devel openssl openssl-devel

1./configure

直接结果:

问题补充: 如果 ./configure 报错没有权限,则执行如下命令

	chmod + x configure
  1. 在nginx-source的根目录中 执行 make

3.根目录中执行 make install

11.4.6 Nginx工作目录说明

说明: 查找工作目录
路径: whereis nginx

11.4.7 跳转到Nginx工作目录

11.4.8 nginx命令

进入nginx/sbin目录中执行

1.启动命令: ./nginx
2.重启命令: ./nginx -s reload
3.关闭命令: ./nginx -s stop

11.5上传前端静态资源

说明: 将前端的web资源上传到nginx的根目录下

  1. 前端文件位置

2.上传目录位置

11.6 修改nginx配置文件

需求说明:
1.实现图片反向代理
2.前端业务代理
3.后端tomcat集群代理

#配置图片代理 image.jt.com
	server {
		listen 80;
		server_name image.jt.com;
		location / {
			root /usr/local/src/images;
		}
	}
	
	#配置前端代理 www.jt.com
	server {
		listen 80;
		server_name web.jt.com;
		location / {
			root  dist;
			index index.html;
		}
	}

	#配置后台服务器集群
	upstream tomcats{
		server 192.168.126.129:8091;
		server 192.168.126.129:8092;
	}
	
	#配置后端代理 manage.jt.com
	server {
		listen 80;
		server_name manage.jt.com;

		location / {
			proxy_pass http://tomcats;
		}
	}

编辑成功之后,将nginx.conf文件上传到Linux系统中(删除原有文件),

上传之后重启nginx服务:

11.7 修改hosts文件
#	127.0.0.1	image.jt.com 
#	127.0.0.1	manage.jt.com
#	127.0.0.1	web.jt.com
	192.168.126.129	image.jt.com 
	192.168.126.129	manage.jt.com
	192.168.126.129	web.jt.com
11.8 项目发布测试

自行测试相关功能: 登录,上传图片,图片回显,上传路径等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值