京淘项目整合

1.环境搭建

1.1 安装前端脚手架

1.1 安装node.js
(1)安装node.js
(2)检查node.js 和 npm 的版本
(3)切换淘宝镜像
(4)安装vue客户端工具 npm install -g @vue/cli --force
(5)检查安装的成果 vue ui
在这里插入图片描述
1.2 配置前端脚手架
解压文件,并在客户端中导入打开运行

1.2.后端项目搭建

2.1 创建项目
2.2 编辑pom.xml文件

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--引入插件lombok 自动的set/get/构造方法插件  -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--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>
    </dependencies>

2.3 编辑层级代码
在这里插入图片描述

1.3.关于脚手架的说明

1.3.1 目录结构
在这里插入图片描述
1.3.2 关于main.js的说明
概念: VUE 引入组件的概念
组件好处: 封装CSS样式/封装JS样式/HTML代码片段. xxxx.vue命名.
父子组件参数传递: 需要使用 Vue.prototype.h t t p = a x i o s 以 后 使 用 http = axios 以后使用http=axios以后使用http.xxx 发起Ajax请求.
在这里插入图片描述
1.3.3 关于路由说明

const routes = [
  {path: '/', redirect: '/login'},
  {path: '/login', component: Login},
  {path: '/elementUI', component: ElementUI}
]

1.4. 用户登录业务实现

1.4.1 页面JS分析
在这里插入图片描述
1.4.2 用户登录JS
在这里插入图片描述
1.4.3 用户业务接口文档说明
请求路径: /user/login
请求方式: POST
请求参数
在这里插入图片描述

响应数据 SysResult对象
在这里插入图片描述
返回值格式如下:


	{"status":200,"msg":"服务器调用成功!","data":"1e893a97634847b3a8b499b173bea620"}
	

1.4.4 编辑SysResult对象

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.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/findAll")
    public List<User> findAll(){

        return userService.findAll();
    }

    public SysResult aa(){

        return new SysResult(201,"xxxx",null);
    }
}

1.5 用户登录模块实现

1.5.1 加密算法MD5
规则说明: MD5加密算法,只能由明文转化为密文. 不可以反向编译.
破解MD5加密算法:
在这里插入图片描述
1.5.2 编辑UserController

 /**
     *  URL地址: /user/login
     *  请求类型: post
     *  参数:    JSON串 username/password
     *  返回值:  SysResult对象
     */
    @PostMapping("/login")
    public SysResult login(@RequestBody User user){

        //返回值一个字符串 token
        String token = userService.login(user);
        if(token == null){
            return SysResult.fail(); //201
        }
        return SysResult.success(token); //200
    }

1.5.3 编辑UserService

/**
     * 业务需求:
     *  1.将密码进行加密处理
     *  2.根据username/password 查询数据库获取数据.
     *  3. 有数据 用户名密码正确
     *     无数据 用户名和密码错误
     * @param user
     * @return
     */
    @Override
    public String login(User user) {
        //1.将密码加密处理
        String password = user.getPassword();
        //2.利用md5加密算法 进行加密
        String md5Pass = DigestUtils.md5DigestAsHex(password.getBytes());
        user.setPassword(md5Pass);
        //3.查询数据库数据
        User userDB = userMapper.findUserByUP(user);
        if(userDB == null){
            //说明: 用户名和密码错误
            return null;
        }
            //说明: 用户名和密码正确
        return "秘钥";
    }

1.5.4 编辑UserMapper

public interface UserMapper {

    @Select("select * from user")
    List<User> findAll();
    @Select("select * from user where username=#{username} and password=#{password}")
    User findUserByUP(User user);
}

1.5.5 关于秘钥说明
说明: 当用户登录之后,可以跳转到系统的首页. 到了系统首页之后,用户可以进行其它的业务操作. 系统如何判断正在操作业务的用户 已经登录?

业务说明:
一般在登录认证系统中,都会返回秘钥信息.来作为用户登录的凭证.
秘钥特点: 最好独一无二.

动态生成秘钥: UUID

 /**
     * 业务需求:
     *  1.将密码进行加密处理
     *  2.根据username/password 查询数据库获取数据.
     *  3. 有数据 用户名密码正确
     *     无数据 用户名和密码错误
     * @param user
     * @return
     */
    @Override
    public String login(User user) {
        //1.将密码加密处理
        String password = user.getPassword();
        //2.利用md5加密算法 进行加密
        String md5Pass = DigestUtils.md5DigestAsHex(password.getBytes());
        user.setPassword(md5Pass);
        //3.查询数据库数据
        User userDB = userMapper.findUserByUP(user);
        if(userDB == null){
            //说明: 用户名和密码错误
            return null;
        }
            //说明: 用户名和密码正确,返回秘钥
        String uuid = UUID.randomUUID().toString()
                          .replace("-","");
        return uuid;
    }

1.6 关于Session和Cookie说明

1.6.1 业务需求说明
用户的请求是一次请求,一次响应. 当响应结束时,服务器返回的数据 也会销毁. 问题: 如果销毁了token 则认为用户没有登录.需要重复登录.
如何解决该问题: 应该持久化token信息.

1.6.2 Session
Session:在计算机中,尤其是在网络应用中,称为“会话控制”。Session对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的Web页之间跳转时,存储在Session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web页时,如果该用户还没有会话,则Web服务器将自动创建一个 Session对象。当会话过期或被放弃后,服务器将终止该会话。Session 对象最常见的一个用法就是存储用户的首选项。例如,如果用户指明不喜欢查看图形,就可以将该信息存储在Session对象中。有关使用Session 对象的详细信息,请参阅“ASP应用程序”部分的“管理会话”。注意会话状态仅在支持cookie的浏览器中保留。

特点: Session总结
(1)Session 称之为 会话控制 技术
(2)Session生命周期, 会话结束 对象销毁.
(3)Session的数据存储在内存中.
(4)Session只可以临时存储数据.不能永久存储.

1.6.2 Cookie总结
Cookie,有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息 [1] 。

特点:

  1. 类型: 小型文本文件.
  2. 文件通常是加密的.
  3. cookie 可以临时或者永久存储.

1.6.3 关于Cookie和Session说明
(1)手机银行的登录信息? Session存储. 数据安全性高
(2)腾讯视频会员登录信息? Cookie存储 1个月免密登录.
(3)公司的财务系统登录信息? Session存储
(4)购物系统的登录信息? Cookie存储.

1.6.4 用户登录信息存储

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

在这里插入图片描述

2. 系统跳转

2.1 系统首页跳转

1.编辑路由JS
在这里插入图片描述

2.首页跳转效果
在这里插入图片描述

2.2 路由导航守卫

2.2.1 需求说明

说明: 当用户在没有登录的条件下. 用户可以手动输入请求地址. 可以直接跳转项目. 这样的方式非常不安全.
解决方案: 前端通过拦截器 控制用户是否登录.
拦截器说明: 用户拦截的是URL中跳转的路径.
结果:
1.拦截 跳转到登录页面.
2.放行 跳转用户目标页面.

2.2.2 路由导航守卫

说明: 编辑index.js文件

/**
 *  参数说明:
 *    1.to 到哪里去
 *    2.from 从哪里来
 *    3.next 请求放行
 *  拦截器策略:
 *    1.如果用户访问/login登录页面 直接放行
 *    2.如果访问其它页面,则校验是否有token
 *      有token     放行
 *      没有token   跳转到登录页面
 */
router.beforeEach((to,from,next) => {
  if(to.path === '/login') return next()
  //获取token数据信息
  let token = window.sessionStorage.getItem('token')
  if(token === null || token === ''){
     return next("/login")
  }
  //放行请求
  next()
})

3 左侧菜单展现

3.1 搭建层级代码

在这里插入图片描述

3.1.1 表设计说明

在这里插入图片描述

3.1.2 关于Rights 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; //不是表格固有属性
}

3.1.3 关于层级代码结构

在这里插入图片描述

3.1.4 前端JS说明

1.生命周期函数调用JS函数

  created() {
      //动态获取左侧菜单信息
      this.getMenuList()
      //设定模式选中按钮
      this.defaultActive = window.sessionStorage.getItem("activeMenu")
    },

2.发起Ajax请求获取服务器数据

 async getMenuList() {
       const {data: result} =  await this.$http.get('/rights/getRightsList')
       if(result.status !== 200) return this.$message.error("左侧菜单查询失败")
       this.menuList = result.data
      },

3.1.5 接口文档说明

请求路径 /rights/getRightsList
请求类型 GET
请求参数 无
响应数据 SysResult对象
在这里插入图片描述

响应数据如图所示
在这里插入图片描述

3.1.6 父子关系封装/Sql语句写法

要求: 查询所有一级菜单和一级菜单所对应的二级菜单 要求关联查询

