首页 > 编程语言 >aosp源码分析 5.0 BlockImageUpdateFn

aosp源码分析 5.0 BlockImageUpdateFn

时间:2023-09-06 11:34:35浏览次数:47  
标签:5.0 blocks tgt transfer list patch aosp 源码 data


block_image_update("/dev/block/bootdevice/by-name/system", package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat");
 
 
 
 
// args:
 
//    - block device (or file) to modify in-place
 
//    - transfer list (blob)
 
//    - new data stream (filename within package.zip)
 
//    - patch stream (filename within package.zip, must be uncompressed)
 
 
 
Value* BlockImageUpdateFn( 
 const 
   
 char 
 * name, State* state,  
 int 
   
 argc, Expr* argv[]) {
 
     
 Value* blockdev_filename;
 
     
 Value* transfer_list_value;
 
     
 char 
 * transfer_list = NULL;
 
     
 Value* new_data_fn;
 
     
 Value* patch_data_fn;
 
     
 bool 
   
 success =  
 false 
 ;
 
  
 
// 因为 
 block_image_update 
 中有类似p 
 ackage_extract_file("system.transfer.list")这种还需要执行
 
// 才能得到返回值的函数
 
// 在 
 ReadValueArgs 
 中利用va_list等C语言的可变参数宏,将block_image_update的四个输入参数
 
"/dev/block/bootdevice/by-name/system", package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat",分别赋值给 
 blockdev_filename 
 , 
 transfer_list_value 
 , 
 new_data_fn 
 , 
 patch_data_fn
 
     
 if 
   
 (ReadValueArgs(state, argv, 4, &blockdev_filename, &transfer_list_value,
 
                       
 &new_data_fn, &patch_data_fn) < 0) {
 
         
 return 
   
 NULL;
 
     
 }
 
 
 
     
 if 
   
 (blockdev_filename->type != VAL_STRING) {
 
         
 ErrorAbort(state,  
 "blockdev_filename argument to %s must be string" 
 , name);
 
         
 goto 
   
 done;
 
     
 }
 
 
 
 
//在 
 package_extract_file("system.transfer.list"), 
 中将type设为了VAL_BLOB
 
// 
 BLOB 
  (binary large object),二进制大对象,是一个可以存储二进制文件的容器 
  
 
BLOB 
 是一个大文件,典型的 
 BLOB 
 是一张图片或一个声音文件,由于它们的尺寸,必须使用特殊的方式来处理(例如:上传、下载或者存放到一个数据库)
 
     
 if 
   
 (transfer_list_value->type != VAL_BLOB) {
 
         
 ErrorAbort(state,  
 "transfer_list argument to %s must be blob" 
 , name);
 
         
 goto 
   
 done;
 
     
 }
 
     
 if 
   
 (new_data_fn->type != VAL_STRING) {
 
         
 ErrorAbort(state,  
 "new_data_fn argument to %s must be string" 
 , name);
 
         
 goto 
   
 done;
 
     
 }
 
     
 if 
   
 (patch_data_fn->type != VAL_STRING) {
 
         
 ErrorAbort(state,  
 "patch_data_fn argument to %s must be string" 
 , name);
 
         
 goto 
   
 done;
 
     
 }


 


这里的ui是 updater info的含义 ,而不是user interface )


UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
 
     
 FILE 
 * cmd_pipe = ui->cmd_pipe;
 
 
 
     
 ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
 
 
 
//patch_data_fn->data指向的是“ 
 system.patch.dat 
 ”这段字符串,而不是 
 ystem.patch.dat 
 这个文件的内容,
 
//因此patch_entry就代表内存中的zip安装包中的 
 system.patch.dat 
 这一项
 
     
 const 
   
 ZipEntry* patch_entry = mzFindZipEntry(za, patch_data_fn->data);
 
     
 if 
   
 (patch_entry == NULL) {
 
         
 ErrorAbort(state,  
 "%s(): no file \"%s\" in package" 
 , name, patch_data_fn->data);
 
         
 goto 
   
 done;
 
     
 }
 
 //计算出 
 patch_entry 
 的起始地址,因为注释中说patch stream must be uncompressed 

 
     
 uint8_t* patch_start = ((UpdaterInfo*)(state->cookie))->package_zip_addr +
 
         
 mzGetZipEntryOffset(patch_entry);
 
 
 
// 
 new_data_fn->data指向的数据是“ 
 system.new.dat 
 ”,而不是 
 ystem.new.dat 
 这个文件的内容,
 
     
 const 
   
 ZipEntry* new_entry = mzFindZipEntry(za, new_data_fn->data);
 
     
 if 
   
 (new_entry == NULL) {
 
         
 ErrorAbort(state,  
 "%s(): no file \"%s\" in package" 
 , name, new_data_fn->data);
 
         
 goto 
   
 done;
 
     
 }
 
 
 
     
 // The transfer list is a text file containing commands to
 
     
 // transfer data from one place to another on the target
 
     
 // partition.  We parse it and execute the commands in order:
 
     
 //
 
     
 //    zero [rangeset]
 
     
 //      - fill the indicated blocks with zeros
 
     
 //
 
     
 //    new [rangeset]
 
     
 //      - fill the blocks with data read from the new_data file
 
     
 //
 
     
 //    bsdiff patchstart patchlen [src rangeset] [tgt rangeset]
 
     
 //    imgdiff patchstart patchlen [src rangeset] [tgt rangeset]
 
     
 //      - read the source blocks, apply a patch, write result to
 
     
 //        target blocks.  bsdiff or imgdiff specifies the type of
 
     
 //        patch.
 
     
 //
 
     
 //    move [src rangeset] [tgt rangeset]
 
     
 //      - copy data from source blocks to target blocks (no patch
 
     
 //        needed; rangesets are the same size)
 
     
 //
 
     
 //    erase [rangeset]
 
     
 //      - mark the given blocks as empty
 
     
 //
 
     
 // The creator of the transfer list will guarantee that no block
 
     
 // is read (ie, used as the source for a patch or move) after it
 
     
 // has been written.
 
     
 //
 
     
 // Within one command the source and target ranges may overlap so
 
     
 // in general we need to read the entire source into memory before
 
     
 // writing anything to the target blocks.
 
     
 //
 
     
 // All the patch data is concatenated into one patch_data file in
 
     
 // the update package.  It must be stored uncompressed because we
 
     
 // memory-map it in directly from the archive.  (Since patches are
 
     
 // already compressed, we lose very little by not compressing
 
     
 // their concatenation.)
 
 
 
     
 pthread_t new_data_thread;
 
// we expand the new data from the archive in a
 
// background thread, and block that threads 'receive uncompressed
 
// data' function until the main thread has reached a point where we
 
// want some new data to be written. We signal the background thread
 
// with the destination for the data and block the main thread,
 
// waiting for the background thread to complete writing that section.
 
// Then it signals the main thread to wake up and goes back to
 
// blocking waiting for a transfer.
 
//
 
// NewThreadInfo is the struct used to pass information back and forth
 
// between the two threads. When the main thread wants some data
 
// written, it sets rss to the destination location and signals the
 
// condition. When the background thread is done writing, it clears
 
// rss and signals the condition again.
 

  typedef struct { 

 

  ZipArchive* za; 

 

  const ZipEntry* entry; 

 
 
 
 
RangeSinkState* rss; 

 
 
 
 

  pthread_mutex_t mu; 

 

  pthread_cond_t cv; 

 

  } NewThreadInfo; 

 
     
 NewThreadInfo nti;
 
     
 nti.za = za;
 
     
 nti.entry = new_entry;
 
     
 nti.rss = NULL; //先将rss标记置空
 
     
 pthread_mutex_init(&nti.mu, NULL); // 
 互斥锁的初始化
 
     
 pthread_cond_init(&nti.cv, NULL); // 
 创建一个条件变量,cv就是condition value的意思
 
  
 extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));


  其中cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指 针。结构 pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用, 默认值是 PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用


