feat: note字段改为{text,images}对象结构,新增/api/upload/image接口

This commit is contained in:
Developer
2026-05-17 12:46:53 +08:00
parent 0ff5a02155
commit bfbfdccdea
6 changed files with 175 additions and 9 deletions
+60
View File
@@ -0,0 +1,60 @@
# 小程序后台 — 部署配置参考
> 摘录自 `D:\003_Project\004_Git\REPOS.md`,仅保留公共部分 + miniapp-api 相关内容。
## 服务器
| 项 | 值 |
|----|-----|
| IP | `8.136.137.59` |
| SSH 密钥 | `D:\003_Project\小程序连接.pem` |
| SSH 连接 | `ssh -i "D:\003_Project\小程序连接.pem" root@8.136.137.59` |
## Gitea
| 项 | 值 |
|----|-----|
| 网址 | `https://git.dxz99wyr.cn` |
| 用户名 | `Superuser` |
| 密码 | `Admin@123` |
| SSH Git 端口 | `2222` |
| SSH Git 地址格式 | `ssh://git@8.136.137.59:2222/Superuser/<仓库名>.git` |
| HTTP Git 地址格式 | `https://git.dxz99wyr.cn/Superuser/<仓库名>.git` |
## miniapp-api(小程序后台)
| 项 | 值 |
|----|-----|
| Git 地址 (SSH) | `ssh://git@8.136.137.59:2222/Superuser/miniapp-api.git` |
| Git 地址 (HTTPS) | `https://git.dxz99wyr.cn/Superuser/miniapp-api.git` |
| 本地路径 | `D:\003_Project\WeixinProject\QuanYiXiaoZhuShou\backend` |
| 服务器路径 | `/opt/ALiYunManager/services/miniapp-api` |
| Docker 服务名 | `miniapp-api` |
| 容器名 | `miniapp-api` |
| 部署方式 | `git pull``docker compose -f /opt/ALiYunManager/docker-compose.yml up -d --no-deps --force-recreate miniapp-api` |
| Webhook Secret | `miniapp-api-deploy-secret` |
## 日常开发流程
```bash
git add .
git commit -m "feat: 描述你的改动"
git push
# ↑ push 后自动触发 Webhook → 服务器拉代码 → 重启容器,无需手动部署
```
## 服务器常用命令
```bash
# 查看所有容器
ssh -i "D:\003_Project\小程序连接.pem" root@8.136.137.59 "docker ps"
# 查看部署日志
ssh -i "D:\003_Project\小程序连接.pem" root@8.136.137.59 "tail -f /home/Git/logs/deploy.log"
# 重启 miniapp-api
ssh -i "D:\003_Project\小程序连接.pem" root@8.136.137.59 "cd /opt/ALiYunManager && docker compose up -d --no-deps --force-recreate miniapp-api"
# 重载 main-nginx
ssh -i "D:\003_Project\小程序连接.pem" root@8.136.137.59 "docker exec main-nginx nginx -t && docker exec main-nginx nginx -s reload"
```
+3 -1
View File
@@ -20,6 +20,7 @@ const equityDetailRoutes = require('./routes/equityDetail');
const membershipRoutes = require('./routes/membership'); const membershipRoutes = require('./routes/membership');
const settingsRoutes = require('./routes/settings'); const settingsRoutes = require('./routes/settings');
const subscribeRoutes = require('./routes/subscribe'); const subscribeRoutes = require('./routes/subscribe');
const uploadRoutes = require('./routes/upload');
const wechatMessageRoutes = require('./routes/wechatMessage'); const wechatMessageRoutes = require('./routes/wechatMessage');
const cron = require('node-cron'); const cron = require('node-cron');
@@ -69,6 +70,7 @@ app.use('/api/equity-detail', equityDetailRoutes);
app.use('/api/membership', membershipRoutes); app.use('/api/membership', membershipRoutes);
app.use('/api/settings', settingsRoutes); app.use('/api/settings', settingsRoutes);
app.use('/api/subscribe', subscribeRoutes); app.use('/api/subscribe', subscribeRoutes);
app.use('/api/upload', uploadRoutes);
app.use('/wechat-message', wechatMessageRoutes); app.use('/wechat-message', wechatMessageRoutes);
@@ -76,7 +78,7 @@ const adminRoutes = require('./routes/admin');
app.use('/api/admin', adminRoutes); app.use('/api/admin', adminRoutes);
const { sendExpiryReminders } = require('./routes/subscribe'); const { sendExpiryReminders } = require('./routes/subscribe');
cron.schedule('30 21 * * *', async () => { cron.schedule('40 9 * * *', async () => {
console.log(`[${new Date().toISOString()}] ⏰ 到期提醒定时任务开始执行...`); console.log(`[${new Date().toISOString()}] ⏰ 到期提醒定时任务开始执行...`);
try { try {
const results = await sendExpiryReminders(); const results = await sendExpiryReminders();
+2 -2
View File
@@ -120,8 +120,8 @@ const userEquitySchema = new mongoose.Schema({
default: 'active' default: 'active'
}, },
note: { note: {
type: String, text: { type: String, default: '' },
default: '' images: [{ type: String }]
}, },
hasUsedBenefit: { hasUsedBenefit: {
type: Boolean, type: Boolean,
+35 -3
View File
@@ -177,17 +177,49 @@ async function sendExpiryReminders() {
try { try {
const equities = await UserEquity.find({ const equities = await UserEquity.find({
owner: sub.userId, owner: sub.userId,
status: 'active', status: 'active'
expireDate: targetDateStr
}); });
for (const equity of equities) { for (const equity of equities) {
const dayLabel = daysBefore === 0 ? '今天' : `${daysBefore}`; const dayLabel = daysBefore === 0 ? '今天' : `${daysBefore}`;
let shouldSend = false;
let reminderTarget = null;
if (equity.expireDate === targetDateStr) {
shouldSend = true;
reminderTarget = {
name: `${equity.platform}${equity.type}`,
type: 'platform'
};
}
if (!shouldSend && equity.benefits && equity.benefits.length > 0) {
for (const benefit of equity.benefits) {
if (benefit.expireDate === targetDateStr) {
shouldSend = true;
reminderTarget = {
name: benefit.name,
type: 'benefit'
};
break;
}
}
}
if (!shouldSend) continue;
let thing2;
if (reminderTarget.type === 'platform') {
thing2 = `您的${reminderTarget.name}将在${dayLabel}到期`;
} else {
thing2 = `您的${equity.platform}权益「${reminderTarget.name}」将在${dayLabel}到期`;
}
const result = await WechatSubscribeService.sendExpiryReminderMessage({ const result = await WechatSubscribeService.sendExpiryReminderMessage({
openid: sub.openid, openid: sub.openid,
templateId: sub.templateId, templateId: sub.templateId,
thing1: equity.platform, thing1: equity.platform,
thing2: `您的${equity.platform}${equity.type}将在${dayLabel}到期`, thing2: thing2,
phrase3: daysBefore === 0 ? '已到期' : '即将到期' phrase3: daysBefore === 0 ? '已到期' : '即将到期'
}); });
+63
View File
@@ -0,0 +1,63 @@
const express = require('express');
const router = express.Router();
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const { auth } = require('../middleware/auth');
const uploadDir = path.join(__dirname, '../../public/uploads/equity-notes');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, uploadDir);
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const ext = path.extname(file.originalname || '') || '.jpg';
cb(null, 'note-' + uniqueSuffix + ext);
}
});
const upload = multer({
storage,
limits: { fileSize: 10 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
const allowedTypes = /jpeg|jpg|png|gif|webp/;
const extname = allowedTypes.test(path.extname(file.originalname || '').toLowerCase());
const mimetype = allowedTypes.test(file.mimetype || '');
if (extname || mimetype) {
cb(null, true);
} else {
cb(new Error('只允许上传图片文件'));
}
}
});
router.post('/image', auth, upload.single('file'), async (req, res, next) => {
try {
if (!req.file) {
return res.status(400).json({
success: false,
error: '请选择要上传的图片'
});
}
const filename = req.file.filename;
const baseUrl = process.env.SERVER_URL || 'https://api-miniapp.dxz99wyr.cn';
const url = `${baseUrl}/uploads/equity-notes/${filename}`;
res.json({
success: true,
data: {
url
}
});
} catch (error) {
next(error);
}
});
module.exports = router;
+12 -3
View File
@@ -158,7 +158,10 @@ router.post('/', auth, requireVip, async (req, res, next) => {
benefits: benefits || [], benefits: benefits || [],
owner: req.user._id, owner: req.user._id,
status: status || 'active', status: status || 'active',
note: note || '', note: {
text: (note && note.text) || '',
images: (note && note.images) || []
},
syncedAt: new Date().toISOString() syncedAt: new Date().toISOString()
}; };
@@ -227,7 +230,10 @@ router.post('/batch', auth, requireVip, async (req, res, next) => {
benefits: benefits || [], benefits: benefits || [],
owner: req.user._id, owner: req.user._id,
status: status || 'active', status: status || 'active',
note: note || '', note: {
text: (note && note.text) || '',
images: (note && note.images) || []
},
syncedAt: new Date().toISOString() syncedAt: new Date().toISOString()
}; };
@@ -287,7 +293,10 @@ router.put('/:id', auth, requireVip, async (req, res, next) => {
if (price !== undefined) updates.price = parseFloat(price) || 0; if (price !== undefined) updates.price = parseFloat(price) || 0;
if (benefits !== undefined) updates.benefits = benefits; if (benefits !== undefined) updates.benefits = benefits;
if (status !== undefined) updates.status = status; if (status !== undefined) updates.status = status;
if (note !== undefined) updates.note = note; if (note !== undefined) updates.note = {
text: (note && note.text) || '',
images: (note && note.images) || []
};
if (platformType !== undefined) updates.platformType = platformType; if (platformType !== undefined) updates.platformType = platformType;
if (brandIcon !== undefined) updates.brandIcon = brandIcon; if (brandIcon !== undefined) updates.brandIcon = brandIcon;
if (brandIconImage !== undefined) updates.brandIconImage = brandIconImage; if (brandIconImage !== undefined) updates.brandIconImage = brandIconImage;