TNS分析(修改版)

本文是上篇" TNSAnalysis " 的修改版。

参考文档:oracle_tns协议.doc ( http://download.csdn.net/detail/afer198215/4413480 )


建立连接时,oracle客户端先向oracle服务器 发送客户端可以使用的最高tns版本连接请求包,服务器会回应一个等于或小于客户端要求tns版本的响应包,

服务器回应包中的tns版本才是双方通信的真实tns版本。


上篇TNSAnalysis的代码分析oracle10以上TNS协议存在问题,本文做部分修改。

以下代码可以分析出 请求类型、连接请求中的TNS协议版本、连接请求字符串、sql请求字符串

支持tns v312(oracle 9)、tns v313(oracle 10)及部分tns v314(oralce 11,尚不确定sql解析是否正确)。

package org.sl.analysis.tns;

import org.sl.util.Utility;

/**
 * TNS分析接口
 * @author shanl
 *
 */
abstract
public class TNSAnalysis {
	/**TNS请求头标志长度8*/
	public static final int REQUEST_HEADER_LENGTH = 8;
	/**请求类型:连接*/
	public static final short REQUEST_TYPE_CONNECTION = 1;	
	/**请求类型:SQL*/
	public static final short REQUEST_TYPE_SQL = 6;	
	/**响应类型:接收*/
	public static final short RESPONSE_TYPE_RECEIVE = 2;
	
	/**请求类型:未知*/
	public static final short REQUEST_TYPE_UNKNOW = -1;
	
	protected byte[] requestData = null;
	protected int requestOffset = 0;
	protected int requestLen = 0; 
	
	/**
	 * 设置tns请求数据,并返回请求类型
	 * @param request 请求数据 
	 * @param offset 请求数据偏移量
	 * @param len 请求数据长度
	 * @return 返回请求类型
	 */
	public int setRequest(byte[] request, int offset, int len){
		this.requestData = request;
		this.requestOffset = offset;
		this.requestLen = len;
		return getRequestType();
	}
	
	/**
	 * 返回请求类型
	 * @return
	 */
	public int getRequestType(){
		return requestData[requestOffset+4];
	}
	
	/**
	 * 返回连接请求的tns版本<br/>
	 * 只有当getRequestType()返回值为REQUEST_TYPE_CONNECTION有效.
	 * @return 如果当前请求类型不是REQUEST_TYPE_CONNECTION,那么返回-1.
	 */
	public int getRequestTNSVersion(){
		int ver = -1;
		
		if(REQUEST_TYPE_CONNECTION == getRequestType()){
			ver = Utility.byte2Short(requestData, requestOffset+8);
		}
		
		return ver;
	}
	
	/**
	 * 返回连接请注字符串<br/>
	 * 只有当getRequestType()返回值为REQUEST_TYPE_CONNECTION有效.
	 * @return
	 */
	abstract public String getConnectData();
	
	/**
	 * 返回sql请求字符串
	 * 只有当getRequestType()返回值为REQUEST_TYPE_SQL有效.
	 * @return
	 */
	abstract public String getSqlData();
}


package org.sl.analysis.tns;

import org.sl.util.Utility;

/**
 * TNS版本313对应oracle 9.2
 * @author shanl
 *
 */
public class TNSV312 extends TNSAnalysis{

    @Override
    public String getConnectData() {
        if(0x01!=this.requestData[4]){
            return "";
        }
        
        int offset = REQUEST_HEADER_LENGTH;
//        int dataLen = (0xff00&(data[offset+16]<<2)) | (0x00ff&data[offset+17]);
//        int dataOffset = (0xff00&(data[offset+18]<<2)) | (0x00ff&data[offset+19]);
        int dataLen = Utility.byte2Short(requestData, offset+16);
        int dataOffset = Utility.byte2Short(requestData, offset+18);
        byte[] buffer = new byte[dataLen];
        int po = 0;
        
        if(dataLen<=0){
            return "";
        }
        
        for(int i=dataOffset,end=dataOffset+dataLen; i<end; i++,po++){
            if(0x00!=this.requestData[i]){
                buffer[po] = this.requestData[i];
            }else{
                buffer[po] = ' ';
            }
        }
        
//        return new String(data, dataOffset, dataLen);
        return new String(buffer,0,dataLen);
    }

    @Override
    public String getSqlData() {
        String sql = "";
        int offset = 8+4;
        
        if(0x03==requestData[10] && 0x5E==requestData[11]){
            sql = parse035e(8+2);
        }else if(0x11==requestData[10] && 0x69==requestData[11]){
            sql = parse1169(offset);
        }else if(0x11==requestData[10] && 0x6b==requestData[11]){
            sql = parse116b(offset);
        }else if(0x03==requestData[10] && 0x3E==requestData[11]){ //如果标志为01则skip=6*8
            sql = parse033e01(offset);
        }else if(0x03==requestData[10] && 0x47==requestData[11]){ //如果标志为01则skip=15*8+5
            sql = parse034701(offset);
        }else{
//            System.out.println("type:"+Integer.toHexString(data[10])+","+Integer.toHexString(data[11]));
        }
        
        return sql;
    }
    
    public String parse034701(int _offset){
        String sql = "";
        int offset = _offset+12;
        int skip = 6*8+1;
        
        if(0x01==requestData[_offset+5] || 0x02==requestData[_offset+5]){
            sql = parse03xx01(offset, skip);
        }else if(0x03==requestData[_offset+12]){
            sql = parse035e(offset);
        }else{
            sql = "";
        }
        
        return sql;
    }
    
    public String parse033e01(int _offset){
        String sql = "";
        int offset = _offset+12;
        int skip = 15*8+5;
        
        if(0x01==requestData[_offset+5] || 0x02==requestData[_offset+5]){
            sql = parse03xx01(offset, skip);
        }else if(0x03==requestData[_offset+12]){
            sql = parse035e(offset);
        }else{
            sql = "";
        }
        
        return sql;
    }
    
    private String parse03xx01(int offset, int skip){
        String sql = "";
        int c = offset;
        int len = 0;
        int position = 0;
        byte[] buffer = new byte[this.requestLen];
        
//        if(0x01==requestData[offset] || 0x02==requestData[offset]){        
//            //is sql
//        }else{
//            return "";
//        }
        
        c += skip;
        if((byte)0xFE == requestData[c]){            
            do{
                len = requestData[++c];                    

                for(int i=c+1,end=i+len; i<end; i++){
                    if(0x00 != requestData[i]){
                        buffer[position++] = requestData[i];
                    }else{
                        buffer[position++] = ' ';
                    }
                }

                c += len;
            }while(len!=0);                
        }else{//非大数量
            len = requestData[c];
            
            for(int i=c+1,end=i+len; i<end; i++){
                if(0x00 != requestData[i]){
                    buffer[position++] = requestData[i];
                }else{
                    buffer[position++] = ' ';
                }    
            }
        }    
        sql = new String(buffer, 0, position);
        
        return sql;
    }

    public String parse035e(int offset){
        String sql = "";
        byte[] buffer = new byte[this.requestLen];
        int skip = 81;
        int c = offset;
        int len = 0;
        int position = 0;
        
        if(0x03!=requestData[c++] || 0x5e!=requestData[c++]){
//            System.out.println("非sql请求.");
            return "";
        }
        
        c += skip;
        if((byte)0xFE == requestData[c]){            
            do{
                len = requestData[++c];                    

                for(int i=c+1,end=i+len; i<end; i++){
                    if(0x00 != requestData[i]){
                        buffer[position++] = requestData[i];
                    }else{
                        buffer[position++] = ' ';
                    }
                }

                c += len;
            }while(len!=0);                
        }else{//非大数量
            len = requestData[c];
            
            for(int i=c+1,end=i+len; i<end; i++){
                if(0x00 != requestData[i]){
                    buffer[position++] = requestData[i];
                }else{
                    buffer[position++] = ' ';
                }    
            }
        }    
        sql = new String(buffer, 0, position);
        
        return sql;
    }
    
    public String parse116b(int offset){
        int _offset = offset+1+12;
        return parse035e(_offset);        
    }
    
    public String parse1169(int offset){
        int _offset = offset+1+12;
        return parse035e(_offset);        
    }
}




package org.sl.analysis.tns;

import org.sl.util.Utility;

/**
 * TNS版本313对应oracle 10
 * @author shanl
 *
 */
public class TNSV313 extends TNSAnalysis{

    @Override
    public String getConnectData() {
        if(0x01!=this.requestData[4]){
            return "";
        }
        
        int offset = REQUEST_HEADER_LENGTH;
//        int dataLen = (0xff00&(data[offset+16]<<2)) | (0x00ff&data[offset+17]);
//        int dataOffset = (0xff00&(data[offset+18]<<2)) | (0x00ff&data[offset+19]);
        int dataLen = Utility.byte2Short(requestData, offset+16);
        int dataOffset = Utility.byte2Short(requestData, offset+18);
        byte[] buffer = new byte[dataLen];
        int po = 0;
        
        if(dataLen<=0){
            return "";
        }
        
        for(int i=dataOffset,end=dataOffset+dataLen; i<end; i++,po++){
            if(0x00!=this.requestData[i]){
                buffer[po] = this.requestData[i];
            }else{
                buffer[po] = ' ';
            }
        }
        
//        return new String(data, dataOffset, dataLen);
        return new String(buffer,0,dataLen);
    }

    @Override
    public String getSqlData() {
        String sql = "";
        int offset = 8+4;
        
        if(0x03==requestData[10] && 0x5E==requestData[11]){
            sql = parse035e(8+2);
        }else if(0x11==requestData[10] && 0x69==requestData[11]){
            sql = parse1169(offset);
        }else if(0x11==requestData[10] && 0x6b==requestData[11]){
            sql = parse116b(offset);
        }else if(0x03==requestData[10] && 0x3E==requestData[11]){ //如果标志为02则skip=6*8
            sql = parse033e01(offset);
        }else if(0x03==requestData[10] && 0x47==requestData[11]){ //如果标志为02则skip=15*8+5
            sql = parse034701(offset);
        }else{
//            System.out.println("type:"+Integer.toHexString(data[10])+","+Integer.toHexString(data[11]));
        }
        
        return sql;
    }
    
    public String parse034701(int _offset){
        String sql = "";
        int offset = _offset+12;
        int skip = 6*8+1;
        
        if(0x01==requestData[_offset+5] || 0x02==requestData[_offset+5]){
            sql = parse03xx01(offset, skip);
        }else if(0x03==requestData[_offset+12]){
            sql = parse035e(offset);
        }else{
            sql = "";
        }
        
        return sql;
    }
    
    public String parse033e01(int _offset){
        String sql = "";
        int offset = _offset+12;
        int skip = 15*8+5;
        
        if(0x01==requestData[_offset+5] || 0x02==requestData[_offset+5]){
            sql = parse03xx01(offset, skip);
        }else if(0x03==requestData[_offset+12]){
            sql = parse035e(offset);
        }else{
            sql = "";
        }
        
        return sql;
    }
    
    private String parse03xx01(int offset, int skip){
        String sql = "";
        int c = offset;
        int len = 0;
        int position = 0;
        byte[] buffer = new byte[this.requestLen];
        
//        if(0x01==requestData[offset] || 0x02==requestData[offset]){        
//            //is sql
//        }else{
//            return "";
//        }
        
        c += skip;
        if((byte)0xFE == requestData[c]){            
            do{
                len = requestData[++c];                    

                for(int i=c+1,end=i+len; i<end; i++){
                    if(0x00 != requestData[i]){
                        buffer[position++] = requestData[i];
                    }else{
                        buffer[position++] = ' ';
                    }
                }

                c += len;
            }while(len!=0);                
        }else{//非大数量
            len = requestData[c];
            
            for(int i=c+1,end=i+len; i<end; i++){
                if(0x00 != requestData[i]){
                    buffer[position++] = requestData[i];
                }else{
                    buffer[position++] = ' ';
                }    
            }
        }    
        sql = new String(buffer, 0, position);
        
        return sql;
    }

    public String parse035e(int offset){
        String sql = "";
        byte[] buffer = new byte[1024*1024*2];
        int skip = 93;
        int c = offset;
        int len = 0;
        int position = 0;
        
        if(0x03!=requestData[c++] || 0x5e!=requestData[c++]){
//            System.out.println("非sql请求.");
            return "";
        }
        
        c += skip;
        if((byte)0xFE == requestData[c]){            
            do{
                len = requestData[++c];                    

                for(int i=c+1,end=i+len; i<end; i++){
                    if(0x00 != requestData[i]){
                        buffer[position++] = requestData[i];
                    }else{
                        buffer[position++] = ' ';
                    }
                }

                c += len;
            }while(len!=0);                
        }else{//非大数量
            len = requestData[c];
            
            for(int i=c+1,end=i+len; i<end; i++){
                if(0x00 != requestData[i]){
                    buffer[position++] = requestData[i];
                }else{
                    buffer[position++] = ' ';
                }    
            }
        }    
        sql = new String(buffer, 0, position);
        
        return sql;
    }
    
    public String parse116b(int offset){
        int _offset = offset+1+12;
        return parse035e(_offset);        
    }
    
    public String parse1169(int offset){
        int _offset = offset+1+12;
        return parse035e(_offset);        
    }
}



package org.sl.analysis.tns;

import java.io.UnsupportedEncodingException;

import org.sl.util.Utility;

/**
 * TNS版本314对应oracle 11
 * @author shanl
 *
 */
public class TNSV314  extends TNSAnalysis{
	@Override
	public String getConnectData() {
		if(0x01!=this.requestData[4]){
			return "";
		}
		
		int offset = REQUEST_HEADER_LENGTH;
		int dataLen = Utility.byte2Short(requestData, offset+16);
		int dataOffset = Utility.byte2Short(requestData, offset+18)+10;
		byte[] buffer = new byte[dataLen];
		int po = 0;
		
		if(dataLen<=0){
			return "";
		}
		
		for(int i=dataOffset,end=dataOffset+dataLen; i<end; i++,po++){
			if(0x00!=this.requestData[i]){
				buffer[po] = this.requestData[i];
			}else{
				buffer[po] = ' ';
			}
		}
		
//		return new String(data, dataOffset, dataLen);
		try {
			return new String(buffer,0,dataLen, "ISO-8859-1");
		} catch (UnsupportedEncodingException e) {
			return new String(buffer,0,dataLen);			
		}
	}

	@Override
	public String getSqlData(){
		throw new RuntimeException("未完成.");
	}
	
	public String getSqlDataV313() {
		String sql = "";
		int offset = 8+4;
		
		if(0x03==requestData[10] && 0x5E==requestData[11]){
			sql = parse035e(8+2);
		}else if(0x11==requestData[10] && 0x69==requestData[11]){
			sql = parse1169(offset);
		}else if(0x11==requestData[10] && 0x6b==requestData[11]){
			sql = parse116b(offset);
		}else{
//			System.out.println("type:"+Integer.toHexString(data[10])+","+Integer.toHexString(data[11]));
		}
		
		return sql;
	}

	public String parse035e(int offset){
		String sql = "";
		byte[] buffer = new byte[1024*1024*2];
		int skip = 93;
		int c = offset;
		int len = 0;
		int position = 0;
		
		if(0x03!=requestData[c++] || 0x5e!=requestData[c++]){
//			System.out.println("非sql请求.");
			return "";
		}
		
		c += skip;
		if((byte)0xFE == requestData[c]){			
			do{
				len = requestData[++c];					

				for(int i=c+1,end=i+len; i<end; i++){
					if(0x00 != requestData[i]){
						buffer[position++] = requestData[i];
					}else{
						buffer[position++] = ' ';
					}
				}

				c += len;
			}while(len!=0);				
		}else{//非大数量
			len = requestData[c];
			
			for(int i=c+1,end=i+len; i<end; i++){
				if(0x00 != requestData[i]){
					buffer[position++] = requestData[i];
				}else{
					buffer[position++] = ' ';
				}	
			}
		}	
		sql = new String(buffer, 0, position);
		
		return sql;
	}
	
	public String parse116b(int offset){
		int _offset = offset+1+12;
		return parse035e(_offset);		
	}
	
	public String parse1169(int offset){
		int _offset = offset+1+12;
		return parse035e(_offset);		
	}
}


package org.sl.analysis.tns;

/**
 * tns V312+V313
 * @author shanl
 *
 */
public class TNSV312_V313  extends TNSAnalysis{
	TNSAnalysis v312 = new TNSV312();
	TNSAnalysis v313 = new TNSV313();

	@Override
	public String getConnectData() {
		String sql = "";
		
		sql = v312.getConnectData();
		if("".equals(sql)){
			sql = v313.getConnectData();
		}
		
		return sql;
	}

	@Override
	public String getSqlData() {
		String sql = "";
		
		sql = v312.getSqlData();
		if("".equals(sql)){
			sql = v313.getSqlData();
		}
		
		return sql;
	}
	
}


package org.sl.analysis.tns;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.sl.util.Utility;

/**
 * TNS分析器的工厂类
 * @author shanl
 *
 */
public class TNSAnalyFactory{
    /**TNS版本312,对应oracle 9*/
    public static final short TNS_VERSION_312 = 312;    
    /**TNS版本313,对应oracle 10*/
    public static final short TNS_VERSION_313 = 313;    
    /**TNS版本314,对应oracle 11*/
    public static final short TNS_VERSION_314 = 314;
    /**未知的tns版本*/
    public static final short TNS_VERSION_UNKNOW = -1;
    
    private static Map<Short,Class<? extends TNSAnalysis>> dict = null;
    
    static{
        dict = new HashMap<Short,Class<? extends TNSAnalysis>>(20);
        dict.put(TNS_VERSION_UNKNOW, TNSV312_V313.class);
        dict.put(TNS_VERSION_312, TNSV312.class);
        dict.put(TNS_VERSION_313, TNSV313.class);
        dict.put(TNS_VERSION_314, TNSV314.class);
    }
    
    /**
     * 注册一个指定版本的tns分析类
     * @param tnsVersion
     * @param analyClass
     */
    synchronized
    public static void registerTNSAnalyClass(short tnsVersion, Class<? extends TNSAnalysis> analyClass){
        if(null==analyClass){
            return;
        }
        dict.put(tnsVersion, analyClass);
    }
    
    /**
     * 移除一个指定版本的tns分析类
     * @param tnsVersion
     * @return
     */
    synchronized
    public static Class<? extends TNSAnalysis> removeTNSAnalyClass(short tnsVersion){
        return dict.remove(tnsVersion);
    }
    
    /**
     * 得到一个当前已注册类合集的复本
     * @return 不会返回null
     */
    synchronized
    public static Map<Short,Class<? extends TNSAnalysis>> duplicate(){
        Map<Short,Class<? extends TNSAnalysis>> nd = new HashMap<Short,Class<? extends TNSAnalysis>>(20);
        Set<Short> keys = dict.keySet();
        
        if(keys.isEmpty()){
            return nd;
        }
        
        for(Short key:keys){
            nd.put(key, dict.get(key));
        }
        
        return nd;
    }
    
    /**
     * 返回指定版本的分析类实例
     * @param tnsVersion
     * @return 如果找不到则返回v312+v313;否则,返回指定tns版本分析类实例.
     */
    synchronized
    public static TNSAnalysis getTNSAnalyInstance(Short tnsVersion){
        TNSAnalysis instance = null;
        Class<? extends TNSAnalysis> cs = null;
        
        try{
            if(dict.containsKey(tnsVersion)){
                cs = dict.get(tnsVersion);
                instance = cs.newInstance();
            }else{
                instance = new TNSV312_V313();
            }
        }catch(Exception ex){
            throw new RuntimeException(ex.getMessage());
        }
        
        return instance;
    }
    
    /**
     * 从请求中得到tns版本
     * @param requestData
     * @param offset     
     * @return 返回-1表示不是含有tns版本的请求
     */
    synchronized
    public static short getTNSVersionFromRequest(byte[] requestData, int offset){
        short ver = -1;
        
        if(TNSAnalysis.REQUEST_TYPE_CONNECTION==requestData[offset+4]){
            ver = Utility.byte2Short(requestData, offset+8);
        }
        
        return ver;
    }
    
    /**
     * 从响应中得到tns版本
     * @param responseData
     * @param offset    
     * @return 返回-1表示不是含有tns版本的响应
     */
    synchronized
    public static short getTNSVersionFromResponse(byte[] responseData, int offset){
        short ver = -1;
        
        if(TNSAnalysis.RESPONSE_TYPE_RECEIVE==responseData[offset+4]){
            ver = Utility.byte2Short(responseData, offset+8);
        }
        
        return ver;
    }    
}



测试类:

package test;

import org.sl.analysis.tns.TNSAnalyFactory;
import org.sl.analysis.tns.TNSAnalysis;
import org.sl.analysis.tns.TNSV314;

public class Test6 {
	public static void main(String[] args){
//		t3();
		t2();
//		t1();
	}
	
	static void t3(){
		String hexStr = ""
			+"00F00000010000000138012C000008007FFF860E0000010000B6003A000"
			+"002006161000000000000000000000D4C0001A4D8000000000000000028"
			+"4445534352495054494F4E3D28414444524553533D2850524F544F434F4"
			+"C3D5443502928484F53543D3139322E3136382E322E32332928504F5254"
			+"3D31353231292928434F4E4E4543545F444154413D28534552564943455"
			+"F4E414D453D697032356F72636C29284349443D2850524F4752414D3D443"
			+"A5C746F6F6C735C504C53514C20446576656C6F7065725C504C53514C446"
			+"5762E6578652928484F53543D504336372928555345523D7368616E6C29292929";			
			
		byte[] bys = new byte[hexStr.length()/2];
		int hex = 0;
		int c = 0;
		int len = 0;
//		TDSAnalyForSybase tds = new TDSAnalyForSybase();
		 
		
		for(int i=0,j=0,endi=hexStr.length(); i<endi; j++){
			hex = Integer.valueOf(hexStr.substring(i, i+=2), 16);
			bys[j] = (byte)hex;			
		}
		
		short tnsVer = TNSAnalyFactory.getTNSVersionFromRequest(bys, 0);
		System.out.println(tnsVer);
		TNSAnalysis sis = TNSAnalyFactory.getTNSAnalyInstance(tnsVer);
		sis.setRequest(bys, 0, bys.length);
		System.out.println("request:"+sis.getConnectData());
		
	}
	
	static void t2(){
		String hexStr = ""
//			+"00200000020000000139000108007FFF01000000002061410000000000000000";	
			+"00200000020000000138000008007FFF01000000002061410000000000000000";
			
		byte[] bys = new byte[hexStr.length()/2];
		int hex = 0;
		int c = 0;
		int len = 0;
//		TDSAnalyForSybase tds = new TDSAnalyForSybase();
		 
		
		for(int i=0,j=0,endi=hexStr.length(); i<endi; j++){
			hex = Integer.valueOf(hexStr.substring(i, i+=2), 16);
			bys[j] = (byte)hex;			
		}
		
		short tnsVer = TNSAnalyFactory.getTNSVersionFromResponse(bys, 0);
		System.out.println(tnsVer);
	}
	
	static void t1(){
		String hexStr = ""
			+"003A000001000000013A012C004120007FFFC60E0000010000EF003A00"
			+"0008006161000000000000000000000000000000000000000000000000"
			+"00F90000060000000000284445534352495054494F4E3D28434F4E4E45"
			+"43545F444154413D285345525645523D44454449434154454429285345"
			+"52564943455F4E414D453D697032356F72636C29284349443D2850524F"
			+"4752414D3D433A5C446F63756D656E74733F616E643F53657474696E67"
			+"735C41646D696E6973747261746F725C3F3F3F3F5C506C53716C446576"
			+"2E6578652928484F53543D4E44544B4D4B574C38455954503542292855"
			+"5345523D41646D696E6973747261746F7229292928414444524553533D"
			+"2850524F544F434F4C3D5443502928484F53543D3139322E3136382E32"
			+"2E32332928504F52543D31353231292929";
			
			
		byte[] bys = new byte[hexStr.length()/2];
		int hex = 0;
		int c = 0;
		int len = 0;
//		TDSAnalyForSybase tds = new TDSAnalyForSybase();
		 
		
		for(int i=0,j=0,endi=hexStr.length(); i<endi; j++){
			hex = Integer.valueOf(hexStr.substring(i, i+=2), 16);
			bys[j] = (byte)hex;			
		}
		TNSAnalyFactory.registerTNSAnalyClass((short) 314, TNSV314.class);
		short tnsVer = TNSAnalyFactory.getTNSVersionFromRequest(bys, 0);
		System.out.println("tns version:"+tnsVer);
		TNSAnalysis sis = TNSAnalyFactory.getTNSAnalyInstance(tnsVer);
		sis.setRequest(bys, 0, bys.length);
		System.out.println("connect:"+sis.getConnectData());
	}
}


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值