当前位置: 首页 > news >正文

SpringBoot电商项目前后端界面搭建

目录

一、SpringBoot项目简介

1、技术点介绍

2、数据表介绍

二、构建SpringBoot项目

1.创建SpringBoot项目并配置POM

2.配置application.yml

3.启动类配置

4.首页访问 ,导入前端页面及页面对应的js/css/images文件

三、实现首页功能

0)导入帮助类

config:

exception:

Generator:

Utils:

在generater类运行自动生成代码:

1)创建公共跳转控制器PageController

2)使用Mybatis-plus反向生成代码(Goods商品信息)

3)创建IndexController并定义商品查询请求处理方法

四、用户明文登录

创建UserController类实现用户登录

        1.1)构建UserDto,定义mobile和password属性

        1.2)创建UserController类

        1.3)定义userLogin(UserDto userDto,HttpServletRequest req,HttpServletResponse resp)

        1.4)定义响应封装类JsonResponseBody和JsonResponseStatus

        1.5)在IUserService中定义userLogin(UserVo userVo),并返回JsonResponseBody

        1.6)全局异常处理

        1.7)自定义注解参数校验(JSR303)

login.js:

测试多种情况: 

五、前端及数据库密码加密

UserServiceImpl.java变更如下

login.js变更如下:

六、服务端客户端登录密码管理

UserServiceImpl.java变更如下


一、SpringBoot项目简介

1、技术点介绍

前端:Freemarker、jQuery
后端:SpringBoot、MyBatisPlus、Lombok
中间件:Redis

2、数据表介绍

用户表:t_user

商品表:t_goods

订单表:t_order

订单项表:t_order_item

数据源表:t_dict_type

数据项表:t_dict_data

后续微服务秒杀项目所用:
秒杀商品表:t_seckill_goods
秒杀订单表:t_seckill_order

二、构建SpringBoot项目

起初搭建时,可以不勾选任何组件

1.创建SpringBoot项目并配置POM

pom依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ycx</groupId>
    <artifactId>spbootpro</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spbootpro</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--freemarker-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

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

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.44</version>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

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

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- mybatis plus依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>
        <!-- mybatis-plus-generator依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.0</version>
        </dependency>

        <!--hariki-->
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
        </dependency>

        <!-- MD5依赖 -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.6</version>
        </dependency>

        <!-- valid验证依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--commons-pool2 对象池依赖 2.0版本的lettuce需要-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <!--spring-session将session借助于第三方存储(redis/mongodb等等),默认redis-->
        <!--<dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>-->
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-easysdk</artifactId>
            <version>2.0.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.配置application.yml

1)添加数据库及连接池配置
2)添加freemarker配置
3)添加mybatis-plus配置
4)添加logging日志配置

yml文件:

server:
    port: 8081
    servlet:
        context-path: /
spring:
    datasource:
        url: jdbc:mysql://localhost:3306/spbootpro?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&characterEncoding=UTF8
        driver-class-name: com.mysql.jdbc.Driver
        password: 1234
        username: root
        hikari:
            # 最小空闲连接数量
            minimum-idle: 5
            # 空闲连接存活最大时间,默认600000(10分钟)
            idle-timeout: 180000
            # 连接池最大连接数,默认是10
            maximum-pool-size: 10
            # 此属性控制从池返回的连接的默认自动提交行为,默认值:true
            auto-commit: true
            # 连接池名称
            pool-name: MyHikariCP
            # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
            max-lifetime: 1800000
            # 数据库连接超时时间,默认30秒,即30000
            connection-timeout: 30000
    freemarker:
        #设置编码格式
        charset: UTF-8
        #后缀
        suffix:
        #文档类型
        content-type: text/html
        #模板前端
        template-loader-path: classpath:/templates/
        #启用模板
        enabled: true
    mvc:
        static-path-pattern: /static/**
    redis:
        #服务端IP
        host: 192.168.122.128
        #端口
        port: 6379
        #密码
        password: 123456
        #选择数据库
        database: 9
        #超时时间
        timeout: 10000ms
        #Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问
        #Lettuce线程安全,Jedis线程非安全
        lettuce:
            pool:
                #最大连接数,默认8
                max-active: 8
                #最大连接阻塞等待时间,默认-1
                max-wait: 10000ms
                #最大空闲连接,默认8
                max-idle: 200
                #最小空闲连接,默认0
                min-idle: 5
#mybatis-plus配置
mybatis-plus:
    #所对应的 XML 文件位置
    mapper-locations: classpath*:/mapper/*Mapper.xml
    #别名包扫描路径
    type-aliases-package: com.ycx.spbootpro.model
    configuration:
        #驼峰命名规则
        map-underscore-to-camel-case: true
#日志配置
logging:
    level:
        com.ycx.spbootpro.mapper: debug

3.启动类配置

package com.ycx.spbootpro;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@MapperScan({"com.ycx.spbootpro.mapper"})
@EnableTransactionManagement
@SpringBootApplication
public class SpbootproApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpbootproApplication.class, args);
    }

}

4.首页访问 ,导入前端页面及页面对应的js/css/images文件

 IndexController :

package com.ycx.spbootpro.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author 杨总
 * @create 2022-11-04 21:02
 */
@Controller
public class IndexController {
    @RequestMapping("/")
    public String index(){
//        前缀+逻辑视图名+后缀
        return "index.html";
    }
}

运行访问:

三、实现首页功能

0)导入帮助类

config:

RedisConfig :

package com.ycx.spbootpro.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
        RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
        redisTemplate.setStringSerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

 exception:

 BusinessException :

package com.ycx.spbootpro.exception;

import com.ycx.spbootpro.utils.JsonResponseStatus;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class BusinessException extends RuntimeException {
    private JsonResponseStatus jsonResponseStatus;
}

