项目--京淘项目

文章目录

    • 一.构建后台项目
      • 1.创建项目
      • 2.编辑pom.xml文件
      • 3.导入src文件
      • 4.修改YML文件
      • 5.编辑启动项
    • 二.前端项目搭建
      • 1.关于前端路径说明
      • 2.操作步骤
        • 1.码云下载前端资料
        • 2.项目解压之后复制到IDEA维护的目录中
        • 3.导入项目
        • 4.运行前端项目
    • 三.代码调试
      • 1.前端代码调试
    • 四.用户登录业务实现
      • 1.项目划分
      • 2.用户模块分析
        • 1.表设计分析
        • 2.POJO分析
      • 3.用户登录业务实现
        • 1.项目开发流程
        • 2.接口文档说明
        • 3.编辑SysResult对象
      • 4.页面JS分析
      • 5.MD5介绍
      • 6.编辑UserController
      • 7.编辑UserService
      • 8.UserServiceImpl
    • 五.用户登录操作
      • 1.关于token的说明:
      • 2.生成UUID
      • 3.Session和Cookie
        • 1.Session介绍
        • 2.Cookie机制
        • 3.session和cookie区别
      • 4.前端保存用户信息
      • 5.实现系统首页跳转
    • 六.权限校验--路由导航守卫(前端路由的拦截器)
      • 1.业务需求
      • 2.路由导航守卫实现
        • 1.参数说明
        • 2.配置前端路由导航守卫
    • 七.左侧菜单列表实现
      • 1.左侧菜单页面分析
      • 2.编辑权限层级代码
        • 1.表设计分析
        • 2. 编辑POJO
        • 3.搭建Rights层级代码
        • 4.业务接口说明
        • 5. 编辑RightsController
          • 6.编辑RightsController
          • 7.页面效果展现
      • 3.debug说明
    • 八.用户模块实现
      • 1.实现用户页面跳转
        • 1.Home组件说明
        • 2.定义子级组件
        • 3.页面效果展现
    • 九. ElementUI说明
      • 1.表格数据
        • 1.编辑UI表格
        • 2.表格数据展现
      • 2.分页插件说明
        • 1.分页页面JS
    • 十.用户业务实现
      • 1.用户列表展现
        • 1. 页面JS分析
        • 2.业务接口文档
        • 3.编辑PageResult对象
        • 4.编辑UserController
        • 5.编辑UserServiceImpl
        • 6.编辑UserMapper
        • 7.页面效果展现
      • 2.MP实现用户分页
        • 1.UserController方法
        • 2.编辑UserServiceImpl方法
        • 3.编辑配置类
      • 3.用户状态修改
        • 1.业务说明
        • 2.页面JS
        • 4.编辑UserController
        • 5.编辑UserService
        • 6.编辑UserServiceImpl
      • 4.用户新增操作
        • 1.对话框
        • 2.页面JS分析
        • 3.用户新增业务接口
        • 4.编辑UserController
        • 5.编辑UserService
        • 6.编辑UserServiceImpl
      • 5.用户信息修改
        • 1.用户修改数据回显
          • 1.业务需求分析
          • 2.页面js分析
          • 3.业务接口文档
          • 4.编辑UserController
          • 5.编辑UserServiceImpl
          • 6.页面效果展现
        • 2.用户修改实现
          • 1. 页面JS分析
          • 2.业务接口文档
          • 3.编辑UserController
          • 4.编辑UserService
        • 3. 用户删除业务实现
          • 1.页面JS分析
          • 2.业务接口文档
          • 3.编辑UserController
          • 4.编辑UserServiceImpl
    • 十一.全局异常处理机制
      • 1.业务需求说明
      • 2.全局异常处理用法
        • 1.实现原理 AOP
        • 2.全局异常处理实现
    • 十二.Spring中的事务机制
      • 1.事务
    • 十三.商品分类业务实现
      • 1.问题说明:
      • 2.业务实现
        • 1. 业务封装原理图
        • 2.封装Map集合
        • 3.代码优化
        • 4.修改商品分类状态
          • 1.业务分析
          • 2.页面JS分析
          • 3.业务接口实现
          • 4.编辑ItemCatController
          • 5.编辑ItemCatService
          • 6.编辑ItemCatServiceImpl
        • 5.商品分类新增
          • 1.业务说明
          • 2.页面JS分析
          • 3.业务接口文档
          • 4.编辑ItemCatController
          • 5.编辑ItemCatService
          • 6.编辑ItemCatServiceImpl
        • 6.商品分类修改操作
          • 1.修改按钮JS分析
          • 2.修改-确定按钮实现
          • 3.业务接口文档
          • 4.编辑ItemCatController
          • 5.编辑ItemCatService
          • 6.编辑ItemCatServiceImpl
        • 7.商品分类删除操作
          • 1.业务说明
          • 2.页面JS分析
          • 3.业务接口实现
          • 4.编辑ItemCatController
          • 5.编辑ItemCatService
          • 6.编辑ItemCatServiceImpl
        • 8.商品模块实现
          • 1.业务分析
            • 1.表设计
            • 2.编辑pojo
            • 3.编辑Item的层级代码
            • 4.实现前台页面跳转
          • 2.商品列表展现
            • 1. 页面JS分析
            • 2.业务接口说明
            • 3.编辑ItemController
            • 4.编辑ItemServiceImpl
            • 5.页面效果展现
          • 3.VUE过滤器用法
            • 1.需求说明
            • 2.过滤器使用
          • 4.添加商品
            • 1.商品新增页面跳转
            • 2. 页面跳转
            • 3.页面效果展现
            • 4.商品新增分析
            • 5.业务接口说明
            • 6.封装ItemVO对象
          • 5.商品状态修改
            • 1.业务分析
            • 2.业务接口
            • 3.编辑ItemController
            • 4.编辑ItemServiceImpl
          • 6.商品删除
            • 1.业务分析
            • 2.业务接口说明
            • 3.编辑ItemController
            • 4.编辑ItemServiceImpl
          • 7. 商品详情说明
            • 1.业务说明
            • 2.表设计
            • 3.编辑ItemDesc
            • 4.编辑ItemController
            • 5.重构ItemServiceImpl
          • 8. 富文本编辑器
    • 十四.关于数据库主外键说明
    • 十五.ElementUI中文件上传
      • 1.UI入门案例
    • 十六.文件上传入门案例
      • 1.UI页面JS分析
      • 2.业务接口说明
      • 3. 编辑ImageVO
      • 4.入门案例实现
    • 十七.图片上传业务实现
      • 1.文件上传的注入事项
      • 2. 正则表达式
        • 1.正则说明
        • 2.语法介绍
      • 3.编辑FileController
      • 4.编辑FileServiceImpl
      • 5.页面效果展现
    • 十八.关于商品新增文件模块说明
      • 1.商品基本模块
      • 2.商品上传
      • 3.商品详情
    • 十九.实现图片功能
      • 1.图片删除
        • 1.业务需求说明
        • 2.删除JS分析
        • 3.业务接口说明
        • 4.编辑FileController
        • 5.编辑FileServiceImpl
      • 2.图片网络路径说明
        • 1.业务说明
        • 2.封装图片网络地址
      • 3.后台数据优化
        • 1.业务说明
        • 2.编辑image.properties文件
        • 3.动态为属性赋值
    • 二十.代理机制
      • 1.业务说明
      • 2.反向代理
      • 3.正向代理
      • 4.正向/反向代理说明
    • 二十一.nginx
      • 1.Nginx介绍
      • 2.nginx特点:
      • 3. Nginx下载
      • 4.nginx启动报错说明
      • 5.nginx进程项说明
      • 6.nginx命令
      • 7.反向代理入门案例
      • 8.实现图片的反向代理
        • 1.业务说明
        • 2.图片服务器代理配置
        • 3.图片回显流程
        • 4.修改hosts文件
        • 5.页面效果展现
    • 二十二.图片上传不回显问题说明:
      • 1.现象说明:
      • 2.检查路径是否正确
    • 二十三.京淘项目前端项目发布
      • 1.关于前端说明:
      • 2.前端项目发布
        • 1.路径修改
        • 2.前端项目打包
        • 3.前端项目发布
        • 4.前端反向代理
    • 二十四. 京淘项目后端发布
      • 1.项目部署流程
      • 2.部署2台tomcat服务器
        • 1.去除热部署
        • 2.动态获取端口
        • 3.代码调试
        • 4.根据端口号测试
        • 5. IDEA主启动项说明
      • 3.Nginx实现tomcat集群部署
        • 1.配置nginx服务器
        • 2.测试效果
      • 4.Nginx负载均衡策略
        • 1.轮询策略
        • 2. 权重策略
        • 3. IPHASH策略
    • 二十五.Linux
      • 1.部署JDK
        • 1.部署JDK流程
        • 2.上传JDK
        • 3.解压命令
        • 4. 编辑Linux环境变量
        • 5.检查JDK是否有效

一.构建后台项目

1.创建项目

项目--京淘项目_第1张图片

2.编辑pom.xml文件


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>

    <groupId>com.jtgroupId>
    <artifactId>jtartifactId>
    <version>1.0-SNAPSHOTversion>

    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.5.3version>
        <relativePath/>
    parent>

    <properties>
        
        <java.version>1.8java.version>
        
        <skipTests>trueskipTests>
    properties>

    
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
        dependency>

        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-jdbcartifactId>
        dependency>

        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.4.3version>
        dependency>

    dependencies>

    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <version>2.5.3version>
            plugin>
        plugins>
    build>

project>

3.导入src文件

说明: 从码云中下载jt的完整代码,之后粘贴到本地项目中,如图所示:
项目--京淘项目_第2张图片

4.修改YML文件

server:
  port: 8091
  servlet:
    context-path: /

#配置数据源
spring:
  datasource:
    #如果使用高版本驱动 则添加cj
    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


#mybatis-plush配置
mybatis-plus:
  type-aliases-package: com.jt.pojo
  mapper-locations: classpath:/mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true

logging:
  level:
    com.jt.mapper: debug

5.编辑启动项

1.点击修改
项目--京淘项目_第3张图片
2.为启动项修改文件.方便以后使用
项目--京淘项目_第4张图片

二.前端项目搭建

1.关于前端路径说明

最好统一项目路径.
要求: 将前端项目放到IDEA维护的工作目录中.
如图所示:
项目--京淘项目_第5张图片

2.操作步骤

1.码云下载前端资料

项目--京淘项目_第6张图片

2.项目解压之后复制到IDEA维护的目录中

注意事项: 不要出现目录的嵌套,要求目录的根 就是项目. 如图
项目--京淘项目_第7张图片

3.导入项目

项目--京淘项目_第8张图片

4.运行前端项目

项目--京淘项目_第9张图片

三.代码调试

1.前端代码调试

目的: 经常性的出现本来编辑的是李逵的代码,但是由于代码不熟练,编辑的李鬼. 所以有如下的测试.
编辑App.vue文件
项目--京淘项目_第10张图片
代码测试:
项目--京淘项目_第11张图片

四.用户登录业务实现

1.项目划分

前端项目网址: http://localhost:8080/
后端项目网址: http://localhost:8091/
用户操作项目请求的流程: 用户----前端服务器------后端服务器