SELECT p.id,p.name,p.parent_id,p.path,p.level,p.created,p.updated,
       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 
	 c.parent_id = p.id
WHERE p.parent_id = 0

3.1.7 编辑RightsController

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

    @Autowired
    private RightsService rightsService;

    /**
     * 查询一级二级数据
     * URL: /rights/getRightsList
     * 参数: 无
     * 返回值: SysResult(List<Rights>)
     */
    @GetMapping("/getRightsList")
    public SysResult getRightsList(){

        List<Rights> rights = rightsService.getRightsList();
        return SysResult.success(rights);
    }
}

3.1.8 编辑RightsService

@Service
public class RightsServiceImpl implements RightsService{

    @Autowired
    private RightsMapper rightsMapper;


    @Override
    public List<Rights> getRightsList() {

        return rightsMapper.getRightsList();
    }
}

3.1.9 编辑RightsMapper/xml映射文件

1.RightsMapper接口

public interface RightsMapper {

    public List<Rights> getRightsList();
}

2.编辑Rights映射文件

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

    <select id="getRightsList" resultMap="rightsRM">
       select p.id,p.name,p.parent_id,p.path,p.level,p.created,p.updated,
       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
	        c.parent_id = p.id
        where p.parent_id = 0
    </select>

    <resultMap id="rightsRM" type="Rights" autoMapping="true">
        <id column="id" property="id"/>
        <!--一对一封装子级菜单List集合-->
        <collection property="children" ofType="Rights">
            <!--封装主键ID-->
            <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>

3.1.10 页面效果展现

在这里插入图片描述

3.1.11 关于项目报错调试步骤

说明: 通过控制台 检查请求路径/响应信息/及JS报错信息. 后台服务器端口号固定 8091
在这里插入图片描述

3.2 Ajax 如何实现异步调用?

Ajax特点:

  1. 局部刷新
  2. 异步访问
    核心组件: Ajax 引擎!!!

3.2 关于页面跳转子级路由说明

在这里插入图片描述

3.2.1 现象说明

1.页面路由跳转 用户点击子级菜单时.页面将整个后端页面进行覆盖. 效果如下.
2.实际效果: 应该在首页的右侧 展现新的页面信息.
在这里插入图片描述
在这里插入图片描述

3.2.2 知识点讲解

功能说明: 组件之间的嵌套问题.
定义路由步骤:

  1. 定义路由url地址.
  2. 路由填充位(占位符)
  3. 定义组件(了解)
  4. 定义路由策略
  5. 实现路由挂载

父子组件嵌套总结:

  1. 定义父级组件
    在这里插入图片描述
  2. 路由策略:
    在这里插入图片描述

3.如果需要嵌套 通过 router-view 进行占位, 通过children属性定义父子关系的结构. 当点击子组件时,会在父级组件的router-view中展现子组件.

3.2.3 首页嵌套规则

1.在Home组件中定义路由的占位符
在这里插入图片描述

2.定义父子组件的策略
在这里插入图片描述

4 用户管理

4.1 用户列表展现

4.1.1 页面JS分析

1.生命周期函数

 	//利用钩子函数实现数据查询
    mounted(){
      this.getUserList()
    }

2.获取数据函数分析

 	 async getUserList(){
        const {data: result} = await this.$http.get('/user/list',{
           params: this.queryInfo
        })
        if(result.status !== 200) return this.$message.error("用户列表查询失败")
        this.userList = result.data.rows
        this.total = result.data.total
        console.log("总记录数:"+this.total)
      },

4.1.2 业务接口说明

请求路径: /user/list
请求类型: GET
请求参数: 后台使用PageResult对象接收
请求案例: http://localhost:8091/user/list?query=查询关键字&pageNum=1&pageSize=10
在这里插入图片描述
响应参数: SysResult对象 需要携带分页对象 PageResult
在这里插入图片描述
PageResult 对象介绍
在这里插入图片描述

返回值效果:

{"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
		}
		]
	}
}

4.1.3 封装PageResult对象

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class PageResult {   //封装VO对象
    private String query;
    private Integer pageNum;
    private Integer pageSize;
    private Long    total;
    private Object  rows;
}

4.1.4 编辑UserController

/**
     * 业务说明:
     *  1. /user/list
     *  2.请求类型: GET
     *  3.参数接收: 后台使用PageResult对象接收
     *  3.返回值: SysResult<PageResult>
     */
     @GetMapping("/list")
     public SysResult getUserList(PageResult pageResult){//参数3
         //业务查询总数.分页条数.
         pageResult = userService.getUserList(pageResult);
        return SysResult.success(pageResult);//参数5个
     }

4.1.5 编辑UserService

 /**
     * 要求查询  1页10条
     * 特点: 数组的结果  口诀: 含头不含尾
     * 语  法:  select * from user limit 起始位置,查询的条数
     * 第一页:  select * from user limit 0,10       0-9
     * 第二页:  select * from user limit 10,10      10-19
     * 第三页:  select * from user limit 20,10      20-29
     * 第N页:   select * from user limit (n-1)*10,10
     * @param pageResult
     * @return
     */
    @Override
    public PageResult getUserList(PageResult pageResult) {
        //1.记录总数 total
        long total = userMapper.getTotal();
        //2.分页后的数据
        int size = pageResult.getPageSize();
        int start = (pageResult.getPageNum() - 1) * size;
        List<User> rows = userMapper.findUserListByPage(start,size);
        return pageResult.setTotal(total).setRows(rows);
    }

4.1.6 编辑UserMapper接口

	@Select("select * from user limit #{start},#{size}")
    List<User> findUserListByPage(@Param("start") int start,@Param("size") int size);

4.1.7 页面效果展现

在这里插入图片描述

5. 利用子查询实现左侧菜单列表

5.1 编辑映射文件

5.1.1 子查询Sql语句写法

**	/*查询一级菜单信息*/
SELECT * FROM rights WHERE parent_id = 0
/* 查询从表数据 */
SELECT * FROM rights WHERE parent_id = 3
**

5.1.2 xml映射文件写法

	 <!--利用子查询的方式实现数据获取
        1.查询主表信息
     -->
    <select id="getRightsList" resultMap="rightsRM">
        select * from rights where parent_id = 0
    </select>
    <resultMap id="rightsRM" type="Rights" autoMapping="true">
        <!--主键信息-->
        <id property="id" column="id"></id>
        <collection property="children" ofType="Rights"
                    select="findChildren" column="id"/>
    </resultMap>
    <select id="findChildren" resultType="Rights">
        select * from rights where parent_id = #{id}
    </select>

6. 用户模块管理

6.1 分页查询补充

6.1.1 用户需求说明
说明: 用户的文本输入框,可能有值,也可能没有数据. 则在后端服务器中应该使用动态Sql的方式实现数据的查询.
在这里插入图片描述

6.1.2 编辑UserController

 /**
     * 业务说明:
     *  1. /user/list
     *  2.请求类型: GET
     *  3.参数接收: 后台使用PageResult对象接收
     *  3.返回值: SysResult<PageResult>
     */
     @GetMapping("/list")
     public SysResult getUserList(PageResult pageResult){//参数3
         //业务查询总数.分页条数.
         pageResult = userService.getUserList(pageResult);
        return SysResult.success(pageResult);//参数5个
     }

6.1.3 编辑UserService

 @Override
    public PageResult getUserList(PageResult pageResult) {
        //1.记录总数 total
        long total = userMapper.getTotal();
        //2.分页后的数据
        //2.1获取每页条数
        int size = pageResult.getPageSize();
        //2.2获取起始位置
        int start = (pageResult.getPageNum() - 1) * size;
        //2.3 获取用户查询的数据
        String query = pageResult.getQuery();
        List<User> rows = userMapper.findUserListByPage(start,size,query);
        return pageResult.setTotal(total).setRows(rows);
    }

6.1.4 编辑UserMapper
1.编辑Mapper接口

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

2.编辑UserMapper.xml 映射文件

<mapper namespace="com.jt.mapper.UserMapper">
    <!--
        resultType: 适合单表查询
        resultMap:  1.多表关联查询  2.字段名称和属性不一致的时候使用
        if 判断条件
               test="query !=null and query !='' 表示同时不满足时条件成立
    -->
    <select id="findUserListByPage" resultType="User">
        select * from user
            <where>
                <if test="query !=null and query !='' ">username like "%"#{query}"%"</if>
            </where>
        limit #{start},#{size}
    </select>
</mapper>

6.2 完成状态修改

6.2.1 业务说明
说明: 通过开关 控制数据库中的 status=true/false 数据库中显示1/0 1/0 与 true/false 对象映射可以互相转化.
根据用户的ID 实现状态的修改.
在这里插入图片描述

