视频资源迁移到OSS服务器上,记录一下迁移过程。

搭建流程

在阿里云上购买oss,并获取具有该Bucket访问权限的AccessKey ID和AccessKey Secret信息。

数据迁移方案一

第一次尝试,不修改后端代码,直接将oss直接挂载映射到本地服务器。发现这种方法并不起作用,由于oss最终传输资源都要经由服务器再下发给用户,传输速度依然受限于服务器带宽,并没有起到加速的作用。

操作流程如下:

(1) 下载安装包。

以下载CentOS 7.0 (x64)版本为例:

sudo wget http://gosspublic.alicdn.com/ossfs/ossfs_1.80.6_centos7.0_x86_64.rpm

(2) 安装ossfs。

(3) CentOS系统/Anolis系统

以CentOS 7.0(x64)版本为例,安装命令如下:

sudo yum install ossfs_1.80.6_centos7.0_x86_64.rpm

(4) 配置账号访问信息。

将Bucket名称以及具有该Bucket访问权限的AccessKey ID和AccessKey Secret信息存放在/etc/passwd-ossfs文件中。文件的权限建议设置为640。

sudo **echo** BucketName:yourAccessKeyId:yourAccessKeySecret > /etc/passwd-ossfs

sudo **chmod** 640 /etc/passwd-ossfs

BucketName、yourAccessKeyId、yourAccessKeySecret请按需替换为您实际的Bucket名称、AccessKey ID和AccessKey Secret,例如:

sudo **echo** bucket-test:LTAIbZcdmQ****:MOk8x0y1coh7A5e2MZEUz**** > /etc/passwd-ossfs

sudo **chmod** 640 /etc/passwd-ossfs

(5)将Bucket挂载到指定目录。

由于我们需要将视频文件进行挂载,此时的挂载目录是/data/docker/videoSystem_v,且BucketName是video-bucket01。

示例如下:

**mkdir** /tmp/ossfs

ossfs video-bucket01 /data/docker/videoSystem_v/videoRes -o url=http://oss-cn-hangzhou.aliyuncs.com

(6)由于系统是跑在docker里面,数据是存储到容器里,因此需要把容器里的数据与oss映射的路径做绑定,这里通过创建docker数据卷做绑定:

 docker volume create --driver local \

  --opt type=none \

  --opt device=/data/docker/videoSystem_v/videoRes \

  --opt o=bind \

  oss_volume_name

(7)启动docker对应镜像,并作为数据卷的绑定:

docker run -p 8181:8181 -v oss_volume_name:/videoSystem_workdir/videoRes --name videoSystem --restart unless-stopped -d videosystem:v4.0

(8)Bucket卸载指令

sudo fusermount -u /data/docker/videoSystem_v/videoRes

​ 测试结果:60mb的视频上传,需要花费6分钟才能上传结束。如果不采用OSS映射方案,一分钟就能结束,上传反而更花费时间,这里推测是因为上传需要先上传到服务器本地内存,然后再从服务器本地上传到OSS服务器,并且中间存在映射关系,服务器自身有其他的业务压力,使得耗费时间更长。并且,上传的1080p视频,从用户网站打开播放,不能流畅播放,跑的依然是原先的服务器带宽,如果直接使用OSS上的视频url链接,绕过服务器,则能直接流畅播放1080p的视频。

​ 总结:该方案并不能实现预期的效果,因此下一方案将从后端代码层面进行修改。

数据迁移方案二

方案二从代码端进行更改,并且更改上传的模式——由前端进行上传。

配置OSS

1、本套方案采用前端上传文件的方式,需要前端向请求sts权限认证,为实现这套流程,需要在OSS控制台申请RAM用户,并且配置相关的请求sts认证权限。具体流程参考:

使用STS临时访问凭证访问OSS (aliyun.com)

2、保存RAM用户的访问密钥(AccessKey ID 和 AccessKey Secret)。

3、将bucket的权限设置为公共读,其文件的url是永久有效的,方便我们存到数据库以及后续通过url进行文件的下载和播放。

后端代码修改

新增AliyunConfig类:

@Component
@ConfigurationProperties(prefix = "aliyun")
@Data
public class AliyunConfig { // aliyun endpoint
private String endpoint; // aliyun accessKeyId
private String accessKeyId; // aliyun accessKeySecret
private String accessKeySecret; // aliyun bucketName
private String bucketName; // aliyun urlPrefix
private String urlPrefix; // aliyun stsEndPoint
private String stsEndPoint; // 用户权限角色
private String roleArn; // OSS所在区域
private String region; // OSS回调地址
private String callBackUrl; // 3D存储路径
private String StorageDirXA; // XR存储路径
private String StorageDirXB; @Bean
public OSS oSSClient() {
return new OSSClient(endpoint, accessKeyId, accessKeySecret);
}
}

在applicantion.yml新增配置信息:

aliyun:
endpoint: oss-cn-shenzhen.aliyuncs.com
accessKeyId: LTAI5t**********B9vBbD
accessKeySecret: 0FI3UB*****3KxnZdKNpHKWcY
bucketName: ******
urlPrefix: https:********.com
stsEndPoint: sts.cn-shenzhen.aliyuncs.com
roleArn: acs:ram::10*****2863:role/ramosstest
region: oss-cn-shenzhen
callBackUrl: https://**********
StorageDir3d: videoRes/shareXA
StorageDirXr: videoRes/shareXB

回调上传

@ResponseBody
@GetMapping("/getSts")
public AjaxResult getSts(HttpServletRequest req) throws com.aliyuncs.exceptions.ClientException {
AssumeRoleResponse.Credentials credentials = null;
String roleSessionName = "AliyunDMSRol--ePolicy";
// OSS服务器上设置的角色拥有所有权限,可以在下面policy添加需要的权限,按需要给予相关权限
String policy = "{\n" +
" \"Statement\": [\n" +
" {\n" +
" \"Action\": [\n" +
" \"oss:GetObject\",\n" +
" \"oss:PutObject\",\n" +
// 前端暂时不需要删除权限
//" \"oss:DeleteObject\",\n" +
" \"oss:ListParts\",\n" +
" \"oss:AbortMultipartUpload\",\n" +
" \"oss:ListObjects\"\n" +
// 下面的oss:* 拥有所有权限 慎用
//" \"oss:*\"\n" +
" ],\n" +
" \"Effect\": \"Allow\",\n" +
" \"Resource\": [\n" +
" \"acs:oss:*:*:colorlight-video/*\",\n" +
" \"acs:oss:*:*:colorlight-video\"\n" +
" ]\n" +
" }\n" +
" ],\n" +
" \"Version\": \"1\"\n" +
"}";
// 设置临时访问凭证的有效时间为3600秒。
Long durationSeconds = 3600L;
Map<String, Object> map = new HashMap<>();
try {
// 添加endpoint(直接使用STS endpoint,前两个参数留空,无需添加region ID)
DefaultProfile.addEndpoint("", "", "Sts", aliyunConfig.getStsEndPoint());
// 构造default profile(参数留空,无需添加region ID)
IClientProfile profile = DefaultProfile.getProfile("", aliyunConfig.getAccessKeyId(), aliyunConfig.getAccessKeySecret());
// 用profile构造client
DefaultAcsClient client = new DefaultAcsClient(profile);
final AssumeRoleRequest request = new AssumeRoleRequest();
request.setMethod(MethodType.POST);
request.setRoleArn(aliyunConfig.getRoleArn());
request.setRoleSessionName(roleSessionName);
request.setPolicy(policy); // Optional
request.setProtocol(ProtocolType.HTTPS); // 必须使用HTTPS协议访问STS服务);
final AssumeRoleResponse response = client.getAcsResponse(request);
credentials = response.getCredentials();
map = ObjectToMapUtil.objectToMap(credentials);
map.put("bucket", aliyunConfig.getBucketName());
map.put("region",aliyunConfig.getRegion());
CallBack callback = new CallBack();
callback.setUrl(aliyunConfig.getCallBackUrl() + "/clt3D/videoUpload/callBack");
callback.setBody("{fileName:${object},size:${size},mimeType:${mimeType},videoName:${x:videoName},definition:${x:definition}}");
callback.setContentType("application/json");
map.put("callback", callback);
} catch (Exception e) {
e.printStackTrace();
}
return AjaxResult.success(map);
}

上传成功后OSS调用回调接口,返回上传的文件的数据:

 @ResponseBody
