微光集市-商品及其商品信息的显示(版本1.0)

文章目录

shopping-project-商品列表显示及查看商品详情

1 前后端环境的搭建

1.1 设及技术

  • springBoot
  • mybatis
  • Redis
  • JWT
  • springSecurity
  • Vue+Element+Axios

1.2 前后端分离

1.2.1 服务端项目

  • myshopping-project-server

    1. 在IDEA创建springboot项目,添加相关依赖包

      在这里插入图片描述

    2. 配置application.yml

      #配置服务器
      server:
        port: 80
        servlet:
          context-path: /
      spring:
        #配置数据源
        datasource:
          url: jdbc:mysql://localhost:3306/myshopping?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: root
          #配置数据库连接池
          druid:
            max-active: 100
            min-idle: 10
            initial-size: 10
            max-wait: 1000
      #配置日志
      logging:
        level:
          root: info
          web: trace
      
      #mybatis配置
      mybatis:
        configuration:
          #日志
       log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
          #映射文件路径
        mapper-locations: /mapper/*Mapper.xml
      
    3. 写一个controller进行测试,运行

      成功

    4. 在核心配置类进行跨域配置

       @Bean
          public WebMvcConfigurer webMvcConfigurer() {
              
              
              return new WebMvcConfigurer() {
              
              
                  /**
                   * 进行跨域配置
                   */
                  @Override
                  public void addCorsMappings(CorsRegistry registry) {
              
              
      
                      registry
                              .addMapping("/**")//当前应用所有资源可被访问
                              .allowedOrigins("http://localhost:8080")//设置允许访问当前应用的项目
                              .allowedMethods("*") //设置允许哪些请求方式进行跨域访问,默认情况只允许简单请求(get,post,head)
                              .allowCredentials(true);//是否允许使用凭证,是否允许使用session后cookie存储凭证信息
                  }
              };
          }
      

1.2.2 客户端项目

  • myshopping-project-client

    扫描二维码关注公众号,回复: 14982745 查看本文章
    1. 使用vue-cli构建项目

      在这里插入图片描述

    2. 配置index.html页面布局

        <style>
          html {
              
              
            width: 100%;
            height: 100%;
          }
      
          body {
              
              
            width: 100%;
            height: 100%;
            margin: 0px;
          }
        </style>
      
    3. App.vue样式

      #app {
              
              
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        width: 100%;
        height: 100%;
        color: #2c3e50;
      }
      
    4. 关闭语法检测

      const {
              
               defineConfig } = require("@vue/cli-service");
      module.exports = defineConfig({
              
              
        transpileDependencies: true,
        lintOnSave:false
      });
      
    5. 测试运行

      成功…

    6. 配置axios

      • npm引入
       cnpm intall axios 
      
      • 在main.js配置 axios
      import axios from "axios";
      //设置axios是Vue的属性
      Vue.prototype.$axios = axios.create({
              
              
        baseURL: 'https://localhost/',//设置axios请求的基础路径
        headers: {
              
               'X-Requested-With': 'XMLHttpRequest' },//指定axios的请求为ajax请求
        withCredentials: true, // 跨域请求是否允许使用凭证
      });
      
      • 测试使用
      <template>
          <div>  
              <button @click="test">测试</button>
          </div>
      </template>
      <script>
      export default{
                
                
           methods:{
                
                
                test(){
                
                
                   this.$axios.get('test/test01')
                              .then(response=>{
                
                
                                  alert(response.data);
                              })
                              .catch(error=>{
                
                
                                  alert(error)
                              });
                },    
           }  
      }
      </script>
      <style scoped>
      </style>
      
    7. 配置ElementUI

      • npm引入

        npm i element-ui -S
        
      • 在mian.js配置ElementUI

        import ElementUI from 'element-ui';
        import 'element-ui/lib/theme-chalk/index.css';
        
        Vue.use(ElementUI);//设置Vue使用Element组件
        
      • 测试

        <el-button type="primary" @click="test">测试</el-button>
        

