SpringBoot+ShardingSphere-分库分表教程(一)

日常使用数据库的时候,更多的时间是在关心业务功能的实现,为了尽快完成新版本的发布上线,通常在项目初期不太会去在意数据库的压力和性能问题。在服务上线一段时间之后,就会发现当初设计存在着很多的不足,这都是项目研发的正常过程。对于有经验的程序员,在项目设计初期就会想到将来有一天会遇到这些问题,所以就从一开始就将代码写的比较完善,这也是提现大龄程序员优势的地方。

有一种常见的现象就是,项目上线初期,由于用户量不多,所以数据库中的数据也不会太多,服务运行的非常顺畅,但是随着生产数据的积累,很快就导致了数据库性能瓶颈的到来。这时候我们最先想到的是提升数据库的资源,加大内存,加大磁盘,从而度过数据库压力的难关,但是这毕竟是暂时的,比如一张表里的数据量迅速的增长,而且你又不能删除这里面的数据,终究有一天会让这张表爆掉。

以mysql为例,如果单张表的行数超过500万行的时候,通常就能感受到非常明显的性能衰减,这点不得不佩服oracle动辄几亿的单表查询能力,但是没办法,两者价格的差距也是性能的差距。如何应对这种持续增长的单表数据呢?一种常用的方式就是分库分表,就是把一张巨大的表,按照一定的规则分到不同的表里去,这样每张分表的数据量就小了,从而保证每个分表的性能,如果分表也不足以支撑大数据量,就通过分库,把数据量分到多个库里去,从而支撑住业务功能。

shardingsphere是诸多分库分表工具中比较优秀的一款,在我经历过的公司中,也应用在了生产服务中,虽然使用过程中遇到的坑也不少,不过总体来说,还是足够支撑业务功能。我们首先介绍一下,分库和分表是两个截然不同的功能,虽然总混在一起说,分表只要我们在Springboot中引入shardingsphere-jdbc这个依赖库即可,但是分库就要单独部署一个服务shardingsphere-proxy,其他服务连接shardingsphere-proxy,从而实现分库的功能。

我们先用shardingsphere-jdbc来进行单库的分表,分表常用的规则有两种,一种是通过时间进行分表,比如一个月一张表,或者一周一张表,另外一种就根据列的数值进行分表,比如id是1-1000用一张表,1001-2000用一张表,分表的规则要按照业务功能去切分,无论哪种分表策略,最终的目标就是让数据均匀的分布在各个分表中。

1、创建数据表

我们先创建一张存储消息的表,过去我们创建消息表就是一张,比如叫sys_message,但是现在我们是用分表,所以就要创建一批表,我们设定消息表使用时间分表策略,每7天一张表,从2025年1月1日开始,所以我们就要创建一张分表sys_message_20250101,然后按照每7天一张表创建出多干个消息分表,这里注意,shardingsphere的分表默认是不自动创建表的,所以我们先手动创建,我制作了一个存储过程可以快速创建出多张sys_message分表。

sys_message_20250101这一张是分表的基础表,没有启动会报错,其他的分表即使没有,启动的时候也不报错,但是用到了就会抛出异常。

CREATE TABLE `sys_message_20250101` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',`msg` longblob COMMENT '消息内容',`version` int NOT NULL DEFAULT '1' COMMENT '版本号',`is_logic_delete` int NOT NULL DEFAULT '0' COMMENT '逻辑删除',`create_by` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`update_by` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '修改人',`update_time` datetime DEFAULT NULL COMMENT '修改时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1989 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='系统-消息表';

自动创建从2025年1月1日起到一年后的分表存储过程:

CREATE PROCEDURE `sp_generate_message_tables`(IN start_date DATE)
BEGINDECLARE end_date DATE DEFAULT DATE_ADD(CURRENT_DATE(), INTERVAL 1 YEAR);DECLARE item_date DATE;DECLARE table_name VARCHAR(50);SET item_date = DATE_ADD(start_date, INTERVAL 7 DAY);WHILE item_date <= end_date DOSET table_name = CONCAT('sys_message_', DATE_FORMAT(item_date, '%Y%m%d'));SET @sql = CONCAT('CREATE TABLE IF NOT EXISTS ', table_name, ' LIKE sys_message_', DATE_FORMAT(start_date, '%Y%m%d'));PREPARE stmt FROM @sql;EXECUTE stmt;DEALLOCATE PREPARE stmt;SET item_date = DATE_ADD(item_date, INTERVAL 7 DAY);END WHILE;SELECT CONCAT('分表生成完成,时间范围:', start_date, ' 至 ', end_date) AS result;
END

2、创建项目shardingsphere-demo

创建一个新项目shardingsphere-demo,并且在pom.xml文件中引入MyBatis-Plus、Shardingsphere和mysql依赖。

<dependencies><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.10.1</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-jsqlparser</artifactId><version>3.5.10.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.4.0</version></dependency><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>shardingsphere-jdbc</artifactId><version>5.5.2</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.24</version></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.54</version></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.4.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>RELEASE</version><scope>compile</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

3、创建MyBatis-Plus的各个类

虽然各个分表的表名是不一样的,但是在代码里我们并不用去记录这些表名,而是使用逻辑表名sys_message进行操作,让Shardingsphere去自动帮我们定位真正的分表。

MessageDO:

package com.mj.shardingsphere.entity;import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;
import java.time.LocalDateTime;/*** 系统-消息表*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "sys_message")
public class MessageDO implements Serializable {/*** id*/@TableId(value = "id", type = IdType.ASSIGN_ID)private Long id;/*** 消息value*/@TableField(value = "msg")private String msg;/*** 版本号*/@Version@TableField(value = "version")private Integer version;/*** 逻辑删除*/@TableLogic@TableField(value = "is_logic_delete")private Integer logicDelete;/*** 创建人*/@TableField(value = "create_by", fill = FieldFill.INSERT)private String createBy;/*** 创建时间*/@TableField(value = "create_time", fill = FieldFill.INSERT)private LocalDateTime createTime;/*** 修改人*/@TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)private String updateBy;/*** 修改时间*/@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)private LocalDateTime updateTime;private static final long serialVersionUID = 1L;
}

MessageMapper:

package com.mj.shardingsphere.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mj.shardingsphere.entity.MessageDO;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface MessageMapper extends BaseMapper<MessageDO> {
}

MessageMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mj.shardingsphere.dao.MessageMapper"><resultMap id="BaseResultMap" type="com.mj.shardingsphere.entity.MessageDO"><!--@mbg.generated--><!--@Table sys_message--><id column="id" jdbcType="BIGINT" property="id" /><result column="msg" jdbcType="VARCHAR" property="msg" /><result column="version" jdbcType="INTEGER" property="version" /><result column="is_logic_delete" jdbcType="INTEGER" property="logicDelete" /><result column="create_by" jdbcType="VARCHAR" property="createBy" /><result column="create_time" jdbcType="TIMESTAMP" property="createTime" /><result column="update_by" jdbcType="VARCHAR" property="updateBy" /><result column="update_time" jdbcType="TIMESTAMP" property="updateTime" /></resultMap><sql id="Base_Column_List"><!--@mbg.generated-->id, msg, version, is_logic_delete, create_by, create_time, update_by, update_time</sql>
</mapper>

4、Shardingsphere配置:

创建一个文件sharding.yml,上半部分就是数据库的配置,将数据源和连接池交给了Sharding进行管理,Springboot里面就不用再配置了。sys_message_algorithm决定了分表的策略,按照时间分表的时候,要定好分表的时间段,可以写一个很长的时间。sharding-suffix-pattern是分表的后缀格式,正是因为有这个配置,Sharding才能很好的把所有的分表整合成了一个逻辑表让我们用分表的时候就像只有一张表一样。最后就是配置分表时间是7天,这时候数据库里的分表也要严格按照7的跨度去生成。

# 模式配置
mode:type: Standalonerepository:type: JDBC
# 数据源配置
dataSources:sharding:dataSourceClassName: com.alibaba.druid.pool.DruidDataSourcedriverClassName: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.18.42:3306/sharding?useSSL=false&useUnicode=true&characterEncoding=UTF-8username: rootpassword: rootdruid:test-on-borrow: truevalidation-query: SELECT 1 FROM DUALweb-stat-filter:enabled: truestat-view-servlet:enabled: truelogin-username: druidlogin-password: 12345pool-prepared-statements: falsemax-pool-prepared-statement-per-connection-size: 20
# 规则配置
rules:# 单表配置- !SINGLEtables:- sharding.*# 数据分片- !SHARDINGtables:sys_message:actualDataNodes: sharding.sys_message_${20250101..20991231}tableStrategy:standard: # 用于单分片键的标准分片场景shardingColumn: create_timeshardingAlgorithmName: sys_message_algorithmkeyGenerateStrategy: # 分布式序列策略column: idkeyGeneratorName: snowflakeauditStrategy: # 分片审计策略auditorNames: # 分片审计算法名称- sharding_key_required_auditorallowHintDisable: true# 分片算法配置shardingAlgorithms:sys_message_algorithm:type: INTERVALprops:datetime-pattern: yyyy-MM-dd HH:mm:ssdatetime-lower: "2025-01-01 00:00:00"  # 添加引号确保格式正确datetime-upper: "2099-12-31 23:59:59"  # 添加引号确保格式正确sharding-suffix-pattern: yyyyMMdddatetime-interval-amount: 7datetime-interval-unit: DAYS# 分布式序列算法配置keyGenerators:snowflake:type: SNOWFLAKE# 分片审计算法配置auditors:sharding_key_required_auditor:type: DML_SHARDING_CONDITIONSprops:sql-show: true
#  sql-simple: false
#  max-connections-size-per-query: 1
#  check-table-metadata-enabled: false

修改一下application.yml文件,把sharding.yml文件引入进去。

spring:application:name: shardingsphere-demodatasource:type: com.alibaba.druid.pool.DruidDataSource# 引入shardingspheredriver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriverurl: jdbc:shardingsphere:classpath:sharding.yml?placeholder-type=environmentinitialSize: 5minIdle: 5maxActive: 20maxWait: 60000timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMillis: 300000validationQuery: 'SELECT 1 FROM DUAL'testWhileIdle: truetestOnBorrow: falsetestOnReturn: falsepoolPreparedStatements: truemaxPoolPreparedStatementPerConnectionSize: 20filters: 'stat,wall'connectionProperties: 'druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000'useGlobalDataSourceStat: truemybatis-plus:configuration:map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:id-type: auto# ????logic-delete-field: deletedlogic-delete-value: 1logic-not-delete-value: 0mapper-locations: classpath:/mapper/**.xmlspringdoc:swagger-ui:path: /swagger-ui.htmltags-sorter: alphaoperations-sorter: alphaapi-docs:path: /v3/api-docsgroup-configs:- group: 'default'paths-to-match: '/**'packages-to-scan: com.mj.shardingsphere
knife4j:enable: trueproduction: falsesetting:language: zh_cn

5、编写服务类

MessageService:

package com.mj.shardingsphere.service;import com.mj.shardingsphere.entity.MessageDO;import java.util.List;public interface MessageService {String sendMessage(String message);List<MessageDO> getMessages();
}

MessageServiceImpl:

这里要注意查询的使用,由于shardingsphere是从分表里进行操作,所以查询的时候也是从所有的分表里进行查询,这是一件很恐怖的事情,所以一定要默认带着分片键并且固定一个区间,让它从有限的分表里进行查询,防止因为查询太多数据而导致服务宕机。这里可能会给业务功能带来一定的困扰,比如就是不知道查询多久数据,这时候必须要在业务功能上做让步。

package com.mj.shardingsphere.service.impl;import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.mj.shardingsphere.dao.MessageMapper;
import com.mj.shardingsphere.entity.MessageDO;
import com.mj.shardingsphere.service.MessageService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;import java.time.LocalDateTime;
import java.util.List;@AllArgsConstructor
@Service
public class MessageServiceImpl implements MessageService {private final MessageMapper messageMapper;@Overridepublic String sendMessage(String message) {MessageDO messageDO = new MessageDO();messageDO.setMsg(message);messageDO.setCreateBy("SYSTEM");messageDO.setCreateTime(LocalDateTime.now());messageDO.setUpdateBy("SYSTEM");messageDO.setUpdateTime(LocalDateTime.now());messageMapper.insert(messageDO);return "ok";}@Overridepublic List<MessageDO> getMessages() {//查询分表的时候,一定要使用分片键去固定分表的区间,防止查询太多的表return messageMapper.selectList(Wrappers.lambdaQuery(MessageDO.class).ge(MessageDO::getCreateTime, LocalDateTime.now().minusMonths(1)).le(MessageDO::getCreateTime, LocalDateTime.now()));}
}

6、测试接口

MessageController:

package com.mj.shardingsphere.controller;import com.mj.shardingsphere.entity.MessageDO;
import com.mj.shardingsphere.service.MessageService;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@AllArgsConstructor
@RequestMapping("/message")
@RestController
public class MessageController {private final MessageService messageService;@GetMapping("/add")public String add() {return messageService.sendMessage("message-" + System.currentTimeMillis());}@GetMapping("/list")public List<MessageDO> lst() {return messageService.getMessages();}
}

http://127.0.0.1:8080/message/add 通过调用add接口,通过日志和数据库,能发现进入到对应时间段内的那个分表了。

http://127.0.0.1:8080/message/list 查询接口查询了最近一个月的数据

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

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

相关文章

INA226 电流计 功率计电路图转PCB制作

上次发布了TI的INA226电路图&#xff0c;今天抽了点时间&#xff0c;把电路图生成了PCB。 帖出来&#xff0c;不足之处&#xff0c;请兄弟们留言指正。 没什么问题就可以去嘉立创白嫖了。^_^

Vcpkg 经典模式完整迁移方案

&#x1f680; 从零开始&#xff1a;高效使用 Vcpkg 安装 Qt WebEngine&#xff08;经典模式 缓存优化 性能释放&#xff09; &#x1f9e9; 背景简介 在使用 Vcpkg 安装 Qt 系列库时&#xff0c;特别是庞大的 qtwebengine 模块&#xff0c;编译量极大&#xff0c;耗时可达…

FPGA产品

FPGA产品 文章目录 FPGA产品1. Xilinx公司FPGA产品2. Altera公司FPGA产品3. FPGA产品的工业等级简介4. FPGA产品的速度等级简介总结 1. Xilinx公司FPGA产品 Xilinx公司是FPGA芯片的发明者&#xff0c;因此是一家骨灰级的老牌FPGA公司&#xff0c;同时也是目前最大的可编程逻辑…

205-06-26 Python深度学习1——安装Anaconda与PyTorch库(Win11+WSL2+Ubuntu24.04版)

文章目录 1 安装 wsl1.1 开启 Windows 支持1.2 安装 wsl1.3 移动 wsl 至其他盘1.4 其他事项 2 安装 Anaconda3 安装 Python 环境3.1 创建 Conda 环境3.2 安装 Pytorch 库&#xff08;gpu&#xff09; 4 安装 Pycharm4.1 Toolbox App 安装4.2 安装 Pycharm4.3 配置 Pycharm 5 测…

Redis 数据迁移同步:应对大 Key 同步挑战

在企业级的数据同步和迁移场景中&#xff0c;Redis 凭借高性能和灵活的数据结构&#xff0c;常被用于缓存和高频读写场景。随着业务数据的积累&#xff0c;Redis 中不可避免会出现包含大量元素的“大 Key”&#xff0c;如包含几十万条数据的 List、Set 或 Hash 类型。在进行全量…

视频关键帧提取

&#x1f39e;️ 视频关键帧提取与特征分析指南 &#x1f4cc; 抽帧数量建议 视频时长推荐抽帧数原因短视频&#xff08;≤15秒&#xff09;3&#xff5e;5 帧覆盖不同场景即可中长视频&#xff08;1&#xff5e;3分钟&#xff09;5&#xff5e;10 帧内容跨度大长视频&#xf…

协作机器人优化自动化工作流程,提升工作效率

无损检测(NDT)是一种检查方法&#xff0c;用于识别材料中的裂纹或缺陷&#xff0c;或者在不损坏材料的情况下确定材料的元素组成。Olympus拥有多种NDT设备&#xff0c;这些设备具有多种多样的测量功能&#xff0c;允许最终用户对各种行业中使用的金属、塑料、陶瓷和复合材料进行…

复用对象Aspose.Words 中 DocumentBuilder 的状态管理解析

doc manager.LoadDocument(filePath) builder.Document doc 是不是builder就自动清空重建了,不需要清理builder Aspose.Words 中 DocumentBuilder 的状态管理解析 在您的代码中&#xff0c;builder.Document doc 这行代码不会自动清空或重建DocumentBuilder的状态。Docume…

(LeetCode 面试经典 150 题 ) 134. 加油站 (贪心)

题目&#xff1a;134. 加油站 思路&#xff1a;贪心&#xff0c;时间复杂度0(n)。 当前点i来到下一个点i1,那么油的变化量是gas[i]-cost[i]。 先统计遍历完所有点后&#xff0c;油的变化量sum。如果sum<0&#xff0c;说明不可能绕行一周&#xff1b;sum>0&#xff0c;说…

Java 线程池总结

一、写在前面 参考阿里开发规约,创建线程池一般用ThreadPoolExecutor 在高并发程序中&#xff0c;频繁创建与销毁线程是一种极其低效且不可控的行为。为了解决这个问题&#xff0c;Java 提供了线程池&#xff08;ThreadPoolExecutor&#xff09;这一强大的并发框架。它不仅提…

【3.3】Pod详解——容器探针部署第一个pod

文章目录 容器探针小知识-控制平面Pod实战声明式模型&命令模式 部署第一个pod编写pod清单文件kubectl命令将清单文件post到api-server验证pod删除pod 容器探针 上面已经讲到容器状态,那么这些容器的状态是怎么检测到的呢?实际上在pod中有三种探针&#xff0c;存活探针(li…

Insar 相位展开真实的数据集的生成与下载(随机矩阵放大,zernike 仿真包裹相位)

1.真实的数据集下载: Delta-X: UAVSAR L1B Interferometric Products, MRD, Louisiana, 2021 | NASA Earthdata 注意下载的时候需要注册登录一下哦 2. 适用于 深度学习训练的数据集 通过网盘分享的文件:InSAR-DLPU.rar 链接: https://pan.baidu.com/s/1CRWAuNYwCHP_iqCeIhf…

C++ 多线程深度解析:掌握并行编程的艺术与实践

在现代软件开发中&#xff0c;多线程&#xff08;multithreading&#xff09;已不再是可选项&#xff0c;而是提升应用程序性能、响应速度和资源利用率的核心技术。随着多核处理器的普及&#xff0c;如何让代码有效地利用这些硬件资源&#xff0c;成为每个 C 开发者必须掌握的技…

(线性代数)矩阵的奇异值Singular Value

矩阵的奇异值是矩阵分析中一个非常重要的概念&#xff0c;尤其是在数值线性代数、数据降维&#xff08;如PCA&#xff09;、图像处理等领域有着广泛应用。奇异值分解&#xff08;SVD, Singular Value Decomposition&#xff09;是一种强大的工具&#xff0c;可以将任意形状的矩…

数据结构复习4

第四章 串 一些面试题 12. 介绍一下KMP算法。★★★ KMP算法是一种高效的字符串匹配算法&#xff0c;用于在一个文本串中查找一个模式串的出现位置。KMP算法通过利用模式串自身的信息&#xff0c;在匹配过程中避免不必要的回溯&#xff0c;从而提高匹配效率。 KMP算法的核心思…

【八股消消乐】消息队列优化—消息有序

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本专栏《八股消消乐》旨在记录个人所背的八股文&#xff0c;包括Java/Go开发、Vue开发、系统架构、大模型开发、具身智能、机器学习、深度学习、力扣算法等相关知识点&#xff…

2D写实交互数字人如何重塑服务体验?

在数字化浪潮席卷全球的当下&#xff0c;人机交互模式正经历着前所未有的变革。从早期的文本命令行界面&#xff0c;到图形用户界面&#xff08;GUI&#xff09;的普及&#xff0c;再到如今语音交互、手势识别等多模态交互技术的兴起&#xff0c;我们与机器之间的沟通方式愈发自…

CI/CD GitHub Actions配置流程

腾讯云服务器宝塔FinalShellgithup 1.在云服务器上创建SSH秘钥对&#xff0c;下载秘钥到本地 2.在服务器中绑定秘钥对&#xff08;绑定后&#xff0c;服务器不能将不允许密码登录&#xff09;绑定前先关机服务器&#xff0c;绑定后再开启服务器 3.FinalShell改为公钥登录&am…

液态交互效果网页开发--源自鸿蒙5以及iOS26的灵感

首先先来看看最终展示效果 当鼠标靠近“开始探索”的按钮的时候&#xff0c;按钮放大并有微弱光效 鼠标靠近之前会给视窗添加一层接近背景的朦胧感&#xff0c;当鼠标放在视窗上朦胧感消失 技术不复杂&#xff0c;这个网页主要是使用了以下关键技术&#xff1a; HTML5 语义化标…

PYTHON从入门到实践9-类和实例

# 【1】面向对象编程 class Student(object):# 可以帮属性值绑定到对象上&#xff0c;self相当于JAVA的thisdef __init__(self, name, age):self.name nameself.age agedef speak(self):print(self.name, 说&#xff1a;老师好)if __name__ __main__:new_student1 Student(…