@PostMapping("/callBack")
@Log(title = "新增视频", module = "clt3D", businessType = BusinessTypeEnum.INSERT)
public AjaxResult callBack(HttpServletRequest request, HttpServletResponse response) {
Video3D newVideo3D;
String fileUUID;
try {
AliyunCallbackUtil aliyunCallbackUtil = new AliyunCallbackUtil();
String ossCallBackBody = aliyunCallbackUtil.GetPostBody(request.getInputStream(), Integer.parseInt(request.getHeader("content-length")));
JSONObject jsonBody = JSON.parseObject(ossCallBackBody);
String fileName = jsonBody.getString("fileName");
String videoOriginalFilename = jsonBody.getString("videoName");
String videoName = NameUtil.format3DVideoName(ResourceUtil.getFileNamePrefix(videoOriginalFilename));
String definition = jsonBody.getString("definition");
String size = jsonBody.getString("size");
String ossUrl = aliyunConfig.getUrlPrefix();
String fileDir;
boolean zipFlag = false;
boolean verifyResult = aliyunCallbackUtil.VerifyOSSCallbackRequest(request, ossCallBackBody);
// 回调签名验证是不是OSS发来的签名
if (!verifyResult) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return AjaxResult.failed("OSS回调签名验证失败");
}
String[] videoNameArray = videoOriginalFilename.split("\\.");
//获取文件类型 video 或者是 image
String fileType = videoNameArray[videoNameArray.length - 1];
//拿到文件名
String[] fileNameArray = fileName.split("/");
String realName = fileNameArray[3];
String[] realNameArray = realName.split("_");
fileUUID = realNameArray[0];
// 判断上传的是不是压缩视频
if (realNameArray[1].equals("zip")) {
Video3D video3D = video3DService.getOne(new QueryWrapper<Video3D>().eq("mark", fileUUID));
video3D.setVideoCompressionUrl(ossUrl + "/" + fileName);
boolean updateFlag = video3DService.updateById(video3D);
if (!updateFlag) {
return AjaxResult.failed("压缩文件上传失败");
} else {
return AjaxResult.success("上传成功");
}
}else {
newVideo3D = new Video3D().setMark(fileUUID)
.setType(0)
.setVideoName(videoName)
.setUploadTime(new Date());
String fileSize = FileUtil.getFileSizeDescription(Integer.parseInt(size));
switch (definition) {
case "360p": // 判断属于哪种清晰度,决定保存的文件名
fileDir = "video360p";
newVideo3D.setVideo360pFormat(fileType);
newVideo3D.setVideo360pSize(fileSize);
newVideo3D.setVideo360pUrl(ossUrl + "/" + fileName);
break;
case "480p":
fileDir = "video480p";
newVideo3D.setVideo480pFormat(fileType);
newVideo3D.setVideo480pSize(fileSize);
newVideo3D.setVideo480pUrl(ossUrl + "/" + fileName);
break;
case "720p":
fileDir = "video720p";
newVideo3D.setVideo720pFormat(fileType);
newVideo3D.setVideo720pSize(fileSize);
newVideo3D.setVideo720pUrl(ossUrl + "/" + fileName);
break;
case "1080p":
fileDir = "video1080p";
newVideo3D.setVideo1080pFormat(fileType);
newVideo3D.setVideo1080pSize(fileSize);
newVideo3D.setVideo1080pUrl(ossUrl + "/" + fileName);
break;
default:
break;
}
}
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.failed("上传失败");
} // 从微信小程序服务器获取视频对应的QR码
try {
newVideo3D.setQrUrl(getQr(getUsableToken().getAccessToken(), fileUUID));
} catch (Exception e) {
e.printStackTrace();
} //把上传的文件数据入库
try {
video3DService.save(newVideo3D);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.failed("上传失败");
}
return AjaxResult.success("上传成功");
}

回调中用到的AliyunCallbackUtil ,使用到了OSS调用回调接口传来的公钥和签名,采用RSA加密方式进行认证

public class AliyunCallbackUtil extends HttpServlet {

   private static final long serialVersionUID = 5522372203700422672L;

