毕业设计学习锋迷商城的的笔记(手写后台商品管理,分类管理,用户,地址管理系统)


目录
  • https://player.bilibili.com/player.html?aid=641370075)(image-https://img-blog.csdnimg.cn/img_convert/6fc48aff3f62b207e20ca7badb21395a.png)(title-毕业设计SpringBoot+Vue+ElementUI商城系统实现(有后台))]

    论文地址

    商城论文地址

    后台代码视频实现讲解思路

    [video(video-zgz2Gybc-1667056389114)(type-bilibili)(url-https://player.bilibili.com/player.html?aid=816242633)(image-https://img-blog.csdnimg.cn/img_convert/69a030b2c0a14349f093065947c707ae.jpeg)(title-毕业设计锋迷商城手敲后台管理,实现逻辑讲解,代码讲解)]

    b站地址

    1. 商品管理

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

    2.商品分类管理

    在这里插入图片描述

    3.商品地址管理

    在这里插入图片描述

    4.用户中心管理

    在这里插入图片描述

    4. 用户权限管理

    5.订单管理

    5.1

    6.商品品牌管理

    锋迷商城项目

    使用Maven聚合项目进行创建(一个maven的父项目多个maven的子项目),

    可以在父项目pom.xml文件中加上:

    pom
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aKHt478Q-1633568521449)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210812151308862.png)]

    1.通过Maven聚合工程搭建项目:

    1. 创建一个Maven的父项目,然后修改它的pom.xml文件,可以删除src等一些没有用的目录

    pom
    

    2.在父项目下面创建多个module,包括(common,beans,mapper,service,api)把它们全部打包成jar包

    pom.xml加上

    jar
    

    3.由于mapper层需要调用beans层(pojo),需要在pom.xml文件中,然后可以在mapper中加入相关的依赖。

       
    
    
            
                org.example
                beans
                2.0.1
            
        
    

    4.创建service需要调用mapper,和common,需要在pom.xml文件中加上

     
                org.example
                mapper
                2.0.1
            
            
                org.example
                common
                2.0.1
            
    
    

    5.api层需要接收前端的请求所以需要我们创建一个SpringBoot工程,你可以创建一个Maven工程,然后加入相关的依赖

    6.api子工程,对外提供接口

    总的说父项目的所以依赖可以被子项目引用,子项目也可以单独的添加所需的依赖

    锋迷商城数据库设计

    2.软件开发步骤

    • 提出问题

    • 可行性分析(技术(一般可以相关人员实现),成本,法律法规)

    • 概要设计

      • 系统设计(技术选型,架构模式)
      • 数据库设计
      • UI设计
      • 业务流程设计
    • 详细设计

      • 实现步骤(业务流程的实现细节)
    • 编码

      • 根据设计好的实现步骤进行代码实现
      • 开发过程使用单元测试
    • 测试

      • 集成测试
      • 功能测试(墨盒)
      • 性能测试(白盒)高并发,压力测试
    • 交付/部署实施

      3.数据库设计流程

    • 根据功能分析出数据库实体(javaBean)

      • 商品,订单,购物车,用户,地址...
    • 提取实体属性

      • spu商品(id,商品名称,商品图片,商品描述...)

      • 1 min10 ..... ....

      • sku(skuId, 参数 , 价格 商品id

      • 101 内存8G\存储128G 2999 1

      • 102 内存12G\存储256G 3999 1

      • 地址(姓名,地址,电话.....)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P8zP9MYA-1633446686624)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210814172548189.png)]

    可以知道价格的依赖于参数的改变而改变,参数依赖于id改变,不满足数据库设计表的要求,可以设计两张表进行实现。

    • 使用数据库的第三范式进行检查数据项是否合理
    • 分析实体关系图:E-R图 (一对一,一对多)
    • 数据库建模(三线图)建模工具(PdMan)
    • 建库建表-sql

    3.数据建模工具PDMan

    • 可视化创建数据库表(数据表

    • 视图显示表之间的关系(关系图)

    • 导出sql指令(模型---导出DDL脚本

    • 记录数据库模型版本管理

    • 可以连接数据库直接生成表

      Spu和Sku的区别

    • spu(Standard Product Unit):商品信息聚合的最小 单位。通俗讲属性值,特性相同的商品可以称为一个SPU.

      产品: 荣耀8 小米10

    • sku(Stock Keeping Unit)最小存货单元,定义为保存最小库存的控制最小可用单元

      sku 荣耀8 8G/128G 10

      sku 荣耀8 4G/124G 20

      注意一下 :订单表的设计功能:因为订单表只要用户一下订单,订单表的相关信息就不可以进行改变 ,所以需要进行地址的的快照和商品信息的快照,这样就算你临时改变了价格的信息或者其他的也没有关系

      购物车的设计:

    4.锋城业务流程设计

    在企业开发中,当完成项目的需求分析,功能分析,数据库分析与设计后,项目组就会按照项目中的功能模块进行开发任务的分配。

    每个人会被分配不同的功能

    4.1用户管理9业务流程分析

    单体架构:页面和控制之间可以进行跳转,同步请求控制器,流程控制由控制器来完成

    前后端分离架构:前端和后端开发开发和部署,前端只能通过异步发送请求,后端只负责接收请求及参数,处理请求,返回结果

    前端可以发送如图所示的请求:需要url,params

    5接口介绍

    狭义:的理解:就是控制器中可以接受用户请求的方法

    标准定义:API(Application Programming interface)应用程序编程接口,就是软件系统不同组成部分衔接的约定。

    5.1接口规范

    作为后端程序员不仅要完成接口程序的开发,还要编写接口的说明文档---接口规范

    5.2Swagger(自动生成服务器接口的规范性文档)

    前后端分离规开发,后端需要编写接口说明文档,会耗费比较多的时间

    swagger是一个用于生成服务器接口的的规范性文档,并且能够对接口进行测试的工具。

    • swagger作用
    • 生成接口规范性文档
    • 生成接口测试工具

    5.2.1引入相关的依赖:

    
            
                io.springfox
                springfox-swagger2
                2.9.2
            
    
            
                io.springfox
                springfox-swagger-ui
                2.9.2
            
    
    
    

    5.2.2 创建相关的配置类

    可以在api这个module中进行相关的controller层的测试,建立一个config包下面的SwaggerConfig类进行相关的测试,加上@Configuration,@EnableSwagger2注解,然后进行配置相关的信息

    package com.qfedu.fmmall.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.w3c.dom.DocumentType;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.service.Contact;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    @Configuration
    @EnableSwagger2
    public class SwaggerConfig {
        /*
        * swagger生成我们的接口文档:
        * 1.需要配置生成文档的信息
        * 2.配置生成规则
        *
        * */
        @Bean
        public Docket docket(){
    
    //创建封面信息对象
            ApiInfoBuilder apiInfoBuilder=new ApiInfoBuilder();//指定生成文档中的封面信息:文档标题,作者,版本
            apiInfoBuilder.title("《锋迷商城》后端接口说明")
                    .description("此文档详细说明了锋迷商城项目后端接口规范")
                    .version("v 2.0.1")
                    .contact(new Contact("houge","www.houge.com","houge@hou.com"));
    
    
            ApiInfo apiInfo=apiInfoBuilder.build();
    
    
            Docket docket=new Docket(DocumentationType.SWAGGER_2) //指定文档风格
                    .apiInfo(apiInfo)
                    .select()
                    .apis(RequestHandlerSelectors.basePackage("com.qfedu.fmmall.controller"))
    //                定义了path之后只会为user开头的请求进行扫描 .paths(PathSelectors.regex("/user/"))
    //                PathSelectors.any()表示任何的请求
                    .paths(PathSelectors.any())
                    .build();
    
    
            return docket;
    
        }
    
    
    
    
    }
    
    

    5.2.3根据你的配置的端口号进行相关的测试

    http://localhost:8080/swagger-ui.html

    5.2.4 swagger提供了一套注解对每个接口进行详细的说明

    @Api(value=" 用户管理",tags="提供用户的登录和注册的接口")//这个接口可以直接放在@Controller注解下面
    

    @ApiOperation 和ApiImplicitParams({ @ApiImplicitParam(dataType="",name="username",value="",required=true), @ApiImplictParm}) 这两个注解放在@RequestMapping("/login")请求之上,用来修饰方法和方法中的参数。

     @ApiOperation("用户登录的接口")
        @ApiImplicitParams({
                @ApiImplicitParam(dataType = "string",name = "username",value = "用户登录的账号",required = true),
                @ApiImplicitParam(dataType = "string",name = "password",value = "用户登录的密码",defaultValue = "111111",required = false)
        })
        @RequestMapping("/login")
    //    @RequestParam可以有默认的参数
        public ResultVO login(@RequestParam("username") String name,@RequestParam(value = "password",defaultValue = "111111") String pwd){
    
            return userService.checkLogin(name,pwd);
    
    
        }
        @RequestMapping(value = "regist",metho
    

    @ApiModel 和@ApiModelProperty接口参数返回一个对象类型时,需要在实体类中添加注解说明(也就是Beans这个Module进行相关的配置)

    package com.qfedu.fmmall.entity;
    
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    
    @ApiModel(value = "用户的买家信息",description = "买家的相关的参数")
    public class User {
    
        @ApiModelProperty(name = "用户id",required = false,dataType = "int")
        private Integer userId;
    
        @ApiModelProperty(dataType = "string",name = "买家姓名",required = true)
        private String  userName;
        @ApiModelProperty(dataType = "string",name = "买家密码",required = true)
        private String userPwd;
        @ApiModelProperty(dataType = "string",name = "买家真实姓名",required = true)
        private String userRealname;
        @ApiModelProperty(dataType = "string",name = "用户图片",required = true)
        private String userImg;
    
    
    }
    
    @ApiIgnore     接口方法注解,添加此注解的方法将不会生成到接口文档中
    

    5.2.5swagger-ui插件使用

    1.api的module加入依赖
    
            
            
                com.github.xiaoymin
                swagger-bootstrap-ui
                1.9.6
            
    
    
    
    2.进行访问,然后可以使用它进行相关的测试

    http://ip:port/doc.html

    一、锋迷商城设计及实现用户管理

    1.UserDao接口的创建:

    package com.qfedu.fmmall.dao;
    
    import com.qfedu.fmmall.entity.User;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public interface UserDao {
    
    //    用户注册
        public int insert(User user);
    
    //   根据用户名进行登录的验证
        public User queryByName(String name);
    
    
    }
    
    
    

    2.UserMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    
    
    
     
      
    
     
     
     
     
     
     
     
     
         
         
         
    
     
    
        
        
        
    
            insert into users(username,password,user_regtime,user_modtime) values (#{username},
            #{password},#{userRegtime},#{userModtime})
        
        
    
    

    3.UserService

    package com.qfedu.fmmall.service;
    
    import com.qfedu.fmmall.entity.User;
    import com.qfedu.fmmall.vo.ResultVO;
    
    public interface UserService {
    //    ResultVO是一个响应给前端的自定义的一个类。
        public ResultVO checkLogin(String username, String pwd);
    
    //    用户注册
        public ResultVO insert(String username, String pwd);
    }
    
    

    4.UserServiceimpl:

    package com.qfedu.fmmall.service.impl;
    
    import com.qfedu.fmmall.service.UserService;
    import com.qfedu.fmmall.dao.UserDao;
    import com.qfedu.fmmall.entity.User;
    import com.qfedu.fmmall.utils.MD5Utils;
    import com.qfedu.fmmall.vo.ResultVO;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Scope;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.Date;
    
    @Service
    @Transactional
    //使所有的线程都用这个对象,单例模式默认是开启的
    @Scope("singleton")
    public class UserServiceimpl implements UserService {
        @Autowired
        private UserDao userDao;//可以在UserDao上面加上userDao,这个不会报红,但是没有什么意义
        @Override
        public ResultVO checkLogin(String username, String pwd) {
    //        查询用户名
            User user = userDao.queryByName(username);
            if(user==null){
    //            用户名不正确
    
                return new ResultVO(10001,"用户名不正确",null);
    
    
    
            }else {
                //密码使用MD5进行加密
                String md5Pwd = MD5Utils.md5(pwd);
    
                if(md5Pwd.equals(user.getPassword())){
    //          验证成功
                    return  new ResultVO(200,"登录成功",user);
                }else {
                    //密码不正确
                    return  new ResultVO(10001,"密码错误",null);
    
                }
    
    
            }
    
    
    
        }
        @Transactional
        @Override
        public ResultVO insert(String username, String pwd) {
    //        判断这个用户是否被注册
    
    //        加上这个锁可以使用所有的注册都用这个userServiceimpl
            synchronized (this){
    //            把密码进行MD5的加密
                String password = MD5Utils.md5(pwd);
    
                User user1 = userDao.queryByName(username);
    //表示用户名没有被注册过,可以进行注册
                if (user1==null){
    //一个是注册时间,regtime,一个是修改时间modtime
                    User user=new User(username,password,new Date(),new Date());
                    int i = userDao.insert(user);
                    if(i>0){
                        return new ResultVO(1000,"注册成功",null);
                    }else {
    
                        return new ResultVO(1001,"注册失败",null);
    
                    }
    
    
                }
    //            判断一下用户名是否已经被注册,然后把数据返回前端,goodjob,Noone can influence you
                else {
    
                    return new ResultVO(1001,"用户名已经被注册",null);
                }
    
    
    
            }
    
    
        }
    }
    
    

    5.api模块的UserController:

    package com.qfedu.fmmall.vo;
    
    
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    
    @ApiModel(value = "ResultVO对象",description = "响应封装的数据给前端")
    public class ResultVO {
    //    响应给前端的状态码
        @ApiModelProperty(dataType = "int",value = "响应的状态码")
        private  int code;
    
    //    响应给前端的提示消息
        @ApiModelProperty(dataType = "string",value = "响应的消息")
        private  String msg;
    //响应给前端的数据
        @ApiModelProperty(dataType = "object",value = "响应数据的内容")
        private  Object data;
    }
    
    

    6.ResultVO一个和前端进行数据交互的类

    package com.qfedu.fmmall.vo;
    
    
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    
    @ApiModel(value = "ResultVO对象",description = "响应封装的数据给前端")
    public class ResultVO {
    //    响应给前端的状态码
        @ApiModelProperty(dataType = "int",value = "响应的状态码")
        private  int code;
    
    //    响应给前端的提示消息
        @ApiModelProperty(dataType = "string",value = "响应的消息")
        private  String msg;
    //响应给前端的数据
        @ApiModelProperty(dataType = "object",value = "响应数据的内容")
        private  Object data;
    }
    
    

    7.在common模块的MD5Utils类:

    package com.qfedu.fmmall.utils;
    
    import java.math.BigInteger;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    
    //MD5 生成器
    public class MD5Utils {
    	public static String md5(String password){
    		//生成一个md5加密器
    		try {
    			MessageDigest md = MessageDigest.getInstance("MD5");
    			//计算MD5 的值
    			md.update(password.getBytes());
    			//BigInteger 将8位的字符串 转成16位的字符串 得到的字符串形式是哈希码值
    			//BigInteger(参数1,参数2) 参数1 是 1为正数 0为0 -1为负数
    			return new BigInteger(1, md.digest()).toString(16);
    		} catch (NoSuchAlgorithmException e) {
    			e.printStackTrace();
    		}
    		return null;
    	}
    }
    
    

    二、逆向工程

    根据创建好的表,生成实体类,和DAO层、映射文件

    在Dependencies下面加入依赖,这个依赖是一个Mybatis的maven插件

    
        
            
                org.mybatis.generator
                mybatis-generator-maven-plugin
                1.3.5
                
                    ${basedir}/src/main/resources/generator/generatorConfig.xml
                
                
    
                
                    
                        mysql
                        mysql-connector-java
                        5.1.46
                    
                    
                        tk.mybatis
                        mapper
                        4.1.5
                    
    
    
                
    
      
    
        
    
    
    
    

    7.1逆向工程配置

    在resources的generator目录下面创建generatorConfig.xml

    1. 需要修改数据库的配置

    2. 需要修改pojo,mapper,Mapper.xml文件生成位置的配置

    3. 
              
    4. 
             
                 
             
        
      
    5. 指定你的用Configuration generatorConfig.xml文件的路径

    6. 注意一下你的文件一定想要有空格什么东西的

    <?xml version="1.0" encoding="UTF-8"?>
    
    
    
        
    
    
    
        
        
    
            
            
    
            
            
                
            
    
            
            
                
                
                
            
    
    
            
            
            
    
            
                
                
            
    
            
            
            
                
                
            
    
            
            
    
            
            
    
            
            

    7.2在pom.xml文件中指定generatorConfig.xml文件的路径

    加上了这个:

     
                    ${basedir}/src/main/resources/generator/generatorConfig.xml
                
    
            
                org.mybatis.generator
                mybatis-generator-maven-plugin
                1.3.5
                
                    ${basedir}/src/main/resources/generator/generatorConfig.xml
                
    
                
                    
                        mysql
                        mysql-connector-java
                        5.1.46
                    
                    
                        tk.mybatis
                        mapper
                        4.1.5
                    
    
    
                
    
    
            
    

    三、跨域问题

    解决方案:
    后端解决办法:在UserController加上@CrossOrigin注解

    前端通过Ajax请求跨域登录:

    		

    前端使用JSONP设置,后端使用@CrossOrigin注解解决---设置响应头header允许跨域。
    debugger;前端 可以加上代码debugger进行相关的调试。可以直接进行前端的校验

    四、前端页面的传值

    cookie和localstorage可以进行前端的页面之间的传值

    Cookie浏览器端的缓存文件:大小受浏览器的限制。

    LocalStorage:为了存储更大容量的数据

    区别:cookie可以和后台进行传值,localStorage只可以在前端存储值,但是存储的时间长。

    4.1Cookie使用(自定义封装一个js,cookie_utils.js)

    var opertor="=";
    
    function getCookieValue(keyStr){
    	
    	
    	var s=window.document.cookie;
    	var arr=s.split("; ");
    for(var i=0;i

    A页面设置值:

    function(res){
    	  console.log(res);
    	  if(res.code==1000){
    // 获取前端传过来的数据 data
    		var userInfo=res.data;
    		// cookie和localstorage可以进行前端的页面之间的传值
    	setCookieValue("username",userInfo.username);
    	setCookieValue("userImg",userInfo.userImg);
    
    		window.location.href="index.html";
    	  }else{
    
    		$("#tips").html("");
    
    
    
    
    	  }
    
    

    B页面取值:

    var name=getCookieValue("username");
     var userImg=getCookieValue("userImg");
     console.log(name+userImg);
    
    

    4.2localStorage

    A页面:

    	localStorage.setItem("user",JSON.stringify(userInfo));
    

    B页面:

    var jsonStr=localStorage.getItem("user");
    
    // 把json串转换为对象
    var userInfo=eval("("+jsonStr+")");
    
    
    // 把取到的值消失
    localStorage.removeItem("user");
    console.log(userInfo);
    

    4.3Vue实现登录

    data:{
    		username:"",
    		password:"",	
    		tips:" ",
    		colorStyle:"",
    		isRight:false,
    	
    
    
    	},
    	methods:{
    		doSubmit:function() {
    			// 校验成功
    
    			if(vm.isRight){
    				var url=baseUrl+"/user/login";
    				axios.get(url,{	
    					params:{
    						username:vm.username,password:vm.password
    
    					} }
    				
    					).then((res)=>{
    
    				console.log(res);
    
    					var vo=res.data;
    					if(vo.code==1){
    						window.location.href="index.html";
    					}else{
    						vm.tips="账号或者密码错误";
    					}
    
    
    
    				});
    
    			}else{
    				vm.tips="请输入正确的用户名和密码";
    				vm.colorStyle="color:red"
    			}
    
                //  1.进行数据的校验
    
                if(vm.username==" "){
                    vm.tips="请输入用户名";
                    vm.colorStyle="color:red";
    
    
                }
                 
             },
             checkInfo:function(){
                if(vm.username==""){
                    vm.tips="请输入用户名";
                    this.colorStyle="color:red";
    				vm.isRight=false;
    
    
                }else if(vm.username.length<6 ||vm.username.length>20){
                    vm.tips="账号长度必须为6-20";
                    vm.colorStyle="color:red";
    				vm.isRight=false;
    
    
    
    
    
                }else{
    // 校验密码
    				if(vm.password==" "){
                    vm.tips="请输入密码";
                    this.colorStyle="color:red";
    				vm.isRight=false;
    
    
                }else if(vm.password.length<6 ||vm.password.length>16){
    				vm.tips="密码长度为6-16";
                    this.colorStyle="color:red";
    
    			}else{
    				vm.tips=" ";
    				vm.isRight=true;
    			}
    
    
    
    			}
    
    
    
    
    
             }
    
    
    
    
    
    
    
    	}
    
    
    

    from表单(用@keyup进行表单的输入的绑定):

    	

    五、前后端分离开发用户认证的问题

    5.1单体项目中:

    可以知道每台服务器中存在多个Session,只是id不相同,Cookie中可以存放sessionId,然后判断是是不是同一个session

    在单体项目中用户怎么认证的?

    在单体项目中视图资源和控制器都在同一台服务器,用户的多次请求老师基于同一个会话,可以基于session进行会话的验证:

    1. 用户登录将信息存放在session中
    2. 根据session中是否有用户信息来判断用户是否可以进行登录。

    5.2前后端分离项目中

    可以知道使用token实现用户验证,token存在于cookie中(同一台服务器可以访问cookie),然后验证token是否正确

    基于token认证的用户代码实现

    在commons模块中引入

    package com.qfedu.fmmall.utils;
    
    import java.util.Base64;
    
    //base64 加密 解密 激活邮件的时候 为 邮箱地址 code验证码 进行加密
    //当 回传回来后 进行邮箱地址 和 code 的解密
    public class Base64Utils {
    	//加密
    	public static String encode(String msg){
    		return Base64.getEncoder().encodeToString(msg.getBytes());
    	}
    	//解密
    	public static String decode(String msg){
    		return new String(Base64.getDecoder().decode(msg));
    	}
    }
    
    

    登录成功生成token:UserController

    package com.qfedu.fmmall.controller;
    
    
    import com.qfedu.fmmall.entity.Users;
    import com.qfedu.fmmall.service.UserService;
    import com.qfedu.fmmall.vo.ResultVO;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiImplicitParams;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    /*@Controller
    @ResponseBody*/
    
    @RestController
    @RequestMapping("/user")
    @CrossOrigin
    @Api(value = "提供用户的登录和注册的接口",tags = "用户管理")
    public class UserController {
    
        @Autowired
        private UserService userService;
    
    //    @ApiIgnore加上这个注解会swagger忽略这个方法
        @ApiOperation("用户登录的接口")
        @ApiImplicitParams({
                @ApiImplicitParam(dataType = "string",name = "username",value = "用户登录的账号",required = true),
                @ApiImplicitParam(dataType = "string",name = "password",value = "用户登录的密码",required = true)
        })
        @RequestMapping("/login")
    //    @RequestParam可以有默认的参数
        public ResultVO login(@RequestParam("username") String name,@RequestParam(value = "password") String pwd){
    
            return userService.checkLogin(name,pwd);
    
    
        }
    
        @ApiOperation(value = "用户注册")
        @PostMapping("/regist")
        @ApiImplicitParams({
                @ApiImplicitParam(dataType = "string",name = "username",value = "用户注册的账号",required = true),
                @ApiImplicitParam(dataType = "string",name = "password",value = "用户注册的密码",required = true)
        })
    //    前端用user传值,后端可以用users 接收
         public ResultVO register(@RequestBody Users users){
    
            return userService.insert(users.getUsername(),users.getPassword());
    
        }
    
    
    }
    

    然后在UserServiceimpl中进行token的加密:

    // 如果登录成功,则需要生成令牌token(token就是按照规则生成的字符串)
    String token= Base64Util.encode(username+"roothouzhicong");

    package com.qfedu.fmmall.service.impl;
    
    import com.qfedu.fmmall.dao.UsersMapper;
    import com.qfedu.fmmall.entity.Users;
    import com.qfedu.fmmall.service.UserService;
    import com.qfedu.fmmall.utils.MD5Utils;
    import com.qfedu.fmmall.vo.ResultStatus;
    import com.qfedu.fmmall.vo.ResultVO;
    import org.apache.logging.log4j.util.Base64Util;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Scope;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import tk.mybatis.mapper.entity.Example;
    
    import java.util.Date;
    import java.util.List;
    
    @Service
    @Transactional
    //使所有的线程都用这个对象,单例模式默认是开启的
    @Scope("singleton")
    public class UserServiceimpl implements UserService {
        @Autowired
        private UsersMapper userDao;//可以在UserDao上面加上userDao,这个不会报红,但是没有什么意义
        @Override
        public ResultVO checkLogin(String username, String pwd) {
    //        查询用户名
    
            Example example = new Example(Users.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andEqualTo("username",username);
            List users = userDao.selectByExample(example);
    
    //
            if(users.size()==0){
    //            用户名不正确
    
                return new ResultVO(10001,"用户名不正确",null);
    
    
    
            }else {
                //密码使用MD5进行加密
                String md5Pwd = MD5Utils.md5(pwd);
                System.out.println(users.get(0).getPassword());
    
                if(md5Pwd.equals(users.get(0).getPassword())){
    
    //                如果登录成功,则需要生成令牌token(token就是按照规则生成的字符串)
                    String token= Base64Util.encode(username+"roothouzhicong");
    
                    //          验证成功
                    return  new ResultVO(ResultStatus.OK,token,users.get(0));
                }else {
                    //密码不正确
                    return  new ResultVO(ResultStatus.NO,"密码错误",null);
    
                }
    
    
            }
    
    
    
        }
        @Transactional
        @Override
        public ResultVO insert(String username, String pwd) {
    //        判断这个用户是否被注册
    
    //        加上这个锁可以使用所有的注册都用这个userServiceimpl
            synchronized (this){
    //            把密码进行MD5的加密
                String password = MD5Utils.md5(pwd);
    
                //        查询用户名
    
                Example example = new Example(Users.class);
                Example.Criteria criteria = example.createCriteria();
                criteria.andEqualTo("username",username);
                List users = userDao.selectByExample(example);
    //表示用户名没有被注册过,可以进行注册
                if (users.size()==0){
    //一个是注册时间,regtime,一个是修改时间modtime
                    Users user=new Users(username,password,new Date(),new Date());
                    int i = userDao.insert(user);
                    if(i>0){
                        return new ResultVO(ResultStatus.OK,"注册成功",null);
                    }else {
    
                        return new ResultVO(ResultStatus.NO,"注册失败",null);
    
                    }
    
    
                }
    //            判断一下用户名是否已经被注册,然后把数据返回前端,goodjob,Noone can influence you
                else {
    
                    return new ResultVO(ResultStatus.NO,"用户名已经被注册",null);
                }
    
    
    
            }
    
    
        }
    }
    
    

    前端设置token:

    	doSubmit:function() {
    			// 校验成功
    
    			if(vm.isRight){
    				var url=baseUrl+"/user/login";
    				axios.get(url,{	
    					params:{
    						username:vm.username,password:vm.password
    
    					} }
    				
    					).then((res)=>{
    
    				console.log(res);
    
    					var vo=res.data;
    
    					console.log(vo);
    					if(vo.code==1){
    					
     // 如果登录成功就把token存储到时cookie中
    						setCookieValue("token",vo.msg);
    
    						 window.location.href="index.html";
    					}else{
    						vm.tips="账号或者密码错误";
    					}
    
    
    
    				});
    

    前端的购物车获取token:

    	
    

    登录进行来可以把购物车获取token,前端的token用CookieUtils.js封装的包进行相关的获取,

    package com.qfedu.fmmall.controller;
    
    
    import com.qfedu.fmmall.utils.Base64Utils;
    import com.qfedu.fmmall.vo.ResultStatus;
    import com.qfedu.fmmall.vo.ResultVO;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @CrossOrigin
    @Api(value = "提供购物车业务相关的接口",tags = "购物车管理接口")
    @RequestMapping("/shopcart")
    public class ShopCartController {
    
        @RequestMapping("/list")
        @ApiImplicitParam(dataType = "string",name = "token",value = "登录的一个标志",required = true)
        public ResultVO shopcartList(String token){
    //        校验输入的token看看是否是用户自己登录的token
           //解密 
            String decode = Base64Utils.decode(token);
            if(token==null){
                return new ResultVO(ResultStatus.NO, "请先登录", null);
    
    
            }else if(decode.endsWith("roothouzhicong")) {
    
    
                System.out.println("购物车列表相关的接口------------");
                return new ResultVO(ResultStatus.OK, "success", null);
    
    
            }else {
    
                return new ResultVO(ResultStatus.NO, "登录已经过期,请重新登录!!", null);
    
            }
    
    
        }
    }
    
    

    六、JWT(json Web Token)一个别人封装好的工具类生成相关的token

    1. 用自定义的token生成的时效性不可以进行定义
    2. 安全性较差

    JWT结构:

    6.1生成JWT

    • 添加依赖:

      
              
                  com.auth0
                  java-jwt
                  3.10.3
              
      
              
              io.jsonwebtoken
              jjwt
              0.9.1
          
      
      

    UserServiceimpl(登录成功)生成token:

     HashMap map=new HashMap<>();
    
                    JwtBuilder builder= Jwts.builder();
                    String token = builder.setSubject(username)   //主题就是token中携带的数据
                            .setIssuedAt(new Date()) //设置token的生成时间
                            .setId(users.get(0).getUserId() + "") //设置用户的id为tokenid
                            .setClaims(map)                         //map中可以存放用户的角色权限信息
                            .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)) //设置token的过期时间
                            .signWith(SignatureAlgorithm.HS256, "houzhicong") //设置加密的方式
                            .compact();
    
                    //          验证成功
    

    前端ShopCart.html通过Cookie获取生成的token:

    	
    

    后端ShopController进行解析Token:

    package com.qfedu.fmmall.controller;
    
    
    import com.qfedu.fmmall.utils.Base64Utils;
    import com.qfedu.fmmall.vo.ResultStatus;
    import com.qfedu.fmmall.vo.ResultVO;
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jws;
    import io.jsonwebtoken.JwtParser;
    import io.jsonwebtoken.Jwts;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @CrossOrigin
    @Api(value = "提供购物车业务相关的接口",tags = "购物车管理接口")
    @RequestMapping("/shopcart")
    public class ShopCartController {
    
        @RequestMapping("/list")
        @ApiImplicitParam(dataType = "string",name = "token",value = "登录的一个标志",required = true)
        public ResultVO shopcartList(String token){
    //        校验输入的token看看是否是用户自己登录的token
    //        String decode = Base64Utils.decode(token);
            if(token==null){
                return new ResultVO(ResultStatus.NO, "请先登录", null);
    
    
            }else {
                JwtParser parser= Jwts.parser();
                parser.setSigningKey("houzhicong");//解析token 必须和生成token时候生成的密码一致
    
    //            如果token正确(密码正确,有效期内) 则正常执行,否则抛出异常
                try{
    
    
                    Jws claimsJws=parser.parseClaimsJws(token);
    
                    Claims body=claimsJws.getBody();//获取token中的用户数据
                    String subject=body.getSubject();//获取生成token设置subject
                   String v1=body.get("key1",String.class);//获取生成token时存储的Claims的map中的值
                    return new ResultVO(ResultStatus.OK, "success", null);
    
                }catch (Exception e){
                    return new ResultVO(ResultStatus.NO, "登录已经过期,请重新登录!!", null);
    
    
                }
    
    
    
            }
    
    
        }
    }
    
    

    6.2使用拦截器进行拦截

    1. 创建一个CheckTokenInterceptor
    2. 创建一个拦截器的类 InterceptorConfig
    6.3.1有
    
    
    package com.qfedu.fmmall.config;
    
    import com.qfedu.fmmall.interceptor.CheckTokenInterceptor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    public class InterceptorConfig implements WebMvcConfigurer {
        @Autowired
        private CheckTokenInterceptor checkTokenInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new CheckTokenInterceptor())
                    .addPathPatterns("/**")
                    .excludePathPatterns("/user/**"
                    ,"/doc.html"
                    ,"/swagger-ui/**");
        }
    }
    

    6.3使用请求头进行传递token

    前端但凡访问受限资源,都必须携带token请求,token可以通过请求行(params),请求头(header),以及请求体(data)传递,但习惯使用header传递

    axios通过请求头传值 里面的参数用Headers 不用Params

    axios({
    					method:"get",
    					url:baseUrl+"shopcart/list",
    					Headers:{
    						token:this.token,
    					}
    
    				}).then(function (res) {
    					console.log(res);
    				});
    			},
    			
    

    6.3.1 CheckTokenInterceptor类 前端会发送预险性请求(只有它通过以后才可以进行第二次请求),需要拦截器进行放行

    package com.qfedu.fmmall.interceptor;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.qfedu.fmmall.vo.ResultStatus;
    import com.qfedu.fmmall.vo.ResultVO;
    import io.jsonwebtoken.*;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    @Configuration
    public class CheckTokenInterceptor implements HandlerInterceptor {
    //   打ov 可以看到它的方法
    
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String token = request.getParameter("token");
    
    //        System.out.println("token----------");
    
    //        前端会发送预险性请求
            String method = request.getMethod();
    
            if("options".equalsIgnoreCase(method)){
    
                return true;
            }
    
            if(token==null){
    //            提示用户进行登录
    
                PrintWriter out = response.getWriter();
                ResultVO resultVO= new ResultVO(ResultStatus.NO, "请先登录", null);
    //         抽出一个方法进行
                doResponse(response,resultVO);
    
    
    //            拦截
                return  false;
    
            }else {
    //            验证token
    
                try{
    
                    JwtParser parser= Jwts.parser();
                    parser.setSigningKey("houzhicong");
                    Jws claimsJws=parser.parseClaimsJws(token);
                    return true;
                }catch (ExpiredJwtException e){
                    ResultVO resultVO= new ResultVO(ResultStatus.NO, "登录过期,请重新登录", null);
                    doResponse(response,resultVO);
    
    
                    return false;
                }
                catch (UnsupportedJwtException e){
                    ResultVO resultVO= new ResultVO(ResultStatus.NO, "Token不合法,请自重", null);
                    doResponse(response,resultVO);
    
    
                    return false;
                }
    
                catch (Exception e){
                    ResultVO resultVO= new ResultVO(ResultStatus.NO, "请先登录", null);
                    doResponse(response,resultVO);
    
    
                    return false;
                }
    
    
    
    
    
            }
    
        }
    
        private void doResponse(HttpServletResponse response,  ResultVO resultVO) throws IOException {
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            PrintWriter out = response.getWriter();
    //        写上Json格式的resultVO
            String s = new ObjectMapper().writeValueAsString(resultVO);
            out.println(s);
            out.flush();
            out.close();
    
    
        }
    }
    
    

    七首页的分类列表的的实现

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wQm4qlh3-1633446686637)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210823182623779.png)]

    得出结论:数据量较少的情况,使用一次性查询,数据量较多使用多次查询

    方案一:一次性查询三级分类

    • 优点只需一查询
    • 缺点:数据库查询效率较低,页面首次加载的速度较慢

    方案二:

    • 先只查询一级分类,用户点击/鼠标移动到一级分类,动态加载二级分类
    • 缺点:需要多次连接数据库

    7.1接口实现

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WFClkTnv-1633446686638)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210823204928346.png)]

    一次性查询出来的sql语句:inner join 和left join 的区别 left join 左边没有关联的数据也会全部显示

    select 
    
    c1.category_id 'category_id1',
    c1.category_name 'category_name',
    c1.category_level 'category_level',
    c1.parent_id 'parent_id',
    c1.category_icon 'category_icon',
    c1.category_slogan 'category_slogan',
    c1.category_pic 'category_pic',
    c1.category_bg_color 'category_bg_color',
    
    c2.category_id 'category_id2',
    c2.category_name 'category_name2',
    c2.category_level 'category_leve2',
    c2.parent_id 'parent_id2',
    
    
    
    
    c3.category_id 'category_id3',
    c3.category_name 'category_name3',
    c3.category_level 'category_leve3',
    c3.parent_id 'parent_id3'
    
    from category c1
    left join category c2 on c2.parent_id=c1.category_id
    left join category c3 on c3.parent_id=c2.category_id
    where c1.category_level=1
    
    
    select *from category c1
      inner join category c2 on c2.parent_id=c1.category_id
      left join category c3 on c3.parent_id=c2.category_id
       where c1.category_level=1
     
    
    --根据父级分类的parent_id进行查询 1 级 parent_id=0
    select *from category where parent_id=0,
    
    • 创建用于封装查询的类别信息CategoryVO

      在Beans模块中entity包下面创建一个CategoryVO实体类用于封装Category和前端 进行数据的响应,相对于Category多了这个属性

      //实体类中实现当前分类的子分类
      private List categories;
      
              public List getCategories() {
                  return categories;
              }
      
              public void setCategories(List categories) {
                  this.categories = categories;
              } 
      
    • 在CategoryMapper中定义一个函数

      package com.qfedu.fmmall.dao;
      
      import com.qfedu.fmmall.entity.Category;
      import com.qfedu.fmmall.entity.CategoryVO;
      import com.qfedu.fmmall.general.GeneralDao;
      
      import java.util.List;
      
      public interface CategoryMapper extends GeneralDao {
      //使用连接查询实现分类列表查询
          public List selectAllCategories();
          
          
          //    子查询
          public List selectAllCategories2(int parentId);
      }
      
    • 映射文件配置

      <?xml version="1.0" encoding="UTF-8"?>
      
      
        
          
          
          
          
          
          
          
          
        
      
      
      
        
          
          
          
          
          
          
          
          
          
            
            
            
            
      
           
      
             
             
             
             
      
      
           
          
      
        
      
      
      
      
      
      
      
      
      
        
          
          
         
      
      
    
    使用子查询实现分类列表的查询:
    
    ```xml
        
    
      
        
        
        
        
        
        
        
        
        
    
    
    
      
    
    
    
    
    
    
      
      
    
    
    

    7.2业务层实现

    CategoryService

    package com.qfedu.fmmall.service;
    
    import com.qfedu.fmmall.vo.ResultVO;
    
    public interface CategoryService {
    
        public ResultVO queryAllCategory();
    }
    
    

    CategoryServiceimpl:

    package com.qfedu.fmmall.service.impl;
    
    import com.qfedu.fmmall.dao.CategoryMapper;
    import com.qfedu.fmmall.entity.CategoryVO;
    import com.qfedu.fmmall.service.CategoryService;
    import com.qfedu.fmmall.vo.ResultStatus;
    import com.qfedu.fmmall.vo.ResultVO;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    
    @Service
    public class CategoryServiceimpl implements CategoryService {
        @Resource
        private CategoryMapper categoryMapper;
    
        @Override
        public ResultVO queryAllCategory() {
            List categoryVOS = categoryMapper.selectAllCategories2(0);
    
            return new ResultVO(ResultStatus.OK,"success",categoryVOS);
    
    
        }
    }
    
    

    控制层实现

    indexController实现:

    @Autowired
        private CategoryService categoryService;
    
    
        @RequestMapping("category-list")
        @ApiOperation("商品分类查询接口")
        public ResultVO queryAllCategory(){
    
            return categoryService.queryAllCategory();
        }
    
    
    

    八、商品的推荐功能实现

    8.1 流程分析

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yol4wuqR-1633446686638)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210828192719883.png)]

    推荐最新上架的商品

    8.2接口开发

    8.2.1数据库的实现
    • sql语句实现

      -- 商品推荐查询最新上架信息
      select *from product order by create_time desc limit 0,3;
      -- 商品图片查询 根据商品id查询商品图片
      select *from product_img where item_id=2;
      
      

      在子工程beans工程下面创建ProductVO (加上了这个属性 private List imgs;因为一个商品包含多张表)

      package com.qfedu.fmmall.entity;
      
      import javax.persistence.Column;
      import javax.persistence.Id;
      import java.util.Date;
      import java.util.List;
      
      public class ProductVO {
          /**
           * 商品id
           */
          @Id
          @Column(name = "product_id")
          private Integer productId;
      
          /**
           * 商品名称
           */
          @Column(name = "product_name")
          private String productName;
      
          /**
           * 商品分类id
           */
          @Column(name = "category_id")
          private Integer categoryId;
      
          /**
           * 一级分类外键id
           */
          @Column(name = "root_category_id")
          private Integer rootCategoryId;
      
          /**
           * 销量
           */
          @Column(name = "sold_num")
          private Integer soldNum;
      
          /**
           * 商品状态
           */
          @Column(name = "product_status")
          private Integer productStatus;
      
          /**
           * 商品内容
           */
          private String content;
      
          /**
           * 创建时间
           */
          @Column(name = "create_time")
          private Date createTime;
      
          /**
           * 更新时间
           */
          @Column(name = "update_time")
          private Date updateTime;
      
      
          private List imgs;
      
          public List getImgs() {
              return imgs;
          }
      
          public void setImgs(List imgs) {
              this.imgs = imgs;
          }
      
          @Override
          public String toString() {
              return "ProductVO{" +
                      "imgs=" + imgs +
                      '}';
          }
      
          /**
           * 获取商品id
           *
           * @return product_id - 商品id
           */
          public Integer getProductId() {
              return productId;
          }
      
          /**
           * 设置商品id
           *
           * @param productId 商品id
           */
          public void setProductId(Integer productId) {
              this.productId = productId;
          }
      
          /**
           * 获取商品名称
           *
           * @return product_name - 商品名称
           */
          public String getProductName() {
              return productName;
          }
      
          /**
           * 设置商品名称
           *
           * @param productName 商品名称
           */
          public void setProductName(String productName) {
              this.productName = productName == null ? null : productName.trim();
          }
      
          /**
           * 获取商品分类id
           *
           * @return category_id - 商品分类id
           */
          public Integer getCategoryId() {
              return categoryId;
          }
      
          /**
           * 设置商品分类id
           *
           * @param categoryId 商品分类id
           */
          public void setCategoryId(Integer categoryId) {
              this.categoryId = categoryId;
          }
      
          /**
           * 获取一级分类外键id
           *
           * @return root_category_id - 一级分类外键id
           */
          public Integer getRootCategoryId() {
              return rootCategoryId;
          }
      
          /**
           * 设置一级分类外键id
           *
           * @param rootCategoryId 一级分类外键id
           */
          public void setRootCategoryId(Integer rootCategoryId) {
              this.rootCategoryId = rootCategoryId;
          }
      
          /**
           * 获取销量
           *
           * @return sold_num - 销量
           */
          public Integer getSoldNum() {
              return soldNum;
          }
      
          /**
           * 设置销量
           *
           * @param soldNum 销量
           */
          public void setSoldNum(Integer soldNum) {
              this.soldNum = soldNum;
          }
      
          /**
           * 获取商品状态
           *
           * @return product_status - 商品状态
           */
          public Integer getProductStatus() {
              return productStatus;
          }
      
          /**
           * 设置商品状态
           *
           * @param productStatus 商品状态
           */
          public void setProductStatus(Integer productStatus) {
              this.productStatus = productStatus;
          }
      
          /**
           * 获取商品内容
           *
           * @return content - 商品内容
           */
          public String getContent() {
              return content;
          }
      
          /**
           * 设置商品内容
           *
           * @param content 商品内容
           */
          public void setContent(String content) {
              this.content = content == null ? null : content.trim();
          }
      
          /**
           * 获取创建时间
           *
           * @return create_time - 创建时间
           */
          public Date getCreateTime() {
              return createTime;
          }
      
          /**
           * 设置创建时间
           *
           * @param createTime 创建时间
           */
          public void setCreateTime(Date createTime) {
              this.createTime = createTime;
          }
      
          /**
           * 获取更新时间
           *
           * @return update_time - 更新时间
           */
          public Date getUpdateTime() {
              return updateTime;
          }
      
          /**
           * 设置更新时间
           *
           * @param updateTime 更新时间
           */
          public void setUpdateTime(Date updateTime) {
              this.updateTime = updateTime;
          }
      }
      

      ProductMapper文件:

      package com.qfedu.fmmall.dao;
      
      import com.qfedu.fmmall.entity.Product;
      import com.qfedu.fmmall.entity.ProductVO;
      import com.qfedu.fmmall.general.GeneralDao;
      
      import java.util.List;
      
      public interface ProductMapper extends GeneralDao {
      //    查询推荐商品信息
          public List selectRecommendProducts();
      }
      

      ProductImgMapper文件:

      package com.qfedu.fmmall.dao;
      
      import com.qfedu.fmmall.entity.ProductImg;
      import com.qfedu.fmmall.general.GeneralDao;
      
      import java.util.List;
      
      public interface ProductImgMapper extends GeneralDao {
          public List selectProductImgByProductId(int productId);
      }
      

      ProductMapper.xml文件实现

      注意一下子查询 的语句:

       
      
      <?xml version="1.0" encoding="UTF-8"?>
      
      
        
          
          
          
          
          
          
          
          
          
          
        
      
        
          
          
          
          
          
          
          
          
          
          
          
        
      
      
        
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      

      8.2.2业务层实现

      package com.qfedu.fmmall.service;
      
      import com.qfedu.fmmall.entity.ProductVO;
      import com.qfedu.fmmall.vo.ResultVO;
      
      import java.util.List;
      
      public interface ProductService {
          public ResultVO selectRecommendProducts();
      
      }
      
      

      8.2.3控制层实现

       @ApiOperation("商品推荐查询信息接口")
          @RequestMapping(value = "/list-recommends",method = RequestMethod.GET)
          public ResultVO selectProductRecommend(){
              ResultVO resultVO = productService.selectRecommendProducts();
              return resultVO;
      
      
          }
      
      

    九、商品详情显示(在Introduction.html进行相关的显示)

    点击商品推荐,商品轮播图,商品的列表页面点击商品,就会进入到商品的详情页面。

    1. 获取商品id
    2. 可以查询商品的详情信息(商品的基本信息,商品套餐,商品图片信息。)
    3. 将商品参数返回给前端

    9.1接口实现

    9.1.1 商品详情接口

    商品基本信息(product),商品套餐(sku),商品图片(product_img)

    • SQL

      -- 根据商品id查询商品详情
      select *from product where product_id=3;
      -- 根据商品id查询商品图片详情
      select *from product_img where item_id=3;
      -- 根据商品id查询当前商品的套餐
      select *from product_sku where product_id=3;
      
    • 可以用子查询实现这个相关的操作

    • dao接口实现(通过product,product_img,product_sku三张表获取商品的详情信息)

      @Repository
      public interface ProductMapper extends GeneralDao {
      //    查询推荐商品信息
          public List selectRecommendProducts();
      }
      
      
      package com.qfedu.fmmall.dao;
      
      import com.qfedu.fmmall.entity.ProductImg;
      import com.qfedu.fmmall.general.GeneralDao;
      import org.springframework.stereotype.Repository;
      
      import java.util.List;
      @Repository
      public interface ProductImgMapper extends GeneralDao {
          public List selectProductImgByProductId(int productId);
      }
      
      package com.qfedu.fmmall.dao;
      
      import com.qfedu.fmmall.entity.ProductSku;
      import com.qfedu.fmmall.general.GeneralDao;
      import org.springframework.stereotype.Repository;
      
      @Repository
      public interface ProductSkuMapper extends GeneralDao {
      }
      
      
    • 业务层实现

    //这里为不需要事务,但是如果某些事务如果调用了我也加入到事务中来
    //    事务默认的隔离级别是可重复读 repeateable read
        @Transactional(propagation = Propagation.SUPPORTS)
        public ResultVO selectProductBasicInfo(String productId) {
    //      1.查询商品的基本信息
    
    
    
            Example example = new Example(Product.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andEqualTo("productId",productId);
            criteria.andEqualTo("productStatus",1);
    
            List products = productMapper.selectByExample(example);
    //        System.out.println(products);
    
    
            if(products.size()>0){
                //      2.查询商品的图片信息
               Example example1 = new Example(ProductImg.class);
                Example.Criteria criteria1 = example1.createCriteria();
                criteria1.andEqualTo("itmeId",productId);
                List productImgs = productImgMapperMapper.selectByExample(example1);
    //            System.out.println(productImgs);
    
    
                //        3.查询商品的套餐信息
    
                Example example2 = new Example(ProductSku.class);
                Example.Criteria criteria2 = example2.createCriteria();
                criteria2.andEqualTo("productId",productId);
                criteria2.andEqualTo("status",1);
                List productSkus = productSkuMapper.selectByExample(example2);
    //            System.out.println(productSkus);
    
    
    //            把所有的商品的详情信息放入HashMap当中进行使用
                HashMap basicInfo=new HashMap<>();
                basicInfo.put("product",products.get(0));
                basicInfo.put("productImgs",productImgs);
                basicInfo.put("productSkus",productSkus);
    
                return new ResultVO(ResultStatus.OK,"success",basicInfo);
    
    
    
    
    
            }else {
    
                new ResultVO(ResultStatus.NO,"查询商品基本信息失败",null);
            }
            return null;
        }
    
    • 控制层实现(这边把商品的详情的信息放到了ResultVO的Object中)
    //    商品详情查询
    
        @RequestMapping(value = "/detail/{pid}",method = RequestMethod.GET)
        public ResultVO getProductBasicInfo(@PathVariable("pid") String productId){
    
            ResultVO resultVO = productService.selectProductBasicInfo(productId);
    //        System.out.println(resultVO);
    
            return resultVO;
    
        }
    
    
    
    

    十、显示商品评价的信息(通过用户和商品评论表进行相关的连表查询)

    -- 根据评论的id查询评论信息,关联用户表查询用户信息
    select u.username,u.user_img,c.* from product_comments c
    inner join users u 
    on c.user_id=u.user_id
    where c.product_id=3
    

    10.1 新建的VO,ProductCommentsVO (一对一的连表查询可以不用在实体类中声明另一个实体类)

    package com.qfedu.fmmall.entity;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import javax.persistence.Column;
    import javax.persistence.Table;
    import java.util.Date;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class ProductCommentsVO {
    
    
        private Integer productId;
        private String productName;
        private Integer orderItemId;
    
        private String isannonymouns;
        private Integer commType;
        private Integer commLevel;
        private String commImgs;
        private String sepcName;
        private Integer replyStatus;
        private String replyContent;
        private Date replyTime;
        private Integer isShow;
    
    
    //用于封装评论对应的用户数据
        private Integer userId;
        private String username;
        private String nickname;
        private String userImg;
    
    
    }
    

    在Mapper定义相应的接口:

    package com.qfedu.fmmall.dao;
    
    import com.qfedu.fmmall.entity.ProductComments;
    import com.qfedu.fmmall.entity.ProductCommentsVO;
    import com.qfedu.fmmall.general.GeneralDao;
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    
    
    @Repository
    public interface ProductCommentsMapper extends GeneralDao {
    
        public List selectCommentsByProductId(int productId);
    }
    

    十一、添加购物车的功能实现

    10.1流程分析:

    点击添加购物车---------商品、选择套餐id,套餐属性,数量,token-------------进行token的校验

    10.2数据库的相关的操作

    1. 增加字段sku_props

      表生成之后 用逆向工程重新生成shopping_cart表的相关的结构。修改generalConfig.xml把%修改成shopping_cart

      注意一下%表示生成所有的表

    10.2.1 购买的数量的前端实现在vue的methods中添加+和-的点击事件

    	changeNum:function(m){
    						if(m==-1 && this.num>1){
    						this.num=this.num-1;
    
    						}else if(m==1 && this.num

    进行商品数量的绑定可以用 v-model="num"进行双向绑定

    库存{{productSkus[currentSkuIndex].stock}}

    10.2.2给加入购物车这个按钮添加点击事件

  • 把相关的添加的购物车的信息放入cart这个类中:

    					addShopCart(){
    					var uid=getCookieValue("userId");
    
    
    						var propStr="";
    						// 套餐属性转换成字符串
    						for(var key in this.chooseskuProps){
    							propStr+=key+":"+this.chooseskuProps[key]+";";
    }
    
    
    						var cart={
    									
    									"cartNum": this.num,
    									"cartTime": "",
    									"productId": this.productId,
    									"productPrice": this.productSkus[this.currentSkuIndex].sellPrice,
    									"skuId": this.productSkus.skuId,
    									"skuProps":propStr,
    									"userId": uid
    								};
    
    						//从cookie中获取token
    
    						var token=getCookieValue("token");
    						console.log("---token-------");
    						console.log(token);
    
    						// 把购物车的信息放入数据库中
    						var url5=baesUrl+"shopcart/add";
    						axios.post(
    							{
    								url:url5,
    								methods:"post",
    								headers:{
    									token:token
    								},
    								data:cart
    }
    
    
    						).then( res=>{
    
    							console.log("------res-----"+res);
    
    
    
    						}
    
    						);
    					 
    
    
    					}
    

    十二、添加购物车时候用户未登录

    12.1 添加购物车用户未登录,业务处理方式:

    1. 查询商品详情页面的时候,就提示先登录,跳转到登录页面
    2. 当点击添加购物车,弹窗显示先登录,完成登录,点击添加购物车
    3. 点击添加购物车,会跳转到登录页面,登录完成之后会跳转到商品详情页面。

    12.2我们使用第三种难度最大的来

    12.3使用Layui进行添加购物车成功/失败的提示

    • 引入lay-ui cdn

      
      
       
      
      
      		
      		
      		
      
      		
      		
      
      
      
      

      2. data中定义相关数据

      
      		
      		
      
      
      
      

      3.分页的相关的方法

      // 通过分页进行查询
      			pager(page){
      				this.pageNum=page;
      				//重新加载页面
      				
      				//  加载页面,请求订单信息
      
      					// -------------分页查询按照状态进行查询
      			var obj={
      				userId:this.userId,
      				pageNum:this.pageNum,
      				limit:this.limit
      			};
      
      			if(this.status!=null){
      				obj.status=this.status;
      
      			}
      
      			// ------------
      		var url1=baseUrl+"order/list";
      		axios({
      			url:url1,
      			method:"get",
      			headers:{
      				token:this.token
      			},
      			params:obj
      
      		}).then((res)=>{
      			console.log(res.data);
      			if(res.data.code==1){
      
      				this.orders=res.data.data.data;
      				console.log(this.orders);
      				this.count=res.data.data.count;
      
      			}
      		});
      				
      				
      			},
      			
      
      

      4. 分页的表格

      	
      序号 订单商品 订单状态 时间 操作

      Linux的常用 的命令的复习

      linux中没有盘符,根路径用 "/"表示

      rm -rf 你的目录的名字
      

      linux的系统结构和相关的目录结构

      bin,sbin(超级管理员的命令目录),etc(系统配置文件),lib/lib4(系统所需的依赖库),boot(系统启动的相关的文件),

      目录 说明
      bin 系统文件
      sbin 超级管理员系统命令
      boot 系统启动相关的目录
      etc 系统配置文件
      lib/lib4 存放系统所需的依赖库
      home 一般用户所在的文件夹
      root 超级管理员目录(root用户目录)
      media 媒体(光驱)
      mnt 挂载(u盘,移动硬盘)
      tmp/opt 临时的文件存储目录,比如日志在tmp或者opt目录下面
      usr 用户目录,我们通常安装的软件,用户的一些文件都在此目录下面
      run srv sys var proc dev 系统相关目录

      ls -a #可以显示隐藏文件

      Linux系统安装jdk

      1. 通过SecureFx上传你的linux版本的jdk

      2. 进行解压tar -zxcf 你的压缩包的名字

      3. 在/etc/profile目录进行环境变量的配置。

        加上如下的配置:

        #JAVA_HOME
        export JAVA_HOME=/opt/module/jdk1.8.0_144
        export PATH=$PATH:$JAVA_HOME/bin
        

      Linux安装Tomcat

      1. 通过Secure在windows中上传你的Tomcat包,

      2. 进行解压到指定的目录

      3. 在它的bin目录下面进行启动

        ./startup.sh
        
      4. 不使用了可以关闭tomcat

        lsof -i:8080
        kill -9 PID
        
        如果你的项目是部署在Tomcat上面的,你可以把你的项目打成war包,放在tomcat的weapp目录下面,运行tomcat即可进行该项目

      Linux安装mysql(在线安装)

      通过如下的指定进行安装:

      wget http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm
      

      然后使用下面指定(如果没有权限需要su root):

       rpm -ivh mysql57-community-release-el7-10.noarch.rpm 
      

      正式安装mysql服务:

      yum -y install mysql-community-server
      

      报错:

      Error: Package: mysql-community-server-5.7.36-1.el7.x86_64 (mysql57-community)
      Requires: systemd

      使用命令看你是否安装了MySQL

      rpm -qa|grep -i mysql
      

      已经安装好的mysql加入开机启动

      systemctl enable mysqld
      

      进行启动mysql服务:

      systemctl start mysqld
      

      20.锋迷项目的后端云部署

      20.1企业项目当中需要进行修改的东西:

      1. application.yml文件中需要改变连接的url;改成你的数据库的服务器的ip地址,比如localhost变为你的ip地址
      2. 如果你有微信支付的回调接口data.put("notify_url","/pay/success");,变成你的云主机的地址。

      Maven中先进行clean,然后进行package进行打包的操作,在api中找到你的打包的jar包进行。

      20.11在你的Linux中建立一个目录用来存放你的jar包:

      运行这个命令进行运行你的项目:

       java -jar api-2.0.1.jar
      

      api-2.0.1.jar为你的jar包的名字。

      如果报错: no main manifest attribute, in api-2.0.1.jar

      可以在你的pom.xml文件中加上这个配置(在dependencies下面):

       
              
                  
                      org.springframework.boot
                      spring-boot-maven-plugin
                      2.0.1.RELEASE
                      
                          com.qfedu.fmmall.ApiApplication
                      
                      
                          
                              
                                  repackage
                              
                          
                      
                  
              
          
      

      然后再次运行上面的命令:java -jar 你的jar包名字。

      启动成功如图所示:

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gmOreCEF-1640608196271)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20211226182330211.png)]

      这样当你ctrl+c你的服务就会停止。

      使用这个指令可以使你的服务一直开着:

      java -jar api-2.0.1.jar &
      

      注意一下:你的依赖的引入不可以重复

      21.前端的项目的部署在云服务器上面

      我们的云主机有安装Tomcat,可以部署在Tomcat上面:

      由于上面的Tomcat8080已经启动了,我们可以修改一下它的conf目录下面的server.xml文件:

      cd /opt/module/apache-tomcat-8.5.73/
      cd conf
      #可以查出8080在server.xml的哪行
      cat -n server.xml |grep 8080
      #可以在server.xml中编辑69行
      vim +69 server.xml
      

      1. 修改所有的请求的localhost地址为你的云服务器的地址

      2.上传Tomcat到你的Linux的服务器中,进行解压

      3.把你的前端的项目从windows中上传到Linux的Tomcat的webapp目录下面。

      4.到bin目录下面进行启动命令为:

      ./startup.sh
      

      5.通过路径进行访问你的前端的项目。

      #可以查看你的Linux的服务器的ip
      ifconfig
      #路径在加上你的Tomcat的端口号9999
      http://192.168.48.5:9999/fmall-static/index.html
      

      访问成功的截图:

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xuUhtOgj-1640608196273)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20211227092944455.png)]

      Tomcat作为前端项目的弊端

      1.前端会有大量的请求,Tomcat的弊端(难以满足高并发的,大约是2000-3000,使用Niginx可以提高高并发的承受,大约2万)

      1. Tomcat的核心价值在于能够便于执行java程序,而不是处理并发
      2. 结论:tomcat不适合部署前端项目

      22.Nginx

      它是一个高性能的Http和反向代理web服务器

      • Nginx是基于http协议的请求和响应(部署web项目) ---静态资源
      • 可以作为反向代理服务器 ----负载均衡(代理服务器)

      高性能体现:

      1. 稳定性好,可以7*24不间断的运行
      2. 配置简洁
      3. 可以承受高并发(5w+)

      23.前端项目部署在Nginx上面

      1. 安装nginx

      2. 将前端项目fmall-static拷贝到nginx的根目录

      3. 修改nginx/conf里面的nginx.conf文件:

        location /{
        root fmall-static;
        index index.html index.htm;
        }
        

        24.Linux安装Nginx(在线安装)

        24.1 安装编译工具(nginx安装之前需要编译)

        yum install -y gcc gcc-c++
        

        24.2安装PCRE

        # 1.下载
        wget http://downloads.sourceforge.net/project/pcre/pcre/8.35/pcre-8.35.tar.gz
        
        # 2.解压
        tar -zxvf pcre-8.35.tar.gz
        
        # 3.进入目录 
        cd pre-8.35
        
        # 4.配置
        ./configure
        
        # 5.编译安装
        make && make install
        
        
        
        

        24.3安装SSL库

        cd /opt/software
        wget http://www.openssl.org/source/openssl-1.0.1j.tar.gz
        tar -zxvf openssl-1.0.1j.tar.gz
        
        # 4.配置
        ./configure
        
        # 5.编译安装
        make && make install
        
        

        24.4安装zlib库

        wget http://zlib.net/zlib-1.2.11.tar.gz
        tar -zxvf zlib-1.2.11.tar.gz  -C ../module/
        cd zlib-1.2.11/
        ./configure
        make && make install
        

        24.5下载Nginx

        可以本地上传或者在线下载

        wget https://nginx.org/download/nginx-1.16.1.tar.gz
        cd nginx-1.16.1/
        
         ./configure --prefix=nginx-1.16.1/ --with-http_stub_status_module --with-http_ssl_module --with-pcre=../pcre-8.35/
         make && make install
        

        成功:

        Configuration summary

        • using PCRE library: ../../pcre-8.35/
        • using system OpenSSL library
        • using system zlib library

        nginx path prefix: "../../nginx-1.16.1/"
        nginx binary file: "../../nginx-1.16.1//sbin/nginx"
        nginx modules path: "../../nginx-1.16.1//modules"
        nginx configuration prefix: "../../nginx-1.16.1//conf"
        nginx configuration file: "../../nginx-1.16.1//conf/nginx.conf"
        nginx pid file: "../../nginx-1.16.1//logs/nginx.pid"
        nginx error log file: "../../nginx-1.16.1//logs/error.log"
        nginx http access log file: "../../nginx-1.16.1//logs/access.log"
        nginx http client request body temporary files: "client_body_temp"

      然后make && make install以后没有报错出现下面的信息:

      test -d 'nginx-1.16.1//logs'
      || mkdir -p 'nginx-1.16.1//logs'
      make[1]: Leaving directory `/opt/module/nginx-1.16.1'

      查看成功的nginx:ll

      [root@hadoop102 nginx-1.16.1]# ll
      total 16
      drwxr-xr-x. 2 root root 4096 Dec 25 07:10 conf
      drwxr-xr-x. 2 root root 4096 Dec 25 07:10 html
      drwxr-xr-x. 2 root root 4096 Dec 25 07:10 logs
      drwxr-xr-x. 2 root root 4096 Dec 25 07:10 sbin
      

      启动nginx:

      cd /opt/software/nginx-1.16.1/nginx-1.16.1/sbin
      ./nginx
      
      

      然后报错:

      ./nginx: error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory

      解决办法:

      find / -name *libcrypto*  
      

      找到这个路径:/usr/local/lib64/libcrypto.so.1.1

      cd /usr/local/lib64/
      

      查看防火墙的状态和关闭防火墙:

      #查看防火墙状态
      /etc/init.d/iptables status
      #关闭防火墙
       service iptables stop
       
       #重新启动防火墙
       service iptables restart
      

      报错:

      error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory

      解决:

      ln -s /usr/local/lib64/libssl.so.1.1 /usr/lib64/libssl.so.1.1
      ln -s /usr/local/lib64/libcrypto.so.1.1 /usr/lib64/libcrypto.so.1.1