2.用户模块分析

1.表设计分析

注意事项:
1.密码 加密处理
2.created/updated 每张表中都有该字段
项目--京淘项目_第12张图片

2.POJO分析

项目--京淘项目_第13张图片

3.用户登录业务实现

1.项目开发流程

人员配置:
1.项目经理(统筹规划协调资源,控制项目进度)
2.职能经理: 凭技术吃饭的 项目组长.
3.产品经理: 根据甲方要求,绘制项目原型. 出原型图
4.UI设计: 根据产品的图,用美学的眼光 绘制图片/按钮/logo
5.前端开发: 绘制页面(html/css/js)
6.后端开发: 根据业务接口文档.实现项目
7.测试工程师: 工具测试(黑百盒测试) 测试机型!!!
8.实施/运维工程师 事情杂/工资低

2.接口文档说明

请求路径: /user/login
请求方式: POST
请求参数
项目--京淘项目_第14张图片
响应数据 SysResult对象

项目--京淘项目_第15张图片

返回值格式如下:


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

3.编辑SysResult对象

说明: 该对象主要负责前端项目与后端项目的数据交互. 几乎所有的后台服务器的返回值都是SysResult对象.

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);
    }

}

4.页面JS分析

1.路由跳转规则

项目--京淘项目_第16张图片
2.axios 全局定义
项目--京淘项目_第17张图片
3.axios的使用
项目--京淘项目_第18张图片

5.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.MD5不可以被破解的. 只能由明文加密为密文. 不可以反向编译

6.编辑UserController

用户名: admin123
密码: admin123456

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.*;

import java.util.List;

/**
 * @author
 * 时间 2021/5/11
 */
@RestController
@CrossOrigin//跨域
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

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

        return userService.findAll();
    }

    /**
     * 业务说明:
     * 思想:根据参数,查询数据库
     *      有值:用户名和密码正确
     *      没有值:用户名和密码错误
     * URL:/user/login
     * 参数:username/password
     * 类型:post
     * 返回值:SysResult对象(token)
     */
    @PostMapping("/login")
    public SysResult login(@RequestBody User user){
        //需求:要求登陆成功之后,返回值标识符信息
        String token = userService.login(user);
        //如果token为null,说明登录失败
        if(token == null || token.length() ==0){
            return SysResult.fail();
        }
        //否则 正确返回
        return SysResult.success(token);

    }
}

7.编辑UserService

package com.jt.service;

import com.jt.pojo.User;

import java.util.List;

/**
 * @author
 * 时间 2021/5/11
 */
public interface UserService {
    List<User> findAll();


    String login(User user);
}

8.UserServiceImpl

package com.jt.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.util.List;

/**
 * @author
 * 时间 2021/5/11
 */
@Service
public class UserServiceImpl implements UserService{

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> findAll() {
        return userMapper.selectList(null);
    }

    /**
     * 思路:
     *      1.将密码进行加密处理
     *      2.根据username/password查询数据库
     *      3.有值:
     *          登录成功
     *         没有值:
     *          登录失败
     *
     */
    @Override
    public String login(User user) {
        //加密算法  MD5
        //1.获取明文
        String password = user.getPassword();
        //2.加密处理
        String md5 = DigestUtils.md5DigestAsHex(password.getBytes());
        user.setPassword(md5);
        //3.查询数据库
        QueryWrapper<User> queryWrapper =
                new QueryWrapper<>(user);
        //4.获取数据对象
        User userDB = userMapper.selectOne(queryWrapper);

        //5.判断登录是否正确
        if(userDB == null){
            return null;
        }
        String token = "我是秘钥";
        return token;

    }
}

五.用户登录操作

1.关于token的说明:

1.由于服务器需要标识已经登录的用户,所以服务器动态生成一个独一无二的token,返回给用户.
2.用户将token保存到本地,方便下次访问时携带.

2.生成UUID

package com.jt.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.util.List;
import java.util.UUID;

/**
 * @author
 * 时间 2021/5/11
 */
@Service
public class UserServiceImpl implements UserService{

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> findAll() {
        return userMapper.selectList(null);
    }

    /**
     * 思路:
     *      1.将密码进行加密处理
     *      2.根据username/password查询数据库
     *      3.有值:
     *          登录成功
     *         没有值:
     *          登录失败
     *
     */
    @Override
    public String login(User user) {
        //加密算法  MD5
        //1.获取明文
        String password = user.getPassword();
        //2.加密处理
        String md5 = DigestUtils.md5DigestAsHex(password.getBytes());
        user.setPassword(md5);
        //3.查询数据库
        QueryWrapper<User> queryWrapper =
                new QueryWrapper<>(user);
        //4.获取数据对象
        User userDB = userMapper.selectOne(queryWrapper);

        //5.判断登录是否正确
        if(userDB == null){
            return null;
        }
        //6.动态使用UUID生成TOKEN,根据当前毫秒数+随机数利用hash算法生成
        //      几乎可以保证不重复
        String token = UUID.randomUUID().toString()
                .replace("-", "");
        return token;

    }
}

3.Session和Cookie

1.Session介绍

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

小结:
1.Session称之为 “会话机制”
2.在浏览器中打开网页 就是一个会话.
3.用户的数据可以保存到会话中,但是有生命周期. 当会话关闭,则数据消失.

2.Cookie机制

Cookie,有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息 [1] 。
说明:
1.cookie是一个小型的文本文件
2.cookie中存储的数据一般都是密文.
3.cookie中的数据的生命周期可控. 几天.几年!!!

3.session和cookie区别

1.session的数据是临时存储.cookie的数据可以永久保存. (生命周期不同)
2.sesion是浏览器中的一个内存对象!而cookie是一个实际的本地文件. (形式不同).
3.session一般存储是一些涉密数据.cookie一般存储相对公开的数据(免密登录). (安全性)

4.前端保存用户信息

login(){
      //获取表单对象之后进行数据校验
      //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")
      })
    }

5.实现系统首页跳转

编辑router/index.js 添加组件信息,实现首页跳转
项目--京淘项目_第19张图片

六.权限校验–路由导航守卫(前端路由的拦截器)

1.业务需求

前端页面跳转是通过路由进行控制,规定:如果用户没有登录,则只允许访问登录页面,只有登录之后才能访问其他页面

难点:如何实现用户请求的拦截
拦截器的作用:拦截用户的请求。
结果1:请求放行
结果2:请求拦截,一般配合重定向使用

2.路由导航守卫实现

1.参数说明

项目--京淘项目_第20张图片

2.配置前端路由导航守卫

//路由导航守卫!!!!!!!

const router = new VueRouter({
  routes
})

/**
 * 定义路由导航守卫
 * 参数1:  to           路由跳转的网址
 * 参数2:   from     路由·从哪里来
 * 参数3:   next     是一个函数,表示放行或者重定向
 *                         next() 放行
 *                         next("/login")  重定向
 * 业务实现:
 *      核心逻辑:  检查是否有token
 *      有token     表示已经登录,放行请求
 *      没有token  表示用户没有登录,重定向到登录页面
 *      如果没有访问login页面  直接放行
 */

router.beforeEach((to,from,next)=> {
    if(to.path  === "/login"){
      return next()
    }
    //说明用户访问的页面不是login  请求需要校验
    //获取token数据
    let token = window.sessionStorage.getItem("token")
    //if(token != null && token.length>0)
    //下列if的判断  解释:如果token不为null   字符串的判断
    if(token){
      return next()
    }
      next("/login")

})

七.左侧菜单列表实现

1.左侧菜单页面分析

项目--京淘项目_第21张图片

2.编辑权限层级代码

1.表设计分析

说明:
name: 权限名称
parent_id: 父级权限 实现了父子级关系.
path: 一级菜单没有路径的, 二级/三级才有路径.
项目--京淘项目_第22张图片

2. 编辑POJO

说明:
1.当前对象中children属性不属于字段,所以使用@TableField(exist = false)进行标识.
2.权限列表中有父子级关系,所以通过children封装子级
项目--京淘项目_第23张图片

3.搭建Rights层级代码

项目--京淘项目_第24张图片

4.业务接口说明

只查询一级和二级的信息

请求路径 /rights/getRightsList
请求类型 GET
请求参数 无
响应数据 SysResult对象
项目--京淘项目_第25张图片

响应数据如图所示
项目--京淘项目_第26张图片

5. 编辑RightsController

项目--京淘项目_第27张图片

6.编辑RightsController
  /**
     * 实现思路:
     *   1.先查询一级菜单信息 parent_id = 0
     *   2.将一级菜单循环遍历 一级菜单对象.
     *   3.根据一级菜单信息,查询当前菜单下的二级.
     *   4.将查询得到的二级菜单,封装到一级对象中
     * 实现思路二(扩展):
     *    利用左连接 实现关联查询 封装数据.
     * @return
     */
    @Override
    public List<Rights> getRightsList() {
        //1.查询一级列表信息
        QueryWrapper<Rights> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id",0);
        List<Rights> oneList = rightsMapper.selectList(queryWrapper);
        //2.遍历一级列表
        for (Rights oneRights : oneList){
            //根据一级查询二级
            queryWrapper.clear(); //清除之前的条件
            queryWrapper.eq("parent_id",oneRights.getId());
            List<Rights> twoList = rightsMapper.selectList(queryWrapper);
            //将查询的结果封装一级对象中
            oneRights.setChildren(twoList);
        }
        return oneList;
    }

7.页面效果展现

项目--京淘项目_第28张图片

3.debug说明

说明: debug是编程中常用的代码的调试工具. 可以让代码一行一行执行.
项目--京淘项目_第29张图片

八.用户模块实现

1.实现用户页面跳转

1.Home组件说明

业务说明: URL:/user 要求跳转组件 User.vue组件,组件的跳转应该在Home组件内部跳转.
Home组件说明: 在main的区域中,设定 作用 Home的子级标签将来组件在该区域展现.
项目--京淘项目_第30张图片

2.定义子级组件

说明: 通过路由实现父子组件嵌套.
项目--京淘项目_第31张图片

3.页面效果展现

项目--京淘项目_第32张图片

九. ElementUI说明

1.表格数据

1.编辑UI表格

<template>
  <div>
    
    
    <el-breadcrumb separator-class="el-icon-arrow-right" >
      <el-breadcrumb-item :to="{ path: '/' }">首页el-breadcrumb-item>
      <el-breadcrumb-item>活动管理el-breadcrumb-item>
      <el-breadcrumb-item>活动列表el-breadcrumb-item>
      <el-breadcrumb-item>活动详情el-breadcrumb-item>
    el-breadcrumb>

    
          <el-card class="box-card">
            <div class="text item">
              
              <el-row :gutter="20">
                <el-col :span="8">
                  
                  <el-input placeholder="请输入内容" v-model="input3" class="input-with-select">
                     <el-button slot="append" icon="el-icon-search">el-button>
                  el-input>
                el-col>
                <el-col :span="3">
                    <el-button type="primary">主要按钮el-button>
                el-col>
              el-row>

            div>
            
            <h1>定义表格h1>

            <el-table :data="tableData"style="width: 100%" stripe="true">
                  <el-table-column
                    label="编号" prop="id">
                  el-table-column>
                  <el-table-column
                    label="名称"  prop="name">
                  el-table-column>
                  <el-table-column
                    label="年龄"  prop="age">
                  el-table-column>
                  <el-table-column
                    label="性别"  prop="sex">
                  el-table-column>
                el-table>
                
                <el-pagination
                      @size-change="handleSizeChange"
                      @current-change="handleCurrentChange"
                      :current-page="100"
                      :page-sizes="[100, 200, 300, 400]"
                      :page-size="100"
                      layout="total, sizes, prev, pager, next, jumper"
                      :total="1000">
                    el-pagination>
          el-card>
  div>
