Skip to content

Commit

Permalink
refactor: 重构 CRUD 相关逻辑;修改错误处理中间件;增加规则实体类
Browse files Browse the repository at this point in the history
  • Loading branch information
CaoMeiYouRen committed Oct 1, 2024
1 parent 9f0ba96 commit a331d84
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 221 deletions.
34 changes: 3 additions & 31 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { timeout } from 'hono/timeout'
import { HTTPException } from 'hono/http-exception'
import { StatusCode } from 'hono/utils/http-status'
import { cors } from 'hono/cors'
import { secureHeaders } from 'hono/secure-headers'
import { TIMEOUT } from './env'
Expand All @@ -13,6 +11,7 @@ import codeRoute from './routes/code'
import userRoute from './routes/user'
import authRoute from './routes/auth'
import dbRoute from './routes/db'
import { errorhandler, notFoundHandler } from './middlewares/error'

const app = new Hono()

Expand All @@ -23,36 +22,9 @@ app.use(timeout(TIMEOUT))
app.use(cors())
app.use(secureHeaders())

app.onError((error, c) => {
const message = process.env.NODE_ENV === 'production' ? `${error.name}: ${error.message}` : error.stack
let status = 500
let statusText = 'INTERNAL_SERVER_ERROR'
if (error instanceof HTTPException) {
const response = error.getResponse()
status = response.status
statusText = response.statusText
}
const method = c.req.method
const requestPath = c.req.path
winstonLogger.error(`Error in ${method} ${requestPath}: \n${message}`)
return c.json({
status,
statusText,
message,
}, status as StatusCode)
})
app.onError(errorhandler)

app.notFound((c) => {
const method = c.req.method
const requestPath = c.req.path
const message = `Cannot ${method} ${requestPath}`
winstonLogger.warn(message)
return c.json({
status: 404,
statusText: 'Not Found',
message,
}, 404)
})
app.notFound(notFoundHandler)

