feat: migrate to PostgreSQL GORM and MinIO file storage
Use UUID models and soft deletion, persist file relationships with deduplicated monthly MinIO objects, and add admin file management. Update mini-program media handling and add local dev configuration.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
const { uploadReference } = require('../../utils/file-media');
|
||||
const { itemsOf, startList, current, mergeUnique, applyList, failList, finishList, withListPagination } = require('../../utils/list-pagination');
|
||||
const { currentUserLocation } = require('../../utils/user-location');
|
||||
const { photoForm, metadataRows } = require('../../utils/photo-metadata');
|
||||
@@ -168,7 +169,7 @@ Page(withDataCache(withListPagination({
|
||||
(photos || []).forEach(function(p) {
|
||||
var batchId = p.batch_id || p.created_at || 'default';
|
||||
if (!batchMap[batchId]) {
|
||||
var cid = parseInt(p.created_by) || 0;
|
||||
var cid = String(p.created_by) || 0;
|
||||
batchMap[batchId] = {
|
||||
batch_id: batchId,
|
||||
creator_name: nameMap[cid] || defaultName,
|
||||
@@ -358,7 +359,7 @@ Page(withDataCache(withListPagination({
|
||||
formData: { sub_dir: 'photos' }
|
||||
});
|
||||
// 兼容不同响应格式:{path:'...'} 或 {success:true, path:'...'} 或 {url:'...'}
|
||||
var uploadedPath = uploadRes && (uploadRes.path || uploadRes.url || '');
|
||||
var uploadedPath = uploadReference(uploadRes);
|
||||
if (uploadedPath) {
|
||||
await api.photoAdd({
|
||||
album_id: String(albumId),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const { uploadReference } = require('../../utils/file-media');
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/checkin-detail/checkin-detail.js
|
||||
@@ -14,9 +15,9 @@ Page(withDataCache({
|
||||
this.setData(getLayout());
|
||||
var id = options.id;
|
||||
if (id) {
|
||||
this.setData({ detailId: parseInt(id) });
|
||||
this.setData({ detailId: String(id) });
|
||||
this.loadCoupleInfo();
|
||||
this.loadDetail(parseInt(id));
|
||||
this.loadDetail(String(id));
|
||||
}
|
||||
var self = this;
|
||||
var swipe = createSwipeBack(function() { wx.navigateBack(); });
|
||||
@@ -191,13 +192,9 @@ Page(withDataCache({
|
||||
name: 'file',
|
||||
formData: { sub_dir: 'checkins' }
|
||||
});
|
||||
photoPath = upResult.path || '';
|
||||
photoPath = uploadReference(upResult);
|
||||
} catch(e) {
|
||||
// 内容安全违规:抛出错误中断打卡并提示用户
|
||||
if (e && e.error) {
|
||||
throw e;
|
||||
}
|
||||
photoPath = '';
|
||||
throw e;
|
||||
}
|
||||
wx.hideLoading();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ Page(withDataCache({
|
||||
this.setData(getLayout());
|
||||
var diaryId = options.id;
|
||||
if (!diaryId) { wx.showToast({ title: '参数错误', icon: 'none' }); return; }
|
||||
this.setData({ diaryId: parseInt(diaryId), myUid: wx.getStorageSync('userId') || 0 });
|
||||
this.setData({ diaryId: String(diaryId), myUid: wx.getStorageSync('userId') || 0 });
|
||||
this.loadDiary();
|
||||
this.loadComments();
|
||||
},
|
||||
@@ -90,7 +90,7 @@ Page(withDataCache({
|
||||
var result = await api.diaryCommentList({diary_id:this.data.diaryId,page:page,page_size:20});
|
||||
if (generation !== this._commentsGeneration) return;
|
||||
var added = itemsOf(result).map(function(comment) {
|
||||
return Object.assign({}, comment, {_time:String(comment.created_at || comment.create_time || '').slice(0,16).replace('T',' ')});
|
||||
return Object.assign({}, comment, {avatar_url:getImageUrl(comment.avatar_url || comment.avatar || ''),_time:String(comment.created_at || comment.create_time || '').slice(0,16).replace('T',' ')});
|
||||
});
|
||||
this.setData({comments:page===1?added:mergeUnique(this.data.comments,added),commentPage:page,commentsTotal:totalOf(result),hasMoreComments:!Array.isArray(result)&&(result.has_more != null?!!result.has_more:page*20<totalOf(result))});
|
||||
} catch(e) {
|
||||
@@ -186,6 +186,7 @@ Page(withDataCache({
|
||||
this._commentsFirst = false;
|
||||
this.setData({commentsLoadingMore:false});
|
||||
var newComment = res.comment || {};
|
||||
newComment.avatar_url = getImageUrl(newComment.avatar_url || newComment.avatar || "");
|
||||
if (newComment.created_at) {
|
||||
newComment._time = newComment.created_at.substring(0, 16).replace('T', ' ');
|
||||
} else if (newComment.create_time) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const { uploadReference } = require('../../utils/file-media');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
const { api, API_BASE, getImageUrl, uploadFileCompat } = require('../../utils/request');
|
||||
const { guardStorageSpace } = require('../../utils/storage-guard');
|
||||
@@ -24,7 +25,7 @@ Page({
|
||||
wx.showToast({ title: '参数错误', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
this.setData({ diaryId: parseInt(id) });
|
||||
this.setData({ diaryId: String(id) });
|
||||
this.loadDiary();
|
||||
},
|
||||
|
||||
@@ -50,7 +51,7 @@ Page({
|
||||
'form.lng': data.lng || 0,
|
||||
'form.location': data.location || '',
|
||||
tempImages: tempImages,
|
||||
existingImages: imgs,
|
||||
existingImages: Array.isArray(data.image_file_ids) && data.image_file_ids.length === imgs.length ? data.image_file_ids : imgs,
|
||||
contentLength: content.length
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -110,10 +111,10 @@ Page({
|
||||
var existing = this.data.existingImages || [];
|
||||
var temps = this.data.tempImages || [];
|
||||
|
||||
// 判断哪些是新上传的图片(tempFilePaths 以 http 开头的是已有图片,否则是新选的)
|
||||
// 已有图片通过原始引用识别;开发工具的本地图片也可能以 http 开头。
|
||||
for (var i = 0; i < temps.length; i++) {
|
||||
var p = temps[i];
|
||||
if (p.indexOf('http') === 0) {
|
||||
if (existing[i] && getImageUrl(existing[i]) === p) {
|
||||
// 已有图片,用原始路径
|
||||
uploadPaths.push(existing[i] || '');
|
||||
} else {
|
||||
@@ -149,42 +150,16 @@ Page({
|
||||
},
|
||||
|
||||
_uploadImage: async function(filePath) {
|
||||
try {
|
||||
var data = await uploadFileCompat({
|
||||
url: API_BASE + '/upload',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: { sub_dir: 'diaries' }
|
||||
});
|
||||
var path = data && (data.path || data.url || '');
|
||||
if (!path) {
|
||||
// 内容安全检测失败:抛出错误提示用户
|
||||
if (data && data.error) {
|
||||
throw data;
|
||||
}
|
||||
try {
|
||||
var retryData = await uploadFileCompat({
|
||||
url: API_BASE + '/upload',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: { sub_dir: 'diaries' }
|
||||
});
|
||||
path = retryData && (retryData.path || retryData.url || '');
|
||||
} catch(retryErr) {
|
||||
console.error('图片上传重试失败', retryErr);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
} catch(e) {
|
||||
// 内容安全违规错误:直接抛出,中断发布并提示用户
|
||||
if (e && e.error) {
|
||||
throw e;
|
||||
}
|
||||
console.error('图片上传失败', e);
|
||||
return '';
|
||||
}
|
||||
var data = await uploadFileCompat({
|
||||
url: API_BASE + '/upload',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: { sub_dir: 'diaries' }
|
||||
});
|
||||
return uploadReference(data);
|
||||
},
|
||||
|
||||
|
||||
goBack: function() {
|
||||
wx.navigateBack();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const { uploadReference } = require('../../utils/file-media');
|
||||
const { itemsOf, totalOf } = require('../../utils/list-pagination');
|
||||
const { withDataCache } = require('../../utils/page-cache');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
@@ -359,7 +360,7 @@ Page(withDataCache({
|
||||
hasPartner: true,
|
||||
partnerName: partner.nickname || partner.username || 'TA',
|
||||
partnerDisplayId: partner.user_id || '',
|
||||
partnerId: Number(partner.id),
|
||||
partnerId: String(partner.id),
|
||||
partnerDetail: details.join(' · ') || 'TA的信息',
|
||||
partnerAvatarUrl: getImageUrl(partner.avatar),
|
||||
partnerInitial: (partner.nickname || partner.username || '?')[0],
|
||||
@@ -511,7 +512,7 @@ Page(withDataCache({
|
||||
const data = await api.uploadAvatar(filePath);
|
||||
self.setData({
|
||||
editAvatarUrl: getImageUrl(data.path),
|
||||
editAvatarPath: data.path
|
||||
editAvatarPath: uploadReference(data)
|
||||
});
|
||||
wx.showToast({ title: '头像上传成功 ✨', icon: 'success' });
|
||||
} catch (e) {
|
||||
@@ -663,7 +664,7 @@ Page(withDataCache({
|
||||
self.setData({partnerAvatarUploading:true});
|
||||
try {
|
||||
const uploaded = await api.uploadPartnerAvatar(res.tempFilePaths[0],partnerId);
|
||||
self.setData({editPartnerAvatarUrl:getImageUrl(uploaded.path),editPartnerAvatarPath:uploaded.path});
|
||||
self.setData({editPartnerAvatarUrl:getImageUrl(uploaded.path),editPartnerAvatarPath:uploadReference(uploaded)});
|
||||
} catch(error) { wx.showToast({title:error.error || '头像上传失败',icon:'none'}); }
|
||||
finally { self.setData({partnerAvatarUploading:false}); }
|
||||
}});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const { uploadReference } = require('../../utils/file-media');
|
||||
const { getLayout } = require('../../utils/layout');
|
||||
// pages/publish/publish.js - 统一发布页(增强版编辑器)
|
||||
const { api, API_BASE, uploadFileCompat } = require('../../utils/request');
|
||||
@@ -50,7 +51,7 @@ Page({
|
||||
onLoad: function(options) {
|
||||
this.setData(getLayout());
|
||||
var type = options.type || 'timeline';
|
||||
var editId = options.edit ? parseInt(options.edit) : 0;
|
||||
var editId = options.edit ? String(options.edit) : "";
|
||||
var cfg = TYPE_CONFIG[type] || TYPE_CONFIG['timeline'];
|
||||
var titleText = editId ? '编辑事件' : cfg.title;
|
||||
var btnText = editId ? '保存修改' : cfg.btn;
|
||||
@@ -173,7 +174,7 @@ Page({
|
||||
repeat_unit: item.repeat_unit || ''
|
||||
};
|
||||
// 加载背景图
|
||||
var bgPath = item.bg_image || '';
|
||||
var bgPath = item.bg_image_file_id || item.bg_image || '';
|
||||
var bgUrl = bgPath ? require('../../utils/request').getImageUrl(bgPath) : '';
|
||||
var repeatOptionIdx = 0;
|
||||
if (form.repeat_interval > 0 && form.repeat_unit) {
|
||||
@@ -384,9 +385,8 @@ Page({
|
||||
name: 'file',
|
||||
formData: { sub_dir: 'timeline_bg' }
|
||||
});
|
||||
bgImagePath = uploadRes && (uploadRes.path || uploadRes.url || '');
|
||||
} catch(e) {}
|
||||
wx.hideLoading();
|
||||
bgImagePath = uploadReference(uploadRes);
|
||||
} finally { wx.hideLoading(); }
|
||||
}
|
||||
var result;
|
||||
if (editId) {
|
||||
@@ -451,93 +451,22 @@ Page({
|
||||
},
|
||||
|
||||
_uploadAlbumCover: async function(filePath) {
|
||||
try {
|
||||
var data = await uploadFileCompat({
|
||||
url: API_BASE + '/upload',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: { sub_dir: 'albums' }
|
||||
});
|
||||
// 兼容不同响应格式:{path:'...'} 或 {success:true, path:'...'} 或 {url:'...'}
|
||||
var path = data && (data.path || data.url || '');
|
||||
if (!path) {
|
||||
// 内容安全检测失败:抛出错误提示用户
|
||||
if (data && data.error) {
|
||||
throw data;
|
||||
}
|
||||
console.error('封面上传返回无路径', JSON.stringify(data), '错误码:', data && data.code);
|
||||
// 尝试重新上传一次
|
||||
try {
|
||||
var retryData = await uploadFileCompat({
|
||||
url: API_BASE + '/upload',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: { sub_dir: 'albums' }
|
||||
});
|
||||
var retryPath = retryData && (retryData.path || retryData.url || '');
|
||||
if (retryPath) {
|
||||
console.log('封面上传重试成功');
|
||||
return retryPath;
|
||||
}
|
||||
} catch(retryErr) {
|
||||
console.error('封面上传重试也失败', retryErr, '错误码:', retryErr && retryErr.code);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
} catch(e) {
|
||||
// 内容安全违规错误:直接抛出,中断发布并提示用户
|
||||
if (e && e.error) {
|
||||
throw e;
|
||||
}
|
||||
console.error('封面上传失败', e, '错误码:', e && e.code);
|
||||
return '';
|
||||
}
|
||||
return this._uploadMedia(filePath, 'albums');
|
||||
},
|
||||
|
||||
_uploadDiaryImage: async function(filePath) {
|
||||
try {
|
||||
var data = await uploadFileCompat({
|
||||
url: API_BASE + '/upload',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: { sub_dir: 'diaries' }
|
||||
});
|
||||
// 兼容不同响应格式:{path:'...'} 或 {success:true, path:'...'} 或 {url:'...'}
|
||||
var path = data && (data.path || data.url || '');
|
||||
if (!path) {
|
||||
// 内容安全检测失败:抛出错误提示用户
|
||||
if (data && data.error) {
|
||||
throw data;
|
||||
}
|
||||
console.error('日记图片上传返回无路径', JSON.stringify(data), '错误码:', data && data.code);
|
||||
// 尝试重新上传一次
|
||||
try {
|
||||
var retryData = await uploadFileCompat({
|
||||
url: API_BASE + '/upload',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: { sub_dir: 'diaries' }
|
||||
});
|
||||
var retryPath = retryData && (retryData.path || retryData.url || '');
|
||||
if (retryPath) {
|
||||
console.log('日记图片重试上传成功');
|
||||
return retryPath;
|
||||
}
|
||||
} catch(retryErr) {
|
||||
console.error('日记图片重试上传也失败', retryErr, '错误码:', retryErr && retryErr.code);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
} catch(e) {
|
||||
// 内容安全违规错误:直接抛出,中断发布并提示用户
|
||||
if (e && e.error) {
|
||||
throw e;
|
||||
}
|
||||
console.error('日记图片上传失败', e, '错误码:', e && e.code);
|
||||
return '';
|
||||
}
|
||||
return this._uploadMedia(filePath, 'diaries');
|
||||
},
|
||||
|
||||
_uploadMedia: async function(filePath, subDir) {
|
||||
var data = await uploadFileCompat({
|
||||
url: API_BASE + '/upload',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: { sub_dir: subDir }
|
||||
});
|
||||
return uploadReference(data);
|
||||
},
|
||||
// ===== 背景图 =====
|
||||
chooseBgImage: function() {
|
||||
var self = this;
|
||||
|
||||
@@ -74,8 +74,8 @@ Page(withDataCache({
|
||||
this.setData(getLayout());
|
||||
var id = options.id;
|
||||
if (id) {
|
||||
this.setData({ detailId: parseInt(id) });
|
||||
this.loadDetail(parseInt(id));
|
||||
this.setData({ detailId: String(id) });
|
||||
this.loadDetail(String(id));
|
||||
}
|
||||
var self = this;
|
||||
var swipe = createSwipeBack(function() { wx.navigateBack(); });
|
||||
@@ -108,7 +108,7 @@ Page(withDataCache({
|
||||
try {
|
||||
var item = await api.timelineDetail(id);
|
||||
var myUid = wx.getStorageSync('userId');
|
||||
var cid = parseInt(item.created_by) || 0;
|
||||
var cid = String(item.created_by) || 0;
|
||||
|
||||
// 获取情侣信息
|
||||
try {
|
||||
|
||||
@@ -101,7 +101,7 @@ Page(withDataCache(withListPagination({
|
||||
|
||||
var today = new Date(); today.setHours(0,0,0,0);
|
||||
var items = itemsOf(list).map(function(item, idx) {
|
||||
var cid = parseInt(item.created_by) || 0;
|
||||
var cid = String(item.created_by) || 0;
|
||||
item.creator_name = nameMap[cid] || (cid == myUid ? '我' : 'TA');
|
||||
item.creator_avatar = avatarMap[cid] || '';
|
||||
item.creator_initial = (item.creator_name || '?')[0].toUpperCase();
|
||||
|
||||
Reference in New Issue
Block a user