pthread_attr_t attr; // 
 线程具有属性,用 
 pthread_attr_t 
 表示,在对该结构进行处理之前必须进行初始化,我们用pthread_attr_init函数对其初始化, 
 用pthread_attr_destroy对其去除初始化
 
     
 pthread_attr_init(&attr);
 
//pthread_attr_setdetachstate  
 修改线程的分离状态属性,可以使用pthread_attr_setdetachstate函数把线程属性detachstate设置为下面的两个合法值之一:设置为PTHREAD_CREATE_DETACHED,以分离状态启动线程;或者设置为PTHREAD_CREATE_JOINABLE,正常启动线程。线程的分离状态决定一个线程以什么样的方式来终止自己。在默认情况下线程是非分离状态的,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
 
     
 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
 
//pthread_create的四个参数:1指向线程 
 标识符的指针 2 设置线程属性 3 线程运行函数的起始地址 4 运行函数的参数。 

 
     
 pthread_create(&new_data_thread, &attr, unzip_new_data, &nti);
 
 
 
     
 int 
   
 i, j;
 
 
 
     
 char 
 * linesave;
 
     
 char 
 * wordsave;
 
 
 
     
 int 
   
 fd = open(blockdev_filename->data, O_RDWR);
 
     
 if 
   
 (fd < 0) {
 
         
 ErrorAbort(state,  
 "failed to open %s: %s" 
 , blockdev_filename->data,  
 strerror 
 ( 
 errno 
 ));
 
         
 goto 
   
 done;
 
     
 }
 
 
 
     
 char 
 * line;
 
     
 char 
 * word;
 
 
 
     
 // The data in transfer_list_value is not necessarily
 
     
 // null-terminated, so we need to copy it to a new buffer and add
 
     
 // the null that strtok_r will need.
 

  // char* transfer_list = NULL; 

 
     
 transfer_list =  
 malloc 
 (transfer_list_value->size+1);
 
     
 if 
   
 (transfer_list == NULL) {
 
         
 fprintf 
 (stderr,  
 "failed to allocate %zd bytes for transfer list\n" 
 ,
 
                 
 transfer_list_value->size+1);
 
         
 exit 
 (1);
 
     
 }
 