6.2.2 前端JS分析
1.知识点
作用域插槽: 一般在表格数据展现时,可以动态获取当前行对象.
用法:
(1)template
(2)slot-scope属性=“变量”

2.页面JS分析

 		   <el-table-column prop="status" label="状态">
              <!-- <template slot-scope="scope">
                  {{scope.row.status}}
              </template> -->
             <template slot-scope="scope">
                <el-switch v-model="scope.row.status" @change="updateStatus(scope.row)"
                  active-color="#13ce66" inactive-color="#ff4949">
                </el-switch>
             </template>
           </el-table-column>

3.页面函数说明

	async updateStatus(user){
         //实现用户状态修改  注意使用模版字符串  ES6中提出的新用法 ${key}
        //const {data: result} = await this.$http.put('/user/status/'+user.id+'/'+user.status)
        const {data: result} = await this.$http.put(`/user/status/${user.id}/${user.status}`)
        if(result.status !== 200) return this.$message.error("用户状态修改失败!")
        this.$message.success("用户状态修改成功!")
      },

6.2.3 业务接口文档说明
请求路径 /user/status/{id}/{status}
请求类型 PUT
请求参数: 用户ID/状态值数据
在这里插入图片描述
返回值结果: SysResult对象

{"status":200,"msg":"服务器调用成功!","data":null}

6.2.4 编辑UserController

/**
     * 业务: 实现用户状态的修改
     * 参数: /user/status/{id}/{status}
     * 返回值: SysResult对象
     * 类型:   put 类型
     */
    @PutMapping("/status/{id}/{status}")
    public SysResult updateStatus(User user){

        userService.updateStatus(user);
        return SysResult.success();
    }

6.2.5 编辑UserService

 //更新操作时修改 status/updated 更新时间
    @Override
    public void updateStatus(User user) {
        user.setUpdated(new Date());
        userMapper.updateStatus(user);
    }

6.2.6 编辑UserMapper

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

6.3 用户新增操作

6.3.1 页面JS分析
1.编辑新增页面
在这里插入图片描述
2.新增页面JS分析
在这里插入图片描述

6.3.2 新增业务接口说明
请求路径 /user/addUser
请求类型 POST
请求参数: 整个form表单数据封装为js对象进行参数传递
在这里插入图片描述
返回值结果: SysResult对象

{"status":200,"msg":"服务器调用成功!","data":null}

6.3.3 编辑UserController

/**
     * 业务: 实现用户新增操作
     * url:  /user/addUser   post类型
     * 参数: 使用User对象接收
     * 返回值: SysResult对象
     */
    @PostMapping("/addUser")
    public SysResult addUser(@RequestBody User user){

        userService.addUser(user);
        return SysResult.success();
    }

6.3.4 编辑UserService

 /**
     * 1.密码进行加密
     * 2.添加状态码信息
     * 3.添加创建时间/修改时间
     * 4.完成入库操作 xml方式
     * @param user
     */
    @Override
    public void addUser(User user) {
        //1.密码加密处理
        Date date = new Date();
        String md5Pass = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
        user.setPassword(md5Pass)
                .setStatus(true)
                .setCreated(date)
                .setUpdated(date); //最好保证时间唯一性.
        userMapper.addUser(user);
    }

6.3.5 编辑UserMapper/xml映射文件
1.编辑mapper接口

 	void addUser(User user);

2.编辑xml映射文件

 <!--完成用户新增操作-->
    <insert id="addUser">
        insert into user(id,username,password,phone,email,status,created,updated)
                value
                        (null,#{username},#{password},#{phone},#{email},#{status},#{created},#{updated})
    </insert>

6.4 修改操作数据回显

6.4.1 页面JS分析
1.按钮点击事件

<el-button type="primary" icon="el-icon-edit" size="small" @click="updateUserBtn(scope.row)"></el-button>

2.数据回显JS

	async updateUserBtn(user){
        this.updateDialogVisible = true
        const {data: result} = await this.$http.get("/user/"+user.id)
        if(result.status !== 200) return this.$message.error("用户查询失败")
        this.updateUserModel = result.data
      },

6.4.2 页面接口文档
请求路径: /user/{id}
请求类型: GET
返回值: SysResult对象
在这里插入图片描述
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
	 }
 }

6.4.3 编辑UserController

 /**
     * 根据ID查询数据库
     * URL:/user/{id}
     * 参数: id
     * 返回值: SysResult(user对象)
     */
    @GetMapping("/{id}")
    public SysResult findUserById(@PathVariable Integer id){

        User user = userService.findUserById(id);
        return SysResult.success(user);
    }

6.4.4 编辑UserService

 @Override
    public User findUserById(Integer id) {

        return userMapper.findUserById(id);
    }

6.4.5 编辑UserMapper

	//原理: mybatis在进行单值传递时(int等基本类型/string) 取值时名称任意
    //     底层通过下标[0]获取的数据和名称无关.
    @Select("select * from user where id=#{id}")
    User findUserById(Integer id);

6.4.6 页面效果展现
在这里插入图片描述

6.5 实现用户的更新操作

6.5.1 页面JS分析
1.页面JS
在这里插入图片描述

2.发起Ajax请求
在这里插入图片描述
6.5.2 修改的业务接口

请求路径: /user/updateUser
请求类型: PUT
请求参数: User对象结构
在这里插入图片描述
返回值: SysResult对象

在这里插入图片描述
JSON格式如下:

{
 "status":200,
 "msg":"服务器调用成功!",
 "data":{}
 }

6.5.3 编辑UserController

/**
     * 业务说明: 实现数据的修改操作
     * URL:  /user/updateUser
     * 参数:  user对象
     * 返回值: SysResult对象
     * 请求类型: PUT
     */
    @PutMapping("/updateUser")
    public SysResult updateUser(@RequestBody User user){

        userService.updateUser(user);
        return SysResult.success();
    }

6.5.4 编辑UserService

//id/phone/email
    @Override
    public void updateUser(User user) {

        userMapper.updateUser(user);
    }

6.5.5 编辑UserMapper

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

6.6 用户删除操作

6.6.1 页面JS修改
在这里插入图片描述

6.6.2 修改的业务接口
请求路径: /user/{id}
请求类型: delete
请求参数:
在这里插入图片描述
返回值: SysResult对象
在这里插入图片描述

6.6.3 编辑UserController

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

    }

6.6.4 编辑UserService

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

6.6.5 编辑UserMapper

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

商品分类管理
1.1 修改案例讲解

/**
     * MP更新操作
     * 说明: 将id=354 的名称改为"六耳猕猴"
     */
    @Test
    public void updateUserById(){
        User user = new User(354,"六耳猕猴",null,null);
        int rows = userMapper.updateById(user);
        System.out.println("响应了"+ rows +"行");
    }

    /**
     * MP更新操作
     * 说明: 将名称为"猴子"的数据改为"齐天大圣"
     * 用法:
     *      1. 参数1, 将修改的数据封装.
     *      2. 参数2, 将修改的条件封装
     * Sql: update demo_user set name="齐天大圣" where name="猴子"
     */
    @Test
    public void updateUserByName(){
        //1.利用pojo对象封装修改数据
        User user = new User(null,"齐天大圣",null,null);
        //1.利用UpdateWrapper封装where条件
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("name","猴子");
        userMapper.update(user,updateWrapper);
        System.out.println("修改操作成功!!!");
    }

7.MP后台项目改造

7.2.1 导入jar包

 		<!--导入MP的jar包文件-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>


7.2.2 编辑POJO ItemCat

/**
 * @author 刘昱江
 * 时间 2021/3/26
 */
@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
    @TableField(exist = false)  //属性不是表字段
    private List<ItemCat> children;
}

7.2.3 编辑ItemCatMapper接口

public interface ItemCatMapper extends BaseMapper<ItemCat> {

}

7.2.4 修改YML文件

#配置端口号
server:
  port: 8091

#管理数据源
spring:
  datasource:
    #高版本驱动使用
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jt?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    #设定用户名和密码
    username: root
    password: root

