跳至主要內容

前后端分离项目架构

wangdx大约 17 分钟

前后端分离技术架构

单 WEB 应用开发架构

  • 现代应用层项目的开发大多数是基于数据库实现的,所以在任何一个项目内部会发现存在有大量的 CRUD 基础功能,通过这些功能可以有效的实现数据更新以及数据查询需求的实现。在应用层技术发展的最初阶段,由于没有高并发的应用场景,所以会将前端程序与后端代码放在同一为了便于应用的开发与代码维护,都会基于 MVC 设计模式进一个成用之中。后端代码通过控制器进行业务调用,,业务处理完成后通过控制器将处理结行代码的实现,果交由前端视图,而前端页面依据后端业务处理的结果,来进行页面的动态生成

搭建 WEB 服务集群

  • 在传统的 JavaWEB 开发过程之中,由于前端代码和动态程序需要捆绑在一起,所以前端开发人员需要掌握一定的 JSP 开发技术(或者掌握页面模版引擎的开发技术)而后端开发人员也需要掌握一定的前端技术才可以完成项目功能,所以最终导致项目的分工困难,同时也为项目的集群环境管理带来困难

前后端分离架构

  • 在这样的技术发展背景下,为了更好的实现有效的团队分工,同时也让项目代码维护变,才引入了前后端分离设计架构。此时前端工程师只是关注与页面展示功能得更加灵活,的实现,而后端工程师负责为前端提供有效的数据,两者结合后就可以实现最终数据的展现

跨域访问与接口规范

  • 前后端分离开发架构主要是基于 Ajax 的一种应用形式,后端应用提供业务专属的接口定义,而前端应用就可以通过这个接口获取所需要的数据,当前端获取到数据之后就可以基于 DOM 解析的方式进行页面的展示处理。而在前后端分离的设计架构之中,最为关键性的技术问题就是跨域访问以及接口规范的定义

跨域解决方案

  • 由于现实开发中前后端应用往往部署在不同的服务主机之中,这样在进行服务接口调用就会出现 HTTP 求的跨域访问的需要,在开发中如果要想解决跨域资源调用的问题时有三类传统的解决方案:
    • JSONP:采用回调标记的方式实现跨域访问,该操作只支持 GET 请求模式;
    • 代理配置:将前端应用与后端应用利用代理连接在一起,前端访问服务接口时就像本地调用一样此类模式需要引入额外的代理主机,并进行代理配置后才可以使用
    • 头信息处理:在每次请求处理时,使用“Access-Control-Alow-Origin”、“Access-Control-Allow-Methods”等相关的头信息定义跨域访问,此种实现方式对代码的改动较小。
  • 本次将使用 Axios 开发组件(该组件包装了 XMLHttpRequest,采用头信息配置方式实现服务访问),并基于 Node.JS 实现 HTTP 请求的发送,以实现最终的跨域服务访问

搭建案例开发环境

1、

// https://mvnrepository.com/artifact/org.mybatis/mybatis
implementation group: 'org.mybatis', name: 'mybatis', version: '3.5.11'
// https://mvnrepository.com/artifact/org.mybatis/mybatis-spring
implementation group: 'org.mybatis', name: 'mybatis-spring', version: '2.0.7'


2、
package com.yootk.context.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScans({
        @ComponentScan("com.yootk.common.web.config"),
        @ComponentScan("com.yootk.ssm.config"),
        @ComponentScan("com.yootk.ssm.service"),
        @ComponentScan("com.yootk.ssm.dao")
})
@EnableAspectJAutoProxy // 启用AOP事务代理
public class SpringApplicationContextConfig {}


3、
package com.yootk.context.config;

