java抽奖系统(一)2.0

news2025/1/8 5:44:59

 1. 项⽬介绍

1.1 背景

        随着数字营销的兴起,企业越来越重视通过在线活动来吸引和留住客⼾。抽奖活动作为⼀种有效的营 销⼿段,能够显著提升⽤⼾参与度和品牌曝光率。于是我们就开发了以抽奖活动作为背景的Spring Boot项⽬,通过这个项⽬提供⼀个全⾯、可靠、易于维护的抽奖平台,该平台将采⽤以下策略:

         集成多种技术组件:利⽤MySQL、Redis、RabbitMQ等常⽤组件,构建⼀个稳定、⾼效、可扩展 的抽奖系统。

        活动、奖品与⼈员管理:允许管理员创建配置抽奖活动;管理奖品信息;管理⼈员信息。

         实现状态机管理:通过精⼼设计的状态机,精确控制活动及奖品状态的转换,提⾼系统的可控性和 可预测性。

         保障数据⼀致性:通过事务管理和数据同步机制,确保数据的⼀致性和完整性。

        加强安全性:实施安全措施,包括数据加密、⽤⼾认证,保护⽤⼾数据和系统安全。

        降低维护成本:提供全⾯的⽇志记录和异常处理机制,简化问题诊断和系统维护。

         提⾼扩展性:采⽤模块化设计与设计模式的使⽤,提⾼系统的灵活性和扩展性。

1.2 需求描述

        1. 包含管理员的注册与登录。

        a. 注册包含:姓名、邮箱、⼿机号、密码

        b. 登录包含两种⽅式:

                 i. 电话+密码登录;

                ii. 电话+短信登录; 验证码获取

                iii. 登录需要校验管理员⾝份。

        2. ⼈员管理:管理员⽀持创建普通⽤⼾,查看⽤⼾列表

                a. 创建普通⽤⼾:姓名,邮箱,⼿机号

                b. ⼈员列表:⼈员id、姓名、⾝份(普通⽤⼾、管理员)

        3. 管理端⽀持创建奖品、奖品列表展⽰功能。

                a. 创建的奖品信息包含:奖品名称、描述、价格、奖品图(上传)

                 b. 奖品列表展⽰(可翻⻚):奖品id、奖品图、奖品名、奖品描述、奖品价值(元)

        4. 管理端⽀持创建活动、活动列表展⽰功能。

                 a. 创建的活动信息包含:

                        i. 活动名称

                        ii. 活动描述

                        iii. 圈选奖品:勾选对应奖品,并设置奖品等级(⼀⼆三等奖),及奖品数量

                        iv. 圈选⼈员:勾选参与抽奖⼈员

                b. 活动列表展⽰(可翻⻚):

                         i. 活动名称

                         ii. 描述 

                        iii. 活动状态:

                                1. 活动状态为进⾏中:点击"活动进⾏中,去抽奖"按钮跳转抽奖⻚

                                2. 活动状态为已完成:点击"活动已完成,查看中奖名单"按钮跳转抽奖⻚查看结果

        5. 抽奖⻚⾯:

                a. 对于进⾏中的活动,管理员才可抽奖。

                b. 每轮抽奖的中奖⼈数跟随当前奖品数量。

                c. 每个⼈只能中⼀次奖

                d. 多轮抽奖,每轮抽奖有3个环节:展⽰奖品信息(奖品图、份数),⼈名闪动,停⽌闪动确定中 奖名单

                        i. 当前⻚展⽰奖品信息,点击‘开始抽奖’按钮,则跳转⾄⼈名闪动画⾯

                        ii. ⼈员闪动画⾯,点击’点我确定‘按钮,确认中奖名单。

                        iii. 当前⻚展⽰中奖名单,点击‘已抽完,下⼀步’按钮,若还有奖品未抽取,则展⽰下⼀个奖品 信息,否则展⽰全部中奖名单

                        iv. 点击’查看上⼀奖项‘按钮,展⽰上⼀个奖品信息

                e. 对于抽奖过程中的异常情况,如抽奖过程中刷新⻚⾯,要保证抽取成功的奖项不能重新抽取。

                        i. 刷新⻚⾯后,若当前奖品已抽完,点击"开始抽奖",则直接展⽰当前奖品中奖名单 f. 如该抽奖活动已完成:

                        ii. 展⽰所有奖项的全部中奖名单

                        iii. 新增"分享结果"按钮,点击可复制当前⻚链接,打开后隐藏其他按钮,只展⽰活动名称与中奖 结果,保留"分享结果"按钮

        6. 通知部分:抽奖完成需以邮件和短信⽅式通知中奖者。

                a. “Hi,xxx。恭喜你在xxx抽奖中获得⼀等奖:⼿机。中奖时间为:xx:xx。请尽快领取您的奖 品。”

        7. 管理端涉及的所有⻚⾯,包括抽奖⻚,需强制管理员登录后⽅可访问。

1.3 系统架构

前端:使⽤JavaScript管理各界⾯的动态性,使⽤AJAX技术从后端API获取数据。  

后端:采⽤SpringBoot3构建后端应⽤,实现业务逻辑。

数据库:使⽤MySQL作为主数据库,存储⽤⼾数据和活动信息。

缓存:使⽤Redis作为缓存层,减少数据库访问次数。

消息队列:使⽤RabbitMQ处理异步任务,如处理抽奖⾏为。

⽇志与安全:使⽤JWT进⾏⽤⼾认证,使⽤SLF4J+logback完成⽇志。 

1.4 项⽬环境

         编程语⾔:Java(后端),JavaScript(前端)。

        开发⼯具包:JDK17 (⻅《JDK17安装》⽂档)

        后端框架:SpringBoot3。

         数据库:MySQL。

        缓存:Redis。

         消息队列:RabbitMQ。

         ⽇志:logback。

         安全:JWT+加密。 

1.5 业务功能模块

⼈员业务模块:管理员注册、登录,及普通⽤⼾的创建。

活动业务模块:活动管理及活动状态管理。

 奖品业务模块:奖品管理与奖品的分配。还包括奖品图的上传。

 通知业务模块:发送短信、邮件等业务,例如验证码发送,中奖通知。

 抽奖业务模块:完成抽奖动作,以及抽奖后的结果展⽰;

1.6 数据库设计

 ⽤⼾表:存储⽤⼾信息,如⽤⼾名、密码、邮箱等。

活动表:存储活动信息,如活动名称、描述、活动状态等。

奖品表:存储奖品信息,如奖品名称、奖品图等。

活动奖品关联表:存储⼀个活动下关联了哪些奖品。

活动⽤⼾关联表:存储⼀个活动下设置的参与⼈员。

中奖记录表:存储⼀个活动的中奖名单,如活动id,奖品id,中奖者id等 

2.  数据库设计

2.1 表的关联图

sql代码略;

 2.2 使⽤source命令导⼊.sql⽂件

首先使用指令进入mysql:

mysql -u root -p

source指令一个sql脚本文件;

注意:使用的sql文件的绝对路径且在路径中不能有中文;

mysql> source E:\java_xiangmu\lottery_system\lottery_system.sql

结果如下:指令执行成功;

3. 新建项目

3.1 选择相应的框架

pom文件配置略:


3.2 代码结构设计

分层结构如下:

        上层依赖下层:

controller层(web层):接收来自前端的请求;

service层:里面含有具体的业务逻辑,是业务逻辑服务层;

数据持久层(dao层):数据访问层,与mysql进行数据交互;

common:通用处理层,只要存放util工具,config配置;

 

4. 功能模块设计

4.1 错误码

        我们对dao层不进行定义错误码(1、许多sql错误定义不出来;2、可以在service层包含一些dao层的错误),对controller层和servoce层进行定义错误码(分别对人员模块,活动模块,奖品模块,抽奖模块进行分别定义)

        由于errcode错误码是遍布两个层级,所以放在common通用层里面; errorcode类主要包含两个信息,一个是错误状态码code,另外一个是错误状态的信息msg;其次对于不同的层级来进行定义错误码;

        对于抓不到,没有思考到的错误,使用全局通用错误码来定义;

public interface GlobalErrorCodeConstants {
    ErrorCode SUCCESS = new ErrorCode(200,"成功!");
    ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500,"系统异常!");
    ErrorCode UNKNOW = new ErrorCode(999,"未知错误!");
}

4.2 ⾃义异常类

        对当前的系统进行设计一套自定义异常类;

        根据控制层和服务层来分别写相应的代码,

        同时在构建异常时引入错误码,比如controller的异常构造,引入controller层的errorcode来进一步完善该层的异常信息;

@Data
public class ServiceException extends RuntimeException{
    private Integer code;//异常码
    private String message;//异常信息

    //添加无参构造主要是为了进行序列化
    public ServiceException(){

    }

    public ServiceException(Integer code,String message){
        this.code = code;
        this.message = message;
    }

    public ServiceException(ErrorCode errorCode) {
        this.code = errorCode.getCode();
        this.message = errorCode.getMsg();
    }
}

        @EqualsAndHashCode(callSuper = true)注解: 

        @data注解会生成自己的@equals和hashcode方法,我们想要使用父类的属性和方法,就要使用上面的注解;

4.3. CommonResult<T>

        对所有接口返回的结果进行封装的类型;

        CommonResult<T>作为控制器层⽅法的返回类型,封装HTTP接⼝调⽤的结果,包括成功数据、错 误信息和状态码。它可以被SpringBoot框架等⾃动转换为JSON或其他格式的响应体,发送给客⼾端。

关于为什么要封装?

1. 统⼀的返回格式:确保客⼾端收到的响应具有⼀致的结构,⽆论处理的是哪个业务逻辑。 2. 错误码和消息:提供错误码(code)和错误消息(msg),帮助客⼾端快速识别和处理错误。

3. 泛型数据返回:使⽤泛型允许返回任何类型的数据,增加了返回对象的灵活性。

4. 静态⽅法:提供了error()和success()静态⽅法,⽅便快速创建错误或成功的响应对象。

5. 错误码常量集成:通过ErrorCode和GlobalErrorCodeConstants使⽤预定义的错误码,保持错 误码的⼀致性和可维护性。

6. 序列化:实现了Serializable接⼝,使得CommonResult对象可以被序列化为多种格式,如 JSON或XML,⽅便⽹络传输。

7. 业务逻辑解耦:将业务逻辑与API的响应格式分离,使得后端开发者可以专注于业务逻辑的实 现,⽽不必关⼼如何构建HTTP响应。

8. 客⼾端友好:客⼾端开发者可以通过统⼀的接⼝获取数据和错误信息,⽆需针对每个API编写特 定的错误处理逻辑。

public class CommonResult<T> implements Serializable {
//    CommonResult<T>是在http中进行传输,所以要进行序列化
    private Integer code;//返回的错误码
    private T data;//返回的数据
    private String msg;//对错误码的描述
    
    //返回成功结果的CommonResult<T>静态构造
    public static <T> CommonResult<T> success(T data){
        CommonResult<T> result = new CommonResult<>();
        result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
        result.data = data;
        result.msg = "";
        return result;
    }

    //返回异常结果的CommonResult<T>静态构造
    public static <T> CommonResult<T> error(Integer code,String msg){
        Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code),"code不是错误的异常");
        CommonResult<T> result = new CommonResult<>();
        result.code = code;
        result.msg = msg;
        return result;
    }

    public static <T> CommonResult<T> error(ErrorCode errorCode){
        return error(errorCode.getCode(),errorCode.getMsg());
    }
}

 4.4 jackson工具实现序列化

        方便将对象转化为string类,本项目中适用于日志打印个redis,rabbitmq的使用场景中;

        相对而言protobuf(pb实现序列化非常快,但是可视化非常差);

public class JacksonUtil {
    private JacksonUtil(){

    }

    private final static ObjectMapper OBJECT_MAPPER;//1点对objectmapper进行私有化单例操作

    static {
        OBJECT_MAPPER = new ObjectMapper();
    }

    private static ObjectMapper getObjectMapper(){
        return  OBJECT_MAPPER;
    }

    //2点。使用callable支持使用lamda表达式来进行处理,无论序列化的操作过程中出现的
    //异常都可以tryparse进行捕捉
    private static <T > T tryParse(Callable<T> parser) {
        return tryParse(parser, JacksonException.class);
    }

    private static <T > T tryParse(Callable<T> parser, Class<? extends Exception> check){
        try {
            return parser.call();
            } catch (Exception ex) {
                if (check.isAssignableFrom(ex.getClass())) {
                    throw new JsonParseException(ex);
                }
            throw new IllegalStateException(ex);
        }
    }

    /**
     *序列化objectmapper
     * @param value
     * @return
     */
    public static String writeValueAsString(Object value) {
        return JacksonUtil.tryParse(()->
                JacksonUtil.getObjectMapper().writeValueAsString(value));
    }

    /**
     反序列化objectmapper
     * @param content
     * @param valueType
     * @return
     * @param <T>
     */
    public static <T > T readValue(String content, Class<T> valueType) {
        return JacksonUtil.tryParse(()->
                JacksonUtil.getObjectMapper().readValue(content, valueType));
    }
    /**
     反序列化list
     * @param content
     * @param parameterClasses
     * @return
     * @param <T>
     */
    public static <T > T readListValue(String content, Class<?> parameterClasses) {
        JavaType javaType =  JacksonUtil.getObjectMapper().getTypeFactory().constructParametricType(List.class, parameterClasses);
        return JacksonUtil.tryParse(()->
        { return JacksonUtil.getObjectMapper().readValue(content,javaType);
        });
    }
}

在这里特别注意两点:jackson里面的部分方法是对springboot框架进行学习处理的

1、 对objectmapper进行私有单例化处理;

2、使用tryparse的方法,其第一个参数是callable<T> parse,支持使用lambda表达式进行处理,无论在lambda表达式哪里出现了问题,都可以在tryparse里面使用try-catch对异常进行处理;

4.5. ⽇志处理

        使用SLF4J+logback的配置:

        在本地(dev),通过控制台来打印出日志;

        服务器:日志主要存在目标目录文件;

application.properties配置:

spring.application.name=lottery_system
## logback xml ##
logging.config=classpath:logback-spring.xml
spring.profiles.active=dev

        新增logback-spring.xml文件:对于本地和服务器的日志进行配置:

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="60 seconds" debug="false">
    <springProfile name="dev">
        <!--输出到控制台-->
        <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n%ex</pattern>
            </encoder>
        </appender>
        <root level="info">
            <appender-ref ref="console" />
        </root>
    </springProfile>

    <springProfile name="prod,test">
        <!--ERROR级别的日志放在logErrorDir目录下,INFO级别的日志放在logInfoDir目录下-->
        <property name="logback.logErrorDir" value="/root/lottery-system/logs/error"/>
        <property name="logback.logInfoDir" value="/root/lottery-system/logs/info"/>
        <property name="logback.appName" value="lotterySystem"/>
        <contextName>${logback.appName}</contextName>

        <!--ERROR级别的日志配置如下-->
        <appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则
                如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天
                的日志改名为今天的日期。即,<File> 的日志都是当天的。
            -->
            <File>${logback.logErrorDir}/error.log</File>
            <!-- 日志level过滤器,保证error.***.log中只记录ERROR级别的日志-->
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            <!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
                <FileNamePattern>${logback.logErrorDir}/error.%d{yyyy-MM-dd}.log</FileNamePattern>
                <!--只保留最近30天的日志-->
                <maxHistory>30</maxHistory>
                <!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
                <!--<totalSizeCap>1GB</totalSizeCap>-->
            </rollingPolicy>
            <!--日志输出编码格式化-->
            <encoder>
                <charset>UTF-8</charset>
                <pattern>%d [%thread] %-5level %logger{36} %line - %msg%n%ex</pattern>
            </encoder>
        </appender>

        <!--INFO级别的日志配置如下-->
        <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则
                如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天
                的日志改名为今天的日期。即,<File> 的日志都是当天的。
            -->
            <File>${logback.logInfoDir}/info.log</File>
            <!--自定义过滤器,保证info.***.log中只打印INFO级别的日志, 填写全限定路径-->
            <filter class="com.example.lotterysystem.common.filter.InfoLevelFilter"/>
            <!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
                <FileNamePattern>${logback.logInfoDir}/info.%d{yyyy-MM-dd}.log</FileNamePattern>
                <!--只保留最近14天的日志-->
                <maxHistory>14</maxHistory>
                <!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
                <!--<totalSizeCap>1GB</totalSizeCap>-->
            </rollingPolicy>
            <!--日志输出编码格式化-->
            <encoder>
                <charset>UTF-8</charset>
                <pattern>%d [%thread] %-5level %logger{36} %line - %msg%n%ex</pattern>
            </encoder>
        </appender>
        <root level="info">
            <appender-ref ref="fileErrorLog" />
            <appender-ref ref="fileInfoLog"/>
        </root>
    </springProfile>
</configuration>

在配置的包中自定义过滤info级别日志的类:

public class InfoLevelFilter extends Filter<ILoggingEvent> {
    //过滤器来过滤info级别的日志,将info的日志进行接受;
    @Override
    public FilterReply decide(ILoggingEvent iLoggingEvent) {
        if (iLoggingEvent.getLevel().toInt() == Level.INFO.toInt()){
            return FilterReply.ACCEPT;
        }
        return FilterReply.DENY;
    }
}

使用测试类来进行日志打印测试:

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

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

相关文章

【5G】Spectrum 频谱

频谱是移动运营商的关键资产&#xff0c;可用的频谱是定义移动网络容量和覆盖范围的重要因素。本章讨论了5G的不同频谱选项、它们的特性以及5G早期部署阶段的预期频谱。5G是首个旨在利用大约400 MHz到90 GHz之间所有频段的移动无线系统。5G还设计用于在许可、共享和非许可频谱带…

复现论文:PromptTA: Prompt-driven Text Adapter for Source-freeDomain Generalization

github&#xff1a;zhanghr2001/PromptTA: Source-free Domain Generalization 论文&#xff1a;[2409.14163] PromptTA: Prompt-driven Text Adapter for Source-free Domain Generalization 自己标注&#xff1a;PromptTA: Prompt-driven Text Adapter for Source-free Domai…

电子应用设计方案-43:智能手机充电器系统方案设计

智能手机充电器系统方案设计 一、引言 随着智能手机的广泛应用&#xff0c;对充电器的性能、效率和安全性提出了更高的要求。本方案旨在设计一款高效、安全、兼容多种快充协议的智能手机充电器。 二、系统概述 1. 系统目标 - 提供快速、稳定、安全的充电功能。 - 兼容主流的智…

基于springboot+vue实现的项目评审系统 (源码+L文+ppt)4-116

摘 要 相比于以前的传统手工管理方式&#xff0c;智能化的管理方式可以大幅降低运营人员成本&#xff0c;实现了项目评审系统的标准化、制度化、程序化的管理&#xff0c;有效地防止了项目评审的随意管理&#xff0c;提高了信息的处理速度和精确度&#xff0c;能够及时、准确…

深入了解架构中常见的4种缓存模式及其实现

4种缓存模式 随着应用程序的复杂性日益增加&#xff0c;缓存管理变得至关重要。缓存不仅能有效减轻数据库负载&#xff0c;还能显著提升数据访问速度。选择合适的缓存模式能够在不同的业务场景下发挥出最佳效果。 本文将详细介绍四种常见的缓存模式&#xff1a;Cache-Aside (…

【论文阅读】处理器芯片敏捷设计方法:问题与挑战

作者&#xff1a;包云岗老师 包云岗老师是计算机体系结构方向的大牛&#xff0c;推动了体系结构方面的开源事业! 欢迎对本栏目感兴趣的人学习"一生一芯"~ 学习体会&#xff1a; 已有的软硬件生态系统和开发成本制约了对新结构的探索。但目前仍在几种路线上做尝试~ 1…

Android记单词app(包含数据库)

一、功能与要求 实现功能:设计与开发记单词系统的,系统功能包括用户登录、用户注册、单词操作(单词的添加、查询、修改及删除)以及忘记密码等。 指标要求:通过用户登录、用户注册、单词操作、忘记密等功能的设计与开发,掌握Android常用布局、控件的使用、监听器的设置以及…

数据结构与算法学习笔记----树与图的深度优先遍历

数据结构与算法学习笔记----树与图的深度优先遍历 author: 明月清了个风 first publish time: 2024.12.9 pa⭐️这里只有一道题哈哈。 Acwing 846.树的重心 给定一棵树&#xff0c;树中包含 n n n个节点&#xff08;编号 1 ∼ n 1 \sim n 1∼n&#xff09;和 n − 1 n - 1 n…

TSWIKI知识库软件

TSWIKI 知识库软件介绍 推荐一个适合本地化部署、自托管的知识库软件 TSWIKI介绍 tswiki 是一个适合小团队、个人的知识库、资料管理的软件&#xff0c;所有数据均本地化存储。可以本地化、私有云部署&#xff0c;安装简单。在线预览。 主要功能说明 1、简化的软件依赖和安…

mid360使用cartorapher进行3d建图导航

1. 添加urdf配置文件&#xff1a; 添加IMU配置关节点和laser关节点 <!-- imu livox --> <joint name"livox_frame_joint" type"fixed"> <parent link"base_link" /> <child link"livox_frame" /> <o…

第四十六篇 Vision Transformer论文翻译

论文连接:https://arxiv.org/abs/2010.11929 GitHub:https://github.com/google-research/vision_transformer 摘要 虽然Transformer架构已成为自然语言处理任务的实际标准,但其在计算机视觉中的应用仍然有限。在计算机视觉中,注意力机制要么与卷积网络结合使用,要么在保…

【VUE2】纯前端播放海康视频录像回放,视频格式为rtsp格式,插件使用海康视频插件

一、需求 1、后端从海康平台拉流视频回放数据&#xff0c;前端进行页面渲染播放&#xff0c;视频格式为rtsp eg&#xff1a; 基本格式&#xff1a;rtsp://<username>:<password><ip_addr>:<port>/<path>参数说明&#xff1a; username&#xff…

STL库中list的使用与迭代器的实现

STL库中list的使用与迭代器的实现 1.使用list中的部分函数assignspliceremoveuniquemeger 2.list的部分功能实现&#xff08;重点&#xff09;框架迭代器的实现 1.使用list中的部分函数 assign 功能一&#xff1a;当前链表的节点全部销毁&#xff0c;替换成迭代区间的值 功能二…

2024年华中杯数学建模C题基于光纤传感器的平面曲线重建算法建模解题全过程文档及程序

2024年华中杯数学建模 C题 基于光纤传感器的平面曲线重建算法建模 原题再现 光纤传感技术是伴随着光纤及光通信技术发展起来的一种新型传感器技术。它是以光波为传感信号、光纤为传输载体来感知外界环境中的信号&#xff0c;其基本原理是当外界环境参数发生变化时&#xff0c…

ETCD的封装和测试

etcd是存储键值数据的服务器 客户端通过长连接watch实时更新数据 场景&#xff1a; 当主机A给服务器存储 name&#xff1a; 小王 主机B从服务器中查name ,得到name-小王 当主机A更改name 小李 服务器实时通知主机B name 已经被更改成小李了。 应用&#xff1a;服务注册与发…

Cesium 问题: 添加billboard后移动或缩放地球,标记点位置会左右偏移

文章目录 问题分析原先的:添加属性——解决漂移移动问题产生新的问题:所选的经纬度坐标和应放置的位置有偏差解决坐标位置偏差的问题完整代码问题 添加 billboard 后, 分析 原先的: // 图标加载 function addStation ({lon, lat, el, testName

进入 Dystopia:第九周游戏指南

本指南将为大家详细说明在第八周的每个体验中可以获得的奖励。 在杂草丛生的反乌托邦废墟中生存&#xff0c;随着大自然重新开垦这片土地&#xff0c;文明已陷入绝望。穿越高耸入云、摇摇欲坠的摩天大楼&#xff0c;抵御末世社会的各种危险。适应这个文明与荒野之间的界限已经消…

leetcode 面试经典 150 题:验证回文串

链接验证回文串题序号125类型字符串解题方法双指针法难度简单 题目 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&#xf…

Android 屏幕采集并编码为H.264

前言 我们前面基于摄像机的图像采集以及编解码已经完成了&#xff0c;那么接下来计划后面的三篇博文分别实现Android屏幕采集实现并进行H.264编解码、MIC音频采集并编码为AAC以及AAC解码播放&#xff0c;希冀可以通过这六篇博文能够对Android上面的音视频编解码有一个初步的学…

深入探索 Compose 渲染流程:从 UI 树到 Skia 绘制的实现解析

文章目录 前言Compose 渲染流程概述1. Compose 解析1.1 Compose 声明性 UI1.2 Compose 编译1.2.1 Compose 编译概述1.2.2 代码示例1.2.3 编译过程细节 1.3 组合与重组合1.3.1 组合&#xff08;Composition&#xff09;1.3.2 重组合1.3.3 组合与重组合的区别1.3.4 组合与重组合的…