基于云开发的校园社区小程序 微信小程序开发实战 课设作业

存档资料 成绩: 

课 程 设 计 报 告 书

所属课程名称: 微信小程序开发实战

题 目: 基于云开发的校园社区小程序

分 院:

专业班级:

学 号:

学生姓名:

指导教师:

20 年 月 日

目 录

第1章 概述22

1.1 开发目的22

1.2 开发环境22

1.3 开发技术22

第2章 需求分析33

2.1 系统功能需求分析33

第3章 系统分析与设计44

3.1 系统功能结构设计44

第4章 系统实现55

4.1 客户端实现55

4.1.1 小程序主页效果实现55

4.1.2 文章推送模块实现66

4.1.3 发帖功能实现77

4.1.4 删帖功能实现99

4.1.5 评论区功能实现1111

4.1.6 消息中心模块实现1414

4.2云开发后台实现1717

4.2.1 数据库1818

4.2.2 云存储1818

4.2.3 云函数1919

参考文献(资料)2020

致 谢2121

第1章 概述

1.1 开发目的

学生可以说是社交生活最活跃的群体了,但主流的社交APP主攻的熟人社交却无法满足校园陌生人社交的需求,而这恰好是校园小程序的机会,由交理校园社区就是款集表白墙、互助、跳蚤市场、吐槽、失物招领等板块于一体的社交类小程序。依托微信小程序官方平台接口实现了免认证图文发帖、评论帖子、回复评论、小程序内实时接收消息、小程序外接收评论或回复消息等功能。小程序的开发旨在为本校学生提供更加便捷的交流平台,让他们的校园生活更加丰富多彩。

1.2 开发环境

后台:完全依赖于云开发,无需搭建后台服务器

开发软件:微信开发者工具

操作系统:windows操作系统

1.3 开发技术

云数据库:是一个可以在小程序前端操作, 也可以在云函数中操作的json类型数据库。

云存储:提供稳定、安全、低成本、简单易用的云端存储服务,支持任意数量和形式的非结构化数据存储,如图片、文档、音频、视频、文件等。

云函数 是一个在小程序端定义编写,编写完毕后部署到云服务器,开发者无需购买、搭建服务器,只需编写函数代码并部署到云端,在云服务器中运行的nodejs函数,同时云函数之间也可互相调用。


第2章 需求分析

2.1 系统功能需求分析

1.浏览和发布内容:

浏览:为了方便用户浏览,可以按照帖子的发布时间和热度进行排序。

板块:在发帖页面最上方选择好对应板块,方便论坛用户筛选查看。

发布图片内容:可以一次上传九张图片。

2.评论区:

楼主:楼主评论回复,昵称后面带有特殊标识。

删除评论与回复:评论人或者回复人可以长按自己的回复或评论进行删除,楼主及管理员可以删除所有评论。

收到消息:楼主只接收直接评论与自己的回复被回复的消息通知,被回复者将收到回复者的消息。

3.接收消息:

在线状态:即时接收评论与回复,收到消息有振动反馈,消息栏可查看消息。

离线状态:当开启接收消息的权限以后,可以在小程序外,即未开启小程序的情况下接收来自小程序的评论与回复消息。

4.推送文章:

管理员可以在后台发布推送文章,用户则可以在小程序的推荐板块看到所推送的内容。

5.“我的”页面:

头像与昵称:授权使用微信头像与昵称。

删帖:在我发布的帖子界面,直接按住左滑会看到删除选项。

评论过的帖子:可以查看评论过的帖子。


第3章 系统分析与设计

3.1 系统功能结构设计

该系统主要包括微信小程序的功能模块和管理后台的功能模块两部分。系统总体的功能结构图如下图3.1所示:


第4章 系统实现

4.1 客户端实现

4.1.1 小程序主页效果实现

小程序主页分为五个部分,第一部分是搜索框,第二部分为轮播图,第三部分为功能导航栏,第四部分为内容浏览,第五部分为用于页面切换的底部导航。轮播图通过<swiper>组件实现,而底部导航的实现只需要在app.json中配置tab信息,这部分难度在于前后数据交互、渲染处理和控件的样式。具体主页效果如下图4.1所示:

图4.1 主页效果图

由于篇幅有限,这里仅展示小程序主页轮播图实现的关键代码如下:

//获取首页轮播图地址(管理openid)并赋值到data!

guanggao(){

db.collection('system').where({'_id':'001'})

.get().then((res)=>{

console.log("轮播图:",res.data[0].lunbotu)

this.setData({

lunbotu:res.data[0].lunbotu,

glid:res.data[0].glid

})

app.glid=res.data[0].glid

})

},

Wxml文件的关键代码如下:

<!-- 轮播图 -->

<view>

<swiper indicator-dots="{{true}}" indicator-color="#f4c903" circular="true">

<block wx:for="{{banner}}" wx:key="banner">

<swiper-item>

<image src='{{item.picUrl}}' mode="aspectFill" style="width:100%;height:100%;" />

</swiper-item>

</block>

</swiper>

</view>

4.1.2 文章推送模块实现

管理员可以在后台发布推送文章,用户则可以在小程序的推荐板块看到所推送的内容,且可以看到文章的浏览量和推送时间。推送的文章可以按照最新发布的时间和浏览量的顺序显示,用户也可以在文章底部进行评论。具体实现界面如下:

图4.2文章推送页面图

文章推送模块关键代码如下:

//点击跳转到详情进行阅读

tiaozhuan(e){

//console.log(e.currentTarget.dataset.address)

var address=e.currentTarget.dataset.address

var id=e.currentTarget.dataset.id

var index=e.currentTarget.dataset.index

// wx.navigateTo({

// url: './detail/detail?address='+address,

// })

console.log("未知id:",id)

wx.navigateTo({

url:"../plate2/plate2?id="+id+"&fenxiang=false&liuyan=true&love="

})

//添加浏览数

// wx.cloud.callFunction({

// name:"look",

// data:{

// id:id,

// type:'tj'

// }

// })

},

onLoad(e){

//写出一周前的时间戳

var now=new Date().getTime()//现在的时间

var yizhou=(now-3600*7000*24)

console.log("现在:",now)

console.log("一周:",yizhou)

this.setData({

yizhou:yizhou

})

this.jiazai()

},

//加载更多

async jiazai(){

var head=this.data.tj.length

var zuixinorzuire=this.data.zuixinorzuire

if(zuixinorzuire==0){

//按照时间排取消时间限制,

zuixinorzuire="time"

var yizhou=0

}else{

//按照热度排行

zuixinorzuire="look"

var yizhou=this.data.yizhou}

4.1.3 发帖功能实现

作为已经登录的用户,不但可以浏览校园社区所有帖子,还可以在社区中相应的专区中以纯文本或者图文的形式发布自己的帖子,可以一次上传九张图片。发帖功能模块如下图4.3所示:

图4.3 发帖功能模块界面

发帖功能模块关键代码如下:

//上传图片

if(zs!=0){

wx.showLoading({

title: '就快好了...',

mask:true

})

var fileID=[]

var js=0

for(var i=0;i<zs;i++){

//取图片的大小进行判断

var path=ss_img[i];//取当前图片路径

var size=await this.qudaxiao(path)

console.log("图片的大小是",size)

if(size>=1048576){

//超过1M需要进行压缩!!

console.log("超过1M需要进行压缩!!")

path=await this.yasuo(path,0.92,800)

}

var time=new Date().getTime()

//直接拼接出云路径

fileID[i]="cloud://cloud1-3g814zmp8211d150.636c-cloud1-3g814zmp8211d150-1308721663/ss_img/"+time.toString()+".jpg"

//console.log("的点点滴滴",fileID[i])

//console.log(time)//取当前时间chuo

wx.cloud.uploadFile({

cloudPath: "ss_img/"+time+".jpg", // 上传至云端的路径

filePath: path, // 小程序临时文件路径

success: res => {

// 返回文件 ID

//console.log("前单个!!id=",i,res.fileID)

//fileID.push(res.fileID)//!!!!!!!对返回的云储存地址进行整合

//console.log("合并",fileID)

js++//记录成功获取云储存路径的图片数量

//console.log(js)

if(js==zs){

ss_xx.tp=fileID//!!!说说信息中的图片写入完毕

console.log("说说图片",fileID)

//带图发帖!!

this.post(ss_xx)

}

},

fail: console.log("上传是不知为啥有错")

})

}

}else{

//纯文本发帖!

this.post(ss_xx)

} wx.request({

url: app.globalData.baseUrl + '/user/save',

method: "POST",

header: {

"Content-Type": "application/x-www-form-urlencoded"

},

data: {

openid: openid,

username: username,

phone: phone,

zhuohao: zhuohao == undefined ? '' : zhuohao,

renshu: renshu == undefined ? '' : renshu,

money: first ? 1000 : 0

},

success(res) {……

},

fail(res) {

console.log("提交失败", res)

}

})

}