   /********************************
* @method : executeGet
* @function : 生成公钥
* @parameter : [url]
* @return : java.lang.String
* @date : 2023/7/21 14:24
********************************/
public String executeGet(String url) {
BufferedReader in = null;
String content = null;
try {
// 定义HttpClient
@SuppressWarnings("resource")
DefaultHttpClient client = new DefaultHttpClient();
// 实例化HTTP方法
HttpGet request = new HttpGet();
request.setURI(new URI(url));
HttpResponse response = client.execute(request);
in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
StringBuffer sb = new StringBuffer("");
String line = "";
String NL = System.getProperty("line.separator");
while ((line = in.readLine()) != null) {
sb.append(line + NL);
}
in.close();
content = sb.toString();
} catch (Exception e) {
} finally {
if (in != null) {
try {
in.close();// 最后要关闭BufferedReader
} catch (Exception e) {
e.printStackTrace();
}
}
return content;
}
} /********************************
* @method : GetPostBody
* @function : 获取回调Body内容
* @parameter : [is, contentLen]
* @return : java.lang.String
* @date : 2023/7/21 14:24
********************************/
public String GetPostBody(InputStream is, int contentLen) {
if (contentLen > 0) {
int readLen = 0;
int readLengthThisTime = 0;
byte[] message = new byte[contentLen];
try {
while (readLen != contentLen) {
readLengthThisTime = is.read(message, readLen, contentLen - readLen);
if (readLengthThisTime == -1) {// Should not happen.
break;
}
readLen += readLengthThisTime;
}
return new String(message);
} catch (IOException e) {
}
}
return "";
} /********************************
* @method : VerifyOSSCallbackRequest
* @function : 验证回调请求
* @parameter : [request, ossCallbackBody]
* @return : boolean
* @date : 2023/7/21 14:24
********************************/
public boolean VerifyOSSCallbackRequest(HttpServletRequest request, String ossCallbackBody) throws NumberFormatException, IOException
{
// 整个验证流程是获取autorization 然后生成公钥,用公钥验签 使用请求url+body 与原先autorization作匹配 相同则验签就成功
boolean ret = false;
String autorizationInput = new String(request.getHeader("Authorization"));
String pubKeyInput = request.getHeader("x-oss-pub-key-url");
byte[] authorization = BinaryUtil.fromBase64String(autorizationInput);
byte[] pubKey = BinaryUtil.fromBase64String(pubKeyInput);
String pubKeyAddr = new String(pubKey);
if (!pubKeyAddr.startsWith("http://gosspublic.alicdn.com/") && !pubKeyAddr.startsWith("https://gosspublic.alicdn.com/"))
{
System.out.println("pub key addr must be oss addrss");
return false;
}
// 生成公钥
String retString = executeGet(pubKeyAddr);
retString = retString.replace("-----BEGIN PUBLIC KEY-----", "");
retString = retString.replace("-----END PUBLIC KEY-----", "");
String queryString = request.getQueryString();
String uri = request.getRequestURI();
String decodeUri = java.net.URLDecoder.decode(uri, "UTF-8");
String authStr = decodeUri;
if (queryString != null && !queryString.equals("")) {
authStr += "?" + queryString;
}
authStr += "\n" + ossCallbackBody;
ret = doCheck(authStr, authorization, retString);
return ret;
} /********************************
* @method : doCheck
* @function : 检查回调签名是否正确
* @parameter : [content, sign, publicKey]
* @return : boolean
* @date : 2023/7/21 14:25
********************************/
public static boolean doCheck(String content, byte[] sign, String publicKey) {
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] encodedKey = BinaryUtil.fromBase64String(publicKey);
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
java.security.Signature signature = java.security.Signature.getInstance("MD5withRSA");
signature.initVerify(pubKey);
signature.update(content.getBytes());
boolean bverify = signature.verify(sign);
return bverify; } catch (Exception e) {
e.printStackTrace();
}
return false;
}

涉及到文件由后端进行上传的代码部分:

File file = new File(filepath);
FileOutputStream fos = new FileOutputStream(file);
byte[] b = new byte[1024];
while ((is.read(b)) != -1) {
fos.write(b);// 写入数据
}
is.close();
fos.close();// 保存数据
//添加写入oss //添加写入oss
try {
ossClient.putObject(aliyunConfig.getBucketName(), "videoRes/share3D/" + mark + "/" + fileName, file);
} catch (Exception e) {
e.printStackTrace();
}
//把本地的FILE文件删除
String qrDir = ResourceUtil.getStorageParentXADir() + mark + "/";
File qrFile = new File(qrDir);
FileUtil.delBatchFile(qrFile);
return aliyunConfig.getUrlPrefix() + "/videoRes/shareXA/" + mark + "/" + fileName;
}