#SpringBoot整合Mybatis-plus
mybatis-plus:
  #指定别名包
  type-aliases-package: com.jt.pojo
  #扫描指定路径下的映射文件
  mapper-locations: classpath:/mybatis/mappers/*.xml
  #开启驼峰映射
  configuration:
    map-underscore-to-camel-case: true
  # 一二级缓存默认开始 所以可以简化

#打印mysql日志
logging:
  level:
    com.jt.mapper: debug

7.2.5 层级代码结构
在这里插入图片描述

8.商品分类

8.1.1页面跳转

说明: 编辑路由 index.js

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'
//使用路由机制
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}
  ]}
]

页面效果展现
在这里插入图片描述

8.1.2完成商品分类业务

8.1.2 页面JS分析

1.生命周期函数

	//定义初始化函数
    created() {
      //默认获取商品分类列表数据
      this.findItemCatList()
    },

**```
2.获取数据函数说明**

```java
async findItemCatList() {
        const {
          data: result
        } = await this.$http.get("/itemCat/findItemCatList/3")
        if (result.status !== 200) return this.$message.error("获取商品分类列表失败!!")
        this.itemCatList = result.data
      },

8.1.3 业务接口文档

请求路径: /itemCat/findItemCatList/{level}
请求类型: get
请求参数: level

在这里插入图片描述
业务说明: 查询3级分类菜单数据 要求三层结构嵌套
返回值: SysResult对象
在这里插入图片描述

8.1.4 商品分类表结构说明

1.表结构
在这里插入图片描述
2.sql案例练习

/*所有的一级菜单 parent_id=0*/
SELECT * FROM item_cat WHERE parent_id = 0
/*查询汽车用户的二级菜单*/
SELECT * FROM item_cat WHERE parent_id = 249
/*查询车载电器的三级菜单*/
SELECT * FROM item_cat WHERE parent_id = 281

小结: 商品分类表,通过parent_id 来指定父子级关系.

8.1.4. 编辑ItemCatController

@RestController
@CrossOrigin
@RequestMapping("/itemCat")
public class ItemCatController {

    @Autowired
    private ItemCatService itemCatService;

    /**
     * 需求: 查询商品分类信息
     * 参数: /{level}   1一级  2 一二级  3 一二三级
     * url: /itemCat/findItemCatList/{level}  restFul
     * 返回值: SysResult(3级列表信息)
     */
    @GetMapping("findItemCatList/{level}")
    public SysResult findItemCatList(@PathVariable Integer level){

        List<ItemCat> itemCatList = itemCatService.findItemCatList(level);
        return SysResult.success(itemCatList);
    }

}

8.1.5 编辑ItemCatService

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;

    /**
     * 步骤1.查询一级菜单列表
     * @param level
     * @return
     */
    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        //1.查询一级菜单
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("parent_id",0);
        List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
        //2.查询二级菜单 二级数据是一级数据的子级 封装到一级数据中.
        for(ItemCat oneItemCat : oneList){
            int oneId = oneItemCat.getId(); //一级对象ID
            //清空原始条件  必须有
            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);
        }
        return oneList;
    }
}

8.2上述案例分析

(1)上述的案例 采用多级循环的方式. 将来会耗费服务器资源 100次 内层100次 总循环1万次. 暂时可以接受

(2)上述的代码 频繁访问数据库.导致数据库压力增大.严重时可能导致数据库服务器宕机. 不能接受的
优化策略: 降低数据库访问的次数

8.7 采用数据结构优化代码
思路:

  1. 用户查询所有的数据库信息. (1-2-3所有数据)
  2. 数据结构 Map<k,v> key唯一的, value可以任意类型.
    思路: Map<parentId,List>
    例子:
  3. Map<0,List<一级ItemCat对象>>
  4. Map<249,List<二级ItemCat对象>>
  5. Map<281,List<三级ItemCat对象>>
    利用map 封装父子关系.

8.2 代码具体实现

@Service
public class ItemCatServiceImpl implements ItemCatService{

    @Autowired
    private ItemCatMapper itemCatMapper;

    /**
     * 利用Map集合封装所有的数据库记录
     * 封装数据:
     *      1.遍历所有的数据信息.
     *      2.获取每一个parentId的值.
     * 例子:
     *      1.{id=1,parentId=0,name="张三"}
     *      2.{id=2,parentId=0,name="李四"}
     *      3.{id=3,parentId=1,name="王五"}
     *      Map= {
     *          key : value
     *          0   : List[张三对象,李四对象.....],
     *          1   : List[王五对象......]
     *      }
     * @return
     */
    public Map<Integer,List<ItemCat>> getMap(){
        Map<Integer,List<ItemCat>> map = new HashMap<>();
        //1.查询所有的数据库信息
        List<ItemCat> itemCatList = itemCatMapper.selectList(null);
        //2.将数据封装到map集合中
        for (ItemCat itemCat : itemCatList){
            Integer key = itemCat.getParentId(); //获取parentId当做key
            //3.判断map集合中是否有值.
            if(map.containsKey(key)){
                //有值: 获取List集合,将自己追加到其中
                map.get(key).add(itemCat);
            }else{
                //没值: 添加数据.将自己作为第一个元素填充
                List<ItemCat> list = new ArrayList<>();
                list.add(itemCat);
                map.put(key,list);
            }
        }
        return map;
    }

    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        long startTime = System.currentTimeMillis();
        Map<Integer,List<ItemCat>> map = getMap();
        //根据level获取子级信息
        if(level == 1){ //只获取一级列表信息
            return map.get(0);
        }
        if(level == 2){ //获取一级和二级数据
            return getTwoList(map);
        }
        List<ItemCat> oneList = getThreeList(map);
        long endTime = System.currentTimeMillis();
        System.out.println("优化前的耗时: 500ms,优化后耗时:"+(endTime - startTime)+"ms");
        return oneList;
    }

    //获取三级列表信息  先获取1级数据,再获取2级数据.再获取3级数据

    private List<ItemCat> getThreeList(Map<Integer, List<ItemCat>> map) {
        //1.调用2级菜单方法.
        List<ItemCat> oneList = getTwoList(map);
        //2.实现思路 遍历一级集合,获取二级数据. 封装三级菜单
        for(ItemCat oneItemCat : oneList){
            //2.1 获取二级数据
            List<ItemCat> twoList = oneItemCat.getChildren();
            if(twoList == null || twoList.size()==0){
                //判断二级集合是否为null.如果为null,表示没有二级菜单.
                continue;
            }
            for (ItemCat twoItemCat : twoList){
                int twoId = twoItemCat.getId();
                List<ItemCat> threeList = map.get(twoId);
                twoItemCat.setChildren(threeList);
            }
        }
        return oneList;
    }

    //通过map集合 获取一级二级菜单信息.
    private List<ItemCat> getTwoList(Map<Integer, List<ItemCat>> map) {
        List<ItemCat> oneList = map.get(0);
        //获取二级信息,应该先遍历一级集合
        for (ItemCat oneItemCat : oneList){
            int oneId = oneItemCat.getId();
            //根据一级Id,获取二级集合
            List<ItemCat> twoList = map.get(oneId);
            oneItemCat.setChildren(twoList);
        }
        return oneList;
    }


    /**
     * 步骤1.查询一级菜单列表
     * @param level
     * @return
     */
   /* @Override
    public List<ItemCat> findItemCatList(Integer level) {
        long startTime = System.currentTimeMillis();
        //1.查询一级菜单
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("parent_id",0);
        List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
        //2.查询二级菜单 二级数据是一级数据的子级 封装到一级数据中.
        for(ItemCat oneItemCat : oneList){
            int oneId = oneItemCat.getId(); //一级对象ID
            //清空原始条件  必须有
            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)+"ms");
        return oneList;
    }*/
}

8.3 商品分类新增实现

8.3.1 页面JS分析

1.页面JS分析

 		//定义商品分类新增对象
        itemCatForm: {
          name: '', //定义商品分类名称
          parentId: 0, //默认父级ID=0
          level: 1 //默认是一级菜单
        },
		
		async addItemCatForm() {
        //先将整个表单进行校验
        this.$refs.itemCatFormRef.validate(async validate => {
          if (!validate) return
          const {
            data: result
          } = await this.$http.post("/itemCat/saveItemCat", this.itemCatForm)
          if (result.status !== 200) return this.$message.error("新增商品分类失败")
          this.$message.success("新增商品分类成功!!!")
          //新增成功,则刷新分类列表信息
          this.findItemCatList();
          this.addItemCatDialogVisible = false
        })
      },


8.3.2 商品分类新增接口文档

请求路径: /itemCat/saveItemCat
请求类型: post
请求参数: 表单数据
在这里插入图片描述

返回值: SysResult对象

在这里插入图片描述

9.3.3 编辑ItemCatController

/**
     * 业务需求:  实现商品分类新增
     * URL:  /itemCat/saveItemCat
     * 类型: post
     * 参数: {"name":"AAAAAA","parentId":0,"level":1} json串
     * 返回值: SysResult对象
     */
    @PostMapping("/saveItemCat")
    public SysResult saveItem(@RequestBody ItemCat itemCat){

        itemCatService.saveItem(itemCat);
        return SysResult.success();
    }

9.3.4 编辑ItemCatService

	@Override
    @Transactional  //事务控制
    public void saveItem(ItemCat itemCat) {
        Date date = new Date();
        itemCat.setStatus(true).setCreated(date).setUpdated(date);//启动
        itemCatMapper.insert(itemCat);
    }

