基于微信小程序的个人记事本(含后端程序及配置信息)

时间:2024-02-25 16:49:04

基于微信小程序的个人记事本(含后端程序及配置信息)

博客班级 https://edu.cnblogs.com/campus/zjcsxy/SE2020
作业要求 https://edu.cnblogs.com/campus/zjcsxy/SE2020/homework/11334
作业目标 1. 编写一个小程序,可以全新编写,也可以学习别人的小程序进行修改 2. 熟悉git代码管理流程,将源代码上传到到github 3. 在博客园班级中写一篇相应的博文
作业源代码 https://github.com/VinceMockghy/notes_wechat_mini_program
学号 31801201
院系 浙大城市学院计算机系
姓名班级 计算1803 郭浩源

前言

第一次软件工程的作业,我的想法是不仅仅是前端。做这样一个小程序的初衷是这样的,虽然现在市面上有很多很多跨平台的备忘录程序(基本上都是windows和android或者ios跨平台,然而我用linux),而且那些程序基本上都是别人的服务器,想象一下自己的备忘录被记录在别人服务器的数据库中,总感觉很奇怪。我就趁着这个小程序的机会,写完整的后端程序搭载在个人服务器并绑定个人域名,结合小程序做前端,制作一整套的记事本小程序。后续的话在我的linux上随便写个bash脚本从服务器拉内容就可以全平台共通了。

效果展示

下面这个gif演示了这个小程序的一些使用方法。

首先点击加号可以跳转到添加笔记的界面,点击保存可以将笔记内容保存到服务器的数据库中

保存成功后自动返回主界面,刚才写的笔记就会出现在界面上(当天写的笔记会被做出标记笔记上半部分会颜色深一点)

点开刚才创建的笔记,可以修改笔记中的内容,并同步到服务器的数据库,也可以将这个笔记删除。

git 提交历史

微信小程序界面

页面分配

"/page/index/index"				小程序的主界面,记事本的每条记录都会出现在这里
"/page/create/index"			记事本创建记录的界面,可以在这里添加编写标题和内容,添加记录
"/page/edit/index"				   记事本中单条记录的编辑界面,可以修改记录的内容并保存(删除记录也放在这里)

index主界面

/page/index/index.wxml

在第一个<view class = "col">中,创建了添加笔记的那个方框以及中间的加号图片,点击这个地方会调用onNewItem,跳转到/page/create/index页面进行新建笔记

和上一部分同级的是一个<block>标签,在这个标签中,通过js文件中数据,以notekey为键值进行遍历。遍历得到的是每一次做过的笔记,点击后会触发onEditItem,跳转到/page/edit/index页面。

<!--index.wxml-->
<view class="container">
    <view class="col">
        <view class="item addBox" bindtap="onNewItem">
            <image class="addIcon" src="../../image/add.png"></image>
        </view>
    </view>
    <block wx:for="{{items}}" wx:key="{{notekey}}">
        <view class="col">
            <view class=\'item note {{item.quality}}\' data-key="{{item.countkey}}" bindtap="onEditItem">
                <view class=\'content\'>
                    <view class=\'txt\'>{{item.title}}</view>
                </view>
                <view class=\'bottom\'>
                    <view class=\'txt\'>
                        {{item.year}}-{{item.month}}-{{item.day}}
                    </view>
                </view>
            </view>
        </view>
    </block>
</view>

/page/index/index.js

在这个js中主要有下面几点需要说明一下

API文件后面会介绍

首先最开始通过require导入的myserveAPI文件是根据后台文件写的发送GET和POST请求用的API文件。因为这是主界面,在页面加载的时候就需要获取到数据库中的全部笔记,所以修改了onLoad方法。在onLoad中调用了serve API中的getnote方法从服务器的数据库中获取所有笔记信息。这一部分主要是做了个时间戳的转换,在主界面上可以看到年月日,同时当天的笔记会被单独标记(根据这里时间的判断修改quality的值来控制单独标记)。

