Skip to content

Commit 3da0ee4

Browse files
authored
Merge pull request #30 from xxcheng123/develop
添加订阅分享类型支持
2 parents ce031a6 + 551bf1c commit 3da0ee4

12 files changed

Lines changed: 239 additions & 60 deletions

File tree

fe/src/api/storage.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export interface Storage {
1212
id: number
1313
parentId: number
1414
name: string
15-
osType: string // subscribe:订阅类型;share:分享类型;person:个人类型;family:家庭类型
15+
osType: string // subscribe:订阅类型;share:分享类型;person:个人类型;family:家庭类型;subscribe_share:订阅分享类型
1616
createdAt: string
1717
updatedAt: string
1818
localPath: string
@@ -27,9 +27,9 @@ export interface Storage {
2727
}
2828

2929
export interface PreAddStorageRequest {
30-
protocol: string // 目前只允许 subscribe、share
31-
subscribeUser?: string // subscribe 时必填
32-
shareCode?: string // share 时必填
30+
protocol: string // 目前允许 subscribe、share、subscribe_share
31+
subscribeUser?: string // subscribe、subscribe_share 时必填
32+
shareCode?: string // share、subscribe_share 时必填
3333
shareAccessCode?: string // share 时可选
3434
cloudToken?: number
3535
}
@@ -41,10 +41,10 @@ export interface PreAddStorageResponse {
4141

4242
export interface AddStorageRequest {
4343
localPath: string
44-
protocol: string // 目前允许 subscribe、share、person、family
44+
protocol: string // 目前允许 subscribe、share、person、family、subscribe_share
4545
cloudToken?: number // person、family 时必填
46-
subscribeUser?: string // subscribe 时必填
47-
shareCode?: string // share 时必填
46+
subscribeUser?: string // subscribe、subscribe_share 时必填
47+
shareCode?: string // share、subscribe_share 时必填
4848
shareAccessCode?: string // share 时可选
4949
fileId?: string // person、family 时必填
5050
familyId?: string // family 时必填

fe/src/utils/cloudpan189Parser.ts

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface CloudPan189ParseResult {
88
shareId: string
99
accessCode?: string
1010
originalUrl: string
11-
type: 'share' | 'subscribe' // 链接类型
11+
type: 'share' | 'subscribe' | 'subscribe_share' // 链接类型
1212
}
1313

1414
// 解析错误类
@@ -87,23 +87,41 @@ function parseCloudPanShareLink(input: string): CloudPan189ParseResult {
8787
* @returns 解析结果
8888
*/
8989
function parse21cnSubscribeLink(input: string): CloudPan189ParseResult {
90-
// 匹配21cn订阅链接中的uuid
91-
const uuidRegex = /https?:\/\/content\.21cn\.com\/h5\/subscrip\/index\.html#\/pages\/own-home\/index\?uuid=([a-f0-9]{32})/i
90+
// 匹配21cn订阅分享链接(包含uuid和shareCode)
91+
const subscribeShareRegex = /https?:\/\/content\.21cn\.com\/h5\/subscrip\/index\.html#\/pages\/own-home\/index\?uuid=([a-f0-9]{32})&shareCode=([a-zA-Z0-9]+)/i
9292

93-
const match = input.match(uuidRegex)
93+
// 匹配普通21cn订阅链接(只有uuid)
94+
const subscribeRegex = /https?:\/\/content\.21cn\.com\/h5\/subscrip\/index\.html#\/pages\/own-home\/index\?uuid=([a-f0-9]{32})/i
9495

95-
if (!match) {
96-
throw new CloudPan189ParseError('未找到有效的21cn订阅链接或uuid格式错误', input)
96+
// 优先匹配订阅分享类型
97+
let match = input.match(subscribeShareRegex)
98+
if (match) {
99+
const uuid = match[1]
100+
const shareCode = match[2]
101+
const originalUrl = match[0]
102+
103+
return {
104+
shareId: uuid,
105+
accessCode: shareCode, // 将shareCode存储在accessCode字段中
106+
originalUrl,
107+
type: 'subscribe_share'
108+
}
97109
}
98110

99-
const uuid = match[1]
100-
const originalUrl = match[0]
101-
102-
return {
103-
shareId: uuid,
104-
originalUrl,
105-
type: 'subscribe'
111+
// 匹配普通订阅类型
112+
match = input.match(subscribeRegex)
113+
if (match) {
114+
const uuid = match[1]
115+
const originalUrl = match[0]
116+
117+
return {
118+
shareId: uuid,
119+
originalUrl,
120+
type: 'subscribe'
121+
}
106122
}
123+
124+
throw new CloudPan189ParseError('未找到有效的21cn订阅链接或uuid格式错误', input)
107125
}
108126

109127
/**
@@ -144,11 +162,28 @@ export function parseMultipleCloudPan189Links(input: string): CloudPan189ParseRe
144162
})
145163
}
146164

147-
// 匹配21cn订阅链接
148-
const cnUrlRegex = /https?:\/\/content\.21cn\.com\/h5\/subscrip\/index\.html#\/pages\/own-home\/index\?uuid=([a-f0-9]{32})/gi
149-
const cnMatches = Array.from(cleanInput.matchAll(cnUrlRegex))
165+
// 匹配21cn订阅分享链接(包含uuid和shareCode)
166+
const cnSubscribeShareRegex = /https?:\/\/content\.21cn\.com\/h5\/subscrip\/index\.html#\/pages\/own-home\/index\?uuid=([a-f0-9]{32})&shareCode=([a-zA-Z0-9]+)/gi
167+
const cnSubscribeShareMatches = Array.from(cleanInput.matchAll(cnSubscribeShareRegex))
168+
169+
for (const match of cnSubscribeShareMatches) {
170+
const uuid = match[1]
171+
const shareCode = match[2]
172+
const originalUrl = match[0]
173+
174+
results.push({
175+
shareId: uuid,
176+
accessCode: shareCode,
177+
originalUrl,
178+
type: 'subscribe_share'
179+
})
180+
}
181+
182+
// 匹配21cn普通订阅链接(只有uuid)
183+
const cnSubscribeRegex = /https?:\/\/content\.21cn\.com\/h5\/subscrip\/index\.html#\/pages\/own-home\/index\?uuid=([a-f0-9]{32})(?!&shareCode=)/gi
184+
const cnSubscribeMatches = Array.from(cleanInput.matchAll(cnSubscribeRegex))
150185

151-
for (const match of cnMatches) {
186+
for (const match of cnSubscribeMatches) {
152187
const uuid = match[1]
153188
const originalUrl = match[0]
154189

@@ -168,12 +203,12 @@ export function parseMultipleCloudPan189Links(input: string): CloudPan189ParseRe
168203
* @param type 链接类型
169204
* @returns 是否有效
170205
*/
171-
export function isValidShareId(shareId: string, type: 'share' | 'subscribe' = 'share'): boolean {
206+
export function isValidShareId(shareId: string, type: 'share' | 'subscribe' | 'subscribe_share' = 'share'): boolean {
172207
if (!shareId || typeof shareId !== 'string') {
173208
return false
174209
}
175210

176-
if (type === 'subscribe') {
211+
if (type === 'subscribe' || type === 'subscribe_share') {
177212
// uuid格式:32位十六进制字符
178213
return /^[a-f0-9]{32}$/i.test(shareId)
179214
}
@@ -199,19 +234,26 @@ export function isValidAccessCode(accessCode: string): boolean {
199234
/**
200235
* 构建天翼云盘分享链接或21cn订阅链接
201236
* @param shareId 分享ID或uuid
202-
* @param accessCode 访问码(可选)
237+
* @param accessCode 访问码或分享码(可选)
203238
* @param type 链接类型
204239
* @returns 完整的分享链接
205240
*/
206-
export function buildCloudPan189Link(shareId: string, accessCode?: string, type: 'share' | 'subscribe' = 'share'): string {
241+
export function buildCloudPan189Link(shareId: string, accessCode?: string, type: 'share' | 'subscribe' | 'subscribe_share' = 'share'): string {
207242
if (!isValidShareId(shareId, type)) {
208-
throw new CloudPan189ParseError(`无效的${type === 'subscribe' ? 'uuid' : '分享ID'}格式`)
243+
throw new CloudPan189ParseError(`无效的${type === 'subscribe' || type === 'subscribe_share' ? 'uuid' : '分享ID'}格式`)
209244
}
210245

211246
if (type === 'subscribe') {
212247
return `https://content.21cn.com/h5/subscrip/index.html#/pages/own-home/index?uuid=${shareId}`
213248
}
214249

250+
if (type === 'subscribe_share') {
251+
if (!accessCode) {
252+
throw new CloudPan189ParseError('订阅分享链接需要提供分享码')
253+
}
254+
return `https://content.21cn.com/h5/subscrip/index.html#/pages/own-home/index?uuid=${shareId}&shareCode=${accessCode}`
255+
}
256+
215257
const baseUrl = `https://cloud.189.cn/t/${shareId}`
216258

217259
if (accessCode) {

fe/src/views/Storage.vue

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
</th>
8383
<th style="text-align: center">序号</th>
8484
<th style="text-align: center">挂载路径</th>
85-
<th style="width: 100px; text-align: center">协议类型</th>
85+
<th style="width: 140px; text-align: center">协议类型</th>
8686
<th style="width: 260px; text-align: center">任务状态</th>
8787
<th style="text-align: center">令牌绑定</th>
8888
<th style="text-align: center">创建时间</th>
@@ -219,6 +219,16 @@
219219
<input v-model="newStorage.shareAccessCode" type="text" class="form-input" placeholder="请输入访问码(可选)" />
220220
</div>
221221
</div>
222+
<div v-if="newStorage.protocol === 'subscribe_share'">
223+
<div class="form-group">
224+
<label class="form-label">订阅用户ID</label>
225+
<input v-model="newStorage.subscribeUser" type="text" class="form-input" placeholder="请输入订阅用户ID" />
226+
</div>
227+
<div class="form-group">
228+
<label class="form-label">分享码</label>
229+
<input v-model="newStorage.shareCode" type="text" class="form-input" placeholder="请输入分享码" />
230+
</div>
231+
</div>
222232
<div v-if="newStorage.protocol === 'person'">
223233
<div class="form-group">
224234
<label class="form-label">选择文件夹</label>
@@ -429,7 +439,7 @@
429439
</td>
430440
<td>
431441
<span class="result-type" :class="`type-${result.type}`">
432-
{{ result.type === 'subscribe' ? '订阅' : '分享' }}
442+
{{ result.type === 'subscribe' ? '订阅' : result.type === 'subscribe_share' ? '订阅分享' : '分享' }}
433443
</span>
434444
</td>
435445
<td>
@@ -456,7 +466,7 @@
456466
</td>
457467
<td>
458468
<input
459-
v-if="result.type === 'subscribe'"
469+
v-if="result.type === 'subscribe' || result.type === 'subscribe_share'"
460470
v-model="result.subscribeUser"
461471
type="text"
462472
class="table-input"
@@ -466,7 +476,7 @@
466476
</td>
467477
<td>
468478
<input
469-
v-if="result.type === 'share'"
479+
v-if="result.type === 'share' || result.type === 'subscribe_share'"
470480
v-model="result.shareCode"
471481
type="text"
472482
class="table-input"
@@ -601,6 +611,7 @@ const newStorage = reactive({
601611
const protocolOptions = [
602612
{ label: '订阅类型', value: 'subscribe' },
603613
{ label: '分享类型', value: 'share' },
614+
{ label: '订阅分享类型', value: 'subscribe_share' },
604615
{ label: '个人类型', value: 'person' },
605616
{ label: '家庭类型', value: 'family' }
606617
]
@@ -752,6 +763,15 @@ const confirmAddStorage = async () => {
752763
toast.warning('请输入分享码')
753764
return
754765
}
766+
} else if (newStorage.protocol === 'subscribe_share') {
767+
if (!newStorage.subscribeUser.trim()) {
768+
toast.warning('请输入订阅用户ID')
769+
return
770+
}
771+
if (!newStorage.shareCode.trim()) {
772+
toast.warning('请输入分享码')
773+
return
774+
}
755775
} else if (newStorage.protocol === 'person') {
756776
if (!newStorage.cloudToken) {
757777
toast.warning('个人类型需要选择云盘令牌')
@@ -794,7 +814,10 @@ const confirmAddStorage = async () => {
794814
if (newStorage.shareAccessCode.trim()) {
795815
requestData.shareAccessCode = newStorage.shareAccessCode.trim()
796816
}
797-
} else if (newStorage.protocol === 'person') {
817+
} else if (newStorage.protocol === 'subscribe_share') {
818+
requestData.subscribeUser = newStorage.subscribeUser.trim()
819+
requestData.shareCode = newStorage.shareCode.trim()
820+
} else if (newStorage.protocol === 'person') {
798821
requestData.fileId = newStorage.fileId.trim()
799822
} else if (newStorage.protocol === 'family') {
800823
requestData.familyId = newStorage.familyId.trim()
@@ -1122,7 +1145,7 @@ const parseLinks = async () => {
11221145
for (const result of results) {
11231146
try {
11241147
const preAddData: any = {
1125-
protocol: result.type === 'subscribe' ? 'subscribe' : 'share'
1148+
protocol: result.type === 'subscribe' ? 'subscribe' : result.type === 'subscribe_share' ? 'subscribe_share' : 'share'
11261149
}
11271150
11281151
if (result.type === 'subscribe') {
@@ -1132,6 +1155,9 @@ const parseLinks = async () => {
11321155
if (result.accessCode) {
11331156
preAddData.shareAccessCode = result.accessCode
11341157
}
1158+
} else if (result.type === 'subscribe_share') {
1159+
preAddData.subscribeUser = result.shareId
1160+
preAddData.shareCode = result.accessCode
11351161
}
11361162
11371163
if (defaultCloudToken.value) {
@@ -1147,8 +1173,8 @@ const parseLinks = async () => {
11471173
suggestedName,
11481174
localPath: defaultPathPrefix.value ? `${defaultPathPrefix.value}/${suggestedName}` : `/${suggestedName}`,
11491175
cloudToken: defaultCloudToken.value,
1150-
subscribeUser: result.type === 'subscribe' ? result.shareId : '',
1151-
shareCode: result.type === 'share' ? result.shareId : ''
1176+
subscribeUser: result.type === 'subscribe' || result.type === 'subscribe_share' ? result.shareId : '',
1177+
shareCode: result.type === 'share' ? result.shareId : result.type === 'subscribe_share' ? result.accessCode : ''
11521178
})
11531179
} catch (error) {
11541180
console.warn(`链接验证失败 (${result.originalUrl}):`, error)
@@ -1193,6 +1219,7 @@ const batchImportStorages = async () => {
11931219
if (!result.localPath.trim()) return true
11941220
if (result.type === 'subscribe' && !result.subscribeUser?.trim()) return true
11951221
if (result.type === 'share' && !result.shareCode?.trim()) return true
1222+
if (result.type === 'subscribe_share' && (!result.subscribeUser?.trim() || !result.shareCode?.trim())) return true
11961223
return false
11971224
})
11981225
@@ -1211,7 +1238,7 @@ const batchImportStorages = async () => {
12111238
try {
12121239
const requestData: AddStorageRequest = {
12131240
localPath: result.localPath.trim(),
1214-
protocol: result.type === 'subscribe' ? 'subscribe' : 'share'
1241+
protocol: result.type === 'subscribe' ? 'subscribe' : result.type === 'subscribe_share' ? 'subscribe_share' : 'share'
12151242
}
12161243
12171244
if (result.cloudToken) {
@@ -1225,6 +1252,9 @@ const batchImportStorages = async () => {
12251252
if (result.accessCode?.trim()) {
12261253
requestData.shareAccessCode = result.accessCode.trim()
12271254
}
1255+
} else if (result.type === 'subscribe_share') {
1256+
requestData.subscribeUser = result.subscribeUser!.trim()
1257+
requestData.shareCode = result.shareCode!.trim()
12281258
}
12291259
12301260
await storageApi.add(requestData)
@@ -1309,6 +1339,8 @@ const getProtocolLabel = (protocol: string): string => {
13091339
return '订阅'
13101340
case 'share':
13111341
return '分享'
1342+
case 'subscribe_share':
1343+
return '订阅分享'
13121344
case 'cloud_folder':
13131345
return '个人'
13141346
case 'cloud_family_folder':
@@ -1627,6 +1659,11 @@ onUnmounted(() => {
16271659
color: #d97706;
16281660
}
16291661
1662+
.protocol-subscribe_share {
1663+
background: #f0fdf4;
1664+
color: #15803d;
1665+
}
1666+
16301667
.protocol-cloud_folder {
16311668
background: #f0f9ff;
16321669
color: #0369a1;

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/bradenaw/juniper v0.15.3
77
github.com/bytedance/gopkg v0.1.2
88
github.com/deckarep/golang-set/v2 v2.8.0
9-
github.com/gin-gonic/gin v1.10.1
9+
github.com/gin-gonic/gin v1.9.1
1010
github.com/glebarez/sqlite v1.11.0
1111
github.com/go-playground/locales v0.14.1
1212
github.com/go-playground/universal-translator v0.18.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ github.com/gammazero/deque v1.1.0 h1:OyiyReBbnEG2PP0Bnv1AASLIYvyKqIFN5xfl1t8oGLo
2828
github.com/gammazero/deque v1.1.0/go.mod h1:JVrR+Bj1NMQbPnYclvDlvSX0nVGReLrQZ0aUMuWLctg=
2929
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
3030
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
31-
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
32-
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
31+
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
32+
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
3333
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
3434
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
3535
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=

internal/bus/bus_file.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,12 @@ func (w *busWorker) deleteVirtualFileHook(ctx context.Context, fileId int64) err
450450
if err := w.delMediaFile(ctx, fileId); err != nil {
451451
errs = append(errs, err)
452452
}
453+
454+
// 删除媒体文件后自动清理空文件夹(目前会删除所有的空文件夹)
455+
if _, err := w.clearEmptyDirs(ctx); err != nil {
456+
w.logger.Warn("清理空文件夹失败", zap.Int64("file_id", fileId), zap.Error(err))
457+
// 不将此错误加入到errs中,因为这不应该阻止删除操作
458+
}
453459
}
454460

455461
return errors2.Join(errs...)

internal/pkgs/utils/http.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package utils
2+
3+
import (
4+
"net/http"
5+
)
6+
7+
var NoFollowRedirectHttpClient = &http.Client{
8+
CheckRedirect: func(req *http.Request, via []*http.Request) error {
9+
return http.ErrUseLastResponse // 停止跟随重定向
10+
},
11+
}

0 commit comments

Comments
 (0)