2 查看商品列表

2.1 服务端

2.1.1 创建商品model(此处仅有图书,后续会加入其他)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book implements Serializable {
    
    
    private Integer book_id;
    private String book_name;
    private Double book_price;
    private String book_image;
    private String book_desc;
    private Integer book_is_display;
}

2.1.2 创建BookController

@RestController
@RequestMapping("/book")
public class BookController {
    
    
    @Resource
    private BookService bookService;
    @RequestMapping("/queryBooks")
    public List<Book> queryBook(){
    
    

        return bookService.queryBook();
    }
}

2.1.3 创建BookServcie

  • BookService接口
public interface BookService {
    
    
    /**
     * 获得所有图书,
     * @param isDisplay 表示是否上架 0表示上架 -1表示下架,没有参数表示所有商品
     * @return
     */
    List<Book> queryBook(Integer... isDisplay);
}
  • BookServcieImpl
@Service
public class BookServiceImp implements BookService {
    
    
    @Resource
    private BookMapper bookMapper;
    @Override
    public List<Book> queryBook(Integer... isDisplay) {
    
    
        return bookMapper.queryBook(isDisplay);
    }
}

2.1.4 创建BookMapper

  • BookMapper接口
@Repository
public interface BookMapper {
    
    
    List<Book> queryBook(Integer... isDisplay);
}

  • BookMapper.Xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shopping.mapper.BookMapper">


    <select id="queryBook" resultType="com.shopping.model.Book">
        select * from myshopping.tbl_book
        <if test="isDisplay!=null and isDisplay.length!=0">
            <where>
                book_is_display=#{isDisplay[0]}
            </where>
        </if>
    </select>
</mapper>

2.1.5 Postman测试接受数据是否成功

成功

在这里插入图片描述

2.2 客户端

2.2.1 接收后端传来的数据及某些数据做出的相应处理

export default {
    
    
    data(){
    
    
        return{
    
    
            booksList:[],//商品数组
            num:1,//购买数量
        }
    },
    methods: {
    
    
        /**
         * 获得商品信息
         */
       queryBooks(){
    
    
        this.$axios
                .get("book/queryBooks")
                .then(response=>{
    
    
                    this.booksList =response.data;
                })
                .catch(err=>{
    
    
                    alert(err)
                })
       },
       /**
        * 截取书名,要求书名必须小于10
        * @param {} bookName 
        */
       subBookName(bookName){
    
    
        if(bookName.length>=10){
    
    
          bookName=bookName.substring(0,10)+"..."; 
        }
        return bookName;
       }
    },
    created(){
    
    
        this.queryBooks();
    }
}

2.2.2 前端模板部分及美化

<template>
    <div class="index_container">
    <el-container>
      <el-header>Header</el-header>
      <el-main>
         <el-row>
            <el-col :span="6" v-for="(book,index) in booksList" :key="book.book_id"  :offset="index==0?0:0" >
                <el-card :body-style="{ padding: '0px'}" style="margin-left: 10px; margin-top: 20px;">
                <img :src="require('@/assets/images/books/'+book.book_image)" class="image">
                <div class="bottom">
                  <div style="display: flex;">
                      <div class="book_desc" >
                        <div>
                          <el-tooltip class="item" effect="dark" :content="book.book_name" placement="top-start">
                              <span>书名:{
   
   { subBookName(book.book_name)}}</span>
                          </el-tooltip>
                        </div>
                        <div><span>单价:{
   
   {book.book_price}}¥</span></div>
                      </div>
                      
                      <div class="book_car" >
                        <i style="color: orange;font-size:40px;" class=el-icon-shopping-cart-full></i>
                      </div>
                  </div>    
                  <div class="button_inputnumber" style="display: flex;">
                    <div style="margin-top: 5px;"><el-input-number size="small" v-model="num" :min="1"></el-input-number></div>                       
                    <div><el-button type="warning" >购买</el-button></div>
                  </div>
                </div>
                </el-card>
            </el-col>
        </el-row>  
      </el-main>
      
      <el-footer>Footer</el-footer>
    </el-container>
    </div>
