diff --git a/doc/api.md b/doc/api.md
index 98ce9a1..43015fe 100644
--- a/doc/api.md
+++ b/doc/api.md
@@ -643,4 +643,96 @@
geted: 0, // 是否已获得, 0: 未获得, 1: 已获得
}
]
-```
\ No newline at end of file
+```
+
+### 17. 邮件列表
+
+1. Method: POST
+2. URI: /api/:accountid/mails
+
+| 字段 | 说明 |
+| -------- | -------------------------------------- |
+| accountid | 帐号id |
+
+> POST参数
+
+
+| 字段 | 说明 |
+| -------- | -------------------------------------- |
+| sid | 店铺id |
+
+3. Response: JSON
+
+```js
+[{
+ _id: '邮件id',
+ title: '邮件标题',
+ content: 1: '邮件正文'
+ status: 0, // 邮件状态: 0: 未读 1: 已读, 2: 已领取附件
+ items: [{
+ itemId: '物品的id'
+ name: '物品名',
+ count: 1 // 数量
+ }]
+ }]
+```
+
+### 18. 设置邮件已读
+
+1. Method: POST
+2. URI: /api/:accountid/mail/read
+
+| 字段 | 说明 |
+| -------- | -------------------------------------- |
+| accountid | 帐号id |
+
+> POST参数
+
+
+| 字段 | 说明 |
+| -------- | -------------------------------------- |
+| ids | 邮件id数组 |
+
+### 19. 领取邮件附件
+
+1. Method: POST
+2. URI: /api/:accountid/mail/attachment
+
+| 字段 | 说明 |
+| -------- | -------------------------------------- |
+| accountid | 帐号id |
+
+> POST参数
+
+
+| 字段 | 说明 |
+| -------- | -------------------------------------- |
+| ids | 邮件id数组 |
+
+3. Response: JSON
+
+```js
+[{ // 获得的物品列表
+ coupon: '优惠券的id',
+ name: '优惠券名',
+ count: 1, //数量
+ couponUrl: '优惠券详情图的url',
+ rewardType: 0, // 0: 优惠券, 1: 抽奖券
+ }]
+```
+
+### 20. 删除邮件
+
+1. Method: POST
+2. URI: /api/:accountid/mail/delete
+
+| 字段 | 说明 |
+| -------- | -------------------------------------- |
+| accountid | 帐号id |
+
+> POST参数
+
+
+| 字段 | 说明 |
+| -------- | -------------------------------------- |
+| ids | 邮件id数组 |
diff --git a/src/api/controllers/mail.controller.ts b/src/api/controllers/mail.controller.ts
new file mode 100644
index 0000000..03f3e50
--- /dev/null
+++ b/src/api/controllers/mail.controller.ts
@@ -0,0 +1,83 @@
+import BaseController from '../../common/base.controller'
+import { role, router } from '../../decorators/router'
+import { UserMail } from '../../models/user/UserMail'
+import { SysMail } from '../../models/content/SysMail'
+import { ZError } from '../../common/ZError'
+import { UserReward } from '../../models/user/UserReward'
+
+class MailController extends BaseController {
+ @role('anon')
+ @router('post /api/:accountId/mails')
+ async list(req: any) {
+ const { accountId, sid } = req.params
+ await UserMail.updateExpire(accountId)
+ let mails = await UserMail.find({ accountId, deleted: false }).sort({ status: 1, _id: -1 })
+ let mailSet: Set = new Set()
+ for (let mail of mails) {
+ mailSet.add(mail.oid)
+ }
+ let sysMails = await SysMail.findMail(accountId, sid, [...mailSet])
+ if (sysMails?.length > 0) {
+ for (let _m of sysMails) {
+ let mail = await UserMail.receiveOne(accountId, _m)
+ mails.push(mail)
+ }
+ }
+ return mails.map(o => o.toJson())
+ }
+
+ @role('anon')
+ @router('post /api/:accountId/mail/delete')
+ async delete(req: any) {
+ const { accountId, ids } = req.params
+ if (!ids || !Array.isArray(ids) || ids.length === 0) {
+ throw new ZError(10, 'ids必须是一个数组')
+ }
+ let result = await UserMail.deleteMails(accountId, ids)
+ return {}
+ }
+
+ @role('anon')
+ @router('post /api/:accountId/mail/read')
+ async read(req: any) {
+ const { accountId, ids } = req.params
+ if (!ids || !Array.isArray(ids) || ids.length === 0) {
+ throw new ZError(10, 'ids必须是一个数组')
+ }
+ await UserMail.updateMails(accountId, ids, 1)
+ return {}
+ }
+
+ @role('anon')
+ @router('post /api/:accountId/mail/attachment')
+ async attachment(req: any) {
+ const { accountId, ids } = req.params
+ if (!ids || !Array.isArray(ids) || ids.length === 0) {
+ throw new ZError(10, 'ids必须是一个数组')
+ }
+ let mails = await UserMail.find({ _id: { $in: ids }, deleted: false, accountId, status: { $lt: 2 } })
+ if (!mails || mails.length === 0) {
+ throw new ZError(11, '未找到符合条件的邮件')
+ }
+ let results = []
+ for (let mail of mails) {
+ if (mail.items && mail.items.length > 0) {
+ for (let item of mail.items) {
+ let record = await UserReward.saveOneRecord({
+ accountId,
+ shop: mail.senderShop,
+ itemId: item.itemId,
+ count: item.count,
+ rewardId: mail._id + '',
+ activityId: mail.oid + '',
+ source: 'mail',
+ })
+ results.push(record)
+ }
+ mail.status = 2
+ await mail.save()
+ }
+ }
+ return results
+ }
+}
diff --git a/src/models/content/SysMail.ts b/src/models/content/SysMail.ts
index 0c26ab0..f7c4e2a 100644
--- a/src/models/content/SysMail.ts
+++ b/src/models/content/SysMail.ts
@@ -1,18 +1,99 @@
import { dbconn } from '../../decorators/dbconn'
-import { getModelForClass, modelOptions, prop } from '@typegoose/typegoose'
+import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
import { Severity } from '@typegoose/typegoose/lib/internal/constants'
import { BaseModule } from '../Base'
+import { noJson } from '../../decorators/nojson'
+
+export class MailItemClass {
+ @prop()
+ public itemId: string
+
+ @prop()
+ public name: string
+
+ @prop()
+ public count: number
+}
@dbconn()
+@index({ accounts: 1, deleted: 1, type: 1, sendTime: 1, endTime: 1 }, { unique: false })
+@index({ shops: 1, deleted: 1, type: 1, sendTime: 1, endTime: 1 }, { unique: false })
@modelOptions({
schemaOptions: { collection: 'sys_mail', timestamps: true },
options: { allowMixed: Severity.ALLOW },
})
export class SysMailClass extends BaseModule {
+ @prop()
+ public sender: string
+
+ @prop()
+ public senderShop: string
+
@prop()
public title: string
@prop()
public content: string
+
+ /**
+ * 邮件类型
+ * @type {number} 0: 普通邮件 1: 店铺群发
+ */
+ @prop({ default: 0 })
+ public type: number
+
+ @prop({ type: () => [String] })
+ public accounts: string[]
+
+ @prop({ type: () => [String] })
+ public shops: string[]
+
+ @prop({ type: () => [MailItemClass] })
+ public items: MailItemClass[]
+
+ /**
+ * 是否删除
+ * @type {boolean}
+ */
+ @noJson()
+ @prop({ default: false })
+ public deleted: boolean
+ @noJson()
+ @prop()
+ public deleteTime: Date
+
+ /**
+ * 创建人
+ * @type {string}
+ */
+ @prop()
+ public createdBy: string
+
+ @prop()
+ public sendTime: number
+
+ @prop()
+ public endTime: number
+
+ public static async findMail(accountId: string, shop: string, excludes: string[]) {
+ let now = Date.now()
+ let mails0 = await SysMail.find({
+ accounts: accountId,
+ type: 0,
+ deleted: false,
+ sendTime: { $lte: now },
+ endTime: { $gte: now },
+ _id: { $nin: excludes },
+ })
+ let mails1 = await SysMail.find({
+ shops: shop,
+ type: 1,
+ deleted: false,
+ sendTime: { $lte: now },
+ endTime: { $gte: now },
+ _id: { $nin: excludes },
+ })
+ return mails0.concat(mails1)
+ }
}
export const SysMail = getModelForClass(SysMailClass, { existingConnection: SysMailClass.db })
diff --git a/src/models/user/UserMail.ts b/src/models/user/UserMail.ts
new file mode 100644
index 0000000..2e28f50
--- /dev/null
+++ b/src/models/user/UserMail.ts
@@ -0,0 +1,94 @@
+import { dbconn } from '../../decorators/dbconn'
+import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
+import { Severity } from '@typegoose/typegoose/lib/internal/constants'
+import { BaseModule } from '../Base'
+import { MailItemClass } from '../content/SysMail'
+import { noJson } from '../../decorators/nojson'
+
+@dbconn()
+@index({ accountId: 1, oid: 1 }, { unique: true })
+@index({ accountId: 1, deleted: 1, status: 1 }, { unique: false })
+@modelOptions({
+ schemaOptions: { collection: 'user_mail', timestamps: true },
+ options: { allowMixed: Severity.ALLOW },
+})
+export class UserMailClass extends BaseModule {
+ @prop()
+ public oid: string
+
+ @prop()
+ public sender: string
+
+ @prop()
+ public senderShop: string
+
+ @prop()
+ public title: string
+
+ @prop()
+ public content: string
+
+ @prop()
+ public accountId: string
+
+ @prop({ type: () => [MailItemClass] })
+ public items: MailItemClass[]
+
+ /**
+ * 邮件状态
+ * @type {number} 0: 未读 1: 已读, 2: 已领取附件
+ */
+ @prop({ default: 0 })
+ public status: number
+
+ /**
+ * 过期时间
+ * @type {number}
+ */
+ @prop()
+ public expire: number
+
+ /**
+ * 是否删除
+ * @type {boolean}
+ */
+ @noJson()
+ @prop({ default: false })
+ public deleted: boolean
+ @noJson()
+ @prop()
+ public deleteTime: Date
+
+ public static async receiveOne(accountId: string, mail: any) {
+ let record = new UserMail({})
+ record.oid = mail.id
+ record.title = mail.title
+ record.content = mail.content
+ record.accountId = accountId
+ record.items = mail.items
+ record.sender = mail.sender
+ record.senderShop = mail.senderShop
+ record.expire = Date.now() + 3600 * 24 * 15 * 1000
+ await record.save()
+ return record
+ }
+
+ public static async updateExpire(accountId: string) {
+ await UserMail.updateMany(
+ { accountId, deleted: false, expire: { $lt: Date.now() } },
+ { $set: { deleted: true, deleteTime: new Date() } },
+ )
+ }
+
+ public static async deleteMails(accountId: string, ids: string[]) {
+ return UserMail.updateMany(
+ { accountId, deleted: false, _id: { $in: ids } },
+ { $set: { deleted: true, deleteTime: new Date() } },
+ )
+ }
+
+ public static async updateMails(accountId: string, ids: string[], status: number) {
+ return UserMail.updateMany({ accountId, deleted: false, _id: { $in: ids } }, { $set: { status: status } })
+ }
+}
+export const UserMail = getModelForClass(UserMailClass, { existingConnection: UserMailClass.db })