毕业设计学习锋迷商城,谷粒商城的的笔记(自己设计并手写后台商品管理,分类管理,用户,地址管理系统,订单管理,微信支付(内网穿透))

文章目录

自己添加的后端管理页面

视频演示效果

毕业设计SpringBoot+Vue+ElementUI商城系统实现(有后台)

论文地址

商城论文地址

商城首页

在这里插入图片描述

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

毕业设计锋迷商城手敲后台管理,实现逻辑讲解,代码讲解

b站地址

1. 商品管理

1.1 商品管理的三级分类级联查询实现

文章地址
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.商品分类管理

在这里插入图片描述
商品分类管理

3.商品地址管理

在这里插入图片描述

4.权限管理系统

在这里插入图片描述

权限管理系统文章地址

5.订单管理

在这里插入图片描述

6.商搜索功能(按类别搜索和商品名字模糊搜索)

在这里插入图片描述

7. 微信支付功能已成功

8. 整合OSS进行文件的存储

OSS详细使用

锋迷商城项目

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

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

<package>pom<package>

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

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

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

<packaging>pom<packaing>

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

pom.xml加上

<packaging>jar</packaging>

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

   <dependencies>

<!--        mapper需要用到Beans所以需要加上beans的依赖-->
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>beans</artifactId>
            <version>2.0.1</version>
        </dependency>
    </dependencies>

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

 <dependency>
            <groupId>org.example</groupId>
            <artifactId>mapper</artifactId>
            <version>2.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>common</artifactId>
            <version>2.0.1</version>
        </dependency>

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引入相关的依赖:

<!--        swagger2接口文档生成工具-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
<!--        swagger-ui-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>


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加入依赖
<!--        swagger-ui插件-->
        <!-- https://mvnrepository.com/artifact/com.github.xiaoymin/swagger-bootstrap-ui -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.6</version>
        </dependency>


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" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qfedu.fmmall.dao.UserDao">

 <resultMap id="userResultMap" type="User">
  <id column="user_id" property="userId"></id>

 <result column="username" property="userName"/>
 <result column="password" property="password"/>
 <result column="nickname" property="nickname"/>
 <result column="realname" property="realname"/>
 <result column="user_img" property="userImg"/>
 <result column="user_mobile " property="userMobile"/>
 <result column=" user_email" property="userEmail"/>
 <result column="user_sex " property="userSex"></result>
     <result column=" user_birth" property="userBirth"></result>
     <result column="user_regtime " property="userRegtime"></result>
     <result column="user_modtime " property="userModtime"></result>

 </resultMap>

    
    <select id="queryByName" resultType="User">

     select *from users where username=#{username}

 </select>
    <insert id="insert" parameterType="User">

        insert into users(username,password,user_regtime,user_modtime) values (#{username},
        #{password},#{userRegtime},#{userModtime})
    </insert>
    </mapper>

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插件

<build>
    <plugins>
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.5</version>
            <configuration>
                <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
            </configuration>
            

            <dependencies>
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>5.1.46</version>
                </dependency>
                <dependency>
                    <groupId>tk.mybatis</groupId>
                    <artifactId>mapper</artifactId>
                    <version>4.1.5</version>
                </dependency>


            </dependencies>

  </plugin>

    </plugins>


</build>

7.1逆向工程配置

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

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

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

  3. <!--%表示当前这个数据库里面的所有的表都会被生成-->
            <table tableName="%"></table>
    
  4. <!-- 指定生成 Mapper 的继承模板 -->
           <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
               <property name="mappers" value="com.hou.general.GeneralDao"/>
           </plugin>
      
    
  5. 指定你的用Configuration generatorConfig.xml文件的路径

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

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <!-- 引入 application.properties -->

<!--    <properties resource="application.properties" />-->

    <!-- MyBatis3Simple:不生成 Example相关类及方法 defaultModelType="flat" -->
    <context id="MysqlContext" targetRuntime="MyBatis3Simple" >

        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <!-- 指定生成 Mapper 的继承模板 -->
        <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
            <property name="mappers" value="com.qfedu.fmmall.general.GeneralDao"/>
        </plugin>

        <!--注意context内的文件要按序放-->
        <commentGenerator>
            <property name="suppressDate" value="true"/>
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>


        <!-- jdbc 连接配置 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/fmmall?characterEncoding=utf8"
                        userId="root"
                        password="roothouzhicong">
        </jdbcConnection>

        <javaTypeResolver>
            <!-- 是否使用bigDecimal, false可自动转化以下类型(Long, Integer, Short, etc.) -->
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>

        <!-- 生成实体类的包名和位置 ,targetPackage指的是包名,targetProject值得是路径位置-->
        <!-- 对于生成的pojo所在包,pojo其实就是domain Entity-->
        <javaModelGenerator targetPackage="com.qfedu.fmmall.entity" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>

        <!-- 对于生成的mapper.xml所在目录 -->
        <sqlMapGenerator targetPackage="/" targetProject="src/main/resources/mappers"/>

        <!-- 配置mapper对应的java映射  也可以叫dao层 -->
        <javaClientGenerator targetPackage="com.qfedu.fmmall.dao" targetProject="src/main/java"
                             type="XMLMAPPER"/>

        <!--%表示当前这个数据库里面的所有的表都会被继承-->
        <table tableName="%"></table>

    </context>
</generatorConfiguration>

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

加上了这个:

 <configuration>
                <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
            </configuration>
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.5</version>
            <configuration>
                <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
            </configuration>

            <dependencies>
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>5.1.46</version>
                </dependency>
                <dependency>
                    <groupId>tk.mybatis</groupId>
                    <artifactId>mapper</artifactId>
                    <version>4.1.5</version>
                </dependency>


            </dependencies>


        </plugin>

三、跨域问题

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

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

		<form>
							<div class="user-name"  style="margin-top: 20px;">
								<label for="user"><span class="glyphicon glyphicon-user" aria-hidden="true"></span></label>
								<input type="text" name="username" id="userName" placeholder="邮箱/手机/用户名">
							</div>
							<div class="user-pass"  style="margin-top: 20px;">
								<label for="password"><span class="glyphicon glyphicon-lock" aria-hidden="true"></span></label>
								<input type="password" name="" id="userPwd" placeholder="请输入密码">
							</div>
						</form>




<input type="button" name="" id="submitBtn"  value="登 录" class="am-btn am-btn-primary am-btn-sm">




<script src="static/js/jquery-1.7.2.min.js"></script>
<script type="text/javascript">

$("#submitBtn").click(function(){
	var name=$("#userName").val();
	var pwd=$('#userPwd').val();
  $.get("http://localhost:8080/user/login",{
	  username:name,
	  password:pwd,
  },function(res){
	  console.log(res);



  },"json"
 
  
  
  
  )



})

</script>





前端使用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<arr.length;i++){
	var str=arr[i];

	var k=str.split(opertor)[0];
	var v=str.split(opertor)[1];
	if(k==keyStr){

		value=v;
		break;
	}

	
}
return value;



}


