Go实现多线程分片下载文件
2024/10/21 16:39 | by 刘新修 ]
我们在下载大文件时,通常会使用多线程下载的方式来加快下载速度。例如常用的多线程下载工具(Gopeed、Aria2、XDM等等),都是通过多线程下载技术充分利用了网络带宽,以提高下载速度。
那么多线程下载是怎么实现的呢?多个线程发送网络请求,是怎么做到同时下载一个文件呢?事实上,借助HTTP协议中的一些机制就可以实现了!
今天我们就通过使用Go语言为例,从了解HTTP请求相关的一些机制开始,实现一个多线程下载的示例。
1,多线程下载原理
事实上,多线程下载的原理很简单,主要的步骤如下:
- 获取待下载文件大小
- 每个线程下载文件的一部分
- 全部下载完成后,拼接为完整文件
实现这些步骤,就涉及到HTTP协议的下列相关机制。
(1) HEAD请求 - 只获取请求头
我们通常发送HTTP请求大多数是GET或者POST类型,发送请求后我们会立即获取响应体,浏览器则会根据响应体的类型来处理内容,例如返回的是text/html就会作为网页显示,返回image/png就会解码为图片等等,响应体的类型由响应头Content-Type标识。当我们下载文件时,事实上也是发送HTTP请求,只不过服务器返回的响应体就是文件本身了!其类型则是application/octet-stream,浏览器也知道这是个文件需要下载。
当然,文件作为响应体通常比起网页、图片要大得多,在多线程下载时,我们就要先获取文件的大小,而不是立即获取文件本身,这时我们就可以向服务器发起HEAD请求而不是GET请求。
服务器收到HEAD请求后,就只会返回对应的响应头,而不会返回响应体,这样我们就可以在下载文件之前,读取响应头中的Content-Length来先获取待下载文件大小。
(2) Range请求头 - 只获取部分响应体
知道了文件大小,我们就需要让每个线程只下载一部分文件,借助HTTP的Range请求头,就可以实现只让服务端返回响应体内容的一部分,而不是返回完整的响应体。
这里我们先来借助书籍《图解HTTP》中对Range请求头的讲解,来学习一下:

- Range: bytes=5001-10000
那么服务端就只会返回响应体的第5001到第10000字节的内容部分,包含第5001和第10000字节,0表示响应体的第一个字节。
这样,在多个线程同时下载文件时,我们在每个线程的请求中使用Range请求头,就可以实现一个线程只下载文件的一部分了!
(3) 为什么多线程下载可以提升速度?
事实上,在我们客户端(下载文件的)和服务端双向网络通信情况都很好的情况下,使用单线程和多线程下载的速度是几乎没有差异的,也就是说能够跑满我们客户端的全部带宽,那么这种情况下我们使用单线程下载反而更能够节省硬件和网络资源。
但是在我们客户端和服务端之间网络波动较大的情况下,例如我们国内从Github下载文件的时候,就会发现多线程下载速度比单线程快得多,反之使用单线程完全无法充分利用我们的网络带宽。
这种现象事实上是因为TCP连接的慢启动机制导致的,众所周知HTTP是基于TCP的协议,每次我们建立HTTP连接时,包括下载文件,都是在传输层基于TCP协议进行传输。TCP慢启动机制是TCP 协议中一种拥塞控制的机制,目的是在开始数据传输时逐步探测网络的容量,避免瞬间发送大量数据而导致网络拥塞。慢启动不是字面意义上的“慢”,而是相对于立即使用最大带宽而言,它会逐渐增加传输速率。
慢启动机制的过程简要概括如下:
- 一开始建立连接:当一个新的TCP连接建立后,发送方并不知道当前网络的拥塞情况。因此,发送方不会马上发送大量数据,而是会使用慢启动机制来逐步增加数据传输的速率,在TCP中使用阻塞窗口
cwnd来限制发送的数据量,也就是说一开始cwnd是非常小的 - 拥塞窗口增长:在建立连接后,每当接收到一个确认
ACK包时,cwnd会指数级增长,直到达到网络的带宽限制或者某个拥塞控制的阈值(称为慢启动阈值ssthresh),这个过程会一直持续,直到发送方探测到网络出现了拥塞(比如丢包或者确认延迟变长),或者cwnd达到了某个预定义的慢启动阈值ssthresh - 慢启动的终止:慢启动机制会在以下情况终止:
- 达到慢启动阈值
ssthresh:当拥塞窗口cwnd增长到慢启动阈值ssthresh时,慢启动机制停止,此时TCP会进入另一种拥塞控制机制,称为拥塞避免,这时cwnd增长变为线性而非指数级 - 发生拥塞(如丢包或超时):如果发送方检测到数据包丢失(例如没有收到确认),它会认为网络已经出现拥塞,此时
ssthresh会被调整为当前cwnd的一半,然后cwnd会重置为1 MSS,重新进入慢启动阶段
- 达到慢启动阈值
可见TCP连接使用cwnd限制两者发送的数据量的大小,并逐步“试探”两者传输数据速率的上限并增加传输的数据量。
在我们下载文件时,事实上是服务端在向我们发送文件,如果网络波动较大、不稳定,TCP连接机会一直将cwnd限制在一个较小的值,在单位时间内,服务端也无法向我们发送更大的数据量。
此时,如果我们使用多线程下载,和服务端建立多个TCP连接,这样即使每个TCP连接的cwnd较小,所有TCP连接加起来传输的数据量仍然可以占满我们的带宽。
2,Go代码实现
知道了HTTP的上述几个机制,相信大家就知道如何实现一个简单的多线程下载了!我们可以总结主要步骤如下:
- 发送
HEAD类型请求,通过Content-Length请求头获取待下载文件大小 - 根据给定的线程数量,结合待下载文件大小,确定每个线程下载的范围部分,也就是每个线程的
Range请求头字节范围 - 启动所有线程,使得每个线程下载它们对应的部分文件,并等待全部线程下载完成
- 合并每个线程下载的部分为最终文件
- 清理每个线程下载的文件部分
这里分别设计下列类(结构体),用于存放多线程下载时的传入参数和状态量:

上述ShardTask类表示一个线程的下载任务,其中会完成一个分片(文件的一部分)的下载请求操作,它有如下作为参数的属性:
Url下载的文件地址Order分片序号ShardFilePath这个分片文件的保存路径RangeStart和RangeEnd下载的文件起始范围和结束范围,用于设定Range请求头
此外,还有作为下载状态的属性:
DownloadSize下载任务进行时,这个线程已下载的文件部分大小TaskDone这个线程的下载任务是否完成
该类的成员方法如下:
DoShardGet执行分片下载任务,在其中会根据RangeStart和RangeEnd设定对应的HTTP请求头,发送请求并下载对应的文件部分
然后就是ParallelGetTask类,表示一整个多线程下载任务,其中包含了一个多线程下载任务的参数和状态量,并且实现了多线程下载的每个步骤,它有如下作为参数的属性:
Url文件的下载链接FilePath文件下载完成后的保存位置Concurrent下载并发数,即同时下载的分片数量TempFolder临时分片文件的保存文件夹
此外还有作为状态的属性:
TotalSize待下载文件的总大小ShardTaskList存储所有分片任务对象指针的列表
该类中的方法主要是分片下载的一些步骤如下:
getLength发送HEAD请求获取Content-Length以获取文件大小,获取后将其设定到TotalSize属性allocateTask根据给定的线程数和获取到的文件大小,计算每个线程下载的文件内容范围,并创建对应的ShardTask结构体放入ShardTaskList中downloadShard为每一个ShardTask对象创建一个线程(Goroutine)并在新的线程中调用ShardTask对象的下载分片方法,以启动所有线程的下载任务,并通过sync.WaitGroup来等待全部线程完成mergeFile下载完成后,合并每个分片为最终文件cleanShard合并完成后,清理下载的每个分片文件printTotalProcess这是一个附加的辅助方法,用于实时输出下载进度Run启动整个多线程下载任务,该函数是暴露的公开函数,其中对上述每个步骤函数进行了组织,按顺序调用执行
下面,我们来看一下它们的代码实现。
(1) ShardTask - 一个线程的下载任务
- package model
- import (
- "bufio"
- "fmt"
- "github.com/fatih/color"
- "io"
- "net/http"
- "os"
- "sync"
- )
- // 全局HTTP客户端
- var httpClient = http.Client{
- Transport: &http.Transport{
- // 关闭keep-alive确保一个线程就使用一个TCP连接
- DisableKeepAlives: true,
- },
- }
- // ShardTask 单个分片下载任务的任务参数和状态量
- type ShardTask struct {
- // 下载链接
- Url string
- // 分片序号,从1开始
- Order int
- // 这个分片文件的路径
- ShardFilePath string
- // 分片的起始范围(字节,包含)
- RangeStart int64
- // 分片的结束范围(字节,包含)
- RangeEnd int64
- // 已下载的部分(字节)
- DownloadSize int64
- // 该任务是否完成
- TaskDone bool
- }
- // NewShardTask 构造函数
- func NewShardTask(url string, order int, shardFilePath string, rangeStart int64, rangeEnd int64) *ShardTask {
- return &ShardTask{
- // 设定任务参数
- Url: url,
- Order: order,
- ShardFilePath: shardFilePath,
- RangeStart: rangeStart,
- RangeEnd: rangeEnd,
- // 初始化状态量
- DownloadSize: 0,
- TaskDone: false,
- }
- }
- // DoShardGet 开始下载这个分片(该方法在goroutine中执行)
- func (task *ShardTask) DoShardGet(waitGroup *sync.WaitGroup) {
- // 创建文件
- file, e := os.OpenFile(task.ShardFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
- if e != nil {
- color.Red("任务%d创建文件失败!", task.Order)
- color.HiRed("%s", e)
- return
- }
- // 准备请求
- request, e := http.NewRequest("GET", task.Url, nil)
- if e != nil {
- color.Red("任务%d创建请求出错!", task.Order)
- color.HiRed("%s", e)
- return
- }
- // 设定请求头
- request.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", task.RangeStart, task.RangeEnd))
- // 发送请求
- response, e := httpClient.Do(request)
- if e != nil {
- color.Red("任务%d发送下载请求出错!", task.Order)
- color.HiRed("%s", e)
- return
- }
- // 读取请求体
- body := response.Body
- // 读取缓冲区
- buffer := make([]byte, 8092)
- // 准备写入文件
- writer := bufio.NewWriter(file)
- for {
- // 读取一次内容至缓冲区
- readSize, readError := body.Read(buffer)
- if readError != nil {
- // 如果读取完毕则退出循环
- if readError == io.EOF {
- break
- } else {
- color.Red("任务%d读取响应错误!", task.Order)
- color.HiRed("%s", readError)
- return
- }
- }
- // 把缓冲区内容追加至文件
- _, writeError := writer.Write(buffer[0:readSize])
- if writeError != nil {
- color.Red("任务%d写入文件时出现错误!", task.Order)
- color.HiRed("%s", writeError)
- return
- }
- _ = writer.Flush()
- // 记录下载进度
- task.DownloadSize += int64(readSize)
- }
- // 关闭全部资源
- _ = body.Close()
- _ = file.Close()
- // 标记任务完成
- task.TaskDone = true
- // 使线程组中计数器-1
- waitGroup.Done()
- }
构造函数NewShardTask负责完成ShardTask的参数传入和状态量初始化,而DoShardGet方法实现了下载一个文件分片的完整步骤,从创建文件准备写入,到设定请求头,发出请求,最后读取响应体保存到文件。
此外,可见这里的http.Client对象中,我们将其DisableKeepAlives设为了true即关闭keep-alive,这是因为默认情况下Go语言的HTTP客户端会复用TCP连接,即使你多个线程发起请求,也会使用一个TCP连接进行。
而多线程下载需要每个线程持有一个单独的TCP连接来达到突破cwnd的限制,因此这里关闭keep-alive实现每个线程发起请求时,使用单独的TCP连接。
(2) ParallelGetTask - 一整个多线程下载任务
- package model
- import (
- "bufio"
- "fmt"
- "gitee.com/swsk33/shard-download-demo/util"
- "github.com/fatih/color"
- "io"
- "net/http"
- "os"
- "path/filepath"
- "strconv"
- "sync"
- "time"
- )
- // ParallelGetTask 多线程下载任务类,存放一个多线程下载任务的参数和状态量
- type ParallelGetTask struct {
- // 文件的下载链接
- Url string
- // 文件的最终保存位置
- FilePath string
- // 下载并发数
- Concurrent int
- // 下载的分片临时文件保存文件夹
- TempFolder string
- // 下载文件的总大小
- TotalSize int64
- // 全部的下载分片任务参数列表
- ShardTaskList []*ShardTask
- }
- // NewParallelGetTask 构造函数
- func NewParallelGetTask(url string, filePath string, concurrent int, tempFolder string) *ParallelGetTask {
- return &ParallelGetTask{
- // 参数赋值
- Url: url,
- FilePath: filePath,
- Concurrent: concurrent,
- TempFolder: tempFolder,
- // 初始化状态量
- TotalSize: 0,
- ShardTaskList: make([]*ShardTask, 0),
- }
- }
- // 发送HEAD请求获取待下载文件的大小
- func (task *ParallelGetTask) getLength() error {
- // 发送请求
- response, e := http.Head(task.Url)
- if e != nil {
- color.Red("发送HEAD请求出错!")
- return e
- }
- // 读取并设定长度
- task.TotalSize = response.ContentLength
- return nil
- }
- // 根据待下载文件的大小和设定的并发数,创建每个分片任务对象
- func (task *ParallelGetTask) allocateTask() {
- // 如果并发数大于总大小,则进行调整
- if int64(task.Concurrent) > task.TotalSize {
- task.Concurrent = int(task.TotalSize)
- }
- // 开始计算每个分片的下载范围
- eachSize := task.TotalSize / int64(task.Concurrent)
- // 创建任务对象
- for i := 0; i < task.Concurrent; i++ {
- task.ShardTaskList = append(task.ShardTaskList, NewShardTask(task.Url, i+1, filepath.Join(task.TempFolder, strconv.Itoa(i+1)), int64(i)*eachSize, int64(i+1)*eachSize-1))
- }
- // 处理末尾部分
- if task.TotalSize%int64(task.Concurrent) != 0 {
- task.ShardTaskList[task.Concurrent-1].RangeEnd = task.TotalSize - 1
- }
- }
- // 根据任务列表进行多线程分片下载操作
- func (task *ParallelGetTask) downloadShard() {
- // 创建线程组
- waitGroup := &sync.WaitGroup{}
- // 开始执行全部分片下载线程
- for _, task := range task.ShardTaskList {
- go task.DoShardGet(waitGroup)
- waitGroup.Add(1)
- }
- // 等待全部下载完成
- waitGroup.Wait()
- }
- // 下载完成后,合并分片文件
- func (task *ParallelGetTask) mergeFile() error {
- // 创建目的文件
- targetFile, e := os.OpenFile(task.FilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0755)
- if e != nil {
- color.Red("创建目标文件出错!")
- return e
- }
- // 创建写入器
- writer := bufio.NewWriter(targetFile)
- // 准备读取每个分片文件
- for _, shard := range task.ShardTaskList {
- shardFile, e := os.OpenFile(shard.ShardFilePath, os.O_RDONLY, 0755)
- if e != nil {
- color.Red("读取分片文件出错!")
- return e
- }
- reader := bufio.NewReader(shardFile)
- readBuffer := make([]byte, 1024*1024)
- for {
- // 读取每个分片文件,一次读取1KB
- readSize, readError := reader.Read(readBuffer)
- // 处理结束或错误
- if readError != nil {
- if readError == io.EOF {
- break
- } else {
- color.Red("读取分片文件出错!")
- return readError
- }
- }
- // 写入到最终合并的文件
- _, writeError := writer.Write(readBuffer[0:readSize])
- if writeError != nil {
- color.Red("写入合并文件出错!")
- return writeError
- }
- _ = writer.Flush()
- }
- // 关闭分片文件资源
- _ = shardFile.Close()
- }
- // 关闭目的文件资源
- _ = targetFile.Close()
- return nil
- }
- // 删除分片临时文件
- func (task *ParallelGetTask) cleanShard() error {
- for _, shard := range task.ShardTaskList {
- e := os.Remove(shard.ShardFilePath)
- if e != nil {
- color.Red("删除分片临时文件%s出错!", shard.ShardFilePath)
- return e
- }
- }
- return nil
- }
- // 在一个新线程中,实时输出每个分片的下载进度和总进度
- func (task *ParallelGetTask) printTotalProcess() {
- go func() {
- // 上一次统计时的已下载大小,用于计算速度
- var lastDownloadSize int64 = 0
- for {
- // 如果全部任务完成则结束输出,并统计并发数
- allDone := true
- // 当前并发数
- currentTaskCount := 0
- for _, shardTask := range task.ShardTaskList {
- if !shardTask.TaskDone {
- allDone = false
- currentTaskCount += 1
- }
- }
- if allDone {
- break
- }
- // 统计所有分片已下载大小之和
- var totalDownloadSize int64 = 0
- for _, shardTask := range task.ShardTaskList {
- totalDownloadSize += shardTask.DownloadSize
- }
- // 计算速度
- currentDownload := totalDownloadSize - lastDownloadSize
- lastDownloadSize = totalDownloadSize
- speedString := util.ComputeSpeed(currentDownload, 300)
- // 输出到控制台
- fmt.Printf("\r当前并发数:%3d 速度:%s 总进度:%3.2f%%", currentTaskCount, speedString, float32(totalDownloadSize)/float32(task.TotalSize)*100)
- // 等待300ms
- time.Sleep(300 * time.Millisecond)
- }
- }()
- }
- // Run 开始执行整个分片多线程下载任务
- func (task *ParallelGetTask) Run() error {
- // 获取文件大小
- e := task.getLength()
- if e != nil {
- color.Red("%s", e)
- return e
- }
- color.HiYellow("已获取到下载文件大小:%d字节", task.TotalSize)
- // 分配任务
- task.allocateTask()
- color.HiYellow("已完成分片任务分配,共计%d个任务", len(task.ShardTaskList))
- // 开启进度输出
- task.printTotalProcess()
- // 开始下载分片
- task.downloadShard()
- color.HiYellow("\n所有分片已下载完成!")
- // 开始合并文件
- e = task.mergeFile()
- if e != nil {
- color.Red("%s", e)
- return e
- }
- color.HiYellow("合并分片完成!")
- // 清理临时分片文件
- e = task.cleanShard()
- if e != nil {
- color.Red("%s", e)
- return e
- }
- color.HiYellow("清理分片临时文件完成!")
- color.Green("分片下载任务完成!")
- return nil
- }
上述printTotalProcess函数中,util.ComputeSpeed函数用于计算下载速度并自动转换为可读单位,代码如下:
- package util
- import (
- "fmt"
- "math"
- )
- // 关于单位的实用工具函数
- // ComputeSpeed 计算网络速度
- // size 一段时间内下载的数据大小,单位字节
- // timeElapsed 经过的时间长度,单位毫秒
- // 返回计算得到的网速,会自动换算单位
- func ComputeSpeed(size int64, timeElapsed int) string {
- bytePerSecond := size / int64(timeElapsed) * 1000
- if 0 <= bytePerSecond && bytePerSecond <= 1024 {
- return fmt.Sprintf("%4d Byte/s", bytePerSecond)
- }
- if bytePerSecond > 1024 && bytePerSecond <= int64(math.Pow(1024, 2)) {
- return fmt.Sprintf("%6.2f KB/s", float64(bytePerSecond)/1024)
- }
- if bytePerSecond > 1024*1024 && bytePerSecond <= int64(math.Pow(1024, 3)) {
- return fmt.Sprintf("%6.2f MB/s", float64(bytePerSecond)/math.Pow(1024, 2))
- }
- return fmt.Sprintf("%6.2f GB/s", float64(bytePerSecond)/math.Pow(1024, 3))
- }
可见通过构造函数NewParallelGetTask完成参数传递和状态量设定后,其它每个私有函数都对应我们多线程下载中的一个步骤,最后由公开函数Run统筹组织起所有的步骤,完成整个多线程下载任务。
3,实现效果
现在我们在main函数中创建一个ParallelGetTask对象,设定好参数后调用其Run方法即可开始多线程下载文件的任务:
- package main
- import (
- "gitee.com/swsk33/shard-download-demo/model"
- )
- func main() {
- // 创建任务
- task := model.NewParallelGetTask(
- "https://github.com/jgraph/drawio-desktop/releases/download/v24.7.17/draw.io-24.7.17-windows-installer.exe",
- "C:\\Users\\swsk33\\Downloads\\draw.io.exe",
- 64,
- "C:\\Users\\swsk33\\Downloads\\temp",
- )
- // 执行任务
- _ = task.Run()
- }
原生JS获取公网IP地址
2024/10/11 16:39 | by 刘新修 ]
- function getUserIP(callback) {
- var ip_dups = {};
- var RTCPeerConnection = window.RTCPeerConnection
- || window.mozRTCPeerConnection
- || window.webkitRTCPeerConnection;
- var useWebKit = !!window.webkitRTCPeerConnection;
- var mediaConstraints = {
- optional: [{RtpDataChannels: true}]
- };
- var servers = {
- iceServers: [
- {urls: "stun:stun.services.mozilla.com"},
- {urls: "stun:stun.l.google.com:19302"},
- ]
- };
- var pc = new RTCPeerConnection(servers, mediaConstraints);
- function handleCandidate(candidate){
- var ip_regex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/
- var hasIp = ip_regex.exec(candidate)
- if (hasIp) {
- var ip_addr = ip_regex.exec(candidate)[1];
- if(ip_dups[ip_addr] === undefined)
- callback(ip_addr);
- ip_dups[ip_addr] = true;
- }
- }
- pc.onicecandidate = function(ice){
- if(ice.candidate) {
- handleCandidate(ice.candidate.candidate);
- }
- };
- pc.createDataChannel("");
- pc.createOffer(function(result){
- pc.setLocalDescription(result, function(){}, function(){});
- }, function(){});
- setTimeout(function(){
- var lines = pc.localDescription.sdp.split('\n');
- lines.forEach(function(line){
- if(line.indexOf('a=candidate:') === 0)
- handleCandidate(line);
- });
- }, 1000);
- }
- getUserIP((ip) => {
- console.log("ipppp === ",ip)
- //ipppp === 121.90.11.160
- })
SHELL 脚本删除文件或目录
2024/09/27 17:28 | by 刘新修 ]
使用SHELL脚本,删除指定文件或目录,
使用方法:bash /sh/delfiles.sh 11.txt 22.txt
- #!/bin/bash
- # 检查是否有参数传入
- if [ "$#" -eq 0 ]; then
- echo "[ERROR] No files to delete! At least one parameter is required! "
- exit 1
- fi
- # 打印脚本名称
- echo "Script Name: $0"
- # 打印脚本所在的目录
- echo "Script Directory: $(dirname $0)"
- # 打印当前工作目录
- echo "Working Directory: $PWD"
- # 进入当前工作目录
- cd $PWD
- echo "=================================================="
- # 循环遍历所有参数并删除文件
- for file in "$@"; do
- if [ -f "$file" ]; then
- rm "$file"
- echo "Deleted file ==> ./$file"
- elif [ -d "$file" ]; then
- rm -rf "$file"
- echo "Deleted directory ==> ./$file"
- else
- echo "File or directory does not exist: $file"
- fi
- done
- echo "=================================================="
Vue 设置proxy后post请求失效,pending挂起
2024/08/16 14:31 | by 刘新修 ]
- proxy: {
- '/dev-api': {
- target: 'http://1x.xx.x.xxx:xxxx', //
- changeOrigin: true,
- pathRewrite: {
- '^/dev-api': ''
- },
- //第一种方法
- onProxyReq: function(proxyReq, req, res, options) {
- if (req.body) {
- const bodyData = JSON.stringify(req.body)
- // incase if content-type is application/x-www-form-urlencoded -> we need to change to application/json
- proxyReq.setHeader('Content-Type', 'application/json')
- proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData))
- // stream the content
- proxyReq.write(bodyData)
- }
- }
- }
- },
- //第二种方法,注释这行
- before: require('./mock/mock-server.js')
js的Blob以及和File、base64的转化
2024/08/02 11:27 | by 刘新修 ]
Blob是什么
在JavaScript中,Blob(二进制大对象)对象是一种用于表示原始数据的类文件对象,它存储了任意类型的数据,通常是二进制数据,但也可以包含文本数据或其他格式的数据。Blob对象通常用于在客户端浏览器中处理二进制数据,比如文件上传、数据传输等场景。(Blob对象可以将任意类型的数据转换为二进制形式进行存储和处理。这使得Blob对象成为处理各种数据类型的有效工具,包括文本、图像、音频、视频等。在JavaScript中,Blob对象提供了一种统一的接口来处理这些数据,无论是从用户上传的文件中获取数据,还是从其他来源获取数据,都可以使用Blob对象来进行二进制处理。)
Blob的用途
Blob对象常见的使用情况包括:
文件上传:当用户通过网页上传文件时,浏览器会创建一个Blob对象来表示上传的文件数据,然后可以将该Blob对象发送到服务器端进行处理。
数据传输:在客户端与服务器端进行数据交互时,可以使用Blob对象来存储和传输二进制数据,例如通过XMLHttpRequest或Fetch API发送Blob对象。
图像处理:在客户端对图像进行处理时,可以将图像数据存储在Blob对象中,并通过Canvas API等技术进行操作和显示。
媒体处理:在处理音频或视频等媒体数据时,可以使用Blob对象来存储和处理媒体数据。
和file文件流的关系
以下是Blob对象和File对象之间的联系和区别:
Blob对象:
Blob对象是二进制数据的容器,可以包含任意类型的数据,如文本、图像、音频或视频等。
可以通过构造函数new Blob(data, options)创建 Blob 对象,其中data是包含数据的数组或数组缓冲区,options是一个可选对象,用于指定 Blob 的 MIME 类型和结束符等信息。File对象:
File对象是Blob对象的一种扩展,通常用于表示用户系统中的文件,具有文件名和最后修改日期等属性。
可以通过new File(data, name, options)构造函数创建 File 对象,其中data是包含数据的数组或数组缓冲区,name是文件名,options是一个可选对象,用于指定文件的 MIME 类型和最后修改日期等信息。
联系与区别:
File对象是Blob对象的子类,因此所有可以对Blob对象执行的操作也同样适用于File对象。
File对象在表示文件时具有额外的元数据,如文件名和最后修改日期等。
在实际使用中,Blob对象通常用于处理二进制数据,而File对象则用于处理用户系统中的文件,如文件上传和操作。
因此,可以说File对象是Blob对象的一种特殊情况,用于在JavaScript中表示用户系统中的文件,并提供了额外的文件相关的属性。
Blob和File文件流,base64的转化
Blob对象转文件: 可以使用File构造函数将Blob对象转换为文件对象。File构造函数接受一个Blob对象和一个文件名作为参数,然后返回一个文件对象。
- var blob = new Blob(["blob data"], { type: "text/plain" });
- var file = new File([blob], "filename.txt");
Blob对象转Base64: 可以使用FileReader对象将Blob对象转换为Base64编码的字符串。FileReader对象可以读取Blob对象中的数据,并以指定的编码格式输出。
- var blob = new Blob(["blob data"], { type: "text/plain" });
- var reader = new FileReader();
- reader.readAsDataURL(blob);
- reader.onload = function () {
- var base64String = reader.result;
- };
Base64转Blob对象: 可以将Base64编码的字符串转换回Blob对象。这可以通过创建一个新的Blob对象并指定Base64字符串的数据类型完成。
- var base64String = "base64 data";
- var byteCharacters = atob(base64String);
- var byteNumbers = new Array(byteCharacters.length);
- for (var i = 0; i < byteCharacters.length; i++) {
- byteNumbers[i] = byteCharacters.charCodeAt(i);
- }
- var byteArray = new Uint8Array(byteNumbers);
- var blob = new Blob([byteArray], { type: "image/jpeg" });
js格式化iso 8601格式的日期为其他格式-处理默认golang time类型格式问题
2024/07/25 10:29 | by 刘新修 ]
golang time类型格式默认序列化为json的时候,是iso 8601格式
比如:2023-03-09T23:43:43+08:00
ISO 8601 格式的时间表示法,常用于表示世界范围内的时间和日期。ISO 8601 格式使用连字符 "-" 分隔日期和时间部分,并以字母 "T" 分隔日期和时间部分,其中 "T" 后面跟着时间部分。在这个时间表示中,"2023-03-09" 表示日期部分,"23:43:43+08:00" 表示时间部分和时区偏移量。其中,"+08:00" 表示相对于 UTC 时间偏移了 8 个小时,也就是北京时间。
//[ISO 8601标准中的日期和时间格式解析]
//2024-07-24T15:36:00+08:00 (要使用"+08:00",UTC时间偏移了8个小时,也就是北京时间:表示东八区)
//2024-07-24T15:36:00-05:00 (而"-05:00"则表示西五区的美国东部时间)
//其中"T"用来分割日期和时间,时间后面跟着的"-07:00"表示西七区,注意"-"是连字符,不是减号
//其中"Z"为UTC时区标识



