项目实战二-SpringMVC实现CORS、前后端分离、元数据模块-数据字典类型

同源策略

  1. 浏览器有同源策略(Same-Origin Policy)
    1. 它规定了:默认情况下,AJAX请求只能发送给同源的URL,同源是指3个相同:协议、域名(IP)、端口
  2. img、script、link、iframe、video、audio等标签不受同源策略的约束
  3. 解决AJAX跨域请求的常用方法:CORS(Cross-Origin Resource Sharing),跨域资源共享
  4. CORS的实现需要客户端和服务器同时支持
    1. 客户端:所有的浏览器都支持(IE至少是IE10版本)
    2. 服务器:需要返回相应的响应头(比如Access-Control-Allow-Origin),告知浏览器这是一个允许跨域访问的请求

SpringMVC实现CORS

  1. 局部设置

     @RestController
     @RequestMapping("/users")
     //只针对某个Controller设置,*代表所有请求源
     //@CrossOrigin("*")
     //指定某个源才能访问,这个也可以放在某个方法上,只针对某个方法进行限制
     @CrossOrigin("http://localhost:63343/")
     public class UserController {
     }
    
  2. 全局设置

    1. 后台项目新建一个WebCfg继承自WebMvcConfigurer,全局配置

       @Configuration
       public class WebCfg implements WebMvcConfigurer {
           @Override
           public void addCorsMappings(CorsRegistry registry) {
               // /**表示对所有的路径开放全局跨域访问权限
               registry.addMapping("/**")
                       //开放哪些IP 、端口、域名的访问权限
                       .allowedOrigins("*")
                       //是否允许客户端发cookie信息
                       .allowCredentials(true)
                       //哪些HTTP方法允许跨域访问
                       .allowedMethods("GET", "POST")
                       //允许HTTP请求中的携带哪些Header信息
                       .allowedHeaders("*");
                       //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
                       //.exposedHeaders("*");
           }
       }
      
    2. 需要客户端设置后才会发Cookie

       let xhr = new XMLHttpRequest();
       xhr.withCredentials = true;
      

前后端项目分离

前后端的协作模式发展

  1. 早期的前后端协作模式
    1. 前端:将UI设计的ps图切图标,然后编写静态页面html+css
    2. 后台:动态模板技术组装成HTML
    3. 缺点:浪费资源,每次后台返回的是整个页面的数据,即使点击某个按钮改变某一小块东西,也需要重新向后台请求服务器
  2. 前后端分离
    1. 前端:切图、页面、交互、路由、业务逻辑
    2. 后台:返回JSON
    3. 优点:节省资源,利用异步网络请求,后台只返回json数据,浏览器解析json数据,动态生成对应的HTML标签,显示到用户面前
  3. 前后端分离项目部署
    1. 前端项目部署在页面服务器(Nginx服务器)
    2. 后端项目部署在Tomcat服务器

前端项目FE

  1. FE文件夹下就一个index.xml文件和jquery.min.js,引用了jquery

     <!DOCTYPE html>
     <html lang="en">
     <head>
         <meta charset="UTF-8">
         <title>Title</title>
         <style>
             td {
                 border: 1px solid #000;
             }
         </style>
     </head>
     <body>
         This is index.html
        
         <button type="button" id="load-btn">加载用户信息</button>
        
         <img src="http://www.baidu.com/img/bd_logo.png">
        
     <script src="js/jquery.min.js"></script>
     <script>
         $(() => {
             $('#load-btn').click(() => {
                 $.getJSON('http://localhost:8080/users/list', (users) => {
                     const $table = $('<table>')
                     $(document.body).append($table)
        
                     for (const user of users) {
                         const $tr = $('<tr>')
                         $table.append($tr)
        
                         $tr.append(`<td>${user.name}</td>`)
                         $tr.append(`<td>${user.age}</td>`)
                     }
                 })
             })
         })
     </script>
     </body>
     </html>
    