import com.yootk.common.interceptor.RequestDataValidateInterceptor;
import com.yootk.common.mapper.CustomObjectMapper;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
@EnableWebMvc // 启用SpringMVC
@ComponentScan("com.yootk.ssm.action") // 扫描控制层路径
public class SpringWebContextConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter converter =
                new MappingJackson2HttpMessageConverter(); // Jackson数据转换器
        CustomObjectMapper objectMapper = new CustomObjectMapper();
        converter.setObjectMapper(objectMapper);
        converter.setSupportedMediaTypes(List.of(
                MediaType.APPLICATION_JSON));
        converters.add(converter); // 追加转换器
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        RequestDataValidateInterceptor interceptor =
                new RequestDataValidateInterceptor(); // 请求数据验证拦截器
        interceptor.setRestSwith(false); // 拦截器显示风格
        registry.addInterceptor(interceptor).addPathPatterns("/pages/**");
    }
}


4、
package com.yootk.action;

import com.yootk.common.web.action.abs.AbstractAction;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

@Controller
public class ErrorAction extends AbstractAction {
    @RequestMapping("/error")
    @ResponseBody // 直接进行REST响应
    public Object error(HttpServletResponse response) {
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); // HTTP状态码
        Map<String, String> result = new HashMap<>(); // 响应错误信息
        result.put("message", "程序出现异常,无法正常执行!");
        result.put("status", String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR));
        return result;
    }
    @RequestMapping("/notfound")
    @ResponseBody
    public Object notfound(HttpServletResponse response) {
        response.setStatus(HttpStatus.NOT_FOUND.value());
        Map<String, String> result = new HashMap<>(); // 响应错误信息
        result.put("message", "程序出现异常,无法正常执行!");
        result.put("status", String.valueOf(HttpStatus.NOT_FOUND));
        return result;
    }
}


5、
package com.yootk.ssm.action.advice;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class ErrorAdvice {
    @ExceptionHandler
    @ResponseBody
    public Object handle(Exception e, HttpServletRequest request,
                               HttpServletResponse response) {
        Map<String, String> result = new HashMap<>();
        result.put("message", e.getMessage());
        result.put("type", e.getClass().getName());
        result.put("path", request.getRequestURI());
        result.put("referer", request.getHeader("Referer"));
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); // 500状态码
        return result;
    }
}


6、
npm config set prefix "H:\repository\npm\npm_global"
npm config set cache "H:\repository\npm\npm_cache"


7、
npm config set registry https://registry.npmmirror.com
npm config set registry http://mirrors.cloud.tencent.com/npm/
npm config set registry https://mirrors.huaweicloud.com/repository/npm/


8、
npm config get registry

9、
C:\Windows\System32\drivers\etc\hosts
127.0.0.1   book-endpoint
127.0.0.1   book-web

10、
npm install
npm run dev

后端业务改造

配置扫描包

  • MyBatis 实现是基于映射文件的方式处理的,所以需要在当前的应用中定义 MyBatisConfig 配置类,该类中要明确的定义好 DAO 包、VO 包以及映射文件的路径
1、
package com.yootk.ssm.config;

import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.io.IOException;

@Configuration
public class MyBatisConfig { // MyBatis配置类
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(
            @Autowired DataSource dataSource) throws IOException { // mybatis.cfg.xml代替
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        // 整体的配置现在全部交由Spring负责,所以此处一定要将数据源设置进来
        factoryBean.setDataSource(dataSource); // 配置数据源
        factoryBean.setTypeAliasesPackage("com.yootk.ssm.vo"); // 别名扫描包
        // 虽然现在提倡的是“零配置”,可是MyBatis不管如何开发都建议保留Mapper映射文件
        // mybatis.cfg.xml配置文件是需要分别定义Mapper文件名称的,但是现在可以扫描实现
        PathMatchingResourcePatternResolver resolver =
                new PathMatchingResourcePatternResolver(); // 路径匹配资源
        String mapperPath = PathMatchingResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                "mybatis/mapper/*.xml"; // 所有Mapper文件的路径批磅亿毫
        factoryBean.setMapperLocations(resolver.getResources(mapperPath));
        return factoryBean;
    }
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        // 传统的数据层开发之中,是通过了DAO调用Mybatis组件,这个操作很重复
        // Spring针对于MyBatis的实现可以自动生成一个代理类,这个代理类会自动调用映射配置SQL
        MapperScannerConfigurer scannerConfigurer = new MapperScannerConfigurer();
        scannerConfigurer.setBasePackage("com.yootk.ssm.dao"); // dao程序包路径
        // 一般良好的做法都需要在需要映射的DAO上配置有一个映射注解(可以没有)
        scannerConfigurer.setAnnotationClass(Mapper.class); // MyBatis提供的注解
        return scannerConfigurer;
    }
}