template>

<script>
// 对外声明组件属性/方法等参数,要被根组件调用
export default {
  data () {
    return {
      msg: '我是vue子组件'
    }
  }
}
script>
<script>
   //对外声明组件属性/方法等参数.要被根组件调用
  export default{
    data(){
      return {
        tableData:[
          {id:1,name:"01",age:100,sex:"男"},
          {id:2,name:"02",age:100,sex:"男"},
          {id:3,name:"03",age:100,sex:"男"},
          {id:4,name:"04",age:100,sex:"男"},
        ]
      }
    },
    methods:{
      handleSizeChange(size){
        alert("当前条数"+size)
      },
      handleCurrentChange(current){
        alert("当前页数"+current)
      }
    }
  }
script>

<style lang="lesss" scoped="">
style>

2.表格数据展现

项目--京淘项目_第33张图片

2.分页插件说明

1.分页页面JS

 
         <el-pagination
             @size-change="handleSizeChange"
             @current-change="handleCurrentChange"
             :current-page="100"
             :page-sizes="[10, 20, 30, 40]"
             :page-size="10"
             layout="total, sizes, prev, pager, next, jumper"
             :total="1000">
         el-pagination>

项目--京淘项目_第34张图片

十.用户业务实现

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)
      },

3.页面URL分析
项目--京淘项目_第35张图片

2.业务接口文档

请求路径: /user/list
请求类型: GET
请求参数: 后台使用PageResult对象接收
请求案例: http://localhost:8091/user/list?query=查询关键字&pageNum=1&pageSize=10

项目--京淘项目_第36张图片
响应参数: SysResult对象 需要携带分页对象 PageResult
项目--京淘项目_第37张图片
PageResult 对象介绍
项目--京淘项目_第38张图片
返回值效果

{"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":"[email protected]",
		 "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":"[email protected]",
		"status":false,
		"role":null
		}
		]
	}
}

3.编辑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;//分页查询的结果
}


4.编辑UserController

package com.jt.controller;

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

import java.util.List;

/**
 * @author
 * 时间 2021/5/11
 */
@RestController
@CrossOrigin//跨域
//@CrossOrigin({"http://localhost:8080"})
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

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

        return userService.findAll();
    }

    /**
     * 业务说明:
     * 思想:根据参数,查询数据库
     *      有值:用户名和密码正确
     *      没有值:用户名和密码错误
     * URL:/user/login
     * 参数:username/password
     * 类型:post
     * 返回值:SysResult对象(token)
     */
    @PostMapping("/login")
    public SysResult login(@RequestBody User user){
        //需求:要求登陆成功之后,返回值标识符信息
        String token = userService.login(user);
        //如果token为null,说明登录失败
        if(token == null || token.length() ==0){
            return SysResult.fail();
        }
        //否则 正确返回
        return SysResult.success(token);

    }
    /**
     * 业务说明:实现用户列表的分页查询
     * url:http://localhost:8080/#/user/list?query=查询关键字&pageResult
     * 参数:pageResult
     */
    @GetMapping("/list")
    public SysResult getUserList(PageResult pageResult){//3个参数
        //pageResult 引用类型
        pageResult = userService.getUserList(pageResult);

        return SysResult.success(pageResult);//封装5个参数
    }
}

5.编辑UserServiceImpl


    /**
     * 分页sql:
     *      语法:select * from user limit 起始位置,每页条数
     *      规则:含头不含尾
     *      查询第一页:
     *          select * from user limit o,10
     *       查询第二页:
     *          select * from user limit 10,10   10-19
     *       查询第三页:
     *          select * from user limit 20,10  20-29
 *           查询第n页:
 *      *       select * from user limit (n-1)*条数,条数
     *
     * @param pageResult
     * @return
     */
    @Override
    public PageResult getUserList(PageResult pageResult) {
        //1.获取总记录数
        //必须是long,大写Long类型不匹配
        long total = userMapper.selectCount(null);
        //2.获取分页结果
        int size = pageResult.getPageSize();
        int start = (pageResult.getPageNum()-1) * size;

        List<User> userList = userMapper.findListByPage( start,size);

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

6.编辑UserMapper

说明: 利用注解实现Sql查询, 映射文件和注解标签二选一 不能同时使用

package com.jt.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jt.pojo.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

/**
 * @author
 * 时间 2021/2/2
 */
public interface UserMapper extends BaseMapper<User> {
    //注解方式查询数据库!!!  限制条件:只适用简单sql  注解/映射文件二选一
    @Select("SELECT * FROM user limit #{start},#{size}")
//    @Insert()
//    @Update()
//    @Delete()
    List<User> findListByPage(int start,int size);
}

7.页面效果展现

项目--京淘项目_第39张图片

2.MP实现用户分页

1.UserController方法

/**
     * 业务说明: 实现用户列表的分页查询
     * URL地址: http://localhost:8091/user/list?query=查询关键字&pageNum=1&pageSize=10
     * 参数: pageResult接收
     * 返回值: SysResult对象(pageResult)
     */
    @GetMapping("/list")
    public SysResult getUserList(PageResult pageResult){//3

        pageResult = userService.getUserList(pageResult);
        return SysResult.success(pageResult);//5
    }

2.编辑UserServiceImpl方法

 /*
    * 业务说明: 利用MP方式查询数据库.
    * 步骤梳理:
    *       1.构建MP的分页对象
    *       2.根据分页对象查询数据.
    *       3.从分页对象中获取数据
    *       4.封装PageResult对象
    *       5.编辑配置类 封装分页拦截器
    * */
    @Override
    public PageResult getUserList(PageResult pageResult) {
        //1.定义分页对象
        IPage<User> page = new Page<>(pageResult.getPageNum(),
                                      pageResult.getPageSize());
        //2.定义条件构造器 指定动态查询Sql
        boolean flag = StringUtils.hasLength(pageResult.getQuery());
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(flag, "username",pageResult.getQuery());

        //3.进行分页查询
        page = userMapper.selectPage(page,queryWrapper);
        //4.从封装后的分页对象中获取数据
        pageResult.setTotal(page.getTotal())
                  .setRows(page.getRecords());
        return pageResult;
   }

3.编辑配置类

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 {
    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false
     * 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    //MyBatisPlus在执行分页操作时,会被该拦截器拦截
    //拦截器的作用  动态拼接where条件
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
        return interceptor;
    }
}

3.用户状态修改

1.业务说明

说明: 通过开关 可以控制数据类型的 true/false
参数说明: 1.传递userId主键
2.传递当前状态信息 true/false
项目--京淘项目_第40张图片

2.页面JS

  1. 作用域插槽
    作用: 可以在表格中的任意单元格,获取当前行的元素信息.
    关键语法: scope.row 获取行元素.
	 <el-table-column prop="status" label="状态">
             <!-- 定义作用于插槽-->
             <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>

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

  1. 业务接口文档
    请求路径 /user/status/{id}/{status}
    请求类型 PUT
    请求参数: 用户ID/状态值数据
    项目--京淘项目_第41张图片
    返回值结果: SysResult对象
{"status":200,"msg":"服务器调用成功!","data":null}

4.编辑UserController

/**
     * 业务说明: 修改状态信息
     * URL:  /user/status/{id}/{status}
     * 参数:  id/status
     * 返回值: SysResult
     */
    @PutMapping("/status/{id}/{status}")
    public SysResult updateStatus(User user){

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

5.编辑UserService

在这里插入图片描述

6.编辑UserServiceImpl

 //规则: 根据对象中不为null的元素当作set条件,
    //      Id当作唯一where条件
    //update user set status=true where id=xxx
    @Override
    public void updateStatus(User user) {//id/status

        userMapper.updateById(user);
    }

4.用户新增操作

1.对话框

说明: 通过属性dialogVisible 控制对话框是否可见.

<!--
        :visible.sync 控制对话框是否可见  true/false
      -->
      <el-dialog
        title="提示"
        :visible.sync="dialogVisible"
        width="30%">
        <span>这是一段信息</span>
        <span slot="footer" class="dialog-footer">
          <el-button @click="dialogVisible = false">取 消</el-button>
          <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
        </span>
      </el-dialog>

2.页面JS分析

项目--京淘项目_第42张图片

3.用户新增业务接口

请求路径 /user/addUser
请求类型 POST
请求参数: 整个form表单数据
项目--京淘项目_第43张图片
返回值结果: SysResult对象

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

4.编辑UserController

/**
     * 业务: 实现用户新增
     * url:  /user/addUser
     * 参数:  整个form表单  对象  json
     * 返回值: SysResult对象
     */
    @PostMapping("/addUser")
    public SysResult addUser(@RequestBody User user){

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

5.编辑UserService

在这里插入图片描述

6.编辑UserServiceImpl

 /**
     * 说明:
     *      1.用户入库操作需要手动补齐的数据有
     *        创建时间/修改时间 保证一致.
     *        status=true 手动填充.
     *      2.密码加密处理  新增和登录加密算法必须一致
     * @param user
     */
    @Override
    public void addUser(User user) {
        String password = user.getPassword();
        //加密处理
        password = DigestUtils.md5DigestAsHex(password.getBytes());
        user.setPassword(password)
            .setStatus(true)
            .setCreated(new Date())
            .setUpdated(user.getCreated());
        userMapper.insert(user);
    }

5.用户信息修改

1.用户修改数据回显

1.业务需求分析

当用户点击修改按钮时该出现弹出框,其中展现用户的数据信息
展现方式:
1.获取用户ID,动态查询后台数据库信息。之后实现数据回显 最新数据
2.可以利用作用域插槽,获取当前行的对象,之后实现数据回显, 页面数据
项目--京淘项目_第44张图片

2.页面js分析
 <el-table-column label="操作">
             <template slot-scope="scope">
                <el-button type="primary" icon="el-icon-edit" size="small" @click="updateUserBtn(scope.row)"></el-button>
                <el-button type="danger" icon="el-icon-delete" size="small" @click="deleteUser(scope.row)"></el-button>
             </template>
           </el-table-column>

//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
      },

3.业务接口文档

请求路径: /user/{id}
请求类型: GET
返回值: SysResult对象
项目--京淘项目_第45张图片
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":"[email protected]",
	 "status":true,
	 "role":null
	 }
 }

4.编辑UserController
/**
     * 业务:实现用户id查询
     * 请求路径: /user/{id}
     * 请求类型: GET
     * 参数:主键id
     * 返回值: SysResult(User)对象
     */
    @GetMapping("/{id}")
    public SysResult getUserById(@PathVariable Integer id){

        User user =userService.getUserById(id);
        return SysResult.success(user);
    }
5.编辑UserServiceImpl
@Override
    public User getUserById(Integer id) {
        return userMapper.selectById(id);
    }