GlobalExceptionHandler :

package com.ycx.spbootpro.exception;

import com.ycx.spbootpro.utils.JsonResponseBody;
import com.ycx.spbootpro.utils.JsonResponseStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler
    public JsonResponseBody<?> exceptionHandler(Exception e){
        JsonResponseBody<?> jsonResponseBody=null;
        e.printStackTrace();
        if(e instanceof BusinessException){
            BusinessException ex= (BusinessException) e;
            jsonResponseBody=new JsonResponseBody<>(ex.getJsonResponseStatus());
        }else if(e instanceof BindException){
            BindException ex= (BindException) e;
            String msg = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage();
            jsonResponseBody=new JsonResponseBody<>(JsonResponseStatus.BIND_ERROR);
            jsonResponseBody.setMsg(msg);
        }else{
            System.out.println("aaaaaa");
        }
        return jsonResponseBody;
    }
}

Generator:

CodeGenerator :

package com.ycx.spbootpro.generator;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class CodeGenerator {

    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir") + "/spbootpro";
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("yangzong");
        gc.setOpen(false);
        gc.setBaseColumnList(true);
        gc.setBaseResultMap(true);
        // gc.setSwagger2(true); 实体属性 Swagger2 注解
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/spbootpro?useUnicode=true&useSSL=false&characterEncoding=utf8");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("1234");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        //pc.setModuleName(scanner("模块名"));
        pc.setParent("com.ycx.spbootpro");
        //设置包名
        pc.setEntity("model");
        mpg.setPackageInfo(pc);

        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };

        // 如果模板引擎是 freemarker
        String templatePath = "/templates/mybatis-generator/mapper2.xml.ftl";
        // 如果模板引擎是 velocity
        // String templatePath = "/templates/mapper.xml.vm";

        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
                        + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
            }
        });
        /*
        cfg.setFileCreate(new IFileCreate() {
            @Override
            public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
                // 判断自定义文件夹是否需要创建
                checkDir("调用默认方法创建的目录,自定义目录用");
                if (fileType == FileType.MAPPER) {
                    // 已经生成 mapper 文件判断存在,不想重新生成返回 false
                    return !new File(filePath).exists();
                }
                // 允许生成模板文件
                return true;
            }
        });
        */
        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();

        // 配置自定义输出模板
        //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
        templateConfig.setMapper("templates/mybatis-generator/mapper2.java");
        templateConfig.setEntity("templates/mybatis-generator/entity2.java");
        templateConfig.setService("templates/mybatis-generator/service2.java");
        templateConfig.setServiceImpl("templates/mybatis-generator/serviceImpl2.java");
        templateConfig.setController("templates/mybatis-generator/controller2.java");
        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        //strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        strategy.setEntitySerialVersionUID(false);
        // 公共父类
        //strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
        // 写于父类中的公共字段
        strategy.setSuperEntityColumns("id");
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix("t_");
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }
}

Utils:

CookieUtils :

package com.ycx.spbootpro.utils;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

@Slf4j
public class CookieUtils {