8.5. 商品分类删除操作

8.5.1 删除业务接口

请求路径: /itemCat/deleteItemCat
请求类型: delete
业务描述: 当删除节点为父级时,应该删除自身和所有的子节点
请求参数:
在这里插入图片描述
返回值结果 SysResult对象
在这里插入图片描述

8.5.2 前端页面JS

1. 页面JS
<el-button type="danger" icon="el-icon-delete" @click="deleteItemCatBtn(scope.row)">删除</el-button>


2. 发起Ajax请求
//传递分类id
        const {data: result} = await this.$http.delete("/itemCat/deleteItemCat",{params:{id:itemCat.id,level:itemCat.level}})
          if(result.status !== 200) return this.$message.error("删除商品分类失败")
          this.$message.success("删除数据成功")
          //删除成功之后,刷新页面数据
          this.findItemCatList()          

8.5.3 编辑ItemCatController

 /**
     * 完成商品分类的删除操作
     * 1. 编辑URL: /itemCat/deleteItemCat
     * 2. 参数: id/level
     * 3. 返回值: SysResult()
     */
    @DeleteMapping("/deleteItemCat")
    public SysResult deleteItemCat(Integer id,Integer level){

        itemCatService.deleteItemCat(id,level);
        return SysResult.success();
    }

8.5.4 编辑ItemCatService

//删除商品分类数据
    @Override
    public void deleteItemCat(Integer id, Integer level) {
        //判断是否为3级菜单
        if(level == 3){
            itemCatMapper.deleteById(id);
        }

        if(level == 2){
            //如果是二级,应该先获取三级数据之后删除,再删除自己
            //delete from item_cat where parent_id=#{id} or id = #{id}
            QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("parent_id",id)
                        .or()
                        .eq("id",id);
            itemCatMapper.delete(queryWrapper);
        }

        /**
         * 如何删除一级菜单?
         *  1.获取二级ID
         *  终极sql: delete from item_cat where parent_id in (twoIds)
         *          or  id in (twoIds)
         *          or  id = #{id}
         */
        if(level == 1){
            QueryWrapper<ItemCat> queryWrapper = new QueryWrapper();
            queryWrapper.eq("parent_id",id);
            List twoIds = itemCatMapper.selectObjs(queryWrapper);
            //清空数据
            queryWrapper.clear();
            //规则: 如果2级菜单有值,才会删除 2级和三级
            queryWrapper.in(twoIds.size()>0,"parent_id",twoIds)
                        .or()
                        .in(twoIds.size()>0,"id",twoIds)
                        .or()
                        .eq("id",id);
            itemCatMapper.delete(queryWrapper);
        }
    }

8.6 状态修改操作

8.6.1 页面JS分析

1. 页面JS函数
updateStatus(scope.row)

2. 页面JS
 	  //根据ID修改状态信息
      async updateStatus(itemCat) {
        const {
          data: result
        } = await this.$http.put(`/itemCat/status/${itemCat.id}/${itemCat.status}`)
        if (result.status !== 200) return this.$message.error("修改状态失败")
        this.$message.success("状态修改成功")
      },

8.6.2业务接口文档

请求路径: /itemCat/status/{id}/{status}
请求类型: put
请求参数:
在这里插入图片描述

返回值: SysResult对象
在这里插入图片描述

8.6.3 编辑ItemCatController

/**
     * 实现商品分类状态修改
     * URL: /itemCat/status/{id}/{status}
     * 请求类型: PUT/POST
     * 请求参数: id/status
     * 返回值: SysResult对象
     */
    @PutMapping("/status/{id}/{status}")
    public  SysResult updateStatus(ItemCat itemCat){

        itemCatService.updateStatus(itemCat);
        return SysResult.success();
    }

8.6.4 编辑ItemCatService

	@Override
    @Transactional
    public void updateStatus(ItemCat itemCat) {//id/status
        itemCat.setUpdated(new Date());
        itemCatMapper.updateById(itemCat);
    }

8.7 商品分类修改操作

8.7.1 页面JS分析

	1.指定修改的按钮
	<el-button type="success" icon="el-icon-edit" @click="updateItemCatBtn(scope.row)">编辑</el-button>
	
	2. 数据的回显
	//由于有层级关系,所有修改只能修改名称
      updateItemCatBtn(itemCat) {
        this.updateItemCatForm = itemCat
        this.updateItemCatDialogVisible = true
      },

	3. 修改页面的JS
	<el-dialog title="修改商品分类" :visible.sync="updateItemCatDialogVisible" width="50%">
      <!-- 定义分类表单 -->
      <el-form :model="updateItemCatForm" :rules="rules" ref="upDateItemCatForm" label-width="100px">
        <el-form-item label="分类名称:" prop="name">
          <el-input v-model="updateItemCatForm.name"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="updateItemCatDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="updateItemCat">确 定</el-button>
      </span>
    </el-dialog>
     
     4. 修改按钮的JS
       async updateItemCat() {
        //修改商品分类信息
        const {data: result} =
          await this.$http.put('/itemCat/updateItemCat', this.updateItemCatForm)
        if (result.status !== 200) return this.$message.error("更新商品分类失败")
        this.$message.success("更新商品分类成功")
        this.findItemCatList();
        this.updateItemCatDialogVisible = false;
      },

8.7.2 页面接口文档
请求路径: /itemCat/updateItemCat
请求类型: put
请求参数: 表单数据 ItemCat对象
返回值: SysResult对象
在这里插入图片描述
数据解析:
在这里插入图片描述
8.7.3 编辑ItemCatController

/**
     * 修改商品分类名称
     * URL: /itemCat/updateItemCat
     * 参数: 整个form表单  JSON串
     * 返回值: SysResult对象
     */
    @PutMapping("/updateItemCat")
    public SysResult updateItemCat(@RequestBody ItemCat itemCat){

        itemCatService.updateItemCat(itemCat);
        return SysResult.success();
    }

8.7.4 编辑ItemCatService

 //由于页面只修改的name名称.所以sql也只修改name/updated
    @Override
    @Transactional
    public void updateItemCat(ItemCat itemCat) {
        //用户只修改name,updated by id
        ItemCat temp = new ItemCat();
        temp.setId(itemCat.getId())
                .setName(itemCat.getName())
                .setUpdated(new Date());
        itemCatMapper.updateById(temp);
    }

8.8 debug断点调试

在这里插入图片描述

9. 商品模块业务实现

9.1 商品页面跳转

编辑index.js的路由文件

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'
//使用路由机制
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}
  ]}
]

9.2 页面效果

在这里插入图片描述

9.2 数据自动填充

9.2.1 业务分析
说明: 如果每次新增/更新时 都需要添加创建时间/修改时间 这样做比较繁琐.能否通过框架自动的实现数据填充.
在这里插入图片描述

9.2.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;	//表示入库/更新时赋值.
}

9.2.3 配置自动填充类

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    //框架用法说明: MP根据实现类,自动的完成数据的注入. MP框架自动调用
    //metaObject: 指定默认的规则
    @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);
    }
}

9.3 构建商品层级代码
9.3.1 item 表设计
1.3.1 item 表设计
在这里插入图片描述

9.3.2 编辑Item POJO

/**
 * @author 刘昱江
 * 时间 2021/4/7
 */
@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 上架
}

9.3.3 编辑层级代码结构

在这里插入图片描述

9.4 完成商品列表展现

9.4.1 页面分析
1.4.1 页面分析

//1. 生命周期函数
 created() {
      //1.获取商品列表数据
      this.getItemList()
    },

//2. 调用 this.getItemList()
      async getItemList() {
        const {data: result} =
            await this.$http.get("/item/getItemList", {
          params: this.queryItemInfo
        })
        if (result.status !== 200) return this.$message.error("商品列表查询失败")
        this.itemList = result.data.rows
        this.total = result.data.total
      },


9.4.2 接口文档说明
请求路径: /item/getItemList?query=&pageNum=1&pageSize=10
请求类型: get
请求参数: 使用pageResult对象接收

在这里插入图片描述
返回值结果:

在这里插入图片描述

9.4.3 编辑ItemController

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

    @Autowired
    private ItemService itemService;

    /**
     * 业务: 实现商品的分页查询
     * URL: /item/getItemList?query=&pageNum=1&pageSize=10
     * 参数: query=&pageNum=1&pageSize=10
     * 返回值: SysResult(PageResult)
     */
    @GetMapping("/getItemList")
    public SysResult getItemList(PageResult pageResult){//3
        //3+2(总记录数,分页结果)
        pageResult  = itemService.getItemList(pageResult);
        return SysResult.success(pageResult);//5
    }
}

9.4.4 编辑ItemService

@Service
public class ItemServiceImpl implements ItemService{