6.页面效果展现

项目--京淘项目_第46张图片

2.用户修改实现

1. 页面JS分析
说明: 当用户点击确定按钮时,会触发点击事件
<span slot="footer" class="dialog-footer">
        <el-button @click="updateDialogVisible = false" >取 消</el-button>
        <el-button type="primary" @click="updateUser">确 定</el-button>
      </span>

点击确定之后,页面JS 发送情况.
项目--京淘项目_第47张图片

2.业务接口文档

请求路径: /user/updateUser
请求类型: PUT
请求参数: User对象结构
在这里插入图片描述
返回值: SysResult对象
项目--京淘项目_第48张图片
JSON格式如下:

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

3.编辑UserController
 /**
     * 业务:实现用户修改操作
     * 请求路径: /user/updateUser
     * 请求类型: PUT
     * 请求参数: User对象提交  JSON字符串  注解接收
     * 返回值:SysResult
     */

    @PutMapping("/updateUser")
    public SysResult updateUser(@RequestBody User user){
        userService.updateUser(user);
        return SysResult.success();
    }
4.编辑UserService
 //不为null的元素当作set条件,Id当作唯一where条件
    @Override
    public void updateUser(User user) {
        userMapper.updateById(user);
    }

3. 用户删除业务实现

1.页面JS分析
 <el-table-column label="操作">
             <template slot-scope="scope">
                <el-button type="primary" icon="el-icon-edit" size="small" @click="updateUserBtn(scope.row)"></el-button>
                <el-button type="danger" icon="el-icon-delete" size="small" @click="deleteUser(scope.row)"></el-button>
             </template>
           </el-table-column>

async deleteUser(user){
         //1.消息确认框
         const result =  await this.$confirm('此操作将永久删除 '+user.username+', 是否继续?', '提示', {
                   confirmButtonText: '确定',
                   cancelButtonText: '取消',
                   type: 'warning'
                 }).catch(error => error)

         //如果确认  confirm  如果取消 cancel
         if(result !== 'confirm'){
            return this.$message.info("删除取消")
         }
         const {data: result2} = await this.$http.delete(`/user/${user.id}`)
         if(result2.status !== 200) return this.$message.error("删除失败")
         this.$message.success("删除成功")
         //重新加载 数据
         this.getUserList()
      }
    }

2.业务接口文档

请求路径: /user/{id}
请求类型: delete
请求参数:
在这里插入图片描述
返回值: SysResult对象
项目--京淘项目_第49张图片

3.编辑UserController
 /**
     * 业务:实现用户删除
     * 请求路径:/user/${user.id}
     * 请求类型: delete
     * 参数: User对象结构
     * 返回值:SysResult对象
     * 
     * 
     * post:新增,表单提交
     * delete:删除操作
     * 使用相同的路径可以实现不同的功能
     */
    @DeleteMapping("/{id}")
    public SysResult deleteById(@PathVariable Integer id){
        userService.deleteById(id);
        return SysResult.success();
    }
4.编辑UserServiceImpl
  @Override
    public void deleteById(Integer id) {
        userMapper.deleteById(id);
    }

十一.全局异常处理机制

1.业务需求说明

说明: 如果后台服务器发生运行时异常,应该第一时间通知用户.否则用户没有任何提示.影响用户体验.

分析:
1.页面没有提示的原因是服务器没有返回201的报错数据.
2.后端服务器报错之后,没有异常处理机制
项目--京淘项目_第50张图片
总结:
异常的处理是项目中经常用到的机制,但是如果将大量的异常处理代码写在方法中,则影响程序的结构.导致代码混乱.

2.全局异常处理用法

1.实现原理 AOP

AOP说明:
名称: “面向切面编程”
作用: 在不影响源码的条件下,对方法进行扩展,降低了业务的耦合性.
通知:
1.前置通知: before
2.后置通知: afterReturning
3.异常通知: afterThrowing
4.最终通知: after
上述的四大通知,不能改变程序的运行的状态.
5.环绕通知: around
环绕通知是功能最为强大的通知方法,可以控制程序的流转过程.

2.全局异常处理实现

package com.jt.aop;

import com.jt.vo.SysResult;
import org.apache.ibatis.jdbc.SQL;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.sql.SQLException;

/**
 * 注解的作用:
 *  1.该注解只拦截Controller层抛出的异常信息
 *    Controller---Service---Mapper  异常向上抛出
 *  2.需要配合指定异常的类型
 *
 */
@RestControllerAdvice
public class SystemAOP {
    //当前Controller层,只拦截运行时异常
    //@ExceptionHandler({RuntimeException.class, SQLException.class})
    @ExceptionHandler({RuntimeException.class})

    public SysResult exception(Exception e){
        //控制台打印异常
        e.printStackTrace();
        return SysResult.fail();
    }
}

十二.Spring中的事务机制

1.事务

如果后台服务器发生问题,则数据信息应该回滚,而不是提交操作.

事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。

事务作用: 可以保证数据的 /持久性(永久性)/一致性/隔离性/原子性

十三.商品分类业务实现

1.问题说明:

原始代码结构:出现2层循环结构,如果外层循环10个,每个内层循环也是10个,完成这项业务需要查询100次数据库

矛盾点:多次查询数据库!!!

优化的策略:能否将查询的次数降低到1次,就可以获取所有的数据信息

说明:数据裤只查询一次,就可以获取商品分类三级嵌套结构

程序设计:
1.数据结构Map<父级ID,子级列表> 列表信息中不包含嵌套关系
例如:Map<0,一级列表信息> 一级列表不包含二级/三级
Map<一级ID,二级列表信息> 只有2级 二级列表信息不包含三级/四级
Map<二级ID,三级列表信息> 只有23级 三级列表信息不包含四级
2.根据数据结构动态根据level查询自己

2.业务实现

1. 业务封装原理图

项目--京淘项目_第51张图片

2.封装Map集合

/**
     * 1.查询所有的商品分类列表,  查询一次数据库,
     * 2.循环遍历所有的数据,按照parentId,List方式封装数据
     * @return
     */
    private Map<Integer, List<ItemCat>> getMap() {
        Map<Integer,List<ItemCat>> map = new HashMap<>();
        List<ItemCat> list = itemCatMapper.selectList(null);
        for(ItemCat itemCat : list){
            //获取parentId
            int parentId = itemCat.getParentId();
            if(map.containsKey(parentId)){
                //key存在
                //追加到map集合中
                map.get(parentId).add(itemCat);
            }else{
                //key不存在
                //1.new List封装list集合
                List<ItemCat> childrenList = new ArrayList<>();
                //2.存入数据
                childrenList.add(itemCat);
                //3.将第一个元素封装到map中
                map.put(parentId,childrenList);
            }
        }
        return map;
    }

3.代码优化


    /**
     * 1.数据结构:Map key=password value="List"
     * 2.封装Map的数据类型
     * 3.如果level=1  只获取一级
     * 4.如果level=2  只获取二级
     * 5.如果level=3  只获取三级
     * @param level
     * @return
     */
    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        long startTime = System.currentTimeMillis();
        //1.封装Map集合
        Map<Integer,List<ItemCat>> map = getMap();

        //2.判断level的值
        if(level == 1){
            return map.get(0);
        }
        //
        if(level == 2){
            return getTwoList(map);
        }
        //如果level不是1,2级,一定是3级
        List<ItemCat> list = getThreeList(map);
        long endTime = System.currentTimeMillis();
        System.out.println("总计时:"+(endTime-startTime));
        return list;
    }

    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 || twoList.size() == 0){
                //由于业务的数据不合理,跳过本次循环,执行下一次
                continue;
                //break;直接结束
            }
            for(ItemCat twoItemCat : twoList){
                //查询三级列表,需要的三级的parentId是二级Id
                int parentId = twoItemCat.getId();
                List<ItemCat> threeList = map.get(parentId);
                //封装数据
               twoItemCat.setChildren(threeList);
            }

        }
       return  oneList;
    }


    private List<ItemCat> getTwoList(Map<Integer, List<ItemCat>> map) {
        //1.先获取一级列表
        List<ItemCat> oneList = map.get(0);
        //2.遍历一级列表,根据一级查询二级
        for (ItemCat oneItemCat: oneList){
            //查询二级,所以二级的parentId是一级的Id
            int parentId = oneItemCat.getId();
            List<ItemCat> twoList = map.get(parentId);
            //封装数据
            oneItemCat.setChildren(twoList);


        }
        return oneList;
    }

    /**
     * 1.查询所有的商品分类列表,  查询一次数据库,
     * 2.循环遍历所有的数据,按照parentId,List方式封装数据
     * @return
     */
    private Map<Integer, List<ItemCat>> getMap() {
        Map<Integer,List<ItemCat>> map = new HashMap<>();
        List<ItemCat> list = itemCatMapper.selectList(null);
        for(ItemCat itemCat : list){
            //获取parentId
            int parentId = itemCat.getParentId();
            if(map.containsKey(parentId)){
                //key存在
                //追加到map集合中
                map.get(parentId).add(itemCat);
            }else{
                //key不存在
                //1.new List封装list集合
                List<ItemCat> childrenList = new ArrayList<>();
                //2.存入数据
                childrenList.add(itemCat);
                //3.将第一个元素封装到map中
                map.put(parentId,childrenList);
            }
        }
        return map;
    }

4.修改商品分类状态

1.业务分析

说明: 当用户点击状态码时,应该实现数据的修改操作.
项目--京淘项目_第52张图片

2.页面JS分析
<el-table-column prop="status" label="状态">
          <!-- 定义作用域插槽 展现数据     scope.row展现行级元素 -->
          <template slot-scope="scope">
            <el-switch v-model="scope.row.status" active-color="#13ce66" inactive-color="#ff4949"
              @change="updateStatus(scope.row)"></el-switch>
          </template>
        </el-table-column>


	 //根据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("状态修改成功")
      },

3.业务接口实现

请求路径: /itemCat/status/{id}/{status}
请求类型: put
请求参数:
在这里插入图片描述
返回值: SysResult对象
项目--京淘项目_第53张图片

4.编辑ItemCatController
 /**
     *
     * url:/itemCat/status/{id}/{status}
     * 请求类型: put
     * 请求参数:/{id}/{status}
     * 返回值: SysResult对象
     */
    @PutMapping("/status/{id}/{status}")
    public SysResult updateStatus(ItemCat itemCat){
        itemCatService.updateStatus(itemCat);
        return SysResult.success(itemCat);
    }
5.编辑ItemCatService
void updateStatus(ItemCat itemCat);
6.编辑ItemCatServiceImpl
 @Override
    @Transactional//事务控制
    public void updateStatus(ItemCat itemCat) {
        itemCatMapper.updateById(itemCat);
    }

5.商品分类新增

1.业务说明

说明: 商品分类实现中,需要添加一级/二级/三级分类信息. 但是父级下拉框中勾选1-2级菜单. 因为三级菜单不能当作父级. 当用户编辑完成之后,点击确定实现商品分类入库.
项目--京淘项目_第54张图片