function setCookieValue(key,value){

	document.cookie=key+opertor+value;



}

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("<label style='color:red'>"+ res.msg +"</label>");




	  }

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进行表单的输入的绑定):

	<form>
							<div class="user-name"  style="margin-top: 20px;">
								<label for="user"><span class="glyphicon glyphicon-user" aria-hidden="true"></span></label>
								<!-- @keyup进行绑定 -->
								<input type="text" name="username" v-model="username" id="userName" @keyup="checkInfo" placeholder="邮箱/手机/用户名">
							</div>
							<div class="user-pass"  style="margin-top: 20px;">
								<label for="password"><span class="glyphicon glyphicon-lock" aria-hidden="true"></span></label>
								<input type="password" name="" v-model="password" id="userPwd" placeholder="请输入密码"@keyup="checkInfo">
							</div>
						</form>

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

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> 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> 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:

	<script type="text/javascript">
		// 进入购物车时要访问购物车列表的接口shopController接口
		var baseUrl="http://localhost:8080/";    
		var vm=new Vue({
			el:"#app",
			data:{
				token:"",
			},
			created() {
				this.token=getCookieValue("token");
				console.log("token="+this.token);
				axios({
					method:"get",
					url:baseUrl+"shopcart/list",
					params:{
						token:this.token,
					}

				}).then(function (res) {
					console.log(res);
				});
			},
			




		})
		
		
		
		
		
		
		
		
		
		</script>

登录进行来可以把购物车获取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

  • 添加依赖:

    <!--        jwt生成 -->
            <dependency>
                <groupId>com.auth0</groupId>
                <artifactId>java-jwt</artifactId>
                <version>3.10.3</version>
            </dependency>
    <!--        jjwt生成-->
            <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
    
    

UserServiceimpl(登录成功)生成token:

 HashMap<String,Object> 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:

	<script type="text/javascript">
		// 进入购物车时要访问购物车列表的接口shopController接口
		var baseUrl="http://localhost:8080/";    
		var vm=new Vue({
			el:"#app",
			data:{
				token:"",
			},
			created() {
				this.token=getCookieValue("token");
				console.log("token="+this.token);
				axios({
					method:"get",
					url:baseUrl+"shopcart/list",
					Headers:{
						token:this.token,
					}

				}).then(function (res) {
					console.log(res);
				});
			},
			




		})
		
		
		
		
		
		
		
		
		
		</script>