4.1.4 删帖功能实现

用户可以在个人中心界面的“我发布的帖子”,直接按住左滑会看到删除选项。因为删除后将无法的实现效果如图4.4所示:

4.4删除帖子实现界面

删除内容模块关键代码如下:

const cloud = require('wx-server-sdk')

// 初始化 cloud

cloud.init({

// API 调用都保持和云函数当前所在环境一致

env: cloud.DYNAMIC_CURRENT_ENV

})

exports.main = async (event, context) => {

//以下全部data内容

var liuyan=event._data.liuyan

var id0=event._data.id0//lv0的id

var id1=event._data.id1//lv1,2的id

var time=event._data.time

var time1=event._data.time1

var id=event._data.id

var ku='ss'

if(liuyan){

var ku='tj'

}

//以上全部data内容

var _=cloud.database().command

if(id1==""){

//这是删除lv0评论

cloud.database().collection(ku).doc(id).get().then((res)=>{

console.log("打印取到的回复",res)

var ss_xx=res.data.ss_xx

var total=0

console.log("total1:",total)

console.log("chang:",ss_xx.huifunr.length)

if(liuyan==false){

for(var i=0;i<ss_xx.huifunr.length;i++){

var dd=ss_xx.huifunr[i]

if(dd.time==time && dd.plrid==id0){

console.log("nbbb",dd.huifunb)

if(dd.huifunb==undefined){

total=-1

}else{

total=dd.huifunb+1

total=-total

}

console.log("total4:",total)

break

}

}

}

console.log("执行了删除lv0:total",total)

cloud.database().collection(ku).doc(id).update({

data: {

'ss_xx.huifunr':_.pull({

'time':_.eq(time),

'plrid':_.eq(id0)

}),

'ss_xx.huifunb':_.inc(total)

}

})

})

}else{

//这是删除lv1,2评论

console.log("执行了删除lv1,2")

cloud.database().collection(ku).where({

_id:id,

"ss_xx.huifunr.plrid":id0,

"ss_xx.huifunr.time":time,

}).update({

data: {

'ss_xx.huifunr.$.huifunb':_.inc(-1),

'ss_xx.huifunr.$.huifu':_.pull({

time:_.eq(time1),

plrid:_.eq(id1)

}),

'ss_xx.huifunb':_.inc(-1)

}

})

}

}

4.1.5 评论区功能实现

评论功能实现需要注意三个细节方面的考虑。首先,进行身份辨别,如果是楼主评论回复,昵称后面带有特殊标识。删除评论与回复时也要进行授权管理,评论人或者回复人可以长按自己的回复或评论进行删除,楼主及管理员可以删除所有评论。当收到消息时,楼主只接收直接评论与自己的回复被回复的消息通知,被回复者将收到回复者的消息。评论区功能的实现如图4.5所示:

图4.5评论区功能实现界面

评论区功能实现的关键代码如下:

/*消息类型:

1.pinglun

2.huifu

*/