2、
package com.yootk.ssm.vo;

import java.io.Serializable;

public class Item implements Serializable {
    private Long iid;
    private String name;
    private String note;
    public Long getIid() {
        return iid;
    }
    public void setIid(Long iid) {
        this.iid = iid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getNote() {
        return note;
    }
    public void setNote(String note) {
        this.note = note;
    }
}

3、
package com.yootk.ssm.vo;

import java.io.Serializable;

public class Book implements Serializable {
    private Long bid;
    private String name;
    private String author;
    private Integer price; // 单位:分
    private String cover; // 图书封面
    private String note; // 图书简介
    private Long iid; // 图书所属分类
    public Long getBid() {
        return bid;
    }
    public void setBid(Long bid) {
        this.bid = bid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
    public Integer getPrice() {
        return price;
    }
    public void setPrice(Integer price) {
        this.price = price;
    }
    public String getCover() {
        return cover;
    }
    public void setCover(String cover) {
        this.cover = cover;
    }
    public String getNote() {
        return note;
    }
    public void setNote(String note) {
        this.note = note;
    }
    public Long getIid() {
        return iid;
    }
    public void setIid(Long iid) {
        this.iid = iid;
    }
}



4、
package com.yootk.ssm.dao;

import com.yootk.ssm.vo.Item;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper // MyBatis整合的时候需要添加的注解标记
public interface IItemDAO {
    public List<Item> findAll();
    public boolean doCreate(Item item);
    public Item findById(long iid);
}


5、
package com.yootk.ssm.dao;


import com.yootk.ssm.vo.Book;

import java.util.List;
import java.util.Map;

public interface IBookDAO{
    public boolean doCreate(Book book); // 增加新数据
    public List<Book> findAll(Map<String, Object> params); // 数据分页查询
    public Long getAllCount(Map<String, Object> params); // 统计数据个数
    public Book findById(Long bid);
    public boolean doEdit(Book book);
    public boolean doRemove(Long bid);
}


6、

<?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.yootk.ssm.dao.IItemDAO"> <!-- 与数据层接口名称一致 -->
    <insert id="doCreate" parameterType="Item">
        INSERT INTO item(name, note) VALUES (#{name}, #{note});
    </insert>
    <select id="findAll" resultType="Item">
        SELECT iid, name, note FROM item
    </select>
    <select id="findById" resultType="Item" parameterType="java.lang.Long">
        SELECT iid, name, note FROM item WHERE iid=#{iid}
    </select>
</mapper>

7、
<?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.yootk.ssm.dao.IBookDAO"> <!-- 与数据层接口名称一致 -->
    <insert id="doCreate" parameterType="Book" keyProperty="bid"
        keyColumn="bid" useGeneratedKeys="true">
        INSERT INTO book(name, author, price, cover, note, iid)
        VALUES (#{name}, #{author}, #{price}, #{cover}, #{note}, #{iid})
    </insert>
    <select id="findAll" resultType="Book" parameterType="java.util.Map">
        SELECT bid, name, author, price, cover, note, iid FROM book
        <where>
            <if test="keyword != null and column != null">
                ${column} LIKE #{keyword}
            </if>
        </where>
        LIMIT #{start}, #{lineSize}
    </select>
    <select id="getAllCount" resultType="java.lang.Long">
        SELECT COUNT(*) FROM book
        <where>
            <if test="keyword != null and column != null">
                ${column} LIKE #{keyword}
            </if>
        </where>
    </select>
    <select id="findById" resultType="Book" parameterType="java.lang.Long">
        SELECT bid, name, author, price, cover, note, iid FROM book WHERE bid=#{bid}
    </select>
    <update id="doEdit" parameterType="Book">
        UPDATE book SET name=#{name}, author=#{author}, price=#{price},
            cover=#{cover}, note=#{note}, iid=#{iid} WHERE bid=#{bid}
    </update>
    <delete id="doRemove" parameterType="java.lang.Long">
        DELETE FROM book WHERE bid=#{bid}
    </delete>
</mapper>
8、
package com.yootk.ssm.service.impl;

import com.yootk.ssm.dao.IItemDAO;
import com.yootk.ssm.service.IItemService;
import com.yootk.ssm.service.abs.AbstractService;
import com.yootk.ssm.vo.Item;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ItemServiceImpl extends AbstractService implements IItemService {
    @Autowired
    private IItemDAO itemDAO;

    @Override
    public List<Item> list() {
        return this.itemDAO.findAll(); // 查询全部数据
    }

    @Override
    public boolean add(Item item) {
        return this.itemDAO.doCreate(item);
    }
}


9、
package com.yootk.ssm.service.impl;

import com.yootk.ssm.dao.IBookDAO;
import com.yootk.ssm.dao.IItemDAO;
import com.yootk.ssm.service.IBookService;
import com.yootk.ssm.service.IItemService;
import com.yootk.ssm.service.abs.AbstractService;
import com.yootk.ssm.vo.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.awt.print.Pageable;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

@Service
public class BookServiceImpl extends AbstractService implements IBookService {
    // 如果此时注入的是IItemDAO接口,那么意味着在进行图书列表查询的时候还要查询数据库
    @Autowired
    private IItemService itemService; // 注入业务层
    @Autowired
    private IBookDAO bookDAO; // 图书表的数据操作
    @Override
    public Map<String, Object> list(int currentPage, int lineSize, String column, String keyword) {
        Map<String, Object> result = new HashMap<>(); // 保存存储结果
        Map<String, Object> params = new HashMap<>(); // 保存全部的查询参数
        params.put("start", (currentPage - 1) * lineSize);
        params.put("lineSize", lineSize);
        if (super.checkEmpty(column, keyword)) {
            params.put("keyword", keyword);
            params.put("column", column);
        }
        result.put("allData", this.bookDAO.findAll(params)); // 获取图书信息
        long count = this.bookDAO.getAllCount(params); // 查询总记录数
        result.put("allRecorders", count); // 总行数
        long pageSize = (count + lineSize - 1) / lineSize;
        result.put("allPages", pageSize); // 总页数
        result.put("allItems", this.itemService.list()); // 缓存支持
        return result;
    }

    @Override
    public Map<String, Object> preAdd() {
        Map<String, Object> result = new HashMap<>();
        result.put("allItems", this.itemService.list()); // 直接获取缓存数据
        return result;
    }

    @Override
    public boolean add(Book book) {
        return this.bookDAO.doCreate(book);
    }
    // 如果不想通过ItemDAO接口进行分类数据的加载,那么就可以使用缓存中的内容进行迭代处理
    @Autowired
    private IItemDAO itemDAO;
    @Override
    public Map<String, Object> get(long bid) {
        Map<String, Object> result = new HashMap<>();
        Book book = this.bookDAO.findById(bid); // 获取图书信息
        result.put("item", this.itemDAO.findById(book.getIid())); // 图书分类信息
        result.put("book", book); // 保存图书信息
        return result;
    }

    @Override
    public Map<String, Object> preEdit(long bid) {
        Map<String, Object> result = new HashMap<>();
        result.put("allItems", this.itemService.list()); // 直接获取缓存数据
        result.put("book", this.bookDAO.findById(bid));
        return result;
    }

    @Override
    public boolean edit(Book book) {
        return this.bookDAO.doEdit(book);
    }

    @Override
    public String delete(long bid) {
        Book book = this.bookDAO.findById(bid);
        if (book != null) {
            this.bookDAO.doRemove(bid);
            return book.getCover();
        }
        return null;
    }
}

10、
package com.yootk.test;

import com.yootk.ssm.context.config.SpringApplicationContextConfig;
import com.yootk.ssm.service.IItemService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ContextConfiguration(classes = {SpringApplicationContextConfig.class})
@ExtendWith(SpringExtension.class)
public class TestItemService {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestItemService.class);
    @Autowired
    private IItemService itemService;
    @Test
    public void testList() {
        this.itemService.list().forEach(System.out::println);
    }
    @Test
    public void testClear() {
        this.itemService.clear();
    }
}

12、
package com.yootk.test;

import com.yootk.ssm.context.config.SpringApplicationContextConfig;
import com.yootk.ssm.service.IBookService;
import com.yootk.ssm.service.IItemService;
import com.yootk.ssm.vo.Book;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ContextConfiguration(classes = {SpringApplicationContextConfig.class})
@ExtendWith(SpringExtension.class)
public class TestBookService {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestBookService.class);
    @Autowired
    private IBookService bookService;

    @Test
    public void testAdd() {
        Book book = new Book();
        book.setIid(1L);
        book.setAuthor("李兴华");
        book.setName("SSM开发实战");
        book.setPrice(7980);
        book.setCover("nophoto.png");
        book.setNote("Spring MVC + Spring Security + MyBatis / MyBatis-Plust + Spring Batch");
        LOGGER.info("【增加图书】{}", this.bookService.add(book));
    }

    @Test
    public void testList() {
        this.bookService.list(1, 3, null, null).forEach((key, value) -> {
            LOGGER.info("【图书数据】key = {}、value = {}", key, value);
        });
    }

    @Test
    public void testGet() {
        LOGGER.info("{}", this.bookService.get(1));
    }
    @Test
    public void testEdit() {
        Book book = new Book();
        book.setBid(7L);
        book.setIid(2L);
        book.setAuthor("李兴华");
        book.setName("SSM开发实战");
        book.setPrice(9980);
        book.setCover("nophoto.png");
        book.setNote("Spring MVC + Spring Security + MyBatis / MyBatis-Plust + Spring Batch");
        LOGGER.info("【修改图书】{}", this.bookService.edit(book));
    }
    @Test
    public void testDelete() {
        LOGGER.info("【删除图书】{}", this.bookService.delete(7L));
    }
}

HTTPie

HTTPie

