目录
一、springboot项目简介
二、构建springboot项目
创建SpringBoot项目并配置POM
pom.xml
application.yml
启动类的配置
SpbootproApplication
测试一下是否能访问前端
IndexController
三、首页功能
导入mybatisplus的代码生成器
首页方法改造
IndexController
页面数据展示
index.html
将页面的展示格式改变
index.html
四、用户明文登录
PageController
UserDto
IUserService
UserServiceImpl
五、前端及数据库密码加密
login.js
UserServiceImpl
login.html
六、服务端客户端登录密码管理
UserServiceImpl
IRedisService
IRedisServiceImpl
用的工具类
CookieUtils
DataUtils
JsonResponseBody
JsonResponseStatus
MD5Utils
MybatisPlusConfig
PageBean
ValidatorUtils
一、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();
}
}