</template>
<style scoped>
.index_container{
      
      
    margin: 0px auto;
    width: 80%;

}
 .el-header, .el-footer {
      
      
    background-color: #B3C0D1;
    color: #333;
    text-align: center;
    line-height: 60px;
  }
  .el-main {
      
      
    background-color: #E9EEF3;
    color: #333;
  }
  
  .book_desc{
      
      
    font-size: 13px;
    width:70%;
  }

  .book_car{
      
      
    width:30%;
    margin-top: 5px;
  }
  .book_desc div{
      
      
    margin-left: 10px;
    margin-top: 10px;
  }
  .button_inputnumber div{
      
      
   margin: auto;
  }
  .bottom {
      
      
    margin-top: 13px;
    line-height: 12px;
  }
  .image {
      
      
    height: 300px;
    width: 100%;
    display: block;
  }
</style>

3 查看商品详情

3.1 客户端

3.1.1 给商品名路由链接,并配置路由

Vue没有超链接<a herf="">标签不能使用,需使用路由连接<router-link to="">

 <router-link class="normal" to="/bookDesc">书名:{
   
   { subBookName(book.book_name) }}		  </router-link>
  • 配置路由
import BookDesc from "../views/BookDesc.vue"
const routes = [
 //配置图书详情页的路由
  {
    
    
    path: "/bookDesc",
    name: "bookDesc",
    component: BookDesc
  },

];
  • 美化路由链接
.normal{
    
    
  display: block;
  color: black;
  text-decoration: none;
}

.normal:hover{
    
    
  color: grey;
}
.normal:active{
    
    
  color: black;
}

3.1.2 在BookDesc.vue写商品详情页的前端模板及其美化

<template>
    <div class="index_container">
        <el-container>
            <el-header>Header</el-header>
            <el-main>
            <div class="desc-div">
                <h2 align="center">商品详情</h2>
                <div class="goodsDesc">
                    <div class="baseInfo">
                        <div class="photo">
                            <el-image :src="require('@/assets/images/books/' + book.book_image)">
                                <div slot="placeholder" class="image-slot">
                                    加载中<span class="dot">...</span>
                                </div>
                            </el-image>
                        </div>
                        <div class="bookInfo">
                            <p>书名:{
   
   {book.book_name}}</p>
                            <p>价格:{
   
   {book.book_price}}</p>
                        </div>
                    </div>
                    <div class="bookDesc">
                        <p>图书详情:{
   
   { book.book_desc}}</p>
        
                    </div>
                    <div class="book-comment">
                        <p>图书评论</p>

                        <div class="comment-info" >
                        <div class="comment-base-info">
                            <label style="display:inline-block;width:70%">评论人:</label>
                            <label style="margin-right:20px">评论时间:</label>
                        </div>
                        <div class="comment-content">
                        
                        </div>
                    </div>

                    </div>
                </div>
            </div>
          </el-main>

            <el-footer>Footer</el-footer>
        </el-container>
    </div>
</template>
<style scoped>
.index_container {
      
      
    margin: 0px auto;
    width: 80%;

}
.el-header,.el-footer {
      
      
        background-color: #b3c0d1;
        color: #333;
        text-align: center;
        line-height: 60px;
}
.el-header{
      
      
        display: flex;
 }

 .el-header .index-logo,.el-header .index-personal{
      
      
        width:50%

 }
.el-main {
      
      
    background-color: #fff;
    color: #333;
}

.desc-div{
      
      
        width:100%;
        border: 0px solid red;
    }
.goodsDesc{
      
      
        
        width: 80%;
        margin:0 auto;
        border:1px solid blue;

    }
.baseInfo{
      
      
        display: flex;
    }

.baseInfo .photo{
      
      
        width: 50%;
        text-align: center;
        margin-bottom:10px;
    }
