超详细!利用SpringBoot+SpringCloud做一个问答项目(十一)

目录

一、发布问题---控制器层

二、发布问题---前端页面

1.使用模拟数据显示下拉菜单中的选项

2.使用真实数据显示“问题标签列表”下拉菜单

3.使用真实数据显示“老师列表”下拉菜单

4.通过前端页面发布问题

一、发布问题---控制器层


当前用户登录是在straw-gateway的服务器上处理的,当登录成功后,表示该用户的信息的Session数据将保存在straw-gateway的服务器的内存,而其它服务器(例如straw-api-question)需要读取Session中的数据以识别用户的身份,及读取相关信息,但是,其它服务器不可能访问straw-gateway服务器的内存!为了实现共享Session,可以在登录成功时,将用户的Session数据保存到Redis服务器中,而不再是保存在内存中,当其它服务器需要获取Session数据时,从Redis服务器中直接获取即可!

为了实现这个目标,需要先添加相关依赖,参考代码:

<!-- https://mvnrepository.com/artifact/org.springframework.session/spring-session-data-redis -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
    <version>2.3.0.RELEASE</version>
</dependency>

先在父级项目中管理以上依赖,然后,在straw-gateway中添加该依赖,同时,在straw-gateway中还需要添加spring-boot-starter-data-redis依赖:

straw-gatewayapplication.properties中配置“使用Redis保存Session数据”:

最后,在StrawGatewayApplication类的声明之前添加@EnableRedisHttpSession注解(也可以不添加):

完成后,启动straw-gateway项目(其它的项目可以暂时不启动,在控制台会提示一些错误信息,可以无视),在浏览器打开登录页面,输入正确的登录信息,登录成功后,在终端通过redis-cli登录Redis客户端控制台,输入keys *即可看到在Redis存在Session数据:

已经能够将Session保存到Redis中之后,在straw-api-question中就需要读取Session中的信息,由于Session信息是Spring Security框架组织的,存入的是LoginUserInfo类型的对象,则取出来也是LoginUserInfo类型的,则straw-api-question项目必须添加Spring Security的依赖,否则,将无法识别,并且,也添加spring-session-data-redis依赖:

由于添加了Spring Security依赖后,当前项目默认就会要求所有请求都是需要登录的!但是,在当前集群中,应该使用网关统一处理登录相关的验证及授权,其它各微服务项目是不需要验证登录的,所以,在straw-api-question项目添加配置,对所有请求直接许可,不再验证用户的身份!这个问题在此前开发straw-api-user时也出现过,则直接将straw-api-user中的WebSecurityConfigurer配置类复制到straw-api-questioncn.tedu.straw.api.question.security包中即可:

并且,在StrawApiQuestionApplication中添加@EnableWebSecurity注解:

完成后,已经可以实现straw-gateway写入Session,由其它服务器读取Session的操作了!但是,除了登录以外的各功能都不是写在straw-gateway项目中的,是客户端直接访问straw-gateway网关,然后由straw-gateway网关再将请求转发到其它服务器,由其它服务器负责具体的处理!

在默认情况下,straw-gateway中使用的Zuul网关在转发时,会将请求(Request)中的请求头(Request Headers)中的Cookie和Set-Cookie视为敏感信息,在转发时不会转发这2项数据,就会导致后面的服务器(例如straw-api-question运行所在的服务器)收到的请求中,请求头不会包含Cookie和Set-Cookie的数据,就无法读取Session(无论Session在哪里都读不到)!为了解决这个问题,需要在straw-gatewayapplication.properties中添加配置,将Cookie和Set-Cookie设置为“不敏感的”,则在转发时就会携带请求头中的Cookie和Set-Cookie,以至于后续的其它服务器可以访问到Session数据:

以上配置的sensitive-headers属性的作用就是“配置敏感请求头”,在等于号的右侧没有写任何值,则表示“任何请求头中的数据都是不敏感的”。

完成后,暂时无法验证以上操作是否成功,应该尝试从控制器中读取Session中保存的数据!

首先,在straw-api-question 项目中添加Spring Validation的依赖,以应用Spring Validation的验证机制。

应该在PostQuestionDTO类的各属性之前添加Spring Validation的相关注解,用于约定客户端提交的请求参数的基本格式,例如:

package cn.tedu.straw.api.question.dto;

import lombok.Data;
import lombok.experimental.Accessors;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;

@Data
@Accessors(chain = true)
public class PostQuestionDTO implements Serializable {

    @NotNull(message = "发布问题失败!请填写问题的标题!")
    @Size(min = 2, max = 100, message = "发布问题失败!标题的长度必须是2~100个字符之间!")
    private String title;
    
    private String content;
    