app.all('/', (c) => c.json({
message: 'Hello Hono!',
Expand Down
2 changes: 2 additions & 0 deletions src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { TextMessage, ImageMessage, VoiceMessage, VideoMessage, ShortVideoMessag
import { BaseObject, BaseEvent, BaseMessage } from './models/wechat-base'
import { User } from './models/user'
import { VerifyCode } from './models/verify-code'
import { Rule } from './models/rule'
import { __DEV__, DATABASE_URL } from '@/env'

let dataSource: DataSource
Expand Down Expand Up @@ -34,6 +35,7 @@ export async function getDataSource() {
ClickEvent,
ViewEvent,
VerifyCode,
Rule,
],
// 自动创建数据库架构
synchronize: __DEV__,
Expand Down
65 changes: 65 additions & 0 deletions src/db/models/rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Column, Entity } from 'typeorm'
import { Base } from './base'

/**
* 规则实体类
* 用于配置接收到用户消息时,需要执行的操作
*
* @author CaoMeiYouRen
* @date 2024-10-01
* @export
* @class Rule
*/
@Entity()
export class Rule extends Base {

// 规则名称
@Column({ type: 'varchar', length: 255, nullable: false })
name: string

// 规则描述
@Column({ type: 'varchar', length: 1024, nullable: true })
description?: string

// 规则是否启用
@Column({ type: 'boolean', default: true })
enabled: boolean

// 规则优先级
@Column({ type: 'int', default: 0 })
priority: number

// 规则类型,文本或正则
@Column({ type: 'varchar', length: 50, nullable: false })
type: 'text' | 'regex'

// 规则匹配的文本或正则
@Column({ type: 'varchar', length: 1024, nullable: true })
match?: string

// 规则执行的操作,回复文本/转发/屏蔽/验证码/登录链接(OAuth2)
@Column({ type: 'varchar', length: 50, nullable: false })
action: 'reply' | 'forward' | 'block' | 'verify' | 'login'

// 回复内容,如果是回复,则需要填写
@Column({ type: 'varchar', length: 1024, nullable: true })
content?: string

// 转发的目标,如果是转发,则需要填写
@Column({ type: 'varchar', length: 255, nullable: true })
target?: string

// OAuth2 登录的 url,会用 POST 方法请求该地址,提交内容为 appId、token、timestamp、sign
// token 为用户的 jwtToken,timestamp 为当前时间戳,sign 为 appId + appSecret + token + timeStamp 的 sha256 哈希值
@Column({ type: 'varchar', length: 1024, nullable: true })
url?: string

// OAuth2 登录的目标 AppId,用于区分要登录哪个 App
@Column({ type: 'varchar', length: 255, nullable: true })
appId?: string

// OAuth2 登录的目标 AppSecret,用于请求签名
@Column({ type: 'varchar', length: 255, nullable: true })
appSecret?: string

}
32 changes: 32 additions & 0 deletions src/middlewares/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Context } from 'hono'
import { HTTPException } from 'hono/http-exception'
import { ErrorHandler, HTTPResponseError, NotFoundHandler } from 'hono/types'
import { StatusCode } from 'hono/utils/http-status'
import winstonLogger from '@/utils/logger'

export const errorhandler: ErrorHandler = (error: HTTPResponseError, c: Context) => {
const message = process.env.NODE_ENV === 'production' ? `${error.name}: ${error.message}` : error.stack
let status = 500
if (error instanceof HTTPException) {
const response = error.getResponse()
status = response.status
}
const method = c.req.method
const requestPath = c.req.path
winstonLogger.error(`Error in ${method} ${requestPath}: \n${message}`)
return c.json({
status,
message,
}, status as StatusCode)
}

export const notFoundHandler: NotFoundHandler = (c: Context) => {
const method = c.req.method
const requestPath = c.req.path
const message = `Cannot ${method} ${requestPath}`
winstonLogger.warn(message)
return c.json({
status: 404,
message,
}, 404)
}
70 changes: 12 additions & 58 deletions src/routes/code.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,17 @@
import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'
import { merge, uniq } from 'lodash-es'
import { getDataSource } from '@/db'
import { VerifyCode } from '@/db/models/verify-code'
import { adminAuth } from '@/middlewares/auth'
import { CrudQuery } from '@/interfaces/crud-query'
import { transformQueryOperator } from '@/utils/helper'
import { createCrudRoute } from '@/utils/route-helper'
// 验证码路由
const app = new Hono()

app.use('/*', adminAuth)

// 获取所有验证码
app.get('/', async (c) => {
const query = c.req.query() as CrudQuery
const limit = Number(query.limit) || 10
const page = Number(query.page) || 1
const skip = Number(query.skip) || (page - 1) * limit
const { where = {}, sort = {}, select = [], relations = [] } = query
const repository = (await getDataSource()).getRepository(VerifyCode)
const [data, total] = await repository.findAndCount({
where: {
...transformQueryOperator(where),
},
skip,
take: limit,
order: merge({
createdAt: 'DESC',
}, sort),
relations: uniq([...relations]),
select: uniq([...select || []]) as any,
})

return c.json({
data,
total,
lastPage: Math.ceil(total / limit),
currentPage: page,
})
})

// 获取单个验证码
app.get('/:id', async (c) => {
const id = parseInt(c.req.param('id'))
const repository = (await getDataSource()).getRepository(VerifyCode)
const message = await repository.findOne({ where: { id } })
return c.json(message)
})

// 删除单个验证码
app.delete('/:id', async (c) => {
const id = parseInt(c.req.param('id'))
const repository = (await getDataSource()).getRepository(VerifyCode)
const message = await repository.findOne({ where: { id } })
if (!message) {
throw new HTTPException(404, { message: '验证码不存在' })
}
await repository.delete({ id })
return c.json({ message: '删除成功' })
const app = createCrudRoute({
name: '验证码',
model: VerifyCode,
methods: {
find: true,
findOne: true,
create: false,
update: false,
delete: true,
},
admin: true,
})

export default app
70 changes: 12 additions & 58 deletions src/routes/message.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,18 @@
import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'
import { merge, uniq } from 'lodash-es'
import { getDataSource } from '@/db'
import { BaseMessage } from '@/db/models/wechat-base'
import { adminAuth } from '@/middlewares/auth'
import { CrudQuery } from '@/interfaces/crud-query'
import { transformQueryOperator } from '@/utils/helper'
import { createCrudRoute } from '@/utils/route-helper'

// message 接口需要 admin 权限
const app = new Hono()

app.use('/*', adminAuth)

// 获取所有消息
app.get('/', async (c) => {
const query = c.req.query() as CrudQuery
const limit = Number(query.limit) || 10
const page = Number(query.page) || 1
const skip = Number(query.skip) || (page - 1) * limit
const { where = {}, sort = {}, select = [], relations = [] } = query
const repository = (await getDataSource()).getRepository(BaseMessage)
const [data, total] = await repository.findAndCount({
where: {
...transformQueryOperator(where),
},
skip,
take: limit,
order: merge({
createdAt: 'DESC',
}, sort),
relations: uniq([...relations]),
select: uniq([...select || []]) as any,
})

return c.json({
data,
total,
lastPage: Math.ceil(total / limit),
currentPage: page,
})
})

// 获取单个消息
app.get('/:id', async (c) => {
const id = parseInt(c.req.param('id'))
const repository = (await getDataSource()).getRepository(BaseMessage)
const message = await repository.findOne({ where: { id } })
return c.json(message)
})

// 删除单个消息
app.delete('/:id', async (c) => {
const id = parseInt(c.req.param('id'))
const repository = (await getDataSource()).getRepository(BaseMessage)
const message = await repository.findOne({ where: { id } })
if (!message) {
throw new HTTPException(404, { message: '消息不存在' })
}
await repository.delete({ id })
return c.json({ message: '删除成功' })
const app = createCrudRoute({
name: '消息',
model: BaseMessage,
methods: {
find: true,
findOne: true,
create: false,
update: false,
delete: true,
},
admin: true,
})

export default app
Loading

0 comments on commit a331d84

Please sign in to comment.