    /**
     *
     * @Description: 得到Cookie的值, 不编码
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName) {
        return getCookieValue(request, cookieName, false);
    }

    /**
     *
     * @Description: 得到Cookie的值
     * @param request
     * @param cookieName
     * @param isDecoder
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null) {
            return null;
        }
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    if (isDecoder) {
                        retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
                    } else {
                        retValue = cookieList[i].getValue();
                    }
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return retValue;
    }

    /**
     *
     * @Description: 得到Cookie的值
     * @param request
     * @param cookieName
     * @param encodeString
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null) {
            return null;
        }
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return retValue;
    }

    /**
     *
     * @Description: 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码
     * @param request
     * @param response
     * @param cookieName
     * @param cookieValue
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue) {
        setCookie(request, response, cookieName, cookieValue, -1);
    }

    /**
     *
     * @Description: 设置Cookie的值 在指定时间内生效,但不编码
     * @param request
     * @param response
     * @param cookieName
     * @param cookieValue
     * @param cookieMaxage
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, int cookieMaxage) {
        setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
    }

    /**
     *
     * @Description: 设置Cookie的值 不设置生效时间,但编码
     * 在服务器被创建,返回给客户端,并且保存客户端
     * 如果设置了SETMAXAGE(int seconds),会把cookie保存在客户端的硬盘中
     * 如果没有设置,会默认把cookie保存在浏览器的内存中
     * 一旦设置setPath():只能通过设置的路径才能获取到当前的cookie信息
     * @param request
     * @param response
     * @param cookieName
     * @param cookieValue
     * @param isEncode
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, boolean isEncode) {
        setCookie(request, response, cookieName, cookieValue, -1, isEncode);
    }

    /**
     *
     * @Description: 设置Cookie的值 在指定时间内生效, 编码参数
     * @param request
     * @param response
     * @param cookieName
     * @param cookieValue
     * @param cookieMaxage
     * @param isEncode
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, int cookieMaxage, boolean isEncode) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
    }

    /**
     *
     * @Description: 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)
     * @param request
     * @param response
     * @param cookieName
     * @param cookieValue
     * @param cookieMaxage
     * @param encodeString
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, int cookieMaxage, String encodeString) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
    }

    /**
     *
     * @Description: 删除Cookie带cookie域名
     * @param request
     * @param response
     * @param cookieName
     */
    public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,
                                    String cookieName) {
        doSetCookie(request, response, cookieName, null, -1, false);
    }


    /**
     *
     * @Description: 设置Cookie的值,并使其在指定时间内生效
     * @param request
     * @param response
     * @param cookieName
     * @param cookieValue
     * @param cookieMaxage	cookie生效的最大秒数
     * @param isEncode
     */
    private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
                                          String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {
        try {
            if (cookieValue == null) {
                cookieValue = "";
            } else if (isEncode) {
                cookieValue = URLEncoder.encode(cookieValue, "utf-8");
            }
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxage > 0)
                cookie.setMaxAge(cookieMaxage);
            if (null != request) {// 设置域名的cookie
                String domainName = getDomainName(request);
                log.info("========== domainName: {} ==========", domainName);
                if (!"localhost".equals(domainName)) {
                    cookie.setDomain(domainName);
                }
            }
            cookie.setPath("/");
            response.addCookie(cookie);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * @Description: 设置Cookie的值,并使其在指定时间内生效
     * @param request
     * @param response
     * @param cookieName
     * @param cookieValue
     * @param cookieMaxage	cookie生效的最大秒数
     * @param encodeString
     */
    private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
                                          String cookieName, String cookieValue, int cookieMaxage, String encodeString) {
        try {
            if (cookieValue == null) {
                cookieValue = "";
            } else {
                cookieValue = URLEncoder.encode(cookieValue, encodeString);
            }
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxage > 0)
                cookie.setMaxAge(cookieMaxage);
            if (null != request) {// 设置域名的cookie
                String domainName = getDomainName(request);
                log.info("========== domainName: {} ==========", domainName);
                if (!"localhost".equals(domainName)) {
                    cookie.setDomain(domainName);
                }
            }
            cookie.setPath("/");
            response.addCookie(cookie);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * @Description: 得到cookie的域名
     * @return
     */
    private static final String getDomainName(HttpServletRequest request) {
        String domainName = null;

        String serverName = request.getRequestURL().toString();
        if (serverName == null || serverName.equals("")) {
            domainName = "";
        } else {
            serverName = serverName.toLowerCase();
            serverName = serverName.substring(7);
            final int end = serverName.indexOf("/");
            serverName = serverName.substring(0, end);
            if (serverName.indexOf(":") > 0) {
                String[] ary = serverName.split("\\:");
                serverName = ary[0];
            }

            final String[] domains = serverName.split("\\.");
            int len = domains.length;
            if (len > 3 && !isIp(serverName)) {
                // www.xxx.com.cn
                domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
            } else if (len <= 3 && len > 1) {
                // xxx.com or xxx.cn
                domainName = "." + domains[len - 2] + "." + domains[len - 1];
            } else {
                domainName = serverName;
            }
        }
        return domainName;
    }

    public static String trimSpaces(String IP){//去掉IP字符串前后所有的空格
        while(IP.startsWith(" ")){
            IP= IP.substring(1,IP.length()).trim();
        }
        while(IP.endsWith(" ")){
            IP= IP.substring(0,IP.length()-1).trim();
        }
        return IP;
    }

    public static boolean isIp(String IP){//判断是否是一个IP
        boolean b = false;
        IP = trimSpaces(IP);
        if(IP.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){
            String s[] = IP.split("\\.");
            if(Integer.parseInt(s[0])<255)
                if(Integer.parseInt(s[1])<255)
                    if(Integer.parseInt(s[2])<255)
                        if(Integer.parseInt(s[3])<255)
                            b = true;
        }
        return b;
    }
}

DataUtils:

package com.ycx.spbootpro.utils;

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

public class DataUtils<T> {
    /**
     * 转换方法,基于商品的排版情况(一行三列、一行四列等等)进行数据分行处理
     * @param cols  一行显示几列
     * @param goods 需要筛选的数据集
     * @return
     */
    public Map<String, List<T>> transfor(int cols, List<T> goods){
        Map<String,List<T>> data=new HashMap<>();
        List<T> rs=new ArrayList<>();
        int len=goods.size();
        for (int i = 0; i < len; i++) {
            rs.add(goods.get(i));
            if((i+1)%cols==0){
                data.put("goods"+(i+1),rs);
                if(i==len-1)
                    break;
                rs=new ArrayList<>();
                continue;
            }
            if(i==len-1){
                data.put("goods"+(i+1),rs);
            }
        }
        return data;
    }
}

JsonResponseBody:

package com.ycx.spbootpro.utils;

import lombok.Data;

import java.io.Serializable;

/**
 * 响应封装类
 */
@Data
public class JsonResponseBody<T> implements Serializable {

    private String msg="OK";
    private T data;
    private Integer code;
    private Integer total;

    public JsonResponseBody(){
        this.data=null;
        this.code=JsonResponseStatus.SUCCESS.getCode();
    }

    public JsonResponseBody(T data){
        this.data=data;
        this.code=JsonResponseStatus.SUCCESS.getCode();
    }

    public JsonResponseBody(T data,Integer total){
        this.data=data;
        this.total=total;
        this.code=JsonResponseStatus.SUCCESS.getCode();
    }

    public JsonResponseBody(JsonResponseStatus jsonResponseStatus){
        this.msg=jsonResponseStatus.getMsg();
        this.code=jsonResponseStatus.getCode();
    }

    public JsonResponseBody(JsonResponseStatus jsonResponseStatus,T data){
        this.data=data;
        this.msg=jsonResponseStatus.getMsg();
        this.code=jsonResponseStatus.getCode();
    }
}

 JsonResponseStatus :

package com.ycx.spbootpro.utils;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

@Getter
@ToString
@AllArgsConstructor
public enum JsonResponseStatus {

    SUCCESS(200,"OK"),
    ERROR(500,"内部错误"),

    USER_LOGIN_ERROR(100101,"用户名或者密码错误"),
    USER_MOBILE_ERROR(100102,"手机号码格式错误"),
    USER_PASSWORD_ERROR(100103,"用户密码错误"),
    USER_USERNAME_ERROR(100104,"账号不存在"),