后端项目

  1. 新建springboot项目BE
  2. pom.xml依赖如下

     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
         <version>2.3.4.RELEASE</version>
     </parent>
    
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
    
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
         <!--热部署-->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-devtools</artifactId>
             <scope>provided</scope>
         </dependency>
     </dependencies>
    
  3. 新建入口类BeApplication
  4. 新建application.yml

     server:
       port: 8080
        
     # 存放所有可以进行跨域的请求
     be:
       cors-origins:
         - http://localhost:63343
         - http://localhost:63342
         - http://127.0.0.1:5500
    
  5. 新建User模型类
  6. 新建控制器

     @RestController
     @RequestMapping("/users")
     //只针对某个Controller设置,*代表所有请求源
     //@CrossOrigin("*")
     //指定某个源才能访问,这个也可以放在某个方法上,只针对某个方法进行限制
     //@CrossOrigin("http://localhost:63343/")
     public class UserController {
         @GetMapping("/list")
         public List<User> list() {
             List<User> users = new ArrayList<>();
             for (int i = 0; i < 10; i++) {
                 users.add(new User("hahaha" + i, i + 20));
             }
             return users;
         }
     }
    
  7. 新建BeProperties

     //com.zh.prop
     @ConfigurationProperties("be")
     @Component
     public class BeProperties {
         private String[] corsOrigins;
        
         public String[] getCorsOrigins() {
             return corsOrigins;
         }
        
         public void setCorsOrigins(String[] corsOrigins) {
             this.corsOrigins = corsOrigins;
         }
     }
    
  8. 新建WebCfg类,全局配置

     //com.zh.cfg
     @Configuration
     public class WebCfg implements WebMvcConfigurer {
         @Autowired
         private BeProperties properties;
            
         @Override
         public void addCorsMappings(CorsRegistry registry) {
             // /**表示对所有的路径开放全局跨域访问权限
             registry.addMapping("/**")
                     //开放哪些IP 、端口、域名的访问权限
                     .allowedOrigins(properties.getCorsOrigins())
                     //是否允许客户端发cookie信息
                     .allowCredentials(true)
                     //哪些HTTP方法允许跨域访问
                     .allowedMethods("GET", "POST")
                     //允许HTTP请求中的携带哪些Header信息
                     .allowedHeaders("*");
                     //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
                     //.exposedHeaders("*");
         }
     }
    

运行项目

  1. FE用VSCode插件本地启动服务运行,假设:服务地址为http://127.0.0.1:5500
  2. BE直接通过IDEA入口类运行,进行测试
  3. 注意:参考MJ课堂代码【OldJiaKao】

Layui

  1. Layui是一款前端UI框架,可以帮助开发者快速搭建后台管理系统的前端页面
    1. 免费版:https://www.layui.com
    2. 专业版(收费版)
    3. LayuiMini(更强大的免费版):http://layuimini.99php.cn
    4. 本项目使用的版本是(单页版):https://github.com/zhongshaofa/layuimini/tree/v2-onepage,将代码下载到本地
  2. LayuiMini基本使用
    1. 下载完毕后,解压,将以下选中的内容全部放到项目中
    2. 在IDEA中->File->new->Module from existing sourc->打开下载解压后的文件夹->选择creat module from existing source->next->finish
    3. 打开index.html,点击右上角的浏览器图片,直接开启一个本地服务,在浏览器中打开http://localhost:63342/BE/layuimini-2-onepage/index.html?_ijt=7jb3re1852k9h9j5ch1gac4dr3
    4. 这个的目的就是可以让我们在写项目的时候直接参考,然后copy

前后端分离驾考项目