//将system.transfer.list 
 文件的所有内容读取到了transfer_list中
 
     
 memcpy 
 (transfer_list, transfer_list_value->data, transfer_list_value->size);
 
     
 transfer_list[transfer_list_value->size] =  
 '\0' 
 ;
 
  // 
 按行分割读取 
 system.transfer.list 
 中的命令
 
     
 line = strtok_r(transfer_list,  
 "\n" 
 , &linesave);
 
 
 
     
 // first line in transfer list is the version number; currently
 
     
 // there's only version 1.
 
// recovery 5.0对应的api是1
 
     
 if 
   
 ( 
 strcmp 
 (line,  
 "1" 
 ) != 0) {
 
         
 ErrorAbort(state,  
 "unexpected transfer list version [%s]\n" 
 , line);
 
         
 goto 
   
 done;
 
     
 }
 
 
 
     
 // second line in transfer list is the total number of blocks we
 
     
 // expect to write.
 
     
 line = strtok_r(NULL,  
 "\n" 
 , &linesave);
 
     
 int 
   
 total_blocks =  
 strtol 
 (line, NULL, 0);
 
     
 // shouldn't happen, but avoid divide by zero.避免除以0
 
     
 if 
   
 (total_blocks == 0) ++total_blocks;
 
     
 int 
   
 blocks_so_far = 0;
 
 
 
     
 uint8_t* buffer = NULL;
 
     
 size_t 
   
 buffer_alloc = 0;
 
 
 
     
 // third and subsequent lines are all individual transfer commands.
 