    @Autowired
    private ItemMapper itemMapper;

    /**
     *  要求: 3+2(总记录数,分页结果)
     *  关于selectPage(参数说明)
     *   参数1: page MP提供的分页对象
     *   参数2: 条件构造器
     * @param pageResult
     * @return
     */
    @Override
    public PageResult getItemList(PageResult pageResult) {
        //1.构建分页对象  参数1: 第几页   参数2: 多少条
        Page<Item> page = new Page<>(pageResult.getPageNum(),pageResult.getPageSize());
        //2.准备条件构造器 构建模糊查询
        QueryWrapper queryWrapper = new QueryWrapper();
        String query = pageResult.getQuery();
        boolean flag = StringUtils.hasLength(query);
        queryWrapper.like(flag,"title",query);

        //3.根据MP查询 实现分页数据的自动封装
        page = itemMapper.selectPage(page,queryWrapper);

        //4.获取数据,返回分页对象
        long total = page.getTotal();
        //获取分页结果
        List<Item> rows = page.getRecords();
        return pageResult.setTotal(total).setRows(rows);
    }
}

9.4.5 编辑分页配置类

@Configuration  //这是配置类
public class MybatisConfig {

    //需要通过配置文件 指定数据库类型.
    // 最新版
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MARIADB));
        return interceptor;
    }
}

9.4.6 页面效果展现
在这里插入图片描述

9.5 商品状态的修改

9.5.1 页面JS分析

		<template slot-scope="scope">
            <el-switch v-model="scope.row.status" active-color="#13ce66" inactive-color="#ff4949"
              @change="updateStatus(scope.row)"></el-switch>
        </template>
		
		async updateStatus(item) {
        const { data: result} =
        await this.$http.put("/item/updateItemStatus", {
          id: item.id,
          status: item.status
        })
        if (result.status !== 200) return this.$message.error("更新状态失败")
        this.$message.success("更新状态成功")
      },

9.5.2 业务接口文档
请求路径: /item/updateItemStatus
请求类型: put
请求参数: 使用对象接收
在这里插入图片描述
返回值结果:
在这里插入图片描述

9.5.3 编辑ItemController

 /**
     * 修改商品的状态信息
     * URL: /item/updateItemStatus
     * 参数: JSON串 {id:xx,status:xx}
     * 返回值: SysResult对象
     */
    @PutMapping("/updateItemStatus")
    public SysResult updateItemStatus(@RequestBody Item item){

        itemService.updateItemStatus(item);
        return SysResult.success();
    }

9.5.4 编辑ItemService

 @Override
    @Transactional //控制事务
    public void updateItemStatus(Item item) {

        itemMapper.updateById(item);
    }

9.6 商品删除操作

9.6.1 页面分析

//根据id删除数据
          const {data: result} = await this.$http.delete("/item/deleteItemById", {
            params: {
              id: item.id
            }
          })
          if (result.status !== 200) return this.$message.error("商品删除失败")
          this.$message.success("商品删除成功")
          //重新获取商品列表信息
          this.getItemList()

9.6.2 业务接口文档

请求路径: /item/deleteItemById
请求类型: delete
请求参数:

在这里插入图片描述
返回值结果:
在这里插入图片描述

9.6.3 编辑ItemController

 /**
     * 业务需求: 根据Id 删除数据
     * URL: /item/deleteItemById
     * 参数: id
     * 返回值: SysResult对象
     */
    @DeleteMapping("/deleteItemById")
    public SysResult deleteItemById(Integer id){

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

9.6.4 编辑ItemService

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

9.7 商品新增操作

9.7.1 实现页面跳转

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}
  ]}
]

页面效果:
在这里插入图片描述

9.7.2 新增页面JS
表结构说明:

  1. 商品的基本信息 保存到item表
  2. 商品的详细信息 保存到item_desc表中.
 /* 添加商品按钮 */
      async addItemBtn(){
        //console.log(this.addItemForm)

        //1.完成表单校验
        this.$refs.addItemFormRef.validate( valid => {
          if(!valid) return this.$message.error("请输入商品必填项")
        })

        //2.完成商品参数的封装
        //2.0 将商品价格扩大100倍
        this.addItemForm.price = this.addItemForm.price * 100
        //2.1 将商品图片的数据转化为字符串
        this.addItemForm.images = this.addItemForm.images.join(",")

        //2.5 实现商品数据提交 用一个大对象 包裹2个小对象
        let submitAddItem = {
          item : this.addItemForm,
          itemDesc: this.itemDesc
        }
        //console.log(submitAddItem)
        let {data: result} = await this.$http.post("/item/saveItem",submitAddItem)
        if(result.status !== 200) return this.$message.error("商品添加失败")
        this.$message.success("商品添加成功")

        //2.5添加完成之后,将数据重定向到商品展现页面
        this.$router.push("/item")
      }

9.7.3 业务接口说明
请求路径: 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对象接收
在这里插入图片描述
ItemVO参数详解:
Item对象
在这里插入图片描述
itemDesc 对象

 	为了降低商品提交代码的耦合性,将大字段信息详情,采用ItemDesc对象进行封装

在这里插入图片描述
返回值结果:
在这里插入图片描述

9.7.4 编辑ItemController

 /**
     * 完成商品新增操作
     * 1.URL地址  http://localhost:8091/item/saveItem
     * 2.参数     post   itemVO JSON串
     * 3.返回值   SysResult对象
     */
    @PostMapping("/saveItem")
    public SysResult saveItem(@RequestBody ItemVO itemVO){

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

9.7.5 编辑ItemService

 @Override
    @Transactional
    public void saveItem(ItemVO itemVO) {
        Item item = itemVO.getItem();
        //设定状态
        item.setStatus(true);
        itemMapper.insert(item);
    }

9.8 商品详情入库

9.8.1 富文本编辑器
说明: 富文本可以在页面中,实现 “所见即所得” 的效果
在这里插入图片描述

9.8.2 引入步骤
1.引入js

/* 导入富文本编辑器 */
import VueQuillEditor from 'vue-quill-editor'

/* 导入富文本编辑器对应的样式 */
import 'quill/dist/quill.core.css' // import styles
import 'quill/dist/quill.snow.css' // for snow theme
import 'quill/dist/quill.bubble.css' // for bubble theme

/* 将富文本编辑器注册为全局可用的组件 */
Vue.use(VueQuillEditor)

2.使用富文本编辑器

 <!-- 定义富文本编辑器-->
 <quill-editor ref="myQuillEditor" v-model="itemDesc.itemDesc">
 </quill-editor>

9.8.3 关于ItemDesc 的说明
说明: 由于Item和ItemDesc 是典型的一对一. 所以要求 item.id = itemDesc.id

@Data
@Accessors(chain = true)
@TableName("item_desc")
public class ItemDesc extends BasePojo{
    @TableId
    private Integer id;
    private String itemDesc;
}

9.8.3 编辑ItemDescMapper

在这里插入代码片public interface ItemDescMapper extends BaseMapper<ItemDesc>{


}

9.8.4 编辑ItemService

 /**
     * 问题: id是主键自增. 入库之后才有主键所以
     *      应该让主键动态回显
     * 1.Mybatis 动态实现回显
     *      <insert id="xxxx" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
     *         insertinto xxxx
     *     </insert>
     * 2.MP是mybatis的增强版本.所以可以实现自动的主键回显!!!
     * @param itemVO
     */
    @Override
    @Transactional
    public void saveItem(ItemVO itemVO) {
        Item item = itemVO.getItem();
        //设定状态
        item.setStatus(true);
        itemMapper.insert(item);
        //获取商品详情
        ItemDesc itemDesc = itemVO.getItemDesc();
        itemDesc.setId(item.getId());
        itemDescMapper.insert(itemDesc);
    }

9.8.5 修改表类型.
说明: 为了存储大字段.修改数据库类型
在这里插入图片描述

10. 商品模块实现

10.1 商品状态修改

10.1.1 业务接口文档
请求路径: /item/updateItemStatus
请求类型: put
请求参数: 使用对象接收
在这里插入图片描述
返回值结果:
在这里插入图片描述

10.1.2 编辑ItemController

/**
     * 实现商品状态修改
     *  - 请求路径: /item/updateItemStatus
     *  - 请求类型: put
     *  - 请求参数:   使用对象接收 id/status
     *  - 返回值:  SysResult对象 JSON串
     */
    @PutMapping("/updateItemStatus")
    public SysResult updateItemStatus(@RequestBody Item item){

        itemService.updateItemStatus(item);
        return SysResult.success();
    }

10.1.3 编辑ItemService
1.编辑接口

 void updateItemStatus(Item item);

2.编辑实现类

 @Override
 @Transactional  //事务注解
  public void updateItemStatus(Item item) {

      itemMapper.updateById(item);
  }

10.2 商品删除操作

10.2.1 编辑业务接口
请求路径: /item/deleteItemById
请求类型: delete
请求参数:
在这里插入图片描述
返回值结果:
在这里插入图片描述

10.2.2 编辑ItemController

/**
     * 完成商品删除操作
     *  - 请求路径: /item/deleteItemById?id=xx
     *  - 请求类型: delete/get
     *  - 请求参数:  id=xxx
     *  - 返回值:   SysResult对象
     */
    @DeleteMapping("/deleteItemById")
    public SysResult deleteItemById(Integer id){

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

10.2.3 编辑ItemService
1.编辑ItemService接口

 void deleteItemById(Integer id);

2.编辑ItemServiceImpl

 //item表/itemDesc表 一对一
    @Override
    @Transactional
    public void deleteItemById(Integer id) {
        itemMapper.deleteById(id);
        itemDescMapper.deleteById(id);
    }

10.3 商品新增实现

10.3.1 路由跳转

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 Item from '../components/items/Item.vue'
import ItemCat from '../components/items/ItemCat.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: '/item', component: Item},
    {path: '/itemCat', component: ItemCat},
    {path: '/item/addItem', component: AddItem}
  ]},
]

