目录
一、springboot项目简介
项目前期准备
使用技术
前端:Freemarker、jQuery
后端:SpringBoot、MyBatisPlus、Lombok
中间件:Redis
数据表介绍
用户表:t_user
商品表:t_goods
订单表:t_order
订单项表:t_order_item
数据源表:t_dict_type
数据项表:t_dict_dat
后期开发项目肯定是还要修改的 现在浅看一下
二、构建springboot项目
创建SpringBoot项目并配置POM
构建项目时并没有选择任何组件(避免于所准备的依赖因为版本的问题导致出现问题)
spring-boot-starter-freemarker
spring-boot-starter-web
mysql-connector-java 5.1.44
lombok
<!-- mybatis plus依赖 -->
mybatis-plus-boot-starter 3.4.0
mybatis-plus-generator 3.4.0
<!-- hariki连接池 -->
HikariCP
<!-- MD5依赖 -->
commons-codec
commons-lang3 3.6
<!-- valid验证依赖 -->
spring-boot-starter-validation
<!-- redis -->
spring-boot-starter-data-redis
参考
pom.xml
<?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.cdl</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>
application.yml参考如下
application.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: 123456
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.26.128
#端口
port: 6379
#密码
password: 123456
#选择数据库
database: 0
#超时时间
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.cdl.spbootpro.model
configuration:
#驼峰命名规则
map-underscore-to-camel-case: true
#日志配置
logging:
level:
com.cdl.spbootpro.mapper: debug
redis使用的连接的IP是虚拟机所分配的
启动类的配置
SpbootproApplication
package com.cdl.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.cdl.spbootpro.mapper"})//扫描接口
@EnableTransactionManagement//开启事务
@SpringBootApplication
public class SpbootproApplication {
public static void main(String[] args) {
SpringApplication.run(SpbootproApplication.class, args);
}
}
将准备的页面资料以及图片等复制放入resources下
测试一下是否能访问前端
新建一个controller的包
IndexController
package com.cdl.spbootpro.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author cdl
* @site www.cdl.com
* @create 2022-11-05 17:31
*/
@Controller
public class IndexController {
@RequestMapping("/")
public String index(){
// 模板前端
// /templates+index.html+""
//前缀+逻辑视图名+后缀
return "index.html";
}
}
运行启动类 浏览器输入地址 可以访问到我们准备的资源页面中
但是此时的数据全都是静态的资源 页面中写的一样的