onNewItem方法就是简单的将页面跳转到/page/create/index上,而onEditItem却有些不同,他需要传入参数,参数的内容就是当前点击到的这篇笔记信息,将选中笔记的信息转为json格式作为参数传入,就能在修改笔记后通过笔记信息中的notekey来修改对应的内容了。

这里还修改了onShow方法,在onShow的时候调用onLoad,做到重载的功能。主要是因为当进入笔记编辑状态保存后返回主界面或者是创建笔记成功并返回主界面后,并不会重新加载这个界面,也就是数据写入数据库了,但本地还不知道,也无法验证是否真的写入了数据库。所以这里通过这个方法,在主界面显示的时候都会调用一次,从服务器的数据库重新获取数据,并重新显示

// pages/create/index.js
var serve = require(\'../../utils/myserveAPI\')

Page({
    data: {
        items: [],
    },

    onLoad: function (options) {
        let that = this;
        serve.getnote((data) => {
            var len = Object.keys(data).length
            var today = new Date()
            var thisYear = today.getFullYear()
            var thisMonth = (today.getMonth() + 1 < 10 ? \'0\' + (today.getMonth() + 1) : today.getMonth() + 1);
            var thisDay = (today.getDate() < 10 ? \'0\' + (today.getDate()) : today.getDate());

            for (let i = 0; i < len; i++) {
                var opdate = new Date(data[i]["updatetime"])
                data[i]["year"] = opdate.getFullYear();
                data[i]["month"] = (opdate.getMonth() + 1 < 10 ? \'0\' + (opdate.getMonth() + 1) : opdate.getMonth() + 1);
                data[i]["day"] = (opdate.getDate() < 10 ? \'0\' + (opdate.getDate()) : opdate.getDate());
                data[i]["countkey"] = i


                if (data[i]["year"] === thisYear && data[i]["month"] === thisMonth && data[i]["day"] === thisDay) {
                    data[i]["quality"] = "today"
                } else {
                    data[i]["quality"] = "none"
                }

            }
            console.log(data)
            this.setData({
                items: data
            })
        })

    },
    onShow: function () {
        this.onLoad()
    },

    onNewItem: function (event) {
        wx.navigateTo({
            url: "/pages/create/index"
        })
    },
    onEditItem: function (event) {
        console.log(event.currentTarget.dataset.key)

        var json = this.data.items[event.currentTarget.dataset.key]
        console.log(json)
        wx.navigateTo({
            url: "/pages/edit/index?json=" + JSON.stringify(json)
        })
    }
})

create创建笔记界面

/page/create/index.wxml

创建笔记界面没什么好多说的,就是在外框放了一个form表单,里面两个输入框,通过最后的保存按钮触发表单的提交。

<!--pages/create/index.wxml-->
<form bindsubmit="onSubmit">
    <view class="container">
        <view class=\'title\'><input name="title" placeholder-class="placeholder" placeholder="在此输入标题" value="" /></view>
        <view class=\'row\' catchtap="onFocus">
            <textarea class=\'text\' maxlength="5000" auto-height="True" placeholder-class="placeholder"  name="content" focus="{{focus}}" auto-focus="true" value="" placeholder="点击添加文本" />
        </view>
        <view class=\'bottom\'>
            <button formType="submit" class=\'btn success\'>保存</button>
        </view>
    </view>
</form>

/page/create/index.js

这里最开始还是一样调用serveAPI接口,在保存按钮被点击并调用onSubmit方法后触发API接口的addnote进行笔记的添加。

// pages/create/index.js
var serve = require(\'../../utils/myserveAPI\')

Page({

    data: {
        item: {
            "title": "",
            "content": "",
        }
    },

    onSubmit: function (event) {
        var item = this.data.item;
        item.title = event.detail.value.title;
        item.content = event.detail.value.content;
        this.setData({
            item: item
        });
        console.log(this.data.item)
        serve.addnote((data) => {
            if (data === "done") {
                wx.showToast({
                    title: "保存成功",
                    success: function () {
                        // 返回首页
                        setTimeout(function () {
                            wx.hideToast();
                            wx.navigateBack();
                        }, 1000)
                    }
                })
            }
        }, this.data.item)
    }
})

edit修改界面

/page/edit/index.wxml

这一部分和上面的新建界面一样,只是多了个删除按钮

<!--pages/edit/index.wxml-->
<form bindsubmit="onSubmit">
    <view class="container">
        <view class=\'title\'><input name="title" placeholder-class="placeholder" placeholder="在此输入标题" value="{{item.title}}" /></view>
        <view class=\'row\' catchtap="onFocus">
            <textarea class=\'text\' maxlength="5000" auto-height="True" placeholder-class="placeholder"  name="content" focus="{{focus}}" auto-focus="true" value="{{item.content}}" placeholder="点击添加文本" />
        </view>
        <view class=\'bottom\'>
            <button formType="submit" class=\'btn success\'>保存</button>
            <button class=\'btn delete\' bindtap="onDelete">删除</button>
        </view>
    </view>
</form>

/page/edit/index.js

在主界面的分析中有说过在进入编辑界面的时候会传入选中笔记的信息,所以在这个界面加载的时候,我们需要将传入的json信息解析出来,并保存到变量中。

onSubmit和onDelete两个方法分别调用serveAPI中的editnote编辑笔记和delnote删除笔记,并在成功后返回主界面

// pages/edit/index.js
var serve = require(\'../../utils/myserveAPI\')

Page({

    data: {
        item: {
            "title": "",
            "content": "",
        }
    },

    onLoad: function (options) {
        var that = this;
        var json = JSON.parse(options.json)
        that.setData({
            item: json
        })
    },
    
    onSubmit: function (event) {
        var item = this.data.item;
        item.title = event.detail.value.title;
        item.content = event.detail.value.content;
        this.setData({
            item: item
        });
        serve.editnote((data) => {
            if (data === "done") {
                wx.showToast({
                    title: "保存成功",
                    success: function () {
                        // 返回首页
                        setTimeout(function () {
                            wx.hideToast();
                            wx.navigateBack();
                        }, 1000)
                    }
                })
            }
        }, this.data.item)
    },

    onDelete: function () {
        serve.delnote((data) => {
            if (data === "done") {
                wx.showToast({
                    title: "保存成功",
                    success: function () {
                        // 返回首页
                        setTimeout(function () {
                            wx.hideToast();
                            wx.navigateBack();
                        }, 1000)
                    }
                })
            }
        }, this.data.item.notekey)
    }
})

serveAPI文件

/utils/myserveAPI.js

这个文件是为了调用服务器上的后端程序而写的API接口。

首先最开始的四个变量分别对应增删改查这四个方法的URL

然后这四个方法中只有查可以直接通过GET的方法获取到,其他的都需要发送数据包进行POST请求(后端是这么定的,这边也就这样写)

GET请求没什么好说的,也没什么坑,但POST请求中一定要有header(没有的话就会报500 Internal server error,当时我还以为是服务器被我搞炸了...)。

然后因为这些请求完成后都需要一些数据处理,这里就需要传入回调函数,具体传入的回调函数内容可以回上面调用这些方法的地方去看,就不一一赘述,毕竟没啥难度。

最后面呢就是对这个API的封装(看教程写的,不这样写调用不了方法,和封python的包差不多)

var GETNOTE = "https://api.zghy.xyz/getnote"
var ADDNOTE = "https://api.zghy.xyz/addnote"
var DELNOTE = "https://api.zghy.xyz/delnote"
var EDITNOTE = "https://api.zghy.xyz/editnote"


function getnote(callback) {
    wx.request({
        url: GETNOTE,
        data: {},
        method: \'GET\',

        success: function (res) {
            callback(res.data)
        }
    })
}

function addnote(callback, notedata) {
    wx.request({
        url: ADDNOTE,
        data: notedata,
        method: \'POST\',
        header: {
            "Content-Type": "application/x-www-form-urlencoded" //用于post
        },
        success: function (res) {
            callback(res.data)
        }
    })
}

function editnote(callback, notedata) {
    var json = {
        "notekey": notedata["notekey"],
        "title": notedata["title"],
        "content": notedata["content"]
    }
    console.log(json)
    wx.request({
        url: EDITNOTE,
        data: json,
        method: \'POST\',
        header: {
            "Content-Type": "application/x-www-form-urlencoded" //用于post
        },
        success: function (res) {
            console.log(res.data)
            callback(res.data)
        }
    })
}

function delnote(callback, notedata) {
    var json = {"notekey": notedata}
    wx.request({
        url: DELNOTE,
        data: json,
        method: \'POST\',
        header: {
            "Content-Type": "application/x-www-form-urlencoded" //用于post
        },
        success: function (res) {
            callback(res.data)
        }
    })
}


module.exports = {
    getnote: getnote,
    addnote: addnote,
    delnote: delnote,
    editnote: editnote
}

前端微信小程序界面上的程序大致就这么些,虽然页面少了点,但我觉得作为小程序的初学足够了,剩下的能玩点后端的以及后端与前端建立连接这种还是不错的。

后端程序

后端采用python flask建立web微框架,通过小程序中的request向个人域名发送GET、POST请求,将笔记的标题内容在服务器的数据库中进行增删改查。

web服务结构

\'/addnote\'							添加笔记 需要发送POST请求
\'/getnote\'							 查询笔记 直接发送GET请求
\'/delnote\'							  删除笔记 需要发送POST请求
\'/editnote\'							 修改笔记 需要发送POST请求

代码结构

这里使用python flask库创建四个web接口,每个接口都有对应的http请求方式。

请求成功如果有数据获取就获取发送过来的数据,没有就直接连接mysql服务器,写写SQL语句,进行简单的增删改查(有点数据库短学期的味道了,不过python的数据库操作总觉得比java方便点,也有可能是错觉)

服务接口写完后就可以通过服务器ip地址+8080端口进行访问。至于为什么是8080端口,有段曲折的经历,后面说

# -*- coding: utf-8 -*-
"""
@Author  : guohaoyuan
@Time    : 2020/10/15 下午6:53
"""

from flask import Flask, request
import pymysql
import mysql_info as SQLINFO
import time
import datetime

app = Flask(__name__)


# 添加笔记 需要发送POST请求
@app.route(\'/addnote\', methods=[\'POST\'])
def addnote():
    now = datetime.datetime.now()
    now = now.strftime("%Y-%m-%d %H:%M:%S")
    title = request.form[\'title\']
    content = request.form[\'content\']
    conn = pymysql.connect(SQLINFO.HOST, SQLINFO.USER, SQLINFO.PASSWORD, SQLINFO.DATABASE)
    cursor = conn.cursor()
    sql = "INSERT INTO NOTE(title,content,createtime,updatetime) VALUES (\'%s\',\'%s\',\'%s\',\'%s\')" % (
        title, content, now, now)
    try:
        cursor.execute(sql)
        conn.commit()
    except:
        conn.rollback()
    conn.close()
    return "done"


# 查询笔记 直接发送GET请求
@app.route(\'/getnote\', methods=[\'GET\'])
def getnote():
    conn = pymysql.connect(SQLINFO.HOST, SQLINFO.USER, SQLINFO.PASSWORD, SQLINFO.DATABASE)
    cursor = conn.cursor()
    sql = \'\'\'SELECT * FROM NOTE\'\'\'
    results = {}
    try:
        cursor.execute(sql)
        notes = cursor.fetchall()
        count = 0
        for row in notes:
            results.update(NOTE(row[0], row[1], row[2], row[3], row[4]).formateNote(count))
            count += 1
    except:
        print("Unable to fetch data!")
    conn.close()
    return results


# 删除笔记 需要发送POST请求
@app.route(\'/delnote\', methods=[\'POST\'])
def delnote():
    notekey = request.form[\'notekey\']
    conn = pymysql.connect(SQLINFO.HOST, SQLINFO.USER, SQLINFO.PASSWORD, SQLINFO.DATABASE)
    cursor = conn.cursor()
    sql = "DELETE FROM NOTE WHERE notekey = %d" % int(notekey)
    try:
        cursor.execute(sql)
        conn.commit()
    except:
        conn.rollback()
    conn.close()
    return "done"

# 修改笔记 需要发送POST请求
@app.route(\'/editnote\', methods=[\'POST\'])
def editnote():
    now = datetime.datetime.now()
    now = now.strftime("%Y-%m-%d %H:%M:%S")
    notekey = request.form[\'notekey\']
    title = request.form[\'title\']
    content = request.form[\'content\']
    conn = pymysql.connect(SQLINFO.HOST, SQLINFO.USER, SQLINFO.PASSWORD, SQLINFO.DATABASE)
    cursor = conn.cursor()
    sql = "UPDATE NOTE SET title = \'%s\',content = \'%s\',updatetime = \'%s\' WHERE notekey = %d" % (
    title, content, now, int(notekey))
    try:
        cursor.execute(sql)
        conn.commit()
    except:
        conn.rollback()
    conn.close()
    return "done"


class NOTE:
    def __init__(self, notekey, title, content, createtime, updatetime):
        self.notekey = int(notekey)
        self.title = str(title)
        self.content = str(content)
        self.createtime = createtime.strftime("%Y-%m-%d %H:%M:%S")
        self.updatetime = updatetime.strftime("%Y-%m-%d %H:%M:%S")

    def formateNote(self, count):
        result = {count: {"notekey": self.notekey, "title": self.title, "content": self.content,
                          "createtime": self.createtime, "updatetime": self.updatetime}}
        return result


if __name__ == \'__main__\':
    app.run(host=\'0.0.0.0\', port=8080, debug=False)

服务器配置

首先你需要租一个服务器,然后需要买一个域名,然后你的域名需要ICP备案,然后你还要学习一些服务器上的web端口跳转以及域名解析与跳转等等的知识。

微信小程序中的服务器需求

这是微信小程序官方给出的可以被微信小程序中wx.request成功发出HTTPS请求的服务器配置,注意是HTTPS,所以一定要有域名(还好我有不然就白搞了)

然后这里还提示如果需要配置端口只能是8080端口,但我最开始不是这个端口,因为我是直接域名跳转,不需要输入端口,但是它还是提示我当前的网址不符合规定,所以才改成的8080端口。然而改了之后还是说不符合规定,所以最后也没管他。

nginx服务器配置域名反向代理

在我的服务器上之前因为个人博客的缘故配置了nginx,一开始开启flask后端服务后,所有走api.zghy.xyz这个二级域名的信息全都会指向默认的443端口,所以就需要配置相应的反向端口代理。

这里就给个反向代理配置文件吧

这里需要个HTTPS的认证证书,阿里云服务器那里有免费的证书可以买,给二级域名买一个就行了

 server {
    listen       443 ssl;  # https 监视端口是 443
    server_name  api.zghy.xyz;
    # 下面两个是认证HTTPS证书后下载的证书地址,我这里就不放出来了
    ssl_certificate      "";
    ssl_certificate_key  "";
    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;
    ssl_ciphers  ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers  on;

    location / {
            proxy_redirect off;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://47.102.101.77:8080;#这里配置了端口的反向代理
    }
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass unix:/run/php-fpm/www.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}
server{
        listen  80;
        server_name api.zghy.xyz;
        rewrite ^(.*)$ https://$host$1 permanent;

}

Python Flask程序后台运行配置

那么服务器上我们的python程序应该如何一直运行呢?这里我用了pm2这个东西,他能将前台程序放到后台运行,并保持运行状态。

效果就是下面这样,通过这个list可以看到小程序的api后台一直在运行。

完整项目连接

https://github.com/VinceMockghy/notes_wechat_mini_program

附上个人博客链接 https://zghy.xyz/2020/10/17/notes-wxmini/#more