目录

一、SpringBoot项目简介

1、技术点介绍

2、数据表介绍

二、构建SpringBoot项目

1.创建SpringBoot项目并配置POM

2.配置application.yml

3.启动类配置

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

三、实现首页功能

0)导入帮助类

config:

exception:

Generator:

Utils:

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

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

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

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

四、用户明文登录

创建UserController类实现用户登录

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

1.2)创建UserController类

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

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

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

1.6)全局异常处理

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

login.js:

测试多种情况:

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

UserServiceImpl.java变更如下

login.js变更如下:

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

UserServiceImpl.java变更如下


一、SpringBoot项目简介

1、技术点介绍

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

2、数据表介绍

用户表:t_user

商品表:t_goods

订单表:t_order

订单项表:t_order_item

数据源表:t_dict_type

数据项表:t_dict_data

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

二、构建SpringBoot项目

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

1.创建SpringBoot项目并配置POM

pom依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.9.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.ycx</groupId><artifactId>spbootpro</artifactId><version>0.0.1-SNAPSHOT</version><name>spbootpro</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><!--freemarker--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency><!--spring web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope><version>5.1.44</version></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--junit--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></dependency><!-- mybatis plus依赖 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.0</version></dependency><!-- mybatis-plus-generator依赖 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.4.0</version></dependency><!--hariki--><dependency><groupId>com.zaxxer</groupId><artifactId>HikariCP</artifactId></dependency><!-- MD5依赖 --><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.6</version></dependency><!-- valid验证依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--commons-pool2 对象池依赖 2.0版本的lettuce需要--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><!--spring-session将session借助于第三方存储(redis/mongodb等等),默认redis--><!--<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency>--><dependency><groupId>com.alipay.sdk</groupId><artifactId>alipay-easysdk</artifactId><version>2.0.1</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

2.配置application.yml

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

yml文件:

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

3.启动类配置

package com.ycx.spbootpro;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;@MapperScan({"com.ycx.spbootpro.mapper"})
@EnableTransactionManagement
@SpringBootApplication
public class SpbootproApplication {public static void main(String[] args) {SpringApplication.run(SpbootproApplication.class, args);}}

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

IndexController :

package com.ycx.spbootpro.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;/*** @author 杨总* @create 2022-11-04 21:02*/
@Controller
public class IndexController {@RequestMapping("/")public String index(){
//        前缀+逻辑视图名+后缀return "index.html";}
}

运行访问:

三、实现首页功能

0)导入帮助类

config:

RedisConfig :

package com.ycx.spbootpro.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();redisTemplate.setStringSerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.setConnectionFactory(connectionFactory);redisTemplate.afterPropertiesSet();return redisTemplate;}
}

exception:

BusinessException :

package com.ycx.spbootpro.exception;import com.ycx.spbootpro.utils.JsonResponseStatus;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class BusinessException extends RuntimeException {private JsonResponseStatus jsonResponseStatus;
}

GlobalExceptionHandler :

package com.ycx.spbootpro.exception;import com.ycx.spbootpro.utils.JsonResponseBody;
import com.ycx.spbootpro.utils.JsonResponseStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandlerpublic JsonResponseBody<?> exceptionHandler(Exception e){JsonResponseBody<?> jsonResponseBody=null;e.printStackTrace();if(e instanceof BusinessException){BusinessException ex= (BusinessException) e;jsonResponseBody=new JsonResponseBody<>(ex.getJsonResponseStatus());}else if(e instanceof BindException){BindException ex= (BindException) e;String msg = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage();jsonResponseBody=new JsonResponseBody<>(JsonResponseStatus.BIND_ERROR);jsonResponseBody.setMsg(msg);}else{System.out.println("aaaaaa");}return jsonResponseBody;}
}

Generator:

CodeGenerator :

