当前位置: 首页 > news >正文

《学成在线》微服务实战项目实操笔记系列(P92~P120)【下】

史上最详细《学成在线》项目实操笔记系列【下】,跟视频的每一P对应,全系列18万字,涵盖详细步骤与问题的解决方案。如果你操作到某一步卡壳,参考这篇,相信会带给你极大启发。

 四、课程发布模块

4.1 (课程发布)模块需求 P92

课程预览:在发布课程之前需要预览一下,看最终的效果有没有问题,课程信息是否完整。

课程审核:预览之后就是运营人员进行审核,审核分为程序自动审核和人工审核。

课程发布:发布之后课程可以被搜索到。

4.2 (课程发布)freemarker P93

freemarker是模板引擎。

在xuecheng-plus-content的xuecheng-plus-content-api的pom.xml下新增依赖:

<!-- Spring Boot 对结果视图 Freemarker 集成 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-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种方法让配置生效,方法1:ctrl+shift+esc直接把任务停掉。方法2:nginx.exe -s reload。可以看到显示没有任何问题:

课程详情页面能正常浏览:

记得下面weight=10后面有一个分号;

upstream fileserver{server 192.168.101.65:9000 weight=10;
}
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 weight=10;}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 weight=10;
}
#api
location /api/ {proxy_pass   http://gatewayserver/;
} 

解析:比如现在我输入:www.51xuecheng.cn/api/content/coursepreview/12,nginx会将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 List<TeachplanDto> 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);//课程计划信息List<TeachplanDto> 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 RestResponse<String> getPlayUrlByMediaId(@PathVariable String mediaId){MediaFiles mediaFiles = mediaFileService.getFileById(mediaId);if (mediaFiles==null) {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("请求上传课程图片");}//查询课程计划//课程计划信息List<TeachplanDto> teachplanTree = teachplanService.findTeachplanTree(courseId);if(teachplanTree==null || 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(coursePublishPreObj==null){//插入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的数据,然后同步到redis,Elasticsearch和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文件中写入依赖:

<dependency><groupId>com.xuecheng</groupId><artifactId>xuecheng-plus-message-sdk</artifactId><version>0.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(coursePublishPre==null){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(coursePublishObj==null){coursePublishMapper.insert(coursePublish);}else{coursePublishMapper.updateById(coursePublish);}//向消息表写入数据MqMessage mqMessage = mqMessageService.addMessage("course_publish", String.valueOf(courseId), null, null);if(mqMessage==null){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下面写入依赖:

<dependency><groupId>com.xuxueli</groupId><artifactId>xxl-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中添加如下依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-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);Map<String, 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文件中写入如下代码:

<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Spring Cloud 微服务远程调用 -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId>
</dependency>
<!--feign支持Multipart格式传参-->
<dependency><groupId>io.github.openfeign.form</groupId><artifactId>feign-form</artifactId><version>3.8.0</version>
</dependency>
<dependency><groupId>io.github.openfeign.form</groupId><artifactId>feign-form-spring</artifactId><version>3.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(value="media-api")用FeignClient注解来指定属于哪个服务,比如媒资服务。

在xuecheng-plus-content-service的content的feignclient下创建MediaServiceClient,写入如下代码:

//远程调用媒资服务的接口
@FeignClient(value="media-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(value="objectName",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调B,B调C,假如此时C服务出现问题,此时A和B都会出现问题。

内容管理服务(上游服务)要调用媒资管理服务(下游服务)。

熔断是下游服务异常时一种保护系统的手段。降级是熔断后上游服务处理熔断的方法。

方法1:使用fallback来定义降级的类,无法拿到熔断降级的具体诱因。

方法2:使用fallbackFactory,定义一个MediaServiceClientFallbackFactory继承FallbackFactory<T>,这个T泛型写的是MediaServiceClient这个类。可以拿到熔断的异常信息。

在xuecheng-plus-content-service的feignclient包的MediaServiceClient接口下面,主要完善@FeignClient注解:

@FeignClient(value="media-api",configuration = MultipartSupportConfig.class,fallbackFactory = MediaServiceClientFallbackFactory.class)

 在xuecheng-plus-content-service的feignclient包的MediaServiceClientFallbackFactory接口下面写入如下代码:

@Component
@Slf4j
public class MediaServiceClientFallbackFactory implements FallbackFactory<MediaServiceClient> {//拿到了熔断的异常信息throwable@Overridepublic 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);Map<String, 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(upload==null){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(value="search",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 FallbackFactory<SearchServiceClient> {@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(stageOne>0){log.debug("课程静态化任务完成,无须处理...");return;}//开始进行课程静态化,生成html页面File file = coursePublishService.generateCourseHtml(courseId);if(file==null){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(stageTwo>0){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配置文件中的如下内容:

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-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_id=XcWebApp&response_type=code&scope=all&redirect_uri=http://www.51xuecheng.cn。用户名:zhangsan,密码:123。选择Approve。

可以获取到授权码:

把授权码填在请求的url中

POST {{auth_host}}/auth/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=authorization_code&code=w8QpqV&redirect_uri=http://www.51xuecheng.cn

密码模式:

POST {{auth_host}}/auth/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username=zhangsan&password=123

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。

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-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合法则放行,不合法则阻塞。

在网关工程添加如下依赖:

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</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 LambdaQueryWrapper<XcUser>().eq(XcUser::getUsername, s));//查询到用户不存在,返回null即可if(xcUser==null){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 password="111111";PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();//生成密码for(int i=0;i<5;i++){String encode = passwordEncoder.encode(password);System.out.println(encode);}
}