2.页面JS分析
 <!-- 2.1定义一行 使用栅格-->
      <el-row>
        <el-col :span="24">
          <el-button type="primary" @click="showAddItemCatDialog">新增分类</el-button>
        </el-col>
      </el-row>

 //当展现新增商品分类时,应该渲染级联框数据
      showAddItemCatDialog() {
        this.findParentItemCatList()
        this.addItemCatDialogVisible = true
      },
      async findParentItemCatList() {
        //动态获取商品分类信息  type=2表示获取2级商品分类信息
        const {
          data: result
        } = await this.$http.get("/itemCat/findItemCatList/2")
        if (result.status !== 200) return this.$message.error("获取商品分类列表失败!!")
        this.parentItemCatList = result.data
      },

	  //商品分类确定按钮实现
	   <span slot="footer" class="dialog-footer">
        <el-button @click="addItemCatDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="addItemCatForm">确 定</el-button>
      </span>
	
	  //新增商品分类JS
	   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
        })
      },


3.业务接口文档

请求路径: /itemCat/saveItemCat
请求类型: post
请求参数: 表单数据
项目--京淘项目_第55张图片
返回值: SysResult对象
项目--京淘项目_第56张图片

4.编辑ItemCatController
/**
     * 需求:实现商品分类新增操作
     * url:/itemCat/saveItemCat
     * 请求类型:post
     * 请求参数: 表单数据ItemCat对象接受
     * 返回值:SysResult对象
     */
    @PostMapping("/saveItemCat")
    public SysResult saveItemCat(@RequestBody ItemCat itemCat){
        itemCatService.saveItemCat(itemCat);
        return SysResult.success();
    }
5.编辑ItemCatService
void saveItemCat(ItemCat itemCat);
6.编辑ItemCatServiceImpl
 @Override
    @Transactional
    public void saveItemCat(ItemCat itemCat) {
        itemCat.setStatus(true);
        itemCatMapper.insert(itemCat);
    }

6.商品分类修改操作

1.修改按钮JS分析
 <el-table-column label="操作">
          <!-- 定义作用域插槽 定义标签等级-->
          <template slot-scope="scope">
            <el-button type="success" icon="el-icon-edit" @click="updateItemCatBtn(scope.row)">编辑</el-button>
            <el-button type="danger" icon="el-icon-delete" @click="deleteItemCatBtn(scope.row)">删除</el-button>
          </template>
        </el-table-column>


	 //由于有层级关系,所有修改只能修改名称
      updateItemCatBtn(itemCat) {
        this.updateItemCatForm = itemCat
        this.updateItemCatDialogVisible = true
      },


2.修改-确定按钮实现
 <!-- 添加修改分类对话框 -->
    <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>

	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;
      },	

3.业务接口文档

请求路径: /itemCat/updateItemCat
请求类型: put
请求参数: 表单数据 ItemCat对象
返回值: SysResult对象
项目--京淘项目_第57张图片

4.编辑ItemCatController
 /**
     * 需求:实现商品分类修改操作
     * url:/itemCat/updateItemCat
     * 请求类型: put
     * 请求参数: form表单数据   json ItemCat对象
     * 返回值: SysResult对象
     */
    @PutMapping("/updateItemCat")
    public SysResult updateItemCat(@RequestBody ItemCat itemCat){
        itemCatService.updateItemCat(itemCat);
        return SysResult.success();
    }
5.编辑ItemCatService
  void updateItemCat(ItemCat itemCat);
6.编辑ItemCatServiceImpl
  @Override
    @Transactional
    public void updateItemCat(ItemCat itemCat) {
        itemCatMapper.updateById(itemCat);
    }

7.商品分类删除操作

1.业务说明

规则:
1.如果删除的商品分类是三级,则可以直接删除.
2.如果删除的商品分类是二级,则先删除三级,在删除二级.
3.如果删除的商品分类是一级,则先删除三级/二级/一级
注意事务的控制.
项目--京淘项目_第58张图片

2.页面JS分析
deleteItemCatBtn (itemCat) {
      // 删除商品分类信息,如果为父级节点则需要删除所有的子级信息
      this.$confirm('此操作将永久删除该数据, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        // 传递分类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()
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        })
      })
    }
3.业务接口实现

请求路径: /itemCat/deleteItemCat
请求类型: delete
业务描述: 当删除节点为父级时,应该删除自身和所有的子节点
请求参数:
项目--京淘项目_第59张图片
返回值结果 SysResult对象11
项目--京淘项目_第60张图片

4.编辑ItemCatController
/**
     * url:/itemCat/deleteItemCat
     * 请求类型: delete
     * 业务描述: 当删除节点为父级时,应该删除自身和所有的子节点
     * 请求参数: id/level
     * 返回值: SysResult对象
     */
    @DeleteMapping("/deleteItemCat")
    public SysResult deleteItemCat(ItemCat itemCat){
        itemCatService.deleteItemCat(itemCat);
        return SysResult.success();
    }
5.编辑ItemCatService
 void deleteItemCat(ItemCat itemCat);
6.编辑ItemCatServiceImpl
/**
     * 需求:删除商品分类信息
     * 条件:如果有子级,应该先删除子级
     * Sql:
     *      *  DELETE FROM item_cat WHERE (parent_id
     *      IN (?,?) OR parent_id = ? OR id = ?)
     * @param itemCat
     */
    @Override
    @Transactional
    public void deleteItemCat(ItemCat itemCat) {
        //1 2 3
        int level = itemCat.getLevel();
        if (level == 3){
            //表示需要删除的数据是3级菜单,可以直接删除
            itemCatMapper.deleteById(itemCat.getId());
        }

        if(level == 2 ){
           QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
           queryWrapper.eq("parent_id", itemCat.getId())
                .or()
                .eq("id", itemCat.getId());
            itemCatMapper.delete(queryWrapper);
        }
        if(level == 1){
            //1.必须获取二级id
            QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("parent_id",itemCat.getId());
            //2.获取结果的第一列字段(主键)   二级id
            List twoIdsList = itemCatMapper.selectObjs(queryWrapper);
            //3.删除三级的数据
            queryWrapper.clear();//清空之前的条件
            queryWrapper.in(twoIdsList.size()>0,"parent_id",twoIdsList)
                    .or()
                    //删除parent_id 等于一级ID的,实则删除的是二级数据
                    .eq("parent_id", itemCat.getId())
                    .or()
                    //删除id=一级Id  则删除一级数据.
                    .eq("id",itemCat.getId());

            itemCatMapper.delete(queryWrapper);
        }


    }

8.商品模块实现

1.业务分析
1.表设计

在这里插入图片描述

2.编辑pojo
/**
 * @author 刘昱江
 * 时间 2021/4/7
 */
@TableName("item")
@Data
@Accessors(chain = true)
public class Item extends BasePojo{
    @TableId(type = IdType.AUTO)
    private Integer id;         //商品Id号
    private String title;       //商品标题信息
    private String sellPoint;   //卖点信息
    private Integer price;      //商品价格 扩大100倍 缩小100倍值不变
    private Integer num;        //商品数量
    private String images;       //商品图片
    private Integer itemCatId;  //商品分类ID号
    private Boolean status;     //状态信息    0 下架 1 上架

}
3.编辑Item的层级代码

项目--京淘项目_第61张图片

4.实现前台页面跳转

项目--京淘项目_第62张图片
页面效果展现:
项目--京淘项目_第63张图片

2.商品列表展现
1. 页面JS分析
	1.生命周期函数
	created() {
      //1.获取商品列表数据
      this.getItemList()
    },
	
	2.实现页面Ajax请求
	//实现商品信息分页查询
      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
      },

2.业务接口说明

请求路径: /item/getItemList?query=&pageNum=1&pageSize=10
请求类型: get
请求参数: 使用pageResult对象接收
项目--京淘项目_第64张图片
返回值结果:
项目--京淘项目_第65张图片

3.编辑ItemController
/**
     * 业务逻辑: 查询商品分页
     * URL: /item/getItemList?query=&pageNum=1&pageSize=10
     * 类型: GET
     * 接收参数: PageResult对象
     * 返回值: SysResult(pageResult)
     */
    @GetMapping("/getItemList")
    public SysResult getItemList(PageResult pageResult){//3个参数

        //参照user模块完成商品分页查询.
        pageResult = itemService.getItemList(pageResult);//+2
        return SysResult.success(pageResult);
    }
4.编辑ItemServiceImpl
/**
     *  需求: 实现商品分页查询
     * @param pageResult
     * @return
     */
    @Override
    public PageResult getItemList(PageResult pageResult) {
        //1.定义分页对象
        IPage page = new Page<>(pageResult.getPageNum(),
                                      pageResult.getPageSize() );
        //2.定义条件构造器
        //判断条件: 用户传递query 则添加where条件
        QueryWrapper<Item> queryWrapper = new QueryWrapper<>();
        boolean flag = StringUtils.hasLength(pageResult.getQuery());

        //leftLike:%_ 以什么结尾
        //rightLike:_% 以什么开头
        queryWrapper.like(flag,"title",pageResult.getQuery());
        //
        //3.进行分页查询
        //page接口原来只有2个,经过分页查询之后,有四个结果
        page = itemMapper.selectPage(page,queryWrapper);
        long total = page.getTotal();
        List<Item> rows = page.getRecords();
       /* pageResult.setTotal(page.getTotal())
                .setRows(page.getRecords());*/
        return pageResult.setTotal(total).setRows(rows);
    }
5.页面效果展现

项目--京淘项目_第66张图片
列宽度修改: 由于按钮显示不全,所以可以将width改为300px.
项目--京淘项目_第67张图片

3.VUE过滤器用法
1.需求说明

说明: 需要将价格信息缩小100倍. 保留2位小数.
项目--京淘项目_第68张图片

2.过滤器使用

项目--京淘项目_第69张图片
过滤器定义
说明: 由于过滤器是全局变量,写在main.js中

/* 定义过滤器
    1.参数: 将|左侧的数据,当作参数传递给函数.
    2.返回值: 必须添加返回值!!!
    Vue.filter("过滤器名称",过滤器动作函数(参数){

    })

 */
Vue.filter("priceFormat",function(price){

    return (price / 100).toFixed(2)
})

项目--京淘项目_第70张图片

4.添加商品
1.商品新增页面跳转

当用户点击添加商品时,应该跳转到商品添加页面‘项目--京淘项目_第71张图片

2. 页面跳转

项目--京淘项目_第72张图片

3.页面效果展现

项目--京淘项目_第73张图片

4.商品新增分析
	1.点击按钮分析
	 <!-- 定义添加商品按钮-->
            <el-button type="primary" class="addItemBtnClass" @click="addItemBtn">添加商品</el-button>
	
	2.检查JS函数
	 /* 添加商品按钮 */
      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 实现商品数据提交
        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")
      }

5.业务接口说明

请求路径: 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: "

请求参数: 使用ItemVO对象接收
项目--京淘项目_第74张图片
ItemVO参数详解:
Item对象
项目--京淘项目_第75张图片
itemDesc 对象

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

项目--京淘项目_第76张图片

6.封装ItemVO对象
/**
 * @author 刘昱江
 * 时间 2021/4/16
 */
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ItemVO {  //该对象封装商品所有的参数信息
    private Item item;
    private ItemDesc itemDesc;
    private ItemParam itemParam;
}

5.商品状态修改
1.业务分析
	//1.修改按钮的JS