后端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<Claims> 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<Claims> 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<CategoryVO> categories;
    
            public List<CategoryVO> getCategories() {
                return categories;
            }
    
            public void setCategories(List<CategoryVO> 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<Category> {
    //使用连接查询实现分类列表查询
        public List<CategoryVO> selectAllCategories();
        
        
        //    子查询
        public List<CategoryVO> selectAllCategories2(int parentId);
    }
    
  • 映射文件配置

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.qfedu.fmmall.dao.CategoryMapper">
      <resultMap id="BaseResultMap" type="com.qfedu.fmmall.entity.Category">
        <!--
          WARNING - @mbg.generated
        -->
        <id column="category_id" jdbcType="VARCHAR" property="categoryId" />
        <result column="category_name" jdbcType="VARCHAR" property="categoryName" />
        <result column="category_level" jdbcType="INTEGER" property="categoryLevel" />
        <result column="parent_id" jdbcType="INTEGER" property="parentId" />
        <result column="category_icon" jdbcType="VARCHAR" property="categoryIcon" />
        <result column="category_slogan" jdbcType="VARCHAR" property="categorySlogan" />
        <result column="category_bg_color" jdbcType="VARCHAR" property="categoryBgColor" />
      </resultMap>
    
    
    
      <resultMap id="CategoryVoMap" type="com.qfedu.fmmall.entity.CategoryVO">
        <!--
          WARNING - @mbg.generated
        -->
        <id column="category_id1" jdbcType="VARCHAR" property="categoryId" />
        <result column="category_name1" jdbcType="VARCHAR" property="categoryName" />
        <result column="category_level1" jdbcType="INTEGER" property="categoryLevel" />
        <result column="parent_id1" jdbcType="INTEGER" property="parentId" />
        <result column="category_icon1" jdbcType="VARCHAR" property="categoryIcon" />
        <result column="category_slogan1" jdbcType="VARCHAR" property="categorySlogan" />
        <result column="category_bg_color1" jdbcType="VARCHAR" property="categoryBgColor" />
        <collection property="categories" ofType="com.qfedu.fmmall.entity.CategoryVO">
          <id column="category_id2" jdbcType="VARCHAR" property="categoryId" />
          <result column="category_name2" jdbcType="VARCHAR" property="categoryName" />
          <result column="category_level2" jdbcType="INTEGER" property="categoryLevel" />
          <result column="parent_id2" jdbcType="INTEGER" property="parentId" />
    
         <collection property="categories" ofType="com.qfedu.fmmall.entity.CategoryVO">
    
           <id column="category_id3" jdbcType="VARCHAR" property="categoryId" />
           <result column="category_name3" jdbcType="VARCHAR" property="categoryName" />
           <result column="category_level3" jdbcType="INTEGER" property="categoryLevel" />
           <result column="parent_id3" jdbcType="INTEGER" property="parentId" />
    
    
         </collection>
        </collection>
    
      </resultMap>
    
    
    
    
    
    
    
    
    
      <select id="selectAllCategories" resultMap="CategoryVoMap">
    
    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>
        
        
       
    
    

使用子查询实现分类列表的查询:

```xml
    
<!--  使用子查询实现的分类列表查询-->
  <resultMap id="CategoryMap2" type="com.qfedu.fmmall.entity.CategoryVO">
    <!--
      WARNING - @mbg.generated
    -->
    <id column="category_id" jdbcType="VARCHAR" property="categoryId" />
    <result column="category_name" jdbcType="VARCHAR" property="categoryName" />
    <result column="category_level" jdbcType="INTEGER" property="categoryLevel" />
    <result column="parent_id" jdbcType="INTEGER" property="parentId" />
    <result column="category_icon" jdbcType="VARCHAR" property="categoryIcon" />
    <result column="category_slogan" jdbcType="VARCHAR" property="categorySlogan" />
    <result column="category_bg_color" jdbcType="VARCHAR" property="categoryBgColor" />
    <collection property="categories" column="category_id" select="com.qfedu.fmmall.dao.CategoryMapper.selectAllCategories2"/>

<!--  这里的column="category_id"将等于
//    子查询
    public List<CategoryVO> selectAllCategories2(int parentId);里面的parentId;
-->

  </resultMap>






  <!--  使用子查询实现的分类列表查询-->
  <select id="selectAllCategories2" resultMap="CategoryMap2">
    select

     category_id,
category_name,
category_level,
parent_id,
category_icon,
category_slogan,
category_pic,
category_bg_color


from category where parent_id=#{parentId};
  </select>


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<CategoryVO> 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<ProductImg> imgs;
    
        public List<ProductImg> getImgs() {
            return imgs;
        }
    
        public void setImgs(List<ProductImg> 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<Product> {
    //    查询推荐商品信息
        public List<ProductVO> 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<ProductImg> {
        public List<ProductImg> selectProductImgByProductId(int productId);
    }
    

    ProductMapper.xml文件实现

    注意一下子查询 的语句:

     <collection property="imgs" column="product_id"
                    select="com.qfedu.fmmall.dao.ProductImgMapper.selectProductImgByProductId"></collection>
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.qfedu.fmmall.dao.ProductMapper">
      <resultMap id="BaseResultMap" type="com.qfedu.fmmall.entity.Product">
        <!--
          WARNING - @mbg.generated
        -->
        <id column="product_id" jdbcType="INTEGER" property="productId" />
        <result column="product_name" jdbcType="VARCHAR" property="productName" />
        <result column="category_id" jdbcType="INTEGER" property="categoryId" />
        <result column="root_category_id" jdbcType="INTEGER" property="rootCategoryId" />
        <result column="sold_num" jdbcType="INTEGER" property="soldNum" />
        <result column="product_status" jdbcType="INTEGER" property="productStatus" />
        <result column="content" jdbcType="VARCHAR" property="content" />
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
        <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
      </resultMap>
    
      <resultMap id="ProductVoMap" type="com.qfedu.fmmall.entity.ProductVO">
        <!--
          WARNING - @mbg.generated
        -->
        <id column="product_id" jdbcType="INTEGER" property="productId" />
        <result column="product_name" jdbcType="VARCHAR" property="productName" />
        <result column="category_id" jdbcType="INTEGER" property="categoryId" />
        <result column="root_category_id" jdbcType="INTEGER" property="rootCategoryId" />
        <result column="sold_num" jdbcType="INTEGER" property="soldNum" />
        <result column="product_status" jdbcType="INTEGER" property="productStatus" />
        <result column="content" jdbcType="VARCHAR" property="content" />
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
        <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
        <collection property="imgs" column="product_id"
                    select="com.qfedu.fmmall.dao.ProductImgMapper.selectProductImgByProductId"></collection>
      </resultMap>
    
    
      <select id="selectRecommendProducts" resultMap="ProductVoMap">
    
    
    select
    product_id,
    product_name,
    category_id,
    root_category_id,
    sold_num,
    product_status,
    content,
    create_time,
    update_time
    
    
    from product order by create_time desc limit 0,3;
    
    
      </select>
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    </mapper>
    

    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<Product> {
    //    查询推荐商品信息
        public List<ProductVO> 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<ProductImg> {
        public List<ProductImg> 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<ProductSku> {
    }
    
    
  • 业务层实现

//这里为不需要事务,但是如果某些事务如果调用了我也加入到事务中来
//    事务默认的隔离级别是可重复读 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<Product> 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<ProductImg> 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<ProductSku> productSkus = productSkuMapper.selectByExample(example2);
//            System.out.println(productSkus);


//            把所有的商品的详情信息放入HashMap当中进行使用
            HashMap<String,Object> 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<ProductComments> {

    public List<ProductCommentsVO> selectCommentsByProductId(int productId);
}

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

10.1流程分析:

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

10.2数据库的相关的操作

  1. 增加字段sku_props

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

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

    <table tableName="shopping_cart"></table>
    

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<this.productSkus[this.currentSkuIndex].stock){
							this.num=parseInt(this.num)   +1;


						}


					},

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

<dd>
															<input id="min" class="am-btn am-btn-default"  type="button" value="-" @click="changeNum(-1)"/>
															<input id="text_box"  type="text" v-model="num" style="width:30px;" />
															<input id="add" class="am-btn am-btn-default"  type="button" value="+" @click="changeNum(1)" />
															<span id="stock1" class="tb-hidden">库存<span class="stock">{{productSkus[currentSkuIndex].stock}}</span></span>
														</dd>

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

<li>
								<div class="clearfix tb-btn tb-btn-basket theme-login">
									<a id="LikBasket" title="加入购物车" href="" @click="addShopCart()"><i></i>加入购物车</a>
								</div>
							</li>

把相关的添加的购物车的信息放入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

    <!-- 引入 layui.css -->
    <link rel="stylesheet" href="//unpkg.com/layui@2.6.8/dist/css/layui.css">
     
    <!-- 引入 layui.js -->
    <script src="//unpkg.com/layui@2.6.8/dist/layui.js">
          
    
    12.3.1声明layui的弹窗组件

    12.3.2成功失败进行提示

十三购物车的列表

流程图:

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

步骤

  1. 获取用户user_id
  2. 通过user_id获取购物车的信息(包括商品的名字,商品图片的信息)
  3. 将购物车信息数据返回给前端 。
  4. 也就是有三张表,购物车表(shopping_cart),商品表(product),商品图片表(product_img 根据商品id查询商品主图)

13.1数据库dao接口的实现

  1. sql语句

    select  c.*,p.product_name,i.url from shopping_cart c
    inner join product p
    inner join product_img i
    on c.product_id=p.product_id 
    and i.item_id=p.product_id
    where user_id=2 and i.is_main=1;
    
   
2. dao接口

  ```java
    List<ShoppingCartVO>  selectShoppingCartByUserId(int userid);

13.2pojo接口实现

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

注意一下数据库的shopping_cart表不需要加上这两个字段,是通过product_img表和product表的column属性来进行关联的

13.3修改购物车

13.31通过这个进行购物车数量的修改:
13.32changNum函数进行实现:
	
           methods:{
			   changeNum(event){
				   var oper=event.srcElement.dataset.oper;

				   var index=event.srcElement.dataset.id;
				   console.log(oper);
				   if(oper=="+"){
					  
						this.shoppingCartsSC[index].cartNum=parseInt(this.shoppingCartsSC[index].cartNum) +1;
					   

				   }else{

					if(this.shoppingCartsSC[index].cartNum>1){
					this.shoppingCartsSC[index].cartNum=parseInt(this.shoppingCartsSC[index].cartNum)-1;
  					}
					

				   }
				   //   修改的cartId和cartNum
					var cartId=this.shoppingCartsSC[index].cartId;
					var cartNum=this.shoppingCartsSC[index].cartNum;
					var url1=baseUrl+"shopcart/update/"+cartId+"/"+cartNum;
					axios({
						url:url1,
						method:"put",
						params:{
							token:this.token
						}

					}).then((res)=>{

						console.log(res);

})



			   },

// 			addNum:function(event){
// 				// 这个可以打印绑定的id的值
// 				console.log("--add"+event.srcElement.dataset.id);
// 			    var index=event.srcElement.dataset.id;
// 				this.shoppingCartsSC[index].cartNum=parseInt(this.shoppingCartsSC[index].cartNum) +1;

				
				
// 			},
			
// 			mulNum:function(event){
			
// 			    var index=event.srcElement.dataset.id;
// 				if(this.shoppingCartsSC[index].cartNum>1){
// 					this.shoppingCartsSC[index].cartNum=parseInt(this.shoppingCartsSC[index].cartNum)-1;
//   }
				
				
				
// 			}



		   }

十四购物车提交订单结算功能实现

14.1实现流程分析

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

十五、订单提交及支付流程

15.1流程分析

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

15.2订单添加接口实现

15.3数据库操作

  • 根据收货id获取收货的地址信息(tkmapper)
  • 根据购物车ID,查询购物车详情(需要关联查询商品名称,sku名称,库存,商品图片,商品价格)----》 获取生成商品快照的数据 只需在ShoppingCartVO中多加上一个stock字段就好,然后在ShoppCartMapper.xml加上需要查询的这个字段
  • 保存订单信息(tkMapper)
  • 修改库存(tkMapper)
  • 保存商品快照(tkMapper)

15.4serviceimpl层实现 注意:这个方法需要加上@Transactional,也就是订单生成的时候,快照也必须生成

  1. 生成OrderId的方法 UUID.random().toString()
  2. 通过时间戳生成System.currentTimeMillis()+(new Random().nextInt(9999)+100)+“”
package com.qfedu.fmmall.service.impl;

import com.qfedu.fmmall.dao.OrdersItemMapper;
import com.qfedu.fmmall.dao.OrdersMapper;
import com.qfedu.fmmall.dao.ProductSkuMapper;
import com.qfedu.fmmall.dao.ShoppingCartMapper;
import com.qfedu.fmmall.entity.Orders;
import com.qfedu.fmmall.entity.OrdersItem;
import com.qfedu.fmmall.entity.ProductSku;
import com.qfedu.fmmall.entity.ShoppingCartVO;
import com.qfedu.fmmall.service.OrderService;
import com.qfedu.fmmall.vo.ResultStatus;
import com.qfedu.fmmall.vo.ResultVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.SQLException;
import java.util.*;

import static java.math.BigDecimal.*;

@Service

public class OrderServiceimpl implements OrderService{

    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    @Autowired
    private OrdersMapper ordersMapper;
    @Autowired
    private OrdersItemMapper ordersItemMapper;
    @Autowired
    private ProductSkuMapper productSkuMapper;
    /* userId 1(zhangsan) 3(houzhicong)
    *cids  "39,40,41"
    * @return
    *
    * */

//    int userId, String receiverName,
//    String receiverMobile,String address,
//    double price,int payType,String orderRemark   把这些用Orders对象来接收


//    保存订单的步骤
//    1.查询选中购买的购物车详情
//    2. 校验库存
//    3.保存订单
//    4.保存订单快照
//    5.购买成功需要删除购物车记录
//    可以知道这四个步骤需要同时成功或者同时失败,符合一个事务的操作(ACID)
    @Transactional
    public Map<String,String> addOrder(List<Integer> cids, Orders orders) throws  SQLException{
         Map<String,String> map=new HashMap<>();
//        根据cids查询购物车的详情记录(包括库存)
        List<ShoppingCartVO> shoppingCartVOList = shoppingCartMapper.selectShoppingcartByids(cids);


//        校验库存
        boolean f=true;

        String untitled="";
        for (ShoppingCartVO sc :shoppingCartVOList
                ) {
            if(Integer.valueOf(sc.getCartNum())>sc.getStock()){
                f=false;

            }

//            获取所有的商品名称,以,分割拼接成字符串
            untitled=untitled+sc.getProductName()+",";

        }
        if(f){
//            表示库存充足进行保存
//            1.userId  2 untitled名称 3 收件人地址,姓名,电话,地址
//            4. 总价格 5.支付方式
//            6.创建 订单的时间
//            7.订单初始状态 1 待支付
        orders.setStatus(1);
        orders.setUntitled(untitled);
        orders.setCreateTime(new Date());
        orders.setCancelTime(new Date());
        orders.setDeliveryTime(new Date());

//        生成订单编号
            String orderId = UUID.randomUUID().toString().replace("-", "");
            orders.setOrderId(orderId);


//            保存订单
            int i=ordersMapper.insert(orders);
            if(i>0){


//               ordersItem 生成商品快照
//                List<OrdersItem> ordersItemList=new ArrayList<>();
                for (ShoppingCartVO sc :shoppingCartVOList) {
//                    生成订单的编号

                    int cnum=Integer.valueOf(sc.getCartNum());
                  String itemid=System.currentTimeMillis()+(new Random().nextInt(9999)+100)+"";
                    String itemid1 = itemid.substring(1, 10);


//                 注意一下double需要转换为Bigdecimal类型


//                public OrdersItem(Integer orderId, Integer productId,
//                String productName,
//                String productImg, Integer skuId, String skuName,
//                BigDecimal productPrice, Integer buyCounts,
//                BigDecimal totalAmount, Date basketDate, Date buyTime,
//                Integer isComment)
                int itemid2=Integer.parseInt(itemid1);
                OrdersItem ordersItem=  new OrdersItem();
                ordersItem.setOrderId(itemid2);
                ordersItem.setProductId(Integer.valueOf(sc.getProductId()));
                ordersItem.setProductName(sc.getProductName());
                ordersItem.setProductImg(sc.getProductImg());
                ordersItem.setSkuId(Integer.valueOf(sc.getSkuId()));
                    System.out.println(sc.getSkuName());
                    ordersItem.setSkuName(sc.getSkuName());
                System.out.println(sc.getSellPrice());
                ordersItem.setProductPrice(new BigDecimal(String.valueOf(sc.getProductPrice())));

                ordersItem.setBuyCounts(cnum);
                ordersItem.setTotalAmount(sc.getProductPrice());
                ordersItem.setBasketDate(new Date());
                ordersItem.setBuyTime(new Date());
                ordersItem.setIsComment(0);

//                ordersItemList.add(ordersItem);
                      int m=ordersItemMapper.insert(ordersItem);

                    }

//                int j = ordersItemMapper.insertList(ordersItemList);
//  扣减库存???
//                根据套餐Id修改库存量
                for (ShoppingCartVO sc :shoppingCartVOList
                ) {
                    String skuId = sc.getSkuId();
                    int newStock=sc.getStock()-Integer.valueOf(sc.getCartNum());

//                    Example example = new Example(ProductSku.class);
//                    Example.Criteria criteria = example.createCriteria();
//                    criteria.andEqualTo("skuId",skuId);


//                    ProductSku productSku = productSkuMapper.selectByPrimaryKey(skuId);
                    ProductSku productSku=new ProductSku();
                    productSku.setSkuId(skuId);
                    productSku.setStock(String.valueOf(newStock));
//                    productSku.setSkuImg(null);

                    productSkuMapper.updateByPrimaryKeySelective(productSku);

                }

//  保存订单成功 删除购物车记录
                for (Integer cid:cids
                     ) {
                    shoppingCartMapper.deleteByPrimaryKey(cid);

                }

                 map.put("orderId",orderId);
                 map.put("productNames",untitled);

              return   map;


            }



        }else{
//            不足
   return null;

        }

        return null;
    }
}

15.5 微信支付

微信支付

swagger报错解决

For input String :“”

在application.yml加上日志的配置:

logging:
  level:
    io.swagger.models.parameters.AbstractSerializableParameter: error

十六 商品个人中心订单信息的查询

16.1 流程分析

  1. 根据用户id进行订单信息的查询
  2. 可以关联进行查询订单快照

sql语句

    <select id="selectOrders" resultMap="OrdertMap1">
        select o.order_id,
        o.user_id,
        o.untitled,
        o.receiver_name,
        o.receiver_mobile,
        o.receiver_address,
        o.total_amount,
        o.actual_amount,
        o.pay_type,
        o.order_remark,
        o.status,
        o.delivery_type,
        o.delivery_flow_id,
        o.order_freight,
        o.delete_status,
        o.create_time,
        o.update_time,
        o.pay_time,
        o.delivery_time,
        o.finish_time,
        o.cancel_time,
        o.close_type




        from orders o

        where o.user_id=#{userId}
        <if test="status!=null">

            and o.status=#{status}
        </if>
        limit #{start},#{limit}
    </select>


OrderMapper.xml文件:


    <resultMap id="OrdertMap1" type="com.qfedu.fmmall.entity.OrdersVO">
        <!--
          WARNING - @mbg.generated
        -->
        <result column="order_id" jdbcType="VARCHAR" property="orderId" />
        <result column="user_id" jdbcType="VARCHAR" property="userId" />
        <result column="untitled" jdbcType="VARCHAR" property="untitled" />
        <result column="receiver_name" jdbcType="VARCHAR" property="receiverName" />
        <result column="receiver_mobile" jdbcType="VARCHAR" property="receiverMobile" />
        <result column="receiver_address" jdbcType="VARCHAR" property="receiverAddress" />
        <result column="total_amount" jdbcType="DECIMAL" property="totalAmount" />
        <result column="actual_amount" jdbcType="DECIMAL" property="actualAmount" />
        <result column="pay_type" jdbcType="VARCHAR" property="payType" />
        <result column="order_remark" jdbcType="VARCHAR" property="orderRemark" />
        <result column="status" jdbcType="INTEGER" property="status" />
        <result column="delivery_type" jdbcType="VARCHAR" property="deliveryType" />
        <result column="delivery_flow_id" jdbcType="VARCHAR" property="deliveryFlowId" />
        <result column="order_freight" jdbcType="DECIMAL" property="orderFreight" />
        <result column="delete_status" jdbcType="INTEGER" property="deleteStatus" />
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
        <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
        <result column="pay_time" jdbcType="TIMESTAMP" property="payTime" />
        <result column="delivery_time" jdbcType="TIMESTAMP" property="deliveryTime" />
        <result column="finish_time" jdbcType="TIMESTAMP" property="finishTime" />
        <result column="cancel_time" jdbcType="TIMESTAMP" property="cancelTime" />
        <result column="close_type" jdbcType="INTEGER" property="closeType" />


<!--        子查询实现 根据OrdersVO的orderId进行子查询-->
        <collection column="ordersItems" property="order_id" select="com.qfedu.fmmall.dao.OrdersItemMapper.selectOrderItemsByOrderId"/>
    </resultMap>

    <select id="selectOrders" resultMap="OrdertMap1">
        select o.order_id,
        o.user_id,
        o.untitled,
        o.receiver_name,
        o.receiver_mobile,
        o.receiver_address,
        o.total_amount,
        o.actual_amount,
        o.pay_type,
        o.order_remark,
        o.status,
        o.delivery_type,
        o.delivery_flow_id,
        o.order_freight,
        o.delete_status,
        o.create_time,
        o.update_time,
        o.pay_time,
        o.delivery_time,
        o.finish_time,
        o.cancel_time,
        o.close_type




        from orders o

        where o.user_id=#{userId}
        <if test="status!=null">

            and o.status=#{status}
        </if>
        limit #{start},#{limit}
    </select>

16.2 接口开发

package com.qfedu.fmmall.dao;

import com.qfedu.fmmall.entity.Orders;
import com.qfedu.fmmall.entity.OrdersVO;
import com.qfedu.fmmall.general.GeneralDao;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface OrdersMapper extends GeneralDao<Orders> {
    List<OrdersVO> selectOrders(@Param("userId") String userId,
                               @Param("status") String status,
                                @Param("start")int start,
                                @Param("limit")int limit);
}

16.2.1 Service层接口开发

  @Override
    public ResultVO selectOrders(String userId, String status, int pageNum, int limit) {
   int start=(pageNum-1)*limit;
        List<OrdersVO> ordersVOS = ordersMapper.selectOrders(userId, status, pageNum, limit);
//   2.查询总记录数
        Example example = new Example(Orders.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andLike("userId",userId);
        if(status!=null&&!"".equals(status)){
            criteria.andLike("status",status);
        }
//        2.查询总记录数
        int count=ordersMapper.selectCountByExample(example);
//        查询总页数
        int pageCount=count%limit==0?count/limit:count/limit+1;

//        3.封装数据
        PageHelper<OrdersVO> ordersVOPageHelper = new PageHelper<>(count, pageCount, ordersVOS);


        return new ResultVO(ResultStatus.OK,"订单查询成功",ordersVOPageHelper);
    }

16.2.2 Controller层实现

package com.qfedu.fmmall.controller;

import com.github.wxpay.sdk.WXPay;
import com.qfedu.fmmall.config.MyPayConfig;
import com.qfedu.fmmall.entity.Orders;
import com.qfedu.fmmall.service.OrderService;
import com.qfedu.fmmall.vo.ResultStatus;
import com.qfedu.fmmall.vo.ResultVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@CrossOrigin
@RequestMapping("/order")
@Api(value = "提供订单相关的接口",tags = "订单管理")
public class OrderController {
    @Autowired
    private OrderService orderService;

    @PostMapping("/add/{cids}")
    public ResultVO add(@PathVariable("cids") List<Integer> cids,
                        @RequestBody Orders orders){

        ResultVO resultVO=null;

//        测试用的OrderId
        try {
            Map<String, String> orderInfo = orderService.addOrder(cids, orders);
            String orderId=orderInfo.get("orderId");


//           订单快照创建成功,申请支付链接

            HashMap<String,String> data=new HashMap<>();
//            设置当前订单信息
            data.put("body",orderInfo.get("productNames")); //商品描述
            data.put("out_trade_no",orderId);//使用当前用户订单编号作为当前支付交易的交易编号
            data.put("fee_type","CNY"); //支付币种
            data.put("total_fee", orders.getActualAmount()+""); //支付金额
            data.put("trade_type","NATIVE");//交易类型

//            必填选项   用于设置支付完成时的回调方法接口
            data.put("notify_url","/pay/success");
            WXPay wxPay=new WXPay(new MyPayConfig());
            Map<String, String> resp = wxPay.unifiedOrder(data);

//            把微信支付平台生成的链接获取到
            orderInfo.put("payUrl",resp.get("code_url"));
            resultVO=new ResultVO(ResultStatus.OK,"提交订单成功!",orderInfo);
            System.out.println(resp);

//            code_url -> weixin://wxpay/bizpayurl?pr=Iv5Fsq6zz
        } catch (SQLException e) {

            resultVO= new ResultVO(ResultStatus.NO,"下单失败",null);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return resultVO;
    }



//    订单分页查询
@RequestMapping(value = "/list",method = RequestMethod.GET)

@ApiImplicitParams({
        @ApiImplicitParam(dataType = "string",name = "userId",value = "用户Id",required = true),
        @ApiImplicitParam(dataType = "string",name = "status",value = "订单状态",required = false),
        @ApiImplicitParam(dataType = "int",name = "pageNum",value = "当前页数",required = true),
        @ApiImplicitParam(dataType = "int",name = "limit",value = "页数大小",required = false),
})
public ResultVO selectOrders(@RequestHeader("token")String token, String userId, String status, int pageNum, int limit) {
    ResultVO resultVO = orderService.selectOrders(userId, status, pageNum, limit);

    return  resultVO;
 }

}

16.2.3前端使用Vue+ElementUI实现分页功能

1. 引入cdn

   <!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">

	<script src="static/js/cookie_utils.js"></script>
		<!-- element-ui需要引入vue.js -->
		<script src="static/js/vue.min.js"></script>
		<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
		<script src="static/js/axios.min.js"></script>
		<script src="static/js/utils.js"></script>



2. data中定义相关数据


		<script type="text/javascript">
			var baseUrl="http://localhost:8080/";
      var vm=new Vue({
		el:"#app",
		data:{
			token:"",
			username:"",
			pageNum:1,
			limit:6,
			userId:"",
			orders:[],
			count:0,
			status:null,
			

		},
		created(){
		 this.token=getCookieValue("token");
		 this.userId=getCookieValue("userId");

		 this.username=getCookieValue("username");

		//  加载页面,请求订单信息
		var url1=baseUrl+"order/list";
		axios({
			url:url1,
			method:"get",
			headers:{
				token:this.token
			},
			params:{
				userId:this.userId,
				pageNum:this.pageNum,
				limit:this.limit
			}

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

			}
		});

	
		},
		methods: {
			// 通过订单状态进行查询
			queryByStatus(status){

			this.status=status;
			console.log(this.status);

// 重新按照状态查询一次



								//  加载页面,请求订单信息
		var url1=baseUrl+"order/list";
		axios({
			url:url1,
			method:"get",
			headers:{
				token:this.token
			},
			params:{
				userId:this.userId,
				pageNum:this.pageNum,
				limit:this.limit,
				status:this.status
			}

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

			}
		});
			


		},
		
		gotoComment:function(event){
			var index=event.srcElement.dataset.index;
			console.log(index);
			var order=this.orders[index];
			localStorage.setItem("order",JSON.stringify(order));
			location.href="user-comment.html";
			
}
	},
		
		
 })

</script>
		



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. 分页的表格

	<table class="table">
						<tr>
							<td>序号</td>
							<td>订单商品</td>
							<td>订单状态</td>
							<td>时间</td>
							<td>操作</td>
						</tr>
						<template v-for="order,index in this.orders">
						
							<tr>
								<td>{{index+1}}</td>
								<td>{{order.untitled}}</td>
								<td>
									<span v-if="order.status=='1'">待付款</span>
									<span v-else-if="order.status=='2'">待发货</span>
									<span v-else-if="order.status=='3'">待收货</span>
									<span v-else-if="order.status=='4'">待评价</span>
									<span v-else-if="order.status=='5'">已完成</span>
									<span v-else-if="order.status=='6'">已关闭</span>

									</td>
									
								<td>{{order.createTime}}</td>
								<td>
									<template v-if="order.status=='1'">
									<button class="btn btn-success btn-xs">
											去支付

									</button >
									<button  class="btn btn-danger btn-xs">
											取消订单
										</button>
									</template>
								
								
							   <template  v-if="order.status=='2'">
									<button  class="btn btn-danger btn-xs">
										取消订单
									</button>
										
									</template>

							    <template v-if="order.status=='3'">
									<button class="btn btn-success btn-xs">
									确认收货

								    </button >	
								
							    </template>
									
									
									<template v-if="order.status=='4'">
										<button class="btn btn-success btn-xs" @click="gotoComment" :data-index="index">
											去评价
										</button >
									</template>
							
					
								<template v-if="order.status=='6'" || v-if="order.status=='5'" >
										<button class="btn btn-danger btn-xs">
											删除
										</button >
								</template> 
								

									


								</td>
							</tr>


						</template>

						<tr>
							<td colspan="5">
								<!-- 分页 -->
								<el-pagination
  background
  layout="prev, pager, next"
  :current-page="pageNum"
  :page-size="limit"
  :total="count" @current-change="pager">
</el-pagination>
								
							</td>
						</tr>






					</table>


十七 Reis实现缓存和分布式锁

Redis详细文档

十八 按类别进行查询商品列表

在这里插入图片描述

-- 前端会传递过来一个三级分类id
select * from product where category_id = #{catgegoryId} #根据前端传递的id进行查询

-- 根据上面查询出来的product,获取商品id,再进行查询商品套餐信息中价格最低的一个商品套餐信息
select * from product_sku where product_Id = #{productId} order by sell_price limit 0,1

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

19. 统一异常处理和时间统一为一样的格式

Spring里面自带的一个配置,所有时间之后都会显示成这种格式

jackson:
    date-format: yyyy-MM-dd HH:mm:ss

controller层的异常可以通过统一的异常类进行处理

package com.qfedu.fmmall.exception;/*
 **
 *@author SmallMonkey
 *@Date 2023/2/16 11:56
 *
 *
 **/

import com.qfedu.fmmall.vo.ResultVO;
import com.sun.org.apache.regexp.internal.RE;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice(basePackages = "com.qfedu.fmmall.controller")
public class FmmallRebuildControllerAdvice {
    @ExceptionHandler(value = Exception.class)
    public ResultVO handValidException(Exception e){
        log.error("数据异常{},异常类型:{}",e.getMessage(),e.getClass());
        ResultVO resultVO = new ResultVO();
        resultVO.setMsg(e.getMessage());
        return resultVO;

    }

}


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下面):

 <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.0.1.RELEASE</version>
                <configuration>
                    <mainClass>com.qfedu.fmmall.ApiApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

然后再次运行上面的命令: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

版权声明:本文为houzhicongone原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
THE END
< <上一篇
下一篇>>