.bookDesc>p{
      
      
        width:100%;
        height: 40px;
        line-height: 40px;
        padding-left:10px;
        box-sizing:border-box;
        color: #fff;
        background-color:darkgray;

    }

   
.book-comment>p{
      
      
        width:100%;
        height: 40px;
        line-height: 40px;
        padding-left:10px;
        box-sizing:border-box;
        color: #fff;
        background-color:darkgray;
        margin-bottom: 0px;
    }
.comment-base-info{
      
      
        width:100%;
        height: 40px;
        line-height: 40px;
        padding-left:10px;
        box-sizing:border-box;
        color: #fff;
        background-color:yellowgreen

}
.comment-content{
      
      
        padding:15px 10px;
}

</style>

3.1.3 配置路由传参

具体问题详见:VUE中路由跳转传参问题

  • 通过query传递参数

     <router-link class="normal" :to="{path:'bookDesc',query:{
           
           'book':book}}">
         书名:{
         
         { subBookName(book.book_name) }}
     </router-link>
    
  • 接受参数

     created() {
          
          
            let book=this.$route.query.book;
        }
    

3.1.4 接收后端传来的参数并做出处理

export default {
    
    
    data() {
    
    
        return {
    
    
           book:{
    
    }, 
        }
    },
    methods: {
    
    
        //根据id查询书的信息
        queryBookById(book_id){
    
    
            this.$axios
                       .get("/book/queryBookById")
                       .then(response=>{
    
    
                           this.book=response.data;
                       })
                       .catch(error=>{
    
    
                        alert(error)
                       });

        }
    },
    created() {
    
    
        //获得Index.vue传来的值
        let book_id=this.$route.query.book_id;
        this.queryBookById(book_id)     
    }
}

3.2 服务端

3.2.1 BookController里增加该功能

@RequestMapping("/queryBookById")
    public Book queryBookById(Integer book_id){
    
    
        return bookService.queryBookById(book_id);
    }

3.2.2 BookService增加该功能

  • BookService接口
/**
     * 根据id查询图书信息
     * @param book_id
     * @return
     */
    Book queryBookById(Integer book_id);
  • BookService接口实现
   @Override
    public Book queryBookById(Integer book_id) {
    
    
        return bookMapper.queryBookById(book_id).get(0);
    }

3.2.3 BookMapper增加该功能

  • BookMapper接口
List<Book> queryBookById(Integer... book_ids);
  • BookMapper.xml
<select id="queryBookById" resultType="com.shopping.model.Book">
   select * from myshopping.tbl_book
   where book_id in 
   <foreach open="(" collection="book_ids" item="book_id" close=")" separator=",">
     #{book_id}
   </foreach>
</select>

4 查看图书评论信息

4.1 服务端

4.1.1 创建comment的model

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommentView implements Serializable {
    
    
    private Integer comment_id;
    private Integer user_id;
    private Integer book_id;
    private String comment_message;
    private Date comment_time;
}

4.1.2 BookController里增加该功能

 @RequestMapping("/queryCommentById")
    public List<CommentView> queryCommentById(Integer book_id){
    
    
        return bookService.queryCommentById(book_id);
    }

4.1.3 BookService增加该功能

  • BookService接口
  /**
     * 根据id查询图书评论
     * @param book_id
     * @return
     */
    List<CommentView> queryCommentById(Integer book_id);
  • BookService接口实现
 	@Resource
    private CommentMapper commentMapper;
    @Override
    public List<CommentView> queryCommentById(Integer book_id) {
    
    
        return commentMapper.queryCommentById(book_id);
    }

4.1.4 创建CommentMapper

  • CommentMapper接口
@Repository
public interface CommentMapper {
    
    
    List<CommentView> queryCommentById(Integer book_id);
}
  • CommentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shopping.mapper.CommentMapper">
    <select id="queryCommentById" resultType="com.shopping.model.view.CommentView">
        select tbl_comment.comment_id,
               tbl_comment.comment_message,
               tbl_comment.comment_time,
               user.user_name
        from myshopping.tbl_comment  join myshopping.tbl_user user
            on tbl_comment.user_id = user.user_id
        where tbl_comment.book_id=#{book_id}
    </select>
