东软餐厅项目问题解决心得

东软餐厅项目问题解决心得
Silence前言
经过不懈的努力前端在我的磕磕绊绊的情况下终于写得差不多了,今天终于开始搭建后端的项目了。后端遇到的问题和前端的修改思路都会记录在这里
解析MySQL Connector/J依赖时遇到了问题
旧版本的MySQL JDBC依赖mysql-connector-java更新为mysql-connector-j
1 | <dependency> |
分包部署的详细方法
- 首先需要通过正常的流程创建springBoot项目。等待项目初始化完成后。
- 对着项目进行右键,点击新建,创建新模块
注意: 这里创建的是maven项目
- 修改pom文件
修改父工程下的pom文件中的打包方式为pom,如下
idea会自动设置为父工程 - 创建子工程
Archetype:选择org.apache.maven.archetypes:maven-archetype-quickstart
选quickstart没得那么多的其他杂七杂八的东西,是最接近纯骨架的模板
我这里创建了两个子工程看起来直观一点,只要看到下面的加载完成了就成功了
分包后修改名字
对着需要修改的模块右键,然后点击重构,选择两个都修改
但是事情往往不会这么简单,在你修改名字后你发现包的前面代表那个包的小蓝点不见了,
这就是我们修改后没有将包导入进来,现在这个只是一个修改名字后的文件夹,不是一个模块了。
解决方法:
在项目模块中,上面有个加号。点击导入模块,把你修改后的模块导入进来。
导入后会让你选择构建的东西,选择maven构建就行了。这样就解决了分包部署后修改名字的问题
首页滑动组件数据渲染与随机展示功能
- 看过我们这个首页滑动组件的就知道,我们这些数据不是一个固定的数据,而是从数据库中获取的,所以我们需要在页面上渲染出来。总体的流程就是先从数据库中获取数据,然后将数据进行一个随机取出,然后调用uinapp 的 组件,进行多次渲染,当然可以是用for循环进行渲染。
我们这里开启的是横向的滑动, 同时渲染了五个数据。
1 | <template> |
1. 导入必要的模块和类型
1 | import {getDishListAPI} from '@/api/dish' |
- 导入API函数:
getDishListAPI
、getSetmealListAPI
、getCategoryAPI
。 - 导入生命周期钩子:
onLoad
、onShow
。 - 导入Vue的响应式引用函数:
ref
。 - 导入类型定义:
DishItem
、CategoryItem
、SetmealItem
。
2. 定义响应式变量
1 | const dishList = ref<(DishItem | SetmealItem)[]>([]) |
dishList
:存储菜品或套餐的列表。categoryList
:存储分类列表。activeIndex
:存储当前激活的分类索引。randomDish
:存储随机选择的菜品或套餐。
3. 定义方法
1 | const getCategoryData = async () => { |
getCategoryData
:异步获取分类数据并更新categoryList
。getDishOrSetmealList
:根据分类类型(菜品或套餐)异步获取对应的列表数据并更新dishList
。selectRandomDish
:从dishList
中随机选择一个菜品或套餐并更新randomDish
。
4. 生命周期钩子
1 | onLoad(async () => { |
onLoad
:页面加载时执行,获取分类数据、默认分类下的菜品列表,并随机选择一个菜品。onShow
:页面显示时执行,重新获取分类数据。
总结代码
主要功能
- 数据获取:通过API函数异步获取菜品、套餐和分类数据。
- 数据处理:根据分类类型获取对应的菜品或套餐列表,并从中随机选择一个菜品或套餐。
- 生命周期管理:在页面加载和显示时执行数据获取和处理逻辑,确保数据的实时性和正确性。
商店的状态获取问题解决
在最初的版本的时候,由于我没有做后台管理系统,这存在了一个问题。就是在前端获取商店状态的时候或出现无法请求的问题。
这里我们将后端的逻辑改一下
1 |
|
直接强制的为开放状态。这样前端就可以直接请求获取商店状态了。
整体项目部署构思
这个项目实际上是微信小程序端,和后端java端的结合体。我们现在的解决办法是,小程序的前端的部分和后端的java的部分分开部署。
前端部署是最简单的,注册开发者以后,在微信开发者工具后台,选择项目,然后上传代码即可。
后端部署的话,我们可以选择使用云服务器,比如阿里云或者腾讯云,然后在服务器上安装jdk、maven、mysql等环境,然后将项目代码上传到服务器,然后配置好数据库连接,然后启动项目。
这样的话,前端和后端的部署可以分开进行,前端只需要上传代码,后端只需要配置环境、启动项目即可。
微信小程序上传失败,代码审查不通过问题解决
这里可以看到问题出现在,图片和音频资源过大的缘故,由于小程序对于启动速度和资源大小的限制原因,我们如果将大量的静态资源放到本地的话
那么整个项目的占用将十分的大,同时会影响小程序的启动速度。
我们需要把一些图片资源放入云端存储中,通过网络调用进行一些图片的加载。
我这里采用的是我一直在用的阿里云oss存储,具体的配置方法可以参考阿里云的文档。
我这里创建了两个包,一个包装的是菜品的图片以及一些店招等信息
另一个是一存储一些,图标信息。当然是一些不是很重要的图标
第二部分其实很简单
就是需要开启一个组件的按需注入,和懒加载而已。这个不影响上传的
微信小程序上传为体验版后,出现无法获取后端请求问题
错误信息
GET https://你要访问的url(可以到浏览器上验证一下,访问之后成功的话会有一个json格式的文本出现)
net::ERR_PROXY_CONNECTION_FAILED(env: Windows,mp,1.06.2402030; lib: 3.3.5)
大概就是这种报错,这个其实是是由于代理的问题,我们打开微信小程序开发工具,在设置里面找到代理,然后关闭代理即可。
微信小程序上传为体验版后,出现无法获取后端请求问题(第二种情况)
小程序开发的时候,在用微信开发者工具做网络请求的时候,调试会出错,提示“不在以下 request 合法域名列表中,请参考文档” https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html
这个就是微信小程序的设定问题,他要求我们在体验版的时候是需要配置合法的request域名的,但是我们在做项目的时候,我们并没有配置合法的request域名,所以就会出现这个问题。 其实我个人不是很明白为什么要这样设定,因为这个是体验版我认为大部分的后端都还没有上传服务器
解决方法:
进入微信公众平台按照下面图片的要求来就行了
配置好后,再次请求,问题就解决了。
在idea中进行maven项目的打包
这里存在一个问题,就是当我们一个有多个模块进行开发的时候例如:
这个图片中,我们存在三个模块,其中,最后一个server模块依赖前两个模块的内容,而每个模块都是一个独立的maven项目,我们该如何打包才能使其打包后能够正常的使用呢?
解决办法:
首先需要明确,此时的项目模块的依赖关系是向下一次依赖的,即二级依赖依赖于一级依赖
所以我们的打包的思路就是先打包一级的依赖,再打包二级的依赖,最后打包三级依赖。(也就是最后一个运行的依赖)
但是看起来很复杂,这个问题其实解决很简单,只需要在maven项目下直接打包就行了。idea会根据我们的依赖关系进行一依次的打包,这也就对应了上述的多模块开发的思路。我们总体的打包操作是直接在父项目下进行的操作。
上传服务进行配置
我们这里使用宝塔面板进行一个部署,对于linux的相关操作我这里就不在过多的陈述了。
点击文件 ,创建一个文件用于存放打包好的包
上传完成后进入网站页面,找到java管理,这里看到我们已经存在了一个springboot的选项。这个是为什么呢?
那就是,因为打包后的springboot项目是自带tomcat的,执行命令后可以直接脱离外部环境直接运行的项目。所以这里我们可以直接部署将所在文件下的包进行选择,然后系统会自动的分配对应的端口,但是此时需要注意你的jdk版本,如果你的jdk版本和你的项目不适配的话,会出现运行失败的情况。
- 这里的端口也需要注意:点击开放端口后,只是你宝塔面板这里开放了端口,这个是不行的,因为你用的服务器,服务器厂商会有自己的拦截。我用的是阿里云的服务器,那么我需要去阿里云的安全组中放行对应的端口
Redis配置
目前应对,相对较小的访问量还不足以看出系统的访问速度的快慢,当有大量的人同时访问时,就会出现访问缓慢的情况,特别时加载菜品数据和切换菜品的时候,会出现加载不出来,或者加载缓慢的情况。这个问题的核心就是一个问题,那就是我们访问的数据是相同的,那么为什么我们不把他进行一个缓存,这部分内容属于高频访问的数据。这个就是Redis存在的核心
Redis的基本使用,我已经放在了前面的博客中,可以去找一找。
宝塔面板部署Redis数据库
宝塔面板部署Redis数据库其实核心是和自己电脑上部署没有什么区别。但是唯一存在的问题就是如何远程访问Redis服务器
宝塔面板下载Redis数据库
进入宝塔面板的应用商店,下载Redis并且等待Redis的配置完成
配置Redis
这里外网的配置为0.0.0.0 表示全部映射到公网上面去
这里映射到公网上面去需要配置Redis的密码。
开启端口访问权限
在宝塔面板中和服务器控制台的安全组中都需要放开这个端口,否则你在本地的SSH访问是不能访问的
端口开放后,直接输入相应的服务器ip地址,以及密码即可正常访问
后端重要代码记录
BaseContext
1 | public class BaseContext { |
ThreadLocal 用于在每个线程中存储独立的数据副本,确保线程安全。每个用户独立的一个线程避免了紊乱
BaseException
1 | public class BaseException extends RuntimeException { |
BaseException 基础的线程类,后面的各种异常信息都继承自这个类
RuntimeException 是 Java 中所有运行时异常的基类。
空参构造:
1 | public class BaseException extends RuntimeException { |
这个是个空参构造,创建一个没有消息的实例。反正我一般喜欢加一个空参构造
带参构造:
1 | public class BaseException extends RuntimeException { |
带参构造这边接收一个String类型的msg消息,调用父类(即 RuntimeException)的构造函数,并将 msg 传递给它。这使得 msg 成为异常的消息。
处理日期和时间的序列化与反序列化
这个序列化器是一个相对固定的代码
1 | public class JacksonObjectMapper extends ObjectMapper { |
Result
1 |
|
统一返回分页数据的分页数据
Serializable
序列化(Serialization)是将对象转换为字节流的过程,以便将其存储在文件中、在网络中传输或在内存中进行持久化。序列化的主要目的是保存对象的状态,以便在需要时可以重新创建该对象。
反序列化(Deserialization)是序列化的逆过程,即从字节流中重新创建对象。
为什么需要序列化和反序列化?
持久化存储:将对象序列化后可以存储在文件中,以便在程序重启后可以重新加载。
网络传输:在分布式系统中,对象需要在网络中传输,序列化可以将对象转换为字节流,便于传输。
进程间通信:在多进程环境中,序列化可以用于进程间传递对象。
Java中的序列化和反序列化
在Java中,实现序列化的方式是让类实现java.io.Serializable接口。这个接口是一个标记接口,没有任何方法,只是告诉JVM这个类的对象可以被序列化。
1 | /** |
这里的第一个succes是空的,对应的是部分操作返回的是空的结果(例如删除时,删除后不需要像前端返回对应的数据)
它们都接收一个泛型,三个成员变量:code、msg 和 data,分别表示状态码、错误信息和返回数据。通过静态方法 success 和 error,可以方便地创建表示成功或失败的结果对象。主要功能是提供一个统一的接口来返回后端处理的结果,便于前端进行统一处理和展示。
Utils
HttpClientUtil:
1 | /** |
HTTP GET请求,处理查询参数,并返回响应内容。它使用了Apache HttpClient库来执行HTTP请求,并确保在请求完成后正确关闭资源。
JWT:
1 |
|
- JWT(JSON Web Token)生成Token的基本流程可以总结如下:
- 定义Header:
指定签名算法,例如HS256。
2. 生成过期时间:
计算当前时间加上过期时间(以毫秒为单位),得到Token的过期时间。
3. 构建JWT:
使用Jwts.builder()创建一个JWT构建器。
设置Payload(Claims):将自定义的声明(claims)添加到JWT中。
设置签名:使用指定的签名算法和秘钥对JWT进行签名。
设置过期时间:将计算得到的过期时间添加到JWT中。
生成JWT字符串:
调用builder.compact()方法生成最终的JWT字符串。
Config
RedisConfiguration:
1 |
|
RedisTemplate 是 Spring Data Redis 提供的一个核心类,用于在 Spring 应用中与 Redis 进行交互。它封装了对 Redis 的各种操作,如字符串操作、列表操作、集合操作、哈希操作等,使得开发者可以更方便地使用 Redis 作为数据存储和缓存解决方案。
主要功能和特点
- 简化操作:RedisTemplate 提供了一系列简化的方法,如 opsForValue()、opsForList()、opsForSet() 等,用于处理不同类型的 Redis 数据结构。
- 序列化支持:RedisTemplate 支持自定义序列化器,可以将 Java 对象序列化为 Redis 存储的字节流,或者将字节流反序列化为 Java 对象。
- 线程安全:RedisTemplate 是线程安全的,可以在多个线程中共享使用。
- 事务支持:RedisTemplate 支持 Redis 事务,可以通过 multi()、exec() 等方法实现事务操作。
- 连接管理:RedisTemplate 内部管理 Redis 连接,开发者无需手动管理连接的创建和释放。
WebMvcConfiguration:
1 |
|
- 配置自定义拦截器:通过addInterceptors方法注册了两个JWT令牌验证拦截器,分别用于管理员和用户的路径拦截和排除
- 扩展消息转换器:通过extendMessageConverters方法扩展了Spring MVC的消息转换器,使用自定义的JacksonObjectMapper来处理Java对象与JSON数据之间的转换,并确保自定义的消息转换器优先使用。
展示的列表
数据模型:
categoryList:这个数组包含了所有的分类信息,每个分类都有一个id和name属性。
activeIndex:这是一个变量,用来追踪当前左侧分类列表中哪个分类是被激活的(即用户当前选中的分类)。
dishList:这个数组包含了当前选中分类下的所有菜品或套餐信息。
左侧分类列表:
使用v-for指令循环categoryList,为每个分类创建一个视图。
通过:class=”{active: index === activeIndex}”动态绑定active类,以突出显示当前激活的分类。
@tap=”getDishOrSetmealList(index)”:当用户点击某个分类时,会触发getDishOrSetmealList方法,并传递该分类的索引。
右侧菜品/套餐列表:
使用v-for指令循环dishList,为每个菜品或套餐创建一个视图。
:url属性动态生成跳转到详情页的链接,根据分类的sort属性决定是传递dishId还是setmealId。
方法getDishOrSetmealList:
这个方法接收一个参数index,即用户点击的分类的索引。
当该方法被触发时,它应该更新activeIndex的值,使其等于index。
同时,该方法会根据categoryList[activeIndex]中的分类信息,去获取该分类下的所有菜品或套餐,并更新dishList。
时间
arrivalTime.value = hours + ‘:’ + minutes.toString().padStart(2, ‘0’);
这行代码的作用是将arrivalTime的值设置为格式化后的时间字符串,确保分钟部分始终是两位数。具体解释如下:
代码解释
arrivalTime.value = hours + ‘:’ + minutes.toString().padStart(2, ‘0’);
CopyInsert
hours:表示小时部分,是一个整数。
minutes:表示分钟部分,是一个整数。
minutes.toString():将分钟部分转换为字符串。
padStart(2, ‘0’):确保分钟部分的字符串长度为2,如果不足2位,则在前面补0。
hours + ‘:’ + minutes.toString().padStart(2, ‘0’):将小时和分钟部分拼接成一个时间字符串,格式为HH:MM。
微信支付
payOrderAPI 是一个用于处理支付请求的API调用函数。以下是关于payOrderAPI的详细解释:
功能
payOrderAPI 的主要功能是向服务器发送支付请求,并更新订单的支付状态。
实现细节
在代码中,payOrderAPI 的实现如下:
const toSuccess = async () => {
// 若订单已超时,跳转到订单已取消页面
if (countdownStore.showM == -1 && countdownStore.showS == -1) {
uni.redirectTo({
url: ‘/pages/orderDetail/orderDetail?orderId=’ + orderId.value,
})
return
}
console.log(‘支付成功’)
// 支付后修改订单状态
const payDTO = {
orderNumber: orderNumber.value,
payMethod: 1, // 本平台默认微信支付
}
await payOrderAPI(payDTO)
// 关闭定时器
if (countdownStore.timer !== undefined) {
clearInterval(countdownStore.timer)
countdownStore.timer = undefined
}
uni.redirectTo({
url:
‘/pages/submit/success?orderId=’ +
orderId.value +
‘&orderNumber=’ +
orderNumber.value +
‘&orderAmount=’ +
orderAmount.value +
‘&orderTime=’ +
orderTime.value,
})
}
CopyInsert
参数
payOrderAPI 接受一个参数对象 payDTO,包含以下字段:
orderNumber:订单号。
payMethod:支付方式,这里默认是微信支付(值为1)。
调用方式
在 toSuccess 函数中调用 payOrderAPI:
const payDTO = {
orderNumber: orderNumber.value,
payMethod: 1, // 本平台默认微信支付
}
await payOrderAPI(payDTO)
CopyInsert
作用
发送支付请求:
payOrderAPI 向服务器发送支付请求,更新订单的支付状态。
关闭定时器:
在支付成功后,关闭倒计时定时器,确保不再继续倒计时。
跳转到支付成功页面:
支付成功后,跳转到支付成功页面,显示支付成功的信息。
总结
payOrderAPI 是一个用于处理支付请求的API调用函数,主要功能是向服务器发送支付请求并更新订单的支付状态。支付成功后,关闭倒计时定时器并跳转到支付成功页面。
请求的封装
这段代码是uni-app框架中的一个网络请求封装函数,以下是代码的重要部分及其解释:
泛型函数定义:
export const http =
http 是一个导出的常量,表示它可以在其他文件中被导入并使用。
(options: UniApp.RequestOptions) 表示函数接收一个参数 options,该参数的类型是 UniApp.RequestOptions,这是uni-app定义的网络请求配置类型。
Promise封装:
return new Promise<Data
返回一个新的 Promise 对象,这个 Promise 的解决值类型是 Data
(resolve, reject) 是Promise的执行器函数,resolve 用于在异步操作成功时解决Promise,reject 用于在操作失败时拒绝Promise。
uni.request调用:
uni.request({ …options, success: (res) => { … }, fail: (err) => { … } })
调用uni-app的 uni.request 方法来发送网络请求。
{ …options } 是将传入的请求配置展开,这样可以直接使用传入的配置。
success: (res) => { … } 是请求成功的回调函数,其中 res 是响应数据。
fail: (err) => { … } 是请求失败的回调函数,其中 err 是错误信息。
响应成功处理:
if (res.statusCode >= 200 && res.statusCode < 300) { … }
检查响应的状态码,如果是在200到299之间,则认为请求成功。
resolve(res.data as Data
401错误处理:
else if (res.statusCode === 401) { … }
如果状态码是401,表示未授权(通常是token过期)。
清理用户信息 userStore.clearProfile() 并跳转到登录页面 uni.navigateTo({ url: ‘/pages/login/login’ })。
调用 reject(res) 拒绝Promise。
其他错误处理:
else { … }
对于其他错误状态码,使用 uni.showToast 显示错误信息。
调用 reject 拒绝Promise,但在这种情况下没有显式调用,因为该分支仅用于显示错误信息。
响应失败处理:
fail: (err) => { … }
如果请求过程中发生网络错误或其他异常,则执行此回调。
使用 uni.showToast 显示网络错误信息。
调用 reject(err) 拒绝Promise。
这段代码的关键在于它封装了uni-app的网络请求,并提供了错误处理和Promise风格的异步操作,使得在组件或页面中调用网络请求更加方便和一致。
dto vo entity
使用场景
Entity的使用:
在数据库操作中,使用User、Order和Product实体来表示数据库中的记录。
例如,通过User实体来查询用户信息,通过Order实体来查询订单信息。
DTO的使用:
在Web服务中,客户端和服务器之间传输数据时使用UserDTO、OrderDTO和ProductDTO。
例如,客户端请求用户信息时,服务器返回UserDTO对象。
VO的使用:
在业务逻辑中,使用MoneyVO来表示货币金额和货币类型。
例如,计算订单总金额时,使用MoneyVO来封装金额和货币类型。
总结
Entity:用于持久化对象,包含业务逻辑和持久化操作。
DTO:用于数据传输,减少网络调用。
VO:用于表示不可变的值对象,封装相关属性。
通过这个例子,可以看到DTO、VO和Entity在不同场景下的具体应用和区别。
后端的处理
后端进来: 配置类 -> 拦截器 -> controller -> service -> serviceImpl ->mapper -> mapper.xml
微信登录
逐步分解并详细解释代码
类定义与依赖注入
public class UserServiceImpl implements UserService {// 微信服务接口地址
public static final String WX_LOGIN = “https://api.weixin.qq.com/sns/jscode2session“;@Autowired
private WeChatProperties weChatProperties;
@Autowired
private UserMapper userMapper;
CopyInsert
UserServiceImpl类实现了UserService接口。
WX_LOGIN是一个静态常量,存储了微信登录接口的URL。
weChatProperties和userMapper是通过Spring的依赖注入机制注入的,分别用于获取微信配置和操作数据库。
2. 用户微信登录方法
/**
- 用户微信登录
- @param userLoginDTO
- @return
*/
public User wxLogin(UserLoginDTO userLoginDTO) {
CopyInsert
wxLogin方法用于处理用户的微信登录请求。
方法参数userLoginDTO是一个数据传输对象,包含了用户登录所需的信息。
- 获取OpenID
// 调用私有方法,其中利用HttpClient来调用微信API服务,获取openid
String openid = getOpenId(userLoginDTO.getCode());
CopyInsert
调用私有方法getOpenId,传入userLoginDTO中的code参数,获取用户的OpenID。 - 检查OpenID是否为空
// 判断openid是否为空,如果为空表示登录失败,抛出业务异常
if (openid == null) {
throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
}
CopyInsert
检查获取到的OpenID是否为空。如果为空,表示登录失败,抛出LoginFailedException异常。 - 检查用户是否为新用户
// 判断当前用户是否为新用户
User user = userMapper.getByOpenid(openid);
CopyInsert
通过userMapper查询数据库,检查是否存在该OpenID对应的用户。 - 新用户自动注册
// 如果是新用户,自动完成注册,插入到数据库
if (user == null) {
user = User.builder()
.openid(openid)
.createTime(LocalDateTime.now())
.build();
userMapper.insert(user);
}
CopyInsert
如果用户不存在(即user为null),则创建一个新的User对象,并设置OpenID和创建时间,然后插入到数据库中。 - 返回用户对象
return user;
}
CopyInsert
最后,返回用户对象。
代码总结
这段代码的主要功能是处理用户的微信登录请求。具体步骤如下:
通过调用微信API获取用户的OpenID。
检查OpenID是否为空,如果为空则抛出登录失败异常。
检查数据库中是否存在该OpenID对应的用户。
如果用户不存在,则自动完成注册并插入到数据库中。
返回用户对象。
通过这些步骤,代码实现了微信登录功能,并确保新用户能够自动注册。
运维:
买服务器 -> 服务器备案 -> 购买域名 -> 域名审核 -> 进入服务器后台 -> 安装宝塔面板 -> 部署流程 -> 绑定域名 -> 域名安全认证 ->
微信小程序绑定安全域名 -> 进入宝塔面板 -> 放开端口 -> 进入阿里云控制台 -> 放开端口