  • 对于 WEB 接口的测试工具来讲,较为常用的就是 cur 命令,但是该命令在使用过程中的语法要求限制较多,同时不同平台的 curl 上还会存在有一些格式上的差异。所以在现代项目中如果要采用命令行的方式进行接口测试时,可以使用 HTTPie 客户端
1、
Windows安装:
pip install --upgrade httpie
MacOS安装:
brew install httpie
Linux安装:
yum -y install epel-release	# 安装EPEL(Extra Packages for Enterprise Linux)组件
yum -y install httpie		# 安装HTTPie工具
yum -y upgrade httpie		# 更新HTTPie工具



2、
http GET http://www.yootk.com

图书分类列表

图书分类列表

  • 图书分类是进行图书数据管理的核心数据,图书分类只需要进行 item 数据表的展示即可,在前后端分离的设计架构之中,可以通过 Jackson 对 lltemService.list()业务方法返回的数据进行转换,,使其以 JSON 结构数据返回给前端,而前端只需要将该数据交由 MVVM 处理引擎,就可以实现数据的列表展示
1、
    @Autowired
    private IItemService itemService;
    @GetMapping("list")
    @ResponseBody // 直接响应
    public Object list() {
        List<Item> allItems = this.itemService.list(); // 直接业务查询
        LOGGER.debug("【查询全部分类】{}", allItems);
        return allItems; // 直接响应
    }

2、
http GET http://book-endpoint/pages/back/admin/item/list -b

增加图书分类

增加图书分类