    private Integer[] tagIds;
    
    private Integer[] teacherIds;

}

关于数据的格式的验证规则,应该自行决定,不同的项目、不同的功能,验证规则可能都不相同。

当需要获取当前登录的用户信息时,在控制器中,在处理请求的方法的参数列表中,通过@AuthenticationPricipal注解装配LoginUserInfo类型的参数,在处理请求过程中,通过该参数对象即可获取相关信息。

QuestionController中进行配置,以处理“发布问题”的请求:

package cn.tedu.straw.api.question.controller;


import cn.tedu.straw.api.question.dto.PostQuestionDTO;
import cn.tedu.straw.api.question.service.IQuestionService;
import cn.tedu.straw.commons.ex.IllegalParameterException;
import cn.tedu.straw.commons.security.LoginUserInfo;
import cn.tedu.straw.commons.util.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author tedu.cn
 * @since 2020-09-16
 */
@RestController
@RequestMapping("/v1/questions")
@Slf4j
public class QuestionController {

    @Autowired
    IQuestionService questionService;

    // http://localhost:8081/v1/questions/post?title=TestTitle&content=TestContent&tagIds=2&tagIds=5&teacherIds=1&teacherIds=3&teacherIds=5
    // http://localhost/api-question/v1/questions/post?title=TestTitle2&content=TestContent2&tagIds=2&tagIds=5&teacherIds=1&teacherIds=3&teacherIds=5
    @RequestMapping("/post")
    public R post(@Valid PostQuestionDTO postQuestionDTO,
                  BindingResult bindingResult,
                  @AuthenticationPrincipal LoginUserInfo loginUserInfo) {
        if (bindingResult.hasErrors()) {
            String errorMessage = bindingResult.getFieldError().getDefaultMessage();
            throw new IllegalParameterException(errorMessage);
        }
        log.debug("从Session中获取当前登录的用户信息:{}", loginUserInfo);
        Integer userId = loginUserInfo.getId();
        String userNickName = loginUserInfo.getNickname();
        log.debug(">>> Session中的用户id={}", userId);
        log.debug(">>> Session中的用户昵称={}", userNickName);
        questionService.postQuestion(postQuestionDTO, userId, userNickName);
        return R.ok();
    }

}

完成后,先重启straw-eureka-server,再重启2个api项目,最后重启straw-gateway,在浏览器中,通过例如 http://localhost/api-question/v1/questions/post?title=TestTitle2&content=TestContent2&tagIds=2&tagIds=5&teacherIds=1&teacherIds=3&teacherIds=5 这样的路径即可发布问题(首次访问会先重定向到登录页,登录成功后即可发布成功)。

二、发布问题---前端页面


1.使用模拟数据显示下拉菜单中的选项

在页面中的选择“问题标签”的下拉列表是通过Vue-Select插件制作的效果:

原本的HTML部分的代码是:

可以看到,目前的模拟数据是直接写在HTML代码中的,不便于调整,当需要通过Vue来管理这个列表中的选项时,应该先确定当前“发布问题”的表单区域范围,为这个范围的父级标签添加id属性,用于创建Vue对象!

当添加id后,就可以基于这个id对应的标签来创建Vue对象了!在static/js下创建question/create.js文件:

然后,在create.js中就可以创建Vue对象了:

create.html中引用该文件:

接下来,需要调整HTML部分的下拉菜单的语法,改为:

在HTML中,可以在控件(例如输入框、按钮、下拉菜单、单选按钮、复选框等)上配置v-model属性,该属性是用于绑定控件的value属性的!

并且,在create.js中声明对应的属性,并为属性赋予测试数据作为值:

以上测试数据中,labelvalue这2个名称是固定的,是Vue-Select处理下拉列表选项时默认使用的“列表项显示的文字”和“选中该选项后将提交的id”的名称。

完成后,重新启动straw-gateway项目(其它相关项目应该也是启动状态),刷新“发布问题”页面,即可看到配置的模拟数据,并且最多只能选择3项。

使用同样的做法,使用模拟数据显示老师的列表:

完成后,再次重启项目检查运行效果。

2.使用真实数据显示“问题标签列表”下拉菜单

先向服务器端发送请求,尝试获取“问题标签列表”,然后,将服务器响应的“问题标签列表”赋值给Vue的tags属性即可!

3.使用真实数据显示“老师列表”下拉菜单

目前,并没有控制器能处理“获取老师列表”的请求,所以,需要先实现该功能!

先在数据库中直接将某些用户数据标识为“老师”:

然后,遵循“持久层 -> 业务层 -> 控制器层”的开发顺序,开发“获取老师列表”的功能!

查询老师列表时,需要执行的SQL语句大致是:

select id, nickname from user where account_type=1 order by id

目前,并没有哪个数据类型适合封装此次的查询结果(如果使用User类是可以封装的,但是,User类中的属性太多,用不上),则在straw-commonscn.tedu.straw.commons.vo包中创建TeacherSelectOptionVO类:

@Data
public class TeacherSelectOptionVO implements Serializable {
    private Integer id;
    private String nickname;
}

straw-api-user项目中,在cn.tedu.straw.api.user.mapper包的UserMapper接口中添加抽象方法:

/**
 * 查询老师的列表,用于显示下拉菜单
 *
 * @return 老师的列表
 */
List<TeacherSelectOptionVO> findTeachers();

然后,在UserMapper.xml中配置以上抽象方法的映射:

<select id="findTeachers" resultType="cn.tedu.straw.commons.vo.TeacherSelectOptionVO">
    select id, nickname from user where account_type=1 order by id
</select>

完成后,应该“写一层,测一层”,在testcn.tedu.straw.api.user.UserMapperTests中编写并执行单元测试:

@Test
void findTeachers() {
    List<TeacherSelectOptionVO> teachers = mapper.findTeachers();
    System.err.println("老师列表长度:" + teachers.size());
    for (TeacherSelectOptionVO teacher : teachers) {
        System.err.println(">>> " + teacher);
    }
}

当测试通过后,继续向后完成业务层的开发,先在IUserService接口中声明抽象方法:

/**
 * 查询老师的列表,用于显示下拉菜单
 *
 * @return 老师的列表
 */
List<TeacherSelectOptionVO> getTeacherList();

然后,在UserServiceImpl中实现以上抽象方法:

@Override
public List<TeacherSelectOptionVO> getTeacherList() {
    return userMapper.findTeachers();
}

完成后,在testcn.tedu.straw.api.user.service.UserServiceTests中编写并执行单元测试:

@Test
void getTeacherList() {
    List<TeacherSelectOptionVO> teachers = service.getTeacherList();
    System.err.println("老师列表长度:" + teachers.size());
    for (TeacherSelectOptionVO teacher : teachers) {
        System.err.println(">>> " + teacher);
    }
}

当测试通过后,继续向后开发控制器层的功能,在UserController中添加处理请求的方法:

// http://localhost:8080/v1/users/teachers/select-option
// http://localhost/api-user/v1/users/teachers/select-option
@GetMapping("/teachers/select-option")
public R<List<TeacherSelectOptionVO>> getTeacherList() {
    return R.ok(userService.getTeacherList());
}

完成后,重启straw-api-user项目,通过 http://localhost:8080/v1/users/teachers/select-option 测试访问,当访问成功后,确保straw-eureka-serverstraw-gateway已经启动,再通过 http://localhost/api-user/v1/users/teachers/select-option 测试访问。

当测试通过后,在static/js/question/create.js中,补充声明loadTeachers函数,在created对应的函数中调用它:

并且loadTeachers函数加载服务器端响应的老师列表,并显示在界面中:

完成后,重启straw-gateway,在“发布问题”页面的表单中,可以看到正确的“老师列表”。

4.通过前端页面发布问题

首先,需要为发布问题的<form>表单绑定提交事件:

并在Vue的方法列表中声明对应的方法:

接下来,就需要在postQuestion()方法中获取表单的相关信息,2个下拉列表的值已经通过此前准备的selectedTagIdsselectedTeacherIds来表示,剩下的还有“标题”和“正文”需要获取,则先为“标题”的输入框绑定Vue属性:

在Vue中也声明该属性:

至于“正文”的值,不能通过以上方式直接绑定,由于在页面中,输入正文的文本域已经配置了id,则需要获取正文的值时,通过jQuery相关语法即可获得!

postQuestion()方法,测试输出页面中填写和选择的值:

完成后,重启straw-gateway,刷新页面,输入和选择数据后,打开浏览器的控制台,点击“发布问题”的按钮,观察控制台的输出结果是否正确。

其实,在使用Vue-Select时,当选中了某个选项后,所配置例如selectedTagIds数组中,存入的并不是选中的选项的id,而是整个选项对象,也就是例如{ label: 'Java基础', value: 1},所以,需要在HTML中的标签上使用:reduce进行配置:

注意:页面中的2个下拉列表都需要补充这条属性!

然后,在create.js中完成提交即可:

由于$.ajax()在提交数组类型的数据时,将提交为tagIds: [1,2,3]这样的格式,SpringMVC是不支持的,所以,在$.ajax()函数中需要补充traditional: true,则$.ajax()会将数组处理为tagIds=1&tagIds=2&tagIds=3这种SpringMVC可以支持的格式。

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值