涉及文件从后端向OSS获取的代码部分:

File file = new File(fileDir);
String objectName = "videoRes/shareXA/"
+ mark + "/"
+ mark
+ SystemConstant.QR_FILE_NAME_SUFFIX_XA
+ SystemConstant.QR_IMAGE_FORMAT_DEFAULT_PNG;
try {
// 下载Object到本地文件,并保存到指定的本地路径中。如果指定的本地文件存在会覆盖,不存在则新建。
// 先创建出文件
if (!file.exists()) {
//先得到文件的上级目录,并创建上级目录,在创建文件
file.getParentFile().mkdir();
try {
//创建文件
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
ossClient.getObject(new GetObjectRequest(aliyunConfig.getBucketName(), objectName), file);
} catch (Exception e) {
e.printStackTrace();
}
FileUtil.fileToZip(fileDir, zipOut, newVideoName);
File imageTargetFile = new File(ResourceUtil.getStorageParent3DDir()
+ mark + "/");
FileUtil.delBatchFile(imageTargetFile);
}

涉及文件删除的代码:

public AjaxResult deleteVideos(@RequestBody JSONObject reqObj) {
JSONArray marksArray = reqObj.getJSONArray("marks");
if (marksArray.size() == 0) {
return AjaxResult.failed(ResultCodeEnum.PARAM_FORMAT_ERROR);
}
int count = 0;
for (int i = 0; i < marksArray.size(); i++) {
String mark = marksArray.getString(i);
try {
if (video3DService.remove(new QueryWrapper<Video3D>().eq("mark", mark))) {
count++;
}
favoritesVideo3DService.remove(new QueryWrapper<FavoritesVideo3D>().eq("mark", mark));
String prefix = "videoRes/share3D/" + mark + "/";
// 列举所有包含指定前缀的文件并删除。
String nextMarker = null;
ObjectListing objectListing = null;
do {
ListObjectsRequest listObjectsRequest = new ListObjectsRequest(aliyunConfig.getBucketName())
.withPrefix(prefix)
.withMarker(nextMarker);
objectListing = ossClient.listObjects(listObjectsRequest);
if (objectListing.getObjectSummaries().size() > 0) {
List<String> keys = new ArrayList<String>();
for (OSSObjectSummary s : objectListing.getObjectSummaries()) {
System.out.println("key name: " + s.getKey());
keys.add(s.getKey());
}
DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(aliyunConfig.getBucketName()).withKeys(keys).withEncodingType("url");
DeleteObjectsResult deleteObjectsResult = ossClient.deleteObjects(deleteObjectsRequest);
List<String> deletedObjects = deleteObjectsResult.getDeletedObjects();
for (String obj : deletedObjects) {
String deleteObj = URLDecoder.decode(obj, "UTF-8");
System.out.println(deleteObj);
}
}
nextMarker = objectListing.getNextMarker();
} while (objectListing.isTruncated());
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.failed();
}
}
return AjaxResult.success("删除成功,共删除" + count + "个视频");
}

资源迁移OSS方案记录的更多相关文章

  1. 平台支持的从经典部署模型到 Azure Resource Manager 的 IaaS 资源迁移

    本文介绍如何才能将基础结构即服务 (IaaS) 资源从经典部署模型迁移到 Resource Manager 部署模型. 用户可以阅读有关 Azure Resource Manager 功能和优点的更多 ...

  2. Nginx+Php中限制站点目录防止跨站的配置方案记录

    Nginx+Php中限制站点目录防止跨站的配置方案记录(使用open_basedir)-------------------方法1)在Nginx配置文件中加入: 1 fastcgi_param  PH ...

  3. 基于freescale i.Mx6(ARM)的阿里云oss调试记录

    交叉编译阿里OSS调试记录 1.1 开通oss服务 具体参考以下链接: https://help.aliyun.com/document_detail/31884.html?spm=a2c4g.111 ...

  4. 服务器资源迁移到aliyun对象存储及oss的权限管理配置

    chinasoft-download增值服务的迁移和部署 需求: 增值服务网站需要从网宿迁移到阿里云,以前的增值服务历史软件存放在服务器中需要迁移到阿里云的oss中存放 需要改造程序给程序添加一个os ...

  5. openstack虚拟机迁移的操作记录

    需求说明:计算节点linux-node1.openstack:192.168.1.8  计算节点linux-node2.openstack:192.168.1.17 这两个计算节点在同一个控制节点下( ...

  6. mysql 5.7 迁移数据方案

    从一台服务器迁移至其他服务器,如何选择最短的停服时间方案 方案一.凌晨3点的全备份+停服后一天的大概一天的增备 1. 拷贝前一天的全备份至新的服务器 rsync -auzrP /Data/dbbak/ ...

  7. 如何将已部署在ASM的资源迁移到ARM中

    使用过Azure的读者都知道,Azure向客户提供了两个管理portal,一个是ASM,一个是ARM,虽然Azure官方没有宣布说淘汰ASM,两个portal可能会在很长的一段时间共存,但是考虑到AR ...

  8. UE4程序及资源加密保护方案

    UnrealEngine4外壳加密 . Virbox Protector 解决代码反汇编和反dump代码,解决软件盗版与算法抄袭. 虚幻引擎4是由游戏开发者为开发游戏而制作的.完整的游戏开发工具套件. ...

  9. WebLogic Server 多租户资源迁移

    重新建立一个动态集群,并启动,注意监听地址不能和其他集群重合 选择相应的资源组进行迁移, 迁移后,访问新的地址成功. 通过OTD负载均衡器访问原有的地址成功. 直接访问原来后台地址失败,表示资源确实已 ...

  10. nginx 部署前端资源的最佳方案

    前言 最近刚来一个运维小伙伴,做线上环境的部署的时候,前端更新资源后,总是需要清缓存才能看到个更新后的结果.客户那边也反馈更新了功能,看不到. 方案 前端小伙伴应该都知道浏览器的缓存策略,协商缓存和强 ...

随机推荐

  1. 我开源了团队内部基于SpringBoot Web快速开发的API脚手架v1.6.0更新

    什么是 rest-api-spring-boot-starter rest-api-spring-boot-starter 适用于SpringBoot Web API 快速构建让开发人员快速构建统一规 ...

  2. 2021-09-27 Core初步实战

    中间件Progarm的定义添加Logging public static IHostBuilder CreateHostBuilder(string[] args) => Host.Create ...

  3. vulnhub Necromancer wp

    flag1 nmap -sU -T4 192.168.220.130 有666端口 nc -nvu 192.168.220.130 666 监听回显消息 tcpdump host 192.168.22 ...

  4. 时序数据库 InfluxDB 第一篇 安装部署

    使用场景: 最近项目上遇到大数据存储的问题,一个IOT融合项目,涉及到大量的工控监测数据存储.当前存储到关系库中的数据已经达到2亿条了.做了很多优化,查询还是很慢.便想着是否有更好的解决方案. 了解到 ...

  5. [gin]数据解析和绑定

    前言 go version: 1.18 本文主要包含JSON.Form.Uri.XML的数据解析与绑定. JSON数据解析与绑定 go代码 package main import ( "ne ...

  6. 基于redis6搭建集群

    前言 系统版本:CentOS 7 redis版本:redis6.2.4,官方tar.gz包 两台服务器: 172.50.11.11 端口7002.7004.7006 172.50.12.11 端口70 ...

  7. jmeter对请求响应结果进行整段内容提取方法

    通过正则表达式提取器,将上一个请求(A请求)响应数据中的整段内容提取,传给下一个需要该提取数据的请求(B请求). 1. 请求接口响应结果 2. 添加正则表达式提取器 设置变量名为"tt&qu ...

  8. 使用C++界面框架ImGUI开发一个简单程序

    目录 简介 使用示例 下载示例 main文件 设置ImGui风格 设置字体 主循环 添加Application类 中文编码问题 界面设计 关于imgui_demo.cpp 创建停靠空间 创建页面 隐藏 ...

  9. .NET周刊【8月第2期 2023-08-14】

    本周由于Myuki大佬感染新冠,国际板块暂停更新一周,将在下周补齐,所以本周只有国内板块. 国内文章 解决 Blazor 中因标签换行导致的行内元素空隙问题 https://www.cnblogs.c ...

  10. 一文读懂LockSupport

    阅读本文前,需要储备的知识点如下,点击链接直接跳转. java线程详解 Java不能操作内存?Unsafe了解一下 LockSupport介绍 搞java开发的基本都知道J.U.C并发包(即java. ...