Skip to content
项目
群组
代码片段
帮助
正在加载...
帮助
为 GitLab 提交贡献
登录/注册
切换导航
H
h2database
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
分枝图
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
计划
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
分枝图
统计图
创建新议题
作业
提交
议题看板
打开侧边栏
Administrator
h2database
Commits
9eb213a0
提交
9eb213a0
authored
1月 04, 2013
作者:
Thomas Mueller
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
MVStore: encrypted stores are now supported - table engine
上级
ecbc84d5
全部展开
隐藏空白字符变更
内嵌
并排
正在显示
9 个修改的文件
包含
72 行增加
和
391 行删除
+72
-391
Database.java
h2/src/main/org/h2/engine/Database.java
+4
-0
DataUtils.java
h2/src/main/org/h2/mvstore/DataUtils.java
+1
-1
MVStore.java
h2/src/main/org/h2/mvstore/MVStore.java
+10
-13
Page.java
h2/src/main/org/h2/mvstore/Page.java
+18
-10
MVTableEngine.java
h2/src/main/org/h2/mvstore/db/MVTableEngine.java
+8
-0
FilePathCrypt.java
h2/src/main/org/h2/store/fs/FilePathCrypt.java
+6
-5
TestMVTableEngine.java
h2/src/test/org/h2/test/store/TestMVTableEngine.java
+22
-0
TestFileSystem.java
h2/src/test/org/h2/test/unit/TestFileSystem.java
+3
-6
FilePathCrypt.java
h2/src/tools/org/h2/dev/fs/FilePathCrypt.java
+0
-356
没有找到文件。
h2/src/main/org/h2/engine/Database.java
浏览文件 @
9eb213a0
...
@@ -2402,4 +2402,8 @@ public class Database implements DataHandler {
...
@@ -2402,4 +2402,8 @@ public class Database implements DataHandler {
throw
DbException
.
throwInternalError
();
throw
DbException
.
throwInternalError
();
}
}
public
byte
[]
getFilePasswordHash
()
{
return
filePasswordHash
;
}
}
}
h2/src/main/org/h2/mvstore/DataUtils.java
浏览文件 @
9eb213a0
...
@@ -512,7 +512,7 @@ public class DataUtils {
...
@@ -512,7 +512,7 @@ public class DataUtils {
for
(
int
i
=
0
,
size
=
s
.
length
();
i
<
size
;)
{
for
(
int
i
=
0
,
size
=
s
.
length
();
i
<
size
;)
{
int
startKey
=
i
;
int
startKey
=
i
;
i
=
s
.
indexOf
(
':'
,
i
);
i
=
s
.
indexOf
(
':'
,
i
);
checkArgument
(
i
>
0
,
"Not a map"
);
checkArgument
(
i
>
=
0
,
"Not a map"
);
String
key
=
s
.
substring
(
startKey
,
i
++);
String
key
=
s
.
substring
(
startKey
,
i
++);
StringBuilder
buff
=
new
StringBuilder
();
StringBuilder
buff
=
new
StringBuilder
();
while
(
i
<
size
)
{
while
(
i
<
size
)
{
...
...
h2/src/main/org/h2/mvstore/MVStore.java
浏览文件 @
9eb213a0
...
@@ -24,7 +24,7 @@ import org.h2.mvstore.cache.CacheLongKeyLIRS;
...
@@ -24,7 +24,7 @@ import org.h2.mvstore.cache.CacheLongKeyLIRS;
import
org.h2.mvstore.cache.FilePathCache
;
import
org.h2.mvstore.cache.FilePathCache
;
import
org.h2.mvstore.type.StringDataType
;
import
org.h2.mvstore.type.StringDataType
;
import
org.h2.store.fs.FilePath
;
import
org.h2.store.fs.FilePath
;
import
org.h2.store.fs.FilePathCrypt
2
;
import
org.h2.store.fs.FilePathCrypt
;
import
org.h2.store.fs.FileUtils
;
import
org.h2.store.fs.FileUtils
;
import
org.h2.util.MathUtils
;
import
org.h2.util.MathUtils
;
import
org.h2.util.New
;
import
org.h2.util.New
;
...
@@ -43,27 +43,21 @@ H:3,...
...
@@ -43,27 +43,21 @@ H:3,...
TODO:
TODO:
- file system encryption (
- automated 'kill process' and 'power failure' test
test and document speed,
- test stream store if data doesn't fit in memory
support un-aligned operations,
test other algorithms)
- mvcc with multiple transactions
- mvcc with multiple transactions
- update checkstyle
- update checkstyle
- automated 'kill process' and 'power failure' test
- maybe split database into multiple files, to speed up compact
- maybe split database into multiple files, to speed up compact
- auto-compact from time to time and on close
- auto-compact from time to time and on close
- test and possibly improve compact operation (for large dbs)
- test and possibly improve compact operation (for large dbs)
- performance test with encrypting file system
- possibly split chunk data into immutable and mutable
- possibly split chunk data into immutable and mutable
- compact: avoid processing pages using a counting bloom filter
- compact: avoid processing pages using a counting bloom filter
- defragment (re-creating maps, specially those with small pages)
- defragment (re-creating maps, specially those with small pages)
- remove DataType.getMaxLength (use ByteArrayOutputStream or getMemory)
- remove DataType.getMaxLength (use ByteArrayOutputStream or getMemory)
- chunk header: store changed chunk data as row; maybe after the root
- chunk header: store changed chunk data as row; maybe after the root
- chunk checksum (header, last page, 2 bytes per page?)
- chunk checksum (header, last page, 2 bytes per page?)
- file locking: solve problem that locks are shared for a VM
- store file "header" at the end of each chunk; at the end of the file
- is there a better name for the file header,
- is there a better name for the file header,
-- if it's no longer always at the beginning of a file?
-- if it's no longer always at the beginning of a file?
store header?
- on insert, if the child page is already full, don't load and modify it
- on insert, if the child page is already full, don't load and modify it
-- split directly (for leaves with 1 entry)
-- split directly (for leaves with 1 entry)
- maybe let a chunk point to possible next chunks
- maybe let a chunk point to possible next chunks
...
@@ -80,7 +74,7 @@ TODO:
...
@@ -80,7 +74,7 @@ TODO:
- chunk metadata: do not store default values
- chunk metadata: do not store default values
- support maps without values (just existence of the key)
- support maps without values (just existence of the key)
- support maps without keys (counted b-tree features)
- support maps without keys (counted b-tree features)
- use a small object cache (StringCache)
- use a small object cache (StringCache)
, test on Android
- dump values
- dump values
- tool to import / manipulate CSV files (maybe concurrently)
- tool to import / manipulate CSV files (maybe concurrently)
- map split / merge (fast if no overlap)
- map split / merge (fast if no overlap)
...
@@ -96,7 +90,10 @@ TODO:
...
@@ -96,7 +90,10 @@ TODO:
-- to support concurrent updates and writes, and very large maps
-- to support concurrent updates and writes, and very large maps
- implement an off-heap file system
- implement an off-heap file system
- remove change cursor, or add support for writing to branches
- remove change cursor, or add support for writing to branches
- file encryption / decryption using multiple threads
- file encryption: try using multiple threads
- file encryption: support un-aligned operations
- file encryption: separate algorithm/key for tweak
- file encryption: add a fast (insecure) algorithm
*/
*/
...
@@ -408,7 +405,7 @@ public class MVStore {
...
@@ -408,7 +405,7 @@ public class MVStore {
file
=
f
.
open
(
readOnly
?
"r"
:
"rw"
);
file
=
f
.
open
(
readOnly
?
"r"
:
"rw"
);
if
(
filePassword
!=
null
)
{
if
(
filePassword
!=
null
)
{
byte
[]
password
=
DataUtils
.
getPasswordBytes
(
filePassword
);
byte
[]
password
=
DataUtils
.
getPasswordBytes
(
filePassword
);
file
=
new
FilePathCrypt
2
.
FileCrypt2
(
password
,
file
);
file
=
new
FilePathCrypt
.
FileCrypt2
(
password
,
file
);
}
}
file
=
FilePathCache
.
wrap
(
file
);
file
=
FilePathCache
.
wrap
(
file
);
if
(
readOnly
)
{
if
(
readOnly
)
{
...
...
h2/src/main/org/h2/mvstore/Page.java
浏览文件 @
9eb213a0
...
@@ -501,10 +501,11 @@ public class Page {
...
@@ -501,10 +501,11 @@ public class Page {
sharedFlags
&=
~
SHARED_KEYS
;
sharedFlags
&=
~
SHARED_KEYS
;
}
}
Object
old
=
keys
[
index
];
Object
old
=
keys
[
index
];
DataType
keyType
=
map
.
getKeyType
();
if
(
old
!=
null
)
{
if
(
old
!=
null
)
{
memory
-=
map
.
getKeyType
()
.
getMemory
(
old
);
memory
-=
keyType
.
getMemory
(
old
);
}
}
memory
+=
map
.
getKeyType
()
.
getMemory
(
key
);
memory
+=
keyType
.
getMemory
(
key
);
keys
[
index
]
=
key
;
keys
[
index
]
=
key
;
}
}
...
@@ -521,8 +522,9 @@ public class Page {
...
@@ -521,8 +522,9 @@ public class Page {
values
=
Arrays
.
copyOf
(
values
,
values
.
length
);
values
=
Arrays
.
copyOf
(
values
,
values
.
length
);
sharedFlags
&=
~
SHARED_VALUES
;
sharedFlags
&=
~
SHARED_VALUES
;
}
}
memory
-=
map
.
getValueType
().
getMemory
(
old
);
DataType
valueType
=
map
.
getValueType
();
memory
+=
map
.
getValueType
().
getMemory
(
value
);
memory
-=
valueType
.
getMemory
(
old
);
memory
+=
valueType
.
getMemory
(
value
);
values
[
index
]
=
value
;
values
[
index
]
=
value
;
return
old
;
return
old
;
}
}
...
@@ -772,8 +774,9 @@ public class Page {
...
@@ -772,8 +774,9 @@ public class Page {
:
DataUtils
.
PAGE_TYPE_LEAF
;
:
DataUtils
.
PAGE_TYPE_LEAF
;
buff
.
put
((
byte
)
type
);
buff
.
put
((
byte
)
type
);
int
compressStart
=
buff
.
position
();
int
compressStart
=
buff
.
position
();
DataType
keyType
=
map
.
getKeyType
();
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
map
.
getKeyType
()
.
write
(
buff
,
keys
[
i
]);
keyType
.
write
(
buff
,
keys
[
i
]);
}
}
if
(
type
==
DataUtils
.
PAGE_TYPE_NODE
)
{
if
(
type
==
DataUtils
.
PAGE_TYPE_NODE
)
{
for
(
int
i
=
0
;
i
<=
len
;
i
++)
{
for
(
int
i
=
0
;
i
<=
len
;
i
++)
{
...
@@ -783,8 +786,9 @@ public class Page {
...
@@ -783,8 +786,9 @@ public class Page {
DataUtils
.
writeVarLong
(
buff
,
counts
[
i
]);
DataUtils
.
writeVarLong
(
buff
,
counts
[
i
]);
}
}
}
else
{
}
else
{
DataType
valueType
=
map
.
getValueType
();
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
map
.
getValueType
()
.
write
(
buff
,
values
[
i
]);
valueType
.
write
(
buff
,
values
[
i
]);
}
}
}
}
if
(
map
.
getStore
().
getCompress
())
{
if
(
map
.
getStore
().
getCompress
())
{
...
@@ -839,12 +843,14 @@ public class Page {
...
@@ -839,12 +843,14 @@ public class Page {
int
maxLength
=
4
+
2
+
DataUtils
.
MAX_VAR_INT_LEN
int
maxLength
=
4
+
2
+
DataUtils
.
MAX_VAR_INT_LEN
+
DataUtils
.
MAX_VAR_INT_LEN
+
1
;
+
DataUtils
.
MAX_VAR_INT_LEN
+
1
;
int
len
=
keyCount
;
int
len
=
keyCount
;
DataType
keyType
=
map
.
getKeyType
();
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
maxLength
+=
map
.
getKeyType
()
.
getMaxLength
(
keys
[
i
]);
maxLength
+=
keyType
.
getMaxLength
(
keys
[
i
]);
}
}
if
(
isLeaf
())
{
if
(
isLeaf
())
{
DataType
valueType
=
map
.
getValueType
();
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
maxLength
+=
map
.
getValueType
()
.
getMaxLength
(
values
[
i
]);
maxLength
+=
valueType
.
getMaxLength
(
values
[
i
]);
}
}
}
else
{
}
else
{
maxLength
+=
8
*
len
;
maxLength
+=
8
*
len
;
...
@@ -912,12 +918,14 @@ public class Page {
...
@@ -912,12 +918,14 @@ public class Page {
private
int
calculateMemory
()
{
private
int
calculateMemory
()
{
int
mem
=
DataUtils
.
PAGE_MEMORY
;
int
mem
=
DataUtils
.
PAGE_MEMORY
;
DataType
keyType
=
map
.
getKeyType
();
for
(
int
i
=
0
;
i
<
keyCount
;
i
++)
{
for
(
int
i
=
0
;
i
<
keyCount
;
i
++)
{
mem
+=
map
.
getKeyType
()
.
getMemory
(
keys
[
i
]);
mem
+=
keyType
.
getMemory
(
keys
[
i
]);
}
}
if
(
this
.
isLeaf
())
{
if
(
this
.
isLeaf
())
{
DataType
valueType
=
map
.
getValueType
();
for
(
int
i
=
0
;
i
<
keyCount
;
i
++)
{
for
(
int
i
=
0
;
i
<
keyCount
;
i
++)
{
mem
+=
map
.
getValueType
()
.
getMemory
(
values
[
i
]);
mem
+=
valueType
.
getMemory
(
values
[
i
]);
}
}
}
else
{
}
else
{
mem
+=
this
.
getChildPageCount
()
*
DataUtils
.
PAGE_MEMORY_CHILD
;
mem
+=
this
.
getChildPageCount
()
*
DataUtils
.
PAGE_MEMORY_CHILD
;
...
...
h2/src/main/org/h2/mvstore/db/MVTableEngine.java
浏览文件 @
9eb213a0
...
@@ -49,6 +49,7 @@ public class MVTableEngine implements TableEngine {
...
@@ -49,6 +49,7 @@ public class MVTableEngine implements TableEngine {
@Override
@Override
public
TableBase
createTable
(
CreateTableData
data
)
{
public
TableBase
createTable
(
CreateTableData
data
)
{
Database
db
=
data
.
session
.
getDatabase
();
Database
db
=
data
.
session
.
getDatabase
();
byte
[]
key
=
db
.
getFilePasswordHash
();
String
storeName
=
db
.
getDatabasePath
();
String
storeName
=
db
.
getDatabasePath
();
MVStore
.
Builder
builder
=
new
MVStore
.
Builder
();
MVStore
.
Builder
builder
=
new
MVStore
.
Builder
();
Store
store
;
Store
store
;
...
@@ -62,6 +63,13 @@ public class MVTableEngine implements TableEngine {
...
@@ -62,6 +63,13 @@ public class MVTableEngine implements TableEngine {
if
(
db
.
isReadOnly
())
{
if
(
db
.
isReadOnly
())
{
builder
.
readOnly
();
builder
.
readOnly
();
}
}
if
(
key
!=
null
)
{
char
[]
password
=
new
char
[
key
.
length
];
for
(
int
i
=
0
;
i
<
key
.
length
;
i
++)
{
password
[
i
]
=
(
char
)
key
[
i
];
}
builder
.
encryptionKey
(
password
);
}
store
=
new
Store
(
db
,
builder
.
open
());
store
=
new
Store
(
db
,
builder
.
open
());
STORES
.
put
(
storeName
,
store
);
STORES
.
put
(
storeName
,
store
);
}
else
if
(
store
.
db
!=
db
)
{
}
else
if
(
store
.
db
!=
db
)
{
...
...
h2/src/main/org/h2/store/fs/FilePathCrypt
2
.java
→
h2/src/main/org/h2/store/fs/FilePathCrypt.java
浏览文件 @
9eb213a0
...
@@ -24,7 +24,7 @@ import org.h2.util.StringUtils;
...
@@ -24,7 +24,7 @@ import org.h2.util.StringUtils;
/**
/**
* An encrypted file.
* An encrypted file.
*/
*/
public
class
FilePathCrypt
2
extends
FilePathWrapper
{
public
class
FilePathCrypt
extends
FilePathWrapper
{
private
static
final
String
SCHEME
=
"crypt2"
;
private
static
final
String
SCHEME
=
"crypt2"
;
...
@@ -32,7 +32,7 @@ public class FilePathCrypt2 extends FilePathWrapper {
...
@@ -32,7 +32,7 @@ public class FilePathCrypt2 extends FilePathWrapper {
* Register this file system.
* Register this file system.
*/
*/
public
static
void
register
()
{
public
static
void
register
()
{
FilePath
.
register
(
new
FilePathCrypt
2
());
FilePath
.
register
(
new
FilePathCrypt
());
}
}
public
FileChannel
open
(
String
mode
)
throws
IOException
{
public
FileChannel
open
(
String
mode
)
throws
IOException
{
...
@@ -121,12 +121,13 @@ public class FilePathCrypt2 extends FilePathWrapper {
...
@@ -121,12 +121,13 @@ public class FilePathCrypt2 extends FilePathWrapper {
/**
/**
* The length of the salt, in bytes.
* The length of the salt, in bytes.
*/
*/
private
static
final
int
SALT_LENGTH
=
32
;
private
static
final
int
SALT_LENGTH
=
8
;
/**
/**
* The number of iterations.
* The number of iterations. It is relatively low; a higher value would
* slow down opening files on Android too much.
*/
*/
private
static
final
int
HASH_ITERATIONS
=
10
000
;
private
static
final
int
HASH_ITERATIONS
=
10
;
private
final
FileChannel
base
;
private
final
FileChannel
base
;
...
...
h2/src/test/org/h2/test/store/TestMVTableEngine.java
浏览文件 @
9eb213a0
...
@@ -7,6 +7,7 @@ package org.h2.test.store;
...
@@ -7,6 +7,7 @@ package org.h2.test.store;
import
java.math.BigDecimal
;
import
java.math.BigDecimal
;
import
java.sql.Connection
;
import
java.sql.Connection
;
import
java.sql.DriverManager
;
import
java.sql.PreparedStatement
;
import
java.sql.PreparedStatement
;
import
java.sql.ResultSet
;
import
java.sql.ResultSet
;
import
java.sql.SQLException
;
import
java.sql.SQLException
;
...
@@ -33,6 +34,7 @@ public class TestMVTableEngine extends TestBase {
...
@@ -33,6 +34,7 @@ public class TestMVTableEngine extends TestBase {
}
}
public
void
test
()
throws
Exception
{
public
void
test
()
throws
Exception
{
testEncryption
();
testReadOnly
();
testReadOnly
();
testReuseDiskSpace
();
testReuseDiskSpace
();
testDataTypes
();
testDataTypes
();
...
@@ -40,6 +42,26 @@ public class TestMVTableEngine extends TestBase {
...
@@ -40,6 +42,26 @@ public class TestMVTableEngine extends TestBase {
testSimple
();
testSimple
();
}
}
private
void
testEncryption
()
throws
Exception
{
FileUtils
.
deleteRecursive
(
getBaseDir
(),
true
);
String
dbName
=
"mvstore"
+
";DEFAULT_TABLE_ENGINE=org.h2.mvstore.db.MVTableEngine"
;
Connection
conn
;
Statement
stat
;
String
url
=
getURL
(
dbName
+
";CIPHER=AES"
,
true
);
String
user
=
"sa"
;
String
password
=
"123 123"
;
conn
=
DriverManager
.
getConnection
(
url
,
user
,
password
);
stat
=
conn
.
createStatement
();
stat
.
execute
(
"create table test(id int)"
);
conn
.
close
();
conn
=
DriverManager
.
getConnection
(
url
,
user
,
password
);
stat
=
conn
.
createStatement
();
stat
.
execute
(
"select * from test"
);
conn
.
close
();
FileUtils
.
deleteRecursive
(
getBaseDir
(),
true
);
}
private
void
testReadOnly
()
throws
Exception
{
private
void
testReadOnly
()
throws
Exception
{
FileUtils
.
deleteRecursive
(
getBaseDir
(),
true
);
FileUtils
.
deleteRecursive
(
getBaseDir
(),
true
);
String
dbName
=
"mvstore"
+
String
dbName
=
"mvstore"
+
...
...
h2/src/test/org/h2/test/unit/TestFileSystem.java
浏览文件 @
9eb213a0
...
@@ -14,8 +14,8 @@ import java.io.OutputStream;
...
@@ -14,8 +14,8 @@ import java.io.OutputStream;
import
java.io.RandomAccessFile
;
import
java.io.RandomAccessFile
;
import
java.nio.ByteBuffer
;
import
java.nio.ByteBuffer
;
import
java.nio.channels.FileChannel
;
import
java.nio.channels.FileChannel
;
import
java.nio.channels.FileLock
;
import
java.nio.channels.FileChannel.MapMode
;
import
java.nio.channels.FileChannel.MapMode
;
import
java.nio.channels.FileLock
;
import
java.sql.Connection
;
import
java.sql.Connection
;
import
java.sql.DriverManager
;
import
java.sql.DriverManager
;
import
java.sql.ResultSet
;
import
java.sql.ResultSet
;
...
@@ -23,7 +23,6 @@ import java.sql.SQLException;
...
@@ -23,7 +23,6 @@ import java.sql.SQLException;
import
java.sql.Statement
;
import
java.sql.Statement
;
import
java.util.List
;
import
java.util.List
;
import
java.util.Random
;
import
java.util.Random
;
import
org.h2.dev.fs.FilePathCrypt
;
import
org.h2.message.DbException
;
import
org.h2.message.DbException
;
import
org.h2.store.fs.FilePath
;
import
org.h2.store.fs.FilePath
;
import
org.h2.store.fs.FileUtils
;
import
org.h2.store.fs.FileUtils
;
...
@@ -58,9 +57,8 @@ public class TestFileSystem extends TestBase {
...
@@ -58,9 +57,8 @@ public class TestFileSystem extends TestBase {
testUnsupportedFeatures
(
getBaseDir
());
testUnsupportedFeatures
(
getBaseDir
());
testMemFsDir
();
testMemFsDir
();
testClasspath
();
testClasspath
();
FilePathCrypt
.
register
();
FilePathDebug
.
register
().
setTrace
(
true
);
FilePathDebug
.
register
().
setTrace
(
true
);
testFileSystem
(
"crypt:aes:x
:"
+
getBaseDir
()
+
"/fs"
);
// testFileSystem("crypt:007
:" + getBaseDir() + "/fs");
testSimpleExpandTruncateSize
();
testSimpleExpandTruncateSize
();
testSplitDatabaseInZip
();
testSplitDatabaseInZip
();
...
@@ -74,8 +72,7 @@ public class TestFileSystem extends TestBase {
...
@@ -74,8 +72,7 @@ public class TestFileSystem extends TestBase {
testFileSystem
(
"memLZF:"
);
testFileSystem
(
"memLZF:"
);
testUserHome
();
testUserHome
();
try
{
try
{
FilePathCrypt
.
register
();
// testFileSystem("crypt:007:" + getBaseDir() + "/fs");
testFileSystem
(
"crypt:aes:x:"
+
getBaseDir
()
+
"/fs"
);
testFileSystem
(
"nio:"
+
getBaseDir
()
+
"/fs"
);
testFileSystem
(
"nio:"
+
getBaseDir
()
+
"/fs"
);
testFileSystem
(
"nioMapped:"
+
getBaseDir
()
+
"/fs"
);
testFileSystem
(
"nioMapped:"
+
getBaseDir
()
+
"/fs"
);
if
(!
config
.
splitFileSystem
)
{
if
(!
config
.
splitFileSystem
)
{
...
...
h2/src/tools/org/h2/dev/fs/FilePathCrypt.java
deleted
100644 → 0
浏览文件 @
ecbc84d5
差异被折叠。
点击展开。
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论