社区系统项目复盘-3

news2024/11/24 20:26:15

文章目录

      • 过滤敏感词
      • 发布帖子
      • 帖子详情
      • 添加评论
      • 私信列表
      • 发送私信
      • 统一处理异常
      • 统一记录日志

基于Springboot的核心功能实现

包括自定义前缀树过滤敏感词;使用异步请求的方式发布帖子;查看帖子详情;添加评论时需要同时增加评论的数据和修改帖子的评论数量,进行两步数据库操作,出于安全考虑采用事务管理;私信列表部分包括显示私信列表与私信详情两个子功能;采用异步方式发送私信;配置Controller的全局配置类进行统一异常处理;采用AOP实现统一记录日志。

过滤敏感词

  • 前缀树
    • 名称:Tire、字典树、查找树
    • 特点:查找效率高,消耗内存大
    • 应用:字符串检索、词频统计、字符串排序等
  • 敏感词过滤器
    • 定义前缀树

    • 根据敏感词,初始化前缀树

    • 编写过滤敏感词的方法

      https://res.craft.do/user/full/fd148a50-4a5b-9a85-bec3-e1645571e2c7/doc/8DD0B865-8ACD-4D75-881A-7B9664DC763F/9E3219F6-85F7-480D-8265-7D08EB91804C_2/JD4Ydd5uwLuqv4zkrNls6vRbdYyP9bUQB36h1j4SsHAz/Image.png

发布帖子

该功能的实现需要异步请求,使用了AJAX

示例:怎么使用jQuery发送AJAX请求?

1.需要在Controller里添加处理异步请求的方法,一般请求方式为post,因为通常是浏览器通过异步的方式向服务器提交一些数据,然后服务端向浏览器响应一个提示,不需要返回网页而是返回一个字符串,所以需要添加@ResponseBody注解

// ajax示例
@RequestMapping(path = "/ajax", method = RequestMethod.POST)
@ResponseBody
public String testAjax(String name, int age) {
	// 请求处理逻辑
	System.out.println(name);
	System.out.println(age);
	// 返回JSON字符串,方法getJSONString()封装在工具类中
	return CommunityUtil.getJSONString(0, "操作成功!");
}

2.需要在网页中写一段jQuery代码,访问处理异步请求的方法。

分为两步:引入jQuery,使用jQuery发送异步请求

// ajax示例
<script src="<https://code.jquery.com/jquery-3.3.1.min.js>" crossorigin="anonymous"></script>
    <script>
        function send() {
            $.post(
                "/community/alpha/ajax",
                {"name":"张三","age":23},
                function(data) {
                    data = $.parseJSON(data);
                    console.log(typeof(data));
                    console.log(data.code);
                    console.log(data.msg);
                }
            );
        }
    </script>

采用AJAX请求,实现发布帖子的功能:

https://res.craft.do/user/full/fd148a50-4a5b-9a85-bec3-e1645571e2c7/doc/8DD0B865-8ACD-4D75-881A-7B9664DC763F/33A61C32-9765-4A58-91B0-E3542A3DB34F_2/xBH8A0EQkg1ONoI6wV0aFGFylxZTvEyjydwp6SkCWXUz/Image.png

具体实现:

首先需要定义一个工具类方法处理Json相关的转换,因为服务端要向客户端返回一些提示信息和数据,使用了fastjson,需要提前导入fastjson的jar包

public class CommunityUtil {
    public static String getJSONString(int code, String msg, Map<String,Object> map){
        JSONObject json = new JSONObject();
        json.put("code",code);
        json.put("msg",msg);
        if(map != null){
            for(String key:map.keySet()){
                json.put(key,map.get(key));
            }
        }
        return json.toJSONString();
    }
    public static String getJSONString(int code, String msg){
        return getJSONString(code,msg,null);
    }
    public static String getJSONString(int code){
        return getJSONString(code,null,null);
    }
}

在DiscussPostService中添加增加帖子的相关业务层代码逻辑:

public int addDiscussPost(DiscussPost post){
        // 参数不能为空
        if(post == null){
            throw new IllegalArgumentException("参数不能为空!");
        }

        // 转义HTML标记
        post.setTitle(HtmlUtils.htmlEscape(post.getTitle()));
        post.setContent(HtmlUtils.htmlEscape(post.getContent()));
        // 过滤敏感词
        post.setTitle(sensitiveFilter.filter(post.getTitle()));
        post.setContent(sensitiveFilter.filter(post.getContent()));

        return discussPostMapper.insertDiscussPost(post);
    }

新建DiscussPostController类,在里面写发布帖子的方法:

// 发布帖子
@RequestMapping(path = "/add",method = RequestMethod.POST)
@ResponseBody
public String addDiscussPost(String title,String content){
	// 判断是否是登录状态
    User user = hostHolder.getUser();
    if(user == null){
        return CommunityUtil.getJSONString(403,"你还没有登录哦!");
    }
	// 添加帖子
    DiscussPost post = new DiscussPost();
    post.setUserId(user.getId());
    post.setTitle(title);
    post.setContent(content);
    post.setCreateTime(new Date());
    discussPostService.addDiscussPost(post);

    // 报错的情况,将来统一处理
    return CommunityUtil.getJSONString(0,"发布成功!");
}

浏览器发送异步请求:

// 获取标题和内容
	var title = $("#recipient-name").val();
	var content = $("#message-text").val();
	// 发送异步请求
	$.post(
		CONTEXT_PATH+"/discuss/add",
		{"title":title,"content":content},
		function (data){
			data = $.parseJSON(data);
			// 在提示框中显示返回的消息
			$("#hintBody").text(data.msg);
			// 显示提示框
			$("#hintModal").modal("show");
			// 2秒后,自动隐藏提示框
			setTimeout(function(){
				$("#hintModal").modal("hide");
				// 刷新页面
				if(data.code == 0){
					window.location.reload();
				}
			}, 2000);
		}
	)

帖子详情

按照数据层,业务层,表现层逐级来写就行,注意 帖子详情中的内容很多,点赞功能暂未实现,点赞的相关内容后面补充(大概redis部分…)

https://res.craft.do/user/full/fd148a50-4a5b-9a85-bec3-e1645571e2c7/doc/8DD0B865-8ACD-4D75-881A-7B9664DC763F/F89C0D01-64DA-48CC-A241-345FFCE48BFA_2/T5oH6HVMrkxG24AnOURIpc6MlrNqxm2sTC7PmrbgRTYz/Image.png

https://res.craft.do/user/full/fd148a50-4a5b-9a85-bec3-e1645571e2c7/doc/8DD0B865-8ACD-4D75-881A-7B9664DC763F/E795B24E-D81F-4F3B-8A0C-B542D93C6D28_2/gRNQY3llCkBV0rvmObmRfdlhdgI9wt6HINs4qtGyKscz/Image.png

表现层代码相对复杂(详细如下),主要是套娃套娃套娃🪆🪆🪆…

帖子,评论(帖子的评论),回复(帖子的评论的回评论)…

// 帖子详情
    @RequestMapping(path = "/detail/{discussPostId}", method = RequestMethod.GET)
    public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page) {
        // 帖子
        DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
        model.addAttribute("post", post);
        // 作者
        User user = userService.findUserById(post.getUserId());
        model.addAttribute("user", user);
        // 点赞数量
        // 点赞状态
        // 评论分页信息
        page.setLimit(5);
        page.setPath("/discuss/detail/" + discussPostId);
        page.setRows(post.getCommentCount());
        // 评论: 给帖子的评论
        // 回复: 给评论的评论
        // 评论列表
        List<Comment> commentList = commentService.findCommentsByEntity(ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());
        // 评论VO(详情)列表
        List<Map<String, Object>> commentVoList = new ArrayList<>();
        if (commentList != null) {
            for (Comment comment : commentList) {
                // 评论VO
                Map<String, Object> commentVo = new HashMap<>();
                // 评论
                commentVo.put("comment", comment);
                // 评论的作者
                commentVo.put("user", userService.findUserById(comment.getUserId()));
                // 点赞数量
                // 点赞状态
                // 回复列表
                List<Comment> replyList = commentService.findCommentsByEntity(
                        ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE);
                // 回复VO(详情)列表
                List<Map<String, Object>> replyVoList = new ArrayList<>();
                if (replyList != null) {
                    for (Comment reply : replyList) {
                        Map<String, Object> replyVo = new HashMap<>();
                        // 回复
                        replyVo.put("reply", reply);
                        // 回复的作者
                        replyVo.put("user", userService.findUserById(reply.getUserId()));
                        // 回复的目标
                        User target = reply.getTargetId() == 0 ? null : userService.findUserById(reply.getTargetId());
                        replyVo.put("target", target);
                        // 点赞数量
                        // 点赞状态
                        replyVoList.add(replyVo);
                    }
                }
                commentVo.put("replys", replyVoList);
                // 回复数量
                int replyCount = commentService.findCommentCount(ENTITY_TYPE_COMMENT, comment.getId());
                commentVo.put("replyCount", replyCount);
                commentVoList.add(commentVo);
            }
        }
        model.addAttribute("comments", commentVoList);
        return "/site/discuss-detail";
    }