</mapper>

4.2 客户端

4.2.1 接收后端传来的数据,并做出处理

export default {
    
    
    data() {
    
    
        return {
    
    
           commentList:[], //图书评论
        }
    },
    methods: {
    
    
        /**
         * 根据图书编号查询评论
         * @param {} book_id 
         */
        queryCommentById(book_id){
    
    
            this.$axios
                .get("/book/queryCommentById?book_id=" + book_id)
                .then(response => {
    
    
                    this.commentList = response.data;
                }) 
                .catch(error => {
    
    
                    alert(error)
                }); 
        }       
    },
    created() {
    
    
        let book_id = this.$route.query.book_id;
        this.queryCommentById(book_id);   
    }
}

4.2.2 前端显示

<div class="book-comment">
   <p>图书评论</p>                
      <div class="comment-info" v-for="comment in commentList" :key="comment.comment_id">
         <div class="comment-base-info">
            <label style="display:inline-block;width:60%">评论人:{
   
   {comment.user_name}}				</label>
            <label style="margin-right:20px">评论时间:{
   
   {comment.comment_time}}</label>
         </div>
         <div class="comment-content">{
   
   { comment.comment_message }}</div>
      </div>
      <div class="comment-info" v-if="commentList.length==0">
         <div class="comment-content">暂无评论</div>
      </div>
 </div>

5 对商品评论进行分页处理

  • 基于程序分页(不用):一次性将数据查出,在程序进行逻辑处理实现分页
  • 基于数据库分页:根据分页的要求,一次查出一页数据mysql limit sqlserver top oracle rowNum

5.1 服务端

5.1.1添加pagehelper依赖

<dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.3.2</version>
</dependency>

5.1.2 配置PageHelper

  pagehelper:
  #配置pagehelper方言,不同数据库生成sql语句不同
  helper-dialect: mysql
  #合理化配置,配置后访问的页码不存在则自动跳转到第一页
  reasonable: true
  #自动分页的配置,依据的是入参,如果参数中有pageNum,pageSize分页参数,则会自动分页.
  supportMethodsArguments: true

5.1.3 创建公共类-分页参数类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageParams {
    
    
  private Integer pageSize;//每页记录数量
  private Integer pageNum;//当前页

}

5.1.4 Service层写分页代码-BookServiceImpl类

    public Map<String, Object> queryCommentById(Integer book_id, PageParams pageParams) {
    
    
        //使用PageHelper实现分页
        //开始分页插件
        PageHelper.startPage(pageParams);
        List<CommentView> commentViews = commentMapper.queryCommentById(book_id);
        //将查询结果封装到PageInfo对象中,该对象是PageHelper提供的
        //该对象会根据commentViews来封装分页相关数据
        PageInfo<CommentView> pageInfo=new PageInfo<>(commentViews);
        //将分页需要返回的客户端数据封装到Map结合中
        Map<String,Object> map=new HashMap<>();
        map.put("total",pageInfo.getTotal());
        map.put("commentList",pageInfo.getList());
//        System.out.println(map.get("total")+"///"+map.get("commentList"));
        return map;
    }

5.1.5 修改BookController

  @RequestMapping("/queryCommentById")
    public Map<String, Object> queryCommentById(Integer book_id, PageParams pageParams){
    
    
        return bookService.queryCommentById(book_id,pageParams);
    }

5.2 客户端

5.2.1 前端使用Element的分页插件

<div class="pagination">
    <el-pagination background :page-size="pageParams.pageSize"      
                              :current-page.sync="pageParams.pageNum" 
                              layout="prev, pager, next,jumper,->,total"
                              :total="total" 
                              @current-change="queryCommentByBookId(book.book_id)">
    </el-pagination>
</div>

5.2.2 前端定义分页参数,及向后端传递参数