exports.main = async (event, context) => {

console.log(event.pinglunnr)

var pinglunnr=event.pinglunnr

var pd=event.pd

var liuyan=pinglunnr.liuyan

var ku='ss'

var id=event.pinglunnr.ssid

var lzid=event.pinglunnr.lzid

var plrid=event.pinglunnr.plrid

const _ = cloud.database().command

if(liuyan==true){

ku='tj',

event.pinglunnr.ywnr=event.pinglunnr.title

}

if(pd[1]!=""){

//这说明是回复评论

console.log("回复评论:",id,pd)

return await cloud.database().collection(ku).where({

"_id":id,

"ss_xx.huifunr.plrid":pd[1],

"ss_xx.huifunr.time":pd[2]

}).update({

data: {

// 添加记录

'ss_xx.huifunr.$.huifunb':_.inc(1),

'ss_xx.huifunr.$.huifu':_.push(event.pinglunnr),

'ss_xx.huifunb':_.inc(1),

}

}).then((res)=>{

给自己加评论过记录

var pinglunguode={

id:event.pinglunnr.ssid,

time:event.pinglunnr.time,

nr:event.pinglunnr.ywnr,

plnr:event.pinglunnr.wbnr,

}

给别人发送消息(被回复者)

//额外加个判断是否是留言

var newmessage={

id:event.pinglunnr.ssid+event.pinglunnr.time,

ssid:event.pinglunnr.ssid,

type:"huifu",

time:event.pinglunnr.time,

bhfpl:event.pinglunnr.bhfpl,

plnr:event.pinglunnr.wbnr,

name:event.pinglunnr.name,

photo:event.pinglunnr.photo

}

if(liuyan==true){

newmessage.liuyan=true

}else{

newmessage.liuyan=false

}

//判断是否回复的自己

if(event.pinglunnr.bhfid!=plrid){

//不是回复的自己

cloud.database().collection('users').doc(event.pinglunnr.bhfid).update({

data: {

message:_.push(newmessage)

}

}).then((res)=>{

//console.log("!!!!",res)

if(pd[0]!=true && liuyan==false){

//首次评论家记录

cloud.database().collection('users').doc(plrid).update({

data:{

pinglunguode:_.push(pinglunguode)

}

}).then((res)=>{

console.log("成功")

//前面重要的做完就在这里进行订阅消息的发送了 } }

console.log("开始检测进行回复")

//1.获取待操作用户的信息

cloud.database().collection('users').doc(event.pinglunnr.bhfid).get().then((res)=>{

//2.取到用户数据进行判断在线状态

console.log("取到用户数据进行判断在线状态:",res.data.online)

console.log("取到用户数据进行判断授权状态:",res.data.allow)

console.log("取到用户数据进行判断次数剩余状态:",res.data.msgnb)

var online=res.data.online

var allow=res.data.allow

var msgnb=res.data.msgnb

var openid=res.data._openid

if(allow){

//开启了授权才可:

console.log("开启了授权才可")

if(!online){

//不在线才可

console.log("不在线才可")

if(msgnb[1]>0)//可推送回复消息

//消息数据格式化

//name 10

//thing 20

var name=event.pinglunnr.yuanname

if(name==undefined){

name=event.pinglunnr.name

}

console.log("length:",name)

if(name.length>20){

name=name.substr(0,17)+"..."

}

if(newmessage.plnr.length>20){

newmessage.plnr=newmessage.plnr.substr(0,17)+"..."

}

if(newmessage.bhfpl.length>20){

newmessage.bhfpl=newmessage.bhfpl.substr(0,17)+"..."

}

console.log("推送回复消息")

console.log("name:",name)

console.log("plnr:",newmessage.plnr)

console.log("date:",event.pinglunnr.riqi)

cloud.openapi.subscribeMessage.send({

touser: openid,

page: 'pages/index/index',

lang: 'zh_CN'

data: {

thing3: {

//评论人

value: name

},

thing2: {

//评论内容

value: newmessage.plnr

},

time4: {

//评论时间

value: event.pinglunnr.riqi

},

thing1: {

//原评论

value: newmessage.bhfpl

}

},

4.1.6 消息中心模块实现

在线状态:即时接收评论与回复,收到消息有振动反馈,消息栏可查看消息。

离线状态:当开启接收消息的权限以后,可以在小程序外,即未开启小程序的情况下接收来自小程序的评论与回复消息。

图4.6 消息中心模块显示

消息中心模块实现的关键代码如下:

//主动授权

allow(){

const tmplIds=this.data.tmplIds

var that=this

wx.requestSubscribeMessage({

tmplIds:tmplIds,

success(res) {

console.log("订阅消息API调⽤成功:",res)

var diyi='IOXeoWHeYUbuM3TJKzyPM1XR-J7iX6HIc9-YWYcYny0'

var dier='9kAS4BdEjH46glaAr-wuo_qZndRNkp5Zqe3vWZbAab4'

var msgnb=that.data.msgnb

console.log(res[diyi],res[dier])

if(res[diyi]=='accept'){

//第一个模板,评论

msgnb[0]++

}else if(res[diyi]=='reject'){

wx.showToast({

title: '您拒绝了接收评论',

icon:'none',

duration: 1000

})

}

if(res[dier]=='accept'){

//第二个模板,回复

msgnb[1]++

}else if(res[dier]=='reject'){

wx.showToast({

title: '您拒绝了接收回复',

icon:'none',

duration: 1000

})

}

console.log(msgnb)

that.setData({

msgnb:msgnb

})

app.userInfo.allow=true

首次授权调用授权成功,下次直接进入剩余推送次数页面

//首次授权调用

that.setData({

allow:'true',

msgnb:msgnb

})

db.collection('users').doc(app.userInfo._id).update({

data:{

allow:true,

msgnb:msgnb

}

})

console.log('已经进行了第一次授权,不再出现此页面!')

},

fail(res) {

console.log("订阅消息API调⽤失败:",res)

var errCode=res.errCode

if(errCode==20004){

wx.showToast({

title: '您拒绝接收消息',

icon:'none'

})

this.turrenoff()

}}

})

},

//增加授权

allowup(e){

console.log("e:",e.currentTarget.dataset.index)

var index=e.currentTarget.dataset.index

var diyi='IOXeoWHeYUbuM3TJKzyPM1XR-J7iX6HIc9-YWYcYny0'

var dier='9kAS4BdEjH46glaAr-wuo_qZndRNkp5Zqe3vWZbAab4'

var tmplIds=this.data.tmplIds

if(index==0){

console.log("被评论")

tmplIds=[diyi]

}else{

console.log("被回复")

tmplIds=[dier]

}

var that=this

wx.requestSubscribeMessage({

tmplIds:tmplIds,

success(res) {

console.log("订阅消息API调⽤成功:",res,"up")

var msgnb=that.data.msgnb

console.log(res[diyi],res[dier])

if(res[diyi]=='accept'){

//第一个模板,评论

msgnb[0]++

}else if(res[diyi]=='reject'){

wx.showToast({

title: '您拒绝了接收评论',

icon:'none',

duration: 1000

})

}

if(res[dier]=='accept'){

//第二个模板,回复

msgnb[1]++

}else if(res[dier]=='reject'){

wx.showToast({

title: '您拒绝了接收回复',

icon:'none',

duration: 1000

})

}

console.log(msgnb)

that.setData({

msgnb:msgnb

})

},

fail(res) {

console.log("订阅消息API调⽤失败:",res)

var errCode=res.errCode

if(errCode==20004){

wx.showToast({

title: '您拒绝接收消息',

icon:'none'

})

this.turrenoff()

}

}

})

},

onReady: function () {

},

onShow: function () {

//var now=new Date()//.getTime()//现在的时间

// var hour = now.getHours();

// console.log("现在的小时:",hour)

},

onUnload: function () {

//加到数据库

console.log("加到数据库")

var msgnb=this.data.msgnb

var allow=app.userInfo.allow

db.collection('users').doc(app.userInfo._id).update({

data:{

msgnb:msgnb,

allow:allow

}

})

console.log('增加了所有授权')

},

})

4.2云开发后台实现

这款微信小程序的开发完全依赖于云开发,无需搭建后台服务器。利用云数据库、云存储、云函数技术实现对轮播图、文章推送、用户和系统的管理。

4.2.1 数据库

数据库主要有四个集合ss、system、tj、user分别代表帖子、系统配置、推荐文章、用户数据四张数据表,管理员可以在云数据对轮播图和推文等进行管理。如下图4.7所示:

图4.7云数据库界面

4.2.2 云存储

云存储可以提供稳定、安全、低成本、简单易用的云端存储服务,支持任意数量和形式的非结构化数据存储,如图片、文档、音频、视频、文件等。在这里,我们建立了两个存储文件夹,ss_img用于存储帖子的图片,tj_img用于存储主页轮播图的图片。在云平台还可以进行存储权限管理,十分便捷。如下图4.8所示:

图4.8云存储界面

4.2.3 云函数

云函数是一个在小程序端定义编写,编写完毕后部署到云服务器,开发者无需购买、搭建服务器,只需编写函数代码并部署到云端,在云服务器中运行的nodejs函数,同时云函数之间也可互相调用。云函数的部署如下图4.9所示:

图4.9云函数部署界面


参考文献(资料)

[1]谭贤.微信公众号运营[M].第一版.北京:人民邮电出版社,2017:7-16

[2]杨启,张丽萍.从互联网生态看微信小程序的发展[J].新闻论坛,2017(2):22-24

[3]曾国强,王楚虹,黄江,师文庆.基于微信小程序投票系统设计[J].机电工程技术,2020,49(01):159-161.

[4]刘志强等.Android应用开发教程[M].第一版.北京:清华大学出版社,2016:10-15

[5]王明甲,刘银灵.数据库自动备份与恢复在运维平台中的应用[J].信息通信.2018,(01):


致 谢

在本次课程设计的过程中,我得到了许多人的帮助,在此向他们表示由衷的致谢。

首先我要感谢老师在课程设计上给予我的指导、提供给我的支持和帮助,在我的小程序渲染层出了问题导致图片显示不出来时,是程老师耐心帮我排查原因最终解决问题,正是他帮我解决了许多技术上的难题,才让我能把小程序做得更加完善,最终成功完成这次课程设计。而在此期间,我不仅学到了许多新的知识,而且也开阔了视野,提高了自己的设计能力,并且充分体会到了在创造过程中探索的艰难和成功时的喜悦。

此次,我还要感谢校方给予我这样一次机会,让我能够独立地完成一次课程设计,还给我们提供机房和空调,让我们拥有良好的课程设计环境。最重要的是,安排这次课程设计实际上是给我们提供一次宝贵的机会,在这学期结束之际,能够将学到的知识应用到实践中,增强了我们实践操作和动手应用能力,提高了独立思考的能力。

最后再一次感谢所有在设计中曾经帮助过我的良师益友和同学,以及CSDN论坛分享自己开发经验的大佬们,他们的分享具有很大的参考价值,并且给了我很多灵感。只跬步何以至千,吾将上下而求索。

本书共包含投票系统、通讯簿管理系统、新闻发布系统、软件下载中心、电子书店系统和论坛系统等六个系统。这六个系统均使用JSP语言和HTML标记语言编写完成的。要想运行该程序,还要进行如下操作: (1) 安装JDK1.4.0或以上版本。 (2) 安装Apache Tomcat 4.0或以上版本。 (3) 配置ODBC数据源。数据源名按各系统所使用的名称配置,具体名称如下 ① 投票系统的数据源名:vote ② 通讯簿管理系统的数据源名:user ③ 新闻发布系统的数据源名:news ④ 软件下载中心的数据源名:download ⑤ 电子书店系统的数据源名:bookstore ⑥ 论坛系统的数据源名:forum vote文件夹包含的是投票系统的源代码。投票系统分为普通用户访问界面和管理员访问界面两部分。普通用户访问界面由index.jsp页面进入,不需要用户名和密码;管理员访问界面由login.jsp页面进入,管理员用户名是:admin,密码是:admin。 userinfo文件夹包含的是通讯簿管理系统的源代码。通讯簿管理系统分为普通用户访问界面和管理员访问界面两部分。两种用户均通过login.htm页面进入系统,所使用的用户名的身份不同即进入不同的访问界面。用户名和密码可以在数据库mydb中的user数据表中查询。 news文件夹包含的是新闻发布系统的源代码。新闻发布系统分为普通用户访问界面和管理员访问界面两部分。普通用户访问界面使用index.jsp页面进入,不需要用户名和密码;管理员访问用户界面使用login.jsp页面进入,管理员的用户名是:admin,密码是:admin。 download文件夹包含的是软件下载中心的源代码。软件下载中心分为普通用户访问界面和管理员访问界面两部分。普通用户访问界面使用index.jsp页面进入,不需要用户名和密码;管理员访问用户界面使用login.jsp页面进入,管理员的用户名是:admin,密码是:admin。 bookstore文件夹包含的是电子书店的源代码。电子书店分为普通用户访问界面和管理员访问界面两部分。两种用户均通过login.jsp页面进入系统,所使用的用户名的身份不同即进入不同的访问界面。用户名和密码可以在数据库book中的user数据表中查询。 forum文件夹包含的是论坛系统的源代码。论坛系统分为普通用户访问界面、版主访问界面和管理员访问界面三部分。三种用户均通过login.jsp页面进入系统,所使用的用户名的身份不同即进入不同的访问界面。用户名和密码可以在数据库forum中的user数据表中查询。 由于编者水平有限,编写时间仓促,书中错误和不妥之处在所难免,请读者和专家批评指正。 ,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值