添加评论

添加评论是在帖子详情页面,给帖子评论,或者是给帖子的评论进行评论(也叫回复)

因为既要增加评论的数据,又要修改帖子的评论数量,所以需要进行两步数据库操作,从安全性考虑,将这两步数据库操作放到一个事务里进行管理。(⚠️:事务管理)

使用声明式事务管理,用@Transactional进行注解,使用isolation属性声明事务的隔离级别,使用propagation属性声明事务的传播机制。

⚠️注意:只有对帖子进行评论的时候才需要更新帖子的评论数量,需要判断一下。业务层逻辑如下:

// 添加评论
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public int addComment(Comment comment){
        if(comment == null){
            throw new IllegalArgumentException("参数不能为空!");
        }
        // 添加评论
        comment.setContent(HtmlUtils.htmlEscape(comment.getContent()));
        comment.setContent(sensitiveFilter.filter(comment.getContent()));
        int rows =  commentMapper.insertComment(comment);
        // 更新帖子评论数量
        if(comment.getEntityType() == ENTITY_TYPE_POST){
            int count = commentMapper.selectCountByEntity(comment.getEntityType(),comment.getEntityId());
            discussPostService.updateCommentCount(comment.getEntityId(),count);
        }
        return rows;
    }

https://res.craft.do/user/full/fd148a50-4a5b-9a85-bec3-e1645571e2c7/doc/8DD0B865-8ACD-4D75-881A-7B9664DC763F/F9345099-7A71-4C54-83AC-B48F4E98CDFF_2/0SvwAtO5zMJcZUQdBcPxzWtxsBScaZKtIymj0CaTxWEz/Image.png

补充:为什么在添加评论的时候既需要触发评论事件,又需要触发发帖事件?

  • 触发评论事件是因为,系统向被评论用户发送系统通知时是采用消息队列的方式实现的。
  • 仅在对帖子进行评论的时候会触发发帖事件,因为当对帖子进行评论时,帖子评论数量发生改变,也就是post.commentCount会有变化,所以要将Elasticsearch中的数据刷新一下。

私信列表

私信列表包括两个子功能,显示会话列表和私信详情信息。

▶️ 对于显示会话列表,需要查询当前用户的会话列表,每个会话只显示一条最新的私信,分页显示该列表,详细步骤主要包括:首先获得当前登录用户,并且设置分页信息,然后查询当前用户的会话列表,遍历会话列表,查询每一条会话所需要显示的其他信息(该会话私信的总数,该会话未读私信的数量,该会话对面用户的用户信息),除此之外,会话列表中还需要显示当前用户所有未读消息的数量。

▶️ 对于私信详情功能,需要查询某个会话所包含的私信列表,分页显示该列表,并且将显示的私信设置为已读状态,详细步骤主要包括:设置分页信息,根据会话Id查询私信列表,遍历私信列表,查询该条私信发送者有关的用户信息,最后将所有的未读私信设置为已读。

https://res.craft.do/user/full/fd148a50-4a5b-9a85-bec3-e1645571e2c7/doc/8DD0B865-8ACD-4D75-881A-7B9664DC763F/51D39B57-A1FB-422D-8A0E-7B85E6348494_2/xlILUHrnIJWaUn4maZkvm3muyyqk2WxniemKR6livwUz/Image.png