<el-table-column prop="status" label="状态" width="80px">
          <template slot-scope="scope">
            <el-switch v-model="scope.row.status" active-color="#13ce66" inactive-color="#ff4949"
              @change="updateStatus(scope.row)"></el-switch>
          </template>
        </el-table-column>

	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("更新状态成功")
      },

2.业务接口

请求路径: /item/updateItemStatus
请求类型: put
请求参数: 使用对象接收
项目--京淘项目_第77张图片
返回值结果:
项目--京淘项目_第78张图片

3.编辑ItemController
/**
     * 需求:商品状态修改
     * 请求路径: /item/updateItemStatus
     * 请求类型: put
     * 请求参数: {id,status} json串   使用对象接收
     *      id: item.id,
     *  status: item.status
     * 返回值结果:SysResult
     *
     * 问题:什么时候使用resultful,什么时候使用get/put/delete请求方式
     *
     *      resultful是传参的一种方式
     */
    @PutMapping("/updateItemStatus")
    public SysResult updateItemStatus(@RequestBody Item item){
        itemService.updateItemStatus(item);
        return SysResult.success();
    }
4.编辑ItemServiceImpl
 @Override
    @Transactional
    public void updateItemStatus(Item item) {
        itemMapper.updateById(item);
    }
6.商品删除
1.业务分析

在这里插入图片描述

async deleteItemBtn (item) {
      // 消息确认框
      this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        // 根据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()
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        })
      })
    },
2.业务接口说明

商品数据删除
请求路径: /item/deleteItemById
请求类型: delete
请求参数:
在这里插入图片描述
返回值结果:
项目--京淘项目_第79张图片

3.编辑ItemController
/**
     * 请求路径: /item/deleteItemById
     * 请求类型: delete
     * 参数: User对象结构
     * 返回值:SysResult对象
     */
    @DeleteMapping("/deleteItemById")
    public SysResult deleteItemById(Item item){
        itemService.deleteItemById(item);
        return SysResult.success();
    }
4.编辑ItemServiceImpl
@Override
    @Transactional
    public void deleteItemById(Item item) {
        itemMapper.deleteById(item);
    }
7. 商品详情说明
1.业务说明

说明: Item中存储的是商品的基本信息,通常用于检索,或者数据展现. itemDesc一般存储的都是商品的详情信息. 为了用户的检索效率更好,所以一般先查询item数据,(itemDesc一般采用大字段的形式,存储html代码的片段).

要求: 一个商品对应一个详情信息, 所以 item和itemDesc一对一
一个商品对应一个详情
一个详情对应一个商品
隐藏属性: item.id = itemDesc.id

补充知识: 数据库中常见对应关系
一对一: 老婆和老公(通常)
一对多: 老公和情人
多对多: 老师和学生
一个老师对应多个学生
一个学生对应多个老师

2.表设计

项目--京淘项目_第80张图片

3.编辑ItemDesc
package com.jt.pojo;

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

/**
 * @author
 * 时间 2021/4/7
 */
@TableName("item_desc")
@Data
@Accessors(chain = true)
public class ItemDesc extends BasePojo{
    //要求:item和itemDesc一对一  id相同
    //     简化了外键约束
    private Integer id;
    private String itemDesc;

}

4.编辑ItemController
/**
     * 业务分析: 实现商品新增操作
     * URL地址: http://localhost:8091/item/saveItem
     * 请求:post
     * 参数: ItemVO的JSON串 {item,itemDesc}
     * 返回值: SysResult对象
     */
    @PostMapping("/saveItem")
    public SysResult saveItem(@RequestBody ItemVO itemVO){
        itemService.saveItem(itemVO);
        return SysResult.success();
    }
5.重构ItemServiceImpl
//实现商品入库
    @Override
    @Transactional
    public void saveItem(ItemVO itemVO) {
        //item  主键自增  数据库入库之后,才会有主键!!!
        Item item = itemVO.getItem();
        item.setStatus(true);
        itemMapper.insert(item);
        //问题:入库之后ID,现阶段item.id=null
        //mybatis实现业务功能,自动回显主键
        //MP自动的将主键的回显功能实现!!!

//        itemMapper.selectOne(new QueryWrapper<>(item));
        //规则:itemId与ItemDescId是一样的

        ItemDesc itemDesc = itemVO.getItemDesc();
        itemDesc.setId(item.getId());
        itemDescMapper.insert(itemDesc);
    }
8. 富文本编辑器

说明: 使用富文本编辑器,可以得到 “所见即所得”
用法:

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		

2.页面展现

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

注意事项: 图片/视频默认转化为01字节信息,并且以16进制的方式展现.

3.页面效果
项目--京淘项目_第81张图片

十四.关于数据库主外键说明

说明:

  1. 数据库提供了主外键的约束关系, 操作数据时必须严格的遵守.但是如果添加了主键/外键,则必然会影响执行的效率. 所以一般主键和外键的约束由程序员通过代码进行控制.
  2. 一般项目中 很少直接定义主键/外键.

十五.ElementUI中文件上传

1.UI入门案例

<el-upload
  class="upload-demo"
  action="https://jsonplaceholder.typicode.com/posts/"
  :on-preview="handlePreview"
  :on-remove="handleRemove"
  :file-list="fileList"
  list-type="picture">
  <el-button size="small" type="primary">点击上传</el-button>
  <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>

<script>
    //对外声明组件属性/方法等参数.要被根组件调用
    export default {
      data(){
        return {
          tableData: [
            {id:100, name:"黑熊精", age: 3000, sex:"男"},
            {id:100, name:"黑旋风", age: 3000, sex:"男"},
            {id:100, name:"黑心肠", age: 3000, sex:"男"},
            {id:100, name:"黑桃A", age: 3000, sex:"男"}
          ],
          dialogVisible: true,
          fileList: [{name: 'food.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'}, {name: 'food2.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'}]

        }
      },
      methods: {
        handleSizeChange(size){
          alert("当前条数:"+size)
        },
        handleCurrentChange(current){
          alert("当前页数:"+current)
        },
        handleRemove(file, fileList) {
          console.log("点击删除按钮时,触发JS")
          console.log(file, fileList);
        },
        handlePreview(file) {
          console.log("点击url地址之后,触发!!!!!")
          console.log(file);
        }
      }
    }
</script>

十六.文件上传入门案例

1.UI页面JS分析

<!--
                了解:
                  action: 文件上传提交的地址
                  name:   默认文件上传的名称 file
            -->
            <el-upload class="upload-demo" :action="uploadUrl" :on-preview="handlePreview" :on-remove="handleRemove"
              :on-success="handleSuccess" list-type="picture" multiple drag>
              <el-button size="small" type="primary">点击上传</el-button>
              <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
            </el-upload>

2.业务接口说明

请求路径: http://localhost:8091/file/upload
请求类型: post
请求参数:
在这里插入图片描述
返回值结果:
项目--京淘项目_第82张图片
ImageVO对象说明
项目--京淘项目_第83张图片

3. 编辑ImageVO

说明: 为了封装文件上传之后的结果,需要封装VO对象 格式如下

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ImageVO implements Serializable {

    private String virtualPath; //虚拟地址,不包含磁盘地址
    private String urlPath;     //URL地址信息.
    private String fileName;    //文件名称
}

4.入门案例实现

package com.jt.controller;

import com.jt.vo.SysResult;
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
@CrossOrigin
@RequestMapping("/file")
public class FileController {

    /**
     * 业务: 实现文件上传
     * url: /file/upload
     * 请求类型: POST
     * 参数: file
     * 返回值: SysResult(imageVO)
     * 高级API:MultipartFile 自动维护了缓存流/自动开关
     *
     * 文件上传步骤:
     *      1.获取文件名称.
     *      2.准备上传文件的目录
     *      3.封装文件全路径  目录/文件名称
     *      4.实现文件上传
     */
    @PostMapping("/upload")
    public SysResult upload(MultipartFile file) throws IOException {
        //1.获取文件名称  a.jpg
        String fileName = file.getOriginalFilename();
        //2.准备文件目录
        String fileDir = "G:/images/";
        //2.1 判断目录是否存在
        File dir = new File(fileDir);
        if(!dir.exists()){
            //如果目录不存在,则创建多级目录
            dir.mkdirs();
        }
        //3.准备文件全路径
        String localPath = fileDir + fileName;
        //4.实现文件输出
        file.transferTo(new File(localPath));
        System.out.println("文件上传成功!!!!");
        return SysResult.success();
    }


}

十七.图片上传业务实现

1.文件上传的注入事项

控制文件上传的类型(后台为主)
控制恶意程序的上传 通过宽度和高度
为了提高检索的速度,应该分目录存储.
动态生成文件名称 UUID,防止文件重名.
实现文件上传 注意路径.

2. 正则表达式

1.正则说明

正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。

2.语法介绍

标识开头/结尾/匹配多次
项目--京淘项目_第84张图片
regex: abc 标识只能匹配固定的字符abc
regex: abc* 标识匹配 ab,c可以任意次.
regex: abc? 标识匹配 ab,c可以1次或者0次.

2.匹配确定次
项目--京淘项目_第85张图片
regex: c{3} c只能出现3次
regex: c{3,} c出现>=3次
regex: c{3,10} c出现>=3 <=10次
regex: .* 匹配所有字符.

3.匹配固定字符
项目--京淘项目_第86张图片

regex:

 	 a[ab]     匹配2个字符  第一个字符必须为a, 第二个字符 必须a 或者b
 	 [a-z][0-9]  第一个字符是a-z  第二个字符必须是0-9

demo: (png|jpg|gif) 字符要么是png,要么是ipg 要么是gif.

3.编辑FileController

package com.jt.controller;

import com.jt.service.FileService;
import com.jt.vo.ImageVO;
import com.jt.vo.SysResult;
import org.apache.ibatis.jdbc.Null;
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 javax.swing.plaf.multi.MultiInternalFrameUI;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {
    @Autowired
    private FileService fileService;
    /**
     * 业务:实现文件上传
     * url:/file/upload
     * 请求类型:post
     * 参数:file
     * 返回值:SysResult(imageVO)
     * 高级api:MultipartFile  自动维护了缓存流/自动开关
     *
     * 文件上传步骤:
     *      1.获取文件名称
     *      2.准备上传文件的目录
     *      3.封装文件的全路径   目录/文件名称
     *      4.实现文件上传
     *
     */
    /*
    @PostMapping("/upload")
    public SysResult upload(MultipartFile file) throws IOException {
        // 1.获取文件名称
        String fileName = file.getOriginalFilename();
        //  准备文件目录
        String fileDir = "E:/Third/images/";
        //2.1 判断目录是否存在
        File dir = new File(fileDir);
        if (!dir.exists()){
            //如果目录不存在,则创建目录
            dir.mkdirs();//创建多级目录
//            dir.mkdir();//创建一级目录
        }
        //3.准备文件全路径
        String localPath = fileDir + fileName;
        //4.实现文件输出  输出流
        file.transferTo(new File(localPath));
        System.out.println("上传文件成功!!!!!!!!!");
        return SysResult.success();
    }*/

    @PostMapping("/upload")
    public SysResult upload(MultipartFile file) throws IOException{
        ImageVO imageVO = fileService.upload(file);
        if (imageVO == null){//说明业务执行有误
            return SysResult.fail();
        }

        return SysResult.success(imageVO);
    }
}