执行后,可以看到生成的每个串都不一样:

可以看到代码的每次比对都是一致的:

测试代码如下:

public static void main(String[] args) {String password="111111";PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();//生成密码for(int i=0;i<5;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_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username={"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。在如下位置添加如下代码:

//认证类型,有password,wx...
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 LambdaQueryWrapper<XcUser>().eq(XcUser::getUsername,username));//查询到用户不存在,要返回null即可,spring security框架抛出异常用户不存在if(xcUser==null){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("请求认证的参数不符合要求");}//认证类型,有password,wx...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?key=checkcode:2d6c2fa5124641fc83339a6e16718cbc&code=bwst

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 FallbackFactory<CheckCodeClient> {@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 LambdaQueryWrapper<XcUser>().eq(XcUser::getUsername,username));//查询到用户不存在,要返回null即可,spring security框架抛出异常用户不存在if(xcUser==null){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;}
}

在下面打上断点: 

相关文章:

《学成在线》微服务实战项目实操笔记系列(P92~P120)【下】

史上最详细《学成在线》项目实操笔记系列【下】&#xff0c;跟视频的每一P对应&#xff0c;全系列18万字&#xff0c;涵盖详细步骤与问题的解决方案。如果你操作到某一步卡壳&#xff0c;参考这篇&#xff0c;相信会带给你极大启发。 四、课程发布模块 4.1 (课程发布)模块需求…...

php数据类型以及运算符、判断条件

php数据类型以及运算符 1. php数据类型2. 使用举例3. 运算符4. 判断条件if else elseif 1. php数据类型 包括 String(字符串)、Integer(整型)、Float(浮点型)、Boolean(布尔型)、Array(数组)、Object(对象)、NULL(空值) 2. 使用举例 1.字符串 2.整型 3.浮点型 4.布尔型 5.数组…...

大数据01-导论

零、文章目录 大数据01-导论 1、数据与数据分析 **数据&#xff1a;是事实或观察的结果&#xff0c;是对客观事物的逻辑归纳&#xff0c;是用于表示客观事物的未经加工的原始素材。**数据可以是连续的值&#xff0c;比如声音、图像&#xff0c;称为模拟数据&#xff1b;也可…...

智能网卡(SmartNIC):增强网络性能

在当今的数字时代&#xff0c;网络性能和数据安全是各行各业面临的关键挑战。智能网卡是一项颠覆性的技术创新&#xff0c;对增强网络性能和加强数据安全性具有关键推动作用。本文旨在探讨智能网卡的工作原理及其在不同应用场景中的重要作用。 什么是智能网卡&#xff1f; 智…...

算法刷题day14

目录 引言一、平均二、三国游戏三、松散子序列 引言 今天做了三道新题&#xff0c;类型是贪心、枚举、DP&#xff0c;不是特别难&#xff0c;但是努力一下刚好能够够得上&#xff0c;还是不错的&#xff0c;只要能够一直坚持下去&#xff0c;不断刷题不断总结&#xff0c;就是…...

个性签名大全

只许一生浮世清欢愿我以孤独作为铠甲&#xff0c;自此不再受伤愿我是阳光&#xff0c;明媚而不忧伤我不敢太勇敢太执着太骄傲&#xff0c;我怕失去开始你是我的天使&#xff0c;最后你是我的唯一姐的霸气&#xff0c;无人能比&#xff0c;哥的傲气&#xff0c;无人能朋唯有万事…...