发送私信

采用异步方式发送私信,发送成功后刷新私信列表

🔎详细步骤:首先根据私信接收者的用户名获得接收者的用户信息,如果接收者用户不存在,直接返回错误提示;如果接收者用户存在,那么构造message对象,将message存到数据库中。

https://res.craft.do/user/full/fd148a50-4a5b-9a85-bec3-e1645571e2c7/doc/8DD0B865-8ACD-4D75-881A-7B9664DC763F/05983D89-9BB3-495B-90D0-2303AF9CB665_2/lcFdk1n7GWMam7HoPKBmTDHZn5upTg8IisWKegwRz2wz/Image.png

统一处理异常

服务端的三层架构:表现层 → 业务层 → 数据层。

浏览器发送的请求一律发给表现层,表现层调用业务层,业务层调用数据层。数据层出现异常后会抛出给它的调用者业务层,业务层会把异常抛出给表现层,所以无论是哪个层的异常,最终都会汇集到表现层,所以对表现层的异常进行捕获和处理就可以处理所有的异常。

https://res.craft.do/user/full/fd148a50-4a5b-9a85-bec3-e1645571e2c7/doc/8DD0B865-8ACD-4D75-881A-7B9664DC763F/93DB8CA5-66D0-47A1-8D94-53A45D1EB35D_2/RdT4bGqyNsp2TvHMRWRZqpwbnU6Fdu65wATALDmFXX0z/Image.png

🔎 Springboot提供的方案:只需要在特定路径src/main/resources/templates/error下,添加对应错误状态的页面,那么在发现相应错误的时候,就会自动的跳转到对应页面。错误状态页面的名字必须是错误状态,比如404。

跳出错误页面是表面上的处理,内在记录日志部分还没有处理。spring提供了@ControllerAdvice注解进行相关处理。

  • @ControllerAdvice:用于修饰类,表示该类是Controller的全局配置类,在此类中,可以对Controller进行如下三种全局配置:异常处理方案、绑定数据方案、绑定参数方案
  • 异常处理方案:@ExceptionHandler:用于修饰方法,该方法会在Controller出现异常后被调用,用于处理捕获到的异常
  • 绑定数据方案:@ModelAttribute:用于修饰方法,该方法会在Controller方法执行前被调用,用于为Model对象绑定参数
  • 绑定参数方案:@DataBinder:用于修饰方法,该方法会在Controller方法执行前被调用,用于绑定参数的转换器

具体实现:

  • 在Controller层写好处理error请求的方法getErrorPage()。
  • 利用@ControllerAdvice注解声明一个Controller全局配置类,对所有Controller的异常做统一的处理。在controller/advice路径下,新建一个类ExceptionAdvice,并用@ControllerAdvice进行注解。
  • 定义处理异常的方法handleException()并用@ExceptionHandler进行注解。
  • 记录日志,包括详细的日志信息。
  • 给浏览器一个响应,重定向到错误页面。注意:这里可能是普通请求也有可能是异步请求,需要区分处理,通过request对象来获取请求方式request.getHeader("x-requested-with");如果请求方式为 XMLHttpRequest,说明是异步请求,响应一个字符串。否则重定向到错误页面。

https://res.craft.do/user/full/fd148a50-4a5b-9a85-bec3-e1645571e2c7/doc/8DD0B865-8ACD-4D75-881A-7B9664DC763F/F3F39B20-BCB2-470A-B5CF-6593E72E8E0B_2/wCOszwfxzz984aA7j4Gv5BYvZWuEHYmdU7tFxmjXSZcz/Image.png

小结:不需要对Controller做任何处理,只需要使用@ControllerAdvice注解声明一个Controller的全局配置类,对添加了@Controller注解的所有类进行一个统一的异常处理。在全局配置类中使用@ExceptionHandler注解定义一个异常处理方法,对于所有类型的异常进行处理,处理过程是:首先输出异常信息(包括异常的详细信息),然后根据请求方式的不同,进行不同的响应,如果是异步请求,则响应给浏览器一个字符串;如果是普通请求,则重定向到错误页面。请求方式是通过request对象来获取的。

// 代码实现
@ControllerAdvice(annotations = Controller.class)
public class ExceptionAdvice {