// 在这个for循环中依次读取每行命令
 
     
 for 
   
 (line = strtok_r(NULL,  
 "\n" 
 , &linesave); line;
 
          
 line = strtok_r(NULL,  
 "\n" 
 , &linesave)) {
 
//  
 style代表每行前的命令名称,如 
 move 
 ,bsdiff等
 
         
 char 
 * style;
 
         
 style = strtok_r(line,  
 " " 
 , &wordsave);
 
 
 
         
 if 
   
 ( 
 strcmp 
 ( 
 "move" 
 , style) == 0) {
 
             
 word = strtok_r(NULL,  
 " " 
 , &wordsave);
 
             
 RangeSet* src = parse_range(word);
 
             
 word = strtok_r(NULL,  
 " " 
 , &wordsave);
 
             
 RangeSet* tgt = parse_range(word);
 
 
 
             
 printf 
 ( 
 "  moving %d blocks\n" 
 , src->size);
 
 
 
             
 allocate(src->size * BLOCKSIZE, &buffer, &buffer_alloc);
 
             
 size_t 
   
 p = 0;
 
             
 for 
   
 (i = 0; i < src->count; ++i) {
 
                 
 check_lseek(fd, (off64_t)src->pos[i*2] * BLOCKSIZE, SEEK_SET);
 
                 
 size_t 
   
 sz = (src->pos[i*2+1] - src->pos[i*2]) * BLOCKSIZE;
 
                 
 readblock(fd, buffer+p, sz);
 
                 
 p += sz;
 
             
 }
 
 
 
             
 p = 0;
 
             
 for 
   
 (i = 0; i < tgt->count; ++i) {
 
                 
 check_lseek(fd, (off64_t)tgt->pos[i*2] * BLOCKSIZE, SEEK_SET);
 
                 
 size_t 
   
 sz = (tgt->pos[i*2+1] - tgt->pos[i*2]) * BLOCKSIZE;
 
                 
 writeblock(fd, buffer+p, sz);
 
                 
 p += sz;
 
             
 }
 
 
 
             
 blocks_so_far += tgt->size;
 
             
 fprintf 
 (cmd_pipe,  
 "set_progress %.4f\n" 
 , ( 
 double 
 )blocks_so_far / total_blocks);
 
             
 fflush 
 (cmd_pipe);
 
 
 
             
 free 
 (src);
 
             
 free 
 (tgt);
 
 
 
         
 }  
 else 
   
 if 
   
 ( 
 strcmp 
 ( 
 "zero" 
 , style) == 0 ||
 
                    
 (DEBUG_ERASE &&  
 strcmp 
 ( 
 "erase" 
 , style) == 0)) {
 
             
 word = strtok_r(NULL,  
 " " 
 , &wordsave);
 
             
 RangeSet* tgt = parse_range(word);
 
 
 
             
 printf 
 ( 
 "  zeroing %d blocks\n" 
 , tgt->size);
 
 
 
             
 allocate(BLOCKSIZE, &buffer, &buffer_alloc);
 
             
 memset 
 (buffer, 0, BLOCKSIZE);
 
             
 for 
   
 (i = 0; i < tgt->count; ++i) {
 
                 
 check_lseek(fd, (off64_t)tgt->pos[i*2] * BLOCKSIZE, SEEK_SET);
 
                 
 for 
   
 (j = tgt->pos[i*2]; j < tgt->pos[i*2+1]; ++j) {
 
                     
 writeblock(fd, buffer, BLOCKSIZE);
 
                 
 }
 
             
 }
 
 
 
             
 if 
   
 (style[0] ==  
 'z' 
 ) {    
 // "zero" but not "erase"
 
                 
 blocks_so_far += tgt->size;
 
                 
 fprintf 
 (cmd_pipe,  
 "set_progress %.4f\n" 
 , ( 
 double 
 )blocks_so_far / total_blocks);
 
                 
 fflush 
 (cmd_pipe);
 
             
 }
 
 
 
             
 free 
 (tgt);
 
         
 }  
 else 
   
 if 
   
 ( 
 strcmp 
 ( 
 "new" 
 , style) == 0) {
 
 
 
             
 word = strtok_r(NULL,  
 " " 
 , &wordsave);
 
             
 RangeSet* tgt = parse_range(word);
 
 
 
             
 printf 
 ( 
 "  writing %d blocks of new data\n" 
 , tgt->size);
 
 
 
             
 RangeSinkState rss;
 
             
 rss.fd = fd;
 
             
 rss.tgt = tgt;
 
             
 rss.p_block = 0;
 
             
 rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE;
 
             
 check_lseek(fd, (off64_t)tgt->pos[0] * BLOCKSIZE, SEEK_SET);
 
 
 
             
 pthread_mutex_lock(&nti.mu);
 
             
 nti.rss = &rss;
 
             
 pthread_cond_broadcast(&nti.cv);
 
             
 while 
   
 (nti.rss) {
 
                 
 pthread_cond_wait(&nti.cv, &nti.mu);
 
             
 }
 
             
 pthread_mutex_unlock(&nti.mu);
 
 
 
             
 blocks_so_far += tgt->size;
 
             
 fprintf 
 (cmd_pipe,  
 "set_progress %.4f\n" 
 , ( 
 double 
 )blocks_so_far / total_blocks);
 
             
 fflush 
 (cmd_pipe);
 
 
 
             
 free 
 (tgt);
 
 
 
         
 }  
 else 
   
 if 
   
 ( 
 strcmp 
 ( 
 "bsdiff" 
 , style) == 0 ||
 
                    
 strcmp 
 ( 
 "imgdiff" 
 , style) == 0) {
 
             
 word = strtok_r(NULL,  
 " " 
 , &wordsave);
 
             
 size_t 
   
 patch_offset =  
 strtoul 
 (word, NULL, 0);
 
             
 word = strtok_r(NULL,  
 " " 
 , &wordsave);
 
             
 size_t 
   
 patch_len =  
 strtoul 
 (word, NULL, 0);
 
 
 
             
 word = strtok_r(NULL,  
 " " 
 , &wordsave);
 
             
 RangeSet* src = parse_range(word);
 
             
 word = strtok_r(NULL,  
 " " 
 , &wordsave);
 
             
 RangeSet* tgt = parse_range(word);
 
 
 
             
 printf 
 ( 
 "  patching %d blocks to %d\n" 
 , src->size, tgt->size);
 
 
 
             
 // Read the source into memory.
 
             
 allocate(src->size * BLOCKSIZE, &buffer, &buffer_alloc);
 
             
 size_t 
   
 p = 0;
 
             
 for 
   
 (i = 0; i < src->count; ++i) {
 
                 
 check_lseek(fd, (off64_t)src->pos[i*2] * BLOCKSIZE, SEEK_SET);
 
                 
 size_t 
   
 sz = (src->pos[i*2+1] - src->pos[i*2]) * BLOCKSIZE;
 
                 
 readblock(fd, buffer+p, sz);
 
                 
 p += sz;
 
             
 }
 
 
 
             
 Value patch_value;
 
             
 patch_value.type = VAL_BLOB;
 
             
 patch_value.size = patch_len;
 
             
 patch_value.data = ( 
 char 
 *)(patch_start + patch_offset);
 
 
 
             
 RangeSinkState rss;
 
             
 rss.fd = fd;
 
             
 rss.tgt = tgt;
 
             
 rss.p_block = 0;
 
             
 rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE;
 
             
 check_lseek(fd, (off64_t)tgt->pos[0] * BLOCKSIZE, SEEK_SET);
 
 
 
             
 if 
   
 (style[0] ==  
 'i' 
 ) {       
 // imgdiff
 
                 
 ApplyImagePatch(buffer, src->size * BLOCKSIZE,
 
                                 
 &patch_value,
 
                                 
 &RangeSinkWrite, &rss, NULL, NULL);
 
             
 }  
 else 
   
 {
 
                 
 ApplyBSDiffPatch(buffer, src->size * BLOCKSIZE,
 
                                  
 &patch_value, 0,
 
                                  
 &RangeSinkWrite, &rss, NULL);
 
             
 }
 
 
 
             
 // We expect the output of the patcher to fill the tgt ranges exactly.
 
             
 if 
   
 (rss.p_block != tgt->count || rss.p_remain != 0) {
 
                 
 fprintf 
 (stderr,  
 "range sink underrun?\n" 
 );
 
             
 }
 
 
 
             
 blocks_so_far += tgt->size;
 
             
 fprintf 
 (cmd_pipe,  
 "set_progress %.4f\n" 
 , ( 
 double 
 )blocks_so_far / total_blocks);
 
             
 fflush 
 (cmd_pipe);
 
 
 
             
 free 
 (src);
 
             
 free 
 (tgt);
 
         
 }  
 else 
   
 if 
   
 (!DEBUG_ERASE &&  
 strcmp 
 ( 
 "erase" 
 , style) == 0) {
 
             
 struct 
   
 stat st;
 
             
 if 
   
 (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode)) {
 
                 
 word = strtok_r(NULL,  
 " " 
 , &wordsave);
 
                 
 RangeSet* tgt = parse_range(word);
 
 
 
                 
 printf 
 ( 
 "  erasing %d blocks\n" 
 , tgt->size);
 
 
 
                 
 for 
   
 (i = 0; i < tgt->count; ++i) {
 
                     
 uint64_t range[2];
 
                     
 // offset in bytes
 
                     
 range[0] = tgt->pos[i*2] * (uint64_t)BLOCKSIZE;
 
                     
 // len in bytes
 
                     
 range[1] = (tgt->pos[i*2+1] - tgt->pos[i*2]) * (uint64_t)BLOCKSIZE;
 
 
 
                     
 if 
   
 (ioctl(fd, BLKDISCARD, &range) < 0) {
 
                         
 printf 
 ( 
 "    blkdiscard failed: %s\n" 
 ,  
 strerror 
 ( 
 errno 
 ));
 
                     
 }
 
                 
 }
 
 
 
                 
 free 
 (tgt);
 
             
 }  
 else 
   
 {
 
                 
 printf 
 ( 
 "  ignoring erase (not block device)\n" 
 );
 
             
 }
 
         
 }  
 else 
   
 {
 
             
 fprintf 
 (stderr,  
 "unknown transfer style \"%s\"\n" 
 , style);
 
             
 exit 
 (1);
 
         
 }
 
     
 }
 
 
 
     
 pthread_join(new_data_thread, NULL);
 
     
 success =  
 true 
 ;
 
 
 
     
 free 
 (buffer);
 
     
 printf 
 ( 
 "wrote %d blocks; expected %d\n" 
 , blocks_so_far, total_blocks);
 
     
 printf 
 ( 
 "max alloc needed was %zu\n" 
 , buffer_alloc);


   done:


free 
 (transfer_list);
 
     
 FreeValue(blockdev_filename);
 
     
 FreeValue(transfer_list_value);
 
     
 FreeValue(new_data_fn);
 
     
 FreeValue(patch_data_fn);
 
     
 return 
   
 StringValue(success ? strdup( 
 "t" 
 ) : strdup( 
 "" 
 ));
 
}

标签:5.0,blocks,tgt,transfer,list,patch,aosp,源码,data
From: https://blog.51cto.com/u_16248677/7384753

相关文章

  • 直播系统源码部署,高效文件管理与传输的FTP协议
    引言: 在直播系统源码部署的过程中,开发协议是支持直播系统源码功能技术搭建成功并发挥作用的关键之一,在直播系统源码的众多协议中,有一个协议可以帮助直播系统源码部署完成后用户进行媒体文件的上传、下载、管理等操作,这个协议就是FTP协议,本文就将具体介绍直播系统源码的FTP协议......
  • Metinfo5.0.4-任意文件包含漏洞复现
    目录文件包含漏洞描述:漏洞等级影响版本漏洞利用蚁剑连接图片马读取敏感目录读取php源码登录管理员界面http://127.0.0.1/MetInfo5.0.4/admin/上传一句话木马,或者图片马制作图片木马copy1.jpg/b+1.php/a2.jpg文件包含首页漏洞描述:MetInfo是一个专业的企业级CMS建......
  • 自适应红色大气虚拟手机靓号交易商城网站源码
       靓号虚拟商城源码跟之前发布的有点不同的就是,这个是用于做手机靓号交易平台网站的,   但从布局跟设计来看我更喜欢今天发的这个,UI看上去是两年前的流行样式,    但是只需要简单的修改下CSS就是一个大气的手机靓号交易平台网站,源码带手机版,以及手机靓号回收功能......
  • 自适应红色大气虚拟手机靓号交易商城网站源码
    源码分享:靓号虚拟商城源码跟之前发布的有点不同的就是,之前的是做靓号的二这个是用于做手机靓号交易平台网站的。但从布局跟设计来看我更喜欢今天发的这个,UI看上去是两年前的流行样式但是只需要简单的修改下CSS就是一个大气的手机靓号交易平台网站,源码带手机版,以及手机靓号回收功能......
  • ECshop仿顺丰优选综合购物商城平台源码旗舰版+团购+触屏版
    源码介绍:一款时尚简洁的综合通用类模板,整站宽屏,头部含多个下拉菜单、购物车及搜索功能,方便扫描;首页商品楼层功能,调用限时抢购和下期限时抢购功能,底部调用评论功能,添加了邮件订阅。二级分类页新增自定义组合筛选、商品排序、翻页等功能;商品详情页开发单选属性、数量加减、剩余件数、......
  • 【全套】源支付5.18最新版协议去授权全套三端开源源码_客户端+云端+监控+协议三网免挂
    推荐系统为:CentOS7.6Linux系统环境:Nginx1.20.1+MySQL5.6.50+PHP-7.2+Redis将商户后台源码上传解压运行目录为Public伪静态为thinkphp访问域名傻瓜模式安装后台安装完了sudorpm-Uvhhttps://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm完成后输......
  • 10分钟从源码级别搞懂AQS(AbstractQueuedSynchronizer)
    10分钟从源码级别搞懂AQS(AbstractQueuedSynchronizer)前言上篇文章15000字、6个代码案例、5个原理图让你彻底搞懂Synchronized有说到synchronized由objectmonitor实现的objectmonitor中由cxq栈和entrylist来实现阻塞队列,waitset实现等待队列,从而实现synchronized的等待/通知......
  • 【计算机毕业设计】英语单词小程序源码
    开发环境及工具:大等于jdk1.8,大于mysql5.5,idea(eclipse),微信开发者工具技术说明:springbootmybatishtmlvue.jsbootstrap小程序代码注释齐全,没有多余代码,适合学习(毕设),二次开发,包含论文技术相关文档。功能介绍:用户端:登录注册(含授权登录)首页显示搜索,单词列表,搜索可根据单词名称模糊......
  • 持币生息理财模式系统开发(源码搭建)
    持币生息钱包即代币持有者通过质押、投票、委托和锁定代币等行为获取区块奖励以及分红等收益。通俗一点讲,就是一种持币者“以币生币”的投资方式,有点类似于银行的储蓄生息(持币生息),所以我们通常叫它POS权益质押经济。区块链是什么意思?区块链的定义:区块链是一个共享的、不可改变的......
  • 医院影像科PACS/RIS系统源码 患者登记、图像采集、图像存储、报告产生
    医院PACS系统源码一套医学影像存储与传输系统,PACS部分主要提供医学影像获取、影像信息网络传递、大容量数据存储、影像显示和处理、影像打印等功能。RIS主要提供分诊登记、叫号、检查报告生成和打印等功能。本套影像存储与传输系统将二者进行无缝对接,提供了一个完整的集患者登记、......