前端常用代码整理(不断更新中)— js,jquery篇(2)

目录 1.随机生成字符串 2.删除数组中重复元素 3.RGB到十六进制转换机制 4.打乱一个数组&#xff0c;重新组合 5.获取两个日期的时间间隔 &#xff08;天数&#xff09; 6.获取当天属于今年的第几天 7.截取字符串长度,超过部分显示为 ... 8.判断数组是否为空 9.英文句子首…...

普中51单片机学习(六)

点亮第一个LED LED相关知识 LED,即发光二极管&#xff0c;是一种半导体固体发光器件。工作原理为&#xff1a;LED的工作是有方向性的&#xff0c;只有当正级接到LED阳极&#xff0c;负极接到LED的阴极的时候才能工作&#xff0c;如果反接LED是不能正常工作的。其原理图如下 …...

visual studio注册码

最近在研究c/c 安装visual studio 需要注册 技术博客http://idea.coderyj.com/ 注册码 Visual Studio 2022(VS2022)激活码&#xff1a; Pro&#xff08;专业版&#xff09;: TD244-P4NB7-YQ6XK-Y8MMM-YWV2J Enterprise&#xff08;企业版&#xff09;: VHF9H-NXBBB-638P6-6JHC…...

Studio One 6.5下载安装激活图文教程

Studio One 6.5是由PreSonus公司打造一款功能强大的数字音乐创作软件&#xff0c;不仅为用户们提供了制作、混合、掌握和执行所有操作&#xff0c;还提供了简洁直观的主界面&#xff0c;因此使用起来也是十分的简单&#xff0c;就算是初学者也可以快速的上手使用起来&#xff0…...

Kubernetes(K8S)集群部署实战

目录 一、准备工作1.1、创建3台虚拟机1.1.1、下载虚拟机管理工具1.1.2、安装虚拟机管理工具1.1.3、下载虚Centos镜像1.1.4、创建台个虚拟机1.1.5、设置虚拟机网络环境 1.2、虚拟机基础配置&#xff08;3台虚拟机进行相同处理&#xff09;1.2.1、配置host1.2.2、关闭防火墙1.2.3…...

流畅的Python(十)-序列的修改、散列和切片

一、核心要义 以第九章定义的二维向量为基础&#xff0c;定义表示多为向量的Vector类。该类将支持如下功能&#xff1a; 1. 基本的序列协议 2. 适当的切片支持&#xff0c;且返回的是新Vector实例 3.综合各个元素的值计算散列值 4.格式化展示 二、代码示例 1、前情提要 …...

TCP/IP五层各层协议详解

TCP/IP协议栈是网络通信的基础&#xff0c;它由五层协议组成&#xff0c;分别是物理层、数据链路层、网络层、传输层和应用层。以下是对各层协议的详细解释&#xff1a; 1. 物理层&#xff08;Physical Layer&#xff09;&#xff1a;该层负责传输比特流&#xff0c;主要定义传…...

MySQL 基础知识(九)之视图

目录 1 视图的介绍 2 视图算法 3 创建视图 4 查看视图结构 5 修改视图 6 删除视图 7 参考文档 1 视图的介绍 视图是一张并不存储数据的虚拟表&#xff0c;其本质是根据 SQL 语句动态查询数据库中的数据。数据库中只存放了视图的定义&#xff0c;通过 SQL 语句使用视图时…...

算法之力扣数青蛙

题目连接 文章目录 题目解析算法原理第一步第二步第三步第三步第四步指向o 代码讲解代码实现 题目解析 先给大家来讲解一下这个题目的意思吧&#xff0c;这个题目是说呢给你一个蛙叫的字符串让你去设计一个算法求出发出这种蛙叫最少需要几只青蛙。比如说第一个样例发出这种叫声…...

【后端高频面试题--Nginx篇】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;后端高频面试题 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; 后端高频面试题--Nginx篇 往期精彩内容什么是Nginx&#xff1f;为什么要用Nginx&#xff1f;为…...

TiDB 在医疗保障信息平台的应用实践

文章介绍了 TiDB 在医疗保障信息平台中的应用。东软医保云应用管理平台通过与 TiDB 联合&#xff0c;成功满足了医疗保障业务中高并发、实时性和复杂查询的要求。在某地市医疗保障信息平台的实践中&#xff0c;TiDB 分布式数据库有效实现了在线交易和实时分析服务&#xff0c;日…...

