简介:在Web应用开发领域,B/S架构与前后端分离是构建现代应用的基础模式。其核心原理在于将业务逻辑、数据持久化与用户界面解耦,通过HTTP协议进行数据交互,从而实现高内聚、低耦合的系统设计。这种架构的技术价值在于提升了系统的可维护性、可扩展性以及团队协作效率。在众多应用场景中,教育科技领域尤为典型,常通过引入游戏化设计来提升用户参与度与学习粘性。具体到技术实现,Java EE中的SSM框架组合——Spring、SpringMVC与MyBatis,为构建稳健的后端服务提供了成熟方案,而微信小程序则以其轻量、即用的特性成为理想的前端载体。本文即以一个集成了积分、徽章与排行榜等游戏化元素的英语学习系统为例,详细剖析了如何运用SSM框架进行业务逻辑编排、数据库事务管理,并实现与微信小程序的安全、高效接口对接,为开发同类激励型应用提供了完整的工程实践参考。
1. 项目概述:一个能“激励”你学英语的微信小程序
看到这个项目标题,很多计算机专业的同学,尤其是正在为毕业设计发愁的朋友,估计眼睛都亮了。一个集齐了Java、SSM、MySQL和微信小程序这些主流技术栈的“英语学习激励系统”,听起来就很有搞头,而且附带了源码、数据库和论文,简直是毕业设计的“梦中情题”。但别急着下载解压,我们先来拆解一下,这个系统到底想干什么,以及它背后的技术逻辑是什么。
简单来说,这是一个典型的B/S(浏览器/服务器)架构的Web应用,只不过前端比较特殊,用的是微信小程序。它的核心目标不是简单地让你背单词、做阅读,而是通过一套机制来“激励”你持续学习。想象一下,你打开微信小程序,完成今天的听力任务,系统立刻给你加积分、发徽章,还能看到好友的排名,这种游戏化的体验,就是“激励”二字的精髓。它解决的核心痛点是:语言学习枯燥且难以坚持。通过积分、排名、任务挑战、成就系统等游戏化元素,将长期的学习目标拆解成可即时获得反馈的短期任务,从而提升用户的学习粘性和主动性。
这个项目非常适合几类人:首先是计算机、软件工程等相关专业的毕业生,它技术栈完整,业务逻辑清晰,工作量适中,是绝佳的毕设选题。其次是有一定Java Web基础,想通过一个完整项目来串联SSM框架、前后端交互和微信小程序开发的初学者。最后,如果你对教育科技、游戏化设计感兴趣,这个项目也能提供一个很好的实践案例。接下来,我们就深入这个系统的“五脏六腑”,看看它是如何被设计和构建出来的。
2. 系统核心设计与业务逻辑拆解
2.1 激励模型:从游戏化理论到功能落地
一个有效的激励系统,绝不是简单地在数据库里加个user_score字段那么简单。它需要一套深思熟虑的模型。在这个英语学习系统中,我推测其激励模型至少包含了以下几个层次:
- 目标层:设定清晰、可达成的学习目标。例如,“每日掌握10个新单词”、“完成一篇VOA慢速听力”。这些目标会被拆解成具体的任务。
- 规则层:定义完成目标后的反馈规则。这是激励的核心。通常包括:
- 积分系统:完成单词学习+10分,完成一篇阅读+20分,连续签到有额外加成。积分是量化的成就体现。
- 等级/徽章系统:积分累积到一定数值,自动升级(如从“英语小白”到“入门学徒”),或解锁特定徽章(如“七日战神”、“听力达人”)。这提供了阶段性的里程碑奖励。
- 排行榜系统:提供日榜、周榜、总榜。社交比较是强大的驱动力,尤其是看到好友排名时。
- 任务与挑战:除了日常任务,还可以有限时挑战(如“周末词汇大闯关”),增加趣味性和紧迫感。
- 反馈层:即时、可视化的反馈。微信小程序前端需要实时更新积分、弹出升级祝贺、展示徽章获取动画,让用户的每一次努力都“看得见”。
在数据库设计上,这就对应着几张核心表:用户表(user)扩展积分、等级字段;积分明细表(score_log)记录每一笔积分的来源;徽章表(badge)和用户徽章关联表(user_badge);任务表(task)定义任务内容与奖励;学习记录表(study_record)记录每次学习行为,作为积分发放的依据。
注意:激励系统的数值设计需要谨慎。积分通货膨胀(太容易获得)会使其贬值,失去激励作用;反之,获取太难又会打击用户积极性。一个常见的技巧是引入“衰减机制”或“动态难度”,例如,连续学习天数越多,每日基础奖励积分可以略有上浮。
2.2 技术架构选型:为什么是SSM+微信小程序?
标题中的技术栈“Java+SSM+MySQL+微信小程序”是一个非常经典且合理的组合,我们来分析一下背后的考量:
后端:Java + SSM
- Spring:作为核心容器,管理所有对象(Bean)的生命周期,提供依赖注入(IoC),让代码结构更清晰、更易于测试和扩展。比如,你的
UserService、TaskService可以很方便地被注入到控制器中。 - SpringMVC:承担Web层的职责,清晰地将请求(Request)、处理(Controller)、响应(View/JSON)分离开。它非常适合处理微信小程序通过HTTP协议发来的各种API请求(如
/api/user/login,/api/task/complete)。 - MyBatis:一个优秀的持久层框架,它通过XML或注解的方式,将Java方法(Mapper接口)与SQL语句绑定。相比传统的JDBC,它大幅减少了模板代码;相比全自动化的Hibernate,它给了开发者对SQL极大的控制权,这对于需要进行复杂查询(如多表关联查询排行榜数据)的学习系统来说,非常合适。
- 为什么不是SpringBoot?对于毕业设计而言,SSM需要手动整合XML配置,虽然比SpringBoot繁琐,但更能体现你对框架底层整合的理解,这在答辩时可能是一个加分项。SpringBoot是更现代、更高效的选择,它简化了配置。如果项目源码是SSM,理解其整合过程对学习大有裨益。
- Spring:作为核心容器,管理所有对象(Bean)的生命周期,提供依赖注入(IoC),让代码结构更清晰、更易于测试和扩展。比如,你的
前端:微信小程序
- 生态与便捷性:微信拥有巨大的用户基数,无需下载安装,扫码即用,非常适合学习这种轻度、高频的场景。
- 开发体验:小程序提供了丰富的原生组件(如按钮、输入框、滚动视图)和API(如网络请求、本地存储、用户信息),可以快速构建出体验良好的应用。其
WXML(模板)、WXSS(样式)、JS(逻辑)、JSON(配置)四件套的学习成本相对较低。 - 与后端交互:小程序通过
wx.requestAPI调用后端SSM项目提供的RESTful接口,以JSON格式进行数据交换,前后端分离,架构清晰。
数据库:MySQL
- 开源免费:对于学生项目和个人学习,没有比这更经济实惠的选择了。
- 成熟稳定:社区活跃,资料丰富,遇到任何问题几乎都能找到解决方案。
- 足以应对:一个学习激励系统的数据量(用户数、学习记录)在毕业设计甚至项目初期阶段,MySQL完全能够胜任。它的关系型模型也非常适合存储用户、任务、积分记录这类结构化数据。
这套技术栈组合,确保了项目的可行性、可展示性和技术深度,完全满足一个优秀毕业设计的要求。
3. 核心模块详解与数据库设计要点
3.1 用户与学习任务模块
这是系统的两大基石。用户模块不仅要处理注册登录,更要维护激励相关的核心属性。
用户表 (t_user或user) 关键字段设计:
CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `openid` varchar(100) DEFAULT NULL COMMENT '微信用户唯一标识,用于静默登录', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `avatar_url` varchar(500) DEFAULT NULL COMMENT '头像', `total_score` int(11) NOT NULL DEFAULT '0' COMMENT '总积分', `current_level` int(11) NOT NULL DEFAULT '1' COMMENT '当前等级', `continuous_login_days` int(11) NOT NULL DEFAULT '0' COMMENT '连续登录天数', `last_login_date` date DEFAULT NULL COMMENT '上次登录日期,用于计算连续登录', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) -- 唯一索引,确保一个微信用户对应一个系统账户 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';openid字段至关重要:微信小程序通过wx.login()获取code,传给后端,后端再用code、appid和secret去微信服务器换取openid和session_key。这个openid就是用户在微信生态内的唯一ID,用于实现免密登录。绝对不要在前端存储或传输secret,这是严重的安全隐患。- 连续登录计算逻辑:这是一个经典的业务逻辑。每次用户登录时,后端需要比对
last_login_date和当前日期。如果是第二天,则continuous_login_days加1;如果间隔超过一天,则重置为1。这个计算应该在服务端完成,避免客户端篡改。
任务表 (t_task) 与学习记录表 (t_study_record):任务表定义了“做什么”和“给多少奖励”。学习记录表则忠实记录用户“做了什么”。
CREATE TABLE `t_task` ( `id` int(11) NOT NULL AUTO_INCREMENT, `task_name` varchar(100) NOT NULL COMMENT '任务名称,如“完成10个单词学习”', `task_type` tinyint(4) NOT NULL COMMENT '任务类型:1-单词,2-听力,3-阅读,4-签到', `reward_score` int(11) NOT NULL DEFAULT '0' COMMENT '完成任务奖励积分', `goal_value` int(11) DEFAULT NULL COMMENT '目标值,如单词数10', `is_daily` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否为每日任务', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态:1-启用,0-禁用', PRIMARY KEY (`id`) ) COMMENT='任务定义表'; CREATE TABLE `t_study_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `user_id` int(11) NOT NULL COMMENT '用户ID', `task_id` int(11) NOT NULL COMMENT '任务ID', `completed_value` int(11) DEFAULT NULL COMMENT '完成值,如实际学了12个单词', `earned_score` int(11) NOT NULL COMMENT '本次获得的积分', `record_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间', PRIMARY KEY (`id`), KEY `idx_user_time` (`user_id`,`record_time`) -- 复合索引,便于按用户和时间查询学习历史 ) COMMENT='学习记录表';实操心得:在用户完成任务时,操作必须是事务性的。以“完成单词学习”为例,一个完整的后端服务方法应该包含以下步骤,并放在一个数据库事务中:
- 插入一条
t_study_record记录。 - 更新
t_user表中的total_score(原积分+本次奖励)。 - 检查并更新用户等级(根据新的
total_score查询等级配置表,如果等级提升,则更新current_level,并可能触发徽章发放)。 - 检查并发放可能的徽章(例如,检查
t_study_record中该用户完成“单词学习”类任务的次数,如果达到100次,则向user_badge表插入一条记录)。 这样做可以保证数据的一致性,避免积分增加了但等级没变,或者记录产生了但积分没加的尴尬情况。
3.2 积分、等级与排行榜的实现
这是激励系统的“发动机”。
积分流水表 (t_score_log):它的存在是为了实现“财务透明”,每一分积分从哪里来,都要有据可查。这对于后续可能出现的“积分纠错”、“活动追溯”至关重要。
CREATE TABLE `t_score_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `score_change` int(11) NOT NULL COMMENT '积分变动值,正为增加,负为消耗', `change_type` tinyint(4) NOT NULL COMMENT '变动类型:1-任务奖励,2-签到,3-兑换消耗,4-管理员调整', `source_id` varchar(50) DEFAULT NULL COMMENT '来源ID,如任务记录ID、签到记录ID', `remark` varchar(200) DEFAULT NULL COMMENT '备注', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user` (`user_id`) ) COMMENT='积分流水表';等级配置表 (t_level_config):等级不应该硬编码在Java代码里,而应该存储在数据库中,方便动态调整。
CREATE TABLE `t_level_config` ( `level` int(11) NOT NULL COMMENT '等级', `level_name` varchar(20) NOT NULL COMMENT '等级名称,如“青铜学徒”', `min_score` int(11) NOT NULL COMMENT '升级所需最低积分', `icon_url` varchar(500) DEFAULT NULL COMMENT '等级图标', PRIMARY KEY (`level`) ) COMMENT='等级配置表';当用户积分变动后,只需执行一条SQL即可判断是否升级:SELECT level FROM t_level_config WHERE min_score <= #{userScore} ORDER BY level DESC LIMIT 1。
排行榜的实现:排行榜是一个典型的读多写少、且对实时性要求较高的场景。
- 方案一:实时查询。每次请求排行榜时,执行SQL:
SELECT user_id, nickname, avatar_url, total_score FROM t_user ORDER BY total_score DESC LIMIT 100。对于毕业设计或用户量不大的初期项目,这个方案最简单直接。但务必在total_score字段上建立索引,否则随着用户量增长,排序操作会非常慢。 - 方案二:定时任务+缓存。这是更优的生产环境方案。用一个定时任务(如Spring的
@Scheduled注解),每隔一段时间(如5分钟)计算一次排行榜,将结果(如Top 100的JSON字符串)存入Redis中。微信小程序请求排行榜接口时,后端直接从Redis读取并返回,性能极高。这属于高级优化,在毕设中可以作为亮点提出。
踩坑提醒:在实现“好友排行榜”时,你需要获取用户的好友关系。微信小程序提供了
wx.getFriendCloudStorage接口,但这仅限于小游戏且使用开放数据域。对于普通小程序,无法直接获取微信好友列表。常见的替代方案是:1. 引导用户分享小程序给好友,通过分享卡片上的参数识别好友关系;2. 设计“邀请码”系统,通过邀请绑定好友关系;3. 直接做“全球榜”或“同校/同城榜”(需要用户额外授权或填写信息)。这是很多初学者容易误解的地方。
4. 前后端交互与微信小程序开发关键点
4.1 微信小程序登录与后端接口设计
这是前后端联调的第一个,也是最重要的关卡。
1. 小程序端登录流程:
// pages/login/login.js Page({ handleLogin() { // 1. 获取临时登录凭证code wx.login({ success: (res) => { if (res.code) { // 2. 将code发送给自家服务器 wx.request({ url: 'https://your-domain.com/api/auth/login', method: 'POST', data: { code: res.code }, success: (authRes) => { if (authRes.data.success) { // 3. 登录成功,服务器返回自定义登录态(如token)和用户信息 const token = authRes.data.token; const userInfo = authRes.data.userInfo; // 4. 将token存储到本地,用于后续接口鉴权 wx.setStorageSync('token', token); // 5. 更新全局状态或跳转首页 getApp().globalData.userInfo = userInfo; wx.switchTab({ url: '/pages/index/index' }); } } }); } } }); } })2. 后端SSM接口实现 (AuthController.java):
@RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private UserService userService; @Value("${wechat.appid}") private String appid; @Value("${wechat.secret}") private String secret; @PostMapping("/login") public ApiResponse login(@RequestBody Map<String, String> params) { String code = params.get("code"); // 1. 构造请求URL,向微信服务器换取openid和session_key String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appid + "&secret=" + secret + "&js_code=" + code + "&grant_type=authorization_code"; // 2. 使用HttpClient或RestTemplate发送GET请求 String wechatResponse = httpClient.get(url); JSONObject json = JSON.parseObject(wechatResponse); String openid = json.getString("openid"); String sessionKey = json.getString("session_key"); // 注意:session_key不应下发给客户端 if (openid == null) { return ApiResponse.error("微信登录失败"); } // 3. 根据openid查找或创建用户 User user = userService.findOrCreateByOpenid(openid); // 4. 生成自定义登录态(例如JWT Token) String token = JwtUtil.generateToken(user.getId().toString()); // 5. 返回token和用户信息给小程序 Map<String, Object> data = new HashMap<>(); data.put("token", token); data.put("userInfo", user); // 注意屏蔽敏感字段如openid return ApiResponse.success(data); } }关键安全点:appid和secret必须放在后端配置文件中(如application.properties),严禁写死在小程序前端代码里。session_key是微信服务器发回的密钥,用于后续解密用户敏感数据(如手机号),也应保存在服务端,与用户ID关联存储(如存Redis),绝不下发。
4.2 小程序页面布局与数据绑定
小程序开发的核心是数据驱动视图。以个人中心页面为例,展示用户积分、等级和徽章。
WXML模板 (pages/profile/profile.wxml):
<view class="profile-container"> <!-- 用户信息区域 --> <view class="user-card"> <image class="avatar" src="{{userInfo.avatarUrl}}"></image> <view class="user-meta"> <text class="nickname">{{userInfo.nickName}}</text> <view class="level-badge">Lv.{{userInfo.level}} {{userInfo.levelName}}</view> </view> </view> <!-- 积分与连续签到 --> <view class="stats-row"> <view class="stat-item" bindtap="goToScoreDetail"> <text class="stat-value">{{userInfo.totalScore}}</text> <text class="stat-label">总积分</text> </view> <view class="stat-item"> <text class="stat-value">{{continuousDays}}</text> <text class="stat-label">连续签到</text> </view> </view> <!-- 徽章列表 --> <view class="section-title">我的徽章</view> <scroll-view class="badge-list" scroll-x> <block wx:for="{{badgeList}}" wx:key="id"> <view class="badge-item {{item.earned ? 'earned' : 'locked'}}"> <image class="badge-icon" src="{{item.iconUrl}}"></image> <text class="badge-name">{{item.name}}</text> </view> </block> </scroll-view> <!-- 学习数据图表 (可使用echarts-for-weixin组件) --> <view class="section-title">本周学习趋势</view> <ec-canvas id="chart" canvas-id="chart" ec="{{ec}}"></ec-canvas> </view>JS逻辑 (pages/profile/profile.js):
Page({ data: { userInfo: {}, continuousDays: 0, badgeList: [], ec: { // echarts配置 lazyLoad: true } }, onLoad() { this.loadUserData(); }, loadUserData() { const token = wx.getStorageSync('token'); wx.request({ url: 'https://your-domain.com/api/user/profile', header: { 'Authorization': 'Bearer ' + token }, success: (res) => { if (res.data.success) { this.setData({ userInfo: res.data.data.userInfo, continuousDays: res.data.data.continuousDays, badgeList: res.data.data.badgeList }); // 初始化图表 this.initChart(res.data.data.studyTrend); } } }); }, initChart(trendData) { // 获取echarts组件实例 this.chartComponent = this.selectComponent('#chart'); this.chartComponent.init((canvas, width, height) => { const chart = echarts.init(canvas, null, { width, height }); const option = { xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] }, yAxis: { type: 'value' }, series: [{ data: trendData, type: 'line' }] }; chart.setOption(option); return chart; }); }, goToScoreDetail() { wx.navigateTo({ url: '/pages/scoreDetail/scoreDetail' }); } })实操心得:小程序的数据绑定是响应式的,但setData是异步的,且频繁调用可能影响性能。对于一次需要更新大量数据的情况,尽量合并到一次setData调用中。例如,不要分别设置userInfo和badgeList,而是:
this.setData({ userInfo: res.data.userInfo, badgeList: res.data.badgeList, continuousDays: res.data.continuousDays });5. 项目部署与上线踩坑实录
一个完整的毕业设计,除了本地运行,最好能部署到公网,让答辩老师随时可以扫码体验。这里以最常见的云服务器(CentOS 7) + Nginx + 域名为例。
5.1 后端项目打包与部署
- 打包:在SSM项目根目录下,使用Maven命令
mvn clean package -DskipTests,会在target目录下生成一个项目名.war文件(如果是War包)或一个可执行的Jar文件。 - 上传服务器:使用FTP工具(如FileZilla)或SCP命令,将War包上传到服务器的某个目录,例如
/home/app/。 - 安装Java环境:确保服务器已安装JDK 8或以上版本。
yum install java-1.8.0-openjdk-devel。 - 安装Tomcat:下载Tomcat 9,解压到
/usr/local/tomcat。将War包放入其webapps目录下。启动Tomcat:/usr/local/tomcat/bin/startup.sh。此时,你的后端API应该可以通过http://服务器IP:8080/你的项目名/访问了。
常见问题1:端口冲突或防火墙。如果8080端口无法访问,检查:1. Tomcat是否真的启动成功(查看
logs/catalina.out)。2. 服务器防火墙是否开放了8080端口(firewall-cmd --zone=public --add-port=8080/tcp --permanent然后firewall-cmd --reload)。3. 云服务商的安全组规则是否允许入站8080端口(在阿里云、腾讯云控制台配置)。
5.2 前端小程序配置与Nginx反向代理
- 小程序配置:在微信开发者工具中,将
wx.request的域名修改为你部署后端API的域名(如https://api.yourdomain.com)。注意:微信小程序要求后端接口必须使用HTTPS和备案域名。 - 申请域名与SSL证书:购买一个域名,并在云服务商处申请免费的SSL证书(如阿里云的DigiCert免费证书)。
- 安装并配置Nginx:
编辑Nginx配置文件# 安装 yum install nginx # 启动 systemctl start nginx systemctl enable nginx/etc/nginx/nginx.conf或/etc/nginx/conf.d/yourdomain.conf:
检查配置并重启Nginx:server { listen 443 ssl http2; # 监听443端口,启用SSL server_name api.yourdomain.com; # 你的API域名 ssl_certificate /path/to/your/certificate.pem; # SSL证书路径 ssl_certificate_key /path/to/your/private.key; # 私钥路径 location / { proxy_pass http://localhost:8080; # 将请求转发给本地的Tomcat 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_set_header X-Forwarded-Proto $scheme; } } # 可选:将80端口的HTTP请求重定向到HTTPS server { listen 80; server_name api.yourdomain.com; return 301 https://$server_name$request_uri; }nginx -t && systemctl reload nginx。
常见问题2:跨域问题(CORS)。如果你的Tomcat应用和小程序域名不同,即使经过Nginx代理,浏览器(小程序模拟器)仍可能遇到跨域问题。最彻底的解决方案是在后端SpringMVC中配置CORS过滤器,而不是依赖Nginx。在Spring配置文件中添加:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") // 生产环境应替换为具体的小程序域名 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowCredentials(true) .maxAge(3600); } }
5.3 数据库迁移与连接配置
本地开发用的localhost,部署到服务器后,数据库连接需要修改。
- 在服务器上安装MySQL:
yum install mysql-server,启动并设置开机自启:systemctl start mysqld && systemctl enable mysqld。运行mysql_secure_installation进行安全初始化(设置root密码等)。 - 导入数据库:将本地的数据库SQL文件(
project.sql)上传到服务器,然后导入:mysql -u root -p < /path/to/project.sql。 - 修改后端配置:将SSM项目中的数据库连接配置文件(如
jdbc.properties)从jdbc:mysql://localhost:3306/english_db改为jdbc:mysql://服务器内网IP:3306/english_db。强烈建议使用内网IP而非公网IP或localhost,更安全高效。同时,确保数据库用户有从应用服务器IP连接的权限:GRANT ALL PRIVILEGES ON english_db.* TO 'app_user'@'应用服务器内网IP' IDENTIFIED BY 'password'; FLUSH PRIVILEGES;。 - 重新打包部署:修改配置后,重新执行Maven打包,并上传新的War包到服务器,重启Tomcat。
部署完成后的检查清单:
- [ ] 后端API能否通过
https://api.yourdomain.com/api/health(假设你写了个健康检查接口) 正常访问并返回数据? - [ ] 小程序开发者工具中,将项目详情里的“不校验合法域名”勾选去掉,测试所有网络请求是否成功?
- [ ] 服务器磁盘空间、内存和CPU使用率是否正常?(
df -h,free -m,top) - [ ] Tomcat和Nginx的日志 (
/usr/local/tomcat/logs/catalina.out,/var/log/nginx/access.log和error.log) 有无报错信息?
走完这一套流程,你的“英语学习激励系统”就不再是躺在你电脑里的代码,而是一个真正可以访问、可以使用的产品了。这个过程里遇到的每一个报错,都是极好的学习材料,务必耐心查看日志,善用搜索。
本文还有配套的精品资源,点击获取