| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- import moment from 'moment';
- import fs from 'fs';
- import { promises as fsPromises } from 'fs';
- import config from '../../config/index.js';
- import _ from 'lodash';
- import path from 'path';
- import gm from 'gm';
- import axios from 'axios';
- import COS from 'cos-nodejs-sdk-v5';
- import { uploadSingle } from '../../middleware/upload.js';
- import { globalState } from '../../util/globalState.js';
- const imageMagick = gm.subClass({ imageMagick: true });
- // 允许的文件类型
- const ALLOWED_IMAGE_TYPES = new Set([
- 'jpg', 'jpeg', 'png', 'bmp', 'heic', 'heif', 'webp', 'gif'
- ]);
- const ALLOWED_AUDIO_TYPES = new Set([
- 'aac', 'mp3', 'mp4', 'm4a', 'flac', 'ogg', 'ape', 'amr',
- 'wma', 'wav', 'aiff', 'caf'
- ]);
- const BLOCKED_TYPES = new Set(['php', 'js', 'txt']);
- // 文件上传配置
- const fileFilter = (req, file, cb) => {
- const extension = path.extname(file.originalname).toLowerCase().slice(1);
-
- if (BLOCKED_TYPES.has(extension)) {
- cb(new Error('不允许上传该类型的文件!'), false);
- } else if (ALLOWED_IMAGE_TYPES.has(extension) || ALLOWED_AUDIO_TYPES.has(extension)) {
- cb(null, true);
- } else {
- cb(new Error('文件格式不支持!'), false);
- }
- };
- // 配置上传中间件
- export const uploadMiddleware = uploadSingle('file', {
- dest: './public/uploads/',
- fileFilter,
- limits: {
- fileSize: 10 * 1024 * 1024 // 10MB
- }
- });
- // 小程序文件上传控制器
- export const UploadFile = async (ctx) => {
- try {
- const file = ctx.request.file;
- if (!file) {
- ctx.body = { errcode: 1002, errMsg: "文件上传错误!" };
- return;
- }
- const extension = path.extname(file.originalname).toLowerCase().slice(1);
- const filename = file.filename;
- const filepath = file.path;
- if (ALLOWED_IMAGE_TYPES.has(extension)) {
- await checkImage(filename, filepath);
- } else if (ALLOWED_AUDIO_TYPES.has(extension)) {
- await uploadFile(filename, filepath);
- }
- ctx.body = {
- errcode: 10000,
- result: {
- Source: file.originalname,
- Target: filename
- }
- };
- } catch (error) {
- console.error('Upload error:', error);
- ctx.body = { errcode: 1001, errMsg: error.message || "上传失败!" };
-
- // 清理临时文件
- if (ctx.request.file && ctx.request.file.path) {
- try {
- await fsPromises.unlink(ctx.request.file.path);
- } catch (e) {
- console.error('Failed to clean up temp file:', e);
- }
- }
- }
- // 安全清理定时器
- setTimeout(async () => {
- try {
- const filepath = './public/uploads/';
- const files = await fsPromises.readdir(filepath);
-
- for (const file of files) {
- const extension = path.extname(file).toLowerCase();
- if (BLOCKED_TYPES.has(extension.slice(1))) {
- await fsPromises.unlink(path.join(filepath, file));
- }
- }
- } catch (error) {
- console.error('Security cleanup error:', error);
- }
- }, 5000);
- };
- // 检查并处理图片
- async function checkImage(filename, filepath) {
- if (!await fsPromises.stat(filepath)) {
- throw new Error('文件不存在');
- }
- const stats = await fsPromises.stat(filepath);
- if (stats.size >= 126976) {
- const maxSize = 750;
-
- return new Promise((resolve, reject) => {
- imageMagick(filepath).size((err, size) => {
- if (err) {
- reject(err);
- return;
- }
- if (size.width > maxSize || size.height > maxSize) {
- imageMagick(filepath)
- .resize(maxSize, maxSize)
- .write(filepath, async () => {
- try {
- await uploadImage(filename, filepath);
- resolve();
- } catch (error) {
- reject(error);
- }
- });
- } else {
- uploadImage(filename, filepath).then(resolve).catch(reject);
- }
- });
- });
- } else {
- await uploadImage(filename, filepath);
- }
- }
- // 上传图片到腾讯云
- async function uploadImage(filename, filepath) {
- const cos = new COS(config.QCloud);
-
- const params = {
- Bucket: 'miaguo-1253256735',
- Region: 'ap-guangzhou',
- Key: filename,
- Body: fs.createReadStream(filepath),
- ContentLength: fs.statSync(filepath).size
- };
- return new Promise((resolve, reject) => {
- cos.putObject(params, (err, data) => {
- if (err) {
- reject(err);
- } else {
- fsPromises.unlink(filepath)
- .then(() => resolve(data))
- .catch(reject);
- }
- });
- });
- }
- // 上传文件到腾讯云
- async function uploadFile(filename, filepath) {
- const cos = new COS(config.QCloud);
-
- const params = {
- Bucket: 'miaguo-1253256735',
- Region: 'ap-guangzhou',
- Key: filename,
- Body: fs.createReadStream(filepath),
- ContentLength: fs.statSync(filepath).size
- };
- return new Promise((resolve, reject) => {
- cos.putObject(params, (err, data) => {
- if (err) {
- reject(err);
- } else {
- fsPromises.unlink(filepath)
- .then(() => resolve(data))
- .catch(reject);
- }
- });
- });
- }
- export async function GetBaiduToken (ctx) {
- const { Code, ProgramID } = ctx.query;
-
- let appid = '', secret = '';
-
- switch (ProgramID) {
- case '99':
- appid = config.wx.phonics_appid;
- secret = config.wx.phonics_appsecret;
- break;
- case '166':
- appid = config.wx.miaoguo_appid;
- secret = config.wx.miaoguo_appsecret;
- break;
- case '105':
- appid = config.wx.math_appid;
- secret = config.wx.math_appsecret;
- break;
- case '164':
- appid = config.wx.mathStar_appid;
- secret = config.wx.mathStar_appsecret;
- break;
- }
- const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appid}&secret=${secret}&js_code=${Code}&grant_type=authorization_code`;
- const resultLogin = await axios.get(url)
- .then(response => {
- const json = response.data;
- return json.openid ? { errcode: 10000 } : { errcode: 102 };
- })
- .catch(err => ({ errcode: 101, errStr: err }));
- let result = 0;
- if (resultLogin.errcode === 10000) {
- result = globalState.getBufferMemory('BaiduToken');
- if (result === 0) {
- const baiduUrl = 'https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=9r1Idx1mgMONvElxU3KGE5Gi&client_secret=f4f606f1a5c0b4eaf800e1e046802d81';
- result = await axios.get(baiduUrl)
- .then(response => {
- const json = response.data;
- if (json.access_token) {
- globalState.SetBufferMemory('BaiduToken', json.access_token, config.BufferMemoryTimeHigh);
- return json.access_token;
- }
- return 0;
- });
- }
- }
- ctx.body = { errcode: 10000, result };
- }
|