    BIND_ERROR(200101,"参数校验异常"),
    TOKEN_EEROR(200102,"token令牌失效或已过期")
    ;

    private final Integer code;
    private final String msg;
}

MD5Utils :

package com.ycx.spbootpro.utils;

import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Component;

import java.util.UUID;

/**
 * MD5加密
 * 用户端:password=MD5(明文+固定Salt)
 * 服务端:password=MD5(用户输入+随机Salt)
 * 用户端MD5加密是为了防止用户密码在网络中明文传输,服务端MD5加密是为了提高密码安全性,双重保险。
 */
@Component
public class MD5Utils {

    //加密盐,与前端一致
    private static String salt="f1g2h3j4";

    /**
     * md5加密
     * @param src
     * @return
     */
    public static String md5(String src){
        return DigestUtils.md5Hex(src);
    }

    /**
     * 获取加密的盐
     * @return
     */
    public static String createSalt(){
        return UUID.randomUUID().toString().replace("-","");
    }

    /**
     * 将前端的明文密码通过MD5加密方式加密成后端服务所需密码
     * 注意:该步骤实际是在前端完成!!!
     * @param inputPass 明文密码
     * @return
     */
    public static String inputPassToFormpass(String inputPass){
        //混淆固定盐salt,安全性更可靠
        String str=salt.charAt(1)+""+salt.charAt(5)+inputPass+salt.charAt(0)+""+salt.charAt(3);
        return md5(str);
    }

    /**
     * 将后端密文密码+随机salt生成数据库的密码
     * @param formPass
     * @param salt
     * @return
     */
    public static String formPassToDbPass(String formPass,String salt){
        //混淆固定盐salt,安全性更可靠
        String str=salt.charAt(7)+""+salt.charAt(9)+formPass+salt.charAt(1)+""+salt.charAt(5);
        return md5(str);
    }

    /**
     * 将用户输入的密码转换成数据库的密码
     * @param inputPass 明文密码
     * @param salt      盐
     * @return
     */
    public static String inputPassToDbPass(String inputPass,String salt){
        String formPass = inputPassToFormpass(inputPass);
        String dbPass = formPassToDbPass(formPass, salt);
        return dbPass;
    }

    public static void main(String[] args) {
        //d7aaa28e3b8e6c88352bd5e7c23829f9
        //5512a78a188b318c074a15f9b056a712
        String formPass = inputPassToFormpass("123456");
        System.out.println("前端加密密码:"+formPass);
        String salt = createSalt();
        System.out.println("后端加密随机盐:"+salt);
        String dbPass = formPassToDbPass(formPass, salt);
        System.out.println("后端加密密码:"+dbPass);
        System.out.println("-------------------------------------------");
        String dbPass1 = inputPassToDbPass("123456", salt);
        System.out.println("最终加密密码:"+dbPass1);
    }
}

MybatisPlusConfig :

package com.ycx.spbootpro.utils;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

//Spring boot方式
@EnableTransactionManagement
@Configuration
@MapperScan("com.zking.shoppingpro.service.*.mapper*")
public class MybatisPlusConfig {

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        // paginationInterceptor.setOverflow(false);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        // paginationInterceptor.setLimit(500);
        // 开启 count 的 join 优化,只针对部分 left join
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
        return paginationInterceptor;
    }
}

PageBean : 

package com.ycx.spbootpro.utils;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Map;

public class PageBean implements Serializable {

	//页码
	private int page=1;
	//每页显示条数
	private int rows=10;
	//总记录数
	private int total=0;
	//是否分页标记
	private boolean pagination=true;
	//上一次请求的路径
	private String url;
	//请求参数Map集合
	private Map<String,String[]> map;
	
	public String getUrl() {
		return url;
	}
	public void setUrl(String url) {
		this.url = url;
	}
	public Map<String, String[]> getMap() {
		return map;
	}
	public void setMap(Map<String, String[]> map) {
		this.map = map;
	}
	public int getPage() {
		return page;
	}
	public void setPage(int page) {
		this.page = page;
	}
	public int getRows() {
		return rows;
	}
	public void setRows(int rows) {
		this.rows = rows;
	}
	public int getTotal() {
		return total;
	}
	public void setTotal(int total) {
		this.total = total;
	}
	public boolean isPagination() {
		return pagination;
	}
	public void setPagination(boolean pagination) {
		this.pagination = pagination;
	}
	
	//重载setPage/setRows/setPagination方法
	public void setPage(String page) {
		if(null!=page&&!"".equals(page))
			this.page=Integer.parseInt(page);
	}
	public void setRows(String rows) {
		if(null!=rows&&!"".equals(rows))
			this.rows=Integer.parseInt(rows);
	}
	public void setPagination(String pagination) {
		if(null!=pagination&&!"".equals(pagination))
			this.pagination=Boolean.parseBoolean(pagination);
	}
	
	public void setRequest(HttpServletRequest req) {
		//获取前端提交的page/rows/pagination参数
		String page=req.getParameter("page");
		String rows=req.getParameter("rows");
		String pagination=req.getParameter("pagination");
		
		//设置属性
		this.setPage(page);
		this.setPagination(pagination);
		this.setRows(rows);
		
		//设置上一次请求的路径
		//第一次请求:
		//http://localhost:8080/项目名/请求路径.action?page=1
		//第二次请求:下一页  page=2
		//项目名+请求路径.action
		//req.getContextPath()+req.getServletPath()==req.getRequestURI()
		this.url=req.getRequestURI();
		
		//获取请求参数集合
		// checkbox name='hobby'
		// Map<String,String[]> hobby==key  value==new String[]{"篮球","足球",..}
		this.map=req.getParameterMap();
	}
	
