啊洋秃头之路
项目源码:洋盘
技术栈
全程手撕组件,目前写过的全栈项目中最满意的项目
响应式官网响应式官网
官网:APP官网
API接口文档:APP端接口文档
app端: Version:1.0.43
前端:uniapp
后端:unicloud
H5端:https://static-1d54e049-f81e-427d-a517-62137322ac07.bspapp.com/
管理端 :
element+ unicloud + 部分node接口
App图片展示
码云里看
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6sCd4lDi-1604919142309)(")]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HchymZrM-1604919142314)(https://cdnforspeed.oss-cn-beijing.aliyuncs.com/Img/Gitee/YangPan/night2.jpg “night2.jpg”)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aqdes5un-1604919142316)(https://cdnforspeed.oss-cn-beijing.aliyuncs.com/Img/Gitee/YangPan/night3.jpg “night3.jpg”)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aZcTBD78-1604919142319)(https://cdnforspeed.oss-cn-beijing.aliyuncs.com/Img/Gitee/YangPan/night4.jpg “night4.jpg”)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T4ZqUkS0-1604919142320)(https://cdnforspeed.oss-cn-beijing.aliyuncs.com/Img/Gitee/YangPan/night5.jpg “night5.jpg”)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j7n961VY-1604919142322)(https://cdnforspeed.oss-cn-beijing.aliyuncs.com/Img/Gitee/YangPan/night_6.jpg “night6.jpg”)]
杂:
0.vuex+axios
1.vue的过渡动画,监听器,计算,页面通信多种方法,
2.canvas,plus的下载器,文件管理,
3.逻辑:短信登录/注册,账号登录(第三方)注册修改信息,评论系统逻辑(到二评论),非对称加密
4.oss的文件上传下载,文件批量/单个删除,sts鉴权
5.unicloud的常操,双/多连表,模糊,排序,正则过滤
6.即时通信的业务逻辑
有个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🐏: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无法做到批量删除 只能自己写算法递归删除
递归添加文件夹到文件夹数组
文件要先删除,然后文件夹目录必须得从最里层开始删除(计算),否则会删除不了
文件夹排序做算法(代码内)
获取指定目录下的指定文件
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;i<list.length;i++){
let RRR = await alioss.FolderList(list[i])
if(RRR.objects && RRR.objects.length>0){
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
}
社区模块
发布文章模块
用户表关联文章表 文章表关联评论表 评论表做二级评论
// 文章表
{
"_id":"文章ID",
"contents":"文章文字内容",
"ImgList":{ // "图片数组"
id:"",
url:""
},
PublishTime:"", // 发布时间
likeCount:"", // 点赞次数
commonCount:"", // 评论人数
shareCount:"", // 转发人数
viewCount:"", // 阅读人数
}
联表查询
// 返回文章列表信息
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()
三联表查询
// 返回评论列表信息 // 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()
找出当前文档下的对象数组下某个字段与传入字段是否匹配的数据
// 查询被点赞过的数据
let IsLike = await db.collection('common').aggregate().match({_id}).project({
likeList: $.filter({
input: '$likeList',
as: 'item',
cond: $.eq(['$$item.UserId', UserId])
})
}).end()
admin端问题汇总
条件查询模糊查询
let result = await db.collection('User').where(Object.assign(
SelJson,{
Nickname:new RegExp(`${Nickname}`),
account:new RegExp(`${account}`)
})
).limit(pageSize).skip(Skips).get()
条件查询日期范围
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对象和数据的用户信息
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
}
}
}
app端社区模块接口
评论排序(CommonSort)
// 接口示例:
{
url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan',
method: 'post',
data:{
type:"CommonSort",
SortType:"String",
articleId:"String",
pageSize:"Number",
pageNum:"Number",
UserId:"String"
}
}
request:
// 打星号必传
*SortType:"String" // 排序种类 (SortNew,SortMoreLike,SortHot)
*articleId:"String" // 文章ID
*pageSize:"Number" // 分页大小
*pageNum:"Number" // 当前页数
*UserId:"String" // 用户ID
respone:
[{
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)
// 接口示例:
{
url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan',
method: 'post',
data:{
type:"CommonLike",
_id:"String",
UserId:"String"
}
}
request:
// 打星号必传
*_id:"String" // 该条评论的id
*UserId:"String" // 用户id
文章点赞(onLike)
// 接口示例:
{
url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan',
method: 'post',
data:{
type:"onLike",
_id:"String",
UserId:"String"
}
}
request
// 打星号必传
*_id:"String" // 该篇文章的_id
*UserId:"String" // 用户id
发布文章(PushArticle)
// 接口示例:
{
url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan',
method: 'post',
data:{
type:"PushArticle",
pageSize:"Number",
pageNum:"Number"
}
}
request:
// 打星号必传
*_id:"String" // 用户id 字段写错 add({UserId:_id})
*contents:"Number" // 发布内容
*imgList:"String" // 发布的图片数组
文章列表(SelArticleList)
// 接口示例:
{
url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan',
method: 'post',
data:{
type:"SelArticleList",
pageSize:"Number",
pageNum:"Number"
}
}
request:
// 打星号必传
*pageSize:"Number" // 分页大小
*pageNum:"Number" // 当前页数
UserId:"String" // 用户id 用来判断是否点赞过
单文章详细信息(SelArticleDetail)
// 接口示例:
{
url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan',
method: 'post',
data:{
type:"SelArticleDetail",
articleId:"String",
UserId:"String"
}
}
request:
// 打星号必传
*articleId:"String" // 文章的_id 参数写错了 // 但是转化了where({_id:articleId})
UserId:"String" // 用户id
评论列表(一级二级)(SelCommonList)
// 接口示例:
{
url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan',
method: 'post',
data:{
type:"SelCommonList",
articleId:"String",
pageSize:"Number",
pageNum:"Number"
}
}
request:
// 打星号必传
*articleId:"String" // 文章ID 联表用
*pageSize:"Number" // 分页大小
*pageNum:"Number" // 当前页数
UserId:"String" // 用户id 用来查询是否点赞过 无登录状态下就没有了
parentId:"String" // 二级评论的父评论ID 用来查询二级评论
发布评论(一级二级)(onCommon)
// 接口示例:
{
url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan',
method: 'post',
data:{
type:"onCommon",
articleId:"String",
UserId:"String",
commonValue:"String",
parentId:"String"
}
}
request:
// 打星号必传
*articleId:"String" // 文章ID
*UserId:"String" // 用户id
*commonValue:"String" // 评论内容
parentId:"String" // 二级评论的父评论ID 用来回复二级评论
app端我的模块接口
查询指定账户的个人发布列表接口(SelPersionPublish)
// 接口示例:
{
url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan',
method: 'post',
data:{
type:"SelPersionPublish",
pageSize:"Number",
pageNum:"Number",
UserId:"String",
PersionId:"String"
}
}
request:
// 打星号必传
*pageSize:"Number" // 分页大小
*pageNum:"Number" // 分页的当前页数
UserId:"String" // 当前登录用户的用户id
*PersionId:"String" // 要查询用户的发布信息的id
app端注册/登录接口
获取短信验证码接口(getSMSInterface)
// 接口示例:
{
url: 'https://qddscxy-16b8d1.service.tcloudbase.com/YangPan',
method: 'post',
data:{
type:"getSMSInterface",
SMSType:"Number",
phone:"Number"
}
}
request:
// 打星号必传
*SMSType:"Number" // 发送短信类型 (注册/登录)
*phone:"Number" // 发送电话
后端模块逻辑
通知模块
一个通知表 来存储通知信息
通知类 :点赞插入通知信息
评论插入通知信息
关注插入通知信息
发文章插入通知信息
收藏插入通知信息
分享插入通知信息
比如我点赞一篇文章:通知表插入一条数据:带有这篇文章的发布者的用户id(noticeUserId),这篇文章的文章id(noticeArticleId),通知内容(noticeType):点赞,触发这个通知的用户(SourceUserId)
然后通知该用户(noticeUserId)