    private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);

    @ExceptionHandler({Exception.class})
    public void handleException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException {
        logger.error("服务器发生异常:" + e.getMessage());
        for(StackTraceElement element : e.getStackTrace()){
            logger.error(element.toString());
        }
        String xRequestedWith = request.getHeader("x-requested-with");
        if("XMLHttpRequest".equals(xRequestedWith)){
            response.setContentType("application/plain;charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.write(CommunityUtil.getJSONString(1,"服务器异常!"));
        }else{
            response.sendRedirect(request.getContextPath()+"/error");
        }
    }
}

统一记录日志

1.AOP的基本实现:

首先定义一个方面组件,该方面组件用@Component @Aspect处理,在方面组件里定义切点和通知。

切点:通过@Pointcut注解实现

// 示例
@Pointcut("execution(* com.community.service.*.*(..))")
public void pointcut() {

}

通知:(共有五种通知方式)

  1. 在连接点前织入 @Before
// 示例
@Before("pointcut()")
public void before() {
	System.out.println("before");
}
  1. 在连接点后织入@After
// 示例
@After("pointcut()")
public void after() {
	System.out.println("after");
}
  1. 在有了返回值以后再处理逻辑 @AfterReturning
// 示例
@AfterReturning("pointcut()")
public void afterRetuning() {
	System.out.println("afterRetuning");
}
  1. 在抛异常的时候织入代码 @AfterThrowing
// 示例
@AfterThrowing("pointcut()")
public void afterThrowing() {
	System.out.println("afterThrowing");
}
  1. 环绕通知,既想在前面织入逻辑,又想在后面织入逻辑 @Around

要有返回值(Object)和参数(ProceedingJoinPoint joinPoint),抛出异常throws Throwable。除了环绕通知以外的其他通知也可以添加连接点JointPoint的参数。

利用连接点,jointPoint.proceed();就是调用目标对象的方法逻辑,将目标组件的返回值return(return obj)。

// 示例
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
	System.out.println("around before");  // 在目标组件前面织入逻辑
	Object obj = joinPoint.proceed();
	System.out.println("around after");   // 在目标组件后面织入逻辑
	return obj;
}

2.统一记录日志需求:对所有的业务组件记录日志,在业务组件调用的一开始记录日志,采用@Before的方式。日志记录格式:用户xxx,在xxx,访问了xxx。

https://res.craft.do/user/full/fd148a50-4a5b-9a85-bec3-e1645571e2c7/doc/8DD0B865-8ACD-4D75-881A-7B9664DC763F/BE7C609C-8CB4-4BC4-9D35-2413BBA348D0_2/XpQPCZI4Iuh8GF7a5inl9AaHz1Evntj18q1epT9U960z/Image.png

具体实现:

  • 定义一个方面组件:ServiceLogAspect,使用@Component@Aspect进行注解
  • 声明切点pointcut(),使用了@Pointcut注解, 切点为所有的业务层方法
@Pointcut("execution (* com.nowcoder.community.service.*.*(..))")
public void pointcut(){

}
  • 定义通知,日志格式:用户[1.2.3.4],在[xxx],访问了[service.xxx()].因为是在业务组件调用的一开始就记录日志,也就是在切点前织入代码逻辑,所以使用@Before注解。
    问题1:用户IP怎么获取?可以通过request对象,先利用RequestContextHolder工具类中的getRequestAttributes()方法获取attributes;然后attributes.getRequest()获得request对象;最后使用request.getRemoteHost获得用户IP。
    问题2:如何获得当前时间?new Date()获取。
    问题3:如果获得调用的是哪个类哪个方法?jointPoint是程序织入的目标,也就是目标组件要调用的方法,通过jointPoint就可以得到调用的是哪个类的哪个方法。

    • joinPoint.getSignature().getDeclaringTypeName():获得类名;
    • joinPoint.getSignature().getName():获得方法名。

    补充:JoinPoint类,用来获取代理类和被代理类的信息。JoinPoint.getSignature()

@Before("pointcut()")
public void before(JoinPoint joinPoint){
	// 用户[1.2.3.4],在[xxx],访问了[com.nowcoder.community.service.xxx()].
	ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
	if(attributes == null){
		return;
	}
	HttpServletRequest request = attributes.getRequest();
	String ip = request.getRemoteHost();
	String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
	String target = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
	logger.info(String.format("用户[%s],在[%s],访问了[%s].",ip,now,target));
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/45807.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电源设备设计

目录 一、通信电源概述 &#xff08;1&#xff09; &#xff08;2&#xff09;通信电源系统组成 &#xff08;3&#xff09;通信电源供电方式 集中供电示意图 分散供电示意图 USP供电示意图 二、交流供电系统设计 1、交流供电--市电引入 交流低压供电示意图 2、交流供电--UPS &…

Innodb是如何运转的

Innodb是如何运转的Innodb体系架构后台线程Master ThreadIO ThreadPurge ThreadPage Cleaner Thread内存缓存池LRU ListFree Listunzip_LRUflush list重做日志缓冲(redo log buffer)额外的内存池checkpoint技术Sharp CheckpointFuzzy CheckpointMaster Thread的工作方式Innodb …

SparkMlib 之逻辑回归及其案例

文章目录什么是逻辑回归&#xff1f;逻辑回归的优缺点逻辑回归示例——预测回头客逻辑回归示例——预测西瓜好坏逻辑回归示例——预测垃圾邮件什么是逻辑回归&#xff1f; 逻辑回归是一种流行的预测分类响应的方法。它是预测结果概率的广义线性模型的特例。在逻辑回归中&#…

EasyRecovery2022中文版电脑端数据恢复软件

EasyRecovery2023数据恢复软件是一款文件恢复软件&#xff0c;能够恢复内容类型非常多&#xff0c;包括办公文档、文件夹、电子邮件、照片、音频等一些常用文件类型都是可以进行恢复&#xff0c;操作非常简单&#xff0c;只需要将存储设备连接到电脑上&#xff0c;运行EasyReco…

【全志T113-S3_100ask】16-1 linux系统驱动四线电阻屏(tpadc、tslib)

【全志T113-S3_100ask】16-1 linux系统使用TPADC驱动四线电阻屏&#xff08;rtp、tslib&#xff09;&#xff08;一&#xff09;背景&#xff08;二&#xff09;焊接鬼才&#xff08;三&#xff09;解析input上报事件&#xff08;四&#xff09;C语言解析input上报事件&#xf…

大数据技术——Flume简介安装配置使用案例

文章目录1. Flume 概述1.1 Flume简介1.2 Flume的特点1.3 Flume的基础架构2. Flume安装配置2.1 下载地址2.2 安装部署3. Flume 使用案例3.1 实时监控单个追加文件3.2 实时监控目录下多个新文件3.3 实时监控目录下的多个追加文件1. Flume 概述 1.1 Flume简介 Flume是一种可配置、…

【Linux】Linux的环境变量(PATH、env、子进程继承环境变量等)

文章目录环境变量1、从一个小案例认识环境变量PATH2、常用的环境变量相关指令与系统调用3、子进程如何继承环境变量的&#xff1f;4、测试其它环境变量环境变量 1、从一个小案例认识环境变量PATH 我们在shell中通过file查看文件信息&#xff0c;看到我们常使用的指令都是可执…

C++ 类的静态成员详解

目录 前言 一、类的静态成员 1.static关键字 2.静态成员变量 3.静态成员函数 二、程序样例 1.程序演示 2.程序截图 总结 前言 本文记录C中 static 修饰类成员成为静态成员&#xff0c;其中包括静态成员类别、作用和程序演示。 嫌文字啰嗦的可直接跳到最后的总结。 一、类的静…

特征提取 - 骨架、中轴和距离变换

目录 1. 介绍 骨架 skeleton 中轴变换 Medial axis transformation 距离变换 distance transform 2. 距离变换的代码实现 distanceTransform 函数介绍 normalize 函数介绍 取局部最大值 完整代码 3. comparation 1. 介绍 骨架 skeleton 骨架的定义&#xff1a;就是…

【毕业设计】33-基于单片机的直流电机的转速检测与控制设计(原理图工程+PCB工程+源代码工程+仿真工程+答辩论文)

typora-root-url: ./ 【毕业设计】33-基于单片机的直流电机的转速检测与控制设计&#xff08;原理图工程PCB工程源代码工程仿真工程答辩论文&#xff09; 文章目录typora-root-url: ./【毕业设计】33-基于单片机的直流电机的转速检测与控制设计&#xff08;原理图工程PCB工程源…

盘点国内主流数字孪生厂商!你了解几家?

在国内&#xff0c;主流的数字孪生解决方案厂商包括华龙迅达、精航伟泰、羚数智能、力控科技、华力创通、同元软控、优也科技、51world、卡奥斯、摩尔元数、易知微、木棉树软件等。由于中国数字孪生市场仍处于早期发展阶段&#xff0c;且受限于建模、仿真和基于数据融合的数字线…

基于单RGB相机的全新三维表示方法|NeurIPS 2022

随着深度学习的发展&#xff0c;基于单张RGB图像的人体三维重建取得了持续进展。 但基于现有的表示方法&#xff0c;如参数化模型、体素栅格、三角网格和隐式神经表示&#xff0c;难以构筑兼顾高质量结果和实时速度的系统。 针对上述问题&#xff0c;天津大学团队联合清华大学…

Linux用户管理

文章目录一. 引子二. 用户管理1. 用户切换2. 注销用户3. 添加用户4. 设置用户密码5. 删除用户6. 查询用户信息三. 用户组管理1. 新增用户组2. 新增用户时添加组3. 修改用户的组四. 用户和组相关文件1. /etc/passwd2. /etc/shadow3. /etc/group一. 引子 Linux是一个多用户、多任…

【JavaScript作用域】

JavaScript作用域1 本节目标2 作用域2.1 作用域概述2.2 全局作用域2.3 局部作用域3 变量的作用域3.1 变量作用域的分类3.2 全局变量3.3 局部变量3.4 从执行效率看全局变量与局部变量3.5 JS没有块级作用域4 作用域链1 本节目标 说出JavaScript的两种作用域区分全局变量和局部变…

TinyML:是否是FPGA在人工智能方面的最佳应用?

TinyML 也是机器学习的一种&#xff0c;他的特点就是缩小深度学习网络可以在微型硬件中使用&#xff0c;主要应用在智能设备上。超低功耗嵌入式设备正在“入侵”我们的世界&#xff0c;借助新的嵌入式机器学习框架&#xff0c;它们将进一步推动人工智能驱动的物联网设备的普及。…

机器学习:一文从入门到读懂PCA(主成分分析)

深度学习&#xff1a;PCA白化前置知识内积的几何意义基基变换不同基下的向量变换逆矩阵不同基下的空间变换方差协方差协方差矩阵协方差矩阵对角化特征值分解、空间变换主成分分析&#xff08;PCA&#xff09;两个原则公式推导求解流程代码实现PCA的优缺点优点缺点前置知识 维度…

【测试沉思录】18.如何测试微信小程序?

作者&#xff1a;雷远缘 编辑&#xff1a;毕小烦 一. 先知道小程序是什么 啥是小程序&#xff1f; “小程序是一种不需要下载安装即可使用的应用&#xff0c;它实现了应用 “触手可及” 的梦想&#xff0c;用户扫一扫或者搜一下即可打开应用。也体现了 “用完即走” 的理念&am…

[附源码]Python计算机毕业设计SSM基于Java的民宿运营管理网站(程序+LW)

环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 Maven管理等…

详解 Spring Boot 项目中的配置文件

目录 1. Spring Boot 项目中配日文件的作用是什么 2. Spring Boot 配置文件的两种格式 3. properties 配置文件 3.1 properties 配置文件的基本语法 3.2 properties 配置文件的分类 3.3 如何读取配置文件 3.4 properties 配置文件的优缺点分析 4. yml 配置文件 4.1 yml …

【JavaSE】初识泛型

大家好&#xff01;我是保护小周ღ&#xff0c;本期为大家带来的是 Java的泛型&#xff0c;会来大家初步了解什么是泛型&#xff0c;以及泛型的使用&#xff0c;感受一手泛型的思想&#xff0c;面向对象编程太爽了~ 目录 一、泛型是什么&#xff1f; 二、泛型的语法 三、包…