	/**
	 * 获取分页的起始位置
	 * @return
	 */
	public int getStartIndex() {
		//第1页:(1-1)*10  ==0    limit 0,10
		//第2页:(2-1)*10  ==10   limit 10,10
		//..
		return (this.page-1)*this.rows;
	}
	
	//获取末页、上一页、下一页
	/**
	 * 获取末页
	 * @return
	 */
	public int getMaxPager() {
		int maxPager=this.total/this.rows;
		if(this.total%this.rows!=0)
			maxPager++;
		return maxPager;
	}
	
	/**
	 * 获取上一页
	 * @return
	 */
	public int getPreviousPager() {
		int previousPager=this.page-1;
		if(previousPager<=1)
			previousPager=1;
		return previousPager;
	}
	
	/**
	 * 获取下一页
	 * @return
	 */
	public int getNextPager() {
		int nextPager=this.page+1;
		if(nextPager>getMaxPager())
			nextPager=getMaxPager();
		return nextPager;
	}
	@Override
	public String toString() {
		return "PageBean [page=" + page + ", rows=" + rows + ", total=" + total + ", pagination=" + pagination
				+ ", url=" + url + ", map=" + map + "]";
	}
	
}

ValidatorUtils :

package com.ycx.spbootpro.utils;

import org.apache.commons.lang3.StringUtils;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 正则校验工具类
 * @author 刘开宇
 */
public class ValidatorUtils {

    private static final Pattern mobile_pattern=Pattern.compile("[1]([0-9])[0-9]{9}$");

    public static boolean isMobile(String mobile){
        if(StringUtils.isEmpty(mobile))
            return false;
    Matcher matcher = mobile_pattern.matcher(mobile);
        return matcher.matches();
}
}

 在generater类运行自动生成代码:

在控制台输入表名:

生成成功:

1)创建公共跳转控制器PageController

package com.ycx.spbootpro.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author 杨总
 * @create 2022-11-05 17:20
 */
@Controller
public class PageController {
    /**
     * 直接跳转页面(没有层级文件夹的情况)
     * 列如:
     * http://localhost:8081/page/paint.html
     * http://localhost:8081/page/perfume.html
     *
     * @return
     */
    @RequestMapping("/page/{page}")
    public String page(@PathVariable(value = "page") String page) {
        return page;
    }

    /**
     * 直接跳转页面(存在层级文件夹的情况)
     *
     * @return
     */
    @RequestMapping("/page/{dir}/{page}")
    public String dir(@PathVariable(value = "dir") String dir,
                      @PathVariable(value = "page") String page) {
        return dir + "/" + page;
    }
}

2)使用Mybatis-plus反向生成代码(Goods商品信息)

package com.ycx.spbootpro.generator;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class CodeGenerator {

    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir") + "/spbootpro";
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("yangzong");
        gc.setOpen(false);
        gc.setBaseColumnList(true);
        gc.setBaseResultMap(true);
        // gc.setSwagger2(true); 实体属性 Swagger2 注解
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/spbootpro?useUnicode=true&useSSL=false&characterEncoding=utf8");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("1234");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        //pc.setModuleName(scanner("模块名"));
        pc.setParent("com.ycx.spbootpro");
        //设置包名
        pc.setEntity("model");
        mpg.setPackageInfo(pc);

        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };

        // 如果模板引擎是 freemarker
        String templatePath = "/templates/mybatis-generator/mapper2.xml.ftl";
        // 如果模板引擎是 velocity
        // String templatePath = "/templates/mapper.xml.vm";

        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
                        + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
            }
        });
        /*
        cfg.setFileCreate(new IFileCreate() {
            @Override
            public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
                // 判断自定义文件夹是否需要创建
                checkDir("调用默认方法创建的目录,自定义目录用");
                if (fileType == FileType.MAPPER) {
                    // 已经生成 mapper 文件判断存在,不想重新生成返回 false
                    return !new File(filePath).exists();
                }
                // 允许生成模板文件
                return true;
            }
        });
        */
        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();

        // 配置自定义输出模板
        //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
        templateConfig.setMapper("templates/mybatis-generator/mapper2.java");
        templateConfig.setEntity("templates/mybatis-generator/entity2.java");
        templateConfig.setService("templates/mybatis-generator/service2.java");
        templateConfig.setServiceImpl("templates/mybatis-generator/serviceImpl2.java");
        templateConfig.setController("templates/mybatis-generator/controller2.java");
        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        //strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        strategy.setEntitySerialVersionUID(false);
        // 公共父类
        //strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
        // 写于父类中的公共字段
        strategy.setSuperEntityColumns("id");
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix("t_");
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }
}

3)创建IndexController并定义商品查询请求处理方法

package com.ycx.spbootpro.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ycx.spbootpro.model.Goods;
import com.ycx.spbootpro.service.IGoodsService;
import com.ycx.spbootpro.utils.DataUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;
import java.util.Map;

/**
 * @author 杨总
 * @create 2022-11-04 21:02
 */
@Controller
public class IndexController {
    @Autowired
    private IGoodsService goodsService;


    @RequestMapping("/")
    public String index(Model model){
        //  摆件花艺
        List<Goods> goods01 = goodsService.list(new QueryWrapper<Goods>()
                .eq("goods_type", "01")
                .last("limit 6"));

        //  壁挂北欧
        List<Goods> goods07 = goodsService.list(new QueryWrapper<Goods>()
                .eq("goods_type", "07")
                .last("limit 12"));

//        为了方便首页数据显示,方便摆放
        DataUtils<Goods> dataUtils = new DataUtils<>();
        Map<String, List<Goods>> gt01 = dataUtils.transfor(3, goods01);
        Map<String, List<Goods>> gt07 = dataUtils.transfor(4, goods07);
        model.addAttribute("gt01",gt01);
        model.addAttribute("gt07",gt07);
        return "index.html";
    }

}

