使用Maven聚合项目进行创建(一个maven的父项目多个maven的子项目),
可以在父项目pom.xml文件中加上:
<package>pom<package>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NA1kXE9E-1633446686620)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210812151308862.png)]
<packaging>pom<packaing>
pom.xml加上
<packaging>jarpackaging>
<dependencies>
<dependency>
<groupId>org.examplegroupId>
<artifactId>beansartifactId>
<version>2.0.1version>
dependency>
dependencies>
<dependency>
<groupId>org.examplegroupId>
<artifactId>mapperartifactId>
<version>2.0.1version>
dependency>
<dependency>
<groupId>org.examplegroupId>
<artifactId>commonartifactId>
<version>2.0.1version>
dependency>
总的说父项目的所以依赖可以被子项目引用,子项目也可以单独的添加所需的依赖
提出问题
可行性分析(技术(一般可以相关人员实现),成本,法律法规)
概要设计
详细设计
编码
测试
交付/部署实施
根据功能分析出数据库实体(javaBean)
提取实体属性
spu商品(id,商品名称,商品图片,商品描述…)
1 min10 … …
sku(skuId, 参数 , 价格 商品id
101 内存8G\存储128G 2999 1
102 内存12G\存储256G 3999 1
地址(姓名,地址,电话…)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P8zP9MYA-1633446686624)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210814172548189.png)]
可以知道价格的依赖于参数的改变而改变,参数依赖于id改变,不满足数据库设计表的要求,可以设计两张表进行实现。
可视化创建数据库表(数据表)
视图显示表之间的关系(关系图)
导出sql指令(模型—导出DDL脚本)
记录数据库模型版本管理
可以连接数据库直接生成表
spu(Standard Product Unit):商品信息聚合的最小 单位。通俗讲属性值,特性相同的商品可以称为一个SPU.
产品: 荣耀8 小米10
sku(Stock Keeping Unit)最小存货单元,定义为保存最小库存的控制最小可用单元
sku 荣耀8 8G/128G 10
sku 荣耀8 4G/124G 20
注意一下 :订单表的设计功能:因为订单表只要用户一下订单,订单表的相关信息就不可以进行改变 ,所以需要进行地址的的快照 ,和商品信息的快照,这样就算你临时改变了价格的信息或者其他的也没有关系
购物车的设计:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xztkvKoC-1633446686626)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210814213038578.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AxMTDyIv-1633446686628)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210814215338098.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BGyg8OoE-1633446686629)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210814230313101.png)]
在企业开发中,当完成项目的需求分析,功能分析,数据库分析与设计后,项目组就会按照项目中的功能模块进行开发任务的分配。
每个人会被分配不同的功能
单体架构:页面和控制之间可以进行跳转,同步请求控制器,流程控制由控制器来完成
前后端分离架构:前端和后端开发开发和部署,前端只能通过异步发送请求,后端只负责接收请求及参数,处理请求,返回结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-96PeIJ8O-1633446686630)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210814230815317.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G0wixnwP-1633446686631)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210815101028557.png)]
**前端可以发送如图所示的请求:**需要url,params
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TPnNWduu-1633446686631)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210815102516744.png)]
狭义:的理解:就是控制器中可以接受用户请求的方法
标准定义:API(Application Programming interface)应用程序编程接口,就是软件系统不同组成部分衔接的约定。
作为后端程序员不仅要完成接口程序的开发,还要编写接口的说明文档—接口规范
前后端分离规开发,后端需要编写接口说明文档,会耗费比较多的时间
swagger是一个用于生成服务器接口的的规范性文档,并且能够对接口进行测试的工具。
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
可以在api这个module中进行相关的controller层的测试,建立一个config包下面的SwaggerConfig类进行相关的测试,加上@Configuration,@EnableSwagger2注解,然后进行配置相关的信息
package com.qfedu.fmmall.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.w3c.dom.DocumentType;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
/*
* swagger生成我们的接口文档:
* 1.需要配置生成文档的信息
* 2.配置生成规则
*
* */
@Bean
public Docket docket(){
//创建封面信息对象
ApiInfoBuilder apiInfoBuilder=new ApiInfoBuilder();//指定生成文档中的封面信息:文档标题,作者,版本
apiInfoBuilder.title("《锋迷商城》后端接口说明")
.description("此文档详细说明了锋迷商城项目后端接口规范")
.version("v 2.0.1")
.contact(new Contact("houge","www.houge.com","[email protected]"));
ApiInfo apiInfo=apiInfoBuilder.build();
Docket docket=new Docket(DocumentationType.SWAGGER_2) //指定文档风格
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.qfedu.fmmall.controller"))
// 定义了path之后只会为user开头的请求进行扫描 .paths(PathSelectors.regex("/user/"))
// PathSelectors.any()表示任何的请求
.paths(PathSelectors.any())
.build();
return docket;
}
}
http://localhost:8080/swagger-ui.html
@Api(value=" 用户管理",tags="提供用户的登录和注册的接口")//这个接口可以直接放在@Controller注解下面
@ApiOperation 和ApiImplicitParams({ @ApiImplicitParam(dataType="",name=“username”,value="",required=true), @ApiImplictParm}) 这两个注解放在@RequestMapping("/login")请求之上,用来修饰方法和方法中的参数。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zcmsSUB7-1633446686632)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210815170433850.png)]
@ApiOperation("用户登录的接口")
@ApiImplicitParams({
@ApiImplicitParam(dataType = "string",name = "username",value = "用户登录的账号",required = true),
@ApiImplicitParam(dataType = "string",name = "password",value = "用户登录的密码",defaultValue = "111111",required = false)
})
@RequestMapping("/login")
// @RequestParam可以有默认的参数
public ResultVO login(@RequestParam("username") String name,@RequestParam(value = "password",defaultValue = "111111") String pwd){
return userService.checkLogin(name,pwd);
}
@RequestMapping(value = "regist",metho
@ApiModel 和@ApiModelProperty当接口参数返回一个对象类型时,需要在实体类中添加注解说明(也就是Beans这个Module进行相关的配置)
package com.qfedu.fmmall.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "用户的买家信息",description = "买家的相关的参数")
public class User {
@ApiModelProperty(name = "用户id",required = false,dataType = "int")
private Integer userId;
@ApiModelProperty(dataType = "string",name = "买家姓名",required = true)
private String userName;
@ApiModelProperty(dataType = "string",name = "买家密码",required = true)
private String userPwd;
@ApiModelProperty(dataType = "string",name = "买家真实姓名",required = true)
private String userRealname;
@ApiModelProperty(dataType = "string",name = "用户图片",required = true)
private String userImg;
}
@ApiIgnore 接口方法注解,添加此注解的方法将不会生成到接口文档中
<dependency>
<groupId>com.github.xiaoymingroupId>
<artifactId>swagger-bootstrap-uiartifactId>
<version>1.9.6version>
dependency>
http://ip:port/doc.html
package com.qfedu.fmmall.dao;
import com.qfedu.fmmall.entity.User;
import org.springframework.stereotype.Repository;
@Repository
public interface UserDao {
// 用户注册
public int insert(User user);
// 根据用户名进行登录的验证
public User queryByName(String name);
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qfedu.fmmall.dao.UserDao">
<resultMap id="userResultMap" type="User">
<id column="user_id" property="userId">id>
<result column="username" property="userName"/>
<result column="password" property="password"/>
<result column="nickname" property="nickname"/>
<result column="realname" property="realname"/>
<result column="user_img" property="userImg"/>
<result column="user_mobile " property="userMobile"/>
<result column=" user_email" property="userEmail"/>
<result column="user_sex " property="userSex">result>
<result column=" user_birth" property="userBirth">result>
<result column="user_regtime " property="userRegtime">result>
<result column="user_modtime " property="userModtime">result>
resultMap>
<select id="queryByName" resultType="User">
select *from users where username=#{username}
select>
<insert id="insert" parameterType="User">
insert into users(username,password,user_regtime,user_modtime) values (#{username},
#{password},#{userRegtime},#{userModtime})
insert>
mapper>
package com.qfedu.fmmall.service;
import com.qfedu.fmmall.entity.User;
import com.qfedu.fmmall.vo.ResultVO;
public interface UserService {
// ResultVO是一个响应给前端的自定义的一个类。
public ResultVO checkLogin(String username, String pwd);
// 用户注册
public ResultVO insert(String username, String pwd);
}
package com.qfedu.fmmall.service.impl;
import com.qfedu.fmmall.service.UserService;
import com.qfedu.fmmall.dao.UserDao;
import com.qfedu.fmmall.entity.User;
import com.qfedu.fmmall.utils.MD5Utils;
import com.qfedu.fmmall.vo.ResultVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
@Service
@Transactional
//使所有的线程都用这个对象,单例模式默认是开启的
@Scope("singleton")
public class UserServiceimpl implements UserService {
@Autowired
private UserDao userDao;//可以在UserDao上面加上userDao,这个不会报红,但是没有什么意义
@Override
public ResultVO checkLogin(String username, String pwd) {
// 查询用户名
User user = userDao.queryByName(username);
if(user==null){
// 用户名不正确
return new ResultVO(10001,"用户名不正确",null);
}else {
//密码使用MD5进行加密
String md5Pwd = MD5Utils.md5(pwd);
if(md5Pwd.equals(user.getPassword())){
// 验证成功
return new ResultVO(200,"登录成功",user);
}else {
//密码不正确
return new ResultVO(10001,"密码错误",null);
}
}
}
@Transactional
@Override
public ResultVO insert(String username, String pwd) {
// 判断这个用户是否被注册
// 加上这个锁可以使用所有的注册都用这个userServiceimpl
synchronized (this){
// 把密码进行MD5的加密
String password = MD5Utils.md5(pwd);
User user1 = userDao.queryByName(username);
//表示用户名没有被注册过,可以进行注册
if (user1==null){
//一个是注册时间,regtime,一个是修改时间modtime
User user=new User(username,password,new Date(),new Date());
int i = userDao.insert(user);
if(i>0){
return new ResultVO(1000,"注册成功",null);
}else {
return new ResultVO(1001,"注册失败",null);
}
}
// 判断一下用户名是否已经被注册,然后把数据返回前端,goodjob,Noone can influence you
else {
return new ResultVO(1001,"用户名已经被注册",null);
}
}
}
}
package com.qfedu.fmmall.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "ResultVO对象",description = "响应封装的数据给前端")
public class ResultVO {
// 响应给前端的状态码
@ApiModelProperty(dataType = "int",value = "响应的状态码")
private int code;
// 响应给前端的提示消息
@ApiModelProperty(dataType = "string",value = "响应的消息")
private String msg;
//响应给前端的数据
@ApiModelProperty(dataType = "object",value = "响应数据的内容")
private Object data;
}
package com.qfedu.fmmall.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "ResultVO对象",description = "响应封装的数据给前端")
public class ResultVO {
// 响应给前端的状态码
@ApiModelProperty(dataType = "int",value = "响应的状态码")
private int code;
// 响应给前端的提示消息
@ApiModelProperty(dataType = "string",value = "响应的消息")
private String msg;
//响应给前端的数据
@ApiModelProperty(dataType = "object",value = "响应数据的内容")
private Object data;
}
package com.qfedu.fmmall.utils;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
//MD5 生成器
public class MD5Utils {
public static String md5(String password){
//生成一个md5加密器
try {
MessageDigest md = MessageDigest.getInstance("MD5");
//计算MD5 的值
md.update(password.getBytes());
//BigInteger 将8位的字符串 转成16位的字符串 得到的字符串形式是哈希码值
//BigInteger(参数1,参数2) 参数1 是 1为正数 0为0 -1为负数
return new BigInteger(1, md.digest()).toString(16);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}
根据创建好的表,生成实体类,和DAO层、映射文件
在Dependencies下面加入依赖,这个依赖是一个Mybatis的maven插件
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-maven-pluginartifactId>
<version>1.3.5version>
<configuration>
<configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xmlconfigurationFile>
configuration>
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.46version>
dependency>
<dependency>
<groupId>tk.mybatisgroupId>
<artifactId>mapperartifactId>
<version>4.1.5version>
dependency>
dependencies>
plugin>
plugins>
build>
在resources的generator目录下面创建generatorConfig.xml
需要修改数据库的配置
需要修改pojo,mapper,Mapper.xml文件生成位置的配置
<table tableName="%">table>
<plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<property name="mappers" value="com.hou.general.GeneralDao"/>
plugin>
指定你的用Configuration generatorConfig.xml文件的路径
注意一下你的文件一定想要有空格什么东西的
DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="MysqlContext" targetRuntime="MyBatis3Simple" >
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<property name="mappers" value="com.qfedu.fmmall.general.GeneralDao"/>
plugin>
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
commentGenerator>
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/fmmall?characterEncoding=utf8"
userId="root"
password="roothouzhicong">
jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
javaTypeResolver>
<javaModelGenerator targetPackage="com.qfedu.fmmall.entity" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
javaModelGenerator>
<sqlMapGenerator targetPackage="/" targetProject="src/main/resources/mappers"/>
<javaClientGenerator targetPackage="com.qfedu.fmmall.dao" targetProject="src/main/java"
type="XMLMAPPER"/>
<table tableName="%">table>
context>
generatorConfiguration>
加上了这个:
<configuration>
<configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xmlconfigurationFile>
configuration>
<plugin>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-maven-pluginartifactId>
<version>1.3.5version>
<configuration>
<configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xmlconfigurationFile>
configuration>
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.46version>
dependency>
<dependency>
<groupId>tk.mybatisgroupId>
<artifactId>mapperartifactId>
<version>4.1.5version>
dependency>
dependencies>
plugin>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NkWuyiKh-1633446686633)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210817151030750.png)]
解决方案:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CmoCSbXN-1633446686634)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210817151350682.png)]
后端解决办法:在UserController加上@CrossOrigin注解
前端通过Ajax请求跨域登录:
<form>
<div class="user-name" style="margin-top: 20px;">
<label for="user"><span class="glyphicon glyphicon-user" aria-hidden="true">span>label>
<input type="text" name="username" id="userName" placeholder="邮箱/手机/用户名">
div>
<div class="user-pass" style="margin-top: 20px;">
<label for="password"><span class="glyphicon glyphicon-lock" aria-hidden="true">span>label>
<input type="password" name="" id="userPwd" placeholder="请输入密码">
div>
form>
<input type="button" name="" id="submitBtn" value="登 录" class="am-btn am-btn-primary am-btn-sm">
<script src="static/js/jquery-1.7.2.min.js">script>
<script type="text/javascript">
$("#submitBtn").click(function(){
var name=$("#userName").val();
var pwd=$('#userPwd').val();
$.get("http://localhost:8080/user/login",{
username:name,
password:pwd,
},function(res){
console.log(res);
},"json"
)
})
script>
前端使用JSONP设置,后端使用@CrossOrigin注解解决—设置响应头header允许跨域。
debugger;前端 可以加上代码debugger进行相关的调试。可以直接进行前端的校验
cookie和localstorage可以进行前端的页面之间的传值
Cookie浏览器端的缓存文件:大小受浏览器的限制。
LocalStorage:为了存储更大容量的数据
区别:cookie可以和后台进行传值,localStorage只可以在前端存储值,但是存储的时间长。
var opertor="=";
function getCookieValue(keyStr){
var s=window.document.cookie;
var arr=s.split("; ");
for(var i=0;i
A页面设置值:
function(res){
console.log(res);
if(res.code==1000){
// 获取前端传过来的数据 data
var userInfo=res.data;
// cookie和localstorage可以进行前端的页面之间的传值
setCookieValue("username",userInfo.username);
setCookieValue("userImg",userInfo.userImg);
window.location.href="index.html";
}else{
$("#tips").html("<label style='color:red'>"+ res.msg +"label>");
}
B页面取值:
var name=getCookieValue("username");
var userImg=getCookieValue("userImg");
console.log(name+userImg);
A页面:
localStorage.setItem("user",JSON.stringify(userInfo));
B页面:
var jsonStr=localStorage.getItem("user");
// 把json串转换为对象
var userInfo=eval("("+jsonStr+")");
// 把取到的值消失
localStorage.removeItem("user");
console.log(userInfo);
data:{
username:"",
password:"",
tips:" ",
colorStyle:"",
isRight:false,
},
methods:{
doSubmit:function() {
// 校验成功
if(vm.isRight){
var url=baseUrl+"/user/login";
axios.get(url,{
params:{
username:vm.username,password:vm.password
} }
).then((res)=>{
console.log(res);
var vo=res.data;
if(vo.code==1){
window.location.href="index.html";
}else{
vm.tips="账号或者密码错误";
}
});
}else{
vm.tips="请输入正确的用户名和密码";
vm.colorStyle="color:red"
}
// 1.进行数据的校验
if(vm.username==" "){
vm.tips="请输入用户名";
vm.colorStyle="color:red";
}
},
checkInfo:function(){
if(vm.username==""){
vm.tips="请输入用户名";
this.colorStyle="color:red";
vm.isRight=false;
}else if(vm.username.length<6 ||vm.username.length>20){
vm.tips="账号长度必须为6-20";
vm.colorStyle="color:red";
vm.isRight=false;
}else{
// 校验密码
if(vm.password==" "){
vm.tips="请输入密码";
this.colorStyle="color:red";
vm.isRight=false;
}else if(vm.password.length<6 ||vm.password.length>16){
vm.tips="密码长度为6-16";
this.colorStyle="color:red";
}else{
vm.tips=" ";
vm.isRight=true;
}
}
}
}
from表单(用@keyup进行表单的输入的绑定):
<form>
<div class="user-name" style="margin-top: 20px;">
<label for="user"><span class="glyphicon glyphicon-user" aria-hidden="true">span>label>
<input type="text" name="username" v-model="username" id="userName" @keyup="checkInfo" placeholder="邮箱/手机/用户名">
div>
<div class="user-pass" style="margin-top: 20px;">
<label for="password"><span class="glyphicon glyphicon-lock" aria-hidden="true">span>label>
<input type="password" name="" v-model="password" id="userPwd" placeholder="请输入密码"@keyup="checkInfo">
div>
form>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DWoelSut-1633446686634)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210823080556194.png)]
可以知道每台服务器中存在多个Session,只是id不相同,Cookie中可以存放sessionId,然后判断是是不是同一个session
在单体项目中用户怎么认证的?
在单体项目中视图资源和控制器都在同一台服务器,用户的多次请求老师基于同一个会话,可以基于session进行会话的验证:
- 用户登录将信息存放在session中
- 根据session中是否有用户信息来判断用户是否可以进行登录。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w4niCSzY-1633446686635)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210823082009032.png)]
可以知道使用token实现用户验证,token存在于cookie中(同一台服务器可以访问cookie),然后验证token是否正确
在commons模块中引入
package com.qfedu.fmmall.utils;
import java.util.Base64;
//base64 加密 解密 激活邮件的时候 为 邮箱地址 code验证码 进行加密
//当 回传回来后 进行邮箱地址 和 code 的解密
public class Base64Utils {
//加密
public static String encode(String msg){
return Base64.getEncoder().encodeToString(msg.getBytes());
}
//解密
public static String decode(String msg){
return new String(Base64.getDecoder().decode(msg));
}
}
登录成功生成token:UserController
package com.qfedu.fmmall.controller;
import com.qfedu.fmmall.entity.Users;
import com.qfedu.fmmall.service.UserService;
import com.qfedu.fmmall.vo.ResultVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/*@Controller
@ResponseBody*/
@RestController
@RequestMapping("/user")
@CrossOrigin
@Api(value = "提供用户的登录和注册的接口",tags = "用户管理")
public class UserController {
@Autowired
private UserService userService;
// @ApiIgnore加上这个注解会swagger忽略这个方法
@ApiOperation("用户登录的接口")
@ApiImplicitParams({
@ApiImplicitParam(dataType = "string",name = "username",value = "用户登录的账号",required = true),
@ApiImplicitParam(dataType = "string",name = "password",value = "用户登录的密码",required = true)
})
@RequestMapping("/login")
// @RequestParam可以有默认的参数
public ResultVO login(@RequestParam("username") String name,@RequestParam(value = "password") String pwd){
return userService.checkLogin(name,pwd);
}
@ApiOperation(value = "用户注册")
@PostMapping("/regist")
@ApiImplicitParams({
@ApiImplicitParam(dataType = "string",name = "username",value = "用户注册的账号",required = true),
@ApiImplicitParam(dataType = "string",name = "password",value = "用户注册的密码",required = true)
})
// 前端用user传值,后端可以用users 接收
public ResultVO register(@RequestBody Users users){
return userService.insert(users.getUsername(),users.getPassword());
}
}
然后在UserServiceimpl中进行token的加密:
// 如果登录成功,则需要生成令牌token(token就是按照规则生成的字符串)
String token= Base64Util.encode(username+“roothouzhicong”);
package com.qfedu.fmmall.service.impl;
import com.qfedu.fmmall.dao.UsersMapper;
import com.qfedu.fmmall.entity.Users;
import com.qfedu.fmmall.service.UserService;
import com.qfedu.fmmall.utils.MD5Utils;
import com.qfedu.fmmall.vo.ResultStatus;
import com.qfedu.fmmall.vo.ResultVO;
import org.apache.logging.log4j.util.Base64Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;
import java.util.Date;
import java.util.List;
@Service
@Transactional
//使所有的线程都用这个对象,单例模式默认是开启的
@Scope("singleton")
public class UserServiceimpl implements UserService {
@Autowired
private UsersMapper userDao;//可以在UserDao上面加上userDao,这个不会报红,但是没有什么意义
@Override
public ResultVO checkLogin(String username, String pwd) {
// 查询用户名
Example example = new Example(Users.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("username",username);
List<Users> users = userDao.selectByExample(example);
//
if(users.size()==0){
// 用户名不正确
return new ResultVO(10001,"用户名不正确",null);
}else {
//密码使用MD5进行加密
String md5Pwd = MD5Utils.md5(pwd);
System.out.println(users.get(0).getPassword());
if(md5Pwd.equals(users.get(0).getPassword())){
// 如果登录成功,则需要生成令牌token(token就是按照规则生成的字符串)
String token= Base64Util.encode(username+"roothouzhicong");
// 验证成功
return new ResultVO(ResultStatus.OK,token,users.get(0));
}else {
//密码不正确
return new ResultVO(ResultStatus.NO,"密码错误",null);
}
}
}
@Transactional
@Override
public ResultVO insert(String username, String pwd) {
// 判断这个用户是否被注册
// 加上这个锁可以使用所有的注册都用这个userServiceimpl
synchronized (this){
// 把密码进行MD5的加密
String password = MD5Utils.md5(pwd);
// 查询用户名
Example example = new Example(Users.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("username",username);
List<Users> users = userDao.selectByExample(example);
//表示用户名没有被注册过,可以进行注册
if (users.size()==0){
//一个是注册时间,regtime,一个是修改时间modtime
Users user=new Users(username,password,new Date(),new Date());
int i = userDao.insert(user);
if(i>0){
return new ResultVO(ResultStatus.OK,"注册成功",null);
}else {
return new ResultVO(ResultStatus.NO,"注册失败",null);
}
}
// 判断一下用户名是否已经被注册,然后把数据返回前端,goodjob,Noone can influence you
else {
return new ResultVO(ResultStatus.NO,"用户名已经被注册",null);
}
}
}
}
前端设置token:
doSubmit:function() {
// 校验成功
if(vm.isRight){
var url=baseUrl+"/user/login";
axios.get(url,{
params:{
username:vm.username,password:vm.password
} }
).then((res)=>{
console.log(res);
var vo=res.data;
console.log(vo);
if(vo.code==1){
// 如果登录成功就把token存储到时cookie中
setCookieValue("token",vo.msg);
window.location.href="index.html";
}else{
vm.tips="账号或者密码错误";
}
});
前端的购物车获取token:
<script type="text/javascript">
// 进入购物车时要访问购物车列表的接口shopController接口
var baseUrl="http://localhost:8080/";
var vm=new Vue({
el:"#app",
data:{
token:"",
},
created() {
this.token=getCookieValue("token");
console.log("token="+this.token);
axios({
method:"get",
url:baseUrl+"shopcart/list",
params:{
token:this.token,
}
}).then(function (res) {
console.log(res);
});
},
})
script>
登录进行来可以把购物车获取token,前端的token用CookieUtils.js封装的包进行相关的获取,
package com.qfedu.fmmall.controller;
import com.qfedu.fmmall.utils.Base64Utils;
import com.qfedu.fmmall.vo.ResultStatus;
import com.qfedu.fmmall.vo.ResultVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@CrossOrigin
@Api(value = "提供购物车业务相关的接口",tags = "购物车管理接口")
@RequestMapping("/shopcart")
public class ShopCartController {
@RequestMapping("/list")
@ApiImplicitParam(dataType = "string",name = "token",value = "登录的一个标志",required = true)
public ResultVO shopcartList(String token){
// 校验输入的token看看是否是用户自己登录的token
//解密
String decode = Base64Utils.decode(token);
if(token==null){
return new ResultVO(ResultStatus.NO, "请先登录", null);
}else if(decode.endsWith("roothouzhicong")) {
System.out.println("购物车列表相关的接口------------");
return new ResultVO(ResultStatus.OK, "success", null);
}else {
return new ResultVO(ResultStatus.NO, "登录已经过期,请重新登录!!", null);
}
}
}
- 用自定义的token生成的时效性不可以进行定义
- 安全性较差
JWT结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LzVJMRUZ-1633446686636)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210823140849801.png)]
添加依赖:
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>3.10.3version>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
UserServiceimpl(登录成功)生成token:
HashMap<String,Object> map=new HashMap<>();
JwtBuilder builder= Jwts.builder();
String token = builder.setSubject(username) //主题就是token中携带的数据
.setIssuedAt(new Date()) //设置token的生成时间
.setId(users.get(0).getUserId() + "") //设置用户的id为tokenid
.setClaims(map) //map中可以存放用户的角色权限信息
.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)) //设置token的过期时间
.signWith(SignatureAlgorithm.HS256, "houzhicong") //设置加密的方式
.compact();
// 验证成功
前端ShopCart.html通过Cookie获取生成的token:
<script type="text/javascript">
// 进入购物车时要访问购物车列表的接口shopController接口
var baseUrl="http://localhost:8080/";
var vm=new Vue({
el:"#app",
data:{
token:"",
},
created() {
this.token=getCookieValue("token");
console.log("token="+this.token);
axios({
method:"get",
url:baseUrl+"shopcart/list",
Headers:{
token:this.token,
}
}).then(function (res) {
console.log(res);
});
},
})
script>
后端ShopController进行解析Token:
package com.qfedu.fmmall.controller;
import com.qfedu.fmmall.utils.Base64Utils;
import com.qfedu.fmmall.vo.ResultStatus;
import com.qfedu.fmmall.vo.ResultVO;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@CrossOrigin
@Api(value = "提供购物车业务相关的接口",tags = "购物车管理接口")
@RequestMapping("/shopcart")
public class ShopCartController {
@RequestMapping("/list")
@ApiImplicitParam(dataType = "string",name = "token",value = "登录的一个标志",required = true)
public ResultVO shopcartList(String token){
// 校验输入的token看看是否是用户自己登录的token
// String decode = Base64Utils.decode(token);
if(token==null){
return new ResultVO(ResultStatus.NO, "请先登录", null);
}else {
JwtParser parser= Jwts.parser();
parser.setSigningKey("houzhicong");//解析token 必须和生成token时候生成的密码一致
// 如果token正确(密码正确,有效期内) 则正常执行,否则抛出异常
try{
Jws<Claims> claimsJws=parser.parseClaimsJws(token);
Claims body=claimsJws.getBody();//获取token中的用户数据
String subject=body.getSubject();//获取生成token设置subject
String v1=body.get("key1",String.class);//获取生成token时存储的Claims的map中的值
return new ResultVO(ResultStatus.OK, "success", null);
}catch (Exception e){
return new ResultVO(ResultStatus.NO, "登录已经过期,请重新登录!!", null);
}
}
}
}
6.3.1有
package com.qfedu.fmmall.config;
import com.qfedu.fmmall.interceptor.CheckTokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private CheckTokenInterceptor checkTokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CheckTokenInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/user/**"
,"/doc.html"
,"/swagger-ui/**");
}
}
前端但凡访问受限资源,都必须携带token请求,token可以通过请求行(params),请求头(header),以及请求体(data)传递,但习惯使用header传递
axios({
method:"get",
url:baseUrl+"shopcart/list",
Headers:{
token:this.token,
}
}).then(function (res) {
console.log(res);
});
},
package com.qfedu.fmmall.interceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qfedu.fmmall.vo.ResultStatus;
import com.qfedu.fmmall.vo.ResultVO;
import io.jsonwebtoken.*;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Configuration
public class CheckTokenInterceptor implements HandlerInterceptor {
// 打ov 可以看到它的方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getParameter("token");
// System.out.println("token----------");
// 前端会发送预险性请求
String method = request.getMethod();
if("options".equalsIgnoreCase(method)){
return true;
}
if(token==null){
// 提示用户进行登录
PrintWriter out = response.getWriter();
ResultVO resultVO= new ResultVO(ResultStatus.NO, "请先登录", null);
// 抽出一个方法进行
doResponse(response,resultVO);
// 拦截
return false;
}else {
// 验证token
try{
JwtParser parser= Jwts.parser();
parser.setSigningKey("houzhicong");
Jws<Claims> claimsJws=parser.parseClaimsJws(token);
return true;
}catch (ExpiredJwtException e){
ResultVO resultVO= new ResultVO(ResultStatus.NO, "登录过期,请重新登录", null);
doResponse(response,resultVO);
return false;
}
catch (UnsupportedJwtException e){
ResultVO resultVO= new ResultVO(ResultStatus.NO, "Token不合法,请自重", null);
doResponse(response,resultVO);
return false;
}
catch (Exception e){
ResultVO resultVO= new ResultVO(ResultStatus.NO, "请先登录", null);
doResponse(response,resultVO);
return false;
}
}
}
private void doResponse(HttpServletResponse response, ResultVO resultVO) throws IOException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
// 写上Json格式的resultVO
String s = new ObjectMapper().writeValueAsString(resultVO);
out.println(s);
out.flush();
out.close();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wQm4qlh3-1633446686637)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210823182623779.png)]
得出结论:数据量较少的情况,使用一次性查询,数据量较多使用多次查询
方案一:一次性查询三级分类
- 优点只需一查询
- 缺点:数据库查询效率较低,页面首次加载的速度较慢
方案二:
- 先只查询一级分类,用户点击/鼠标移动到一级分类,动态加载二级分类
- 缺点:需要多次连接数据库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WFClkTnv-1633446686638)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210823204928346.png)]
一次性查询出来的sql语句:inner join 和left join 的区别 left join 左边没有关联的数据也会全部显示
select
c1.category_id 'category_id1',
c1.category_name 'category_name',
c1.category_level 'category_level',
c1.parent_id 'parent_id',
c1.category_icon 'category_icon',
c1.category_slogan 'category_slogan',
c1.category_pic 'category_pic',
c1.category_bg_color 'category_bg_color',
c2.category_id 'category_id2',
c2.category_name 'category_name2',
c2.category_level 'category_leve2',
c2.parent_id 'parent_id2',
c3.category_id 'category_id3',
c3.category_name 'category_name3',
c3.category_level 'category_leve3',
c3.parent_id 'parent_id3'
from category c1
left join category c2 on c2.parent_id=c1.category_id
left join category c3 on c3.parent_id=c2.category_id
where c1.category_level=1
select *from category c1
inner join category c2 on c2.parent_id=c1.category_id
left join category c3 on c3.parent_id=c2.category_id
where c1.category_level=1
--根据父级分类的parent_id进行查询 1 级 parent_id=0
select *from category where parent_id=0,
创建用于封装查询的类别信息CategoryVO
在Beans模块中entity包下面创建一个CategoryVO实体类用于封装Category和前端 进行数据的响应,相对于Category多了这个属性
//实体类中实现当前分类的子分类
private List<CategoryVO> categories;
public List<CategoryVO> getCategories() {
return categories;
}
public void setCategories(List<CategoryVO> categories) {
this.categories = categories;
}
在CategoryMapper中定义一个函数
package com.qfedu.fmmall.dao;
import com.qfedu.fmmall.entity.Category;
import com.qfedu.fmmall.entity.CategoryVO;
import com.qfedu.fmmall.general.GeneralDao;
import java.util.List;
public interface CategoryMapper extends GeneralDao<Category> {
//使用连接查询实现分类列表查询
public List<CategoryVO> selectAllCategories();
// 子查询
public List<CategoryVO> selectAllCategories2(int parentId);
}
映射文件配置
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qfedu.fmmall.dao.CategoryMapper">
<resultMap id="BaseResultMap" type="com.qfedu.fmmall.entity.Category">
<id column="category_id" jdbcType="VARCHAR" property="categoryId" />
<result column="category_name" jdbcType="VARCHAR" property="categoryName" />
<result column="category_level" jdbcType="INTEGER" property="categoryLevel" />
<result column="parent_id" jdbcType="INTEGER" property="parentId" />
<result column="category_icon" jdbcType="VARCHAR" property="categoryIcon" />
<result column="category_slogan" jdbcType="VARCHAR" property="categorySlogan" />
<result column="category_bg_color" jdbcType="VARCHAR" property="categoryBgColor" />
resultMap>
<resultMap id="CategoryVoMap" type="com.qfedu.fmmall.entity.CategoryVO">
<id column="category_id1" jdbcType="VARCHAR" property="categoryId" />
<result column="category_name1" jdbcType="VARCHAR" property="categoryName" />
<result column="category_level1" jdbcType="INTEGER" property="categoryLevel" />
<result column="parent_id1" jdbcType="INTEGER" property="parentId" />
<result column="category_icon1" jdbcType="VARCHAR" property="categoryIcon" />
<result column="category_slogan1" jdbcType="VARCHAR" property="categorySlogan" />
<result column="category_bg_color1" jdbcType="VARCHAR" property="categoryBgColor" />
<collection property="categories" ofType="com.qfedu.fmmall.entity.CategoryVO">
<id column="category_id2" jdbcType="VARCHAR" property="categoryId" />
<result column="category_name2" jdbcType="VARCHAR" property="categoryName" />
<result column="category_level2" jdbcType="INTEGER" property="categoryLevel" />
<result column="parent_id2" jdbcType="INTEGER" property="parentId" />
<collection property="categories" ofType="com.qfedu.fmmall.entity.CategoryVO">
<id column="category_id3" jdbcType="VARCHAR" property="categoryId" />
<result column="category_name3" jdbcType="VARCHAR" property="categoryName" />
<result column="category_level3" jdbcType="INTEGER" property="categoryLevel" />
<result column="parent_id3" jdbcType="INTEGER" property="parentId" />
collection>
collection>
resultMap>
<select id="selectAllCategories" resultMap="CategoryVoMap">
select
c1.category_id 'category_id1',
c1.category_name 'category_name',
c1.category_level 'category_level',
c1.parent_id 'parent_id',
c1.category_icon 'category_icon',
c1.category_slogan 'category_slogan',
c1.category_pic 'category_pic',
c1.category_bg_color 'category_bg_color',
c2.category_id 'category_id2',
c2.category_name 'category_name2',
c2.category_level 'category_leve2',
c2.parent_id 'parent_id2',
c3.category_id 'category_id3',
c3.category_name 'category_name3',
c3.category_level 'category_leve3',
c3.parent_id 'parent_id3'
from category c1
left join category c2 on c2.parent_id=c1.category_id
left join category c3 on c3.parent_id=c2.category_id
where c1.category_level=1
select>
使用子查询实现分类列表的查询:
```xml
CategoryService
package com.qfedu.fmmall.service;
import com.qfedu.fmmall.vo.ResultVO;
public interface CategoryService {
public ResultVO queryAllCategory();
}
CategoryServiceimpl:
package com.qfedu.fmmall.service.impl;
import com.qfedu.fmmall.dao.CategoryMapper;
import com.qfedu.fmmall.entity.CategoryVO;
import com.qfedu.fmmall.service.CategoryService;
import com.qfedu.fmmall.vo.ResultStatus;
import com.qfedu.fmmall.vo.ResultVO;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class CategoryServiceimpl implements CategoryService {
@Resource
private CategoryMapper categoryMapper;
@Override
public ResultVO queryAllCategory() {
List<CategoryVO> categoryVOS = categoryMapper.selectAllCategories2(0);
return new ResultVO(ResultStatus.OK,"success",categoryVOS);
}
}
indexController实现:
@Autowired
private CategoryService categoryService;
@RequestMapping("category-list")
@ApiOperation("商品分类查询接口")
public ResultVO queryAllCategory(){
return categoryService.queryAllCategory();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yol4wuqR-1633446686638)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210828192719883.png)]
推荐最新上架的商品
sql语句实现
-- 商品推荐查询最新上架信息
select *from product order by create_time desc limit 0,3;
-- 商品图片查询 根据商品id查询商品图片
select *from product_img where item_id=2;
在子工程beans工程下面创建ProductVO (加上了这个属性 private List imgs;因为一个商品包含多张表)
package com.qfedu.fmmall.entity;
import javax.persistence.Column;
import javax.persistence.Id;
import java.util.Date;
import java.util.List;
public class ProductVO {
/**
* 商品id
*/
@Id
@Column(name = "product_id")
private Integer productId;
/**
* 商品名称
*/
@Column(name = "product_name")
private String productName;
/**
* 商品分类id
*/
@Column(name = "category_id")
private Integer categoryId;
/**
* 一级分类外键id
*/
@Column(name = "root_category_id")
private Integer rootCategoryId;
/**
* 销量
*/
@Column(name = "sold_num")
private Integer soldNum;
/**
* 商品状态
*/
@Column(name = "product_status")
private Integer productStatus;
/**
* 商品内容
*/
private String content;
/**
* 创建时间
*/
@Column(name = "create_time")
private Date createTime;
/**
* 更新时间
*/
@Column(name = "update_time")
private Date updateTime;
private List<ProductImg> imgs;
public List<ProductImg> getImgs() {
return imgs;
}
public void setImgs(List<ProductImg> imgs) {
this.imgs = imgs;
}
@Override
public String toString() {
return "ProductVO{" +
"imgs=" + imgs +
'}';
}
/**
* 获取商品id
*
* @return product_id - 商品id
*/
public Integer getProductId() {
return productId;
}
/**
* 设置商品id
*
* @param productId 商品id
*/
public void setProductId(Integer productId) {
this.productId = productId;
}
/**
* 获取商品名称
*
* @return product_name - 商品名称
*/
public String getProductName() {
return productName;
}
/**
* 设置商品名称
*
* @param productName 商品名称
*/
public void setProductName(String productName) {
this.productName = productName == null ? null : productName.trim();
}
/**
* 获取商品分类id
*
* @return category_id - 商品分类id
*/
public Integer getCategoryId() {
return categoryId;
}
/**
* 设置商品分类id
*
* @param categoryId 商品分类id
*/
public void setCategoryId(Integer categoryId) {
this.categoryId = categoryId;
}
/**
* 获取一级分类外键id
*
* @return root_category_id - 一级分类外键id
*/
public Integer getRootCategoryId() {
return rootCategoryId;
}
/**
* 设置一级分类外键id
*
* @param rootCategoryId 一级分类外键id
*/
public void setRootCategoryId(Integer rootCategoryId) {
this.rootCategoryId = rootCategoryId;
}
/**
* 获取销量
*
* @return sold_num - 销量
*/
public Integer getSoldNum() {
return soldNum;
}
/**
* 设置销量
*
* @param soldNum 销量
*/
public void setSoldNum(Integer soldNum) {
this.soldNum = soldNum;
}
/**
* 获取商品状态
*
* @return product_status - 商品状态
*/
public Integer getProductStatus() {
return productStatus;
}
/**
* 设置商品状态
*
* @param productStatus 商品状态
*/
public void setProductStatus(Integer productStatus) {
this.productStatus = productStatus;
}
/**
* 获取商品内容
*
* @return content - 商品内容
*/
public String getContent() {
return content;
}
/**
* 设置商品内容
*
* @param content 商品内容
*/
public void setContent(String content) {
this.content = content == null ? null : content.trim();
}
/**
* 获取创建时间
*
* @return create_time - 创建时间
*/
public Date getCreateTime() {
return createTime;
}
/**
* 设置创建时间
*
* @param createTime 创建时间
*/
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
/**
* 获取更新时间
*
* @return update_time - 更新时间
*/
public Date getUpdateTime() {
return updateTime;
}
/**
* 设置更新时间
*
* @param updateTime 更新时间
*/
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
}
package com.qfedu.fmmall.dao;
import com.qfedu.fmmall.entity.Product;
import com.qfedu.fmmall.entity.ProductVO;
import com.qfedu.fmmall.general.GeneralDao;
import java.util.List;
public interface ProductMapper extends GeneralDao<Product> {
// 查询推荐商品信息
public List<ProductVO> selectRecommendProducts();
}
package com.qfedu.fmmall.dao;
import com.qfedu.fmmall.entity.ProductImg;
import com.qfedu.fmmall.general.GeneralDao;
import java.util.List;
public interface ProductImgMapper extends GeneralDao<ProductImg> {
public List<ProductImg> selectProductImgByProductId(int productId);
}
注意一下子查询 的语句:
<collection property="imgs" column="product_id"
select="com.qfedu.fmmall.dao.ProductImgMapper.selectProductImgByProductId">collection>
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qfedu.fmmall.dao.ProductMapper">
<resultMap id="BaseResultMap" type="com.qfedu.fmmall.entity.Product">
<id column="product_id" jdbcType="INTEGER" property="productId" />
<result column="product_name" jdbcType="VARCHAR" property="productName" />
<result column="category_id" jdbcType="INTEGER" property="categoryId" />
<result column="root_category_id" jdbcType="INTEGER" property="rootCategoryId" />
<result column="sold_num" jdbcType="INTEGER" property="soldNum" />
<result column="product_status" jdbcType="INTEGER" property="productStatus" />
<result column="content" jdbcType="VARCHAR" property="content" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
resultMap>
<resultMap id="ProductVoMap" type="com.qfedu.fmmall.entity.ProductVO">
<id column="product_id" jdbcType="INTEGER" property="productId" />
<result column="product_name" jdbcType="VARCHAR" property="productName" />
<result column="category_id" jdbcType="INTEGER" property="categoryId" />
<result column="root_category_id" jdbcType="INTEGER" property="rootCategoryId" />
<result column="sold_num" jdbcType="INTEGER" property="soldNum" />
<result column="product_status" jdbcType="INTEGER" property="productStatus" />
<result column="content" jdbcType="VARCHAR" property="content" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
<collection property="imgs" column="product_id"
select="com.qfedu.fmmall.dao.ProductImgMapper.selectProductImgByProductId">collection>
resultMap>
<select id="selectRecommendProducts" resultMap="ProductVoMap">
select
product_id,
product_name,
category_id,
root_category_id,
sold_num,
product_status,
content,
create_time,
update_time
from product order by create_time desc limit 0,3;
select>
mapper>
package com.qfedu.fmmall.service;
import com.qfedu.fmmall.entity.ProductVO;
import com.qfedu.fmmall.vo.ResultVO;
import java.util.List;
public interface ProductService {
public ResultVO selectRecommendProducts();
}
@ApiOperation("商品推荐查询信息接口")
@RequestMapping(value = "/list-recommends",method = RequestMethod.GET)
public ResultVO selectProductRecommend(){
ResultVO resultVO = productService.selectRecommendProducts();
return resultVO;
}
点击商品推荐,商品轮播图,商品的列表页面点击商品,就会进入到商品的详情页面。
商品基本信息(product),商品套餐(sku),商品图片(product_img)
SQL
-- 根据商品id查询商品详情
select *from product where product_id=3;
-- 根据商品id查询商品图片详情
select *from product_img where item_id=3;
-- 根据商品id查询当前商品的套餐
select *from product_sku where product_id=3;
可以用子查询实现这个相关的操作
dao接口实现(通过product,product_img,product_sku三张表获取商品的详情信息)
@Repository
public interface ProductMapper extends GeneralDao<Product> {
// 查询推荐商品信息
public List<ProductVO> selectRecommendProducts();
}
package com.qfedu.fmmall.dao;
import com.qfedu.fmmall.entity.ProductImg;
import com.qfedu.fmmall.general.GeneralDao;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ProductImgMapper extends GeneralDao<ProductImg> {
public List<ProductImg> selectProductImgByProductId(int productId);
}
package com.qfedu.fmmall.dao;
import com.qfedu.fmmall.entity.ProductSku;
import com.qfedu.fmmall.general.GeneralDao;
import org.springframework.stereotype.Repository;
@Repository
public interface ProductSkuMapper extends GeneralDao<ProductSku> {
}
业务层实现
//这里为不需要事务,但是如果某些事务如果调用了我也加入到事务中来
// 事务默认的隔离级别是可重复读 repeateable read
@Transactional(propagation = Propagation.SUPPORTS)
public ResultVO selectProductBasicInfo(String productId) {
// 1.查询商品的基本信息
Example example = new Example(Product.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("productId",productId);
criteria.andEqualTo("productStatus",1);
List<Product> products = productMapper.selectByExample(example);
// System.out.println(products);
if(products.size()>0){
// 2.查询商品的图片信息
Example example1 = new Example(ProductImg.class);
Example.Criteria criteria1 = example1.createCriteria();
criteria1.andEqualTo("itmeId",productId);
List<ProductImg> productImgs = productImgMapperMapper.selectByExample(example1);
// System.out.println(productImgs);
// 3.查询商品的套餐信息
Example example2 = new Example(ProductSku.class);
Example.Criteria criteria2 = example2.createCriteria();
criteria2.andEqualTo("productId",productId);
criteria2.andEqualTo("status",1);
List<ProductSku> productSkus = productSkuMapper.selectByExample(example2);
// System.out.println(productSkus);
// 把所有的商品的详情信息放入HashMap当中进行使用
HashMap<String,Object> basicInfo=new HashMap<>();
basicInfo.put("product",products.get(0));
basicInfo.put("productImgs",productImgs);
basicInfo.put("productSkus",productSkus);
return new ResultVO(ResultStatus.OK,"success",basicInfo);
}else {
new ResultVO(ResultStatus.NO,"查询商品基本信息失败",null);
}
return null;
}
// 商品详情查询
@RequestMapping(value = "/detail/{pid}",method = RequestMethod.GET)
public ResultVO getProductBasicInfo(@PathVariable("pid") String productId){
ResultVO resultVO = productService.selectProductBasicInfo(productId);
// System.out.println(resultVO);
return resultVO;
}
-- 根据评论的id查询评论信息,关联用户表查询用户信息
select u.username,u.user_img,c.* from product_comments c
inner join users u
on c.user_id=u.user_id
where c.product_id=3
package com.qfedu.fmmall.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Column;
import javax.persistence.Table;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProductCommentsVO {
private Integer productId;
private String productName;
private Integer orderItemId;
private String isannonymouns;
private Integer commType;
private Integer commLevel;
private String commImgs;
private String sepcName;
private Integer replyStatus;
private String replyContent;
private Date replyTime;
private Integer isShow;
//用于封装评论对应的用户数据
private Integer userId;
private String username;
private String nickname;
private String userImg;
}
在Mapper定义相应的接口:
package com.qfedu.fmmall.dao;
import com.qfedu.fmmall.entity.ProductComments;
import com.qfedu.fmmall.entity.ProductCommentsVO;
import com.qfedu.fmmall.general.GeneralDao;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ProductCommentsMapper extends GeneralDao<ProductComments> {
public List<ProductCommentsVO> selectCommentsByProductId(int productId);
}
点击添加购物车---------商品、选择套餐id,套餐属性,数量,token-------------进行token的校验
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m6DHVLUt-1633446686639)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210917194652463.png)]
增加字段sku_props
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Aok2Tjfo-1633446686640)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210917195750634.png)]
shopping_cart | |
---|---|
表生成之后 用逆向工程重新生成shopping_cart表的相关的结构。修改generalConfig.xml,把%修改成shopping_cart
注意一下**%表示生成所有的表**
changeNum:function(m){
if(m==-1 && this.num>1){
this.num=this.num-1;
}else if(m==1 && this.num
进行商品数量的绑定可以用 v-model="num"进行双向绑定
<dd>
<input id="min" class="am-btn am-btn-default" type="button" value="-" @click="changeNum(-1)"/>
<input id="text_box" type="text" v-model="num" style="width:30px;" />
<input id="add" class="am-btn am-btn-default" type="button" value="+" @click="changeNum(1)" />
<span id="stock1" class="tb-hidden">库存<span class="stock">{
{productSkus[currentSkuIndex].stock}}span>件span>
dd>
<li>
<div class="clearfix tb-btn tb-btn-basket theme-login">
<a id="LikBasket" title="加入购物车" href="" @click="addShopCart()"><i>i>加入购物车a>
div>
li>
把相关的添加的购物车的信息放入cart这个类中:
addShopCart(){
var uid=getCookieValue("userId");
var propStr="";
// 套餐属性转换成字符串
for(var key in this.chooseskuProps){
propStr+=key+":"+this.chooseskuProps[key]+";";
}
var cart={
"cartNum": this.num,
"cartTime": "",
"productId": this.productId,
"productPrice": this.productSkus[this.currentSkuIndex].sellPrice,
"skuId": this.productSkus.skuId,
"skuProps":propStr,
"userId": uid
};
//从cookie中获取token
var token=getCookieValue("token");
console.log("---token-------");
console.log(token);
// 把购物车的信息放入数据库中
var url5=baesUrl+"shopcart/add";
axios.post(
{
url:url5,
methods:"post",
headers:{
token:token
},
data:cart
}
).then( res=>{
console.log("------res-----"+res);
}
);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YQQLuRrZ-1633446686641)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210920173405055.png)]
引入lay-ui cdn
<link rel="stylesheet" href="//unpkg.com/[email protected]/dist/css/layui.css">
<script src="//unpkg.com/[email protected]/dist/layui.js">
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hxrSswP2-1633446686642)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210922163219931.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MH4X9lzj-1633446686642)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210922163850890.png)]
流程图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NmuB7noP-1633446686643)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210922164854113.png)]
步骤
sql语句
select c.*,p.product_name,i.url from shopping_cart c
inner join product p
inner join product_img i
on c.product_id=p.product_id
and i.item_id=p.product_id
where user_id=2 and i.is_main=1;
2. dao接口
```java
List selectShoppingCartByUserId(int userid);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HvWCzaRC-1633446686643)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210922172106911.png)]
注意一下数据库的shopping_cart表不需要加上这两个字段,是通过product_img表和product表的column属性来进行关联的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a4sYcBBB-1633446686644)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210924171559447.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GNtRjXEM-1633446686644)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210924204100420.png)]
methods:{
changeNum(event){
var oper=event.srcElement.dataset.oper;
var index=event.srcElement.dataset.id;
console.log(oper);
if(oper=="+"){
this.shoppingCartsSC[index].cartNum=parseInt(this.shoppingCartsSC[index].cartNum) +1;
}else{
if(this.shoppingCartsSC[index].cartNum>1){
this.shoppingCartsSC[index].cartNum=parseInt(this.shoppingCartsSC[index].cartNum)-1;
}
}
// 修改的cartId和cartNum
var cartId=this.shoppingCartsSC[index].cartId;
var cartNum=this.shoppingCartsSC[index].cartNum;
var url1=baseUrl+"shopcart/update/"+cartId+"/"+cartNum;
axios({
url:url1,
method:"put",
params:{
token:this.token
}
}).then((res)=>{
console.log(res);
})
},
// addNum:function(event){
// // 这个可以打印绑定的id的值
// console.log("--add"+event.srcElement.dataset.id);
// var index=event.srcElement.dataset.id;
// this.shoppingCartsSC[index].cartNum=parseInt(this.shoppingCartsSC[index].cartNum) +1;
// },
// mulNum:function(event){
// var index=event.srcElement.dataset.id;
// if(this.shoppingCartsSC[index].cartNum>1){
// this.shoppingCartsSC[index].cartNum=parseInt(this.shoppingCartsSC[index].cartNum)-1;
// }
// }
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r7eZLbEB-1633446686645)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210924214722021.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v5zo96Td-1633446686646)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210926112259576.png)]
package com.qfedu.fmmall.service.impl;
import com.qfedu.fmmall.dao.OrdersItemMapper;
import com.qfedu.fmmall.dao.OrdersMapper;
import com.qfedu.fmmall.dao.ProductSkuMapper;
import com.qfedu.fmmall.dao.ShoppingCartMapper;
import com.qfedu.fmmall.entity.Orders;
import com.qfedu.fmmall.entity.OrdersItem;
import com.qfedu.fmmall.entity.ProductSku;
import com.qfedu.fmmall.entity.ShoppingCartVO;
import com.qfedu.fmmall.service.OrderService;
import com.qfedu.fmmall.vo.ResultStatus;
import com.qfedu.fmmall.vo.ResultVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.SQLException;
import java.util.*;
import static java.math.BigDecimal.*;
@Service
public class OrderServiceimpl implements OrderService{
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Autowired
private OrdersMapper ordersMapper;
@Autowired
private OrdersItemMapper ordersItemMapper;
@Autowired
private ProductSkuMapper productSkuMapper;
/* userId 1(zhangsan) 3(houzhicong)
*cids "39,40,41"
* @return
*
* */
// int userId, String receiverName,
// String receiverMobile,String address,
// double price,int payType,String orderRemark 把这些用Orders对象来接收
// 保存订单的步骤
// 1.查询选中购买的购物车详情
// 2. 校验库存
// 3.保存订单
// 4.保存订单快照
// 5.购买成功需要删除购物车记录
// 可以知道这四个步骤需要同时成功或者同时失败,符合一个事务的操作(ACID)
@Transactional
public Map<String,String> addOrder(List<Integer> cids, Orders orders) throws SQLException{
Map<String,String> map=new HashMap<>();
// 根据cids查询购物车的详情记录(包括库存)
List<ShoppingCartVO> shoppingCartVOList = shoppingCartMapper.selectShoppingcartByids(cids);
// 校验库存
boolean f=true;
String untitled="";
for (ShoppingCartVO sc :shoppingCartVOList
) {
if(Integer.valueOf(sc.getCartNum())>sc.getStock()){
f=false;
}
// 获取所有的商品名称,以,分割拼接成字符串
untitled=untitled+sc.getProductName()+",";
}
if(f){
// 表示库存充足进行保存
// 1.userId 2 untitled名称 3 收件人地址,姓名,电话,地址
// 4. 总价格 5.支付方式
// 6.创建 订单的时间
// 7.订单初始状态 1 待支付
orders.setStatus(1);
orders.setUntitled(untitled);
orders.setCreateTime(new Date());
orders.setCancelTime(new Date());
orders.setDeliveryTime(new Date());
// 生成订单编号
String orderId = UUID.randomUUID().toString().replace("-", "");
orders.setOrderId(orderId);
// 保存订单
int i=ordersMapper.insert(orders);
if(i>0){
// ordersItem 生成商品快照
// List ordersItemList=new ArrayList<>();
for (ShoppingCartVO sc :shoppingCartVOList) {
// 生成订单的编号
int cnum=Integer.valueOf(sc.getCartNum());
String itemid=System.currentTimeMillis()+(new Random().nextInt(9999)+100)+"";
String itemid1 = itemid.substring(1, 10);
// 注意一下double需要转换为Bigdecimal类型
// public OrdersItem(Integer orderId, Integer productId,
// String productName,
// String productImg, Integer skuId, String skuName,
// BigDecimal productPrice, Integer buyCounts,
// BigDecimal totalAmount, Date basketDate, Date buyTime,
// Integer isComment)
int itemid2=Integer.parseInt(itemid1);
OrdersItem ordersItem= new OrdersItem();
ordersItem.setOrderId(itemid2);
ordersItem.setProductId(Integer.valueOf(sc.getProductId()));
ordersItem.setProductName(sc.getProductName());
ordersItem.setProductImg(sc.getProductImg());
ordersItem.setSkuId(Integer.valueOf(sc.getSkuId()));
System.out.println(sc.getSkuName());
ordersItem.setSkuName(sc.getSkuName());
System.out.println(sc.getSellPrice());
ordersItem.setProductPrice(new BigDecimal(String.valueOf(sc.getProductPrice())));
ordersItem.setBuyCounts(cnum);
ordersItem.setTotalAmount(sc.getProductPrice());
ordersItem.setBasketDate(new Date());
ordersItem.setBuyTime(new Date());
ordersItem.setIsComment(0);
// ordersItemList.add(ordersItem);
int m=ordersItemMapper.insert(ordersItem);
}
// int j = ordersItemMapper.insertList(ordersItemList);
// 扣减库存???
// 根据套餐Id修改库存量
for (ShoppingCartVO sc :shoppingCartVOList
) {
String skuId = sc.getSkuId();
int newStock=sc.getStock()-Integer.valueOf(sc.getCartNum());
// Example example = new Example(ProductSku.class);
// Example.Criteria criteria = example.createCriteria();
// criteria.andEqualTo("skuId",skuId);
// ProductSku productSku = productSkuMapper.selectByPrimaryKey(skuId);
ProductSku productSku=new ProductSku();
productSku.setSkuId(skuId);
productSku.setStock(String.valueOf(newStock));
// productSku.setSkuImg(null);
productSkuMapper.updateByPrimaryKeySelective(productSku);
}
// 保存订单成功 删除购物车记录
for (Integer cid:cids
) {
shoppingCartMapper.deleteByPrimaryKey(cid);
}
map.put("orderId",orderId);
map.put("productNames",untitled);
return map;
}
}else{
// 不足
return null;
}
return null;
}
}
For input String :""
在application.yml加上日志的配置:
logging:
level:
io.swagger.models.parameters.AbstractSerializableParameter: error
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cKOFAXHr-1633446686646)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210929154617257.png)]