4.编辑FileServiceImpl

package com.jt.service;

import com.jt.vo.ImageVO;
import org.springframework.beans.factory.annotation.Autowired;
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 localDir = "E:/Third/images";
    /*@Autowired
    private FileMapper fileMapper;*/

    /**
     * 1.校验是否为图片
     * 2.木马.exe.jpg 判断是否满足图片固有属性  高度/宽度
     * 3.为了提高查询效率,要求分目录存储
     *      3.1 按照后缀名分配  jpg,png,gif  效率提升不能满足要求
     *      3.2 按照日期分   yyy/MM/dd/HH/ 可以
     *      3.3 商品分类  出现分布不均现象
     *      3.4 根据名称hash 之后截串
     *          demmo:hash(a)=qw|er|as|dg/a.jpg
     *          弊端:hash码可能出现分布不均的现象
     * 4.防止文件重名  使用uuid代替名称
     * @param file
     * @return
     */
    @Override
    public ImageVO upload(MultipartFile file) {
        //1.获取图片名称  demo: abc.jpg  abc.JPG
        String fileName = file.getOriginalFilename();
        //bug说明:由于windows系统不区分大小写,所以将字母全部转化为小写
        fileName = fileName.toLowerCase();
        //利用正则判断是否为图片
        if(!fileName.matches("^.+\\.(jpg|png|gif)$")){
            //如果不是图片,则返回null

            return null;
        }
        //2.检查文件是否为恶意程序
        try {
            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            int width = bufferedImage.getWidth();
            int height = bufferedImage.getHeight();
            if (width == 0 || height == 0){
                //说明文件不是图片
                return null;
            }
            //3.根据时间实现目录的创建  时间---yyyy/MM/dd
            String dateDir = new SimpleDateFormat("/yyyy/MM/dd/")
                    .format(new Date());
            //"G:/images/2021/9/8"
            String localDirPath = localDir+ dateDir;
            //创建目录:
            File dirFile = new File(localDirPath);
            if(!dirFile.exists()){
                dirFile.mkdirs();
            }
            //4.使用uuid替换文件名称  唯一:系统内部唯一
           String uuid = UUID.randomUUID().toString()
                   .replace("-", "");
            //截取文件的后缀 abc.jpg
            int index = fileName.lastIndexOf(".");
            //获取类型 .jpg
            String fileType = fileName.substring(index);
            String newFileName = uuid + fileType;
            //5.实现文件上传操作  目录/文件名称
            String realFilePath = localDirPath + newFileName;
            file.transferTo(new File(realFilePath));
            System.out.println("文件上传成功!!!!");

            //6.封装返回值
            /**6.1
             *  封装虚拟路径,在各个系统之间可以灵活切换,只保存动态的目录
             *  path = 时间/uuid.type
             *
             */
            String virtualPath = dateDir + newFileName;
            String url = "https://img14.360buyimg.com/n0/jfs/t1/203725/5/5245/117493/61370b24E0b7558ce/4f6716a30595b275.jpg";
           return new ImageVO(virtualPath,url,newFileName);


        } catch (IOException e) {
            e.printStackTrace();
            return null;//表示程序有问题
        }

    }
}

5.页面效果展现

项目--京淘项目_第87张图片

十八.关于商品新增文件模块说明

重点: 业务需求. 业务顺序

1.商品基本模块

用户点击添加商品按钮时, 1.获取商品分类3级列表信息. 2.跳转到商品新增页面.
用户录入基本的商品信息.
用户录入价格时 需要在后期将数据扩大100倍.

2.商品上传

  1. 当用户点击上传图片时,根据 属性名称 file=“图片的字节信息” 实现数据的传递
  2. 后端通过特定的接口MultipartFile(接收字节信息,内部封装IO流)
  3. 上传文件时 首先使用正则表达式保证文件的类型正确
  4. 判断图片是否为恶意程序.由于图片有特定的属性width/height 获取文件的宽度和高度,如果有一项为0则上传的文件不是图片.
  5. 为了提高文件的查询效率,将文件进行分目录存储. 采用的策略以"时间为维度"进行的目录分隔.
  6. 为了防止文件重名,采用UUID的方式重新定义文件名称.
  7. 最终实现文件上传.返回特定的VO数据供前端展现.

3.商品详情

  1. 通常商品信息和详情信息是分开维护的.由于商品详情是大字段(文本域),查询的效率低.
  2. 商品表与详情表是一对一的对应关系. 设计时 item.id=itemDesc.id.
  3. 之后利用富文本编辑器(所见即所得),展现详情信息,其中存储的是html代码片段.
  4. 最后将item/itemDesc封装之后提交入库.

十九.实现图片功能

1.图片删除

1.业务需求说明

当用户点击删除图片时,应该发起请求同时删除服务器的数据.
重点: 如果需要删除数据,则应该传递虚拟路径信息
项目--京淘项目_第88张图片

2.删除JS分析

//移除图片的方法
      async handleRemove(file) {
        //移除数组中的数据
        let virtualPath = file.response.data.virtualPath
        //通过findIndex函数 获取数组中指定数据的位置
        let index = this.addItemForm.images.findIndex(x => x === virtualPath)
        //删除数组中指定的数据
        this.addItemForm.images.splice(index, 1)
        //删除服务中的文件
        let {
          data: result
        } = await this.$http.delete("/file/deleteFile", {
          params: {
            virtualPath: virtualPath
          }
        })
        if (result.status !== 200) return this.$message.error("删除图片失败")
        this.$message.success("删除图片成功")
      },

3.业务接口说明

请求路径: http://localhost:8091/file/deleteFile
请求类型: delete
请求参数:
在这里插入图片描述
返回值结果:
项目--京淘项目_第89张图片

4.编辑FileController

/**
     * 业务:删除图片
     * url: http://localhost:8091/file/deleteFile
     * 参数:虚拟路径
     * 返回值:SysResult 对象
     *
     */
    @DeleteMapping("/deleteFile")
    public SysResult deleteFile(String virtualPath){
        fileService.deleteFile(virtualPath);
        return SysResult.success();
    }

5.编辑FileServiceImpl

/**
     * 实现思路:
     *      1.根据虚拟地址,拼接磁盘地址
     *      2.判断文件是否存在
     *      3.实现文件删除
     * @param virtualPath
     */
    @Override
    public void deleteFile(String virtualPath) {
        //1.生成本地磁盘地址
        String path = localDir + virtualPath;
        File file = new File(path);
        if (file.exists()){
            file.delete();
        }

    }

2.图片网络路径说明

1.业务说明

如果用户需要查看服务器中的图片,则一般使用网络地址.
难点: 图片真实存在于磁盘地址中. 如何实现磁盘地址与网络地址的映射!
业务分析:
1.本地磁盘地址: ‪G:\images\2021\09\09\abc.jpg
2.网络访问地址: http://image.jt.com\2021\09\09\abc.jpg

2.封装图片网络地址

说明: 编辑完整的FileServiceImpl

package com.jt.service;


import com.jt.vo.ImageVO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
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
@PropertySource("classpath:/image.properties")
public class FileServiceImpl implements FileService{
    //封装路径的前缀
    @Value("${image.localDir}")
    private String localDir;/* = "E:/Third/images";*/
    @Value("${image.perUrl}")
    private String perUrl;/* = "http://image.jt.com";*/


    /**
     * 1.校验是否为图片
     * 2.木马.exe.jpg 判断是否满足图片固有属性  高度/宽度
     * 3.为了提高查询效率,要求分目录存储
     *      3.1 按照后缀名分配  jpg,png,gif  效率提升不能满足要求
     *      3.2 按照日期分   yyy/MM/dd/HH/ 可以
     *      3.3 商品分类  出现分布不均现象
     *      3.4 根据名称hash 之后截串
     *          demmo:hash(a)=qw|er|as|dg/a.jpg
     *          弊端:hash码可能出现分布不均的现象
     * 4.防止文件重名  使用uuid代替名称
     * @param file
     * @return
     */
    @Override
    public ImageVO upload(MultipartFile file) {
        //1.获取图片名称  demo: abc.jpg  abc.JPG
        String fileName = file.getOriginalFilename();
        //bug说明:由于windows系统不区分大小写,所以将字母全部转化为小写
        fileName = fileName.toLowerCase();
        //利用正则判断是否为图片
        if(!fileName.matches("^.+\\.(jpg|png|gif)$")){
            //如果不是图片,则返回null

            return null;
        }
        //2.检查文件是否为恶意程序
        try {
            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            int width = bufferedImage.getWidth();
            int height = bufferedImage.getHeight();
            if (width == 0 || height == 0){
                //说明文件不是图片
                return null;
            }
            //3.根据时间实现目录的创建  时间---yyyy/MM/dd
            String dateDir = new SimpleDateFormat("/yyyy/MM/dd/")
                    .format(new Date());
            //"G:/images/2021/9/8"
            String localDirPath = localDir+ dateDir;
            //创建目录:
            File dirFile = new File(localDirPath);
            if(!dirFile.exists()){
                dirFile.mkdirs();
            }
            //4.使用uuid替换文件名称  唯一:系统内部唯一
           String uuid = UUID.randomUUID().toString()
                   .replace("-", "");
            //截取文件的后缀 abc.jpg
            int index = fileName.lastIndexOf(".");
            //获取类型 .jpg
            String fileType = fileName.substring(index);
            String newFileName = uuid + fileType;
            //5.实现文件上传操作  目录/文件名称
            String realFilePath = localDirPath + newFileName;
            file.transferTo(new File(realFilePath));
            System.out.println("文件上传成功!!!!");

            //6.封装返回值
            /**6.1
             *  封装虚拟路径,在各个系统之间可以灵活切换,只保存动态的目录
             *  path = 时间/uuid.type
             *  网络地址://http:image.jt.com/2021/11/11/a.jpg
             *
             */
            String virtualPath = dateDir + newFileName;
            //http://image.jt.com/2021/11/11/a.jpg
            String url = perUrl + virtualPath;

            System.out.println("磁盘地址"+realFilePath);
            System.out.println("网络地址:"+url);

           return new ImageVO(virtualPath,url,newFileName);


        } catch (IOException e) {
            e.printStackTrace();
            return null;//表示程序有问题
        }

    }

    /**
     * 实现思路:
     *      1.根据虚拟地址,拼接磁盘地址
     *      2.判断文件是否存在
     *      3.实现文件删除
     * @param virtualPath
     */
    @Override
    public void deleteFile(String virtualPath) {
        //1.生成本地磁盘地址
        String path = localDir + virtualPath;
        File file = new File(path);
        if (file.exists()){
            file.delete();
        }

    }
}

3.后台数据优化

1.业务说明

说明: 如果路径信息会发生变化,则最好的方式通过动态赋值的方式完成.
解决方案:

  1. 编辑properties文件
  2. 通过@Value注解动态赋值.
  3. 项目--京淘项目_第90张图片

2.编辑image.properties文件

#通过配置文件 动态赋值
image.localDir=G:/images
image.preUrl=http://image.jt.com

3.动态为属性赋值

项目--京淘项目_第91张图片