  • 图书信息的增加需要提供对应的表单页面,当用户填写完表单之后,就可以利用 AXIOS 发出一个服务接口的调用请求,随后由控制层调用业务层进行数据存储,业务层在处理完成后会将当前的处理结果发送给前端应用,前端可以根据结果进行成功或失败的信息提示
1、
    @RequestDataValidate("name:string;note:string") // 定义拦截验证规则
    @PostMapping("add") // 采用了POST提交模式
    @ResponseBody // 直接响应
    public Object add(@RequestBody Item item) { // Jackson处理支持
        LOGGER.info("【VO】{}", item);
        return this.itemService.add(item); // 业务调用
    }

2、
http POST http://book-endpoint/pages/back/admin/item/add name=Java编程 note=李兴华Java原创图书 -b

强制刷新分类数据缓存

强制刷新图书分类缓存

  • 在当前项目应用中,所有的图书分类的数据信息保存在了缓存数据库之中,这样在数据更新后,就需要强制性的刷新缓存,才可以获取到新的列表信息,由于在 lltemService 接口中提供了 clear()与 list()方法,所以可以在控制层先清空缓存,而后再加载新的数据给前端页面显示
1、
    @PatchMapping("refresh")
    @ResponseBody // 直接进行数据响应
    public Object refresh() {
        LOGGER.debug("强制性刷新缓存");
        this.itemService.clear(); // 清除缓存
        return this.itemService.list(); // 数据的重新加载
    }

2、

http PATCH http://book-endpoint/pages/back/admin/item/refresh -b

3、
  refresh: function (vue) { // 强制刷新
    return new Promise((resolve, reject) => { // 重新加载分类数据
      vue.$axios.patch('/book-endpoint/pages/back/admin/item/refresh', {}) // Patch请求
        .then(response => {
          resolve(response.data) // 返回处理结果
        })
        .catch((error) => { // 异常处理
          reject(error)
        })
    })
  },

图书分页列表显示

数据列表显示

