Skip to content

Commit 211225b

Browse files
committed
feat(shorturl).
1 parent 120b9ed commit 211225b

File tree

5 files changed

+125
-13
lines changed

5 files changed

+125
-13
lines changed

Diff for: package.json

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
"pinia": "^2.0.22",
101101
"qface": "^1.2.0",
102102
"qrcode": "^1.5.1",
103+
"randomstring": "^1.2.2",
103104
"rd": "^2.0.1",
104105
"rss-feed-emitter": "^3.2.3",
105106
"sharp": "^0.30.7",

Diff for: src/commands/shorturl.ts

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { Context, Time, Session, segment } from 'koishi'
2+
import randomstring from 'randomstring'
3+
import Koa from 'koa'
4+
5+
declare module 'koishi' {
6+
interface Tables {
7+
shorturl: Shorturl
8+
}
9+
}
10+
11+
export interface Shorturl {
12+
id: number
13+
key: string
14+
url: string
15+
count: number
16+
}
17+
18+
export interface Config {
19+
root: string
20+
authority: number
21+
}
22+
23+
export const KEY_LENGTH = 6
24+
export const PRE_LENGTH_CHECK = true
25+
26+
27+
export const name = 'shorturl'
28+
export const using = ['web', 'database'] as const
29+
30+
export function shorturlMiddlewareFactory(ctx: Context) {
31+
return async (net: Koa.Context, next: Koa.Next) => {
32+
if (!net.url.startsWith('/s/')) {
33+
return next()
34+
}
35+
36+
const key = net.url.slice(3)
37+
if (!PRE_LENGTH_CHECK || key.length === KEY_LENGTH) {
38+
const data = (await ctx.database.get('shorturl', { key }))?.[0]
39+
if (data) {
40+
// await ctx.database.update('shorturl', {})
41+
net.status = 302
42+
net.redirect(data.url)
43+
} else {
44+
net.status = 404
45+
}
46+
} else {
47+
net.status = 404
48+
}
49+
50+
if (net.status === 404) {
51+
net.body = '404 Not Found: The shorturl you requested is not found on this server.'
52+
}
53+
}
54+
}
55+
56+
export default function (ctx: Context, config: Config) {
57+
async function generateKey(): Promise<string> {
58+
let key: string
59+
do {
60+
key = randomstring.generate({
61+
length: KEY_LENGTH,
62+
charset: 'alphanumeric'
63+
})
64+
} while ((await ctx.database.get('shorturl', { key })).length !== 0)
65+
return key
66+
}
67+
68+
function userShortcut(session: Session): string {
69+
return `${session.username}(${session.platform}:${session.userId})`
70+
}
71+
72+
const logger = ctx.logger('shorturl')
73+
74+
if (!config.root) { throw new Error('[shorturl] No root url configured.') }
75+
config.authority = config.authority || 1
76+
77+
ctx.model.extend('shorturl', {
78+
id: 'unsigned',
79+
key: 'string',
80+
url: 'string',
81+
count: 'unsigned',
82+
}, {
83+
autoInc: true,
84+
})
85+
86+
ctx.command('shorturl <url:string>', '短网址生成器', { authority: config.authority })
87+
.action(async ({ session }, url: string) => {
88+
if (!url || !url.length || url.length > 1000) { return session.execute('help shorturl') }
89+
logger.info('shorturl', 'add', userShortcut(session), url)
90+
const key = await generateKey()
91+
await ctx.database.create('shorturl', { key, url, count: 0 })
92+
return segment.quote(session.messageId) + config.root + key
93+
})
94+
}

Diff for: src/services/web/app.ts

+12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import bodyParser from 'koa-bodyparser'
1515
import { Config } from './index'
1616
import { UserMeta, ChannelMeta, getUser, getChannel } from '../../utils/usermeta'
1717

18+
import { shorturlMiddlewareFactory } from '../../commands/shorturl'
19+
1820
export { Context as RouteContext } from 'koa'
1921

2022
const viteDist = path.join(__dirname, 'vite/dist')
@@ -89,6 +91,12 @@ export class WebService {
8991
return target
9092
}
9193

94+
getPluginMiddlewares():Array<Koa.Middleware> {
95+
return [
96+
shorturlMiddlewareFactory(this.ctx)
97+
]
98+
}
99+
92100
register(argv: PageArgument): string {
93101
const { data } = argv
94102

@@ -162,6 +170,10 @@ export class WebService {
162170
gzip: true,
163171
}))
164172