二十.代理机制

1.业务说明

网络地址: http://image.jt.com/2021/09/09/8a3e2666cfbc4f1c9f0136339d42a562.jpg
磁盘地址: G:/images/2021/09/09/8a3e2666cfbc4f1c9f0136339d42a562.jpg
如果用户直接通过网络地址进行访问,是无法直接获取图片信息. 如果需要获取图片则应该实现 域名与磁盘地址的映射.

2.反向代理

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

特点:
1.反向代理服务器位于用户和服务器之间.
2.用户访问反向代理服务器,就可以获取真实的资源.
3.反向代理机制 用户无需了解真实的服务器信息.
4.反向代理保护了服务器端信息,也称之为服务器端代理.

3.正向代理

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

特点:

  1. 正向代理服务器位于用户和服务器之间.
  2. 用户发起请求时,非常明确自己访问的服务器到底是谁
  3. 真实的服务器不清楚真实的用户是谁.保护了用户的信息.
    所以称之为客户端代理.
    项目--京淘项目_第92张图片

4.正向/反向代理说明

说明: 用户每一次请求都包含了正向代理和反向代理.
正向代理一般适用于网络的通信.
反向代理一般适用于服务器获取信息.
项目--京淘项目_第93张图片

二十一.nginx

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网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。

2.nginx特点:

  1. nginx是非常优秀的反向代理服务器
  2. 占有内存少 不到2M tomcat服务器占有多大内存—200M左右
  3. 并发能力强 3~ 5万次/秒 tomcat服务器并发能力 250~500次/秒 调优之后可以实现1000次/秒
  4. nginx可以当作负载均衡服务器作用

3. Nginx下载

注意事项:
1.nginx开发语言 C语言,对中文不友好.所以注意程序员操守
2.nginx启动时会默认占用80端口
项目--京淘项目_第94张图片
项目--京淘项目_第95张图片

4.nginx启动报错说明

说明:nginx启动时会占用80端口.所以需要释放80资源.
步骤1: 查询 80端口被哪个进程占用
项目--京淘项目_第96张图片
步骤2: 关闭进程
项目--京淘项目_第97张图片
步骤3: 如果80端口 被PID=4占用,则需要升级驱动配置.
项目--京淘项目_第98张图片

5.nginx进程项说明

说明: 在windows中nginx服务每次点击启动之后,都会生成2个进程项.
注意事项: 在windows中nginx只能启动一次.
异常信息如下:
项目--京淘项目_第99张图片

关于启动2项说明:
进程项1: nginx主要进程信息.
进程项2: nginx的守护进程 主要的任务防止主进程意外关闭.
关闭nginx 应该先关闭守护(内存晓得)再关闭主进程(内存大的).

6.nginx命令

说明: nginx命令执行 需要nginx.exe所在的根目录中执行.
项目--京淘项目_第100张图片

命令:

  1. 启动命令 start nginx
  2. 重启命令 nginx -s reload
  3. 关闭命令 nginx -s stop
    4.项目--京淘项目_第101张图片

7.反向代理入门案例

知识点:

  1. nginx反向代理需要http协议支持.
  2. server 每个反向代理服务都是一个server.
  3. listen 关键字 默认监听80端口.
  4. server_name 根据指定的域名进行反向代理
  5. location 反向代理时拦截的策略 / 所有的请求
  6. root 代表反向代理的是一个目录
  7. index 默认访问的页面

反向代理配置:

 http {
	 server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
	 }
}

8.实现图片的反向代理

1.业务说明

磁盘地址:G:/images/2021/09/09/a.jpg
网络地址:http://image.jt.com/2021/09/09/a.jpg

问题: 如何通过网络地址访问磁盘的图片?
项目--京淘项目_第102张图片
用户通过域名访问真实的服务器.
2.nginx根据自身的配置进行拦截,根据配置文件将域名http://image.jt.com转化为具体的磁盘地址 G:/
3.根据磁盘地址访问真实的服务器资源.
4/5. 服务器将数据交给nginx,之后nginx将数据返回给用户.至此实现了反向代理.

2.图片服务器代理配置

注意事项:
1.启动时没有报错信息
2.重启时才会有报错. 所有最好先执行启动,再执行重启

#配置图片服务器
	#拦截域名:http://image.jt.com:80
	#代理路径:G:/images 
	server {
		listen 80;
		server_name image.jt.com;
		location / {
			root  G:/images;
		}
	}

3.图片回显流程

项目--京淘项目_第103张图片
业务说明: 操作系统为了测试方便,在计算中保留了hosts文件. 该文件的主要的作用就是实现域名与IP地址的映射.但是该映射,只对本机有效.

4.修改hosts文件

路径: C:\Windows\System32\drivers\etc

#  IP 与   域名映射
#	127.0.0.1       localhost
#	::1             localhost

#图片服务器配置
127.0.0.1       image.jt.com
#前端服务器配置
127.0.0.1       www.jt.com
#后端服务器配置
127.0.0.1       manage.jt.com

解决方案:
1.添加管理权限 选中hosts文件之后 右键属性.
项目--京淘项目_第104张图片
方式2: 以超级管理员的方式运行Switch hosts软件
项目--京淘项目_第105张图片
方式3: 添加指定的用户权限 步骤 1.获取当前计算机的名称 PC 2.添加用户信息.
注意事项: 计算机名称不要写中文.
项目--京淘项目_第106张图片
方式4: 取消只读属性
项目--京淘项目_第107张图片

5.页面效果展现

项目--京淘项目_第108张图片

二十二.图片上传不回显问题说明:

1.现象说明:

说明:文件上传正确,但是回显出问题,应该按照如下的方式进行调试
项目--京淘项目_第109张图片

2.检查路径是否正确

1.检查磁盘地址/网络地址 除了前缀不同之处,其他的必须相同
磁盘地址:E:/Third/images/2021/09/10/84c43d94354a4feb9c982d9e5ddbe609.jpg
网络地址:http://image.jt.com/2021/09/10/84c43d94354a4feb9c982d9e5ddbe609.jpg

2.检查前端业务地址
复制地址之后,将前缀换位本地磁盘地址,再次检查路径是否正确.
项目--京淘项目_第110张图片
检查路径.
项目--京淘项目_第111张图片

3.检查Nginx服务器启动项
项目--京淘项目_第112张图片

4.检查Nginx配置文件
域名与磁盘的映射必须正确

项目--京淘项目_第113张图片

5.检查hosts文件是否正确

项目--京淘项目_第114张图片
6.检查hosts文件是否有效 如图表示hosts文件一切正常. 如果不能显示,则重启计算机.
项目--京淘项目_第115张图片

二十三.京淘项目前端项目发布

1.关于前端说明:

  1. vue项目开发阶段采用脚手架的方式进行开发。如果开发完成应该将项目打包编译,编译为浏览器可以识别的静态资源文件(HTML/CSS/JS)
  2. Nginx可以作为web服务器,并且默认端口号是80

2.前端项目发布

1.路径修改

说明: 前端向后端发起请求时,网址 http://localhost:8091/xxxx,实际开发中服务器都是通过域名的方式访问,所以需要将前端的网址统一改为域名.
修改main.js 修改ajax请求的前缀
项目--京淘项目_第116张图片
改AddItem.vue文件 修改文件上传的路径
项目--京淘项目_第117张图片

2.前端项目打包

通过build方式,将前端项目编译打包.
项目--京淘项目_第118张图片

3.前端项目发布

说明: 将前端生成的dist目录 复制到nginx根目录中 如图所示:
项目--京淘项目_第119张图片

4.前端反向代理

需求: 用户通过域名http://www.jt.com 访问系统的首页index.html
配置信息:

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

页面效果:
项目--京淘项目_第120张图片

二十四. 京淘项目后端发布

1.项目部署流程

项目--京淘项目_第121张图片

2.部署2台tomcat服务器

1.去除热部署

说明: 现在需要准备2台tomcat服务器,需要执行main方法2次.如果有热部署,则修改代码之后重启会影响配置流程. 所有关闭热部署.
项目--京淘项目_第122张图片

2.动态获取端口

说明: 由于nginx中有负载均衡的策略 所以需要动态获取端口,验证是否负载均衡.

@RestController
public class PortController {

    @Value("${server.port}")
    private Integer port;

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

        return "访问端口:" + port;
    }
}

3.代码调试

1.根据8091启动服务器.
2.修改yml文件中的端口号8092,之后再次启动服务
如图所示:
项目--京淘项目_第123张图片

4.根据端口号测试

项目--京淘项目_第124张图片
项目--京淘项目_第125张图片
如果上述的测试通过,则表示后台服务器配置正确.

5. IDEA主启动项说明

如图需要将允许多个运行勾选,之后可以运行多个java程序.
项目--京淘项目_第126张图片

3.Nginx实现tomcat集群部署

1.配置nginx服务器

#定义tomcat集群
	# 负载均衡策略: 1.轮询策略
	upstream tomcats {
		server 127.0.0.1:8091;
		server 127.0.0.1:8092;
	}
	
	#配置后台服务器 manage.jt.com  8091/8092
	server {
		listen 80;
		server_name manage.jt.com;

		location / {
			#代理的是一个请求路径
			proxy_pass http://tomcats;
		}
	}

2.测试效果

项目--京淘项目_第127张图片
项目--京淘项目_第128张图片

4.Nginx负载均衡策略

1.轮询策略

说明: tomcat服务器依次访问.

#定义tomcat集群
	# 负载均衡策略: 1.轮询策略
	upstream tomcats {
		server 127.0.0.1:8091;
		server 127.0.0.1:8092;
	}

2. 权重策略

说明: 根据权重设定,分配网络请求到不同的服务器中. 值越大访问越多.

#定义tomcat集群
	# 负载均衡策略: 1.轮询策略  2.权重策略
	upstream tomcats {
		server 127.0.0.1:8091 weight=10;
		server 127.0.0.1:8092 weight=1;
	}

3. IPHASH策略

需求: 如果需要让用户与服务器绑定.则可以使用ip_hash策略
使用说明:
1.方便进行压力测试.
2.某些用户的数据保存到服务器的Session中时,需要绑定数据.
3.公司特殊业务场景可能用到iphash.

#定义tomcat集群
	# 负载均衡策略: 1.轮询策略  2.权重策略  3.iphash策略
	upstream tomcats {
		ip_hash;
		server 127.0.0.1:8091;
		server 127.0.0.1:8092;
	}

算法说明:
项目--京淘项目_第129张图片

二十五.Linux

具体内容参见课前资料的文档.
项目--京淘项目_第130张图片

1.部署JDK

1.部署JDK流程

上传JDK安装包 /usr/local/src
解压安装包
修改Linux环境变量
JDK环境测试

2.上传JDK

说明: 通过远程工具中的sftp协议,实现文件上传.
项目--京淘项目_第131张图片

3.解压命令

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

2.修改文件名称 mv jdk1.8.0_51 jdk1.8
项目--京淘项目_第132张图片

4. 编辑Linux环境变量

1.路径: /etc/profile
2.修改环境变量:
项目--京淘项目_第133张图片

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

5.检查JDK是否有效

项目--京淘项目_第134张图片

你可能感兴趣的:(项目,mysql,spring,boot,vue)