支付交易——跨境交易

摘要 老王兢兢业业经营生意多年&#xff0c;一步步从小杂货店做到现在&#xff0c;成立大型贸易公司。在做大做强的过程中&#xff0c;老王觉得国内市场已经饱和&#xff0c;竞争处处是红海。老王留意海外很多年了&#xff0c;决定走出去&#xff0c;转向海外:将国外的商品引进…...

上位机图像处理和嵌入式模块部署(上位机主要功能)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 目前关于机器视觉方面&#xff0c;相关的软件很多。比如说商业化的halcon、vision pro、vision master&#xff0c;当然也可以用opencv、pytorch自…...

【前端工程化面试题】webpack的module、bundle、chunk分别指的是什么?

首先从语法方面 在配置文件中有 module 这个配置项&#xff0c;里面有 rules 选项用来配置各种 loader&#xff0c;还有其他各种选项&#xff0c;参考官网。bundle 和 chunk 在配置文件中是没有这个选项的&#xff0c;但是会出现在配置的值中。 module 模块 指单个文件&#xf…...

软件实例分享,家具生产出库管理系统软件教程

软件实例分享&#xff0c;家具生产出库管理系统软件教程 一、前言 以下软件程序教程以 佳易王家具行业生产出库管理系统软件V16.1为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 销售管理——产品状态查询变更&#xff0c;可以根据生产进度变更…...

[uniapp的页面传参]详细讲解uniapp中页面传参的传递方式和接受方式 使用案例 代码注释

目录 一、传递方式1. URL传参2. Storage传参3. Vuex传参4.api传参eventChannel 二、接受方式1. URL传参2. Storage传参3. Vuex传参4.api传参eventChannel 三、使用案例四.提醒 在uniapp中&#xff0c;页面传参是非常常见的需求。本文将详细讲解uniapp中页面传参的传递方式和接受…...