三、首页功能
目标:将数据库中的数据展示在首页的页面上
数据库数据将这些数据展示到对应的商品上
导入mybatisplus的代码生成器
CodeGenerator
package com.cdl.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("cdl");
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("123456");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
//pc.setModuleName(scanner("模块名"));
pc.setParent("com.cdl.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();
}
}
将自定义的代码生成模板放到templates目录下,覆盖Mybatis-plus默认的代码生成模板
名为mybatis-generator的文件夹,将该文件夹放入resources下的templates下
controller2.java.ftl
package ${package.Controller};
import org.springframework.web.bind.annotation.RequestMapping;
<#if restControllerStyle>
import org.springframework.web.bind.annotation.RestController;
<#else>
import org.springframework.stereotype.Controller;
</#if>
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>
/**
* <p>
* ${table.comment!} 前端控制器
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if restControllerStyle>
@RestController
<#else>
@Controller
</#if>
@RequestMapping("<#if package.ModuleName?? && package.ModuleName != "">/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle??>${controllerMappingHyphen}<#else>${table.entityPath}</#if>")
<#if kotlin>
class ${table.controllerName}<#if superControllerClass??> : ${superControllerClass}()</#if>
<#else>
<#if superControllerClass??>
public class ${table.controllerName} extends ${superControllerClass} {
<#else>
public class ${table.controllerName} {
</#if>
}
</#if>
entity2.java.ftl
package ${package.Entity};
<#list table.importPackages as pkg>
import ${pkg};
</#list>
<#if swagger2>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
</#if>
<#if entityLombokModel>
import lombok.Data;
import lombok.EqualsAndHashCode;
<#if chainModel>
import lombok.experimental.Accessors;
</#if>
</#if>
/**
* <p>
* ${table.comment!}
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if entityLombokModel>
@Data
<#if superEntityClass??>
@EqualsAndHashCode(callSuper = true)
<#else>
@EqualsAndHashCode(callSuper = false)
</#if>
<#if chainModel>
@Accessors(chain = true)
</#if>
</#if>
<#if table.convert>
@TableName("${table.name}")
</#if>
<#if swagger2>
@ApiModel(value="${entity}对象", description="${table.comment!}")
</#if>
<#if superEntityClass??>
public class ${entity} extends ${superEntityClass}<#if activeRecord><${entity}></#if> {
<#elseif activeRecord>
public class ${entity} extends Model<${entity}> {
<#else>
public class ${entity} implements Serializable {
</#if>
<#if entitySerialVersionUID>
private static final long serialVersionUID = 1L;
</#if>
<#-- ---------- BEGIN 字段循环遍历 ---------->
<#list table.fields as field>
<#if field.keyFlag>
<#assign keyPropertyName="${field.propertyName}"/>
</#if>
<#if field.comment!?length gt 0>
<#if swagger2>
@ApiModelProperty(value = "${field.comment}")
<#else>
/**
* ${field.comment}
*/
</#if>
</#if>
<#if field.keyFlag>
<#-- 主键 -->
<#if field.keyIdentityFlag>
@TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
<#elseif idType??>
@TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
<#elseif field.convert>
@TableId("${field.annotationColumnName}")
</#if>
<#-- 普通字段 -->
<#elseif field.fill??>
<#-- ----- 存在字段填充设置 ----->
<#if field.convert>
@TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
<#else>
@TableField(fill = FieldFill.${field.fill})
</#if>
<#elseif field.convert>
@TableField("${field.annotationColumnName}")
</#if>
<#-- 乐观锁注解 -->
<#if (versionFieldName!"") == field.name>
@Version
</#if>
<#-- 逻辑删除注解 -->
<#if (logicDeleteFieldName!"") == field.name>
@TableLogic
</#if>
private ${field.propertyType} ${field.propertyName};
</#list>
<#------------ END 字段循环遍历 ---------->
<#if !entityLombokModel>
<#list table.fields as field>
<#if field.propertyType == "boolean">
<#assign getprefix="is"/>
<#else>
<#assign getprefix="get"/>
</#if>
public ${field.propertyType} ${getprefix}${field.capitalName}() {
return ${field.propertyName};
}
<#if chainModel>
public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
<#else>
public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
</#if>
this.${field.propertyName} = ${field.propertyName};
<#if chainModel>
return this;
</#if>
}
</#list>
</#if>
<#if entityColumnConstant>
<#list table.fields as field>
public static final String ${field.name?upper_case} = "${field.name}";
</#list>
</#if>
<#if activeRecord>
@Override
protected Serializable pkVal() {
<#if keyPropertyName??>
return this.${keyPropertyName};
<#else>
return null;
</#if>
}
</#if>
<#if !entityLombokModel>
@Override
public String toString() {
return "${entity}{" +
<#list table.fields as field>
<#if field_index==0>
"${field.propertyName}=" + ${field.propertyName} +
<#else>
", ${field.propertyName}=" + ${field.propertyName} +
</#if>
</#list>
"}";
}
</#if>
}
mapper2.java.ftl
package ${package.Mapper};
import ${package.Entity}.${entity};
import ${superMapperClassPackage};
/**
* <p>
* ${table.comment!} Mapper 接口
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if kotlin>
interface ${table.mapperName} : ${superMapperClass}<${entity}>
<#else>
public interface ${table.mapperName} extends ${superMapperClass}<${entity}> {
}
</#if>
mapper2.xml.ftl
<?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="${package.Mapper}.${table.mapperName}">
<#if enableCache>
<!-- 开启二级缓存 -->
<cache type="org.mybatis.caches.ehcache.LoggingEhcache"/>
</#if>
<#if baseResultMap>
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="${package.Entity}.${entity}">
<#list table.fields as field>
<#if field.keyFlag><#--生成主键排在第一位-->
<id column="${field.name}" property="${field.propertyName}" />
</#if>
</#list>
<#list table.commonFields as field><#--生成公共字段 -->
<result column="${field.name}" property="${field.propertyName}" />
</#list>
<#list table.fields as field>
<#if !field.keyFlag><#--生成普通字段 -->
<result column="${field.name}" property="${field.propertyName}" />
</#if>
</#list>
</resultMap>
</#if>
<#if baseColumnList>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
<#list table.commonFields as field>
${field.columnName},
</#list>
${table.fieldNames}
</sql>
</#if>
</mapper>
service2.java.ftl
package ${package.Service};
import ${package.Entity}.${entity};
import ${superServiceClassPackage};
/**
* <p>
* ${table.comment!} 服务类
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if kotlin>
interface ${table.serviceName} : ${superServiceClass}<${entity}>
<#else>
public interface ${table.serviceName} extends ${superServiceClass}<${entity}> {
}
</#if>
serviceImpl2.java.ftl
package ${package.ServiceImpl};
import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
import ${package.Service}.${table.serviceName};
import ${superServiceImplClassPackage};
import org.springframework.stereotype.Service;
/**
* <p>
* ${table.comment!} 服务实现类
* </p>
*
* @author ${author}
* @since ${date}
*/
@Service
<#if kotlin>
open class ${table.serviceImplName} : ${superServiceImplClass}<${table.mapperName}, ${entity}>(), ${table.serviceName} {
}
<#else>
public class ${table.serviceImplName} extends ${superServiceImplClass}<${table.mapperName}, ${entity}> implements ${table.serviceName} {
}
</#if>
运行代码生成类 输入要生成的表名
生成成功
编辑IndexController并定义商品查询请求处理方法
主要查询出 装饰摆件 和 墙式壁挂两个类别的上坪在首页进行展示
SELECT * FROM t_goods;
SELECT * FROM t_goods where goods_type = '01';
SELECT * FROM t_goods where goods_type = '07';SELECT * FROM t_dict_type;
SELECT * FROM t_dict_data;
首页数据绑定语法
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>
首页方法改造
注意:
IndexController
package com.cdl.spbootpro.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cdl.spbootpro.model.Goods;
import com.cdl.spbootpro.service.IGoodsService;
import com.cdl.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 org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/**
* @author cdl
* @site www.cdl.com
* @create 2022-11-05 17:31
*/
@Controller
public class IndexController {
@Autowired
private IGoodsService goodsService;
@RequestMapping("/")
public String index(Model model){
// 模板前端
// /templates+index.html+""
//前缀+逻辑视图名+后缀
// 主要查询出 装饰摆件 和 墙式壁挂两个类别的上坪在首页进行展示
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";
}
}
页面数据展示
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>
<body>
<!------------------------------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>
<#list gt01?keys as key>
<div class="flower clearfix tran">
<!--遍历中的所有的key,是为了拿该key中的对象-->
<#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 class="flower clearfix tran">
<a href="proDetail.html" class="clearfix">
<dl>
<dt>
<span class="abl"></span>
<img src="img/flo1.jpg"/>
<span class="abr"></span>
</dt>
<dd>【花艺】七头美丽玫瑰仿真花束</dd>
<dd><span>¥ 79.00</span></dd>
</dl>
</a>
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/flo2.jpg"/>
<span class="abr"></span>
</dt>
<dd>【花艺】七头美丽玫瑰仿真花束</dd>
<dd><span>¥ 79.00</span></dd>
</dl>
</a>
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/flo3.jpg"/>
<span class="abr"></span>
</dt>
<dd>【花艺】七头美丽玫瑰仿真花束</dd>
<dd><span>¥ 79.00</span></dd>
</dl>
</a>
</div>-->
</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>
<div class="pList clearfix tran">
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/s7.jpg"/>
<span class="abr"></span>
</dt>
<dd>【最家】不锈钢壁饰墙饰软装</dd>
<dd><span>¥688.00</span></dd>
</dl>
</a>
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/s10.jpg"/>
<span class="abr"></span>
</dt>
<dd>【最家】小城动物木板画壁挂北欧</dd>
<dd><span>¥188.00</span></dd>
</dl>
</a>
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/s4.jpg"/>
<span class="abr"></span>
</dt>
<dd>【最家】玄关假山水壁饰背景墙饰挂件创意</dd>
<dd><span>¥599.00</span></dd>
</dl>
</a>
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/s9.jpg"/>
<span class="abr"></span>
</dt>
<dd>【最家】金属树枝壁饰铜鸟装饰品</dd>
<dd><span>¥928.00</span></dd>
</dl>
</a>
</div>
<div class="pList clearfix tran">
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/s6.jpg"/>
<span class="abr"></span>
</dt>
<dd>【最家】金属壁饰创意背景墙面挂件创意</dd>
<dd><span>¥228.00</span></dd>
</dl>
</a>
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/s8.jpg"/>
<span class="abr"></span>
</dt>
<dd>【最家】小城动物木板画壁挂北欧</dd>
<dd><span>¥199.00</span></dd>
</dl>
</a>
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/s12.jpg"/>
<span class="abr"></span>
</dt>
<dd>【最家】欧式复古挂钟创意餐厅钟表家居挂件</dd>
<dd><span>¥666.00</span></dd>
</dl>
</a>
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/s1.jpg"/>
<span class="abr"></span>
</dt>
<dd>【最家】客厅地中海欧式现代相片墙创意</dd>
<dd><span>¥59.80</span></dd>
</dl>
</a>
</div>
<div class="pList clearfix tran">
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/s5.jpg"/>
<span class="abr"></span>
</dt>
<dd>【最家】铁艺荷叶壁挂软装背景墙上装饰品</dd>
<dd><span>¥800.00</span></dd>
</dl>
</a>
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/s3.jpg"/>
<span class="abr"></span>
</dt>
<dd>【最家】欧式照片墙 创意组合结婚礼物</dd>
<dd><span>¥189.00</span></dd>
</dl>
</a>
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/s2.jpg"/>
<span class="abr"></span>
</dt>
<dd>【最家】欧式钟表相框墙挂墙创意组合</dd>
<dd><span>¥148.00</span></dd>
</dl>
</a>
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/s11.jpg"/>
<span class="abr"></span>
</dt>
<dd>【最家】小城动物木板画壁挂北欧</dd>
<dd><span>¥188.00</span></dd>
</dl>
</a>
</div>
</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>
运行结果:
可以发现所有的数据已经不一样了
将页面的展示格式改变
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>
<body>
<!------------------------------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>
<#list gt01?keys as key>
<div class="flower clearfix tran">
<!--遍历中的所有的key,是为了拿该key中的对象-->
<#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 class="flower clearfix tran">
<a href="proDetail.html" class="clearfix">
<dl>
<dt>
<span class="abl"></span>
<img src="img/flo1.jpg"/>
<span class="abr"></span>
</dt>
<dd>【花艺】七头美丽玫瑰仿真花束</dd>
<dd><span>¥ 79.00</span></dd>
</dl>
</a>
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/flo2.jpg"/>
<span class="abr"></span>
</dt>
<dd>【花艺】七头美丽玫瑰仿真花束</dd>
<dd><span>¥ 79.00</span></dd>
</dl>
</a>
<a href="proDetail.html">
<dl>
<dt>
<span class="abl"></span>
<img src="img/flo3.jpg"/>
<span class="abr"></span>
</dt>
<dd>【花艺】七头美丽玫瑰仿真花束</dd>
<dd><span>¥ 79.00</span></dd>
</dl>
</a>
</div>-->
</div>
</div>
<!------------------------------ad------------------------------>
<a href="#" class="ad"><img src="img/ib1.jpg"/></a>
<!------------------------------people------------------------------>
<div class="people">
< 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>
此时的页面还是不能够跳转的
四、用户明文登录
要实现登录功能,要确保页面间能够跳转
公共跳转控制器PageController
PageController
package com.cdl.spbootpro.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author cdl
* @site www.cdl.com
* @create 2022-11-05 21:45
*/
@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;
}
}
跳转成功
在js下建一个包(login)再建一个文件(login.js)用于写登录的内容
$(function () {
// alert(1);
// 给登录按钮添加事件
$("#login").click(function () {
let mobile = $("#mobile").val();
let password = $("#password").val();
//2.向后台发起登录ajax请求
$.post('/user/userLogin',{
mobile:mobile,
password:password
},function(rs){
if(rs.code!=200){
alert(rs.msg);
}else
//alert(rs.msg);
location.href='/';
},'json');
});
});
定义UserDto.java接受前台传递的参数
UserDto
package com.cdl.spbootpro.model.dto;
import com.cdl.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;
}
处理浏览器端的请求 UserController
package com.cdl.spbootpro.controller;
import com.cdl.spbootpro.model.dto.UserDto;
import com.cdl.spbootpro.service.IUserService;
import com.cdl.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 cdl
* @since 2022-11-05
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
@RequestMapping("/userLogin")
public JsonResponseBody userLogin(@Valid UserDto userDto,
HttpServletRequest req,
HttpServletResponse resp){
return userService.userLogin(userDto,req,resp);
}
}
对应的业务代码
IUserService
package com.cdl.spbootpro.service;
import com.cdl.spbootpro.model.User;
import com.baomidou.mybatisplus.extension.service.IService;
import com.cdl.spbootpro.model.dto.UserDto;
import com.cdl.spbootpro.utils.JsonResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* <p>
* 用户信息表 服务类
* </p>
*
* @author cdl
* @since 2022-11-05
*/
public interface IUserService extends IService<User> {
JsonResponseBody userLogin(UserDto userDto, HttpServletRequest req, HttpServletResponse resp);
}
UserServiceImpl
package com.cdl.spbootpro.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cdl.spbootpro.exception.BusinessException;
import com.cdl.spbootpro.model.User;
import com.cdl.spbootpro.mapper.UserMapper;
import com.cdl.spbootpro.model.dto.UserDto;
import com.cdl.spbootpro.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cdl.spbootpro.utils.JsonResponseBody;
import com.cdl.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;
/**
* <p>
* 用户信息表 服务实现类
* </p>
*
* @author cdl
* @since 2022-11-05
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private UserMapper userMapper;
@Override
public JsonResponseBody userLogin(UserDto userDto, HttpServletRequest req, HttpServletResponse resp) {
//要做的事情:
// 判断mobile和password是否为空 已由JSP303完成(在controller中的UserDto添加@Valid便可解决)
//判断mobile格式是否正确 (自定义验证注解@IsMobile)
//根据用户手机号码查询用户
User user = userMapper.selectOne(new QueryWrapper<User>()
.eq("id", userDto.getMobile()));
// 判断所查询的用户是否存在(校验账号)
if(null==user)
throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);
//校验密码
if(!user.getPassword().equals(userDto.getPassword()))
throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);
return new JsonResponseBody<>();
}
}
注意:
此时的数据库(没有使用盐加密)
测试一下
什么也不填
输入正确的手机号码和密码(123456)
会进入主页面
输入错的密码
五、前端及数据库密码加密
盐加密
前端加密:防止客户端浏览器F12导致密码泄露
后端加密:防止数据库数据泄露导致密码泄露
login.js变更如下
login.js
$(function () {
// alert(1);
// 给登录按钮添加事件
$("#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);
console.log("mobile=%s,password=%s",mobile,pwd);
//2.向后台发起登录ajax请求
$.post('/user/userLogin',{
mobile:mobile,
password:pwd
},function(rs){
if(rs.code!=200){
alert(rs.msg);
}else
//alert(rs.msg);
location.href='/';
},'json');
});
});
UserServiceImpl
package com.cdl.spbootpro.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cdl.spbootpro.exception.BusinessException;
import com.cdl.spbootpro.model.User;
import com.cdl.spbootpro.mapper.UserMapper;
import com.cdl.spbootpro.model.dto.UserDto;
import com.cdl.spbootpro.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cdl.spbootpro.utils.JsonResponseBody;
import com.cdl.spbootpro.utils.JsonResponseStatus;
import com.cdl.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;
/**
* <p>
* 用户信息表 服务实现类
* </p>
*
* @author cdl
* @since 2022-11-05
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private UserMapper userMapper;
@Override
public JsonResponseBody userLogin(UserDto userDto, HttpServletRequest req, HttpServletResponse resp) {
//要做的事情:
// 判断mobile和password是否为空 已由JSP303完成(在controller中的UserDto添加@Valid便可解决)
//判断mobile格式是否正确 (自定义验证注解@IsMobile)
//根据用户手机号码查询用户
User user = userMapper.selectOne(new QueryWrapper<User>()
.eq("id", userDto.getMobile()));
// 判断所查询的用户是否存在(校验账号)
if(null==user)
throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);
//前台传递到后台的密码 要经过工具类MD5加密一次才有可能跟数据库密码匹配上
String pwd = MD5Utils.formPassToDbPass(userDto.getPassword(), user.getSalt());
//校验密码
if(!user.getPassword().equals(pwd))
throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);
return new JsonResponseBody<>();
}
}
在login.html中引入md5.js
login.html
<!DOCTYPE html>
<html>
<head>
<#include "common/head.html">
<link rel="stylesheet" type="text/css" href="css/public.css"/>
<link rel="stylesheet" type="text/css" href="css/login.css"/>
<script type="text/javascript" src="js/jquery-1.12.4.min.js"></script>
<script type="text/javascript" src="js/md5.js"></script>
<script type="text/javascript" src="js/login/login.js"></script>
</head>
<body>
<!-------------------login-------------------------->
<div class="login">
<form action="#" method="post">
<h1><a href="${ctx}/"><img src="img/temp/logo.png"></a></h1>
<p></p>
<div class="msg-warn hide"><b></b>公共场所不建议自动登录,以防账号丢失</div>
<p><input style="font-size:14px;" type="text" id="mobile" value="" placeholder="邮箱/手机号"></p>
<p><input style="font-size:14px;" type="password" id="password" value="" placeholder="密码"></p>
<p><input type="button" id="login" value="登 录"></p>
<p class="txt"><a class="" href="${ctx}/page/reg.html">免费注册</a><a href="${ctx}/page/forget.html">忘记密码?</a></p>
</form>
</div>
</body>
</html>
加密成功
六、服务端客户端登录密码管理
登录令牌管理
将登录的用户数据分别保留在客户端以及服务端
UserServiceImpl.java变更如下
UserServiceImpl
package com.cdl.spbootpro.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cdl.spbootpro.exception.BusinessException;
import com.cdl.spbootpro.model.User;
import com.cdl.spbootpro.mapper.UserMapper;
import com.cdl.spbootpro.model.dto.UserDto;
import com.cdl.spbootpro.service.IRedisService;
import com.cdl.spbootpro.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cdl.spbootpro.utils.CookieUtils;
import com.cdl.spbootpro.utils.JsonResponseBody;
import com.cdl.spbootpro.utils.JsonResponseStatus;
import com.cdl.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 java.util.UUID;
/**
* <p>
* 用户信息表 服务实现类
* </p>
*
* @author cdl
* @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 userLogin(UserDto userDto, HttpServletRequest req, HttpServletResponse resp) {
//要做的事情:
// 判断mobile和password是否为空 已由JSP303完成(在controller中的UserDto添加@Valid便可解决)
//判断mobile格式是否正确 (自定义验证注解@IsMobile)
//根据用户手机号码查询用户
User user = userMapper.selectOne(new QueryWrapper<User>()
.eq("id", userDto.getMobile()));
// 判断所查询的用户是否存在(校验账号)
if(null==user)
throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);
//前台传递到后台的密码 要经过工具类MD5加密一次才有可能跟数据库密码匹配上
String pwd = MD5Utils.formPassToDbPass(userDto.getPassword(), user.getSalt());
//校验密码
if(!user.getPassword().equals(pwd))
throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);
//将登陆用户对象与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
package com.cdl.spbootpro.service;
import com.cdl.spbootpro.model.User;
/**
* @author cdl
* @site www.cdl.com
* @create 2022-11-06 0:43
*/
public interface IRedisService {
//存贮数据
void setUserToRedis(String token, User user);
User getUserToRedis(String token);
}
IRedisServiceImpl
package com.cdl.spbootpro.service.impl;
import com.cdl.spbootpro.model.User;
import com.cdl.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 cdl
* @site www.cdl.com
* @create 2022-11-06 0:45
*/
@Service
public class IRedisServiceImpl 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);//进reids
}
@Override
public User getUserToRedis(String token) {
User user = (User) redisTemplate.opsForValue().get("user:" + token);
return user;
}
}
运行
用的工具类
utils下
CookieUtils
package com.cdl.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<T>
package com.cdl.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<T>
package com.cdl.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.cdl.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.cdl.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(4)+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.cdl.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.cdl.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.cdl.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.cdl.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();
}
}