package com.ycx.spbootpro.generator;import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;public class CodeGenerator {/*** <p>* 读取控制台内容* </p>*/public static String scanner(String tip) {Scanner scanner = new Scanner(System.in);StringBuilder help = new StringBuilder();help.append("请输入" + tip + ":");System.out.println(help.toString());if (scanner.hasNext()) {String ipt = scanner.next();if (StringUtils.isNotBlank(ipt)) {return ipt;}}throw new MybatisPlusException("请输入正确的" + tip + "!");}public static void main(String[] args) {// 代码生成器AutoGenerator mpg = new AutoGenerator();// 全局配置GlobalConfig gc = new GlobalConfig();String projectPath = System.getProperty("user.dir") + "/spbootpro";gc.setOutputDir(projectPath + "/src/main/java");gc.setAuthor("yangzong");gc.setOpen(false);gc.setBaseColumnList(true);gc.setBaseResultMap(true);// gc.setSwagger2(true); 实体属性 Swagger2 注解mpg.setGlobalConfig(gc);// 数据源配置DataSourceConfig dsc = new DataSourceConfig();dsc.setUrl("jdbc:mysql://localhost:3306/spbootpro?useUnicode=true&useSSL=false&characterEncoding=utf8");// dsc.setSchemaName("public");dsc.setDriverName("com.mysql.jdbc.Driver");dsc.setUsername("root");dsc.setPassword("1234");mpg.setDataSource(dsc);// 包配置PackageConfig pc = new PackageConfig();//pc.setModuleName(scanner("模块名"));pc.setParent("com.ycx.spbootpro");//设置包名pc.setEntity("model");mpg.setPackageInfo(pc);// 自定义配置InjectionConfig cfg = new InjectionConfig() {@Overridepublic void initMap() {// to do nothing}};// 如果模板引擎是 freemarkerString templatePath = "/templates/mybatis-generator/mapper2.xml.ftl";// 如果模板引擎是 velocity// String templatePath = "/templates/mapper.xml.vm";// 自定义输出配置List<FileOutConfig> focList = new ArrayList<>();// 自定义配置会被优先输出focList.add(new FileOutConfig(templatePath) {@Overridepublic String outputFile(TableInfo tableInfo) {// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;}});/*cfg.setFileCreate(new IFileCreate() {@Overridepublic boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {// 判断自定义文件夹是否需要创建checkDir("调用默认方法创建的目录,自定义目录用");if (fileType == FileType.MAPPER) {// 已经生成 mapper 文件判断存在,不想重新生成返回 falsereturn !new File(filePath).exists();}// 允许生成模板文件return true;}});*/cfg.setFileOutConfigList(focList);mpg.setCfg(cfg);// 配置模板TemplateConfig templateConfig = new TemplateConfig();// 配置自定义输出模板//指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别templateConfig.setMapper("templates/mybatis-generator/mapper2.java");templateConfig.setEntity("templates/mybatis-generator/entity2.java");templateConfig.setService("templates/mybatis-generator/service2.java");templateConfig.setServiceImpl("templates/mybatis-generator/serviceImpl2.java");templateConfig.setController("templates/mybatis-generator/controller2.java");templateConfig.setXml(null);mpg.setTemplate(templateConfig);// 策略配置StrategyConfig strategy = new StrategyConfig();strategy.setNaming(NamingStrategy.underline_to_camel);strategy.setColumnNaming(NamingStrategy.underline_to_camel);//strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");strategy.setEntityLombokModel(true);strategy.setRestControllerStyle(true);strategy.setEntitySerialVersionUID(false);// 公共父类//strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");// 写于父类中的公共字段strategy.setSuperEntityColumns("id");strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));strategy.setControllerMappingHyphenStyle(true);strategy.setTablePrefix("t_");mpg.setStrategy(strategy);mpg.setTemplateEngine(new FreemarkerTemplateEngine());mpg.execute();}
}

Utils:

CookieUtils :

package com.ycx.spbootpro.utils;import lombok.extern.slf4j.Slf4j;import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;@Slf4j
public class CookieUtils {/**** @Description: 得到Cookie的值, 不编码* @param request* @param cookieName* @return*/public static String getCookieValue(HttpServletRequest request, String cookieName) {return getCookieValue(request, cookieName, false);}/**** @Description: 得到Cookie的值* @param request* @param cookieName* @param isDecoder* @return*/public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {Cookie[] cookieList = request.getCookies();if (cookieList == null || cookieName == null) {return null;}String retValue = null;try {for (int i = 0; i < cookieList.length; i++) {if (cookieList[i].getName().equals(cookieName)) {if (isDecoder) {retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");} else {retValue = cookieList[i].getValue();}break;}}} catch (UnsupportedEncodingException e) {e.printStackTrace();}return retValue;}/**** @Description: 得到Cookie的值* @param request* @param cookieName* @param encodeString* @return*/public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {Cookie[] cookieList = request.getCookies();if (cookieList == null || cookieName == null) {return null;}String retValue = null;try {for (int i = 0; i < cookieList.length; i++) {if (cookieList[i].getName().equals(cookieName)) {retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);break;}}} catch (UnsupportedEncodingException e) {e.printStackTrace();}return retValue;}/**** @Description: 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码* @param request* @param response* @param cookieName* @param cookieValue*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue) {setCookie(request, response, cookieName, cookieValue, -1);}/**** @Description: 设置Cookie的值 在指定时间内生效,但不编码* @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, int cookieMaxage) {setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);}/**** @Description: 设置Cookie的值 不设置生效时间,但编码* 在服务器被创建,返回给客户端,并且保存客户端* 如果设置了SETMAXAGE(int seconds),会把cookie保存在客户端的硬盘中* 如果没有设置,会默认把cookie保存在浏览器的内存中* 一旦设置setPath():只能通过设置的路径才能获取到当前的cookie信息* @param request* @param response* @param cookieName* @param cookieValue* @param isEncode*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, boolean isEncode) {setCookie(request, response, cookieName, cookieValue, -1, isEncode);}/**** @Description: 设置Cookie的值 在指定时间内生效, 编码参数* @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage* @param isEncode*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, int cookieMaxage, boolean isEncode) {doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);}/**** @Description: 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)* @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage* @param encodeString*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, int cookieMaxage, String encodeString) {doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);}/**** @Description: 删除Cookie带cookie域名* @param request* @param response* @param cookieName*/public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,String cookieName) {doSetCookie(request, response, cookieName, null, -1, false);}/**** @Description: 设置Cookie的值,并使其在指定时间内生效* @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage cookie生效的最大秒数* @param isEncode*/private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {try {if (cookieValue == null) {cookieValue = "";} else if (isEncode) {cookieValue = URLEncoder.encode(cookieValue, "utf-8");}Cookie cookie = new Cookie(cookieName, cookieValue);if (cookieMaxage > 0)cookie.setMaxAge(cookieMaxage);if (null != request) {// 设置域名的cookieString 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) {// 设置域名的cookieString 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.cndomainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];} else if (len <= 3 && len > 1) {// xxx.com or xxx.cndomainName = "." + 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){//判断是否是一个IPboolean b = false;IP = trimSpaces(IP);if(IP.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){String s[] = IP.split("\\.");if(Integer.parseInt(s[0])<255)if(Integer.parseInt(s[1])<255)if(Integer.parseInt(s[2])<255)if(Integer.parseInt(s[3])<255)b = true;}return b;}
}

DataUtils:

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

JsonResponseBody:

package com.ycx.spbootpro.utils;import lombok.Data;import java.io.Serializable;/*** 响应封装类*/
@Data
public class JsonResponseBody<T> implements Serializable {private String msg="OK";private T data;private Integer code;private Integer total;public JsonResponseBody(){this.data=null;this.code=JsonResponseStatus.SUCCESS.getCode();}public JsonResponseBody(T data){this.data=data;this.code=JsonResponseStatus.SUCCESS.getCode();}public JsonResponseBody(T data,Integer total){this.data=data;this.total=total;this.code=JsonResponseStatus.SUCCESS.getCode();}public JsonResponseBody(JsonResponseStatus jsonResponseStatus){this.msg=jsonResponseStatus.getMsg();this.code=jsonResponseStatus.getCode();}public JsonResponseBody(JsonResponseStatus jsonResponseStatus,T data){this.data=data;this.msg=jsonResponseStatus.getMsg();this.code=jsonResponseStatus.getCode();}
}

JsonResponseStatus :

package com.ycx.spbootpro.utils;import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;@Getter
@ToString
@AllArgsConstructor
public enum JsonResponseStatus {SUCCESS(200,"OK"),ERROR(500,"内部错误"),USER_LOGIN_ERROR(100101,"用户名或者密码错误"),USER_MOBILE_ERROR(100102,"手机号码格式错误"),USER_PASSWORD_ERROR(100103,"用户密码错误"),USER_USERNAME_ERROR(100104,"账号不存在"),BIND_ERROR(200101,"参数校验异常"),TOKEN_EEROR(200102,"token令牌失效或已过期");private final Integer code;private final String msg;
}

MD5Utils :

package com.ycx.spbootpro.utils;import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Component;import java.util.UUID;/*** MD5加密* 用户端:password=MD5(明文+固定Salt)* 服务端:password=MD5(用户输入+随机Salt)* 用户端MD5加密是为了防止用户密码在网络中明文传输,服务端MD5加密是为了提高密码安全性,双重保险。*/
@Component
public class MD5Utils {//加密盐,与前端一致private static String salt="f1g2h3j4";/*** md5加密* @param src* @return*/public static String md5(String src){return DigestUtils.md5Hex(src);}/*** 获取加密的盐* @return*/public static String createSalt(){return UUID.randomUUID().toString().replace("-","");}/*** 将前端的明文密码通过MD5加密方式加密成后端服务所需密码* 注意:该步骤实际是在前端完成!!!* @param inputPass 明文密码* @return*/public static String inputPassToFormpass(String inputPass){//混淆固定盐salt,安全性更可靠String str=salt.charAt(1)+""+salt.charAt(5)+inputPass+salt.charAt(0)+""+salt.charAt(3);return md5(str);}/*** 将后端密文密码+随机salt生成数据库的密码* @param formPass* @param salt* @return*/public static String formPassToDbPass(String formPass,String salt){//混淆固定盐salt,安全性更可靠String str=salt.charAt(7)+""+salt.charAt(9)+formPass+salt.charAt(1)+""+salt.charAt(5);return md5(str);}/*** 将用户输入的密码转换成数据库的密码* @param inputPass 明文密码* @param salt      盐* @return*/public static String inputPassToDbPass(String inputPass,String salt){String formPass = inputPassToFormpass(inputPass);String dbPass = formPassToDbPass(formPass, salt);return dbPass;}public static void main(String[] args) {//d7aaa28e3b8e6c88352bd5e7c23829f9//5512a78a188b318c074a15f9b056a712String formPass = inputPassToFormpass("123456");System.out.println("前端加密密码:"+formPass);String salt = createSalt();System.out.println("后端加密随机盐:"+salt);String dbPass = formPassToDbPass(formPass, salt);System.out.println("后端加密密码:"+dbPass);System.out.println("-------------------------------------------");String dbPass1 = inputPassToDbPass("123456", salt);System.out.println("最终加密密码:"+dbPass1);}
}

MybatisPlusConfig :

package com.ycx.spbootpro.utils;import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;//Spring boot方式
@EnableTransactionManagement
@Configuration
@MapperScan("com.zking.shoppingpro.service.*.mapper*")
public class MybatisPlusConfig {@Beanpublic PaginationInterceptor paginationInterceptor() {PaginationInterceptor paginationInterceptor = new PaginationInterceptor();// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false// paginationInterceptor.setOverflow(false);// 设置最大单页限制数量,默认 500 条,-1 不受限制// paginationInterceptor.setLimit(500);// 开启 count 的 join 优化,只针对部分 left joinpaginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));return paginationInterceptor;}
}

PageBean :

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

ValidatorUtils :

package com.ycx.spbootpro.utils;import org.apache.commons.lang3.StringUtils;import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** 正则校验工具类* @author 刘开宇*/
public class ValidatorUtils {private static final Pattern mobile_pattern=Pattern.compile("[1]([0-9])[0-9]{9}$");public static boolean isMobile(String mobile){if(StringUtils.isEmpty(mobile))return false;Matcher matcher = mobile_pattern.matcher(mobile);return matcher.matches();
}
}

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

在控制台输入表名:

生成成功:

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

package com.ycx.spbootpro.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;/*** @author 杨总* @create 2022-11-05 17:20*/
@Controller
public class PageController {/*** 直接跳转页面(没有层级文件夹的情况)* 列如:* http://localhost:8081/page/paint.html* http://localhost:8081/page/perfume.html** @return*/@RequestMapping("/page/{page}")public String page(@PathVariable(value = "page") String page) {return page;}/*** 直接跳转页面(存在层级文件夹的情况)** @return*/@RequestMapping("/page/{dir}/{page}")public String dir(@PathVariable(value = "dir") String dir,@PathVariable(value = "page") String page) {return dir + "/" + page;}
}

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

package com.ycx.spbootpro.generator;import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;public class CodeGenerator {/*** <p>* 读取控制台内容* </p>*/public static String scanner(String tip) {Scanner scanner = new Scanner(System.in);StringBuilder help = new StringBuilder();help.append("请输入" + tip + ":");System.out.println(help.toString());if (scanner.hasNext()) {String ipt = scanner.next();if (StringUtils.isNotBlank(ipt)) {return ipt;}}throw new MybatisPlusException("请输入正确的" + tip + "!");}public static void main(String[] args) {// 代码生成器AutoGenerator mpg = new AutoGenerator();// 全局配置GlobalConfig gc = new GlobalConfig();String projectPath = System.getProperty("user.dir") + "/spbootpro";gc.setOutputDir(projectPath + "/src/main/java");gc.setAuthor("yangzong");gc.setOpen(false);gc.setBaseColumnList(true);gc.setBaseResultMap(true);// gc.setSwagger2(true); 实体属性 Swagger2 注解mpg.setGlobalConfig(gc);// 数据源配置DataSourceConfig dsc = new DataSourceConfig();dsc.setUrl("jdbc:mysql://localhost:3306/spbootpro?useUnicode=true&useSSL=false&characterEncoding=utf8");// dsc.setSchemaName("public");dsc.setDriverName("com.mysql.jdbc.Driver");dsc.setUsername("root");dsc.setPassword("1234");mpg.setDataSource(dsc);// 包配置PackageConfig pc = new PackageConfig();//pc.setModuleName(scanner("模块名"));pc.setParent("com.ycx.spbootpro");//设置包名pc.setEntity("model");mpg.setPackageInfo(pc);// 自定义配置InjectionConfig cfg = new InjectionConfig() {@Overridepublic void initMap() {// to do nothing}};// 如果模板引擎是 freemarkerString templatePath = "/templates/mybatis-generator/mapper2.xml.ftl";// 如果模板引擎是 velocity// String templatePath = "/templates/mapper.xml.vm";// 自定义输出配置List<FileOutConfig> focList = new ArrayList<>();// 自定义配置会被优先输出focList.add(new FileOutConfig(templatePath) {@Overridepublic String outputFile(TableInfo tableInfo) {// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;}});/*cfg.setFileCreate(new IFileCreate() {@Overridepublic boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {// 判断自定义文件夹是否需要创建checkDir("调用默认方法创建的目录,自定义目录用");if (fileType == FileType.MAPPER) {// 已经生成 mapper 文件判断存在,不想重新生成返回 falsereturn !new File(filePath).exists();}// 允许生成模板文件return true;}});*/cfg.setFileOutConfigList(focList);mpg.setCfg(cfg);// 配置模板TemplateConfig templateConfig = new TemplateConfig();// 配置自定义输出模板//指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别templateConfig.setMapper("templates/mybatis-generator/mapper2.java");templateConfig.setEntity("templates/mybatis-generator/entity2.java");templateConfig.setService("templates/mybatis-generator/service2.java");templateConfig.setServiceImpl("templates/mybatis-generator/serviceImpl2.java");templateConfig.setController("templates/mybatis-generator/controller2.java");templateConfig.setXml(null);mpg.setTemplate(templateConfig);// 策略配置StrategyConfig strategy = new StrategyConfig();strategy.setNaming(NamingStrategy.underline_to_camel);strategy.setColumnNaming(NamingStrategy.underline_to_camel);//strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");strategy.setEntityLombokModel(true);strategy.setRestControllerStyle(true);strategy.setEntitySerialVersionUID(false);// 公共父类//strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");// 写于父类中的公共字段strategy.setSuperEntityColumns("id");strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));strategy.setControllerMappingHyphenStyle(true);strategy.setTablePrefix("t_");mpg.setStrategy(strategy);mpg.setTemplateEngine(new FreemarkerTemplateEngine());mpg.execute();}
}

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

package com.ycx.spbootpro.controller;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ycx.spbootpro.model.Goods;
import com.ycx.spbootpro.service.IGoodsService;
import com.ycx.spbootpro.utils.DataUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.List;
import java.util.Map;/*** @author 杨总* @create 2022-11-04 21:02*/
@Controller
public class IndexController {@Autowiredprivate IGoodsService goodsService;@RequestMapping("/")public String index(Model model){//  摆件花艺List<Goods> goods01 = goodsService.list(new QueryWrapper<Goods>().eq("goods_type", "01").last("limit 6"));//  壁挂北欧List<Goods> goods07 = goodsService.list(new QueryWrapper<Goods>().eq("goods_type", "07").last("limit 12"));//        为了方便首页数据显示,方便摆放DataUtils<Goods> dataUtils = new DataUtils<>();Map<String, List<Goods>> gt01 = dataUtils.transfor(3, goods01);Map<String, List<Goods>> gt07 = dataUtils.transfor(4, goods07);model.addAttribute("gt01",gt01);model.addAttribute("gt07",gt07);return "index.html";}}

首页数据绑定语法:

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

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

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

index.html:

<!DOCTYPE html>
<html><head lang="en"><#include "common/head.html" /><link rel="stylesheet" type="text/css" href="css/public.css"/><link rel="stylesheet" type="text/css" href="css/index.css" /></head><div><!------------------------------head------------------------------><#include "common/top.html" /><!-------------------------banner---------------------------><div class="block_home_slider"><div id="home_slider" class="flexslider"><ul class="slides"><li><div class="slide"><img src="img/banner2.jpg"/></div></li><li><div class="slide"><img src="img/banner1.jpg"/></div></li></ul></div></div><!------------------------------thImg------------------------------><div class="thImg"><div class="clearfix"><a href="${ctx}/page/vase_proList.html"><img src="img/i1.jpg"/></a><a href="${ctx}/page/proList.html"><img src="img/i2.jpg"/></a><a href="#2"><img src="img/i3.jpg"/></a></div></div><!------------------------------news------------------------------><div class="news"><div class="wrapper"><h2><img src="img/ih1.jpg"/></h2><div class="top clearfix"><a href="${ctx}/page/proDetail.html"><img src="img/n1.jpg"/><p></p></a><a href="${ctx}/page/proDetail.html"><img src="img/n2.jpg"/><p></p></a><a href="${ctx}/page/proDetail.html"><img src="img/n3.jpg"/><p></p></a></div><div class="bott clearfix"><a href="${ctx}/page/proDetail.html"><img src="img/n4.jpg"/><p></p></a><a href="${ctx}/page/proDetail.html"><img src="img/n5.jpg"/><p></p></a><a href="${ctx}/page/proDetail.html"><img src="img/n6.jpg"/><p></p></a></div><h2><img src="img/ih2.jpg"/></h2><#if gt01?? && gt01?size gt 0><!--遍历gt01中所有的key,是为了该key中的对象--><#list gt01?keys as key><div class="flower clearfix tran"><#list gt01[key] as g><a href="proDetail.html" class="clearfix"><dl><dt><span class="abl"></span><img src="${g.goodsImg}"/><span class="abr"></span></dt><dd>${g.goodsName}</dd><dd><span>¥ ${g.goodsPrice}</span></dd></dl></a></#list></div></#list></#if></div></div><!------------------------------ad------------------------------><a href="#" class="ad"><img src="img/ib1.jpg"/></a><!------------------------------people------------------------------><div class="people"><div class="wrapper"><h2><img src="img/ih3.jpg"/></h2><#if gt07?? && gt07?size gt 0><#list gt07?keys as key><div class="pList clearfix tran"><#list gt07[key] as g><a href="proDetail.html"><dl><dt><span class="abl"></span><img src="${g.goodsImg}"/><span class="abr"></span></dt><dd>${g.goodsName}</dd><dd><span>¥ ${g.goodsPrice}</span></dd></dl></a></#list></div></#list></#if>></div></div><#include "common/footer.html"/><script src="js/public.js" type="text/javascript" charset="utf-8"></script><script src="js/nav.js" type="text/javascript" charset="utf-8"></script><script src="js/jquery.flexslider-min.js" type="text/javascript" charset="utf-8"></script><script type="text/javascript">$(function() {$('#home_slider').flexslider({animation: 'slide',controlNav: true,directionNav: true,animationLoop: true,slideshow: true,slideshowSpeed:2000,useCSS: false});});</script></body>
</html>

运行:

四、用户明文登录

创建UserController类实现用户登录

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

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

package com.ycx.spbootpro.model.dto;import com.ycx.spbootpro.validator.IsMobile;
import lombok.Data;import javax.validation.constraints.NotBlank;@Data
public class UserDto {@NotBlank(message = "手机号码不能为空!")@IsMobileprivate String mobile;@NotBlank(message = "密码不能为空!")private String password;
}

1.2)创建UserController类

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

package com.ycx.spbootpro.controller;import com.ycx.spbootpro.model.dto.UserDto;
import com.ycx.spbootpro.service.IUserService;
import com.ycx.spbootpro.utils.JsonResponseBody;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;/*** <p>* 用户信息表 前端控制器* </p>** @author yangzong* @since 2022-11-05*/
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate IUserService userService;@RequestMapping("/toLogin")public JsonResponseBody toLogin(@Valid UserDto userDto,HttpServletRequest req,HttpServletResponse resp){return userService.toLogin(userDto,req,resp);}
}

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

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

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

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

package com.ycx.spbootpro.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ycx.spbootpro.exception.BusinessException;
import com.ycx.spbootpro.model.User;
import com.ycx.spbootpro.mapper.UserMapper;
import com.ycx.spbootpro.model.dto.UserDto;
import com.ycx.spbootpro.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ycx.spbootpro.utils.JsonResponseBody;
import com.ycx.spbootpro.utils.JsonResponseStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;/*** <p>* 用户信息表 服务实现类* </p>** @author yangzong* @since 2022-11-05*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowiredprivate UserMapper userMapper;@Overridepublic JsonResponseBody toLogin(UserDto userDto, HttpServletRequest req, HttpServletResponse resp) {
//        1.5.1)判断mobile和password是否为空(已由JSP303完成)
//        1.5.2)判断mobile格式是否正确(自定义验证注解)
//        1.5.3)根据用户手机号码查询用户是否存在User user = userMapper.selectOne(new QueryWrapper<User>().eq("id", userDto.getMobile()));//1.5.4)校验账号//判断用户对象是否存在if(null==user)throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);//        1.5.5)校验密码if(!user.getPassword().equals(userDto.getPassword()))throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);return new JsonResponseBody<>();}}

1.6)全局异常处理

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

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

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

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

查看用户表数据

login.js:

$(function () {alert(2);//登录向后台发送Ajax请求$("#login").click(function () {let mobile = $("#mobile").val();let password = $("#password").val();$.post("/user/toLogin",{mobile:mobile,password:password},function (res) {alert(res.msg)},"json");})
});

测试多种情况:

1.手机号为空

2.手机号为非法字符

3.密码为空

4.手机号不存在

5.手机号密码正确

6.密码错误

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

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

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

运行:

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

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

记得在login.html导入:

UserServiceImpl.java变更如下

package com.ycx.spbootpro.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ycx.spbootpro.exception.BusinessException;
import com.ycx.spbootpro.model.User;
import com.ycx.spbootpro.mapper.UserMapper;
import com.ycx.spbootpro.model.dto.UserDto;
import com.ycx.spbootpro.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ycx.spbootpro.utils.JsonResponseBody;
import com.ycx.spbootpro.utils.JsonResponseStatus;
import com.ycx.spbootpro.utils.MD5Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;/*** <p>* 用户信息表 服务实现类* </p>** @author yangzong* @since 2022-11-05*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowiredprivate UserMapper userMapper;@Overridepublic JsonResponseBody toLogin(UserDto userDto, HttpServletRequest req, HttpServletResponse resp) {
//        1.5.1)判断mobile和password是否为空(已由JSP303完成)
//        1.5.2)判断mobile格式是否正确(自定义验证注解)
//        1.5.3)根据用户手机号码查询用户是否存在User user = userMapper.selectOne(new QueryWrapper<User>().eq("id", userDto.getMobile()));//1.5.4)校验账号//判断用户对象是否存在if(null==user)throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);//        前台传递到后台的密码要经过工具类md5加密一次才有可能跟数据库密码匹配上String pwd = MD5Utils.formPassToDbPass(userDto.getPassword(), user.getSalt());//        1.5.5)校验密码if(!pwd.equals(user.getPassword()))throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);return new JsonResponseBody<>();}}

login.js变更如下

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

再次运行:

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

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

UserServiceImpl.java变更如下

package com.ycx.spbootpro.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ycx.spbootpro.exception.BusinessException;
import com.ycx.spbootpro.model.User;
import com.ycx.spbootpro.mapper.UserMapper;
import com.ycx.spbootpro.model.dto.UserDto;
import com.ycx.spbootpro.service.IRedisService;
import com.ycx.spbootpro.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ycx.spbootpro.utils.CookieUtils;
import com.ycx.spbootpro.utils.JsonResponseBody;
import com.ycx.spbootpro.utils.JsonResponseStatus;
import com.ycx.spbootpro.utils.MD5Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.UUID;/*** <p>* 用户信息表 服务实现类* </p>** @author yangzong* @since 2022-11-05*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate IRedisService redisService;@Overridepublic JsonResponseBody toLogin(UserDto userDto, HttpServletRequest req, HttpServletResponse resp) {
//        1.5.1)判断mobile和password是否为空(已由JSP303完成)
//        1.5.2)判断mobile格式是否正确(自定义验证注解)
//        1.5.3)根据用户手机号码查询用户是否存在User user = userMapper.selectOne(new QueryWrapper<User>().eq("id", userDto.getMobile()));//1.5.4)校验账号//判断用户对象是否存在if(null==user)throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);//        前台传递到后台的密码要经过工具类md5加密一次才有可能跟数据库密码匹配上String pwd = MD5Utils.formPassToDbPass(userDto.getPassword(), user.getSalt());//        1.5.5)校验密码if(!pwd.equals(user.getPassword()))throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);//6.将登陆用户对象与token令牌进行绑定保存到cookie和redis//创建登陆令牌tokenString token= UUID.randomUUID().toString().replace("-","");//将token令牌保存到cookie中CookieUtils.setCookie(req,resp,"token",token,7200);//将登陆token令牌与用户对象user绑定到redis中redisService.setUserToRedis(token,user);//将用户登陆的昵称设置到cookie中CookieUtils.setCookie(req,resp,"nickname",user.getNickname());return new JsonResponseBody<>();}}

IRedisService.java:

package com.ycx.spbootpro.service;import com.ycx.spbootpro.model.User;/*** @author 杨总* @create 2022-11-05 20:45*/
public interface IRedisService {void setUserToRedis(String token, User user) ;User getUserByToken(String token);
}

RedisServiceImpl.java:

package com.ycx.spbootpro.service.impl;import com.ycx.spbootpro.model.User;
import com.ycx.spbootpro.service.IRedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;/*** @author 杨总* @create 2022-11-05 20:49*/
@Service
public class RedisServiceImpl implements IRedisService {@Autowiredprivate RedisTemplate<String,Object> redisTemplate;@Overridepublic void setUserToRedis(String token, User user) {redisTemplate.opsForValue().set("user:"+token,user,7200L, TimeUnit.SECONDS);}@Overridepublic User getUserByToken(String token) {return (User) redisTemplate.opsForValue().get("user:"+token);}}

测试如下:

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

SpringBoot电商项目前后端界面搭建相关推荐

  1. Springboot电商项目前后端搭建

    目录 一.SpringBoot项目简介 1.技术点介绍 2.数据表介绍 二.构建SpringBoot项目 1.创建SpringBoot项目并配置POM 2.配置application.yml yml文 ...

  2. (附源码)springboot电商系统前端界面设计与浏览器兼容性研究 毕业设计 231058

    基于springboot电商系统前端界面设计 摘  要 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势:对于电商系统前端界面设计与浏览器兼容性研究当然也不能排除 ...

  3. SpringBoot电商项目之购物车下单(沙箱支付)

    目录 一.购物车结算前端功能实现 二.购物车结算后端功能实现 1.从session中获取购物车对象 2.筛选出要结算的订单项列表集合 3.订单页前台展示 三.结算页的下单前端 生成订单 1.前端相关处 ...

  4. UI实用素材|电商购物类APP界面设计原则!

    移动端的一个特点是可探索性,而且体验感比较强.用户在用手点触.化动过程中就会触发动作,这些动作又会带来相应的反馈,如果在页面一些地方设计上惊喜的反馈方式,会给用户带来很多的乐趣,并且会鼓励用户进行很多 ...

  5. 京东手机电商大数据统计平台搭建

    京东手机电商大数据统计平台搭建 一.虚拟机搭建 1.下载虚拟机 2.解压虚拟机 3.VMware扫描添加虚拟机 4.虚拟机网络说明 5.虚拟机已安装软件和框架说明 二.核心代码 三.数据展示环境搭建 ...

  6. 淘宝电商项目落地,从零开始搭建亿级系统架构笔记

    电商亿级系统架构设计笔记,分为:基础篇.数据库篇.缓存篇.消息队列篇.分布式服务篇.维护篇.实战篇.通过学习这份笔记,你可以系统的学会从零开始搭建亿级系统架构.其中每篇中又有具体的设计实施的笔记供大家 ...

  7. 电商平台-Java后端生成Token架构与设计详解

    目的:Java开源生鲜电商平台-Java后端生成Token目的是为了用于校验客户端,防止重复提交. 技术选型:用开源的JWT架构. 1.概述:在web项目中,服务端和前端经常需要交互数据,有的时候由于 ...

  8. Java项目:网上电商项目(前后端分离+java+vue+Springboot+ssm+mysql+maven+redis)

    源码获取:博客首页 "资源" 里下载! 一.项目简述 本系统功能包括: 一款基于Springboot+Vue的电商项目,前后端分离项目,前台后台都有,前台商品展示购买,购物车分类, ...

  9. 电商平台后台管理系统--环境搭建与管理员登录(一)

    将之前写好的ssm复制,重新命名为myecpbm,然后修改相关文件名和配置,文件目录结构和要修改的项目名如下图一 图一 1.选中改文件,Window==>show view==>Navig ...

最新文章

  1. AI时代人类需要具备的九种软技能,你get了几个?
  2. 经管资源库项目的总结笔记
  3. ABAP计算间隔月份
  4. SSM整合——Spring+SpringMVC+MyBatis整合
  5. 【mysql】安装 mysql 5.7 完成后,默认密码是啥?怎么查看默认密码?
  6. 在windows 2008/2012中配置RADIUS 客户端计算机上网WiFi 认证
  7. java 分布式编译_linux分布式编译distcc和ccache的部署
  8. 国土空间适宜性评价与承载力评价之间的逻辑关系是什么?
  9. SAP License:财务与会计的区别
  10. vue组件通信之父子组件通信
  11. 第 1 章 JVM 和 Java 体系架构
  12. 金融分析师 python_金融分析中的Python环境搭建
  13. Cesium专栏-裁剪效果(基于3dtiles模型,附源码下载)
  14. 对HTML5标签的认识(三)
  15. 与孩子一起学编程07章
  16. 达梦数据库、表字段创建索引或删除索引,增加表字段、修改字段类型或长度、修改注释sql语句
  17. EXCEL中IF函数的嵌套结构以及AND与OR的用法
  18. 花猫c语言,C语言实现小猫钓鱼游戏 - 技术经验 - W3xue
  19. 电池充电语音警报——隐私政策
  20. pythonmsgbox怎么使用_弹出MsgBox后Python是否继续执行代码?

热门文章

  1. Linux下查看显卡型号
  2. 我对移动端架构的思考
  3. 人脸活体检测论文:Multi-Modal Face Anti-Spoofing Based on Central Difference Networks
  4. Javaweb google身份宝验证
  5. [赛后总结]G2022 Regular Contest 02总结
  6. 初中英语语法(005)-时态
  7. android关闭听筒模式,Android开发【06-29视频贴】切换听筒模式部分手机失效,怎么解决?...
  8. 亲测linux系统安装mysql5.7.22
  9. 20190927CF训练
  10. WAP流量变现的几种方式