Python实现时间序列分析霍尔特季节性平滑模型(Holt算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 霍尔特季节性平滑模型是指数平滑技术的一种扩展形式&#xff0c;由E. S. Holt和P. R. Winters分别独立…...

Rokid Station 进fastboot

前一阵子手里的station开不开机了&#xff0c;反复重启&#xff0c;摸索出进fastboot的方法&#xff1a; 关机状态下同时按电源键下面的确认键&#xff08;○键&#xff09;&#xff0c;指示灯会进入白色常亮状态&#xff0c;插入电脑会在设备管理器内显示DNL设备&#xff08;…...

Java支持的默认访问修饰符是什么?

Java支持的默认访问修饰符是没有指定任何访问修饰符&#xff0c;通常被称为“包访问级别”或“默认访问级别”。当一个类成员&#xff08;包括类、接口、变量以及方法&#xff09;没有显式地指定任何访问修饰符时&#xff0c;它就会拥有默认访问级别。 在默认访问级别下&#…...

Java使用Documents4j实现Word转PDF(知识点+案例)

文章目录 前言源码获取一、认识Documents4j二、快速集成2.1、pom.xml依赖2.2、word转PDF实现项目目录WordUtils.javaDemo6.java测试效果 参考文章资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里…...

CSimplemathproblem ---- 牛客网

题目描述 这一节课&#xff0c;Priest给大家做了一个小测试。 老师给了你两个正整数X, Y。并按照一下规则做运算&#xff0c;求出答案Z。 如果X是Y的因数&#xff0c;则Z等于X Y。否则Z Y - X。 输入描述: 输入两个正整数X, Y。 (1 < X < Y < 100000000000000)。 输…...

[嵌入式系统-27]:RT-Thread -14- 操作系统配置:rtconfig.h文件与menuconfig命令

目录 一、rtconfig.h 1.1 概述 1.2 软硬件资源配置 1.3 功能模块选择 1.4 内核配置详解 1.5 调度器配置 1.6 硬件设备驱动配置 1.7 网络配置 1.8 调试配置 二、menuconfig 2.1 概述 2.2 主要功能 三、RT Thread配置 VS Linux配置 一、rtconfig.h 1.1 概述 rtco…...

C++面向对象程序设计-北京大学-郭炜【课程笔记(一)】

C面向对象程序设计-北京大学-郭炜【课程笔记&#xff08;一&#xff09;】 1、引用的概念1.1、引用应用的简单示例1.2、常引用 2、"const"关键字的用法&#xff08;常量指针/指针常量&#xff09;3、动态内存分配4、内联函数5、函数重载5.1、什么是函数重载5.2、函数…...

C语言:国家名称按字母表排序

题目描述 输入一个整数n(n<20)&#xff0c;表示待输入国家的数量。随后输入n个国家或地区的名称 (名称长度为1~30)&#xff0c;要求按字母顺序升序输出。 注意&#xff1a;名称中可能包含空格符。 提示 字符串比较请使用函数&#xff1a; int strcmp(const char* str1&a…...

2/18作业

1. #!/bin/bash function fun() { uidgrep ^ubuntu /etc/passwd | cut -d : -f 3 gidgrep ^ubuntu /etc/passwd | cut -d : -f 4 echo "uid为$uid,gid为$gid" } resultfun echo $result...

书生浦语笔记与作业汇总

第一节笔记 第二节笔记与作业 第三节笔记 第三节作业 第四节笔记 第四节作业 第五节笔记 第五节作业 第六节笔记 第六节作业...

嵌入式培训机构四个月实训课程笔记(完整版)-Linux ARM驱动编程第五天-ARM Linux编程之自动创建节点 (物联技术666)

链接&#xff1a;https://pan.baidu.com/s/1V0E9IHSoLbpiWJsncmFgdA?pwd1688 提取码&#xff1a;1688 驱动程序编写好后&#xff0c;还需要创建设备节点&#xff0c;有两种方式&#xff0c;一是通过mknod命令去手动创建&#xff0c;例如&#xff1a;mknod /dev/hello c 250 0&…...

基于51/STM32单片机的智能药盒 物联网定时吃药 药品分类

功能介绍 以51/STM32单片机作为主控系统&#xff1b; LCD1602液晶显示当前时间、温湿度、药品重量 3次吃药时间、药品类目和药品数量 HX711压力采集当前药品重量 红外感应当前药盒是否打开 DS1302时钟芯片显示当前年月日、时分秒、星期 DHT11采集当前环境温度和湿度 …...

【学网攻】 第(27)节 -- HSRP(热备份路由器协议)

系列文章目录 目录 系列文章目录 文章目录 前言 一、HSRP(热备份路由器协议)是什么&#xff1f; 二、实验 1.引入 实验目标 实验背景 技术原理 实验步骤 实验设备 实验拓扑图 实验配置 实验验证 文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻】 第(2)节 -- 交…...

【实战】二、Jest难点进阶(三) —— 前端要学的测试课 从Jest入门到TDD BDD双实战(七)

文章目录 一、Jest 前端自动化测试框架基础入门二、Jest难点进阶3.mock timers 学习内容来源&#xff1a;Jest入门到TDD/BDD双实战_前端要学的测试课 相对原教程&#xff0c;我在学习开始时&#xff08;2023.08&#xff09;采用的是当前最新版本&#xff1a; 项版本babel/core…...

Python编程中的异常处理

什么是异常&#xff1f; 程序错误&#xff08;errors&#xff09;有时也被称为程序异常&#xff08;exceptions&#xff09;&#xff0c;这是每个编程人员都会经常遇到的问题。在过去&#xff0c;当遇到这类情况时&#xff0c;程序会终止执行并显示错误信息&#xff0c;通常是…...

mysql3.7之触发器

1.触发器的定义 触发器是由事件来触发某个操作&#xff0c;这些事件包括INSERT、UPDATE、DELETE事件。所谓事件就是指用户的动作或者触发某项行为。如果定义了触发程序&#xff0c;当数据库执行这些语句时候&#xff0c;就相当于事件发生了&#xff0c;就会自动激发触发器执行…...

12.QT文件对话框 文件的弹窗选择-QFileDialog

目录 前言&#xff1a; 技能&#xff1a; 内容&#xff1a; 1. 界面 2.信号槽 3.其他函数 参考&#xff1a; 前言&#xff1a; 通过按钮实现文件弹窗选择以及关联的操作 效果图就和平时用电脑弹出的选文件对话框一样 技能&#xff1a; QString filename QFileDialog::ge…...

ArcGIS学习(八)基于GIS平台的控规编制办法

ArcGIS学习(八)基于GIS平台的控规编制办法 上一任务我们学习了”如何进行图片数据的矢量化?" 这一关我们来学习一个比较简单的案例一一”如何在ArcGIS中录入控规指标,绘制控规图纸?" 首先,先来看看这个案例的分析思路以及导入CAD格式的控规图纸。 接着,来看…...

软件测试-自动化测试-面试题研究,知识要点,高频、重点知识点,自动化测试知识要点、知识梳理-PYTHON+自动化,评估试题

自动化项目实战能力评估 介绍一下你的自动化测试框架&#xff1f; 我的框架主要根据分层思想设计了几个独立模块&#xff1a; 模块一&#xff1a;主要存放通用业务代码&#xff0c;比如接口访问&#xff0c;数据库操作&#xff0c;excel 操作&#xff0c;等等 模块二&#xf…...

一.重新回炉Spring Framework: 理解Spring IoC

1. 写在前面的话 说实话&#xff0c;从事java开发工作时间也不短了&#xff0c;对于Spring Framework&#xff0c;也是天天用&#xff0c;这期间也碰到了很多问题&#xff0c;也解决了很多问题。可是&#xff0c;总感觉对Spring Framework还是一知半解&#xff0c;不能有个更加…...

目标检测算法之YOLOv5的应用实例(零售业库存管理、无人机航拍分析、工业自动化领域应用的详解)

1.YOLOv5在"零售业库存管理"领域的应用 在零售业库存管理中,YOLOv5可以帮助自动化商品识别和库存盘点过程。通过使用深度学习模型来实时识别货架上的商品,零售商可以更高效地管理库存,减少人工盘点的时间和成本。以下是一个使用YOLOv5进行商品识别的Python脚本示…...

[蓝桥 2017]九宫幻方

九宫幻方 题目描述 小明最近在教邻居家的小朋友小学奥数&#xff0c;而最近正好讲述到了三阶幻方这个部分&#xff0c;三阶幻方指的是将 1~9 不重复的填入一个 3*3 的矩阵当中&#xff0c;使得每一行、每一列和每一条对角线的和都是相同的。 三阶幻方又被称作九宫格&#xf…...

Qt - 编译报错:“invalid use of incomplete type ‘class Ui::xxx‘ui(new Ui::xxx)”的解决方法

问题起因 今天在创建Qt设计器界面类时&#xff0c;类名的英文拼写错误&#xff0c;然后就重命名了文件&#xff0c;而Qt Creator也帮我自动修改了一部分内容&#xff0c;之后我手动将cpp文件中的#include " *** "里的内容给修改了&#xff0c;构造函数和析构函数处也…...

基于Doris构建亿级数据实时数据分析系统

背景 随着公司业务快速发展&#xff0c;对业务数据进行增长分析的需求越来越迫切&#xff0c;与此同时我们的业务数据量也在快速激增、每天的数据新增量大概在30w 左右&#xff0c;一年就会产生1 个亿的数据&#xff0c;显然基于传统MySQL数据库已经无法支撑满足以上需求 基于上…...

javascript中的prototype;javascript中的原型链

文章目录 深入理解JavaScript原型链1. 什么是原型链&#xff1f;2. 原型链的结构3. 如何访问原型链&#xff1f;4. 示例演示原型链5. 原型链与继承6. 实际应用场景 深入理解JavaScript原型链 1. 什么是原型链&#xff1f; 在JavaScript中&#xff0c;每个对象都有一个原型&am…...

CI/CD部署

什么是CI&#xff0c;什么是CD CI和CD是软件开发中持续集成和持续交付的缩写。 CI代表持续集成&#xff08;Continuous Integration&#xff09;&#xff0c;是一种实践&#xff0c;旨在通过自动化构建、测试和代码静态分析等过程&#xff0c;频繁地将代码变更合并到共享存储…...

定点数,定点数二维向量,定点数三维向量,定点数数学类

定点数&#xff0c;定点数二维向量&#xff0c;定点数三维向量&#xff0c;定点数数学类 介绍浮点数定点数封装的定点数FixedNumber定点数二维向量定点数三维向量定点数数学类总结 介绍 众所周知定点数是用于做帧同步时保持不同cpu不同设备保持一致稳定的代替浮点数的变量&…...

安装ts-node有感

起因&#xff1a;想要在vsCode上运行ts脚本 解决方案&#xff1a; 1.安装vsCode插件 code runner 2.全局安装ts-node 这一步遇到三个问题&#xff1a; ①.node版本问题&#xff1a;需安装版本18以上node&#xff0c;可使用nvm去控制不同的node版本 ②.certificate has exp…...