项目实战二-SpringMVC实现CORS、前后端分离、元数据模块-数据字典类型
同源策略
- 浏览器有同源策略(Same-Origin Policy)
- 它规定了:默认情况下,AJAX请求只能发送给同源的URL,同源是指3个相同:协议、域名(IP)、端口
- img、script、link、iframe、video、audio等标签不受同源策略的约束
- 解决AJAX跨域请求的常用方法:CORS(Cross-Origin Resource Sharing),跨域资源共享
- CORS的实现需要客户端和服务器同时支持
- 客户端:所有的浏览器都支持(IE至少是IE10版本)
- 服务器:需要返回相应的响应头(比如Access-Control-Allow-Origin),告知浏览器这是一个允许跨域访问的请求
SpringMVC实现CORS
-
局部设置
@RestController @RequestMapping("/users") //只针对某个Controller设置,*代表所有请求源 //@CrossOrigin("*") //指定某个源才能访问,这个也可以放在某个方法上,只针对某个方法进行限制 @CrossOrigin("http://localhost:63343/") public class UserController { } -
全局设置
-
后台项目新建一个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("*"); } } -
需要客户端设置后才会发Cookie
let xhr = new XMLHttpRequest(); xhr.withCredentials = true;
-
前后端项目分离
前后端的协作模式发展
- 早期的前后端协作模式
- 前端:将UI设计的ps图切图标,然后编写静态页面html+css
- 后台:动态模板技术组装成HTML
- 缺点:浪费资源,每次后台返回的是整个页面的数据,即使点击某个按钮改变某一小块东西,也需要重新向后台请求服务器
- 前后端分离
- 前端:切图、页面、交互、路由、业务逻辑
- 后台:返回JSON
- 优点:节省资源,利用异步网络请求,后台只返回json数据,浏览器解析json数据,动态生成对应的HTML标签,显示到用户面前
- 前后端分离项目部署
- 前端项目部署在页面服务器(Nginx服务器)
- 后端项目部署在Tomcat服务器
前端项目FE
-
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>
后端项目
- 新建springboot项目BE
-
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> - 新建入口类BeApplication
-
新建application.yml
server: port: 8080 # 存放所有可以进行跨域的请求 be: cors-origins: - http://localhost:63343 - http://localhost:63342 - http://127.0.0.1:5500 - 新建User模型类
-
新建控制器
@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; } } -
新建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; } } -
新建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("*"); } }
运行项目
- FE用VSCode插件本地启动服务运行,假设:服务地址为http://127.0.0.1:5500
- BE直接通过IDEA入口类运行,进行测试
- 注意:参考MJ课堂代码【OldJiaKao】
Layui
- Layui是一款前端UI框架,可以帮助开发者快速搭建后台管理系统的前端页面
- 免费版:https://www.layui.com
- 专业版(收费版)
- LayuiMini(更强大的免费版):http://layuimini.99php.cn
- 本项目使用的版本是(单页版):https://github.com/zhongshaofa/layuimini/tree/v2-onepage,将代码下载到本地
- LayuiMini基本使用
- 下载完毕后,解压,将以下选中的内容全部放到项目中
- 在IDEA中->File->new->Module from existing sourc->打开下载解压后的文件夹->选择creat module from existing source->next->finish
- 打开index.html,点击右上角的浏览器图片,直接开启一个本地服务,在浏览器中打开
http://localhost:63342/BE/layuimini-2-onepage/index.html?_ijt=7jb3re1852k9h9j5ch1gac4dr3 - 这个的目的就是可以让我们在写项目的时候直接参考,然后copy
前后端分离驾考项目
JiaKaoBE
- 新建后端工程JiaKaoBE
-
添加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> - 添加入口类JiaKaoApplication
- 添加配置文件:
-
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 -
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
-
- 数据库创建:通过IDEA生成数据库jiakao,执行sql使用项目中sql文件夹中的new_jiakao.sql
-
通过EasyCode-MybatisCodeHepler插件(使用详情看后面章节EasyCode)自动生成dict_item、dict_type这两张表对应的下面层
controller mapper pojo.po service -
创建MyBatisPlusCfg类
//com.zh.jk.common.cfg @Configuration @MapperScan("com.zh.jk.mapper") public class MyBatisPlusCfg {} -
在DictTypeController中新增查询接口
@RestController @RequestMapping("/dictTypes") public class DictTypeController { @Autowired private DictTypeService service; @GetMapping public List<DictType> list() { return service.list(); } } - 运行项目,浏览器输入:
http://localhost:8888/dictTypes即可查询dict_type表中的所有数据了
数据字典类型模块开发
- 新建前端项目JiakaoFE
- 新建一个JiakaoFE文件夹,将JiakaoBE中的【前端模板】内容复制进去
- 在IDEA中->File->new->Module from existing sourc->打开JiakaoBE文件夹->选择creat module from existing source->next->finish
-
然后打开index.html,修改访问后台的端口
Commons.baseUrl = 'http://localhost:8888/' - 点击右上角浏览器图标,则启动本地服务器。
-
查看JiakaoFE下api下的init.json
{ "title": "数据字典类型", "href": "page/metadata/dictType/list.html", "icon": "fa fa-cube" }, - 在page下新建目录
metadata/dictType- 列表页面:新建list.html,写list代码(略)
- 添加、编辑页面:新建save.html,写save代码(略)
- 配置JiakaoBE跨域
-
application-dev.yml新增跨域配置
jk: cfg: cors-origins: - http://localhost:63342 -
添加对应属性JkProperties类
//com.zh.jk.common.prop @ConfigurationProperties("jk") @Component @Data public class JkProperties { private Cfg cfg; //内部类 @Data public static class Cfg { private String[] corsOrigins; } } -
添加配置文件,设置跨区域
//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"); } }
-
- 添加分页功能
-
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); }
-
- 设置mybatis-plus插件自动生成主键的方式
- 这个必须要配置,因为添加一条数据的时候,需要mybatis-plus需要知道按照如何方式生成新的主键
-
打开appliaction.yml
mybatis-plus: # 设置别名 type-aliases-package: com.mj.jk.pojo # 设置主键的生成策略 global-config: db-config: # 自动增长 id-type: auto
-
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); } } - 设置DictTypeController返回数据格式
-
layui官方文档说明,返回数据格式必须是
{ "msg": "", "code": 0, "data": [] } -
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("保存失败"); } } } -
重新运行JiakaoBE项目
-
- 重新打开JiakaoFE的index.html:点击数据字典类型,可以正常实现 添加、删除、编辑、搜索功能
- 通过同样的方法快速实现【省份】模块