JS - ISO 8601格式时间,转换时间戳方法
- /*** ISO日期时间戳转时间对象 ***/
- const isoString = '2021-03-25T15:00:00Z';
- const date = new Date(isoString);
- /*** 时间对象转ISO日期时间戳字符串 ***/
- const date = new Date();
- const isoString = date.toISOString();
JS时区转换方法
- //UTC时区时间
- var iso8601String = "2022-01-01T12:00:00.000Z";
- var date = new Date(iso8601String);
- console.log('date', date)
- //打印转换的时间戳
- var utcStr = date.toUTCString(); //转换为UTC字符串
- var ut_localTimeString = date.toLocaleString("en-US", {timeZone: "UTC", hour12: false}); //UTC等于伦敦0时区
- var uk_localTimeString = date.toLocaleString("en-US", {timeZone: "Europe/London", hour12: false}); //欧洲-伦敦
- var us_localTimeString = date.toLocaleString("en-US", {timeZone: "America/New_York", hour12: false}); //北美-纽约
- var cn_localTimeString = date.toLocaleString("en-US", {timeZone: "Asia/Shanghai", hour12: false}); //亚洲-上海
- //console.log('utcStr', utcStr)
- console.log('ut_localTimeString(UTC)', ut_localTimeString);
- console.log('uk_localTimeString(UK)', uk_localTimeString);
- console.log('us_localTimeString(US)', us_localTimeString);
- console.log('cn_localTimeString(CN)', cn_localTimeString);