首页数据绑定语法:

1) list集合判空
<#if goods07?? && goods07?size gt 0>

2) 遍历map集合,获取所有的keys
<#list goods07?keys as key>

3) 根据key获取对应value值goods01[key]
<#list goods07[key] as g>

 index.html:

<!DOCTYPE html>
<html>
	<head lang="en">
		<#include "common/head.html" />
		<link rel="stylesheet" type="text/css" href="css/public.css"/>
		<link rel="stylesheet" type="text/css" href="css/index.css" />
	</head>
	<div>
		<!------------------------------head------------------------------>
		<#include "common/top.html" />

		<!-------------------------banner--------------------------->
		<div class="block_home_slider">
			<div id="home_slider" class="flexslider">
				<ul class="slides">
					<li>
						<div class="slide">
							<img src="img/banner2.jpg"/>
						</div>
					</li>
					<li>
						<div class="slide">
							<img src="img/banner1.jpg"/>
						</div>
					</li>
				</ul>
			</div>
		</div>
		
		<!------------------------------thImg------------------------------>
		<div class="thImg">
			<div class="clearfix">
				<a href="${ctx}/page/vase_proList.html"><img src="img/i1.jpg"/></a>
				<a href="${ctx}/page/proList.html"><img src="img/i2.jpg"/></a>
				<a href="#2"><img src="img/i3.jpg"/></a>
			</div>
		</div>
		
		<!------------------------------news------------------------------>
		<div class="news">
			<div class="wrapper">
				<h2><img src="img/ih1.jpg"/></h2>
				<div class="top clearfix">
					<a href="${ctx}/page/proDetail.html"><img src="img/n1.jpg"/><p></p></a>
					<a href="${ctx}/page/proDetail.html"><img src="img/n2.jpg"/><p></p></a>
					<a href="${ctx}/page/proDetail.html"><img src="img/n3.jpg"/><p></p></a>
				</div>
				<div class="bott clearfix">
					<a href="${ctx}/page/proDetail.html"><img src="img/n4.jpg"/><p></p></a>
					<a href="${ctx}/page/proDetail.html"><img src="img/n5.jpg"/><p></p></a>
					<a href="${ctx}/page/proDetail.html"><img src="img/n6.jpg"/><p></p></a>
				</div>
				<h2><img src="img/ih2.jpg"/></h2>
				<#if gt01?? && gt01?size gt 0>
				<!--遍历gt01中所有的key,是为了该key中的对象-->
					<#list gt01?keys as key>
						<div class="flower clearfix tran">
						<#list gt01[key] as g>
							<a href="proDetail.html" class="clearfix">
								<dl>
									<dt>
										<span class="abl"></span>
										<img src="${g.goodsImg}"/>
										<span class="abr"></span>
									</dt>
									<dd>${g.goodsName}</dd>
									<dd><span>¥ ${g.goodsPrice}</span></dd>
								</dl>
							</a>
						</#list>
						</div>
					</#list>
				</#if>


			</div>
		</div>

		<!------------------------------ad------------------------------>
		<a href="#" class="ad"><img src="img/ib1.jpg"/></a>
		
		<!------------------------------people------------------------------>
		<div class="people">
			<div class="wrapper">
				<h2><img src="img/ih3.jpg"/></h2>
				<#if gt07?? && gt07?size gt 0>
				<#list gt07?keys as key>
				<div class="pList clearfix tran">
					<#list gt07[key] as g>
					<a href="proDetail.html">
						<dl>
							<dt>
								<span class="abl"></span>
								<img src="${g.goodsImg}"/>
								<span class="abr"></span>
							</dt>
							<dd>${g.goodsName}</dd>
							<dd><span>¥ ${g.goodsPrice}</span></dd>
						</dl>
					</a>
					</#list>
				</div>
				</#list>
				</#if>>

			</div>
		</div>

		<#include "common/footer.html"/>

		<script src="js/public.js" type="text/javascript" charset="utf-8"></script>
		<script src="js/nav.js" type="text/javascript" charset="utf-8"></script>
		<script src="js/jquery.flexslider-min.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			$(function() {
				$('#home_slider').flexslider({
					animation: 'slide',
					controlNav: true,
					directionNav: true,
					animationLoop: true,
					slideshow: true,
					slideshowSpeed:2000,
					useCSS: false
				});

			});
		</script>
	</body>
</html>

运行: 

 

四、用户明文登录

创建UserController类实现用户登录

        1.1)构建UserDto,定义mobile和password属性

        定义UserDto.java接受前台传递的参数:

package com.ycx.spbootpro.model.dto;

import com.ycx.spbootpro.validator.IsMobile;
import lombok.Data;

import javax.validation.constraints.NotBlank;

@Data
public class UserDto {
    @NotBlank(message = "手机号码不能为空!")
    @IsMobile
    private String mobile;
    @NotBlank(message = "密码不能为空!")
    private String password;
}

        


        1.2)创建UserController类

处理浏览器端的请求 UserController.java

package com.ycx.spbootpro.controller;


import com.ycx.spbootpro.model.dto.UserDto;
import com.ycx.spbootpro.service.IUserService;
import com.ycx.spbootpro.utils.JsonResponseBody;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;