10.3.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对象接收
在这里插入图片描述
ItemVO参数详解:
Item对象
在这里插入图片描述
itemDesc 对象

 	为了降低商品提交代码的耦合性,将大字段信息详情,采用ItemDesc对象进行封装

```![在这里插入图片描述](https://img-blog.csdnimg.cn/a2baec77cdcd4a18b714fc824e19ec9c.png)
返回值结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/0e1ef71911954ab8a8bef6ca3e338c8b.png)




**10.3.3 编辑ItemController**

```java
 /**
     *  - 请求路径: http://localhost:8091/item/saveItem
     *  - 请求类型: post
     *  - 前端传递参数分析 使用ItemVO对象接收  JSON
     *  - 返回值: SysResult对象
     */
    @PostMapping("/saveItem")
    public SysResult saveItem(@RequestBody ItemVO itemVO){

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

10.3.4 编辑ItemService
1.编辑ItemService接口

void saveItem(ItemVO itemVO);

2.编辑ItemServiceImpl实现类

  /**
     *  难点: 数据如何回显
     *  1.useGeneratedKeys="true" keyColumn="字段" keyProperty="属性名"
     *  2.MP自动将数据进行回显
     */
    @Override
    @Transactional
    public void saveItem(ItemVO itemVO) {
        //1.入库商品表
        Item item = itemVO.getItem();
        item.setStatus(true);
        //ID主键自增,入库之后才会有ID,要求Id自动回显!!!
        itemMapper.insert(item);
        //2.商品入库操作
        ItemDesc itemDesc = itemVO.getItemDesc();
        itemDesc.setId(item.getId());
        itemDescMapper.insert(itemDesc);
    }

10.4 商品图片上传

10.4.1 文件上传业务接口
知识回顾: GET请求类型 支持最大的上传量2K(不同的浏览器支持不一样 最大),所以一般上传操作使用POST请求.

请求路径: http://localhost:8091/file/upload
请求类型: post
请求参数:
在这里插入图片描述
返回值结果:
在这里插入图片描述
ImageVO对象说明
在这里插入图片描述

10.4.2 编辑ImageVO

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ImageVO {
    private String virtualPath; //动态变化的路径
    private String urlPath;     //网络地址
    private String fileName;    //图片名称  uuid.jpg
}

10.4.3 编辑FileController

package com.jt.controller;

import com.jt.service.FileService;
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;
import java.io.InputStream;

@RestController
@RequestMapping("/file")
@CrossOrigin
public class FileController {

    @Autowired
    private FileService fileService;


    /**
     *  - 请求路径: http://localhost:8091/file/upload
     *  - 请求类型: post
     *  - 请求参数: file: 二进制的字节信息
     *  - 返回值:  SysResult对象(ImageVO)
     *  核心API:
     *       file.transferTo("图片的全路径");
     */
   @PostMapping("/upload")
    public SysResult upload(MultipartFile file) throws IOException {

        ImageVO imageVO = fileService.upload(file);
        if(imageVO == null){
            return SysResult.fail();
        }
        return SysResult.success(imageVO);
    }
}


10.4.4 正则表达式
正则表达式,又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。
许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开的。正则表达式通常缩写成“regex”,单数有regexp、regex,复数有regexps、regexes、regexen。
1.匹配次数
在这里插入图片描述
2.匹配固定次
在这里插入图片描述
3.3. 匹配任意单个字符
在这里插入图片描述
4. 区间字符
在这里插入图片描述
5. 分组取值
在这里插入图片描述

10.4.5 编辑FileService
1.编辑FileService接口

public interface FileService {
    ImageVO upload(MultipartFile file);
}


2.编辑FileServiceImpl

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{

    //定义根目录  windows系统采用该目录    Linux系统需要切换目录
    private String preFilePath = "D:/JT-SOFT/image";
    //定义网络地址前缀
    private String preURLPath = "http://image.jt.com";


    /**
     * 业务思路:
     *      1.校验图片的类型  jpg|png|gif
     *      2.校验是否为恶意程序 木马.exe.jpg
     *      3.将图片进行分目录存储  hash方式存储|时间格式存储
     *      4.防止图片重名,使用UUID.
     * @param file
     * @return
     */

    @Override
    public ImageVO upload(MultipartFile file) {
        //第一步: 校验图片类型
        //1.获取文件名称  abc.JPG
        //2.bug: 文件名称大小写问题 全部转化为小写
        String fileName = file.getOriginalFilename().toLowerCase();
        if(!fileName.matches("^.+\\.(jpg|png|gif)$")){
            //如果图片不满足条件,则程序终止
            return null;
        }

        //第二步: 校验是否为恶意程序
        try {
            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            int height = bufferedImage.getHeight();
            int width = bufferedImage.getWidth();
            if(height==0 || width==0){
                return null;
            }

            //第三步: 分目录存储 提高检索的效率  按照时间将目录划分 /yyyy/MM/dd/
            String datePath = new SimpleDateFormat("/yyyy/MM/dd/").format(new Date());
            // D:/JT-SOFT/image2022/01/10
            String fileDir = preFilePath + datePath;
            //创建目录
            File dirFile = new File(fileDir);
            if(!dirFile.exists()){//目录没有的时候,应该创建目录
                dirFile.mkdirs();
            }

            //第四步: 防止文件重名
            String uuid = UUID.randomUUID().toString().replace("-","");
            int index = fileName.lastIndexOf(".");
            //获取数据 .jpg
            String fileType = fileName.substring(index);
            //新文件名称
            fileName = uuid + fileType;

            //第五步: 实现文件上传
            // D:/JT-SOFT/image/2022/01/10/uuid.jpg
            String filePath = fileDir + fileName;
            file.transferTo(new File(filePath));

            //第六步: 封装ImageVO返回数据    /2022/01/10/uuid.jpg
            String virtualPath = datePath + fileName;

            //第七步: 封装网络地址 http://image.jt.com/2022/01/10/uuid.jpg
            String urlPath =  preURLPath + virtualPath;
            System.out.println(urlPath);
            return new ImageVO(virtualPath,urlPath,fileName);

        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

10.5图片删除操作

10.5.1文件删除JS
在这里插入图片描述

10.5.2业务接口
请求路径: http://localhost:8091/file/deleteFile
请求类型: delete
请求参数:
在这里插入图片描述
返回值结果:
在这里插入图片描述

10.5.3编辑ItemController

 /**
     * 业务说明: 文件删除操作
     * URL地址:   http://localhost:8091/file/deleteFile
     * 请求类型:   delete
     * 参数:      virtualPath 虚拟路径
     * 返回值:    SysResult对象
     */
    @DeleteMapping("/deleteFile")
    public SysResult deleteFile(String virtualPath){

        fileService.deleteFile(virtualPath);
        return SysResult.success();
    }

10.5.4编辑ItemService

@Override
    public void deleteFile(String virtualPath) {
        String filePath = localDirPath + virtualPath;
        File file = new File(filePath);
        if(file.exists()){ //如果文件存在,则删除数据
            file.delete();
        }
    }

10.6图片路径封装

10.6.1 路径分析
1.图片网络地址: https://img14.360buyimg.com/n0/jfs/t2/ac4a3f32ea776da3.jpg
协议://域名:80/虚拟地址

2.图片地址封装: http://image.jt.com:80/2021/11/11/uuid.jpg.

10.6.2 页面URL地址封装

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.net.FileNameMap;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

@Service
public class FileServiceImpl implements FileService{

    private String localDirPath = "E:/project3/images";
    private String preUrl = "http://image.jt.com";

    //1.校验图片类型   xxx.jpg   校验后缀是否为jpg
    @Override
    public ImageVO upload(MultipartFile file) {

        //1.1 获取文件名称  abc.jpg
        String fileName = file.getOriginalFilename();
        //1.2 全部转化为小写字母
        fileName = fileName.toLowerCase();
        //1.3正则校验是否为图片类型
        if(!fileName.matches("^.+\\.(jpg|png|gif)$")){
            //图片类型 不匹配  程序应该终止
            return null;
        }

        //2.校验是否为恶意程序 怎么判断就是一张图 高度和宽度
        //2.1 通过图片对象进行处理
        try {
            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            int height = bufferedImage.getHeight();
            int width = bufferedImage.getWidth();
            if(height == 0 || width == 0){
                return null;
            }
            //3.将图片分目录存储 yyyy/MM/dd
            String dateDir = new SimpleDateFormat("/yyyy/MM/dd/")
                                .format(new Date());
            String dateDirPath = localDirPath + dateDir;
            File dirFile = new File(dateDirPath);
            if(!dirFile.exists()){
                dirFile.mkdirs();
            }

            //4.防止文件重名  动态生成UUID.类型
            //4.1 动态生成UUID
            String uuid = UUID.randomUUID().toString()
                              .replace("-","");
            //4.2 获取图片类型        abc.jpg    .jpg
            String fileType = fileName.substring(fileName.lastIndexOf("."));
            // uuid.jpg
            String newFileName = uuid + fileType;

            //5.实现文件上传 1.准备全文件路径  2. 封装对象实现上传
            String path = dateDirPath + newFileName;
            file.transferTo(new File(path));

            //6. 实现ImageVO数据的返回
            //6.1 准备虚拟路径 /2021/11/11/uuid.jpg
            String virtualPath = dateDir + newFileName;
            //6.2 准备URL地址  域名前缀 + 虚拟路径
            String url =  preUrl + virtualPath;
            System.out.println(url);
            return new ImageVO(virtualPath,url,newFileName);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public void deleteFile(String virtualPath) {
        String filePath = localDirPath + virtualPath;
        File file = new File(filePath);
        if(file.exists()){ //如果文件存在,则删除数据
            file.delete();
        }
    }
}

10.7动态为属性赋值

10.7.1 业务需求
说明: 如果将属性写死到java类中,后期维护时 导致维护不方便.
优化: 可以通过@value注解动态赋值.

在这里插入图片描述
10.7.2 编辑properties配置文件

image.localDirPath=E:/project3/images
image.preUrl=http://image.jt.com

10.7.3 属性动态赋值
在这里插入图片描述

11. 部署JDK

11.1 上传JDK

在这里插入图片描述

11.2 解压压缩包

   tar -xvf jdk-8u51-linux-x64.tar.gz

11.3 删除安装文件

[root@localhost src]# rm -f jdk-8u51-linux-x64.tar.gz


11.4 修改JDK名称

	mv jdk1.8.0_51 jdk1.8 

11.5 检查JDK是否有效

在这里插入图片描述

11.6 JDK环境变量配置

命令:

vim   /etc/profile

配置信息:

#设定jdk环境
export JAVA_HOME=/usr/local/src/jdk1.8
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib

11.7补充知识
浏览器自动将http转化为https问题

在这里插入图片描述
11.8设定安全策略
修改完成之后,需要将 浏览器清空缓存,之后重启. ctrl + shift + delete

在这里插入图片描述

12 Linux 项目部署

12.1 项目搭建流程

在这里插入图片描述

12.2 Linux安装Mariadb数据库

数据库安装

12.3 后端项目发布

12.3.1 后端项目修改
1.修改数据库的用户名和密码
在这里插入图片描述
2.修改文件上传的路径
在这里插入图片描述

12.3.2 后端项目打包
在这里插入图片描述

12.3.3 上传jar包文件
在这里插入图片描述

12.3.4 发布项目

    java -jar 8091.jar

12.3.5 项目测试
在这里插入图片描述

12.4 jar包文件项目启动报错说明

在这里插入图片描述
main方法的检查, 查看是否为主启动类的方法
在这里插入图片描述

12.5 关于Linux 进程项说明

12.5.1 查询进程
1.jps 检索java服务项
在这里插入图片描述
2.检索任意服务

 	ps -ef | grep mysql

12.5.2 杀死进程
关闭当前线程: ctrl + c
杀死进程:

  1. kill PID号 普通的杀死
  2. kill -15 PID号 较为强硬的杀死
  3. kill -9 PID号 强制杀死

12.6 项目后台发布

12.6.1 前台发布测试
说明: 通过java -jar xxx.jar的方式启动项目 该项目与启动的终端绑定在一起. 如果终端关闭.则项目也会关闭.
12.6.2 后台发布
效果: 关闭终端之后, 服务不会受到影响.

	 nohup java -jar 8091.jar => 8091.log &
	 nohup java -jar 8092.jar => 8092.log &

13 安装Nginx服务器

13.1 下载Linux版本Nginx

在这里插入图片描述

13.2 上传到指定目录下

在这里插入图片描述

13.3 解压/删除/改名

1.解压命令

	tar -xvf nginx-1.21.4.tar.gz

2.删除文件/修改名称
在这里插入图片描述

13.4 Nginx 安装步骤

nginx开发语言: C语言

13.4.1 配置安装文件
在这里插入图片描述

13.4.2 安装nginx
1). make
2). make install
在这里插入图片描述

13.5 Nginx实现前/后端项目发布

Nginx安装和使用

13.6 路径说明

  1. 静态资源文件 dist /usr/local/nginx/dist
  2. nginx.conf /usr/local/nginx/config/nginx.conf
  3. nginx启动项目路径 /usr/local/nginx/sbin/
  4. tomcats发布路径 /usr/local/src/tomcats

13.7 易错项

1.查询表失败 可能是表字母大小写问题
2.数据库链接地址: root/root 3306端口
3.image.properties 配置文件中 不要有多余的空格.
4.nginx配置信息:

#配置图片代理
	server {
		listen 80;
		server_name image.jt.com;
		location / {
			root /usr/local/src/images;
		}
	}

	#2.配置tomcat服务器集群
	#2.1 本地IP访问  127.0.0.1:8091
	#2.2 远程访问    192.168.126.129:8091
	upstream tomcats {
		server 192.168.126.129:8091;
		server 192.168.126.129:8092;
	}
	#实现后端服务器代理
	server {
		listen 80;
		server_name manage.jt.com;
		location / {
			proxy_pass http://tomcats;
		}
	}

	#前端服务器代理
	server {
		listen 80;
		server_name www.jt.com;
		location / {
			root dist;
			index index.html;
		}
	}

13.8 日志查看

cat 输出文件所有的内容
more 输出文档所有的内容,分页输出,空格浏览下一屏,q退出
less 用法和more相同,只是通过PgUp、PgOn键来控制
tail 用于显示文件后几号,使用频繁
tail -10 nginx.conf 查看nginx.conf的最后10行
tail –f nginx.conf 动态查看日志,方便查看日志新增的信息
ctrl+c 结束查看

未归类得知识点:

1、uuid和md5的区别:uuid生成的值可能基本会重复,虽然也有重复的可能性但是比较小;而md5生成的值可能会重复重复。所以现在大部分用的是uuid,另外为了防止防止别人一眼就看出来是uuid(因为uuid的值,中间是由-连接的,原生的uuid很容易就被人认出来了),所以可以通过替换的方式来把"-"替换成空串,需要用到关键字(replace)
2、方法可以重载,但是里面的参数范围不可以耦合
3、在axios中,同步访问虽然快而高效,但是在服务器太忙的时候,返回数据会不及时,导致用户收不到数据;而异步两个对象之间是没办法异步的,所以就添加一个核心的组件,axios引擎
5、没有主启动方法的时候可以看看target里面编译的有没有问题,或者可以重新编译下看看
6、生产库或者测试环境下都好,禁止使用select * from 来进行查询,因为数据量太大了,会消耗服务器内存,存不下,导致服务器奔溃, 所以就采用分页查询。
7、在html中在代码前面点击提示回车的时候,要注意后面的代码可能会被覆盖掉,所以在插入值得之后最好隔着一个空格在进行插入
8、一般情况下,url浏览器信息的大小为2k
9、传图片,传字节,一般用post传递
10、在linux里面是不认识反斜杆的,所以需要用正斜杠
11、
12、
14、
15、
16、
17、
18、
19、
20、
21、
22、
23、
24、
25、
26、
27、
28、
29、
30、
31、
32、
33、
34、
35、
36、
37、
38、
39、
40、
41、
42、
43、
44、
45、
46、
47、
48、
49、
50、

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值