十堰微网站建设价格,国内wordpress大神,韩国化妆品网站模板,东莞网页设计史上最详细《学成在线》项目实操笔记系列【下】#xff0c;跟视频的每一P对应#xff0c;全系列18万字#xff0c;涵盖详细步骤与问题的解决方案。如果你操作到某一步卡壳#xff0c;参考这篇#xff0c;相信会带给你极大启发。 四、课程发布模块
4.1 (课程发布)模块需求…史上最详细《学成在线》项目实操笔记系列【下】跟视频的每一P对应全系列18万字涵盖详细步骤与问题的解决方案。如果你操作到某一步卡壳参考这篇相信会带给你极大启发。 四、课程发布模块
4.1 (课程发布)模块需求 P92
课程预览在发布课程之前需要预览一下看最终的效果有没有问题课程信息是否完整。
课程审核预览之后就是运营人员进行审核审核分为程序自动审核和人工审核。
课程发布发布之后课程可以被搜索到。
4.2 (课程发布)freemarker P93
freemarker是模板引擎。
在xuecheng-plus-content的xuecheng-plus-content-api的pom.xml下新增依赖
!-- Spring Boot 对结果视图 Freemarker 集成 --
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-freemarker/artifactId
/dependency在nacos的freemarker-config-dev.yaml中写入如下代码 在xuecheng-plus-content-api下进行如下配置 在xuecheng-plus-content的xuecheng-plus-content-api的api下创建FreemarkerController中写入如下代码
Slf4j
RestController
public class FreemarkerController {GetMapping(/testfreemarker)public ModelAndView test(){ModelAndView modelAndView new ModelAndView();//指定模型modelAndView.addObject(name,小明);//指定模板modelAndView.setViewName(test);//根据视图名称加.ftl找到模板return modelAndView;}
}
测试没啥问题 4.3 (课程发布)部署门户 P94
静态资源一般部署到nginx上。
首先解压nginx文件配置文件位置如下 啥也不配置直接启动效果如下 windows本地机hosts文件地址如下C:\Windows\System32\drivers\etc写入如下配置
127.0.0.1 www.51xuecheng.cn 51xuecheng.cn ucenter.51xuecheng.cn teacher.51xuecheng.cn file.51xuecheng.cn
在server_name中写入如下门户地址 完整的配置如下
server {listen 80;server_name www.51xuecheng.cn localhost;#rewrite ^(.*) https://$server_name$1 permanent;#charset koi8-r;ssi on;ssi_silent_errors on;#access_log logs/host.access.log main;location / { alias C:/xuechengzaixian/xc-ui-pc-static-portal/;index index.html index.htm;}#静态资源location /static/img/ { alias C:/xuechengzaixian/xc-ui-pc-static-portal/img/;} location /static/css/ { alias C:/xuechengzaixian/xc-ui-pc-static-portal/css/;} location /static/js/ { alias C:/xuechengzaixian/xc-ui-pc-static-portal/js/;} location /static/plugins/ { alias C:/xuechengzaixian/xc-ui-pc-static-portal/plugins/;add_header Access-Control-Allow-Origin http://ucenter.51xuecheng.cn; add_header Access-Control-Allow-Credentials true; add_header Access-Control-Allow-Methods GET;} location /plugins/ { alias C:/xuechengzaixian/xc-ui-pc-static-portal/plugins/;}
}
保存之后 2种方法让配置生效方法1ctrlshiftesc直接把任务停掉。方法2nginx.exe -s reload。可以看到显示没有任何问题 课程详情页面能正常浏览 记得下面weight10后面有一个分号;
upstream fileserver{server 192.168.101.65:9000 weight10;
}
server {listen 80;server_name file.51xuecheng.cn;#charset koi8-r;ssi on;ssi_silent_errors on;#access_log logs/host.access.log main;location /video {proxy_pass http://fileserver;}location /mediafiles {proxy_pass http://fileserver;}
}
效果如下 下面测试一下 http://file.51xuecheng.cn/mediafiles/2022/09/13/a16da7a132559daf9e1193166b3e7f52.jpg 最后想要视频能够播放添加如下配置 location /course/preview/learning.html {alias D:/itcast2022/xc_edu3.0/code_1/xc-ui-pc-static-portal/course/learning.html;} location /course/search.html { root D:/itcast2022/xc_edu3.0/code_1/xc-ui-pc-static-portal;} location /course/learning.html { root D:/itcast2022/xc_edu3.0/code_1/xc-ui-pc-static-portal;} 进入到播放详情页面
http://www.51xuecheng.cn/course/course_template.html 搜索videoObject然后找到video对应的url链接填写minio上视频的链接。 经测试视频播放没有问题 完整配置如下
worker_processes 1;
events {worker_connections 1024;
}
http {include mime.types;default_type application/octet-stream;sendfile on;keepalive_timeout 65;upstream fileserver{server 192.168.101.65:9000 weight10;}server {listen 80;server_name file.51xuecheng.cn;ssi on;ssi_silent_errors on;location /video {proxy_pass http://fileserver;}location /mediafiles {proxy_pass http://fileserver;}}server {listen 80;server_name www.51xuecheng.cn localhost;ssi on;ssi_silent_errors on;location / { alias C:/xuechengzaixian/xc-ui-pc-static-portal/;index index.html index.htm;}location /static/img/ { alias C:/xuechengzaixian/xc-ui-pc-static-portal/img/;} location /static/css/ { alias C:/xuechengzaixian/xc-ui-pc-static-portal/css/;} location /static/js/ { alias C:/xuechengzaixian/xc-ui-pc-static-portal/js/;} location /static/plugins/ { alias C:/xuechengzaixian/xc-ui-pc-static-portal/plugins/;add_header Access-Control-Allow-Origin http://ucenter.51xuecheng.cn; add_header Access-Control-Allow-Credentials true; add_header Access-Control-Allow-Methods GET;} location /plugins/ { alias C:/xuechengzaixian/xc-ui-pc-static-portal/plugins/;}location /course/preview/learning.html {alias C:/xuechengzaixian/xc-ui-pc-static-portal/course/learning.html;} location /course/search.html { root C:/xuechengzaixian/xc-ui-pc-static-portal;} location /course/learning.html { root C:/xuechengzaixian/xc-ui-pc-static-portal;} }
}4.4 (课程预览)接口开发 P95
把course_template.html这里面都是写死的数据拷贝到xuecheng-plus-content-api的resources的templates下改后缀名为course_template.ftl 在xuecheng-plus-content-api的api下创建CoursePublishController写入如下代码
Controller
public class CoursePublishController {AutowiredCoursePublishService coursePublishService;GetMapping(/coursepreview/{courseId})public ModelAndView preview(PathVariable(courseId)Long courseId){ModelAndView modelAndView new ModelAndView();//查询课程的信息作为模板数据CoursePreviewDto coursePreviewDto coursePublishService.getCoursePreviewInfo(courseId);modelAndView.addObject(model,coursePreviewDto);modelAndView.setViewName(course_template);return modelAndView;}
}
注解Controller响应页面RestController响应json
启动content后访问下面连接
http://localhost:63040/content/coursepreview/12
会出现下面界面 css页面在nginx里面现在可以直接在nginx中配置经过网关。
-
代码如下千万记得在gatewayserver后面还要加上一个/
upstream gatewayserver{server 127.0.0.1:63010 weight10;
}
#api
location /api/ {proxy_pass http://gatewayserver/;
}
解析比如现在我输入www.51xuecheng.cn/api/content/coursepreview/12nginx会将www.51xuecheng.cn/api/解析成http://127.0.0.1:63010/然后把content/coursepreview/12拼接到解析后的访问前缀中http://127.0.0.1:63010/content/coursepreview/12。 输入url先到nginx然后到网关最后到微服务。
在xuecheng-plus-content-model下面创建CoursePreviewDto然后写入如下代码
Data
public class CoursePreviewDto {//课程基本信息营销信息private CourseBaseInfoDto courseBase;//课程计划信息private ListTeachplanDto teachplans;//课程师资信息..
}在xuecheng-plus-content-service的service下面创建CoursePublishService
//课程发布相关接口
public interface CoursePublishService {/*** param courseId 课程id* return*/public CoursePreviewDto getCoursePreviewInfo(Long courseId);
}
在xuecheng-plus-content-service的service的impl下面创建CoursePublishServiceImpl写入如下代码
//课程发布相关接口实现
Slf4j
Service
public class CoursePublishServiceImpl implements CoursePublishService {AutowiredCourseBaseInfoService courseBaseInfoService;AutowiredTeachplanService teachplanService;Overridepublic CoursePreviewDto getCoursePreviewInfo(Long courseId) {CoursePreviewDto coursePreviewDto new CoursePreviewDto();//课程基本信息营销信息CourseBaseInfoDto courseBaseInfo courseBaseInfoService.getCourseBaseInfo(courseId);//课程计划信息ListTeachplanDto teachplanTree teachplanService.findTeachplanTree(courseId);coursePreviewDto.setTeachplans(teachplanTree);return coursePreviewDto;}
}
前端放开下面的服务网关端口启动下面3个服务 访问下面的地址
http://localhost:8601/
在IDEA中打上断点点击预览按钮 然后看看取得的数据是不是完整正确的 上一步只是获取到数据下一步开始在页面中动态展示。
更改完模板之后可以选择build下面的recompile进行重新编译。 举例修改下面2个地方 有一个现成的course_template.ftl文件 这里要记得修改27行的下面这个地方 在nginx中配置如下主要是为了看视频和目录 /content/open/主要为了显示目录/media/open/主要为了显示视频资源。 在xuecheng-plus-content-api的api下面创建CourseOpenController类写入如下代码
Api(value 课程公开查询接口,tags 课程公开查询接口)
RestController
RequestMapping(/open)
public class CourseOpenController {Autowiredprivate CourseBaseInfoService courseBaseInfoService;Autowiredprivate CoursePublishService coursePublishService;GetMapping(/course/whole/{courseId})public CoursePreviewDto getPreviewInfo(PathVariable(courseId) Long courseId) {//获取课程预览信息CoursePreviewDto coursePreviewInfo coursePublishService.getCoursePreviewInfo(courseId);return coursePreviewInfo;}
}
在xuecheng-plus-media-api的api下面创建MediaOpenController类写入如下代码
Api(value 媒资文件管理接口,tags 媒资文件管理接口)
RestController
RequestMapping(/open)
public class MediaOpenController {AutowiredMediaFileService mediaFileService;ApiOperation(预览文件)GetMapping(/preview/{mediaId})public RestResponseString getPlayUrlByMediaId(PathVariable String mediaId){MediaFiles mediaFiles mediaFileService.getFileById(mediaId);if (mediaFilesnull) {return RestResponse.validfail(找不到视频);}String url mediaFiles.getUrl();if (StringUtils.isEmpty(url)) {return RestResponse.validfail(该视频正在处理中);}return RestResponse.success(mediaFiles.getUrl());}
}
启动下面4个服务 测试如下视频可以正常播放 4.5 提交课程审核 P96
在course_base表中设置课程审核状态字段包括未提交、已提交、审核通过、审核不通过。
只有审核通过才能够发布。只有未提交和审核不通过才能到已提交状态。
但是要注意教学机构可以在审核状态中修改部分信息但是在运营人员正在审核时教学机构不能修改信息可以新建一个副本。 如上图建一个预发布表集成了课程营销信息、课程师资、课程基本信息、课程计划这几张表。
审核审的是预发布表修改修改的是左边的4张表。
如果可以发布就是把预发布表的拷贝到发布表。
如果审核人员正在审核预发布表则教育机构不能提交审核。
信息组合可以直接以json串的格式传入 对提交的约束如下 在xuecheng-plus-content-service的service下创建CoursePublishService中写入如下代码
public void commitAudit(Long companyId,Long courseId);
在xuecheng-plus-content-service的service下的CoursePublishServiceImpl下写入如下代码
Override
public void commitAudit(Long companyId, Long courseId) {CourseBaseInfoDto courseBaseInfo courseBaseInfoService.getCourseBaseInfo(courseId);if(courseBaseInfo null){XueChengPlusException.cast(课程找不到);}//审核状态String auditStatus courseBaseInfo.getAuditStatus();//如果课程的审核状态为已提交则不允许提交if(auditStatus.equals(202003)){XueChengPlusException.cast(课程已提交请等待审核);}//课程的图片、计划信息没有填写也不允许提交String pic courseBaseInfo.getPic();if(StringUtils.isEmpty(pic)){XueChengPlusException.cast(请求上传课程图片);}//查询课程计划//课程计划信息ListTeachplanDto teachplanTree teachplanService.findTeachplanTree(courseId);if(teachplanTreenull || teachplanTree.size()0){XueChengPlusException.cast(请编写课程计划);}//查询到课程基本信息、营销信息。计划等信息插入到课程预发布表CoursePublishPre coursePublishPre new CoursePublishPre();BeanUtils.copyProperties(courseBaseInfo,coursePublishPre);//营销信息CourseMarket courseMarket courseMarketMapper.selectById(courseId);//转JSONString courseMarketJson JSON.toJSONString(courseMarket);coursePublishPre.setMarket(courseMarketJson);//计划信息//转jsonString teachplanTreeJson JSON.toJSONString(teachplanTree);coursePublishPre.setTeachplan(teachplanTreeJson);//状态为已提交coursePublishPre.setStatus(202003);//提交时间coursePublishPre.setCreateDate(LocalDateTime.now());//查询预发布表如果有记录则更新没有则插入CoursePublishPre coursePublishPreObj coursePublishPreMapper.selectById(courseId);if(coursePublishPreObjnull){//插入coursePublishPreMapper.insert(coursePublishPre);}else{//更新coursePublishPreMapper.updateById(coursePublishPre);}//更新课程基本信息表的审核状态为已提交CourseBase courseBase courseBaseMapper.selectById(courseId);courseBase.setAuditStatus(202003);//审核状态为已提交courseBaseMapper.updateById(courseBase);
}
记得在xuecheng-plus-content-service下的CourseBaseInfoServiceImpl的getCourseBaseInfo方法下写入如下代码
CourseCategory mtObj courseCategoryMapper.selectById(courseBase.getMt());
String mtName mtObj.getName();//大分类名称
courseBaseInfoDto.setMtName(mtName);
CourseCategory stObj courseCategoryMapper.selectById(courseBase.getSt());
String stName stObj.getName();//小分类名称
courseBaseInfoDto.setStName(stName); 重启content模块测试
如果提交审核失败页面上端会显示失败的原因。 如果提交审核成功会在content数据库的course_publish_pre表里面看到这条记录。 首先更改预发布表的状态为审核通过 然后更改课程基本信息表 4.6 (课程发布)需求分析 P97
发布之后课程信息的网页是能够众多网民看的如果存储在数据库中可能导致性能低下。
课程的信息要插入到Elasticsearch中把课程信息缓存到Redis中。生成的课程静态页面html文件上传到minio中。 4.7 什么是分布式事务 P98
本地事务使用服务自己的数据库来控制事务是spring利用数据库本身的事务特性去控制事务。本地事务具有CAID四大特性会将事务纳入一个不可分割的执行单元。
分布式事务特点是涉及到多个服务来执行同一件事。分布式系统之间要完成一件事服务之间还要通过远程调用交互
分布式事务例子
微服务架构比如下订单后调用库存服务减库存 单服务多数据库 多服务单数据库 4.8 什么是CAP理论 P99
CAP是Consistency、Availability、Partition tolerance即一致性、可用性、分区容忍性的缩写。
一致性用户不管访问哪个结点拿到的数据都是最新的比如查询小明的信息不能出现在数据库没有改变情况下两次查询结果不一样。
可用性指任何时候查询用户信息都可以查询到结果但不保证查询到最新的数据。
分区容忍性也叫分区容错性当系统采用分布式架构时由于网络通信异常导致请求中断、消息丢失但系统依然对外提供服务。 A和C不能同时满足要么满足AP强调可用性要么满足CP一致性。
比如用户把自己的名字“小明”上传到服务节点1。如果要保证一致性只有当服务节点1中的数据同步到服务节点2中系统才可用。如果要保证可用性就不能等待信息同步完成在同步过程中也能使用。
银行转账一定保证CP。
但现实生活中一般AP的场景比较多。所以提出了BASE理论。
BASE是Basically Available(基本可用)Soft State(软状态)和Eventuallyconsistent(最终一致性)。
基本可用比如在订单高峰的时候只要支付能用即可。
软状态有一个中间状态比如运输中...支付中...
最终一致性最终的数据要一致。
实现AP保证数据最终一致性
使用消息队列如失败自动充实达到最大失败次数人工处理。
使用任务调度的方案启动任务调度将课程信息由数据库同步到Elasticsearch、Minio、redis中。
4.9 分布式事务控制方案 P100
现在可以新建一张消息表现在可以在该表中标记一个字段值表示为要发布的课程然后任务调度中心去调度服务把信息同步到redis、Elasticsearch和minio中。写完之后把数据删掉
本地消息表任务调度的机制来完成分布式事务的最终事务一致性的控制。
course_publish和mq_message表是在同一个数据库可以使用数据库事务来控制
任务调度程序可以读取mq_message的数据然后同步到redisElasticsearch和minio中。 现在假如redis挂掉了怎么办呢无数轮也没用了。程序自动运维监管系统网管系统告警系统运维人员收到告警远程处理或者去现场处理。
4.10 (课程发布)发布接口P101
在xuecheng-plus-content-api的api的CoursePublishController下写入代码
在xuecheng-plus-content-service的service的CoursePublishService下写入代码
在xuecheng-plus-content-service的service的impl的CoursePublishServiceImpl下写入代码
4.11 (课程发布)消息sdk P102
在第8天资料中解压出下面文件xuecheng-plus-message-sdk 把工具包拷贝到工程目录中设置为maven工程 在xuecheng-plus-content-service下的pom.xml文件中写入依赖
dependencygroupIdcom.xuecheng/groupIdartifactIdxuecheng-plus-message-sdk/artifactIdversion0.0.1-SNAPSHOT/version
/dependency
在xuecheng-plus-content-api的api的CoursePublishController下写入代码
ApiOperation(课程发布)
ResponseBody
PostMapping(/coursepublish/{courseId})
public void coursepublish(PathVariable(courseId) Long courseId){Long companyId 1232141425L;coursePublishService.publish(companyId,courseId);
}
在xuecheng-plus-content-service的service的CoursePublishService下写入代码
public void publish(Long companyId,Long courseId);
在xuecheng-plus-content-service的service的impl的CoursePublishServiceImpl下写入代码
Autowired
CoursePublishMapper coursePublishMapper;
Autowired
MqMessageService mqMessageService;
Transactional
Override
public void publish(Long companyId, Long courseId) {//查询预发布表CoursePublishPre coursePublishPre coursePublishPreMapper.selectById(courseId);if(coursePublishPrenull){XueChengPlusException.cast(课程没有审核记录无法发布);}//状态String status coursePublishPre.getStatus();//课程如果没有审核通过不允许发布if(!status.equals(202004)){XueChengPlusException.cast(课程没有审核通过不允许发布);}//向课程发布表写入数据CoursePublish coursePublish new CoursePublish();BeanUtils.copyProperties(coursePublishPre,coursePublish);//先查询课程发布表有则更新,没有再添加CoursePublish coursePublishObj coursePublishMapper.selectById(courseId);if(coursePublishObjnull){coursePublishMapper.insert(coursePublish);}else{coursePublishMapper.updateById(coursePublish);}//向消息表写入数据MqMessage mqMessage mqMessageService.addMessage(course_publish, String.valueOf(courseId), null, null);if(mqMessagenull){XueChengPlusException.cast(CommonError.UNKOWN_ERROR);}//将预发布表数据删除coursePublishPreMapper.deleteById(courseId);
}
启动contentApplication和gatewayApplication和systemApplication记得启动nginx和前端进行前后端联调。
在预发布表course_publish_pre已有一条数据改为202004 course_base那条数据也改为202004记得是在audit_status这个字段进行修改 进入前端找到之前那条已经审核通过的然后点击发布在课程发布表可以看到course_publish在mq_message也可以看到记录 4.12 (课程发布)课程发布任务调度 P103
在xuecheng-plus-content-service的service下面创建jobhandler包然后创建一个CoursePublishTask类写入如下代码
在xuecheng-plus-content-service的pom.xml下面写入依赖
dependencygroupIdcom.xuxueli/groupIdartifactIdxxl-job-core/artifactId
/dependency
在content-service-dev.yaml配置文件中进行配置 xxl:job:admin: addresses: http://192.168.101.65:8088/xxl-job-adminexecutor:appname: coursepublish-jobaddress: ip: port: 8999logpath: /data/applogs/xxl-job/jobhandlerlogretentiondays: 30accessToken: default_token
把XxlJobConfig拷贝到xuecheng-plus-content-service的config下面 执行器coursepublish-job 在任务管理-课程发布任务执行器新建下面的任务记得启动 要在下面这个地方打上断点 4.13 (课程发布)页面静态化P104
原理因为静态页面可以使用nginx每秒大约5万并发apache等高性能的web服务器并发性能高。
页面静态化将生产html页面过程提前提前使用模板引擎技术生成html页面客户端可以直接请求到html页面。
用页面静态化技术的时机当数据不频繁变化。因为课程发布后仍能修改但需要经过课程审核。
在xuecheng-plus-content-service中添加如下依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-freemarker/artifactId
/dependency
在xuecheng-plus-content-service的test下创建FreemarkerTest写入如下测试代码这里我是把classpath写死了老师的写法会报错可能是因为路径上有中文的缘故吧
SpringBootTest
public class FreemarkerTests {AutowiredCoursePublishService coursePublishService;//测试页面静态化Testpublic void testGenerateHtmlByTemplate() throws IOException, TemplateException {//配置freemarkerConfiguration configuration new Configuration(Configuration.getVersion());//加载模板//选指定模板路径,classpath下templates下//得到classpath路径//String classpath this.getClass().getResource(/).getPath();String classpath C:\\xuechengzaixian\\xuecheng-plus-project\\xuecheng-plus-content\\xuecheng-plus-content-service\\target\\test-classes;configuration.setDirectoryForTemplateLoading(new File(classpath /templates/));//设置字符编码configuration.setDefaultEncoding(utf-8);//指定模板文件名称Template template configuration.getTemplate(course_template.ftl);//准备数据CoursePreviewDto coursePreviewInfo coursePublishService.getCoursePreviewInfo(2L);MapString, Object map new HashMap();map.put(model, coursePreviewInfo);//静态化//参数1模板参数2数据模型String content FreeMarkerTemplateUtils.processTemplateIntoString(template, map);System.out.println(content);//将静态化内容输出到文件中InputStream inputStream IOUtils.toInputStream(content);//输出流FileOutputStream outputStream new FileOutputStream(C:\\software\\test.html);IOUtils.copy(inputStream, outputStream);}
}
因为微服务各个服务之间是各司其职的现在媒资服务是专门负责上传的现在如果想把生成的静态文件上传到minio需要Feign。 在xuecheng-plus-content-service的pom.xml文件中写入如下代码
dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId
/dependency
!-- Spring Cloud 微服务远程调用 --
dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-openfeign/artifactId
/dependency
dependencygroupIdio.github.openfeign/groupIdartifactIdfeign-httpclient/artifactId
/dependency
!--feign支持Multipart格式传参--
dependencygroupIdio.github.openfeign.form/groupIdartifactIdfeign-form/artifactIdversion3.8.0/version
/dependency
dependencygroupIdio.github.openfeign.form/groupIdartifactIdfeign-form-spring/artifactIdversion3.8.0/version
/dependency把如下的代码写入到nacos的feign-dev.yaml中 feign:hystrix:enabled: truecircuitbreaker:enabled: true
hystrix:command:default:execution:isolation:thread:timeoutInMilliseconds: 30000 #熔断超时时间
ribbon:ConnectTimeout: 60000 #连接超时时间ReadTimeout: 60000 #读超时时间MaxAutoRetries: 0 #重试次数MaxAutoRetriesNextServer: 1 #切换实例的重试次数
拷贝MultipartSupportConfig到xuecheng-plus-content-service的config下 在xuecheng-plus-content-api和service的test的配置文件中都引入下面的配置文件
shared-configs:- data-id: feign-${spring.profiles.active}.yamlgroup: xuecheng-plus-commonrefresh: true在MediaFileService和MediaFileServiceImpl的uploadFile方法中添加一个参数String objectName。 4.14 (课程发布)熔断降级 P105
现在是内容管理服务调用媒资管理服务。
spring会生成一个代理对象在代理对象中去实现远程调用。
FeignClient(valuemedia-api)用FeignClient注解来指定属于哪个服务比如媒资服务。
在xuecheng-plus-content-service的content的feignclient下创建MediaServiceClient写入如下代码
//远程调用媒资服务的接口
FeignClient(valuemedia-api,configuration {MultipartSupportConfig.class})
public interface MediaServiceClient {RequestMapping(value/media/upload/coursefile,consumes MediaType.MULTIPART_FORM_DATA)public String upload(RequestPart(filedata)MultipartFile filedata,RequestParam(valueobjectName,required false)String objectName);
} 在xuecheng-plus-content-service的test下的content下创建FeignUploadTest写入如下代码
SpringBootTest
public class FeignUploadTest {AutowiredMediaServiceClient mediaServiceClient;Testpublic void test() {//将file转MultipartFileFile file new File(C:\\software\\test.html);MultipartFile multipartFile MultipartSupportConfig.getMultipartFile(file);mediaServiceClient.upload(multipartFile,course/test.html);}
} 在xuecheng-plus-content-service的test下的ContentApplication的类上加入如下注解
EnableFeignClients(basePackages{com.xuecheng.content.feignclient})
现在出现的是下面这个问题 需要加入下面蓝色区域的代码 可以看到minio中的mediafiles下的course目录下有了test.html文件 可以通过下面的链接查看上传的文件其中test.html要替换为你自己的文件名
http://192.168.101.65:9000/mediafiles/course/test.html
但没有基本的样式 在nginx的配置文件中找到server_name为www.51xuecheng.cn localhost的server配置然后在其下添加如下配置 访问下面这个页面
http://www.51xuecheng.cn/course/test.html
效果如下 feign远程调用涉及熔断。
微服务雪崩如果A调BB调C假如此时C服务出现问题此时A和B都会出现问题。
内容管理服务上游服务要调用媒资管理服务下游服务。
熔断是下游服务异常时一种保护系统的手段。降级是熔断后上游服务处理熔断的方法。 方法1使用fallback来定义降级的类无法拿到熔断降级的具体诱因。 方法2使用fallbackFactory定义一个MediaServiceClientFallbackFactory继承FallbackFactoryT这个T泛型写的是MediaServiceClient这个类。可以拿到熔断的异常信息。
在xuecheng-plus-content-service的feignclient包的MediaServiceClient接口下面主要完善FeignClient注解
FeignClient(valuemedia-api,configuration MultipartSupportConfig.class,fallbackFactory MediaServiceClientFallbackFactory.class) 在xuecheng-plus-content-service的feignclient包的MediaServiceClientFallbackFactory接口下面写入如下代码
Component
Slf4j
public class MediaServiceClientFallbackFactory implements FallbackFactoryMediaServiceClient {//拿到了熔断的异常信息throwableOverridepublic MediaServiceClient create(Throwable throwable) {return new MediaServiceClient() {//发生熔断上传服务调用此方法执行降级逻辑Overridepublic String upload(MultipartFile filedata, String objectName) {log.debug(远程调用上传文件的接口发生熔断:{},throwable.toString(),throwable);return null;}};}
}
结构如下 我的xuecheng-plus-content-service的test很奇怪没办法识别到nacos的配置所以我单独在test的resources的bootstrap.yml中写入如下配置代码
feign:hystrix:enabled: truecircuitbreaker:enabled: true
hystrix:command:default:execution:isolation:thread:timeoutInMilliseconds: 30000 #熔断超时时间
ribbon:ConnectTimeout: 60000 #连接超时时间ReadTimeout: 60000 #读超时时间MaxAutoRetries: 0 #重试次数MaxAutoRetriesNextServer: 1 #切换实例的重试次数
测试首先停掉媒资服务可以把所有服务都停掉然后在return null上打断点如果进入到return null即表示降级成功。
熔断下游服务出现问题触发熔断。
4.15 (课程发布)页面静态化任务 P106
在xuecheng-plus-content-service的service的CoursePublishService下新增2个方法的声明
//课程静态化
public File generateCourseHtml(Long courseId);
//上传课程静态化页面
public void uploadCourseHtml(Long courseId,File file);
在xuecheng-plus-content-service的service的impl的CoursePublishServiceImpl下写入2个方法的实现代码
Autowired
CoursePublishService coursePublishService;
Override
public File generateCourseHtml(Long courseId) {//配置freemarker,加载模板Configuration configuration new Configuration(Configuration.getVersion());//最终的静态文件File htmlFile null;try{//得到classpath路径String classpath this.getClass().getResource(/).getPath();//选指定模板路径,classpath下templates下configuration.setDirectoryForTemplateLoading(new File(classpath /templates/));//设置字符编码configuration.setDefaultEncoding(utf-8);//指定模板文件名称Template template configuration.getTemplate(course_template.ftl);//准备数据CoursePreviewDto coursePreviewInfo this.coursePublishService.getCoursePreviewInfo(courseId);MapString, Object map new HashMap();map.put(model, coursePreviewInfo);//静态化//参数1模板参数2数据模型String html FreeMarkerTemplateUtils.processTemplateIntoString(template, map);//输入流InputStream inputStream IOUtils.toInputStream(html,utf-8);htmlFile File.createTempFile(coursepublish,.html);//输出流FileOutputStream outputStream new FileOutputStream(htmlFile);IOUtils.copy(inputStream, outputStream);}catch (Exception ex){log.error(页面静态化出现问题课程id:{},courseId,ex);ex.printStackTrace();}return htmlFile;
}
Autowired
MediaServiceClient mediaServiceClient;
Override
public void uploadCourseHtml(Long courseId, File file) {try {//将file转成MultipartFileMultipartFile multipartFile MultipartSupportConfig.getMultipartFile(file);//远程调用得到返回值String upload mediaServiceClient.upload(multipartFile, course/ courseId .html);if(uploadnull){log.debug(远程调用走降级逻辑得到上传的结果为null,课程id:{},courseId);XueChengPlusException.cast(上传静态文件过程中存在异常);}}catch(Exception ex){ex.printStackTrace();XueChengPlusException.cast(上传静态文件过程中存在异常);}
}
把下面这行代码复制粘贴到xuecheng-plus-content-api的ContentApplication的启动类上
EnableFeignClients(basePackages{com.xuecheng.content.feignclient}) xuecheng-plus-content-api的pom.xml文件中进行如下配置 第1步启动媒资系统管理服务网关启动。前端启动。内容服务以断点方式启动。
第2步在前端提交审核然后course_publish_pre会有一条记录我们手动更改为审核通过status设为202004记得course_base表中相应记录的audit_status同样也要修改为审核通过。
第3步任务调度中心的课程发布任务执行器启动。在xuecheng-plus-content-service的service的jobhandler的CoursePublishTask下execute下面打上断点。
第4步点发布记录会被写入course_publish表和mq_message表是在content数据库下。逐步跟进。
第5步注意执行结束后mq_message的消息要被删除最终需要写入mq_message_history表。 4.16 课程搜索 P107
传统搜索方法先找文章再找词。
全文检索方法先找词再找文章。首先把词提取出来创建索引索引里面都是词然后拿着词去搜索。
首先要创建索引然后再搜索。
在虚拟机上已经有elasticsearch和kibana。尚未启动输入下面4行命令启动
docker stop elasticsearch
docker stop kibana
docker start elasticsearch
docker start kibana
Elasticsearch中的Index索引相当于MySQL的表
Elasticsearch中的Document文档相当于MySQL的行
Elasticsearch中的Field字段相当于MySQL的列
Elasticsearch中的Mapping字段相当于MySQL的列
解压下面的文件然后拷贝到项目下面。 这里需要注意配置文件中的内容
namespace和group要进行更改 启动搜索工程。
在api-test下面创建xc-media-api然后写入测试的代码
课程信息索引同步
实时性高1.可以手动编写代码在service里面同步。2.可以通过Canal实现。
实时性不强1.MQ向mysql写数据的时候向mq写入消息搜索服务监听mq收到消息后写入索引。存在问题要保证消息可靠性在向mq发消息要可靠mq本身要可靠服务监听mq也要可靠。代码实现比较复杂。
2.Logstash开源实时日志分析平台ELK包括Elasticsearch、Kibana、Logstash其中Logstash负责收集、解析和转换日志信息。可以实现MySQL与Elasticsearch之间数据同步。
3.任务调度向mysql写数据的时候记录修改记录开启一个定时任务根据修改记录将数据同步到Elasticsearch。
内容管理调搜索服务。
在xuecheng-plus-content的feignclient这个包下定义一个SearchServiceClient接口写如下代码
FeignClient(valuesearch,fallbackFactory SearchServiceClientFallbackFactory.class)
public interface SearchServiceClient {PostMapping(/search/index/course)public Boolean add(RequestBody CourseIndex courseIndex);
}把xuecheng-plus-search的po下的CourseIndex拷贝到xuecheng-plus-content-service的feignclient下。 在xuecheng-plus-content的feignclient这个包下定义一个SearchServiceClientFallbackFactory接口写如下代码
Slf4j
Component
public class SearchServiceClientFallbackFactory implements FallbackFactorySearchServiceClient {Overridepublic SearchServiceClient create(Throwable throwable) {return new SearchServiceClient() {Overridepublic Boolean add(CourseIndex courseIndex) {log.error(添加课程索引发生熔断,索引信息:{},熔断异常:{},courseIndex,throwable.toString(),throwable);//走降级了返回falsereturn false;}};}
}
在xuecheng-plus-content-service的service的jobhandler下的CoursePublishTask的代码如下
Slf4j
Component
public class CoursePublishTask extends MessageProcessAbstract {AutowiredCoursePublishService coursePublishService;AutowiredSearchServiceClient searchServiceClient;AutowiredCoursePublishMapper coursePublishMapper;//任务调度入口XxlJob(CoursePublishJobHandler)public void coursePublishJobHandler() throws Exception{//分片参数int shardIndex XxlJobHelper.getShardIndex();int shardTotal XxlJobHelper.getShardTotal();//调用抽象类的方法执行任务process(shardIndex,shardTotal,course_publish,30,60);}//执行课程发布任务的逻辑,如果此方法抛出异常说明任务执行失败Overridepublic boolean execute(MqMessage mqMessage) {//从mqMessage拿到课程idLong courseId Long.parseLong(mqMessage.getBusinessKey1());//课程静态化上传到miniogenerateCourseHtml(mqMessage,courseId);//向elasticsearch写索引数据saveCourseIndex(mqMessage,courseId);//向redis写缓存//课程静态化上传到minio//返回true任务完成return true;}//生成课程静态化页面并上传至文件系统private void generateCourseHtml(MqMessage mqMessage,long courseId){//消息idLong taskId mqMessage.getId();MqMessageService mqMessageService this.getMqMessageService();//做任务幂等性处理//取出该阶段执行状态int stageOne mqMessageService.getStageOne(taskId);if(stageOne0){log.debug(课程静态化任务完成无须处理...);return;}//开始进行课程静态化,生成html页面File file coursePublishService.generateCourseHtml(courseId);if(filenull){XueChengPlusException.cast(生成的静态页面为空);}//将html上传到miniocoursePublishService.uploadCourseHtml(courseId,file);//任务处理完成写任务状态为完成mqMessageService.completedStageOne(taskId);}//保存课程索引信息 第二个阶段任务private void saveCourseIndex(MqMessage mqMessage,long courseId){//任务idLong taskId mqMessage.getId();MqMessageService mqMessageService this.getMqMessageService();//取出第二个阶段状态int stageTwo mqMessageService.getStageTwo(taskId);//任务幂等性处理if(stageTwo0){log.debug(课程索引信息已写入无需执行...);return;}//查询课程信息调用搜索服务添加索引接口//从课程发布表查询课程信息CoursePublish coursePublish coursePublishMapper.selectById(courseId);CourseIndex courseIndex new CourseIndex();BeanUtils.copyProperties(coursePublish,courseIndex);//远程调用Boolean add searchServiceClient.add(courseIndex);if(!add){XueChengPlusException.cast(远程调用搜索服务添加课程索引失败);}//完成本阶段的任务mqMessageService.completedStageTwo(courseId);}
}
把GatewayApplication和ContentApplication和SearchApplication和SystemApplication和MediaApplication启动。 可以看到一些初始数据 测试
第1步我拿最后一条数据做试验先把图片上传上去。
第2步然后点击提交审核。然后course_publish_pre会有一条记录我们手动更改为审核通过status设为202004记得course_base表中相应记录的audit_status同样也要修改为审核通过。 第4步点发布。可以看到新增了这门课。 五、认证授权模块
5.1 SpringSecurity认证授权测试 P108
什么是用户身份认证用户去访问系统资源时要求验证用户的身份信息身份合法即可继续访问。
常见的用户身份认证方式用户名密码、微信扫码等。
项目包含学生、学习机构老师、平台运营人员三类用户每一类用户在访问项目受保护资源时都需要进行身份认证。 什么是用户授权用户认证通过后去访问系统资源系统会判断用户是否拥有访问资源的权限只允许访问有权限的系统资源没有权限的资源无法访问这个过程叫用户授权。 微信扫码和QQ扫码能方便用户登录省去了用户注册的成本能够对资源进行共享。
认证功能几乎是每个项目都要具备的功能并且它与业务无关市面上有很多的认证框架。Spring Security。
把资料中的xuecheng-plus-search解压后拷贝到项目当中更改bootstrap.yml配置文件中的如下内容 dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-security/artifactId
/dependency
dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-oauth2/artifactId
/dependency出现下面问题
class lombok.javac.apt.LombokProcessor (in unnamed module 0x6002e944) cannot access class com.sun.tools.javac.processing.JavacProcessingEnvironment (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.processing to unnamed module 0x6002e944
解决方法把xuecheng-plus-parent的lombok版本调高。 访问localhost:63070/auth/user/52效果如下 访问localhost:63070/auth/login 在登录界面输入用户名zhangsan密码123登录成功。反之提示登录失败。 加入如下注解
PreAuthorize(hasAuthority(p1))
PreAuthorize(hasAuthority(p2)) 重新启动项目效果如下 下面logout 点击Log Out然后输入用户名lisi密码456。 授权把权限赋予不同类别的用户判断谁有权限有权限就能访问资源。
过滤器是在请求到达之前的预处理或者后处理拦截器是对方法调用的前后或者抛出异常时的处理监听器是监听特定事件执行相应操作
5.2 OAuth2协议测试 P109
现在的问题是一个新用户在网站没有信息如果要注册需要填写一大堆的个人信息如果可以通过扫码获取信息会比较方便。 流程客户端向用户申请到授权码用户同意后客户端携带授权码去获取令牌获取到令牌客户端携带令牌去获取用户信息。 把下面这个bean复制到WebSecurityConfig中
Bean
public AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();
}重新启动项目。 如果启动失败注意一定要用老师配套的jdk1.8在第1天的资料中。 在api-test包下面创建一个xc-auth-api.http文件 获取授权码输入下面的链接http://localhost:63070/auth/oauth/authorize?client_idXcWebAppresponse_typecodescopeallredirect_urihttp://www.51xuecheng.cn。用户名zhangsan密码123。选择Approve。 可以获取到授权码 把授权码填在请求的url中
POST {{auth_host}}/auth/oauth/token?client_idXcWebAppclient_secretXcWebAppgrant_typeauthorization_codecodew8QpqVredirect_urihttp://www.51xuecheng.cn 密码模式
POST {{auth_host}}/auth/oauth/token?client_idXcWebAppclient_secretXcWebAppgrant_typepasswordusernamezhangsanpassword123 5.3 jwt令牌 P110
现在的问题是认证服务发的令牌资源服务要每次请求认证服务才能拿到令牌。
现在想解决上面的问题可以使用JWT格式的令牌解决上面的问题。
jwt令牌包含3个方面Header、Payload、Signature
Header里面包含算法类型。Payload里面是内容不建议存放敏感信息因为可以被解码还原为原始内容。前2部分用的编码方式都是base64url。
Signature是签名用于防止jwt内容被篡改。只要秘钥不泄露就无法篡改后不被知觉。 现在资源服务只需要获得秘钥就能验证JWT。
如果认证服务和资源服务使用相同秘钥叫作对称加密效率高秘钥泄露可以伪造jwt令牌。
测试的时候只需要把TokenConfig的配置全部换成下面的配置 重新启动项目点击密码模式登录头、载体、签名之间用.号分割。 5.4 资源服务继承JWT P111
逻辑客户端访问门户通过统一认证入口请求统一认证服务如果认证成功将jwt令牌颁发给客户端客户端携带jwt令牌才能去访问教学管理、选课学习、运营管理这些模块。 首先让微服务整合spring security管控所有的资源。
把spring security的依赖加入到xuecheng-plus-content-api。
dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-security/artifactId
/dependency
dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-oauth2/artifactId
/dependency将TokenConfig和ResourceServiceConfig拷贝到content下的config下config要新建。 ResourceServiceConfig的RESOURCE_ID不要乱变要和前面的一致。 要管控的文件全部在这里配注意老师给的初始文件配置这里是注释掉的 启动content服务。
访问下面的会提示说请求不到 正常时能访问到。加入jwt后访问不到 先用Auth的方法获取到令牌加上令牌后访问就能获取到资源。 5.5 网关认证 P112
网关的职责一个是路由转发一个是进行校验。
针对认证1.网站白名单维护针对不用认证的URL全部放行。2.校验jwt的合法性。校验jwt的合法性jwt合法则放行不合法则阻塞。 在网关工程添加如下依赖
dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-security/artifactId
/dependency
dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-oauth2/artifactId
/dependency
dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId
/dependency
dependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactId
/dependency把资料中的文件全部拷贝到网关服务中格式如下 在security-whitelist.properties中配置的是白名单 如下是白名单的位置 记得先把临时全部放行那条注释掉 启动gateway、auth、content服务。
在xuecheng-plus-content-api中把config下的ResourceServerConfig给屏蔽掉 注意事项1.要在网关添加依赖。2.读懂网关的过滤器。3.要配置白名单。4.在微服务要放行所有。
可以在网关的如下位置打断点进行跟踪调试 访问如下的请求 可以看到请求请求到的token 5.6 连接用户数据库 P113
首先要把xuecheng-plus-auth的config下的WebSecurityConfig这个类的配置用户信息服务部分给注释起来。 在xuecheng-plus-auth的ucenter下面创建service/impl包然后创建一个UserServiceImpl类写入如下代码
Slf4j
Component
public class UserServiceImpl implements UserDetailsService {AutowiredXcUserMapper xcUserMapper;Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {//账号String username s;//根据username账号查询数据库XcUser xcUser xcUserMapper.selectOne(new LambdaQueryWrapperXcUser().eq(XcUser::getUsername, s));//查询到用户不存在返回null即可if(xcUsernull){return null;}//如果查到了用户拿到正确的密码最终封装成一个UserDetails对象给spring security框架返回由String password xcUser.getPassword();//权限String[] authorities {test};UserDetails userDetails User.withUsername(username).password(password).authorities(authorities).build();return userDetails;}
}
在WebSecurityConfig中写入如下代码
public static void main(String[] args) {String password111111;PasswordEncoder passwordEncoder new BCryptPasswordEncoder();//生成密码for(int i0;i5;i){String encode passwordEncoder.encode(password);System.out.println(encode);}
}
执行后可以看到生成的每个串都不一样 可以看到代码的每次比对都是一致的 测试代码如下
public static void main(String[] args) {String password111111;PasswordEncoder passwordEncoder new BCryptPasswordEncoder();//生成密码for(int i0;i5;i){String encode passwordEncoder.encode(password);System.out.println(encode);boolean matches passwordEncoder.matches(password, encode);System.out.println(matches);}
}
修改下面这个地方 数据库如下 测试在如下位置打断点 当用户名为zhangsan时因为数据库里没有匹配的所以失败 接下来把username改为t1然后密码改为111111可以正常获取到token。 5.7 扩展用户信息 P114
用户的相关信息需要记录到令牌像昵称头像用户id都要记录进去所以需要扩展用户的信息。
现在采用的方案是不仅存放username同时还存放一大堆的东西进去。 在api-test包下的xc-auth-api.http下写入如下代码token后面填写密码模式获取到的token
###校验jwt令牌
POST {{auth_host}}/auth/oauth/check_token?token
可以看到json解析出来的内容就很多了 5.8 工具类获取用户身份 P115
在xuecheng-plus-content-api的content下面建一个util在util下面创建一个SecurityUtil类把代码全部拷贝进去 在CourseBaseInfoController中现在可以直接通过SecurityUtil.getUser()来获取用户的信息。 重新启动content服务以断点调试的方式启动。然后在xc-content-api.http中执行下面的方法 可以看到获取到了user的信息。 5.9 统一认证入口 P116
统一认账入口包括账号密码认证微信扫码认证手机验证码认证。
首先要统一请求的参数建了一个统一的认证类统一认证请求的参数统一为AuthParamsDto。 第1步是对xuecheng-plus-auth的ucenter的service的impl下的UserServiceImpl类的代码进行修改主要是将传入的json转成AuthParamsDto对象在loadUserByUsername的开头写入如下代码
//将传入的json转成AuthParamsDto对象
AuthParamsDto authParamsDto null;
try {authParamsDto JSON.parseObject(s, AuthParamsDto.class);
} catch (Exception e) {throw new RuntimeException(请求认证的参数不符合要求);
}
//账号
String username authParamsDto.getUsername();
第2步在api-test下的xc-auth-api.http中添加如下的代码
### 密码模式,请求AuthParamsDto参数
POST {{auth_host}}/auth/oauth/token?client_idXcWebAppclient_secretXcWebAppgrant_typepasswordusername{username:t1,password:111111,authType:password}
第3步在xuecheng-plus-auth的config下创建DaoAuthenticationProviderCustom下面加入下面的代码
//重写了DaoAuthenticationProvider的校验密码的方法因为我们统一了认证入口有一些认证方式不需要校验密码
Component
public class DaoAuthenticationProviderCustom extends DaoAuthenticationProvider {Autowiredpublic void setUserDetailsService(UserDetailsService userDetailsService){super.setUserDetailsService(userDetailsService);}Overrideprotected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {//子类重写父类方法为空就好}
} 不是所有的验证都要校验密码手机验证码就不需要校验密码。
第4步进入到config下的WebSecurityConfig中写入如下代码
Autowired
DaoAuthenticationProviderCustom daoAuthenticationProviderCustom;Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{auth.authenticationProvider(daoAuthenticationProviderCustom);
}
测试在下面的位置打上断点 第5步在xuecheng-plus-auth的service下创建AuthService //统一的认证接口
public interface AuthService {//认证方法XcUserExt execute(AuthParamsDto authParamsDto);
}
第6步策略模式根据不同的认证方式进来有不同的策略。 //账号名密码方式
Service(password_authservice)
public class PasswordAuthServiceImpl implements AuthService {Overridepublic XcUserExt execute(AuthParamsDto authParamsDto) {return null;}
}
//微信扫码认证
Service(wx_authservice)
public class WxAuthServiceImpl implements AuthService {Overridepublic XcUserExt execute(AuthParamsDto authParamsDto) {return null;}
} 第7步可以在UserServiceImpl中根据认证类型从spring容器中取出指定的bean。在如下位置添加如下代码 //认证类型有passwordwx...
String authType authParamsDto.getAuthType();
//根据认证类型从spring容器中取出指定的bean
String beanName authType_authservice;
AuthService authService applicationContext.getBean(beanName,AuthService.class);
//调用
XcUserExt execute authService.execute(authParamsDto);
Autowired
ApplicationContext applicationContext;
5.10 统一账号密码认证 P117
在xuecheng-plus-auth的service的impl下的PasswordAuthServiceImpl中写入如下代码
//账号名密码方式
Service(password_authservice)
public class PasswordAuthServiceImpl implements AuthService {AutowiredXcUserMapper xcUserMapper;AutowiredPasswordEncoder passwordEncoder;Overridepublic XcUserExt execute(AuthParamsDto authParamsDto) {//账号String username authParamsDto.getUsername();//todo:校验验证码//账号是否存在,根据username账号查询数据库XcUser xcUser xcUserMapper.selectOne(new LambdaQueryWrapperXcUser().eq(XcUser::getUsername,username));//查询到用户不存在,要返回null即可,spring security框架抛出异常用户不存在if(xcUsernull){throw new RuntimeException(账号不存在);}//校验密码是否正确//如果查到了用户拿到正确的密码String passwordDb xcUser.getPassword();//拿到用户输入的密码String passwordForm authParamsDto.getPassword();//校验密码boolean matches passwordEncoder.matches(passwordForm, passwordDb);if(!matches){throw new RuntimeException(账号或密码错误);}XcUserExt xcUserExt new XcUserExt();BeanUtils.copyProperties(xcUser,xcUserExt);return xcUserExt;}
}
在xuecheng-plus-auth的service的impl下的UserServiceImpl中写入如下代码
Slf4j
Component
public class UserServiceImpl implements UserDetailsService {AutowiredXcUserMapper xcUserMapper;AutowiredApplicationContext applicationContext;Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {//将传入的json转成AuthParamsDto对象AuthParamsDto authParamsDto null;try {authParamsDto JSON.parseObject(s, AuthParamsDto.class);} catch (Exception e) {throw new RuntimeException(请求认证的参数不符合要求);}//认证类型有passwordwx...String authType authParamsDto.getAuthType();//根据认证类型从spring容器中取出指定的beanString beanName authType_authservice;AuthService authService applicationContext.getBean(beanName,AuthService.class);//调用统一execute方法完成认证XcUserExt xcUserExt authService.execute(authParamsDto);//封装xcUserExt用户信息为UserDetailsUserDetails userPrincipal getUserPrincipal(xcUserExt);return userPrincipal;}//查询用户信息public UserDetails getUserPrincipal(XcUserExt xcUser){String password xcUser.getPassword();//权限String[] authorities {test};xcUser.setPassword(null);String userJson JSON.toJSONString(xcUser);UserDetails userDetails User.withUsername(userJson).password(password).authorities(authorities).build();return userDetails;}
}
测试在xc-auth-api.http发送如下请求 在下面位置打上断点逐步跟踪看看最终结果是否正确。 5.11 部署验证码服务 P118
验证码可以防止恶性攻击。具有认证、找回密码、支付验证、人机判断等功能。
解压下面的zip包然后拷贝到项目当中 修改yaml配置文件中的配置 在nacos中配置redis 在api-test下创建xc-checkcode-api.http写入如下代码
### 申请验证码
POST {{checkcode_host}}/checkcode/pic
生成的效果如下 key会被存储在redis中 复制图片链接打开即可看到校验码 发送下面的代码来看是否正确
### 校验验证码
POST {{checkcode_host}}/checkcode/verify?keycheckcode:2d6c2fa5124641fc83339a6e16718cbccodebwst 5.12 账号密码认证测试 P119
启动网关和验证码服务redis也要启动记得前端的nginx和项目的serve也要启动
然后打开前端的登录页面是401状态。可以到网关服务的security-whitelist.properties中临时把所有页面放行 可以看到验证码出现 然后可以将xuecheng-plus-auth服务也断点启动在如下位置打上断点 如果出现下面的问题 输入用户名、密码、验证码后点击确定跳转到下面 在xuecheng-plus-auth的ucenter下面创建feignclient包。在feignclient包下创建CheckCodeClient接口写入如下代码
FeignClient(value checkcode,fallbackFactory CheckCodeClientFactory.class)
RequestMapping(/checkcode)
public interface CheckCodeClient {PostMapping(value/verify)public Boolean verify(RequestParam(key) String key, RequestParam(code) String code);
}
同样在feignclient包系创建CheckCodeClientFactory类写入如下代码
Slf4j
Component
public class CheckCodeClientFactory implements FallbackFactoryCheckCodeClient {Overridepublic CheckCodeClient create(Throwable throwable) {return new CheckCodeClient() {Overridepublic Boolean verify(String key, String code) {log.debug(调用验证码服务熔断异常:{},throwable.getMessage());return null;}};}
}在bootstrap.yml中写入如下配置 完善之后的PasswordAuthServiceImpl代码如下
//账号名密码方式
Service(password_authservice)
public class PasswordAuthServiceImpl implements AuthService {AutowiredXcUserMapper xcUserMapper;AutowiredPasswordEncoder passwordEncoder;AutowiredCheckCodeClient checkCodeClient;Overridepublic XcUserExt execute(AuthParamsDto authParamsDto) {//账号String username authParamsDto.getUsername();//输入的验证码String checkcode authParamsDto.getCheckcode();//验证码对应的keyString checkcodekey authParamsDto.getCheckcodekey();if(StringUtils.isEmpty(checkcode)||StringUtils.isEmpty(checkcode)){throw new RuntimeException(请输入验证码);}//远程调用验证码服务接口去校验验证码Boolean verify checkCodeClient.verify(checkcodekey, checkcode);if(verify null || !verify){throw new RuntimeException(验证码输入错误);}//账号是否存在,根据username账号查询数据库XcUser xcUser xcUserMapper.selectOne(new LambdaQueryWrapperXcUser().eq(XcUser::getUsername,username));//查询到用户不存在,要返回null即可,spring security框架抛出异常用户不存在if(xcUsernull){throw new RuntimeException(账号不存在);}//校验密码是否正确//如果查到了用户拿到正确的密码String passwordDb xcUser.getPassword();//拿到用户输入的密码String passwordForm authParamsDto.getPassword();//校验密码boolean matches passwordEncoder.matches(passwordForm, passwordDb);if(!matches){throw new RuntimeException(账号或密码错误);}XcUserExt xcUserExt new XcUserExt();BeanUtils.copyProperties(xcUser,xcUserExt);return xcUserExt;}
}
在下面打上断点