续迁移登录功能
上传课讲到步骤5:编写UserDetailServiceImpl
步骤6:
上面步骤编写的方法实际上是由Spring-Security调用的
下面我们就创建security包来编写security的配置
代码如下
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailServiceImpl userDetailService;
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userDetailService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()//对当前全部请求进行授权
.antMatchers(
"/img/*",
"/js/*",
"/css/*",
"/bower_components/**",
"/login.html",
"/register.html",
"/register"
)//设置路径
.permitAll()//允许全部请求访问上面定义的路径
//其它路径需要全部进行表单登录验证
.anyRequest().authenticated().and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.failureUrl("/login.html?error")
.defaultSuccessUrl("/index.html")
.and().logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login.html?logout");
}
}
步骤7:
现在启动网关是报错的,原因是网关使用了commons模块
而commons模块加载的jar包是mybatis-plus-boot-starter
这个jar包在服务启动时会尝试启动mybatis所以报错
我们现在需要剔除启动功能,所以要换一个依赖
父项目的pom.xml需要先添加没有启动功能的版本的管理
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<!-- 其它配置略 -->
</dependencies>
</dependencyManagement>
在commons模块的pom.xml文件中依赖上面的jar包即可
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
</dependency>
</dependencies>
现在可以启动Eureka\sys\gateway进行登录的测试了!!!
步骤8:
最后我们来编写根据不同身份跳转不同页面的代码
gateway模块,HomeController类中
@RestController
@Slf4j
public class HomeController {
//声明两个常亮以便判断用户的角色
static final GrantedAuthority STUDENT =
new SimpleGrantedAuthority("ROLE_STUDENT");
static final GrantedAuthority TEACHER =
new SimpleGrantedAuthority("ROLE_TEACHER");
//显示首页
@GetMapping("/index.html")
public ModelAndView index(
@AuthenticationPrincipal User user){
if(user.getAuthorities().contains(STUDENT)){
return new ModelAndView("index");
}else if(user.getAuthorities().contains(TEACHER)){
return new ModelAndView("index_teacher");
}
return null;
}
@GetMapping("/register.html")
public ModelAndView register(){
return new ModelAndView("register");
}
}
Session共享和Redis
什么是Session共享问题
-
用户登录后,session对象时保存在当前服务器内存中的
如果这个用户访问了别的微服务的服务器,不可能获得之前保存在登录服务器内存中的session这样session就会丢失,一旦丢失就不能执行必须登录才能运行的业务
-
现在要解决这个问题需要一个公共的区域
能够使所有微服务服务器都能获得同一个session对象
这样无论哪个用户登录,在所有微服务之间该用户的session都是可用的
这个事情就是session共享问题
使用Redis实现Session共享
什么是Redis
一个C语言编写的基于内存存储,支持网络\集群,开源的非关系型数据库
关系型数据库是用表格存数据 ,非关系型是用键值对存数据
Redis能够将当前保存的数据,复制到硬盘上,所以也支持长时间保存
和Redis相似的一个缓存软件是memcached
Redis每秒10万次读写操作
Redis保存的值除了String还支持list,set,zset,hash等类型
安装Redis
将下载到的压缩包解压即可,免安装
Redis的启动
点击redis-start.bat可以直接启动redis,但是不能关闭窗口,不方便
windows如果想后台启动redis步骤如下
-
双击运行service-installing.bat文件,出现dos窗口几秒之后消失
-
双击运行service-start.bat文件,出现dos窗口几秒之后消失
此时Redis已经后台启动,默认端口号6379
-
双击redis-cli.exe文件出现一个可以输入命令的dos窗口
这个窗口称为redis的客户端窗口,可以向我们启动的Redis发送指令
-
如果想停止或卸载Redis的服务分别点击service-stop.bat或service-uninstalling.bat
在客户端启动后输入info命令
如果看到各种信息的显示表示当前Redis完全正常!
Redis的使用
常用数据类型
几点提示
- key 不能超过1024字节,浪费内存,还会降低查询效率
- key尽量见名知意
- 一个项目可以统一一种key的命名模式sys_username\sys_userinfo
下面打开客户端编写如下代码测试
最基本的保存和获取
127.0.0.1:6379> set mystr "hello world!" //设置字符串类型
127.0.0.1:6379> get mystr //读取字符串类型
对数值的简单操作
127.0.0.1:6379> set mynum "2" //创建数字
OK
127.0.0.1:6379> get mynum //查询数字
"2"
127.0.0.1:6379> incr mynum //数字增加
(integer) 3
127.0.0.1:6379> get mynum //查询数字
"3"
127.0.0.1:6379> decr mynum //数字减少
(integer) 2
127.0.0.1:6379> get mynum //查询数字
"2"
我们上面对数值的修改操作都是线程安全的,不会因为请求的并发发生信息不准确的情况
很多网站使用redis进行计数
SpringBoot中使用Redis
原始情况下有一套Jedis API可以访问Redis
就像JDBC访问mysql
现在Spring提供了进一步的封装,访问Redis更简单,就像mybatis一样
gateway模块添加依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
添加完pom的依赖,还要配置Redis的基本信息
就像配置数据库的ip地址和端口号一样
application.properties
spring.redis.host=localhost
spring.redis.port=6379
测试类
@SpringBootTest
@Slf4j
public class RedisTest {
@Resource
RedisTemplate<String,String> redisTemplate;
@Test
public void redis(){
//添加数据
//redisTemplate.opsForValue().set("msg","helloWorld!!!");
//获得数据
String str=redisTemplate.opsForValue().get("msg");
System.out.println(str);
//删除数据
//redisTemplate.delete("msg");
}
}
实现微服务间的Session共享
需要共享session的微服务需要如下操作
步骤1:
添加pom依赖(上面笔记中有)
步骤2:
在主方法所在的类中添加一个启用共享session功能的注解
@SpringBootApplication
@EnableZuulProxy
@EnableRedisHttpSession //允许共享session
public class StrawGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(StrawGatewayApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
步骤3:
在application.properties中添加共享session的路由配置和保存配置
## 此处省略其它的配置 ...
zuul.routes.sys.sensitive-headers=Authorization
## 设置将Session保存到Redis中
spring.session.store-type=redis
实际上上面的配置就完成了session共享的基本设置了
但是微服务所有项目需要需要session的共享就都需要上面的配置
我们的sys模块还没有配置所有
转到sys模块
重复上面的步骤
步骤1:
pom.xml文件
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
步骤2:
主方法
@SpringBootApplication
@EnableEurekaClient
@MapperScan("cn.tedu.straw.sys.mapper")
@EnableRedisHttpSession
public class StrawSysApplication {
public static void main(String[] args) {
SpringApplication.run(StrawSysApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
步骤3:
application.properties
spring.redis.port=6379
spring.redis.host=localhost
spring.session.store-type=redis
到此为止我们对session共享的设置就完成了
下面要编写一个业务测试一下
实现显示当前用户信息
先来简单验证一下session的共享效果
在Sys模块的DemoController中获得Spring-Security中的用户信息并输出
DemoController代码如下
// localhost:9000/sys/v1/sys/testSession
@GetMapping("/testSession")
public String session(
@AuthenticationPrincipal User user){
System.out.println(user.getUsername());
return user.getUsername();
}
重启sys和gateway服务,
先成功登录到index.html,然后输入代码中的路径:localhost:9000/sys/v1/sys/testSession
如果能看到用户名即表示session共享成功了
下面我们就可以实现显示用户信息面板的功能了
步骤1:
sys模块中保证IUserservice中包含获得用户信息的方法
UserVo currentUserVo(String username);
步骤2:
UserServiceImpl类中这个方法的实现如下
@Override
public UserVo currentUserVo(String username) {
UserVo user=userMapper.findUserVoByUsername(username);
//现在暂时无法查询用户的提问数和收藏数
return user;
}
步骤3:
sys模块中的UserController类中添加获得用户信息面板的方法
代码如下
@GetMapping("/me")
public R<UserVo> me(
@AuthenticationPrincipal User user){
UserVo userVo=userService.currentUserVo(user.getUsername());
return R.ok(userVo);
}
步骤4:
修改gateway模块中static/js/user_info.js中的ajax请求路径为/sys/v1/users/me
let userApp = new Vue({
el: "#userApp",
data: {
user: {}
},
methods: {
loadCurrentUser: function () {
$.ajax({
url: "/sys/v1/users/me",
method: "get",
success: function (r) {
console.log(r)
if (r.code==OK) {
userApp.user=r.data;
}else{
console.log(r.message);
}
}
});
}
},
created: function () {
//页面加载完毕后立即调用loadCurrentUser方法
this.loadCurrentUser();
}
});
迁移问答功能
我们之前已经给大家介绍了最终微服务项目的基本结构
问答功能被迁移到了straw-faq这个模块
所以我们开始创建这个模块
创建项目
创建straw-faq项目,并父子相认
子项目的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>straw</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>straw-faq</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>straw-faq</name>
<description>Demo project for Spring Boot</description>
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
父项目添加module
<modules>
<!-- 此处省略其它模块 ... -->
<module>straw-faq</module>
</modules>
然后开始配置faq模块的Eureka
Eureka的pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
faq的主方法
@SpringBootApplication
@EnableEurekaClient
public class StrawFaqApplication {
public static void main(String[] args) {
SpringApplication.run(StrawFaqApplication.class, args);
}
}
application.properties
server.port=8001
spring.application.name=faq-service
为了让Eureka更稳定的运行,前辈们总结了以下配置
数据证明配置如下信息,Eureka运行更加可靠
eureka.instance.prefer-ip-address=false
eureka.instance.hostname=localhost
eureka.instance.ip-address=127.0.0.1
eureka.instance.instance-id=${spring.application.name}:${eureka.instance.hostname}:${server.port}
开始迁移数据层
在这里插入图片描述
faq项目的pom.xml文件
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>
spring-cloud-starter-netflix-eureka-client
</artifactId>
</dependency>
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>straw-commons</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
主方法类中添加MapperScan
@SpringBootApplication
@EnableEurekaClient
@MapperScan("cn.tedu.straw.faq.mapper")
public class StrawFaqApplication {
public static void main(String[] args) {
SpringApplication.run(StrawFaqApplication.class, args);
}
}
faq模块的application.properties文件
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/straw?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
spring.datasource.username=root
spring.datasource.password=root
logging.level.cn.tedu.straw.faq.mapper=trace
logging.level.cn.tedu.straw.faq=debug
测试对象可用
@SpringBootTest
class StrawFaqApplicationTests {
@Resource
QuestionMapper questionMapper;
@Resource
QuestionTagMapper questionTagMapper;
@Resource
TagMapper tagMapper;
@Resource
UserQuestionMapper userQuestionMapper;
@Test
void contextLoads() {
System.out.println(questionMapper);
System.out.println(questionTagMapper);
System.out.println(tagMapper);
System.out.println(userQuestionMapper);
}
}
开始迁移业务层
复制业务逻辑层之后,问答业务中有的信息是来自UserService的
这种情况我们就需要使用Ribbon的RestTemplate来调用了
首先要到Sys模块提供这个服务
临时转到sys模块 UserController中添加方法
@GetMapping("/master")
public List<cn.tedu.straw.commons.model.User> master(){
return userService.getMasters();
}
因为这个类中上面有方法使用了User这个类名,这里的User实体要用全类名表示
回到faq模块
要想调用sys的方法就必须在主方法中注入RestTemplate对象
代码如下
@SpringBootApplication
@EnableEurekaClient
@MapperScan("cn.tedu.straw.faq.mapper")
public class StrawFaqApplication {
public static void main(String[] args) {
SpringApplication.run(StrawFaqApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
开始修改IQuestionService接口
getMyQuestions和saveQuestion两个方法中都添加了String username参数
代码如下
public interface IQuestionService extends IService<Question> {
//按登录用户查询当前用户问题的方法
PageInfo<Question> getMyQuestions(
String username,Integer pageNum,Integer pageSize
);
//保存用户发布信息的方法
void saveQuestion(String username,QuestionVo questionVo);
//按用户id查询问题数的
Integer countQuestionsByUserId(Integer userId);
//分页查询当前登录的老师问题的方法
PageInfo<Question> getQuestionsByTeacherName(
String username,Integer pageNum,Integer pageSize
);
//按id查询问题详情的方法
Question getQuestionById(Integer id);
}
上面接口的实现由QuestionServiceImpl来实现
这个实现类修改的地方比较多,主要在新增了一个方法getUser
在getMyQuestions,saveQuestion,getQuestionsByTeacherName三方法中有代码改动
需要额外留意
修改为完之后代码如下
@Service
@Slf4j
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question> implements IQuestionService {
@Autowired
QuestionMapper questionMapper;
@Autowired
QuestionTagMapper questionTagMapper;
@Autowired
UserQuestionMapper userQuestionMapper;
@Autowired
ITagService tagService;
@Autowired
RestTemplate restTemplate;
//使用RestTemplate获得用户的信息
private User getUser(String username){
String url="http://sys-service/v1/auth/user?username={1}";
User user=restTemplate.getForObject(
url,User.class,username);
return user;
}
//按登录用户查询当前用户问题的方法
@Override
public PageInfo<Question> getMyQuestions(
//传入翻页查询的参数
String username ,Integer pageNum,Integer pageSize
) {
//分页查询,决定查询的页数
if(pageNum==null || pageSize==null){
//分页查询信息不全,直接抛异常
throw ServiceException.invalidRequest("参数不能为空");
}
//获得当前登录用户的用户名
//String username=userService.currentUsername();
log.debug("当前登录用户为:{}",username);
//如果已经登录,使用之前编写好的findUserByUsername方法
//查询出当前用户的详细信息(实际上主要需要用户的id)
//User user=userMapper.findUserByUsername(username);
User user=getUser(username);
if(user == null){
throw ServiceException.gone("登录用户不存在");
}
log.debug("开始查询{}用户的问题",user.getId());
QueryWrapper<Question> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("user_id",user.getId());
queryWrapper.eq("delete_status",0);
queryWrapper.orderByDesc("createtime");
//执行查询之前,要设置分页查询信息
PageHelper.startPage(pageNum,pageSize);
//紧接着的查询就是按照上面分页配置的分页查询
List<Question> list=questionMapper.selectList(queryWrapper);
log.debug("当前用户的问题数量为:{}",list.size());
//遍历当前查询出的所有问题对象
for(Question q: list){
//将问题每个对象的对应的Tag都查询出来,并赋值为实体类中的List<Tag>
List<Tag> tags=tagNamesToTags(q.getTagNames());
q.setTags(tags);
}
return new PageInfo<Question>(list);
}
@Override
@Transactional
public void saveQuestion(String username,QuestionVo questionVo) {
log.debug("收到问题数据{}",questionVo);
// 获取当前登录用户信息(可以验证登录情况)
// String username=userService.currentUsername();
// User user=userMapper.findUserByUsername(username);
User user=getUser(username);
// 将该问题包含的标签拼接成字符串以","分割 以便添加tag_names列
StringBuilder bud=new StringBuilder();
for(String tag : questionVo.getTagNames()){
bud.append(tag).append(",");
}
//删除最后一个","
bud.deleteCharAt(bud.length()-1);
String tagNames=bud.toString();
// 构造Question对象
Question question=new Question()
.setTitle(questionVo.getTitle())
.setContent(questionVo.getContent())
.setUserId(user.getId())
.setUserNickName(user.getNickname())
.setTagNames(tagNames)
.setCreatetime(LocalDateTime.now())
.setStatus(0)
.setPageViews(0)
.setPublicStatus(0)
.setDeleteStatus(0);
// 新增Question对象
int num=questionMapper.insert(question);
if(num!=1){
throw new ServiceException("服务器忙!");
}
log.debug("保存了对象:{}",question);
// 处理新增的Question和对应Tag的关系
Map<String, Tag> name2TagMap=tagService.getName2TagMap();
for(String tagName : questionVo.getTagNames()){
//根据本次循环的标签名称获得对应的标签对象
Tag tag=name2TagMap.get(tagName);
//构建QuestionTag实体类对象
QuestionTag questionTag=new QuestionTag()
.setQuestionId(question.getId())
.setTagId(tag.getId());
//执行新增
num=questionTagMapper.insert(questionTag);
if(num!=1){
throw new ServiceException("数据库忙!");
}
log.debug("新增了问题和标签的关系:{}",questionTag);
}
// 处理新增的Question和对应User(老师)的关系
//Map<String, User> masterMap=userService.getMasterMap();
String url="http://sys-service/v1/users/master";
User[] users=restTemplate.getForObject(
url,User[].class);
Map<String,User> masterMap=new HashMap<>();
for(User u:users){
masterMap.put(u.getNickname(),u);
}
for(String masterName : questionVo.getTeacherNickNames()){
//根据本次循环的讲师名称获得对应的讲师对象
User uu=masterMap.get(masterName);
//构建QuestionTag实体类对象
UserQuestion userQuestion=new UserQuestion()
.setQuestionId(question.getId())
.setUserId(uu.getId())
.setCreatetime(LocalDateTime.now());
//执行新增
num=userQuestionMapper.insert(userQuestion);
if(num!=1){
throw new ServiceException("数据库忙!");
}
log.debug("新增了问题和讲师的关系:{}",userQuestion);
}
}
//根据Question的tag_names列的值,返回List<Tag>
private List<Tag> tagNamesToTags(String tagNames){
//得到的tag_name拆分字符串
//tagNames="java基础,javaSE,面试题"
String[] names=tagNames.split(",");
//names={"java基础","javaSE","面试题"}
//声明List以便返回
List<Tag> list=new ArrayList<>();
Map<String,Tag> map=tagService.getName2TagMap();
//遍历String数组
for(String name:names) {
//根据String数组中当前的元素获得Map对应的value
Tag tag=map.get(name);
//将这个value保存在list对象中
list.add(tag);
}
return list;
}
@Override
public Integer countQuestionsByUserId(Integer userId) {
//使用QueryWrapper查询数量的方法
QueryWrapper<Question> query=new QueryWrapper<>();
query.eq("user_id",userId);
query.eq("delete_status",0);
Integer count=questionMapper.selectCount(query);
//别忘了返回
return count;
}
@Override
public PageInfo<Question> getQuestionsByTeacherName(
String username, Integer pageNum, Integer pageSize) {
if(pageNum == null)
pageNum=1;
if(pageSize == null)
pageSize=8;
//根据用户名查询用户对象
//User user=userMapper.findUserByUsername(username);
User user=getUser(username);
//设置分页查询
PageHelper.startPage(pageNum,pageSize);
List<Question> questions=
questionMapper.findTeachersQuestions(user.getId());
//别忘了,要将问题列中的标签字符串转成标签的List
for(Question q: questions){
List<Tag> tags=tagNamesToTags(q.getTagNames());
q.setTags(tags);
}
return new PageInfo<Question>(questions);
}
@Override
public Question getQuestionById(Integer id) {
//先按id查询出Question
Question question=questionMapper.selectById(id);
//再按Question的tag_names列的标签转换为List<Tag>
List<Tag> tags=tagNamesToTags(question.getTagNames());
//将转换完成的List<Tag>保存到这个Question的tags属性中
question.setTags(tags);
return question;
}
}
而ITagService和实现类TagServiceImpl类就无需改动了!
faq模块的session共享
步骤1:
faq的pom.xml文件添加依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
步骤2:
faq的application.properties配置
spring.redis.port=6379
spring.redis.host=localhost
spring.session.store-type=redis
步骤3:
主方法类中加注解
@SpringBootApplication
@EnableEurekaClient
@MapperScan("cn.tedu.straw.faq.mapper")
@EnableRedisHttpSession
public class StrawFaqApplication {
public static void main(String[] args) {
SpringApplication.run(StrawFaqApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
临时转到gateway模块配置faq的路由
gateway项目的application.properties文件添加如下
zuul.routes.faq.path=/faq/**
zuul.routes.faq.service-id=faq-service
#允许路由传递"敏感头" 即包含登录认证有关信息
zuul.routes.faq.sensitive-headers=Authorization
回到faq模块
添加Spring-Security的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
新建security包中新建SecurityConfig类中设置全部放行的安全策略代码如下
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.anyRequest().permitAll();
}
}
为了测试faq当前的配置和Session共享的成功
编写一个DemoController类代码如下
@RestController
@Slf4j
@RequestMapping("/v1/faq")
public class DemoController {
@GetMapping("/demo")
public String demo(
@AuthenticationPrincipal UserDetails userDetails){
return userDetails.getUsername();
}
}
重启gateway\faq,在保证redis运行,Eureka运行的前提下
打开浏览器登录显示主页后输入url:http://localhost:9000/faq/v1/faq/demo
观察是否能够显示用户名
迁移控制器
将portal项目中的TagController和QuestionController类赋值到faq模块的controller包中即可!
TagController没有改动
也参考下代码
@RestController
//下面的注解表示想访问本控制器中的任何方法需要前缀/v1/tags
//这个v1开头的格式是后期微服务的标准名为RESTful
@RequestMapping("/v1/tags")
public class TagController {
@Autowired
private ITagService tagService;
//查询所有标签@GetMapping("")表示使用类上声明的前缀就可以访问这个方法
@GetMapping("")
public R<List<Tag>> tags(){
List<Tag> list=tagService.getTags();
return R.ok(list);
}
}
QuestionController改动较多需要仔细对照
代码如下
@RestController
@RequestMapping("/v1/questions")
@Slf4j
public class QuestionController {
@Autowired
IQuestionService questionService;
//查询返回当前登录用户发布的问题
@GetMapping("/my")
public R<PageInfo<Question>> my(Integer pageNum,
@AuthenticationPrincipal UserDetails user) {
if (pageNum == null) {
pageNum = 1;
}
int pageSize = 8;
log.debug("开始查询当前用户的问题");
//这里要处理个异常,因为用户可能没有登录
try {
PageInfo<Question> questions =
questionService.getMyQuestions(
user.getUsername(), pageNum, pageSize);
return R.ok(questions);
} catch (ServiceException e) {
log.error("用户查询问题失败!", e);
return R.failed(e);
}
}
//学生发布问题的控制器方法
@PostMapping
public R createQuestion(
@Validated QuestionVo questionVo,
BindingResult result,
@AuthenticationPrincipal UserDetails user) {
if (result.hasErrors()) {
String message = result.getFieldError()
.getDefaultMessage();
log.warn(message);
return R.unproecsableEntity(message);
}
if (questionVo.getTagNames().length == 0) {
log.warn("必须选择至少一个标签");
return R.unproecsableEntity("必须选择至少一个标签");
}
if (questionVo.getTeacherNickNames().length == 0) {
log.warn("必须选择至少一个老师");
return R.unproecsableEntity("必须选择至少一个老师");
}
//这里应该将vo对象交由service层去新增
log.debug("接收到表单数据{}", questionVo);
questionService.saveQuestion(
user.getUsername(), questionVo);
return R.ok("发布成功!");
}
//处理讲师获得分页问题列表的方法
//这个方法需要特殊权限才能访问
@GetMapping("/teacher")
@PreAuthorize("hasRole('ROLE_TEACHER')")
public R<PageInfo<Question>> teachers(
//声明权限是为了获得用户名的
@AuthenticationPrincipal UserDetails user,
Integer pageNum) {
if (pageNum == null)
pageNum = 1;
Integer pageSize = 8;
//调用业务逻辑层的方法
PageInfo<Question> pageInfo = questionService
.getQuestionsByTeacherName(
user.getUsername(), pageNum, pageSize
);
return R.ok(pageInfo);
}
//显示问题详细的Controller方法
//为了遵守RESTful的风格这个位置的路径比较特殊
//例如:/v1/questions/12
//上面的路径SpringMvc会自动将12赋值给{id}
//@PathVariable标记的同名属性的值也会是12
@GetMapping("/{id}")
public R<Question> question(
@PathVariable Integer id) {
//判断必须要有id
if (id == null) {
return R.invalidRequest("ID不能为空");
}
Question question = questionService.getQuestionById(id);
return R.ok(question);
}
}
转到getway模块
我们所有的前端js代码都在网关
js/tags_nav.js把它的url请求修改为
url:'/faq/v1/tags',
js/index.js,把它的ajaxurl修改为
url: '/faq/v1/questions/my',
js/index_teacher.js,把它的ajaxurl修改为
url: '/faq/v1/questions/teacher',
可以启动全部的微服务测试了!