开发一个题库系统App和小程序的心得
<p><span style="font-size: 2em">序言</span></p><p>对于一名开发者来说,独自开发一款小程序与App,也许总会有一些疑问:</p>
<p> 1. 需要掌握哪些技术?</p>
<p>答:java、vue、及常规Linux命令</p>
<p> </p>
<p>2. 需要多少成本?</p>
<p>答:服务器购买,(我的配置2核2G+50G+4M,云服务器新人福利408三年)</p>
<p>域名购买,10块的域名够用,后续每年30左右的续期费用;</p>
<p>短信套餐购买,50块钱,够用很久了;</p>
<p>微信小程序发布,需要300块钱的审核费用;</p>
<p>ios版本的App发布,貌似也要钱。</p>
<p> </p>
<p>3.需要多久完成?</p>
<p>答:如果第一次,需完成域名备案、服务器环境搭建、程序基础功能开发等,可能用时较久;</p>
<p>如果第二次,仅仅在第一次的基础加改代码,短时间可以完成就够了。</p>
<p> </p>
<h1>1. 心得说明</h1>
<p>本文基于个人开发和发布《九云题库》H5/微信小程序/APP的经验,分享从零到完成一套完整系统开发、发布的全过程,其中涉及到部署资源的获取,开发过程、部分程序设计思路、注意事项以及最终部署方法等。</p>
<h2>1.1 功能说明</h2>
<ol>
<li>实现PC端/H5/App/小程序4端的常规登陆/注册/更新等</li>
<li>实现题目分类和题目的增删改查功能,支持手机端和PC端操作</li>
<li>实现题目收藏/取消收藏,移动端</li>
<li>实现错题记录/移除记录,移动端</li>
<li>实现刷题/看题/搜题功能,移动端</li>
<li>实现题目提问/留言讨论功能,移动端</li>
<li>其它移动端的基础必备功能</li>
</ol>
<p align="left"> </p>
<h2>1.2 附件截图</h2>
<p>以下是App、H5、微信小程序三个移动端的部分截图</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341470-184654066.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341784-650633347.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341295-1716500890.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341597-61661400.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341135-227551486.png" alt=""></p>
<p> </p>
<h1>2. 实现方案</h1>
<h2>1.1 资源方案</h2>
<h3>2.1.1 服务器选择与规划</h3>
<p>服务器选择,系统需要有一个承载平台,因此需要一个服务器,购买云服务器centos7系统是一个不错的选择,或者使用其它服务器。</p>
<p> </p>
<p>服务器规划,软件和应用尽可能都放在/home路径,方便统一管理,如:/home/app放系统软件</p>
<p>/home/nginx放nginx配置</p>
<p>/home/docker放docker镜像</p>
<p>/home/minio放minio文件</p>
<p>/home/mysql_tump放mysql备份文件</p>
<p>注:软件安装最好修改默认端口,否则服务器容易被攻击。</p>
<p><img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100342088-263527520.png" alt=""></p>
<p> </p>
<h3>2.1.2 服务器-jdk环境</h3>
<p>将下载好的 jdk-8u211-linux-x64.tar.gz 放在 /home/app 路径</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 进入目录解压
</span><span style="color: rgba(0, 0, 255, 1)">tar</span> -zxvf jdk-8u211-linux-x64.<span style="color: rgba(0, 0, 255, 1)">tar</span><span style="color: rgba(0, 0, 0, 1)">.gz
# 修改环境变量
</span><span style="color: rgba(0, 0, 255, 1)">vi</span> /etc/<span style="color: rgba(0, 0, 0, 1)">profile
# 添加以下配置
export JAVA_HOME</span>=/home/app/jdk1.<span style="color: rgba(128, 0, 128, 1)">8</span><span style="color: rgba(0, 0, 0, 1)">.0_211
export CLASSPATH</span>=${JAVA_HOME}/<span style="color: rgba(0, 0, 0, 1)">lib
export PATH</span>=$PATH:${JAVA_HOME}/<span style="color: rgba(0, 0, 0, 1)">bin
# 应用配置
source </span>/etc/<span style="color: rgba(0, 0, 0, 1)">profile
# 校验是否成功
javac
java </span>-<span style="color: rgba(0, 0, 0, 1)">version
</span><span style="color: rgba(0, 0, 255, 1)">echo</span> $PATH</pre>
</div>
<p> </p>
<h3>2.1.3 服务器-端口开通</h3>
<p>云服务器平台需要开通端口,服务器启用防火墙,开通各个服务的端口,以下是centos7下的防火墙相关操作,同时云服务器平台也需要开通对应的端口</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 查看防火墙状态
systemctl status firewalld.service
# 永久启用防火墙
systemctl enable firewalld.service
# 查看防火墙配置情况
firewall</span>-cmd --list-<span style="color: rgba(0, 0, 0, 1)">all
# 查看端口
netstat </span>-apn | <span style="color: rgba(0, 0, 255, 1)">grep</span> <span style="color: rgba(128, 0, 128, 1)">8080</span><span style="color: rgba(0, 0, 0, 1)">
# 添加</span>/<span style="color: rgba(0, 0, 0, 1)">移除端口
firewall</span>-cmd --permanent --zone=public --add-port=<span style="color: rgba(128, 0, 128, 1)">8080</span>/<span style="color: rgba(0, 0, 0, 1)">tcp
firewall</span>-cmd --permanent --zone=public --remove-port=<span style="color: rgba(128, 0, 128, 1)">80</span>/<span style="color: rgba(0, 0, 0, 1)">tcp
# 添加区间类型的端口
firewall</span>-cmd --zone=public--add-port=<span style="color: rgba(128, 0, 128, 1)">4400</span>-<span style="color: rgba(128, 0, 128, 1)">4600</span>/udp --<span style="color: rgba(0, 0, 0, 1)">permanent
firewall</span>-cmd --zone=public--add-port=<span style="color: rgba(128, 0, 128, 1)">4400</span>-<span style="color: rgba(128, 0, 128, 1)">4600</span>/tcp --<span style="color: rgba(0, 0, 0, 1)">permanent
# 重新加载防火墙
firewall</span>-cmd --reload</pre>
</div>
<p> </p>
<h3>2.1.4 服务器-Nginx</h3>
<p>执行命令-增加支持ssl</p>
<div class="cnblogs_code">
<pre># 下载nginx包,解析到/home/app/<span style="color: rgba(0, 0, 0, 1)">nginx,安装命令
.</span>/configure --prefix=/home/app/nginx --with-<span style="color: rgba(0, 0, 0, 1)">http_ssl_module
</span><span style="color: rgba(0, 0, 255, 1)">make</span>
<span style="color: rgba(0, 0, 255, 1)">make</span> <span style="color: rgba(0, 0, 255, 1)">install</span><span style="color: rgba(0, 0, 0, 1)">
nginx </span>-<span style="color: rgba(0, 0, 0, 1)">V
# 启动nginx服务,切换目录到</span>/home/app/nginx/<span style="color: rgba(0, 0, 0, 1)">sbin下面
.</span>/<span style="color: rgba(0, 0, 0, 1)">nginx
# 重新加载配置</span>|重启|停止|<span style="color: rgba(0, 0, 0, 1)">退出
.</span>/nginx -s reload|reopen|stop|<span style="color: rgba(0, 0, 0, 1)">quit
# 查看nginx服务是否启动成功
</span><span style="color: rgba(0, 0, 255, 1)">ps</span> -ef | <span style="color: rgba(0, 0, 255, 1)">grep</span><span style="color: rgba(0, 0, 0, 1)"> nginx
# 配置nginx全局
vim </span>/etc/<span style="color: rgba(0, 0, 0, 1)">profile
# nginx
NGINX_HOME</span>=/home/app/<span style="color: rgba(0, 0, 0, 1)">nginx
export PATH</span>=$PATH:$NGINX_HOME/<span style="color: rgba(0, 0, 0, 1)">sbin
# 应用配置
source </span>/etc/profile</pre>
</div>
<p> </p>
<p>Nginx配置说明,在/home/app/nginx/conf/nginx.conf的http下增加引用配置,其具体nginx配置只需在引用目录添加即可,方便管理和维护</p>
<div class="cnblogs_code">
<pre>include /home/nginx/conf/conf.d<span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">.conf;</span></pre>
</div>
<p> </p>
<p>在引用目录下编写对应各个需求的nginx配置,一个nginx配置写一个文件,cert专门放ssl证书</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100342151-856855537.png" alt=""></p>
<p> <br clear="all">
</p>
<h3>2.1.5
服务器-gitea</h3>
<p>安装源码管理工具,gitea很占用服务器性能资源,也可以使用开源平台</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># nginx配置
server {
listen</span><span style="color: rgba(128, 0, 128, 1)">443</span><span style="color: rgba(0, 0, 0, 1)"> ssl;
server_namegit.ninecloud.top;
ssl_certificate </span>/home/nginx/conf/conf.d/cert/<span style="color: rgba(0, 0, 0, 1)">git.pem;
ssl_certificate_key </span>/home/nginx/conf/conf.d/cert/<span style="color: rgba(0, 0, 0, 1)">git.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE</span>-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!<span style="color: rgba(0, 0, 0, 1)">RC4;
ssl_protocols TLSv1 TLSv1.</span><span style="color: rgba(128, 0, 128, 1)">1</span> TLSv1.<span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">;
ssl_prefer_server_ciphers on;
location </span>/<span style="color: rgba(0, 0, 0, 1)"> {
proxy_pass http:</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">127.0.0.1:3000/;</span>
<span style="color: rgba(0, 0, 0, 1)"> proxy_set_header Host $http_host;
proxy_set_header X</span>-Forwarded-<span style="color: rgba(0, 0, 0, 1)">For $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
}
}</span></pre>
</div>
<p> </p>
<h3>2.1.6 服务器-docker</h3>
<p>安装docker并设置远程访问,设置远程访问后,方便idea直接发布后端</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 安装docker
</span><span style="color: rgba(0, 0, 255, 1)">yum</span> <span style="color: rgba(0, 0, 255, 1)">install</span> docker-<span style="color: rgba(0, 0, 0, 1)">ce
# 启动docker
systemctl start docker
# 设置开机自启
systemctl enable docker
# 配置docker远程访问
</span><span style="color: rgba(0, 0, 255, 1)">vi</span> /usr/lib/systemd/system/<span style="color: rgba(0, 0, 0, 1)">docker.service
# 在ExecStart</span>=/usr/bin/<span style="color: rgba(0, 0, 0, 1)">dockerd后面添加
</span>-H tcp:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">0.0.0.0:1264</span>
<span style="color: rgba(0, 0, 0, 1)">
# 重新加载和重启
systemctl daemon</span>-<span style="color: rgba(0, 0, 0, 1)">reload
systemctl restart docker.service</span></pre>
</div>
<p> </p>
<h3>2.1.7 服务器-minio</h3>
<p>安装成功后,浏览器打开9090端口,创建Minio仓库并设置权限,既可在后端配置使用,此处通过docker安装minio</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 下载镜像
docker pull minio</span>/<span style="color: rgba(0, 0, 0, 1)">minio
# 查看镜像
ddocker images
# 创建两个目录,一个用来存放配置,一个用来存储上传文件的目录,启动前需要先创建Minio外部挂载的配置文件( </span>/home/minio/config),和存储上传文件的目录( /home/minio/<span style="color: rgba(0, 0, 0, 1)">data)
</span><span style="color: rgba(0, 0, 255, 1)">mkdir</span> -p /home/minio/<span style="color: rgba(0, 0, 0, 1)">config
</span><span style="color: rgba(0, 0, 255, 1)">mkdir</span> -p /home/minio/<span style="color: rgba(0, 0, 0, 1)">data
# 启动
docker run </span>-p <span style="color: rgba(128, 0, 128, 1)">9000</span>:<span style="color: rgba(128, 0, 128, 1)">9000</span> -p <span style="color: rgba(128, 0, 128, 1)">9090</span>:<span style="color: rgba(128, 0, 128, 1)">9090</span><span style="color: rgba(0, 0, 0, 1)"> \
</span>--net=<span style="color: rgba(0, 0, 0, 1)">host \
</span>--<span style="color: rgba(0, 0, 0, 1)">name minio \
</span>-d --restart=<span style="color: rgba(0, 0, 0, 1)">always \
</span>-e <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">MINIO_ACCESS_KEY=yourAccount</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> \
</span>-e <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">MINIO_SECRET_KEY=yourPassword</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> \
</span>-v /home/minio/data:/<span style="color: rgba(0, 0, 0, 1)">data \
</span>-v /home/minio/config:/root/<span style="color: rgba(0, 0, 0, 1)">.minio \
minio</span>/<span style="color: rgba(0, 0, 0, 1)">minio server \
</span>/data --console-address <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">:9090</span><span style="color: rgba(128, 0, 0, 1)">"</span> -address <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">:9000</span><span style="color: rgba(128, 0, 0, 1)">"</span></pre>
</div>
<p><br> </p>
<h3>2.1.8
服务器-redis</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 安装
</span><span style="color: rgba(0, 0, 255, 1)">wget</span> http:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">download.redis.io/releases/redis-6.2.1.tar.gz</span>
<span style="color: rgba(0, 0, 255, 1)">tar</span> xzvf redis-<span style="color: rgba(128, 0, 128, 1)">6.2</span>.<span style="color: rgba(128, 0, 128, 1)">1</span>.<span style="color: rgba(0, 0, 255, 1)">tar</span><span style="color: rgba(0, 0, 0, 1)">.gz
cd redis</span>-<span style="color: rgba(128, 0, 128, 1)">6.2</span>.<span style="color: rgba(128, 0, 128, 1)">1</span>
<span style="color: rgba(0, 0, 255, 1)">make</span><span style="color: rgba(0, 0, 0, 1)">
cd src
</span><span style="color: rgba(0, 0, 255, 1)">make</span> <span style="color: rgba(0, 0, 255, 1)">install</span> PREFIX=/home/app/<span style="color: rgba(0, 0, 0, 1)">redis
# redis全局环境</span>/etc/<span style="color: rgba(0, 0, 0, 1)">profile
REDIS_HOME</span>=/home/app/<span style="color: rgba(0, 0, 0, 1)">redis
export PATH</span>=$PATH:$REDIS_HOME/<span style="color: rgba(0, 0, 0, 1)">bin
# 设置密码,配置</span>/home/app/redis/<span style="color: rgba(0, 0, 0, 1)">redis.conf
# bind </span><span style="color: rgba(128, 0, 128, 1)">127.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.1</span> -::<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">
daemonize yes
protected</span>-<span style="color: rgba(0, 0, 0, 1)">mode no
requirepass yourPassword</span></pre>
</div>
<p> </p>
<p>最后这步很重要,修改redis端口,个人服务器曾因未修改端口被攻击,修改端口号方式</p>
<div class="cnblogs_code">
<pre>/home/app/redis/<span style="color: rgba(0, 0, 0, 1)">redis.conf
# 改变默认端口号port </span><span style="color: rgba(128, 0, 128, 1)">6379</span></pre>
</div>
<p> </p>
<h3>2.1.9 服务器-mysql8</h3>
<p>安装mysql8后,对于每个系统,要使用单独的数据库以及单独的账号,避免某个数据库账号泄露导致所有数据库数据泄露的风险。</p>
<p>备份数据很重要,通过Linux的定时任务crontab,实现数据库每日备份,并删除过期的备份</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341737-1169624854.png" alt=""></p>
<p> 在scripts中写入备份和删除备份脚本</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 备份脚本</span>
vim /home/mysql_tump/scripts/backup.<span style="color: rgba(0, 0, 255, 1)">sh</span><span style="color: rgba(0, 0, 0, 1)">
#</span>!/bin/<span style="color: rgba(0, 0, 0, 1)">bash
# 备份目录
BACKUP_ROOT</span>=/home/<span style="color: rgba(0, 0, 0, 1)">mysql_tump
BACKUP_FILEDIR</span>=$BACKUP_ROOT/<span style="color: rgba(0, 0, 0, 1)">files
BACKUP_LOGDIR</span>=$BACKUP_ROOT/<span style="color: rgba(0, 0, 0, 1)">logs
# 当前日期
DATE</span>=$(<span style="color: rgba(0, 0, 255, 1)">date</span> +%Y%m%<span style="color: rgba(0, 0, 0, 1)">d)
DATABASES</span>=<span style="color: rgba(0, 0, 0, 1)">(testdb test2db)
# 循环备份
</span><span style="color: rgba(0, 0, 255, 1)">echo</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Begin for mysql tump!</span><span style="color: rgba(128, 0, 0, 1)">'</span>
<span style="color: rgba(0, 0, 255, 1)">for</span> db <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> ${DATABASES[@]}
</span><span style="color: rgba(0, 0, 255, 1)">do</span> <span style="color: rgba(0, 0, 255, 1)">echo</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Is tumpping</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)"> ${db}
mysqldump </span>--defaults-extra-<span style="color: rgba(0, 0, 255, 1)">file</span>=/etc/my.cnf --default-character-set=utf8 --lock-all-tables --flush-logs --log-error=$BACKUP_LOGDIR/${db}_$DATE.error.log -E -R -B ${db} | <span style="color: rgba(0, 0, 255, 1)">gzip</span>> $BACKUP_FILEDIR/<span style="color: rgba(0, 0, 0, 1)">${db}_$DATE.sql.gz
</span><span style="color: rgba(0, 0, 255, 1)">done</span>
<span style="color: rgba(0, 0, 255, 1)">echo</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Success for mysql tump!</span><span style="color: rgba(128, 0, 0, 1)">'</span></pre>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 删除过期备份</span>
vim /home/mysql_tump/scripts/backup_rm.<span style="color: rgba(0, 0, 255, 1)">sh</span><span style="color: rgba(0, 0, 0, 1)">
#</span>!/bin/<span style="color: rgba(0, 0, 0, 1)">bash
# 删除备份
</span><span style="color: rgba(0, 0, 255, 1)">echo</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Begin for remove tump!</span><span style="color: rgba(128, 0, 0, 1)">'</span>
<span style="color: rgba(0, 0, 255, 1)">find</span> /home/mysql_tump/files -type f -mtime +<span style="color: rgba(128, 0, 128, 1)">5</span> | <span style="color: rgba(0, 0, 255, 1)">xargs</span> <span style="color: rgba(0, 0, 255, 1)">rm</span> -<span style="color: rgba(0, 0, 0, 1)">f
</span><span style="color: rgba(0, 0, 255, 1)">find</span> /home/mysql_tump/logs -type f -mtime +<span style="color: rgba(128, 0, 128, 1)">5</span> | <span style="color: rgba(0, 0, 255, 1)">xargs</span> <span style="color: rgba(0, 0, 255, 1)">rm</span> -<span style="color: rgba(0, 0, 0, 1)">f
</span><span style="color: rgba(0, 0, 255, 1)">echo</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Success for remove tump!</span><span style="color: rgba(128, 0, 0, 1)">'</span></pre>
</div>
<p> </p>
<p>编辑Linux自带的crontab任务,加入备份和删除备份两个任务</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 定时任务</span>
crontab -<span style="color: rgba(0, 0, 0, 1)">e
</span><span style="color: rgba(128, 0, 128, 1)">00</span> <span style="color: rgba(128, 0, 128, 1)">05</span> * * * bash /home/mysql_tump/scripts/backup.<span style="color: rgba(0, 0, 255, 1)">sh</span>
<span style="color: rgba(128, 0, 128, 1)">30</span> <span style="color: rgba(128, 0, 128, 1)">05</span> * * * bash /home/mysql_tump/scripts/backup_rm.<span style="color: rgba(0, 0, 255, 1)">sh</span></pre>
</div>
<p> </p>
<h3>2.1.10 域名选择与规划</h3>
<p>小程序只支持https域名式地址,因此必须有一个域名。可在云服务商注册购买域名或其它域名方式,域名需要进行备案。</p>
<p> </p>
<p>域名规划,可以解析多个子域名。子域名全部指向服务器IP,具体子域名通过nginx指向不同的端口,负载均衡也由nginx完成,通过不同域名指向不同的领域系统或功能,如:</p>
<p>www.ninecloud.top — 网站首页与静态文件路径</p>
<p>api.ninecloud.top — 接口</p>
<p>fs.ninecloud.top — Nginx文件服务</p>
<p>minio.ninecloud.top — Minio文件服务</p>
<p>git.ninecloud.top — Gitea服务</p>
<p>doc.ninecloud.top —文档服务</p>
<p> </p>
<p>每个子域名需要各自申请ssl证书,将证书文件放在服务器并在nginx里配置,即可实现https访问,在云服务商购买的域名都有免费的ssl证书可用。</p>
<br>
<h3>2.1.11
短信方案</h3>
<p>系统存在验证码登录,找回密码等场景,因此有短信发送需求,可购买各服务商平台的短信套餐,接入系统</p>
<p> </p>
<p>发送短信需要在服务商的云平台申请签名和模版,各个云平台管理都比较严格,尤其是个人用户,申请签名和模版很难成功,需要耐心。</p>
<p> </p>
<h3>2.1.12
微信公众平台</h3>
<p>微信发布小程序,需要在微信公众平台注册小程序账号,进行一些认证,https地址白名单设置,权限设置等,拿到后续开发需要的AppID,AppSecret等关键数据。</p>
<p> </p>
<h3>2.1.13
其它小程序平台</h3>
<p>尝试成功发布过支付宝小程序,和微信小程序大同小异。</p>
<br>
<h3>2.1.14
App发布</h3>
<p>通过jre生成Android签名证书,然后使用开发工具HbuilderX工具打包成apk文件,放在服务器,用户直接通过浏览器下载即可安装使用。</p>
<p>Window本地生成App证书方式,dos命令进入jre目录</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 生成证书 </span>
<span style="color: rgba(0, 0, 0, 1)">#testalias为证书别名,test.keystore为证书名称
keytool </span>-genkey -alias testalias -keyalg RSA -keysize <span style="color: rgba(128, 0, 128, 1)">2048</span> -validity <span style="color: rgba(128, 0, 128, 1)">36500</span> -<span style="color: rgba(0, 0, 0, 1)">keystore test.keystore
# 中间的内容不用填,在最后的提示中,确认证书密码
Enter key password </span><span style="color: rgba(0, 0, 255, 1)">for</span> <testalias>(RETURN <span style="color: rgba(0, 0, 255, 1)">if</span> same as keystore password): </pre>
</div>
<p> </p>
<p>在HbuilderX中,打包app时,需要用到上面的文件,别名,和密码</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341995-1402760841.png" alt=""><br clear="all">
</p>
<p> </p>
<h2>1.2
代码方案</h2>
<h3>2.2.1
开发发布说明</h3>
<p>工具说明:</p>
<p>后端工具idea 框架若依Plus</p>
<p>前端工具VsCode 框架若依vue3</p>
<p>移动端工具HbuilderX 框架uniapp vue3</p>
<p>微信小程序的devtools</p>
<p>数据库工具dbever</p>
<p>服务器工具Xshell、Xftp</p>
<p>其它工具,接口调试postman,P图工具photoshop,redis客户端等</p>
<p> </p>
<p>发布说明:</p>
<p>后端发布,在idea设置docker远程连接,打包jar,以docker命令部署</p>
<p>前端部署,生成静态文件,上传服务器/home/www/msw/目录</p>
<p>移动端H5,生成静态文件,上传服务器/home/www/uquestion目录</p>
<p>移动端App,打包生成Apk后,上传/home/nginx/apk目录,通过系统配置指定路径和版本确定apk地址,App端可自动触发升级,H5端可点击下载</p>
<p>移动端微信小程序,HbuilderX生成小程序项目文件,通过微信小程序工具打开文件上传即可,然后进入微信公众平台,进行审核升级</p>
<br>
<h3>2.2.2
后端开发-数据库设计</h3>
<p>分类表</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341721-1609175646.png" alt=""></p>
<p>问题表</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341734-1077024123.png" alt=""></p>
<p>评论表</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341606-33055996.png" alt=""></p>
<p>日志表</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341462-868282412.png" alt=""></p>
<h3>2.2.3
后端开发-框架基于若依Plus</h3>
<p>在若依plus基础上增加ruoyi-question模块,单独存放题库相关功能。</p>
<p>其中四个controller分别是:class-题目分类,option-题目,log-收藏和错题,comment-提问与回复</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100342229-2099271719.png" alt=""></p>
<p> </p>
<p>通过SaToken的SaMode.OR模式,给PC端和手机端都需要的接口增加两个任意满足权限,手机端通用基础权限可固定为app:base:api,用户注册会默认给到相关权限,权限代码例如:</p>
<div class="cnblogs_code">
<pre>@SaCheckPermission(value = {<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">question:class:list</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">app:base:api</span><span style="color: rgba(128, 0, 0, 1)">"</span>}, mode =<span style="color: rgba(0, 0, 0, 1)"> SaMode.OR)
@GetMapping(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/getAllList</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
public R</span><List<QuestionClass>><span style="color: rgba(0, 0, 0, 1)"> getAllList(QuestionClass entry) {
return R.ok(baseService.getAllList(entry));
}</span></pre>
</div>
<p> </p>
<h3>2.2.4 后端开发-登陆/注册机制</h3>
<p>登录有两种方式,账号密码登录和手机号验证码登录,个人版的微信小程序只能获取OpenId无法获取手机号,OpenId唯一可用于登录, 但由于多端可注册可登录,避免同一用户出现两个账号,因此小程序放弃OpenId登录,与其它端保持一致。</p>
<p>以下是登陆Body文件, 其中通过登陆类型type来区分两种登陆方式,通过registerFlag来判断手机号验证码登录时,系统没有账号是否自动注册。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">@Data
public class AppLoginBody {
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 登录类型,0-密码登录,1-验证码登录
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
@NotBlank(message </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">登录类型不允许为空</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
private String type;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 用户账号
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
@NotBlank(message </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{user.username.not.blank}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
@Length(min </span>= UserConstants.USERNAME_MIN_LENGTH, max = UserConstants.USERNAME_MAX_LENGTH, message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{user.username.length.valid}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
private String username;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 用户密码
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
private String password;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 验证码
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
private String smsCode;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 注册标记 为1表示无用户则注册
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
private String registerFlag;
}</span></pre>
</div>
<p> </p>
<h3>2.2.5 后端开发-其它说明</h3>
<p>其它还需使用后端的功能有:公告,系统配置,退出登录,更新用户信息或头像,找回密码,发送短信,生成二维码等</p>
<br>
<h3>2.2.6
前端开发-框架基于若依Vue3</h3>
<p>将若依前端代码进行深度改造,样式代码尽可能单独提出来在index.scss引入,统一管理,如表格样式,弹窗样式,树样式等。</p>
<p>好处是尽可能在具体页面不再写css内容,确保整个系统样式统一,便于维护和修改。</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704101439135-1922780358.png" alt=""></p>
<p> 表格样式</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704101439019-1908094444.png" alt=""></p>
<p> 将函数方法组件分四个引用文件一次性引入,避免在main.js文件内写太多的内容,不利于维护和管理</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704101439118-901242062.png" alt=""></p>
<p> </p>
<p>如框架工具具体情况如下,后续新增公共js方法文件,直接在此文件添加即可;新增单个的方法,直接在对应文件添加方法即可,无需再引用。</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704101439341-41899106.png" alt=""></p>
<h3>2.2.7
前端开发-题目分类管理</h3>
<p>题库分类,使用树形结构设计,支持增删改查和复制,以及点击题目数量跳转到对应分类的题目管理页面</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341955-347240952.png" alt=""></p>
<p> </p>
<p>分类的编辑</p>
<p><img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341084-145281227.png" alt=""></p>
<p> </p>
<h3>2.2.8
前端开发-题目管理</h3>
<p>题目管理,除常规的增删改查外,还有批量删除和复制功能,列表支持单击勾选,表格关键字过滤题目,列显示隐藏等。</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100342226-1104855496.png" alt=""></p>
<p> </p>
<p>编辑功能,选项支持添加与删除,添加时,自动按ABCDE…的顺序排列,删除时重新排列顺序。</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341235-720503889.png" alt=""></p>
<br>
<h3>2.2.9
移动端开发-框架参考若依App</h3>
<p>仅仅是参考若依App移动端,第一是因为若依App是vue2版本,第二是因为若依App很多功能都没有开发,不足以支撑完整功能。</p>
<p>与PC端一样,尽可能把样式文件单独提取出来,避免重复写css,如图片样式,广告样式,列表样式等</p>
<p>由于移动端的样式复杂多变,很难固定统一,所以用基础样式,将常用的样式全部写好,html的class直接用即可。</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100342302-1095247273.png" alt=""></p>
<p align="left"> </p>
<p align="left">公共方法的引用,同前端一样,由一个文件引用全部方法,然后在main.js里面引用此文件,方便维护和管理。</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100342102-1645674004.png" alt=""></p>
<p> </p>
<p>组件引用,uniapp组件只需要按规范写在components文件下即可</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341162-291433807.png" alt=""></p>
<p> </p>
<h3>2.2.10
移动端开发-功能规划</h3>
<p>App采用常规设计,有三个导航按钮,分别是首页,题库,我的</p>
<p><img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341817-872001052.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341140-275855849.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100340898-545712152.png" alt=""></p>
<p> 首页,最上用滚动banner和动态公告,让系统有一些动感,下方放直通分类的题目。</p>
<p> 题库页,使用树形结构展示所有分类和题目,最上方搜索栏可以过滤题目和选择是否有答案的题目。</p>
<p> 我的页,常规功能是用户信息,上传图像,邀请好友,设置等,其中系统设置、题库分类、题目管理不在App基础权限内,一般用户未授权前不可见,我的收藏和我的错题记录刷题过程中的操作。</p>
<p align="left"> </p>
<h3>2.2.11
移动端开发-登陆机制</h3>
<p>对于移动端,用户登陆后,如果以账号密码方式登陆,则存储用户名密码,同时存储获取到的token等信息,后续登陆同PC前端一样,header中携带token进行访问;</p>
<p>不同之处在于,移动端token过期后,自动触发重登陆机制,用存储中的账号密码自动重新登录,用户是没有任何感觉的,避免每次打开都要重新登陆。同时设置退出和清除缓存功能,让用户可以清除缓存信息。</p>
<p>
<img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341519-556746049.png" alt="" width="332" height="716"> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341355-12015.png" alt="" width="332" height="716"></p>
<br>
<h3>2.2.12
移动端开发-上传图像</h3>
<p>若依App原版上传图像截取,图片是铺满方式,支持有限,个人用不惯。重写图像裁剪组件,经过一番调试后,兼容H5、App和微信小程序。</p>
<p>组件支持左右上下翻转、不同角度的旋转、不同程度的放大缩小,支持预览等。</p>
<p><img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100342017-2044630214.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100342013-1059967296.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100342050-1096795729.png" alt=""></p>
<p> </p>
<h3>2.2.13
移动端开发-App更新机制</h3>
<p>H5端直接显示最新版本,支持下载App。</p>
<p>App端每次更新一个版本进行打包,将打包的apk文件以版本号命名,上传到文件服务器,准备发布新版本直接就改配置参数为对应版本即可;</p>
<p>用户登陆后获取到最新参数配置,其中有App的版本号,和当前系统版本号比对,如果不一致则提醒用户更新。</p>
<p>以下分别是App-我的页面,App关于页面,H5关于页面情况</p>
<p>
<img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341753-1104689354.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341020-296154177.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341282-233773315.png" alt=""></p>
<br clear="all">
<p align="left"> </p>
<h3>2.2.14
移动端开发-分类/题目管理</h3>
<p>分类和题目管理,都采用树形结构,移动端录入题目一样支持复制,但如果在手机端操作还是没那么方便,如果是修改或者是新增少量,倒是比较快,如果很多,用电脑以H5地址打开,或者直接在后端管理录入,效果更好</p>
<p><img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341205-1754013979.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341325-1690142834.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341275-1440856892.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341582-813078128.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341266-1633097227.png" alt=""></p>
<p>
</p>
<h3>2.2.15
移动端开发-题库刷题</h3>
<p>刷题页面支持搜索,搜索后非末级也显示题目数量,并可点击进入搜索内容的全部题目</p>
<p><img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341319-529915703.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341498-511981296.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100340877-1750133036.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100341332-681567894.png" alt=""> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100340870-249242241.png" alt=""></p>
<p> </p>
<h3>2.2.16
移动端开发-其它功能</h3>
<p>邀请好友,服务器根据用户图像生成二维码,更改二维码样式颜色等。</p>
<p>
<img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100342101-1195397094.png" alt=""></p>
<p>设置-含公告查看,修改密码,联系我们,清空缓存,关于,退出登录</p>
<p> <img src="https://img2024.cnblogs.com/blog/1094982/202407/1094982-20240704100340382-419068043.png" alt=""></p>
<p align="left"> </p>
<h1>3.
总结一下</h1>
<p>做一套完整的系统,可以完善自己所学的知识,找到自己的弱项,也有一种成就感。</p>
<p>题库功能还有优化空间,如题库类型需要问答型题目,题目还可以展示做题人数,正确率等信息,评论支持上传图片等。</p>
</div>
<div id="MySignature" role="contentinfo">
<div>作者名称:Vrapile</div>
<div>联系方式:发送邮件Vrapile@163.com,另博客账号也是本人微信号。</div>
<div>版权声明:此文是博主业余爱好所写,文章或有错误与不足之处,欢迎留言指正建议、共同探讨!另此文为博主原创,未经博主同意不得转载,否则保留追究法律责任的权利。 </div><br><br>
来源:https://www.cnblogs.com/Vrapile/p/18283068 哇,楼主太给力了!这么详细的全栈开发心得分享,必须mark一下!
先感谢楼主的无私分享!
看完了整篇帖子,感觉信息量好大,从服务器选型到小程序发布,从后端到前端,讲得都非常详细。对我们这些想自己动手做项目的人来说,简直就是宝藏级别的教程!
简单总结一下我学到的:
技术栈:Java + Vue + uniapp,覆盖PC端和移动端
服务器成本:云服务器 + 域名 + 短信 + 小程序认证,一年几百块搞定
开发框架:若依系列,确实很适合快速开发
部署方案:Docker + Nginx + MySQL + Redis + Minio,标准的微服务架构
有个小问题想请教一下:
[*]关于数据库设计,分类表和题目表的关联是怎么处理的?有没有考虑到题目有多级分类的情况?
[*]移动端图片裁剪组件是自己重写的吗?有没有现成的uniapp组件推荐?
[*]小程序审核需要注意哪些坑?我看楼主提到微信审核比较严格
最后想说:独立开发一套系统确实很考验耐心和实力,楼主能做成这样已经很棒了!希望能看到后续的优化分享,比如题目类型扩展、做题统计功能等。
已收藏原文链接,有时间再慢慢研究!
支持楼主,期待更多作品!👍👍👍
頁:
[1]