/**
 * <p>
 * 用户信息表 前端控制器
 * </p>
 *
 * @author yangzong
 * @since 2022-11-05
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private IUserService userService;

    @RequestMapping("/toLogin")
    public JsonResponseBody toLogin(@Valid UserDto userDto,
                                    HttpServletRequest req,
                                    HttpServletResponse resp){

        return userService.toLogin(userDto,req,resp);
    }
}


        1.3)定义userLogin(UserDto userDto,HttpServletRequest req,HttpServletResponse resp)


        1.4)定义响应封装类JsonResponseBody和JsonResponseStatus

        1.5)在IUserService中定义userLogin(UserVo userVo),并返回JsonResponseBody

1.5.1)判断mobile和password是否为空
1.5.2)判断mobile格式是否正确
1.5.3)根据用户手机号码查询用户是否存在
1.5.4)校验账号
1.5.5)校验密码

package com.ycx.spbootpro.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ycx.spbootpro.exception.BusinessException;
import com.ycx.spbootpro.model.User;
import com.ycx.spbootpro.mapper.UserMapper;
import com.ycx.spbootpro.model.dto.UserDto;
import com.ycx.spbootpro.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ycx.spbootpro.utils.JsonResponseBody;
import com.ycx.spbootpro.utils.JsonResponseStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author yangzong
 * @since 2022-11-05
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public JsonResponseBody toLogin(UserDto userDto, HttpServletRequest req, HttpServletResponse resp) {
//        1.5.1)判断mobile和password是否为空(已由JSP303完成)
//        1.5.2)判断mobile格式是否正确(自定义验证注解)
//        1.5.3)根据用户手机号码查询用户是否存在
        User user = userMapper.selectOne(new QueryWrapper<User>()
                .eq("id", userDto.getMobile()));

        //1.5.4)校验账号
        //判断用户对象是否存在
        if(null==user)
            throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);


//        1.5.5)校验密码
        if(!user.getPassword().equals(userDto.getPassword()))
            throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);
      
        return new JsonResponseBody<>();
    }

}

        1.6)全局异常处理

1.6.1)创建BusinessException
1.6.2)创建GlobalExceptionHandler
1.6.3)修改userLogin中的异常处理方式

        1.7)自定义注解参数校验(JSR303)

1.7.1)创建自定义注解IsMobile
1.7.2)创建自定义校验规则类MobileValidator
1.7.3)在UserVo类的mobile属性中使用IsMobile注解

        自定义JSR303注解,完成服务端登录账号的验证

查看用户表数据

login.js:

$(function () {
    alert(2);

    //登录向后台发送Ajax请求
   $("#login").click(function () {
       let mobile = $("#mobile").val();
       let password = $("#password").val();


       $.post("/user/toLogin",{
           mobile:mobile,
           password:password
       },function (res) {
           alert(res.msg)
       },"json");
   })
});

测试多种情况: 

1.手机号为空

2.手机号为非法字符

3.密码为空

4.手机号不存在

5.手机号密码正确

6.密码错误

五、前端及数据库密码加密

前端加密:防止客户端浏览器F12导致密码泄露

后端加密:防止数据库数据泄露导致密码泄露

运行:

把 后端加密随机盐 和 加密密码  复制进表数据:

 

 再次运行,每次运行出来的密码都不一样:

记得在login.html导入:

UserServiceImpl.java变更如下

package com.ycx.spbootpro.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ycx.spbootpro.exception.BusinessException;
import com.ycx.spbootpro.model.User;
import com.ycx.spbootpro.mapper.UserMapper;
import com.ycx.spbootpro.model.dto.UserDto;
import com.ycx.spbootpro.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ycx.spbootpro.utils.JsonResponseBody;
import com.ycx.spbootpro.utils.JsonResponseStatus;
import com.ycx.spbootpro.utils.MD5Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author yangzong
 * @since 2022-11-05
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public JsonResponseBody toLogin(UserDto userDto, HttpServletRequest req, HttpServletResponse resp) {
//        1.5.1)判断mobile和password是否为空(已由JSP303完成)
//        1.5.2)判断mobile格式是否正确(自定义验证注解)
//        1.5.3)根据用户手机号码查询用户是否存在
        User user = userMapper.selectOne(new QueryWrapper<User>()
                .eq("id", userDto.getMobile()));

        //1.5.4)校验账号
        //判断用户对象是否存在
        if(null==user)
            throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);

//        前台传递到后台的密码要经过工具类md5加密一次才有可能跟数据库密码匹配上
        String pwd = MD5Utils.formPassToDbPass(userDto.getPassword(), user.getSalt());

//        1.5.5)校验密码
        if(!pwd.equals(user.getPassword()))
            throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);

        return new JsonResponseBody<>();
    }

}

login.js变更如下

$(function () {
    alert(2);

    //登录向后台发送Ajax请求
   $("#login").click(function () {
       let mobile = $("#mobile").val();
       let password = $("#password").val();
        //1.密码加密
       //1) 定义固定盐
       let salt='f1g2h3j4';
       //2) 固定盐混淆
       let temp=salt.charAt(1)+""+salt.charAt(5)+password+salt.charAt(0)+""+salt.charAt(3);
       //3) 使用MD5完成前端第一次加密
       let pwd=md5(temp);

       $.post("/user/toLogin",{
           mobile:mobile,
           password:pwd
       },function (res) {
           alert(res.msg)
       },"json");
   })
});

再次运行:

 

六、服务端客户端登录密码管理

将登录的用户数据分别保留在客户端以及服务端

UserServiceImpl.java变更如下

package com.ycx.spbootpro.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ycx.spbootpro.exception.BusinessException;
import com.ycx.spbootpro.model.User;
import com.ycx.spbootpro.mapper.UserMapper;
import com.ycx.spbootpro.model.dto.UserDto;
import com.ycx.spbootpro.service.IRedisService;
import com.ycx.spbootpro.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ycx.spbootpro.utils.CookieUtils;
import com.ycx.spbootpro.utils.JsonResponseBody;
import com.ycx.spbootpro.utils.JsonResponseStatus;
import com.ycx.spbootpro.utils.MD5Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.UUID;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author yangzong
 * @since 2022-11-05
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private IRedisService redisService;

    @Override
    public JsonResponseBody toLogin(UserDto userDto, HttpServletRequest req, HttpServletResponse resp) {
//        1.5.1)判断mobile和password是否为空(已由JSP303完成)
//        1.5.2)判断mobile格式是否正确(自定义验证注解)
//        1.5.3)根据用户手机号码查询用户是否存在
        User user = userMapper.selectOne(new QueryWrapper<User>()
                .eq("id", userDto.getMobile()));

        //1.5.4)校验账号
        //判断用户对象是否存在
        if(null==user)
            throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);

//        前台传递到后台的密码要经过工具类md5加密一次才有可能跟数据库密码匹配上
        String pwd = MD5Utils.formPassToDbPass(userDto.getPassword(), user.getSalt());

//        1.5.5)校验密码
        if(!pwd.equals(user.getPassword()))
            throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);

        //6.将登陆用户对象与token令牌进行绑定保存到cookie和redis
        //创建登陆令牌token
        String token= UUID.randomUUID().toString().replace("-","");
        //将token令牌保存到cookie中
        CookieUtils.setCookie(req,resp,"token",token,7200);
        //将登陆token令牌与用户对象user绑定到redis中
        redisService.setUserToRedis(token,user);
        //将用户登陆的昵称设置到cookie中
        CookieUtils.setCookie(req,resp,"nickname",user.getNickname());


        return new JsonResponseBody<>();
    }

}

IRedisService.java:

package com.ycx.spbootpro.service;

import com.ycx.spbootpro.model.User;

/**
 * @author 杨总
 * @create 2022-11-05 20:45
 */
