# YangPan **Repository Path**: qdds666/YangPan ## Basic Information - **Project Name**: YangPan - **Description**: 基于uniapp+unicloud开发的多功能网盘 基于oss 实现文件的上传/下载/删除/创建 基于环信IM 实现聊天 社交模块 发布(图片/语音/视频)/点赞/二级评论/通知/删除 个人中心(第三方登录/短信) 效率管理 uchart - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 58 - **Forks**: 17 - **Created**: 2020-09-28 - **Last Updated**: 2025-10-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 啊洋秃头之路 # 技术栈 全程手撕组件,目前写过的全栈项目中最满意的项目 响应式官网[响应式官网](http://47.114.114.84:5331) 官网:[APP官网](https://static-5fca2525-737d-4882-8b12-0d20756461bf.bspapp.com/YangPanOfficial/index.html) API接口文档:[APP端接口文档](https://static-5fca2525-737d-4882-8b12-0d20756461bf.bspapp.com/doc/index.html) app端: `Version:1.0.42` ​ 前端:uniapp ​ 后端:unicloud H5端:https://static-1d54e049-f81e-427d-a517-62137322ac07.bspapp.com/ 管理端 : ​ element+ unicloud + 部分node接口 # App图片展示 ![1](https://cdnforspeed.oss-cn-beijing.aliyuncs.com/Img/Gitee/YangPan/night1.jpg "night1.jpg") ![2](https://cdnforspeed.oss-cn-beijing.aliyuncs.com/Img/Gitee/YangPan/night2.jpg "night2.jpg") ![3](https://cdnforspeed.oss-cn-beijing.aliyuncs.com/Img/Gitee/YangPan/night3.jpg "night3.jpg") ![4](https://cdnforspeed.oss-cn-beijing.aliyuncs.com/Img/Gitee/YangPan/night4.jpg "night4.jpg") ![5](https://cdnforspeed.oss-cn-beijing.aliyuncs.com/Img/Gitee/YangPan/night5.jpg "night5.jpg") ![6](https://cdnforspeed.oss-cn-beijing.aliyuncs.com/Img/Gitee/YangPan/night_6.jpg "night6.jpg") 杂技能点: ```js // uniapp块: vuex+axios+uni路由,canvas,uchart,视频/音频组件 // Vue块: vue的过渡动画,监听器,计算,页面通信多种方法 // H5+块: downloader,uploader,IO,runtime // app业务块: 引导/启动页,短信/账号注册登录修改重置,非对称加密,切换账号,app版本更新(apk/wgt) 评论点赞系统(二评论),通知系统 // OSS块: 上传下载,文件批量/单个删除,sts鉴权,签名的使用 // unicloud块: unicloud的常操,双/多连表,模糊,排序,正则过滤 // 即时通信块: 即时通信的业务逻辑,本地聊天记录缓存数据设计 ``` 有个app逻辑md文件 # 运行配置 ## 需要创建的表及字段标明在table文件夹内 ## 你需要改oss配置文件的 region: 'oss-cn-hangzhou', bucket: 'XXXXXXXXXXXXXXX', accessKeyId: 'XXXXXXXXXXXXXXX', accessKeySecret: 'XXXXXXXXXXXXXXX', host: "XXXXXXXXXXXXXXX" const sts = new STS({ // RAM账号 accessKeyId: 'XXXXXXXXXXXXXXX', accessKeySecret: 'XXXXXXXXXXXXXXX' }); ​ ​ 临时sts权限的 策略 ​ StsToken = await sts.assumeRole( ​ 'acs:ram::XXXXXXXXXXXXXXX', { ​ "Statement": [{ ​ "Action": [ ​ "oss:*" ​ ], ​ "Effect": "Allow", ​ "Resource": ["acs:oss:*:*:xxxxxxxxxxx/*"] ​ }], ​ "Version": "1" ​ }, '3600', '666'); ## 你需要改SMS配置文件的 exports.SMSConfig = { register: { smsKey: 'XXXXXXXXXXXXXXX', smsSecret: 'XXXXXXXXXXXXXXX', templateId: 'XXXXXXXXXXXXXXX' }, login: { smsKey: 'XXXXXXXXXXXXXXX', smsSecret: 'XXXXXXXXXXXXXXX', templateId: 'XXXXXXXXXXXXXXX' } } # 文件目录 ``` ├─cloudfunctions-tcb // 后端目录 │ ├─common // 公共模块 │ ├─test // app端接口汇总 │ │ ├─model // 数据封装(废弃) │ │ ├─oss // oss文件配置表 │ │ ├─router // 路由 │ │ │ ├─community // 社区模块接口汇总 │ │ │ ├─file // 文件模块接口汇总 │ │ │ ├─home // 我的模块接口汇总 │ │ │ └─Update // 更新插件模块接口汇总 │ │ ├─SMS // 短信验证码配置表 │ │ └─tools // 工具类 │ └─YangPanAdmin // admin管理端汇总 │ ├─model // │ ├─oss // │ ├─router // │ └─tools // ├─common │ ├─API │ │ └─utils // 请求封装 │ ├─components // 封装组件 │ │ ├─AppHeader │ │ ├─AppHeaderNvue │ │ ├─u-message-input │ │ ├─update │ │ └─w-picker │ │ └─city-data │ ├─LocalStorage │ ├─public │ ├─Tools │ └─verify ├─components │ ├─loadImg │ ├─tki-qrcode │ ├─uni-popup │ └─uni-pullRefresh ├─pages │ ├─community // 社区 │ │ └─conpoment │ ├─file // 文件 │ │ ├─content │ │ └─header │ ├─home // 我的模块 │ │ ├─FnBlock │ │ ├─header │ │ ├─info │ │ ├─LoginOut │ │ └─OperateBar │ ├─index │ │ ├─header │ │ └─main │ ├─other │ │ ├─dialogs │ │ │ └─newFolder │ │ ├─FileTypeList │ │ ├─guide │ │ ├─ImgView │ │ ├─login │ │ │ ├─form │ │ │ ├─header │ │ │ └─thirdLogin │ │ ├─myCollect │ │ ├─myFans │ │ ├─myPublish │ │ │ └─components │ │ ├─myVisitor │ │ ├─register │ │ │ ├─form │ │ │ ├─header │ │ │ └─thirdLogin │ │ ├─Setting │ │ │ └─myInfoSet │ │ ├─SMSCodeLogin │ │ ├─UploadUrlManager │ │ └─VideoView │ └─public │ ├─map │ ├─pullRefresh │ ├─QRcode │ └─TransferPopup ├─tables │ ├─adminVersion // app版本表 │ ├─Article // 社区 │ ├─common // 评论表 │ ├─ModelList // 模型表 │ ├─notice // 通知表 │ ├─SMSCache // 短信表 │ ├─UploadImg // 上传图片表 │ ├─UploadRecord // 上传记录表 │ ├─User // 用户表 ``` # APP端问题汇总 ## 文件夹路径的 ## 文件上传 ## 文件的下载,多线程并发 ## 删除改文件夹下的所有文件(遍历查询删除) 阿里的SDK无法做到批量删除 只能自己写算法递归删除 ``` 递归添加文件夹到文件夹数组 文件要先删除,然后文件夹目录必须得从最里层开始删除(计算),否则会删除不了 文件夹排序做算法(代码内) ``` ## 获取指定目录下的指定文件 ```js exports.getSpecifyTypeFile = async (body_data)=>{ let RES = { code:200,msg:"操作成功~"} let {UserInfo,FileType,pageNum,pageSize} = body_data if(FileType!='ImgTypes' && FileType!='ViodeoTypes' && FileType!='AudioTypes') return {code:204,msg:"参数错误"} let skips = pageNum * pageSize let OSSRes = await alioss.FolderList(UserInfo.account+'/') let FolederList = OSSRes.prefixes // 文件夹路径 let FileArray = [...(OSSRes.objects.slice(1))] // 返回的所有文件数组 // 根据跟目录拿出该文件下的所有文件类型 let digui = async(list)=>{ for(let i=0;i0){ RRR.objects.forEach( (item,index)=>{ index ==0 ? "":FileArray.push(item) }) } if(RRR.prefixes && RRR.prefixes.length>0){ digui(RRR.prefixes) } } } await digui(FolederList) let resultArray = [] // 要返回出去的数据 FileArray.forEach(i=>{ if(FileTypeTest[`${FileType}`]().includes(i.name.match(/[^\.]\w*$/)[0])){ resultArray.push(i) } }) RES.data = resultArray return RES } ``` ## 社区模块 ### 发布文章模块 用户表关联文章表 文章表关联评论表 评论表做二级评论 ```json // 文章表 { "_id":"文章ID", "contents":"文章文字内容", "ImgList":{ // "图片数组" id:"", url:"" }, PublishTime:"", // 发布时间 likeCount:"", // 点赞次数 commonCount:"", // 评论人数 shareCount:"", // 转发人数 viewCount:"", // 阅读人数 } ``` ### 联表查询 ```js // 返回文章列表信息 let result = await transaction.collection('Article').aggregate().lookup({ from:'User', localField:'UserId', foreignField:'_id', as:"UserInfo" }).lookup({ from:'common', localField:'_id', foreignField:'articleId', as:"CommonList" }).sort({PublishTime:-1}).limit(pageSize).skip(Skips).end() ``` ### 三联表查询 ```js // 返回评论列表信息 // parentId:null是找出所有的一级评论 let result = await db.collection('common').aggregate().match({ articleId,parentId:null }).lookup({ from:'User', localField:'UserId', foreignField:'_id', as:"UserInfo" }).lookup({ from:'common', localField:'_id', foreignField:'parentId', as:"SecondList" }).sort({commonTime:-1}).limit(pageSize).skip(Skips).end() ``` ### 找出当前文档下的对象数组下某个字段与传入字段是否匹配的数据 ```js // 查询被点赞过的数据 let IsLike = await db.collection('common').aggregate().match({_id}).project({ likeList: $.filter({ input: '$likeList', as: 'item', cond: $.eq(['$$item.UserId', UserId]) }) }).end() ``` # admin端问题汇总 ## 条件查询模糊查询 ```js let result = await db.collection('User').where(Object.assign( SelJson,{ Nickname:new RegExp(`${Nickname}`), account:new RegExp(`${account}`) }) ).limit(pageSize).skip(Skips).get() ``` ## 条件查询日期范围 ```js let like_Json = {} like_Json['registerTime'] = dbCmd.and([ dbCmd.gte(new Date(registerTimeBeginTime)), dbCmd.lte(new Date(registerTimeEndTime)) ]) let result = await db.collection('User').where(Object.assign( SelJson,like_Json ).limit(pageSize).skip(Skips).get() ``` ## 数据库的删除(事务) admin管理端删除 用户 删除oss对象和数据的用户信息 ```js exports.DelUser = async function(body_data) { let res = { code:200, msg:"成功~" } let {UserInfo,_id} = body_data if (!_id.toString()) {return {msg: "_id必传"}} try { const result = await db.runTransaction(async transaction => { let SelRes = await transaction.collection('User').doc(_id).get() if (SelRes.data) { try { let ossRes = null let DelRes = await db.collection('User').doc(_id).remove() // 删除用户信息 if(DelRes.deleted>0){ ossRes = await digui_del(SelRes.data.account+'/') // 删除oss的数据 (app端的文件数据) if(ossRes===200){ ossRes = await digui_del('uploadImg/'+SelRes.data.account+'/') // 删除oss的数据(uploadImg下该用户的所有信息) } } console.log(`事务全部成功~`) return{ ossRes, DelRes } } catch (e) { // 会作为 runTransaction reject 的结果出去 await transaction.rollback('oss,remove') } } else { // 会作为 runTransaction reject 的结果出去 await transaction.rollback('查询User') } }) return { code:200, msg:'删除成功~', data:{ ossRes:result.ossRes, DelRes:result.DelRes } } } catch (e) { console.error(`事务失败`,e) return { msg:'内部错误~', code:500 } } } ``` ## 联表查询(模糊查询的条件在另外一个表) 先使用unwind('$UserInfo') 将lookup的匹配结果扁平化,然后使用match函数匹配 ```js /****** 处理参数为空的数据 不传给数据库 ******/ let MatchJson = {UserInfo:{}} let SelArg = ['Nickname','account','sex','Region','IsCurrentUse'] // 匹配参数 let like_SelArg = ['Nickname','account'] // 需要模糊查询的参数 for(let i in SelArg){ if(body_data[SelArg[i]]){ MatchJson[SelArg[i]] = body_data[SelArg[i]] if(like_SelArg.findIndex(item=>item===SelArg[i])!==-1){ // 处理模糊查询 MatchJson.UserInfo[SelArg[i]] = new RegExp(`${body_data[SelArg[i]]}`) delete MatchJson[SelArg[i]] } } } ``` ```js let result = await transaction.collection('Article').aggregate().sort({PublishTime:-1}) .lookup({ from:'User', localField:'UserId', foreignField:'_id', as:"UserInfo" }).lookup({ from:'common', localField:'_id', foreignField:'articleId', as:"CommonList" }).unwind('$UserInfo').unwind('$CommonList') .match(MatchJson).skip(Skips).limit(pageSize).end() ``` # app端社区模块接口 ## 评论排序(CommonSort) ```js // 接口示例: { url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan', method: 'post', data:{ type:"CommonSort", SortType:"String", articleId:"String", pageSize:"Number", pageNum:"Number", UserId:"String" } } ``` ### request: ```js // 打星号必传 *SortType:"String" // 排序种类 (SortNew,SortMoreLike,SortHot) *articleId:"String" // 文章ID *pageSize:"Number" // 分页大小 *pageNum:"Number" // 当前页数 *UserId:"String" // 用户ID ``` ### respone: ```js [{ SecondList: [] UserId: "e656fa635f64c1790014f3077b54ae5a" UserInfo: [{_id: "e656fa635f64c1790014f3077b54ae5a", account: "13067271903", pwd: "1203523342zyw",…}] articleId: "e373396c5f71f75100a607e52e317e73" commonIp: "211.97.131.82" commonTime: 1601394532379 commonValue: "🐮🍺" iSlike: true likeList: [{UserId: "e656fa635f64c1790014f3077b54ae5a", UpdateTime: 1601513089441, Ip: "211.97.128.95"},…] _id: "1b64dd7b5f73576400b78cb93a6d3562" },{ SecondList: [] UserId: "e656fa635f64c1790014f3077b54ae5a" UserInfo: [{_id: "e656fa635f64c1790014f3077b54ae5a", account: "13067271903", pwd: "1203523342zyw",…}] articleId: "e373396c5f71f75100a607e52e317e73" commonIp: "211.97.131.82" commonTime: 1601394532379 commonValue: "🐮🍺" iSlike: true likeList: [{UserId: "e656fa635f64c1790014f3077b54ae5a", UpdateTime: 1601513089441, Ip: "211.97.128.95"},…] _id: "1b64dd7b5f73576400b78cb93a6d3562" }] ``` ## 评论点赞(CommonLike) ```js // 接口示例: { url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan', method: 'post', data:{ type:"CommonLike", _id:"String", UserId:"String" } } ``` ### request: ```js // 打星号必传 *_id:"String" // 该条评论的id *UserId:"String" // 用户id ``` ## 文章点赞(onLike) ```js // 接口示例: { url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan', method: 'post', data:{ type:"onLike", _id:"String", UserId:"String" } } ``` ### request ```js // 打星号必传 *_id:"String" // 该篇文章的_id *UserId:"String" // 用户id ``` ## 发布文章(PushArticle) ```js // 接口示例: { url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan', method: 'post', data:{ type:"PushArticle", pageSize:"Number", pageNum:"Number" } } ``` ### request: ```js // 打星号必传 *_id:"String" // 用户id 字段写错 add({UserId:_id}) *contents:"Number" // 发布内容 *imgList:"String" // 发布的图片数组 ``` ## 文章列表(SelArticleList) ```js // 接口示例: { url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan', method: 'post', data:{ type:"SelArticleList", pageSize:"Number", pageNum:"Number" } } ``` ### request: ```js // 打星号必传 *pageSize:"Number" // 分页大小 *pageNum:"Number" // 当前页数 UserId:"String" // 用户id 用来判断是否点赞过 ``` ## 单文章详细信息(SelArticleDetail) ```js // 接口示例: { url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan', method: 'post', data:{ type:"SelArticleDetail", articleId:"String", UserId:"String" } } ``` ### request: ```js // 打星号必传 *articleId:"String" // 文章的_id 参数写错了 // 但是转化了where({_id:articleId}) UserId:"String" // 用户id ``` ## 评论列表(一级二级)(SelCommonList) ```js // 接口示例: { url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan', method: 'post', data:{ type:"SelCommonList", articleId:"String", pageSize:"Number", pageNum:"Number" } } ``` ### request: ```js // 打星号必传 *articleId:"String" // 文章ID 联表用 *pageSize:"Number" // 分页大小 *pageNum:"Number" // 当前页数 UserId:"String" // 用户id 用来查询是否点赞过 无登录状态下就没有了 parentId:"String" // 二级评论的父评论ID 用来查询二级评论 ``` ## 发布评论(一级二级)(onCommon) ```js // 接口示例: { url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan', method: 'post', data:{ type:"onCommon", articleId:"String", UserId:"String", commonValue:"String", parentId:"String" } } ``` ### request: ```js // 打星号必传 *articleId:"String" // 文章ID *UserId:"String" // 用户id *commonValue:"String" // 评论内容 parentId:"String" // 二级评论的父评论ID 用来回复二级评论 ``` # app端我的模块接口 ## 查询指定账户的个人发布列表接口(SelPersionPublish) ```js // 接口示例: { url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan', method: 'post', data:{ type:"SelPersionPublish", pageSize:"Number", pageNum:"Number", UserId:"String", PersionId:"String" } } ``` ## request: ```js // 打星号必传 *pageSize:"Number" // 分页大小 *pageNum:"Number" // 分页的当前页数 UserId:"String" // 当前登录用户的用户id *PersionId:"String" // 要查询用户的发布信息的id ``` # app端注册/登录接口 ## 获取短信验证码接口(getSMSInterface) ```js // 接口示例: { url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan', method: 'post', data:{ type:"getSMSInterface", SMSType:"Number", phone:"Number" } } ``` ### request: ```js // 打星号必传 *SMSType:"Number" // 发送短信类型 (注册/登录) *phone:"Number" // 发送电话 ``` # 后端模块逻辑 ## 通知模块 一个通知表 来存储通知信息 ​ 通知类 :点赞插入通知信息 ​ 评论插入通知信息 ​ 关注插入通知信息 ​ 发文章插入通知信息 ​ 收藏插入通知信息 ​ 分享插入通知信息 比如我点赞一篇文章:通知表插入一条数据:带有这篇文章的发布者的用户id(noticeUserId),这篇文章的文章id(noticeArticleId),通知内容(noticeType):点赞,触发这个通知的用户(SourceUserId) 然后通知该用户(noticeUserId) ## 评论模块 ## 登录注册模块 ## 文件模块 # app端文件模块接口 # admin端接口 # unicoud手册 # 联表后数组扁平化 ## 使用unwind ```js let result = await transaction.collection('Article').aggregate().sort({PublishTime:-1}) .lookup({ from:'User', localField:'UserId', foreignField:'_id', as:"UserInfo" }).unwind('$UserInfo').match(MatchJson).skip(Skips).limit(pageSize).end() ```