JiaKaoBE

  1. 新建后端工程JiaKaoBE
  2. 添加pom.xml依赖

     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
         <version>2.3.4.RELEASE</version>
     </parent>
    
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <druid.version>1.2.1</druid.version>
         <mybatis-plus.version>3.4.1</mybatis-plus.version>
     </properties>
    
     <dependencies>
    
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
         <!-- 数据库相关 -->
         <!-- mysql-->
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
         </dependency>
         <!-- 连接池-->
         <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>druid-spring-boot-starter</artifactId>
             <version>${druid.version}</version>
         </dependency>
         <!-- mybatis-plus,因为这个依赖mybaits,因此可以注释mybatis-spring-boot-starter依赖-->
         <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-boot-starter</artifactId>
             <version>${mybatis-plus.version}</version>
         </dependency>
    
    
         <!-- debug阶段使用,使用provided -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-configuration-processor</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-devtools</artifactId>
             <scope>provided</scope>
         </dependency>
         <!-- 单元测试-->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
    
     </dependencies>
    
     <build>
         <plugins>
             <plugin>
                 <groupId>org.mybatis.generator</groupId>
                 <artifactId>mybatis-generator-maven-plugin</artifactId>
                 <version>1.3.7</version>
                 <configuration>
                     <!-- 指定配置文件的位置 -->
                     <configurationFile>src/test/resources/generatorConfig.xml</configurationFile>
                     <overwrite>true</overwrite>
                     <verbose>true</verbose>
                 </configuration>
                 <!-- 插件依赖的第三方库 -->
                 <dependencies>
                     <dependency>
                         <groupId>mysql</groupId>
                         <artifactId>mysql-connector-java</artifactId>
                         <version>5.1.49</version>
                     </dependency>
                 </dependencies>
             </plugin>
         </plugins>
     </build>
    
  3. 添加入口类JiaKaoApplication
  4. 添加配置文件:
    1. application.yml

       # 设置数据库
       spring:
         datasource:
           type: com.alibaba.druid.pool.DruidDataSource
           driver-class-name: com.mysql.cj.jdbc.Driver
         profiles:
           # 使用开发环境,会使用application-dev.xml
           active: dev
       mybatis-plus:
         # 设置别名
         type-aliases-package: com.zh.jk.pojo
      
    2. application-dev.yml

       spring:
         datasource:
           username: root
           password: root
           url: jdbc:mysql://localhost:3306/jiakao?serverTimezone=GMT%2B8
              
       # 设置端口号
       server:
         port: 8888
              
       # 设置日志级别
       logging:
         level:
           com.zh.jk: debug
      
  5. 数据库创建:通过IDEA生成数据库jiakao,执行sql使用项目中sql文件夹中的new_jiakao.sql
  6. 通过EasyCode-MybatisCodeHepler插件(使用详情看后面章节EasyCode)自动生成dict_item、dict_type这两张表对应的下面层

     controller
     mapper
     pojo.po
     service
    
  7. 创建MyBatisPlusCfg类

     //com.zh.jk.common.cfg
     @Configuration
     @MapperScan("com.zh.jk.mapper")
     public class MyBatisPlusCfg {}
    
  8. 在DictTypeController中新增查询接口

     @RestController
     @RequestMapping("/dictTypes")
     public class DictTypeController {
         @Autowired
         private DictTypeService service;
        
         @GetMapping
         public List<DictType> list() {
             return service.list();
         }
     }
    
  9. 运行项目,浏览器输入:http://localhost:8888/dictTypes即可查询dict_type表中的所有数据了

