项目之前后端分离及导航栏标签列表(7)
🌊 作者主页:海拥
🌊 简介:🏆CSDN全栈领域优质创作者、🥇HDZ核心组成员、🥈蝉联C站周榜前十
24. 前后端分离
前端:客户端,例如网页及相关组件都是属于前端开发领域;
后端:服务器端;
在传统的开发模式下,当服务器端处理了某种请求后,就会执行转发或重定向操作,使得客户端的浏览器访问另一个页面,整个开发过程,或需要开发的组件都是由服务器端开发人员完成的(即使使用到了前端的网页技术,甚至有专门的人员开发网页,最终也需要整合到服务器端的项目中,从项目的角度来看,并没有分离)。
如需希望实现前后端分离,首先,就要使得服务器端不会过度甚至根本就不依赖网页,当处理了客户端的请求后,直接将相关数据响应到客户端去,完全不关心数据如何显示的问题,各客户端发出请求后将收到这些数据,然后自行根据客户端技术进行处理即可。
使用前后端分离的做法,可以使得开发人员是分离的,即前端开发人员开发前端的产品,后端开发人员开发服务器端需要实现的功能,分工明确,同时,由于后端不再处理页面显示,不需要使用到网页,在处理请求后,响应时,响应的数据内容将更加少,则传输数据耗时更短,流量开销更小,用户体验更好,同时,这种模式更加适用于多种不同的客户端。
简单来说:前后端分离的典型特征就是“服务器端处理完请求后,不再关心数据的呈现的问题,只是单纯的将数据响应到客户端,由客户端自行处理数据的显示”。
在前后端分离的做法中,后端负责提供“接口”,此“接口”表示一种对接的方式,通常表现为服务器端项目中的控制器组件,它负责与前端进行“对接”,前端只需要根据后端的约定(请求路径、请求参数、请求类型等)来提交请求,就可以得到某种数据结果,前端根本不需要关心后端是如何实现这些功能的,当然,后端也不会向前端暴露实现的细节,基于这样的特点,后端提供的数据处理功能,对于前端来说,也是API。
通常,如果服务器端向客户端提供API接口,在URL中通常会体现出相关的字样,例如:
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
}
关于请求路径的设计,并没有绝对的要求,也没有相关的规定,如果自己没有设计URL的思路,可以采取:
请求某种类型的数据列表:/api/v1/users
请求某种类型的某个数据:/api/v1/users/9527
请求操作某种类型的某个数据:/api/v1/users/9527/update
大致原则是:
- 访问数据列表时,如果访问列表的方法非常单一(例如用户列表,通常就只有1种显示条件,而商品列表却可以有很多种条件),在设计URL时,数据种类名称使用复数,右侧不再添加任何字符串;
- 访问某条数据时,在以上基础上,在右侧添加数据的唯一标识,通常是数据的id,例如:
/api/版本/数据种类/id
; - 对某种数据进行操作时,在以上基础上,在右侧添加需要执行的命令,例如:
/api/版本/数据种类/id/数据操作
; - 以上设计方式仅供参考。
25. 显示导航栏标签列表-持久层
从tag数据表中查询数据,就可以获取标签的数据列表,需要执行的SQL语句大致是:
SELECT id, name FROM tag ORDER BY id
为了更直接的封装查询结果,应该先在cn.tedu.straw.portal.vo
包下创建TagVO
类,用于封装以上查询的结果:
package cn.tedu.straw.portal.vo;
@Data
public class TagVO {
private Integer id;
private String name;
}
由于使用了新的数据类型封装查询结果,就需要自行编写持久层的功能!
先在TagMapper
接口中添加抽象方法:
/**
* 查询所有标签数据
*
* @return 所有标签数据的列表
*/
List<TagVO> findAll();
再在TagMapper.xml
中配置抽象方法映射的SQL语句:
<select id="findAll" resultType="cn.tedu.straw.portal.vo.TagVO">
SELECT
id, name
FROM
tag
ORDER BY
id
</select>
创建TagMapperTests
测试类,进行单元测试:
package cn.tedu.straw.portal.mapper;
@SpringBootTest
@Slf4j
public class TagMapperTests {
@Autowired
TagMapper mapper;
@Test
void findAll() {
List<TagVO> tags = mapper.findAll();
log.debug("tags count = {}", tags.size());
for (TagVO tag : tags) {
log.debug(">>> tag : {}", tag);
}
}
}
26. 显示导航栏标签列表-业务层
在ITagService
中添加抽象方法:
public interface ITagService extends IService<Tag> {
/**
* 获取标签列表
* @return 标签列表
*/
List<TagVO> getTags();
/**
* 获取缓存的标签列表
* @return 缓存的标签列表
*/
List<TagVO> getCachedTags();
}
在TagServiceImpl
中实现抽象方法:
@Service
@Slf4j
public class TagServiceImpl extends ServiceImpl<TagMapper, Tag> implements ITagService {
@Autowired
private TagMapper tagMapper;
/**
* 缓存的标签列表
*/
private List<TagVO> tags = new CopyOnWriteArrayList<>();
@Override
public List<TagVO> getTags() {
// 判断有没有必要锁住代码
if (tags.isEmpty()) {
// 锁住代码
synchronized (tags) {
// 判断有没有必要重新加载数据
if (tags.isEmpty()) {
tags.addAll(tagMapper.findAll());
log.debug("create tags cache ...");
log.debug(">>> tags : {}", tags);
}
}
}
return tags;
}
@Override
public List<TagVO> getCachedTags() {
return tags;
}
}
以上做法是一种缓存的做法,由于Spring管理对象是单例的,所以,当项目运行时,以上TagServiceImpl
类的对象只会存在1个,其中的tags
属性肯定也只会有1个,并且,Spring通过单例状态管理的对象是常驻内存的,所以,tags
存储的数据会一直在内存中,并不会消失,就起到了“缓存”的作用,当频繁获取标签数据时,都直接将tags
数据返回即可,并不需要反复查询数据库!
当然,使用了以上缓存后,每次获取标签数据时,都是获取的以上缓存数据,即使数据库的数据被修改了,以上缓存也不会更新,就会导致获取到的数据不准确!可以在数据发生变化后将缓存清空,则缓存数据会重新加载,缓存中的数据就是新的数据了!也可以使用定时更新的机制,也就是每间隔一定的时间,自动将缓存中的数据清空,则下次尝试访问数据时,由于缓存中没有数据,就会从数据库中进行查询,从而得到新的、准确的数据!
关于定期清空缓存,可以使用计划任务来实现:
@Component
@EnableScheduling
@Slf4j
public class CacheSchedule {
@Autowired
private ITagService tagService;
@Scheduled(initialDelay = 10 * 60 * 1000, fixedRate = 10 * 60 * 1000)
public void clearCache() {
tagService.getCachedTags().clear();
log.debug("clear tags cache ...");
}
}
创建TagServiceTests
测试类,进行测试:
@SpringBootTest
@Slf4j
public class TagServiceTests {
@Autowired
ITagService service;
@Test
void getTags() {
List<TagVO> tags = service.getTags();
log.debug("tags count = {}", tags.size());
for (TagVO tag : tags) {
log.debug(">>> tag : {}", tag);
}
}
}
27. 显示导航栏标签列表-控制器层
由于现在发出请求后,需要响应数据到客户端,所以,在表示响应结果的R
类中,需要添加新的属性用于表示“响应到客户端的数据”,用户提交不同的请求时,期望得到的数据可能是不同的,例如,可能希望得到当前用户的信息,或当前用户发布的提问的列表,或当前用户的收藏列表等,所以,在声明“数据”的类型时,要么使用Object
,可以表示任何类型,要么使用泛型,使用时再决定具体的类型!
以使用泛型为例,在R
类中添加属性:
private T data;
由于类中使用了泛型的占位符,必须在类的声明中也补充声明占位符:
public class R<T> {
}
同时,为了更加快捷的响应结果,还添加方法:
public static <T> R ok(T data) {
return new R<T>().setState(State.OK).setData(data);
}
然后,在TagController
中,处理请求:
@RestController
@RequestMapping("/api/v1/tags")
public class TagController {
@Autowired
private ITagService tagService;
// http://localhost:8080/api/v1/tags
// http://localhost:8080/api/v1/tags/
@GetMapping("")
public R<List<TagVO>> getTags() {
return R.ok(tagService.getTags());
}
}
28. 显示导航栏标签列表-前端页面
先将static下的question文件夹拖拽到templates文件夹下,拖拽时弹出的对话框中不要勾选任何选项,直接确定即可。
在SystemController
中添加处理请求的方法,以转发页面:
@GetMapping("/question/create.html")
public String createQuestion() {
return "question/create";
}
完成后,通过http://localhost:8080/question/create.html即可打开“发表提问”的页面。
在页面的顶部导航区域,需要显示问题的标签列表。
在question/create.html页面中:
- 约184行:为
<div>
添加id="navTagsApp"
- 约187行:为
<a>
添加v-for="tag in tags"
,为<small>
添加v-text="tag.name"。
以上v-for
是用于遍历的,添加在<a>
标签上,就会遍历生成当前<a>
标签的全部代码,其表达式中tag in tags
表示在Vue中存在名为tags
的数据,该数据应该是数组类型的,在遍历过程中,每个数组元素都使用tag
作为名称,该语法可参考Java语法中的增强for循环;以上v-text
是用于绑定<small>
标签中将要显示的文本,由于它在<a>
标签的内部,所以可以访问到遍历过程中得到的tag
数据,服务器端向客户端响应的“标签Tag”数据中既包含id
也包括name
,此处需要显示name
,所以表达式的值是tag.name
。
当前页面中,显示导航栏的标签列表的操作是多个页面都需要使用的,为了便于统一使用,应该将相关的JS代码写在独立的.js
文件中,则多个页面都可以引用该文件!
在static文件夹下默认就存在js文件夹,该文件夹中已经存在一些测试使用的JS文件,先将这些文件全部删除!然后,在js文件夹,创建commons文件夹,并在这个文件夹中创建nav_tags.js文件。
先在nav_tags.js中编写测试代码:
let navTagsApp = new Vue({
el: '#navTagsApp',
data: {
tags: [
{ id: 1, name: '第1阶段' },
{ id: 2, name: '第2阶段' },
{ id: 3, name: '第3阶段' },
{ id: 4, name: '第4阶段' }
]
}
});
并在create.html中引用以上js文件:
<script src="/js/commons/nav_tags.js"></script>
测试无误后,完整的JS代码:
let navTagsApp = new Vue({
el: '#navTagsApp',
data: {
tags: [
{ id: 1, name: '第1阶段' },
{ id: 2, name: '第2阶段' },
{ id: 3, name: '第3阶段' },
{ id: 4, name: '第4阶段' }
]
},
methods: {
loadTags: function() {
$.ajax({
url: '/api/v1/tags',
type: 'get',
dataType: 'json',
success: function(json) {
navTagsApp.tags = json.data;
}
});
}
},
created: function () {
this.loadTags();
}
});
29. 发布问题表单中显示标签下拉列表
在question/create.html中,第209行,将原有的<select>
标签整个改为:
<v-select :options="tags" v-model="selectedTags"
multiple required placeholder="请选择问题的分类标签(可多选)">
</v-select>
第190行,将<div>
标签的原id="app"
改为id="createQuestionApp"。
在js文件夹下创建question文件夹,并在这个文件夹中创建create.js文件,用于编写当前页面中需要执行的代码。
在create.html中引用以上新创建的js文件:
<script src="/js/question/create.js"></script>
接下来,在create.js中添加测试代码:
Vue.component('v-select', VueSelect.VueSelect);
let createQuestionApp = new Vue({
el: '#createQuestionApp',
data: {
tags: ['Spring', 'SpringMVC', 'MyBatis', 'SpringBoot'],
selectedTags: []
}
});
完成后,重新打开页面,在问题标签的下拉列表中就可以看到以上定义的4个选项。
一般情况下,客户端向服务器提交数据时,可以选择的话,应该尽量提交id
相关的值,而不是提交字符串的值!假设某标签的id
是8
,名称是SpringBoot
,最终客户端提交数据时,应该将8
提交到服务器端,而不是把SpringBoot
提交到服务器端!
以上tags
的值是字符串数组,最终提交时,selectedTags
中也会是字符串数据!应该生成列表项时,为每个标签数据指定id
,以保证用户选中某些选项后,可以获取这些标签数据的id,最终才可以将这些id提交到服务器端!
为v-select
绑定的:options
就是列表项数据,该数据可以是JSON对象的数组,默认情况下,每个JSON对象中的label
属性表示列表项显示的文本,value
属性表示将要提交的值,所以,可以将以上测试代码改为:
Vue.component('v-select', VueSelect.VueSelect);
let createQuestionApp = new Vue({
el: '#createQuestionApp',
data: {
tags: [
{ label: 'MyBatis Plus', value: 1 },
{ label: 'Spring Security', value: 2 },
{ label: 'Spring Validation', value: 3 },
{ label: 'Lombok', value: 4 },
{ label: 'Vue', value: 5 }
],
selectedTags: []
}
});
作业
1. 显示真实的问题标签到下拉列表
提示:当从服务器端获取到数据后,对数据进行遍历,可以:
for (let i = 0; i < json.data.length; i++) {
let op = {
label: json.data[i].name,
value: json.data[i].id
};
tags[i] = op;
}
2. 显示老师列表到下拉列表
需要从持久层到业务层,到控制器层,到前端页面,层层开发,每开发一层,及时测试。
查询老师列表的SQL语句:
select id, nickname, gender, phone from user where type=1 order by id;
先创建TeacherVO
类。
在UserMapper
接口中添加:
List<TeacherVO> findTeachers();
在UserMapper.xml
中配置映射。
在UserMapperTests
中测试。
在IUserService
中添加:
List<TeacherVO> findTeachers();
List<TeacherVO> findCachedTeachers();
在UserServiceImpl
中实现以上2个方法,实现过程可参考TagServiceImpl
。
在CacheSchedule
的计划任务中,清除Tag
数据缓存时,一并清除Teacher
数据缓存。
在UserServiceTests
中测试。
将请求路径设计为http://localhost:8080/api/v1/users/teacher/list,处理请求的方法的返回值是R<List<TeacherVO>>
。
在前端页面中,参考“标签”的做法,显示“老师”的下拉列表。
🌊 面试题库:Java、Python、前端核心知识点大全和面试真题资料
🌊 电子图书:图灵程序丛书 300本、机械工业出版社6000册免费正版图书
🌊 办公用品:精品PPT模板几千套,简历模板一千多套
🌊 学习资料:2300套PHP建站源码,微信小程序入门资料
公众号【海拥】内回复【资源】获取以上所有资料
我已经写了很长一段时间的技术博客,这是我的一篇关于整合基于注解的SSM框架小结。我乐于通过文章分享技术与快乐。您可以访问我的博客主页: 华为云-海拥、我的个人博客:haiyong.site 以了解更多信息。希望你们会喜欢!
- 点赞
- 收藏
- 关注作者
评论(0)