public interface IRedisService {

    void setUserToRedis(String token, User user) ;

    User getUserByToken(String token);
}

RedisServiceImpl.java:

package com.ycx.spbootpro.service.impl;

import com.ycx.spbootpro.model.User;
import com.ycx.spbootpro.service.IRedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @author 杨总
 * @create 2022-11-05 20:49
 */
@Service
public class RedisServiceImpl implements IRedisService {
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Override
    public void setUserToRedis(String token, User user) {
        redisTemplate.opsForValue().set("user:"+token,user,7200L, TimeUnit.SECONDS);
    }

    @Override
    public User getUserByToken(String token) {
        return (User) redisTemplate.opsForValue().get("user:"+token);
    }


}

测试如下:

 

 

今日内容就到这里啦~再会!

相关文章:

  • Android 天气APP(三十六)运行到本地AS、更新项目版本依赖、去掉ButterKnife
  • 【C++学习】string的使用
  • WeMos Mini ESP32-S2FN4R2介绍
  • Halcon图像分割总结
  • 5 h0255. 迷宫问题,6 h0253. 鸣人和佐助(广度优先搜索)
  • 《数据结构》堆栈(铁路、洗牌、汉诺塔、走迷宫)全解析
  • 基于时序行为的协同过滤推荐算法(Python)
  • Vue--》计算属性与监视(侦听)属性的使用
  • 【状语从句练习题】because / because of / although / in spite of
  • Web前端开发基础教程二
  • MyBatis-写分页的几种方法,怎么写分页最简单
  • 狗厂员工来面试本想难为一下,结果被虐得连console.log也不敢写了
  • Pytorch(Tensor)-Numpy(ndarrays) API对照表
  • RK3399驱动开发 | 14 - AP6255 SDIO WiFi 调试(基于linux5.4.32内核)
  • _cpp 红黑树快速了解底层结构
  • 10个最佳ES6特性 ES7与ES8的特性
  • 345-反转字符串中的元音字母
  • Django 博客开发教程 16 - 统计文章阅读量
  • java多线程
  • k个最大的数及变种小结
  • Mysql5.6主从复制
  • PHP的类修饰符与访问修饰符
  • Spark RDD学习: aggregate函数
  • Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比...
  • V4L2视频输入框架概述
  • XML已死 ?
  • 笨办法学C 练习34:动态数组
  • 前端技术周刊 2019-01-14:客户端存储
  • 实现简单的正则表达式引擎
  • 使用Swoole加速Laravel(正式环境中)
  • 突破自己的技术思维
  • 终端用户监控:真实用户监控还是模拟监控?
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • raise 与 raise ... from 的区别
  • 国内唯一,阿里云入选全球区块链云服务报告,领先AWS、Google ...
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • (02)vite环境变量配置
  • (1)Android开发优化---------UI优化
  • (C)一些题4
  • (Matlab)基于蝙蝠算法实现电力系统经济调度
  • (分布式缓存)Redis分片集群
  • (附源码)springboot 个人网页的网站 毕业设计031623
  • (附源码)springboot码头作业管理系统 毕业设计 341654
  • (免费领源码)python#django#mysql公交线路查询系统85021- 计算机毕业设计项目选题推荐
  • (三)Honghu Cloud云架构一定时调度平台
  • (使用vite搭建vue3项目(vite + vue3 + vue router + pinia + element plus))
  • (万字长文)Spring的核心知识尽揽其中
  • (转)Windows2003安全设置/维护
  • (转)负载均衡,回话保持,cookie
  • (转)关于如何学好游戏3D引擎编程的一些经验
  • .NET CORE Aws S3 使用
  • .Net Memory Profiler的使用举例
  • .Net 垃圾回收机制原理(二)
  • .Net6支持的操作系统版本(.net8已来,你还在用.netframework4.5吗)