数据字典类型模块开发

  1. 新建前端项目JiakaoFE
    1. 新建一个JiakaoFE文件夹,将JiakaoBE中的【前端模板】内容复制进去
    2. 在IDEA中->File->new->Module from existing sourc->打开JiakaoBE文件夹->选择creat module from existing source->next->finish
    3. 然后打开index.html,修改访问后台的端口

       Commons.baseUrl = 'http://localhost:8888/'
      
    4. 点击右上角浏览器图标,则启动本地服务器。
  2. 查看JiakaoFE下api下的init.json

     {
       "title": "数据字典类型",
       "href": "page/metadata/dictType/list.html",
       "icon": "fa fa-cube"
     },
    
  3. 在page下新建目录metadata/dictType
    1. 列表页面:新建list.html,写list代码(略)
    2. 添加、编辑页面:新建save.html,写save代码(略)
  4. 配置JiakaoBE跨域
    1. application-dev.yml新增跨域配置

       jk:
         cfg:
           cors-origins:
             - http://localhost:63342
      
    2. 添加对应属性JkProperties类

       //com.zh.jk.common.prop
       @ConfigurationProperties("jk")
       @Component
       @Data
       public class JkProperties {
           private Cfg cfg;
                  
           //内部类
           @Data
           public static class Cfg {
               private String[] corsOrigins;
           }
              
       }
      
    3. 添加配置文件,设置跨区域

       //com.zh.jk.common.cfg
       @Configuration
       public class WebCfg implements WebMvcConfigurer {
           @Autowired
           private JkProperties properties;
              
           @Override
           public void addCorsMappings(CorsRegistry registry) {
               // /**表示对所有的路径开放全局跨域访问权限
               registry.addMapping("/**")
                       // 开放哪些IP、端口、域名的访问权限
                       .allowedOrigins(properties.getCfg().getCorsOrigins())
                       // 是否允许发送Cookie信息
                       .allowCredentials(true)
                       // 哪些HTTP方法允许跨域访问
                       .allowedMethods("GET", "POST");
           }
       }
      
  5. 添加分页功能
    1. MyBatisPlusCfg添加配置

       @Bean
       public MybatisPlusInterceptor mybatisPlusInterceptor(){
           MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
           PaginationInnerInterceptor innerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
           //当页数超过总页数时,自动跳回到第1页
           innerInterceptor.setOverflow(true);
           interceptor.addInnerInterceptor(innerInterceptor);
           return interceptor;
       }
          
       @Bean
       public ConfigurationCustomizer configurationCustomizer(){
           return configuration -> configuration.setUseDeprecatedExecutor(false);
       }
      
  6. 设置mybatis-plus插件自动生成主键的方式
    1. 这个必须要配置,因为添加一条数据的时候,需要mybatis-plus需要知道按照如何方式生成新的主键
    2. 打开appliaction.yml

       mybatis-plus:
         # 设置别名
         type-aliases-package: com.mj.jk.pojo
         # 设置主键的生成策略
         global-config:
           db-config:
             # 自动增长
             id-type: auto
      
  7. service层改造

     //com.zh.jk.service
     public interface DictTypeService extends IService<DictType> {
         IPage<DictType> list(long page, long limit, String keyword);
     }
     //com.zh.jk.service.impl
     @Service
     @Transactional
     public class DictTypeServiceImpl extends ServiceImpl<DictTypeMapper, DictType> implements DictTypeService {
         @Autowired
         private DictTypeMapper mapper;
        
        
         @Override
         public IPage<DictType> list(long page, long limit, String keyword) {
             Page<DictType> mpPage = new Page<>(page,limit);
             //配置搜索条件
             LambdaQueryWrapper<DictType> queryWrapper = new LambdaQueryWrapper<>();
             if(!StringUtils.isEmpty(keyword)){
                 queryWrapper.like(DictType::getName,keyword).or()
                         .like(DictType::getValue,keyword).or()
                         .like(DictType::getIntro,keyword);
             }
             //根据id降序
             queryWrapper.orderByDesc(DictType::getId);
             return mapper.selectPage(mpPage,queryWrapper);
         }
     }
    
  8. 设置DictTypeController返回数据格式
    1. layui官方文档说明,返回数据格式必须是

       {
       "msg": "",
       "code": 0,
       "data": []
       }
      
    2. DictTypeController改造如下

       @RestController
       @RequestMapping("/dictTypes")
       public class DictTypeController {
           @Autowired
           private DictTypeService service;
              
           @GetMapping
           public Map<String,Object> list(long page, long size,String keyword) {
               IPage<DictType> iPage = service.list(page,size,keyword);
               Map<String,Object> map = new HashMap<>();
               map.put("code",0);
               map.put("msg","");
               map.put("count",iPage.getTotal());
               map.put("data",iPage.getRecords());
               return map;
           }
           @PostMapping("/remove")
           public Map<String,Object> remove(String id){
               //id = 10 或者 id = 10,20,30
               String[] idStrs =  id.split(",");
              
               //批量删除
               //数组转化为集合
               //Arrays.asList(idStrs)
               if (service.removeByIds(Arrays.asList(idStrs))){
                   Map<String,Object> map = new HashMap<>();
                   map.put("code",0);
                   map.put("msg","删除成功");
                   return map;
               }else {
                   throw new RuntimeException("删除失败");
               }
           }
           @PostMapping("/save")
           public Map<String,Object> save(DictType dictType){
               if ( service.saveOrUpdate(dictType)){
                   Map<String,Object> map = new HashMap<>();
                   map.put("code",0);
                   map.put("msg","保存成功");
                   return map;
               }else {
                   throw new RuntimeException("保存失败");
               }
           }
       }
      
    3. 重新运行JiakaoBE项目

  9. 重新打开JiakaoFE的index.html:点击数据字典类型,可以正常实现 添加、删除、编辑、搜索功能
  10. 通过同样的方法快速实现【省份】模块
Table of Contents