173+
for (const middleware of this.getPluginMiddlewares()) {
174+
this.app.use(middleware)
175+
}
176+
165177
this.app
166178
.use(this.router.routes())
167179
.use(this.router.allowedMethods())

Diff for: src/services/web/vite/components.d.ts

-13
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,20 @@ export {}
77

88
declare module '@vue/runtime-core' {
99
export interface GlobalComponents {
10-
AAvatar: typeof import('@arco-design/web-vue')['Avatar']
11-
AAvatarGroup: typeof import('@arco-design/web-vue')['AvatarGroup']
1210
ACard: typeof import('@arco-design/web-vue')['Card']
13-
ACardMeta: typeof import('@arco-design/web-vue')['CardMeta']
14-
ADescriptions: typeof import('@arco-design/web-vue')['Descriptions']
15-
AImage: typeof import('@arco-design/web-vue')['Image']
16-
AImagePreviewGroup: typeof import('@arco-design/web-vue')['ImagePreviewGroup']
1711
ALayout: typeof import('@arco-design/web-vue')['Layout']
1812
ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent']
1913
ALayoutFooter: typeof import('@arco-design/web-vue')['LayoutFooter']
2014
ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader']
2115
APageHeader: typeof import('@arco-design/web-vue')['PageHeader']
22-
APopover: typeof import('@arco-design/web-vue')['Popover']
23-
ASkeleton: typeof import('@arco-design/web-vue')['Skeleton']
24-
ASkeletonShape: typeof import('@arco-design/web-vue')['SkeletonShape']
2516
ASpin: typeof import('@arco-design/web-vue')['Spin']
26-
ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
2717
Avatar: typeof import('./src/components/Avatar.vue')['default']
2818
ExternalLink: typeof import('./src/components/ExternalLink.vue')['default']
2919
Footer: typeof import('./src/layout/Footer.vue')['default']
3020
Header: typeof import('./src/layout/Header.vue')['default']
3121
HeaderAvatar: typeof import('./src/components/HeaderAvatar.vue')['default']
32-
HeaderPopover: typeof import('./src/components/HeaderPopover.vue')['default']
33-
HeaderPopup: typeof import('./src/components/HeaderPopup.vue')['default']
3422
IconFile: typeof import('@arco-design/web-vue/es/icon')['IconFile']
3523
IconGithub: typeof import('@arco-design/web-vue/es/icon')['IconGithub']
36-
IconInfoCircle: typeof import('@arco-design/web-vue/es/icon')['IconInfoCircle']
3724
Markdown: typeof import('./src/components/Markdown.vue')['default']
3825
PageError: typeof import('./src/layout/PageError.vue')['default']
3926
PageHome: typeof import('./src/layout/PageHome.vue')['default']

Diff for: yarn.lock

+18
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,11 @@ array-union@^2.1.0:
11461146
resolved "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
11471147
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
11481148

1149+
1150+
version "1.0.2"
1151+
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.2.tgz#5fcc373920775723cfd64d65c64bef53bf9eba6d"
1152+
integrity sha512-GVYjmpL05al4dNlKJm53mKE4w9OOLiuVHWorsIA3YVz+Hu0hcn6PtE3Ydl0EqU7v+7ABC4mjjWsnLUxbpno+CA==
1153+
11491154
asap@^2.0.0:
11501155
version "2.0.6"
11511156
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
@@ -4295,6 +4300,19 @@ [email protected]:
42954300
discontinuous-range "1.0.0"
42964301
ret "~0.1.10"
42974302

4303+
4304+
version "2.0.3"
4305+
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec"
4306+
integrity sha512-lDVjxQQFoCG1jcrP06LNo2lbWp4QTShEXnhActFBwYuHprllQV6VUpwreApsYqCgD+N1mHoqJ/BI/4eV4R2GYg==
4307+
4308+
randomstring@^1.2.2:
4309+
version "1.2.2"
4310+
resolved "https://registry.yarnpkg.com/randomstring/-/randomstring-1.2.2.tgz#61b3d42624c7b1fcaaaba161c53452eb3d8fef1c"
4311+
integrity sha512-9FByiB8guWZLbE+akdQiWE3I1I6w7Vn5El4o4y7o5bWQ6DWPcEOp+aLG7Jezc8BVRKKpgJd2ppRX0jnKu1YCfg==
4312+
dependencies:
4313+
array-uniq "1.0.2"
4314+
randombytes "2.0.3"
4315+
42984316
range-parser@~1.2.1:
42994317
version "1.2.1"
43004318
resolved "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"

0 commit comments

Comments
 (0)