export default {
    
    
    data() {
    
    
        return {
    
    
            commentList: [],//图书评论数组
            /**
             * 分页参数
             */
            pageParams: {
    
    
                pageSize: 4,//每页显示的行数
                pageNum: 1//设置当前页
            },
            total: 0,//总行数
            
        }
    },
    methods: {
    
    
        /**
         * 根据图书编号获得该图书的所有评论信息
         */
        queryCommentByBookId(book_id) {
    
    
            this.pageParams.book_id = book_id;//向对象中添加了一个属性
            this.$axios
                .get('book/queryCommentById', {
    
    
                    params: this.pageParams
                })
                .then(response => {
    
    
                    let map = response.data;
                    this.total = map.total;//获得总记录数
                    this.commentList = map.commentList;//获得查询的具体数据
                })
                .catch(error => {
    
    
                    alert(error);
                })
        }

    },
    created() {
    
    
        //获得Index.vue传入的查询参数
        let book_id = this.$route.query.book_id;
        this.queryCommentByBookId(book_id);
    }
}

6 查看商品列表-基于Redis查询

  • 前端第一次获取数据,后端程序访问Redis,检测有无数据,若无数据,后端程序mysql中获取数据,mysql返回的数据,后端程序将数据一份保存在Redis中,一份在显示在前端
  • 第二次访问Redis,里面包含数据,后端程序直接从Redis中返回数据到前端
  • 数据库与Redis同步问题

    • 当数据库中的数据发生改变直接删除Redis中的数据
    • 当数据库中某条数据发生改变,修改Redis中该条数据

6.1 服务端

6.1.1引入Redis依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>${springboot-date-redis}</version>
</dependency>

6.1.2 在Service层做处理-BookServiceImpl

  • 思路

    1. 检测商品是否为上架商品

      ①如果传来的数据为0,则表示上架商品,默认从Redis中读取

      ②若传来数据不为0,则从数据库中读取

    2. 检测Redis是否包含上架商品

      ①若包含直接Redis中读取

      ②若不包含则从数据库中读取以上架商品

    3. 从数据库中获取已上架数据并向Redis中存储一份,然后返回

  • 采用hash存储商品信息显然更合适

  • 注入RedisTemplate

  private RedisTemplate redisTemplate;
    //构造器注入,并设置序列化Redis
    @Autowired
    public BookServiceImpl(BookMapper bookMapper, CommentMapper commentMapper, RedisTemplate redisTemplate) {
    
    
        this.bookMapper = bookMapper;
        this.commentMapper = commentMapper;
        this.redisTemplate = redisTemplate;
        this.redisTemplate.setKeySerializer(new StringRedisSerializer());
        this.redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    }
  • 具体业务代码
    @Override
    public List<Book> queryBook(Integer... isDisplay) {
    
    
        //从数据库获取数据
        List<Book> bookList = bookMapper.queryBook(isDisplay);
        Map<String,Object> booksMap=new HashMap<>();
        //检测商品是否上架
        if (isDisplay[0] == 0) {
    
    
            //获得hash操作对象
            HashOperations hashOperations = redisTemplate.opsForHash();
            //获得Redis中bookList中的数据
            Map bookListMap = hashOperations.entries("bookList");
            //检测Redis中是否存在数据,如不存在从数据库中获取数据
            if (bookListMap.size() == 0) {
    
    
                //遍历bookList,将bookList中数据转存到booksMap集合中
                for (Book book:bookList){
    
    
                    booksMap.put("BOOKKEY:"+book.getBook_id(), book);
                }
                //将booksMap集合存到Redis中,RedisKey为BookList
                hashOperations.putAll("bookList",booksMap);
                return bookList;
            }else {
    
    //否则从Redis中直接获取数据
                Collection<Book> values = bookListMap.values();
                List<Book> bookLists=new ArrayList<>();
                for (Book book:values){
    
    
                    bookLists.add(book);
                }
                return bookLists;
            }
        }else{
    
    
            return bookList;
        }
    }

猜你喜欢

转载自blog.csdn.net/woschengxuyuan/article/details/123757872