  • 图书信息列表在进行处理时,需要前端应用向后端应用传递相应的数据分页参数,这样后端应用才可以根据这些参数进行业务调用,并将所需的数据返回给前端。由于当前的项目中基于了 ElementUl 前端框架进行了页面实现,所以只需要返回总页数即可实现分页组件的驱动处理
1、
<el-pagination background layout="prev, pager, next" :page-count="totalPage"
      :current-page.sync="currentPage" @current-change="loadBookData"></el-pagination>


2、
    @Override
    public Map<String, Object> list(int currentPage, int lineSize, String column, String keyword) {
        Map<String, Object> result = new HashMap<>(); // 保存存储结果
        Map<String, Object> params = new HashMap<>(); // 保存全部的查询参数
        params.put("start", (currentPage - 1) * lineSize);
        params.put("lineSize", lineSize);
        if (super.checkEmpty(column, keyword)) {
            params.put("keyword", keyword);
            params.put("column", column);
        }
        result.put("allData", this.bookDAO.findAll(params)); // 获取图书信息
        long count = this.bookDAO.getAllCount(params); // 查询总记录数
        result.put("allRecorders", count); // 总行数
        long pageSize = (count + lineSize - 1) / lineSize;
        result.put("allPages", pageSize); // 总页数
        result.put("allItems", this.itemService.list()); // 缓存支持
        return result;
    }

3、
package com.yootk.ssm.action;

import com.yootk.ssm.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/pages/back/admin/book")
public class BookAction {
    @Autowired
    private IBookService bookService; // 图书的业务接口
    @GetMapping("/list")
    @ResponseBody // 直接进行REST响应操作
    public Object list(int currentPage, int lineSize, String column, String keyword) {
        return this.bookService.list(currentPage, lineSize, column, keyword);
    }
}


4、

http GET http://book-endpoint/pages/back/admin/book/list currentPage==1 lineSize==5 column==name keworld== -b

5、
var bookHandler = { // 定义图书有关的调用函数
  list: function (vue, currentPage) {
    return new Promise((resolve, reject) => { // 图书分页列表
      var params = new URLSearchParams() // 封装请求参数
      params.append('currentPage', currentPage) // 设置参数内容
      params.append('lineSize', 2) // 设置参数内容
      params.append('column', 'tab') // 设置参数内容
      params.append('keyword', '') // 设置参数内容
      vue.$axios.get('/book-endpoint/pages/back/admin/book/list', { // GET请求
        params: params // 传递请求参数
      })
        .then(response => {
          resolve(response.data) // 返回处理结果
        })
        .catch((error) => { // 异常处理
          reject(error)
        })
    })
  }

6、
    loadBookData () { // 加载图书信息
      let loadingInstance = Loading.service({
        lock: true,
        text: 'Loading',
        spinner: 'el-icon-loading',
        background: 'rgba(0, 0, 0, 0.7)'
      }) // 数据加载组件
      this.$nextTick(() => { // 调用加载组件
        setTimeout(() => {
          this.$back.book.list(this, this.currentPage).then(res => { // 加载图书数据
            this.totalPage = res.allPages // 获取总页数
            this.bookData = res.allData // 图书数据
            this.itemData = res.allItems // 分类数据
            loadingInstance.close() // 关闭数据加载组件
          })
        }, 100)
      })
    }

增加图书数据

图书增加

  • 在本次应用的设计中,每一本图书都会存在有一个对应的类别,所以在进行图书数据增就需要通过已经存在的接口获取到全部的分类数据,而后才可以进行表单页面的加时,显示
1、
    @GetMapping("/add_pre")
    @ResponseBody
    public Object addPre() { // 实现增加表单的显示操作
        return this.bookService.preAdd(); // 调用业务处理
    }
2、
http GET http://book-endpoint/pages/back/admin/book/add_pre -b

3、
  addPre: function (vue) { // 获取增加前数据
    return new Promise((resolve, reject) => { // 数据查询
      vue.$axios.get('/book-endpoint/pages/back/admin/book/add_pre', {}) // GET请求
        .then(response => {
          resolve(response.data) // 返回处理结果
        })
        .catch((error) => { // 异常处理
          reject(error)
        })
    })
  },

4、
<el-upload drag list-type="picture" accept=".jpg,.jpen,.png,.bmp,.gif"
  action="/book-endpoint/pages/back/admin/book/upload" name="photo"
  :on-success="handleUploadPhotoSuccess" :on-remove="handlePhotoRemove">
  <i class="el-icon-upload"></i>
  <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
  <div class="el-upload__tip" slot="tip">只能上传图片文件</div>
</el-upload>


5、
    private static final String SAVE_DIR = "/WEB-INF/upload/book/"; // 文件保存路径
    @PostMapping("/upload") // 文件上传使用POST请求模式
    @ResponseBody // 直接响应
    public Object update(MultipartFile photo) { // 接收上传文件
        return UploadFileUtils.save(photo, SAVE_DIR); // 文件上传保存
    }

6、
http -f POST http://book-endpoint/pages/back/admin/book/upload photo@C:\Users\12654\Pictures\小狐狸冬瓜.jpeg -b

7、
    @DeleteMapping("/remove")
    @ResponseBody
    public Object remove(String photo) {
        return UploadFileUtils.delete(SAVE_DIR, photo);
    }

8、
http -f DELETE http://book-endpoint/pages/back/admin/book/remove photo==7d08e1bc-0b0e-4aa1-ac74-d4ca34405c10.jpeg -b


9、
    @PostMapping("/add")
    @ResponseBody
    public Object add(Book book, double tprice) { // 本次的操作之中所有的金钱都使用分
        book.setPrice((int) (tprice * 100)); // 数据的处理
        return this.bookService.add(book);
    }

编辑

1、
    @RequestMapping("/edit_pre")
    @ResponseBody
    public Object editPre(long bid) { // 图书修改前的查询处理
        return this.bookService.preEdit(bid);
    }
    @PostMapping("/edit")
    @ResponseBody
    public Object edit(Book book, double tprice, String old) {
        book.setPrice((int) (tprice * 100)); // 数据的处理
        if (!old.equals(book.getCover())) { // 此时的图像已经更新了
            UploadFileUtils.delete(SAVE_DIR, old); // 删除旧的图片名称
        }
        return this.bookService.edit(book);
    }

删除

1、
    @DeleteMapping("/delete")
    @ResponseBody
    public Object delete(long bid) {
        String cover = this.bookService.delete(bid); // 删除信息并返回图片名称
        if (StringUtils.hasLength(cover)) { // 内容不为空,有长度
            return UploadFileUtils.delete(SAVE_DIR, cover);
        }
        return true;
    }

2、
http -f DELETE http://book-endpoint:8080/pages/back/admin/book/delete?bid=5 -b

3、
  delete: function (vue, bid) { // 删除图书数据
    return new Promise((resolve, reject) => {
      var params = new URLSearchParams() // 封装请求参数
      params.append('bid', bid) // 设置传递参数
      vue.$axios.delete('/book-endpoint/pages/back/admin/book/delete', { params: params }) // DELETE请求
        .then(response => {
          resolve(response.data) // 返回处理结果
        })
        .catch((error) => { // 异常处理
          reject(error)
        })
    })
  },

4、
    handleDelete (bid) { // 删除图书信息
      this.$confirm('确定要删除该图书信息吗?', '提示', { // 删除确认提示框
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        let loadingInstance = Loading.service({
          lock: true,
          text: 'Loading',
          spinner: 'el-icon-loading',
          background: 'rgba(0, 0, 0, 0.7)'
        }) // 数据加载组件
        this.$nextTick(() => { // 服务调用
          setTimeout(() => {
            this.$back.book.delete(this, bid).then(res => { // 删除图书数据
              this.$message({
                type: 'success',
                message: '图书数据已删除'
              }) // 提示信息
              for (var ind in this.bookData) { // JSON循环
                if (this.bookData[ind].bid === bid) { // 删除ID匹配
                  this.bookData.splice(ind, 1) // 删除指定索引数组
                }
              }
              loadingInstance.close() // 关闭加载页面
            })
          }, 100)
        })
      }).catch(() => {})
    },

http 测试合集

http GET http://book-endpoint:8080/pages/back/admin/item/list -b
http POST http://book-endpoint:8080/pages/back/admin/item/add name=Java编程 note=李兴华Java原创图书 -b
http PATCH http://book-endpoint:8080/pages/back/admin/item/refresh -b
http GET http://book-endpoint:8080/pages/back/admin/book/list currentPage==1 lineSize==5 column==name keworld== -b
http GET http://book-endpoint:8080/pages/back/admin/book/add_pre -b
http -f POST http://book-endpoint:8080/pages/back/admin/book/upload photo@c:\java.jpg -b
http -f DELETE http://book-endpoint:8080/pages/back/admin/book/remove photo==0acfe2c7-f952-4821-8f34-4e832565327f.jpeg -b
http -f DELETE http://book-endpoint:8080/pages/back/admin/book/delete?bid=5 -b

demo


上次编辑于: