025-从零搭建微服务-文件服务(一)

news2024/12/24 9:57:49

写在最前

如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。

源码地址(后端):https://gitee.com/csps/mingyue

源码地址(前端):https://gitee.com/csps/mingyue-ui

文档地址:https://gitee.com/csps/mingyue/wikis

对象存储服务

对象存储服务(Object Storage Service,简称 OSS)是一种云计算服务,用于存储和管理大规模数据、多媒体文件、备份和归档数据等。它采用了对象存储的方式,将数据以对象的形式存储在云端,并为用户提供了可靠、高可用、高扩展性、低成本的存储解决方案。

以下是对象存储服务的一些关键特点和功能:

  1. 无限扩展性: 对象存储服务具有高度的扩展性,用户可以根据需要动态扩展存储容量,无需关心硬件限制。
  2. 高可用性: 数据在多个地理位置和设备上复制存储,以确保数据的高可用性。如果某个存储节点发生故障,系统会自动切换到备用节点,以保证数据不丢失。
  3. 数据安全: 对象存储服务提供多层次的数据安全措施,包括数据加密、访问控制、身份验证、审计日志等,以确保数据的安全性和隐私性。
  4. 低成本: 对象存储服务采用了灵活的计费模型,用户只需支付实际使用的存储容量和数据传输流量,没有预付费要求。
  5. 多种数据类型支持: 用户可以在对象存储服务上存储各种类型的数据,包括文本、图像、音频、视频、备份文件等。
  6. 数据管理: 提供了丰富的数据管理功能,如数据迁移、数据备份、数据归档、数据版本控制等,使用户能够有效管理存储的数据。
  7. 云端计算集成: 可与云计算服务(如云服务器 ECS、云函数 Function Compute 等)无缝集成,以支持构建更强大的云端应用和解决方案。

对象存储服务通常用于各种云计算场景,包括网站托管、数据备份和恢复、多媒体存储和分发、大数据分析等。它提供了可靠的数据存储和管理解决方案,帮助用户降低存储成本、提高数据可用性,并支持灵活的数据访问和操作。

国内常用的 OSS 服务

  1. 阿里云 OSS(Object Storage Service): 阿里云的 OSS 是中国领先的对象存储服务,提供高可用性、高可靠性、高扩展性的存储解决方案。它支持多种数据类型的存储,具有数据安全和数据管理功能。OSS 也支持多区域部署,以确保数据的高可用性。
  2. 腾讯云 COS(Cloud Object Storage): 腾讯云的 COS 是一个可扩展的对象存储服务,具有高度的可用性和安全性。它支持多媒体文件存储、备份和归档数据,还可以与其他腾讯云服务无缝集成,用于构建各种应用。
  3. 华为云 OBS(Object Storage Service): 华为云的 OBS 是一种高性能、高可用性的对象存储服务,适用于大规模数据存储和管理。它支持数据多副本存储、数据加密、数据版本控制等功能,以确保数据的安全性和可用性。

开源的 OSS 服务

  1. Ceph: Ceph 是一个开源的分布式存储系统,它提供了对象存储、块存储和文件存储的功能。Ceph 的对象存储部分被称为 RADOS(可扩展可自修复的对象存储),它具有高度的可扩展性和可用性。Ceph 可以在私有云环境中部署,也可以与公共云服务集成。
  2. Minio: Minio 是一个开源的对象存储服务器,专注于提供 S3 兼容的存储服务。它易于部署和管理,支持多种操作系统和云平台。Minio 可以用于构建私有云对象存储解决方案或者作为开发和测试环境中的对象存储服务器。
  3. Swift: Swift 是由 OpenStack 社区开发的对象存储服务,特别适用于构建大规模分布式存储基础设施。它提供了 RESTful API,可用于存储和检索数据,支持多租户和数据冗余。
  4. OpenIO: OpenIO 是一个面向对象存储和数据管理的开源平台,具有高可用性和可扩展性。它支持多种存储介质,包括硬盘、固态硬盘和内存,并且可以自动优化数据存储和检索。
  5. Rook: Rook 是一个云原生存储编排框架,它可以用来管理和部署各种存储解决方案,包括 Ceph、Minio 和 NFS 等。Rook 可以轻松地将这些存储解决方案集成到 Kubernetes 集群中,以支持容器化应用程序的持久性存储需求。

愿景

本文件服务将兼容所有S3协议的云厂商均支持,测试集成厂商如下:Minio、阿里云 OSS(aliyun)、七牛Kodo(qiniu)、腾讯 COS(qcloud)

接下来先从 Minio 开始~

Minio

Minio 是一个开源的对象存储服务器,专注于提供 S3 兼容的存储服务。Minio 的主要用途包括构建私有云对象存储解决方案、存储和管理大规模数据、备份和归档数据、构建容器化应用程序的持久性存储等。它是一个强大而灵活的对象存储服务器,适用于各种不同的应用和场景。

它具有以下主要特点和功能:

  1. S3 兼容性: Minio 完全兼容 Amazon S3 的 API,这意味着您可以使用现有的 S3 客户端工具和库来与 Minio 进行交互。这使得迁移到 Minio 或者在 Minio 上构建应用程序变得非常容易。
  2. 易于部署: Minio 非常容易部署和配置。您可以在各种操作系统上安装 Minio,包括 Linux、macOS 和 Windows。此外,Minio 还提供了 Docker 镜像,可以轻松地在容器中运行。
  3. 高可用性: Minio 支持分布式部署,可以创建多个节点,以提供高可用性和容错性。如果一个节点发生故障,数据仍然可以通过其他节点访问。
  4. 数据安全: Minio 支持数据加密,您可以选择在传输和存储数据时启用加密,以确保数据的安全性。此外,Minio 还支持访问控制,允许您定义访问策略和权限。
  5. 灵活的存储后端: Minio 支持多种存储后端,包括本地磁盘、分布式文件系统(如GlusterFS、Ceph)和云存储(如Amazon S3、Google Cloud Storage)。这使得 Minio 适用于不同的部署需求。
  6. 监控和日志: Minio 提供了监控和日志功能,可以帮助您跟踪存储的使用情况、性能指标和错误日志,以便及时发现和解决问题。
  7. 社区支持: Minio 拥有活跃的开源社区,提供了丰富的文档、教程和支持资源,以帮助用户更好地使用和管理 Minio。

Docker 部署 Minio

docker-compose 部署 minio,版本 RELEASE.2023-09-04T19-57-37Z

version: '3.8'
services:  
  mingyue-minio:
    image: minio/minio:RELEASE.2023-09-04T19-57-37Z
    container_name: mingyue-minio
    ports:
      - 5000:9000 # api 端口
      - 5001:9001 # 控制台端口
    environment:
      MINIO_ACCESS_KEY: minioadmin  #管理后台用户名
      MINIO_SECRET_KEY: minioadmin  #管理后台密码,最小8个字符
    volumes:
      - ./minio/data:/data              #映射当前目录下的data目录至容器内/data目录
      - ./minio/config:/root/.minio/     #映射配置目录
    command: server --console-address ':9001' /data  #指定容器中的目录 /data
    privileged: true
    restart: always

登录访问

访问地址用户名密码
http://mingyue-minio:5001/loginminioadminminioadmin

image-20230908135008015

创建桶

image-20230908140111214

上传测试

image-20230908140424709

上传完成

image-20230908140536053

访问测试

http://minio服务器ip:5000/存储目录/文件名

http://mingyue-minio:5000/mingyue/logo_sm.jpg,页面报错,因为匿名(游客没登录)用户没有访问权限

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<Error>
  <Code>AccessDenied</Code>
  <Message>Access Denied.</Message>
  <Key>logo_sm.jpg</Key>
  <BucketName>mingyue</BucketName>
  <Resource>/mingyue/logo_sm.jpg</Resource>
  <RequestId>1782D77A870F45C7</RequestId>
  <HostId>dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8</HostId>
</Error>

如果我们需要我们上传的文件可以被匿名用户访问,那么需要添加访问权限 - Anonymous Access

再次访问测试,就可以看到上传的图片了

image-20230908144619076

创建 Access Key

Access Key 用于后续接口调用的身份认证使用。例:

Access Key:d6zVm5AP07uGCqSmsTxe

Secret Key:Vsm6qQDHgGchukEpyEoeX3dTe7fic60nTi8D9a0I

image-20230911140012528

新建 mingyue-common-oss 模块

添加依赖

<dependencies>
    <dependency>
        <groupId>com.csp.mingyue</groupId>
        <artifactId>mingyue-common-core</artifactId>
    </dependency>

    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-s3</artifactId>
    </dependency>
</dependencies>

创建配置类

@Data
@ConfigurationProperties(prefix = "oss")
public class OssProperties {

	/**
	 * 配置key
	 */
	private String configKey;

	/**
	 * 域名
	 */
	private String endpoint;

	/**
	 * 自定义域名
	 */
	private String domain;

	/**
	 * 前缀
	 */
	private String prefix;

	/**
	 * ACCESS_KEY
	 */
	private String accessKey;

	/**
	 * SECRET_KEY
	 */
	private String secretKey;

	/**
	 * 存储空间名
	 */
	private String bucketName;

	/**
	 * 存储区域
	 */
	private String region;

	/**
	 * 是否https(Y:是;N:否)
	 */
	private String isHttps;

	/**
	 * 桶权限类型(0:private;1:public;2:custom)
	 */
	private String accessPolicy;

}

新建 mingyue-oss-biz.yml Nacos 配置

# 文件服务配置
oss:
  configKey: minio
  endpoint: mingyue-minio:5000
  domain:
  prefix:
  accessKey: d6zVm5AP07uGCqSmsTxe
  secretKey: Vsm6qQDHgGchukEpyEoeX3dTe7fic60nTi8D9a0I
  bucketName: mingyue
  region: 
  isHttps: N
  accessPolicy: 1

编写 Oss Client

所有兼容S3协议的云厂商均支持(阿里云 腾讯云 七牛云 minio)

public class OssClient {

	private final String configKey;

	private final OssProperties properties;

	private final AmazonS3 client;

	public OssClient(String configKey, OssProperties ossProperties) {
		this.configKey = configKey;
		this.properties = ossProperties;
		try {
			AwsClientBuilder.EndpointConfiguration endpointConfig = new AwsClientBuilder.EndpointConfiguration(
					properties.getEndpoint(), properties.getRegion());

			AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey());
			AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);
			ClientConfiguration clientConfig = new ClientConfiguration();
			if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) {
				clientConfig.setProtocol(Protocol.HTTPS);
			}
			else {
				clientConfig.setProtocol(Protocol.HTTP);
			}
			AmazonS3ClientBuilder build = AmazonS3Client.builder().withEndpointConfiguration(endpointConfig)
					.withClientConfiguration(clientConfig).withCredentials(credentialsProvider)
					.disableChunkedEncoding();
			if (!StrUtil.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) {
				// minio 使用https限制使用域名访问 需要此配置 站点填域名
				build.enablePathStyleAccess();
			}
			this.client = build.build();

			createBucket();
		}
		catch (Exception e) {
			if (e instanceof OssException) {
				throw e;
			}
			throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]");
		}
	}

	public void createBucket() {
		try {
			String bucketName = properties.getBucketName();
			if (client.doesBucketExistV2(bucketName)) {
				return;
			}
			CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
			AccessPolicyType accessPolicy = getAccessPolicy();
			createBucketRequest.setCannedAcl(accessPolicy.getAcl());
			client.createBucket(createBucketRequest);
			client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType()));
		}
		catch (Exception e) {
			throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
		}
	}

	public UploadResult upload(byte[] data, String path, String contentType) {
		return upload(new ByteArrayInputStream(data), path, contentType);
	}

	public UploadResult upload(InputStream inputStream, String path, String contentType) {
		if (!(inputStream instanceof ByteArrayInputStream)) {
			inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
		}
		try {
			ObjectMetadata metadata = new ObjectMetadata();
			metadata.setContentType(contentType);
			metadata.setContentLength(inputStream.available());
			PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, inputStream,
					metadata);
			// 设置上传对象的 Acl 为公共读
			putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
			client.putObject(putObjectRequest);
		}
		catch (Exception e) {
			throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
		}
		return UploadResult.builder().fileUrl(getUrl() + "/" + path).fileName(path).build();
	}

	public UploadResult upload(File file, String path) {
		try {
			PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, file);
			// 设置上传对象的 Acl 为公共读
			putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
			client.putObject(putObjectRequest);
		}
		catch (Exception e) {
			throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
		}
		return UploadResult.builder().fileUrl(getUrl() + "/" + path).fileName(path).build();
	}

	public void delete(String path) {
		path = path.replace(getUrl() + "/", "");
		try {
			client.deleteObject(properties.getBucketName(), path);
		}
		catch (Exception e) {
			throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]");
		}
	}

	public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
		return upload(data, getPath(properties.getPrefix(), suffix), contentType);
	}

	public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
		return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
	}

	public UploadResult uploadSuffix(File file, String suffix) {
		return upload(file, getPath(properties.getPrefix(), suffix));
	}

	/**
	 * 获取文件元数据
	 * @param path 完整文件路径
	 */
	public ObjectMetadata getObjectMetadata(String path) {
		path = path.replace(getUrl() + "/", "");
		S3Object object = client.getObject(properties.getBucketName(), path);
		return object.getObjectMetadata();
	}

	public InputStream getObjectContent(String path) {
		path = path.replace(getUrl() + "/", "");
		S3Object object = client.getObject(properties.getBucketName(), path);
		return object.getObjectContent();
	}

	public String getUrl() {
		String domain = properties.getDomain();
		String endpoint = properties.getEndpoint();
		String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://";
		// 云服务商直接返回
		if (StrUtil.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
			if (StrUtil.isNotBlank(domain)) {
				return header + domain;
			}
			return header + properties.getBucketName() + "." + endpoint;
		}
		// minio 单独处理
		if (StrUtil.isNotBlank(domain)) {
			return header + domain + "/" + properties.getBucketName();
		}
		return header + endpoint + "/" + properties.getBucketName();
	}

	public String getPath(String prefix, String suffix) {
		// 生成uuid
		String uuid = IdUtil.fastSimpleUUID();
		// 文件路径
		String path = DateUtil.today() + "/" + uuid;
		if (StrUtil.isNotBlank(prefix)) {
			path = prefix + "/" + path;
		}
		return path + suffix;
	}

	public String getConfigKey() {
		return configKey;
	}

	/**
	 * 获取私有 URL 链接
	 * @param objectKey 对象KEY
	 * @param second 授权时间
	 */
	public String getPrivateUrl(String objectKey, Integer second) {
		GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(
				properties.getBucketName(), objectKey).withMethod(HttpMethod.GET)
						.withExpiration(new Date(System.currentTimeMillis() + 1000L * second));
		URL url = client.generatePresignedUrl(generatePresignedUrlRequest);
		return url.toString();
	}

	/**
	 * 检查配置是否相同
	 */
	public boolean checkPropertiesSame(OssProperties properties) {
		return this.properties.equals(properties);
	}

	/**
	 * 获取当前桶权限类型
	 * @return 当前桶权限类型 code
	 */
	public AccessPolicyType getAccessPolicy() {
		return AccessPolicyType.getByType(properties.getAccessPolicy());
	}

	private static String getPolicy(String bucketName, PolicyType policyType) {
		StringBuilder builder = new StringBuilder();
		builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n");
		if (policyType == PolicyType.WRITE) {
			builder.append("\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n");
		}
		else if (policyType == PolicyType.READ_WRITE) {
			builder.append("\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n");
		}
		else {
			builder.append("\"s3:GetBucketLocation\"\n");
		}
		builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
		builder.append(bucketName);
		builder.append("\"\n},\n");
		if (policyType == PolicyType.READ) {
			builder.append(
					"{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
			builder.append(bucketName);
			builder.append("\"\n},\n");
		}
		builder.append("{\n\"Action\": ");
		switch (policyType) {
			case WRITE:
				builder.append(
						"[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n");
				break;
			case READ_WRITE:
				builder.append(
						"[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n");
				break;
			default:
				builder.append("\"s3:GetObject\",\n");
				break;
		}
		builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
		builder.append(bucketName);
		builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n");
		return builder.toString();
	}

}

编写 Oss Factory

采用工厂设计,为后续引入阿里云、腾讯云、七牛云等不同OSS服务做准备

@Slf4j
@Component
@RequiredArgsConstructor
public class OssFactory {

	private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>();

	private final OssProperties ossProperties;

	/**
	 * 获取实例
	 */
	public OssClient instance() {
		String configKey = ossProperties.getConfigKey();

		// 配置不相同则重新构建
		CLIENT_CACHE.put(configKey, new OssClient(configKey, ossProperties));
		log.info("创建 OSS 实例 key => {}", configKey);

		return CLIENT_CACHE.get(configKey);
	}

}

OSS 文件服务

OSS 对象存储服务逻辑接口实现

@Override
public SysOssVo upload(MultipartFile file) {
  String originalFilename = file.getOriginalFilename();
  String suffix = StrUtil.sub(originalFilename, originalFilename.lastIndexOf("."), originalFilename.length());
  OssClient storage = ossFactory.instance();
  UploadResult uploadResult;
  try {
    uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
  }
  catch (IOException e) {
    throw new ServiceException(e.getMessage());
  }

  // 保存文件信息
  SysOssVo vo = new SysOssVo();
  BeanUtil.copyProperties(uploadResult, vo);
  vo.setOssId(System.currentTimeMillis());

  return vo;
}

对象存储服务

@Slf4j
@Tag(name = "对象存储服务")
@RestController
@RequestMapping("oss")
@RequiredArgsConstructor
public class OssController {

	private final OssService ossService;

	@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
	@Operation(summary = "上传文件")
	public R<UploadVo> upload(@RequestPart("file") MultipartFile file) {
		if (ObjectUtil.isNull(file)) {
			return R.fail("上传文件不能为空");
		}

		SysOssVo oss = ossService.upload(file);
		UploadVo vo = new UploadVo();
		vo.setFileUrl(oss.getUrl());
		vo.setFileName(oss.getFileName());
		vo.setOssId(oss.getOssId());

		return R.ok(vo);
	}

}

启动服务

启动服务,打开接口文档,上传文件测试

http://mingyue-gateway:9100/webjars/swagger-ui/index.html?urls.primaryName=oss#/%E5%AF%B9%E8%B1%A1%E5%AD%98%E5%82%A8%E6%9C%8D%E5%8A%A1/upload

成功后接口返回如下:

{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "fileUrl": "http://mingyue-minio:5000/mingyue/2023-09-11/a6987303827148b983c84ac415240480.png",
    "fileName": "2023-09-11/a6987303827148b983c84ac415240480.png",
    "ossId": 1694414731976
  }
}

改进空间

1. 上传文件后未保存文件信息,无法溯源

解决方案:添加 OSS 文件存储表

2. 配置通过 Nacos 单一 OSS 服务支持

解决方案:添加 OSS 配置表

小结

本节完成了 Minio 的部署,并且通过编码的方式将文件上传至 Minio。接下来先围绕【改进空间】优化上传服务,然后再接入其他 OSS 存储服务,逐步完善文件服务。

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

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

相关文章

thinkphp5.0 composer 安装oss提示php版本异常

场景复现&#xff1a; 本地 phpstudy 环境&#xff0c;安装的有7.0到7.3三个版本&#xff0c;首先确认composer已经安装 composer安装阿里云oss的命令为&#xff1a;composer require aliyuncs/oss-sdk-php 运行报错&#xff1a; Problem 1- Root composer.json requires php…

电机故障数据集

1.电机常见的故障类型有以下几种&#xff1a; 轴承故障&#xff1a;轴承是电机运转时最容易受损的部件之一。常见故障包括磨损、疲劳、过热和润滑不良&#xff0c;这些问题可能导致噪音增加和电机性能下降。 绝缘老化&#xff1a;电机绝缘材料随着使用时间的增加会老化&#x…

微服务·数据一致-seata

微服务数据一致-seata 概述 Seata&#xff08;Simple Extensible Autonomous Transaction Architecture&#xff09;是一个开源的分布式事务解决方案&#xff0c;旨在帮助应用程序分布式事务管理的挑战。Seata提供了一套全面的工具和框架&#xff0c;可用于实现跨多个数据库和…

Nginx+Tomcat(多实例)实现动静分离和负载均衡

一、Tomcat 多实例部署 1.在安装好jdk环境后&#xff0c;添加两例tomcat服务 #解压安装包 cd /opt tar zxvf apache-tomcat-9.0.16.tar.gz#移动并复制一例 mkdir /usr/local/tomcat mv apache-tomcat-9.0.16 /usr/local/tomcat/tomcat1 cp -a /usr/local/tomcat/tomcat1 /usr…

常用JVM配置参数

在IDE的后台打印GC日志&#xff1a; 既然学习JVM&#xff0c;阅读GC日志是处理Java虚拟机内存问题的基础技能&#xff0c;它只是一些人为确定的规则&#xff0c;没有太多技术含量。 既然如此&#xff0c;那么在IDE的控制台打印GC日志是必不可少的了。现在就告诉你怎么打印。 …

Django03_Django基本配置

Django03_Django基本配置 3.1 整体概述 django项目创建后&#xff0c;在主应用中&#xff0c;会有一个settings.py文件&#xff0c;这个就是该项目的配置文件 settings文件包含Django安装的所有配置settings文件是一个包含模块级变量的python模块&#xff0c;所以该模块本身必…

解决nbsp;不生效的问题

代码块 {{title}} title:附 \xa0\xa0\xa0件,//或者 <span v-html"title"></span> title:附 件&#xff1a;,效果图

青骨申报|CSC管理信息平台使用指南

2023年青年骨干教师出国研修项目于9月10-25日网上报名&#xff0c;为此知识人网小编特转载最新版本的国家留学基金委&#xff08;CSC&#xff09;国家公派留学管理信息平台使用指南&#xff08;国内申请访学类&#xff09;&#xff0c;以方便申报者查阅。 提示&#xff1a;国家…

静态代理和动态代理笔记

总体分为: 1.静态代理: 代理类和被代理类需要实现同一个接口.在代理类中初始化被代理类对象.在代理类的方法中调 用被代理类的方法.可以选择性的在该方法执行前后增加功能或者控制访问 2.动态代理: 在程序执行过程中,实用JDK的反射机制,创建代理对象,并动态的指定要…

数字档案管理系统单机版功能

nhdeep数字档案管理系统&#xff0c;简化了档案库配置过程&#xff0c;内置标准著录项&#xff0c;点击创建新档案库后选择档案库类型为案卷库或一文一件库后&#xff0c;可立即使用此档案库&#xff1b; 支持添加额外的自定义著录项&#xff0c;支持批量数据导入&#xff0c;…

读高性能MySQL(第4版)笔记06_优化数据类型(上)

1. 良好的逻辑设计和物理设计是高性能的基石 1.1. 反范式的schema可以加速某些类型的查询&#xff0c;但同时可能减慢其他类型的查询 1.2. 添加计数器和汇总表是一个优化查询的好方法&#xff0c;但它们的维护成本可能很 1.3. 将修改schema作为一个常见事件来规划 2. 让事情…

JVM GC垃圾回收

一、GC垃圾回收算法 标记-清除算法 算法分为“标记”和“清除”阶段&#xff1a;标记存活的对象&#xff0c; 统一回收所有未被标记的对象(一般选择这种)&#xff1b;也可以反过来&#xff0c;标记出所有需要回收的对象&#xff0c;在标记完成后统一回收所有被标记的对象 。它…

二分搜索树节点删除(Java 实例代码)

目录 二分搜索树节点删除 src/runoob/binary/BSTRemove.java 文件代码&#xff1a; 二分搜索树节点删除 本小节介绍二分搜索树节点的删除之前&#xff0c;先介绍如何查找最小值和最大值&#xff0c;以及删除最小值和最大值。 以最小值为例&#xff08;最大值同理&#xff09…

在 Simscape Electrical 中对两区 MVDC 电动船的建模和仿真(Simulink实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Linux Centos7内网服务器离线升级openssh9.3

内网服务器需要升级openssh&#xff0c;被折磨了一整天&#xff0c;觉得有必要记录一下&#xff0c;不然对不起这差点崩溃的一天&#xff0c;主要的几个难点就是不能yum一键安装&#xff0c;需要自己找到对应的依赖版本然后通过堡垒机上传到内网&#xff0c;还有就是服务器很干…

无涯教程-JavaScript - ISPMT函数

描述 ISPMT函数计算在特定投资期间支付的利息。提供此功能是为了与Lotus 1-2-3兼容。 语法 ISPMT (rate, per, nper, pv)争论 Argument描述Required/OptionalRateThe interest rate for the investment.RequiredPerThe period for which you want to find the interest, an…

vue-puzzle-vcode完成验证码拖拽

一、 vue-puzzle-vcode插件【推荐】 GitHub地址&#xff1a;beeworkshop/vue-puzzle-vcode 1、安装vue-puzzle-vcode cnpm i -S vue-puzzle-vcode2、实现代码 <template><div><Vcode :show"isShow" success"success" close"close&…

7年经验之谈 —— Web测试是什么,有何特点?

Web测试是指对Web应用程序进行验证和评估的过程&#xff0c;以确保其功能、性能和安全性符合预期。 Web测试具体包括以下几个方面的内容&#xff1a; 功能测试&#xff1a;验证Web应用程序是否按照需求规格说明书中定义的功能正常工作。功能测试包括输入验证、表单提交、页面…

【Flutter】Flutter 使用 pull_to_refresh 实现下拉刷新和上拉加载

【Flutter】Flutter 使用 pull_to_refresh 实现下拉刷新和上拉加载 文章目录 一、前言二、pull_to_refresh 包简介三、安装与基本使用四、高级功能与配置五、实际业务中的用法六、完整示例七、总结 一、前言 你好&#xff01;在移动开发中&#xff0c;下拉刷新和上拉加载是非常…

【数据结构】双向链表详解

当我们学习完单链表后&#xff0c;双向链表就简单的多了&#xff0c;双向链表中的头插&#xff0c;尾插&#xff0c;头删&#xff0c;尾删&#xff0c;以及任意位置插&#xff0c;任意位置删除比单链表简单&#xff0c;今天就跟着小张一起学习吧&#xff01;&#xff01; 双向链…