如下JS格式化时间方法
- function formatDate(dateString, format = 'yyyy-MM-dd HH:mm:ss') {
- const date = new Date(dateString);
- const year = date.getFullYear();
- const month = String(date.getMonth() + 1).padStart(2, '0');
- const day = String(date.getDate()).padStart(2, '0');
- const hour = String(date.getHours()).padStart(2, '0');
- const minute = String(date.getMinutes()).padStart(2, '0');
- const second = String(date.getSeconds()).padStart(2, '0');
- const formattedDate = format
- .replace(/yyyy/g, year)
- .replace(/MM/g, month)
- .replace(/dd/g, day)
- .replace(/HH/g, hour)
- .replace(/mm/g, minute)
- .replace(/ss/g, second);
- return formattedDate;
- }
- // 示例用法
- console.log(formatDate('2022-03-09 23:43:43')); // 输出:2022-03-09 23:43:43
- console.log(formatDate('03/09/2022', 'yyyy年MM月dd日')); // 输出:2022年03月09日
- console.log(formatDate('09 Mar 2022 23:43:43 GMT', 'yyyy-MM-dd HH:mm:ss')); // 输出:2022-03-09 23:43:43
解决JS两数相加的精度问题
2024/07/11 08:40 | by 刘新修 ]
JS中小数相加存在的精度问题
- console.log(0.1+0.2);//0.30000000000000004
- console.log(0.2+0.7);//0.8999999999999999
解决办法
- <script>
- // 精确乘法计算
- function FloatMul(arg1, arg2) {
- var m = 0,
- s1 = arg1.toString(),//将第一个数据转换成字符出类型
- s2 = arg2.toString();//将第二个数据转换成字符出类型
- try {
- m += s1.split(".")[1].length;//截取数据的小数部分,得到小数位数
- } catch (e) {}
- try {
- m += s2.split(".")[1].length;//截取数据的小数部分,得到小数位数
- //将两个数据的小数位数长度相加
- } catch (e) {}
- var result = (Number(s1.replace(".", "")) * Number(s2.replace(".", ""))) / Math.pow(10, m);//将两个数扩大对应的小数位数倍转换成数字类型在相乘,乘完再除于对应的扩大倍数得到最终结果
- return isNaN(result) ? 0 : result;
- }
- //获得两个小数相加的精确值
- const accountAdd = (arg1, arg2) => {
- let r1, r2, m;
- try {
- r1 = arg1.toString().split(".")[1].length;//将第一个数据转换成字符出类型,截取数据的小数部分,得到小数位数
- } catch (e) {
- r1 = 0;
- }
- try {
- r2 = arg2.toString().split(".")[1].length;//将第一个数据转换成字符出类型,截取数据的小数部分,得到小数位数
- } catch (e) {
- r2 = 0;
- }
- m = Math.pow(10, Math.max(r1, r2));//取出得到的最长位数,将10扩大最长位数倍
- return (FloatMul(arg1, m) + FloatMul(arg2, m)) / m;
- };
- let sum = accountAdd(2.3443, -1.987);
- console.log(sum);//0.3573
- </script>
解决Chrome, NET::ERR_CERT_AUTHORITY_INVALID
2024/06/06 10:37 | by 刘新修 ]
MD5 加密之 java 与 js 实现
2024/01/09 16:45 | by 刘新修 ]
MD5 加密算法实际是一种信息摘要算法,其加密不可逆向解密;
其一般用作数据签名,来确保信息传输的完整性与安全性;
- 完整性:传输数据完整未丢失
- 安全性:数据未被恶意篡改
一、java 实现 MD5 加密
- public class MD5Util {
- /**
- * 对字符串md5加密
- *
- * @param str 传入要加密的字符串
- * @return MD5加密后的字符串(小写+字母)
- */
- public static String getMD5LowerCase(String str) {
- try {
- // 生成一个MD5加密计算摘要
- MessageDigest md = MessageDigest.getInstance("MD5");
- // 计算md5函数
- md.update(str.getBytes());
- // digest()最后确定返回md5 hash值,返回值为8为字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符
- // BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值
- return new BigInteger(1, md.digest()).toString(16);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
- /**
- * 对字符串md5加密
- *
- * @param str 传入要加密的字符串
- * @return MD5加密后的字符串(大写+数字)
- */
- public static String getMD5UpperCase(String s) {
- char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- 'A', 'B', 'C', 'D', 'E', 'F' };
- try {
- byte[] btInput = s.getBytes();
- // 获得MD5摘要算法的 MessageDigest 对象
- MessageDigest mdInst = MessageDigest.getInstance("MD5");
- // 使用指定的字节更新摘要
- mdInst.update(btInput);
- // 获得密文
- byte[] md = mdInst.digest();
- // 把密文转换成十六进制的字符串形式
- int j = md.length;
- char str[] = new char[j * 2];
- int k = 0;
- for (int i = 0; i < j; i++) {
- byte byte0 = md[i];
- str[k++] = hexDigits[byte0 >>> 4 & 0xf];
- str[k++] = hexDigits[byte0 & 0xf];
- }
- return new String(str);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
- public static void main(String[] args) {
- String str = "\"我+Admin1234~!@#¥%……&*()\"";
- System.out.println(str);
- //"我+Admin1234~!@#¥%……&*()"
- String s1 = getMD5LowerCase(str);
- System.out.println(s1);
- //6bb6f83c026357a15cdf12e5d6b2b310
- String s2 = getMD5UpperCase(str);
- System.out.println(s2);
- //6BB6F83C026357A15CDF12E5D6B2B310
- }
- }
测试
db06c78d1e24cf708a14ce81c9b617ec
DB06C78D1E24CF708A14CE81C9B617EC
二、js 实现 MD5 加密
使用第三方库 crypto-js,示例代码如下:
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="utf-8">
- <title>111</title>
- <style type="text/css">
- body{background:#2C91AE;}
- </style>
- </head>
- <body>
- </body>
- <script src="http://172.16.102.156/public/crypto/crypto-js.min.js"></script>
- <!-- 中国文书网MD5加密封装方法调用 -->
- <script type="text/javascript" src="md5.js"></script>
- <script type="text/javascript">
- /*** 刘新修本地md5加密方法封装(大写) ***/
- // function md5(string) {
- // function rotateLeft(lValue, iShiftBits) {
- // return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
- // }
- // function addUnsigned(lX, lY) {
- // var lX4, lY4, lX8, lY8, lResult;
- // lX8 = (lX & 0x80000000);
- // lY8 = (lY & 0x80000000);
- // lX4 = (lX & 0x40000000);
- // lY4 = (lY & 0x40000000);
- // lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
- // if (lX4 & lY4) {
- // return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
- // }
- // if (lX4 | lY4) {
- // if (lResult & 0x40000000) {
- // return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
- // } else {
- // return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
- // }
- // } else {
- // return (lResult ^ lX8 ^ lY8);
- // }
- // }
- // function f(x, y, z) {
- // return (x & y) | ((~x) & z);
- // }
- // function g(x, y, z) {
- // return (x & z) | (y & (~z));
- // }
- // function h(x, y, z) {
- // return (x ^ y ^ z);
- // }
- // function i(x, y, z) {
- // return (y ^ (x | (~z)));
- // }
- // function FF(a, b, c, d, x, s, ac) {
- // a = addUnsigned(a, addUnsigned(addUnsigned(f(b, c, d), x), ac));
- // return addUnsigned(rotateLeft(a, s), b);
- // }
- // function GG(a, b, c, d, x, s, ac) {
- // a = addUnsigned(a, addUnsigned(addUnsigned(g(b, c, d), x), ac));
- // return addUnsigned(rotateLeft(a, s), b);
- // }
- // function HH(a, b, c, d, x, s, ac) {
- // a = addUnsigned(a, addUnsigned(addUnsigned(h(b, c, d), x), ac));
- // return addUnsigned(rotateLeft(a, s), b);
- // }
- // function II(a, b, c, d, x, s, ac) {
- // a = addUnsigned(a, addUnsigned(addUnsigned(i(b, c, d), x), ac));
- // return addUnsigned(rotateLeft(a, s), b);
- // }
- // function convertToWordArray(string) {
- // var lWordCount;
- // var lMessageLength = string.length;
- // var lNumberOfWords_temp1 = lMessageLength + 8;
- // var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
- // var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
- // var lWordArray = new Array(lNumberOfWords - 1);
- // var lBytePosition = 0;
- // var lByteCount = 0;
- // while (lByteCount < lMessageLength) {
- // lWordCount = (lByteCount - (lByteCount % 4)) / 4;
- // lBytePosition = (lByteCount % 4) * 8;
- // lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));
- // lByteCount++;
- // }
- // lWordCount = (lByteCount - (lByteCount % 4)) / 4;
- // lBytePosition = (lByteCount % 4) * 8;
- // lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
- // lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
- // lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
- // return lWordArray;
- // }
- // function wordToHex(lValue) {
- // var WordToHexValue = "", WordToHexValue_temp = "", lByte, lCount;
- // for (lCount = 0; lCount <= 3; lCount++) {
- // lByte = (lValue >>> (lCount * 8)) & 255;
- // WordToHexValue_temp = "0" + lByte.toString(16);
- // WordToHexValueWordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
- // }
- // return WordToHexValue;
- // }
- // function utf8Encode(string) {
- // stringstring = string.replace(/\r\n/g, "\n");
- // var utftext = "";
- // for (var n = 0; n < string.length; n++) {
- // var c = string.charCodeAt(n);
- // if (c < 128) {
- // utftext += String.fromCharCode(c);
- // }
- // else if ((c > 127) && (c < 2048)) {
- // utftext += String.fromCharCode((c >> 6) | 192);
- // utftext += String.fromCharCode((c & 63) | 128);
- // }
- // else {
- // utftext += String.fromCharCode((c >> 12) | 224);
- // utftext += String.fromCharCode(((c >> 6) & 63) | 128);
- // utftext += String.fromCharCode((c & 63) | 128);
- // }
- // }
- // return utftext;
- // }
- // var x = [],
- // k, AA, BB, CC, DD, a, b, c, d,
- // S11 = 7, S12 = 12, S13 = 17, S14 = 22,
- // S21 = 5, S22 = 9 , S23 = 14, S24 = 20,
- // S31 = 4, S32 = 11, S33 = 16, S34 = 23,
- // S41 = 6, S42 = 10, S43 = 15, S44 = 21;
- // string = utf8Encode(string);
- // x = convertToWordArray(string);
- // // console.log(JSON.stringify(x))
- // // console.log(x.length)
- // a = 0x67452301;
- // b = 0xEFCDAB89;
- // c = 0x98BADCFE;
- // d = 0x10325476;
- // for (k = 0; k < x.length; k += 16) {
- // AA = a;
- // BB = b;
- // CC = c;
- // DD = d;
- // a = FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
- // d = FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
- // c = FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
- // b = FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
- // a = FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
- // d = FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
- // c = FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
- // b = FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
- // a = FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
- // d = FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
- // c = FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
- // b = FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
- // a = FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
- // d = FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
- // c = FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
- // b = FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
- // a = GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
- // d = GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
- // c = GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
- // b = GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
- // a = GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
- // d = GG(d, a, b, c, x[k + 10], S22, 0x2441453);
- // c = GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
- // b = GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
- // a = GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
- // d = GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
- // c = GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
- // b = GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
- // a = GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
- // d = GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
- // c = GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
- // b = GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
- // a = HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
- // d = HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
- // c = HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
- // b = HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
- // a = HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
- // d = HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
- // c = HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
- // b = HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
- // a = HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
- // d = HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
- // c = HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
- // b = HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
- // a = HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
- // d = HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
- // c = HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
- // b = HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
- // a = II(a, b, c, d, x[k + 0], S41, 0xF4292244);
- // d = II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
- // c = II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
- // b = II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
- // a = II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
- // d = II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
- // c = II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
- // b = II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
- // a = II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
- // d = II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
- // c = II(c, d, a, b, x[k + 6], S43, 0xA3014314);
- // b = II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
- // a = II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
- // d = II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
- // c = II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
- // b = II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
- // a = addUnsigned(a, AA);
- // b = addUnsigned(b, BB);
- // c = addUnsigned(c, CC);
- // d = addUnsigned(d, DD);
- // }
- // var temp = wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d);
- // return temp.toUpperCase();
- // };
- // console.log('刘新修本地封装/md5加密(--测试--)', md5('测试'))
- //"DB06C78D1E24CF708A14CE81C9B617EC"
- /*** 第三方类库CryptoJS.MD5()方法 || 和中国文书网测试结果一致(小写) ***/
- console.log('刘新修/CryptoJS.MD5()-加密(--测试--)\n', CryptoJS.MD5('测试').toString())
- </script>
- </html>
中网文书网/MD5加密(--测试--) db06c78d1e24cf708a14ce81c9b617ec
刘新修/CryptoJS.MD5()-加密(--测试--) db06c78d1e24cf708a14ce81c9b617ec
发布专属环境SHELL脚本
2023/10/13 17:06 | by 刘新修 ]
- #ifconfig
- echo $projectPath
- echo $developer
- echo $rebuildBranch
- ### 获取最后一层的项目名称
- las_dir=$(basename "$projectPath")
- ### 提取上一层目录的全路径
- cur_dir=$(dirname $projectPath)
- ### 获取业务线的名称第二层
- pat_dir=$(basename "$cur_dir")
- echo $las_dir
- echo $cur_dir
- echo $pat_dir
- ### 通用前端发布脚本( 从本地同步来取 dist.tar.gz 文件包 ) ###
- wwwroot=/data/apps/nginx/htdocs/public/frontend$projectPath &&
- cd $wwwroot && pwd && git fetch && git checkout dev-$developer &&
- git pull origin dev-$developer &&
- bash /sh/downdist.sh /data/apps/nginx/htdocs/public/frontend$projectPath/upload/dynamicAssets.json /data/apps/nginx/htdocs/public/tarfile/XXX/$developer$projectPath http://dl.ktm1234.com/XXX/$developer$projectPath
- ### 通用前端发布脚本( 从本地同步来取 dist.tar.gz 文件包 ) ###
- wwwroot=/data/apps/nginx/htdocs/public/frontend$projectPath &&
- cd $wwwroot && pwd
- if [[ $rebuildBranch == true ]] ;then
- echo '---- 全新构建分支 ------'
- git checkout . && git checkout master &&
- git branch -D dev-$developer && git fetch &&
- git checkout -b dev-$developer origin/dev-$developer &&
- bash /sh/downdist.sh /data/apps/nginx/htdocs/public/frontend$projectPath/upload/dynamicAssets.json /data/apps/nginx/htdocs/public/tarfile/XXX/$developer$projectPath http://dl.ktm1234.com/XXX/$developer$projectPath
- else
- echo '---- 更新新版本 ------'
- git fetch && git checkout dev-$developer &&
- git checkout . && git pull origin dev-$developer &&
- bash /sh/downdist.sh /data/apps/nginx/htdocs/public/frontend$projectPath/upload/dynamicAssets.json /data/apps/nginx/htdocs/public/tarfile/XXX/$developer$projectPath http://dl.ktm1234.com/XXX/$developer$projectPath
- fi










下载文件




