3.1 后端项目搭建
3.1.1 gitee下载脚手架
下载地址:https://gitee.com/77jubao2015/springbootdemo
打开浏览器输入以上地址,点击下载即可,如图所示:
3.1.2 把脚手架导入到idea开发工具
步骤01 把下载后的脚手架放到指定位置并解压,把解压后的文件夹名称改为pig_feet_rice即可,如图所示:
步骤02 打开IDEA开发选择Open,选择项目存放的路径,然后点击OK即可,如图所示:
步骤03 修改项目的名称为pig_feet_rice,如图所示:
步骤04 修改pom.xml文件
主要修改项目名称、Spring Boot版本号、JDK版本号等信息,代码如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.4</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>cn.xueden</groupId><artifactId>pig_feet_rice</artifactId><version>1.0.0</version><name>pig_feet_rice</name><description>springboot+Vue3 整合开发猪脚饭微信小程序</description><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--Mysql依赖包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.23</version></dependency><!-- druid数据源驱动 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.6</version></dependency><!--监控sql日志--><dependency><groupId>org.bgee.log4jdbc-log4j2</groupId><artifactId>log4jdbc-log4j2-jdbc4.1</artifactId><version>1.16</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--工具包--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.6.5</version></dependency></dependencies><build><finalName>xuedenpay1.0</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.4.5</version><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>
3.1.3 修改配置文件application.yml
这里我们主要是修改数据源即可,如图所示:
代码如下所示:
server:port: 8081#配置数据源
spring:datasource:druid:db-type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpyurl: jdbc:log4jdbc:mysql://127.0.0.1:3306/pig_feet_rice?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=falseusername: rootpassword: root# 初始化配置initial-size: 3# 最小连接数min-idle: 3# 最大连接数max-active: 15# 获取连接超时时间max-wait: 5000# 连接有效性检测时间time-between-eviction-runs-millis: 90000# 最大空闲时间min-evictable-idle-time-millis: 1800000test-while-idle: truetest-on-borrow: falsetest-on-return: falsevalidation-query: select 1# 配置监控统计拦截的filtersfilters: statstat-view-servlet:url-pattern: /druid/*reset-enable: falseweb-stat-filter:url-pattern: /*exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"#配置jpajpa:hibernate:# 生产环境设置成 none,避免程序运行时自动更新数据库结构ddl-auto: update
修改为自己本地电脑的数据库即可
3.1.4 修改启动类为PigFeetRiceApplication
如图所示:
3.2 前端项目搭建
3.2.1 从gitee下载前端demo
地址:https://gitee.com/77jubao2015/wxpaydemo
再浏览器打开上面的地址,点击下载即可,如图所示:
3.2.2 把项目导入到webstorm开发工具
步骤01 下载后直接解压,并把项目名称改为pig_feet_rice_web,如图所示:
步骤02 打开WebStorm 选择open,然后选择指定的项目即可,如图所示:
步骤03 修改标题
在src->store->modules->app.tsw文件下修改标题,如图所示:
改为如下所示:
3.2.3 在终端输入命令yarn install安装依赖
yarn install
如图所示
3.2.4 启动项目
选择左边npm,如图所示:
点击serve即可,如图所示:
3.3 系统后台登录功能实现
3.3.1 创建实体类Admin
在 cn.xueden.domain 包目录下新建一个 Admin实体类,代码如下所示:
package cn.xueden.domain;import cn.xueden.base.BaseEntity;
import lombok.Data;import javax.persistence.*;
/**功能描述:系统管理员实体类* @author 梁志杰* @Date:2022/5/5* @Description:cn.xueden* @version:1.0*/
@Data
@Entity
@Table(name = "p_sys_admin")
@org.hibernate.annotations.Table(appliesTo = "p_sys_admin",comment="系统管理员信息表")
public class Admin extends BaseEntity {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Column(name = "id", nullable = false)private Integer id;@Column(name = "login_name")private String loginName;@Column(name = "password", length = 50)private String password;@Column(name = "status", nullable = false)private Integer status;}
在 cn.xueden.base 包目录下新建一个 BaseEntity实体类,代码如下所示:
package cn.xueden.base;import cn.xueden.annotation.EnableXuedenCreateBy;
import cn.xueden.annotation.EnableXuedenUpdateBy;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.sql.Timestamp;/**功能描述:公共Entity* @Author:梁志杰* @Date:2022/4/1* @Description:cn.xueden.base* @version:1.0*/
@Getter
@Setter
@MappedSuperclass
public class BaseEntity implements Serializable {/*** 创建时间*/@Column(name = "create_time",nullable = false)@CreationTimestampprivate Timestamp createTime;/*** 创建者ID*/@Column(name = "create_by")@EnableXuedenCreateByprivate Long createBy;/*** 更新时间*/@Column(name = "update_time")@UpdateTimestampprivate Timestamp updateTime;/*** 更新者ID*/@Column(name = "update_by")@EnableXuedenUpdateByprivate Long updateBy;/*** 备注*/@Column(name = "remarks")private String remarks;public @interface Update {}@Overridepublic String toString() {ToStringBuilder builder = new ToStringBuilder(this);Field[] fields = this.getClass().getDeclaredFields();try {for (Field f : fields) {f.setAccessible(true);builder.append(f.getName(), f.get(this)).append("\n");}} catch (Exception e) {builder.append("toString builder encounter an error");}return builder.toString();}
}
3.3.2 创建持久层接口AdminRepository
在 cn.xueden.repository 包目录下新建一个 AdminRepository接口,代码如下所示:
package cn.xueden.repository;import cn.xueden.domain.Admin;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**系统管理员信息持久层* @author 梁志杰* @Date:2022/5/5* @Description:cn.xueden.repository* @version:1.0*/
public interface AdminRepository extends JpaRepository<Admin, Long>, JpaSpecificationExecutor<Admin> {
}
3.3.3 创建业务接口IAdminService
在 cn.xueden.service包目录下新建一个 IAdminService接口,代码如下所示:
package cn.xueden.service;/**功能描述:系统管理员业务接口* @author:梁志杰* @date:2022/5/5* @description:cn.xueden.service* @version:1.0*/
public interface IAdminService {
}
3.3.4 创建业务接口实现类AdminServiceImpl
在 cn.xueden.service.impl包目录下新建一个 AdminServiceImpl接口实现类,代码如下所示:
package cn.xueden.service.impl;import cn.xueden.repository.AdminRepository;
import cn.xueden.service.IAdminService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;/**功能描述:系统管理员业务接口实现类* @author:梁志杰* @date:2022/5/5* @description:cn.xueden.service.impl* @version:1.0*/
@Service
@Transactional(readOnly = true)
public class AdminServiceImpl implements IAdminService {private final AdminRepository adminRepository;public AdminServiceImpl(AdminRepository adminRepository) {this.adminRepository = adminRepository;}
}
3.3.5 创建登录前端控制器LoginController
在一个名为controller的包新建一个LoginController类,代码如下所示:
package cn.xueden.controller;import cn.xueden.service.IAdminService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/**功能描述:系统后台登录前端控制器* @author:梁志杰* @date:2022/5/5* @description:cn.xueden.controller* @version:1.0*/
@RestController
@RequestMapping("login")
public class LoginController {private final IAdminService adminService;public LoginController(IAdminService adminService) {this.adminService = adminService;}
}
3.3.6 新建一个登陆方法login
在LoginController类下新增一个登陆方法,代码如下所示:
/*** 登录方法* @param admin* @return*/@PostMappingpublic BaseResult login(@RequestBody Admin admin,HttpServletRequest request){Admin dbAdmin = adminService.login(admin);if(dbAdmin==null){return BaseResult.fail("登录失败,账号不存在");}else if(!dbAdmin.getPassword().equals(admin.getPassword())){return BaseResult.fail("登录失败,密码不正确");}else if(dbAdmin.getStatus()==0){return BaseResult.fail("登录失败,账号被封禁");}// 生成tokenString token = HutoolJWTUtil.createToken(dbAdmin);request.getServletContext().setAttribute("token",token);return BaseResult.success("登录成功",token);}
3.3.7 在IAdminService接口下新增一个登陆方法login
代码如下所示:
/*** 登录* @param admin* @return*/Admin login(Admin admin);
3.3.8 在AdminServiceImpl实现类下新建一个login方法
代码如下所示:
/*** 登录* @param admin* @return*/@Overridepublic Admin login(Admin admin) {Admin dbAdmin = adminRepository.findByLoginName(admin.getLoginName());return dbAdmin;}
3.3.9 在AdminRepository接口下新建一个findByLoginName方法
代码如下所示:
/*** 根据登录名查找管理员信息* @param loginName* @return*/Admin findByLoginName(String loginName);
3.3.10 新建一个JWT工具类HutoolJWTUtil
代码如下所示:
package cn.xueden.utils;import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTPayload;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.jwt.JWTValidator;
import cn.xueden.domain.Admin;import java.util.HashMap;
import java.util.Map;/*** @author:梁志杰* @date:2022/5/6* @description:cn.xueden.utils* @version:1.0*/
public class HutoolJWTUtil {/*** 生成token* @param admin* @return*/public static String createToken(Admin admin){DateTime now = DateTime.now();DateTime newTime = now.offsetNew(DateField.MINUTE, 120);Map<String,Object> payload = new HashMap<String,Object>();//签发时间payload.put(JWTPayload.ISSUED_AT, now);//过期时间payload.put(JWTPayload.EXPIRES_AT, newTime);//生效时间payload.put(JWTPayload.NOT_BEFORE, now);//载荷payload.put("loginName", admin.getLoginName());payload.put("aid", admin.getId());String key = "www.xueden.cn";String token = JWTUtil.createToken(payload, key.getBytes());return token;}/*** 校验token* @param token* @return*/public static boolean JwtVerify(String token){String key = "www.xueden.cn";JWT jwt = JWTUtil.parseToken(token);boolean verifyKey = jwt.setKey(key.getBytes()).verify();System.out.println(verifyKey);boolean verifyTime = jwt.validate(0);System.out.println(verifyTime);if(verifyKey&&verifyTime){return true;}return false;}/*** 解析token* @param token* @return*/public static Long parseToken(String token){final JWT jwt = JWTUtil.parseToken(token);return Long.parseLong(jwt.getPayload("aid").toString());}
}
3.3.11 修改login目录下的index.vue文件
如图所示:
代码如下所示:
<template><div class="login-wrap" @keydown.enter="login"><div class="login-con"><el-card class="box-card"><template #header><span class="login--header">登录</span></template><el-formref="loginForm":model="form":rules="rules"class="login-form"><el-form-item prop="loginName"><el-inputv-model="form.loginName"placeholder="请输入账号"class="form--input"><template #prefix><span class="svg-container"><svg-icon icon-class="user" /></span></template></el-input></el-form-item><el-form-item prop="password"><el-inputv-model="form.password"show-password:minlength="3":maxlength="18"placeholder="请输入密码"class="form--input"><template #prefix><span class="svg-container"><svg-icon icon-class="password" /></span></template></el-input></el-form-item><el-form-item><el-button:loading="loading"type="primary"class="login--button"@click="login">登录</el-button></el-form-item></el-form></el-card></div></div>
</template><script lang="ts">
import { defineComponent, ref, unref, reactive, watch } from 'vue'
import { useRouter } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
import { permissionStore } from '@/store/modules/permission'
import { appStore } from '@/store/modules/app'
import wsCache from '@/cache'
import { ElNotification } from 'element-plus'
import { loginApi } from './api'
import { Message } from '_c/Message'interface FormModule {loginName: string,password: string
}
interface RulesModule {loginName: any[],password: any[]
}export default defineComponent({name: 'Login',setup() {const { push, addRoute, currentRoute } = useRouter()const loginForm = ref<HTMLElement | null>(null)const loading = ref<boolean>(false)const redirect = ref<string>('')watch(() => {return currentRoute.value}, (route) => {redirect.value = (route.query && route.query.redirect) as string}, {immediate: true})const form = reactive<FormModule>({loginName: '',password: ''})const rules = reactive<RulesModule>({loginName: [{ required: true, message: '请输入账号' }],password: [{ required: true, message: '请输入密码' }]})async function login(): Promise<void> {const formWrap = unref(loginForm) as anyif (!formWrap) returnloading.value = truetry {formWrap.validate(async(valid: boolean) => {if (valid) {const formData = unref(form)const res = await loginApi({data: formData})if (res.status !== 200) {Message.error(res.message)return false}wsCache.set(appStore.userInfo, form)permissionStore.GenerateRoutes().then(() => {permissionStore.addRouters.forEach(async(route: RouteRecordRaw) => {await addRoute(route.name!, route) // 动态添加可访问路由表})permissionStore.SetIsAddRouters(true)push({ path: redirect.value || '/' })})} else {console.log('error submit!!')return false}})} catch (err) {console.log(err)} finally {loading.value = false}}return {loginForm,loading, redirect, form, rules,login}}
})
</script><style lang="less" scoped>
.login-wrap {width: 100%;height: 100%;background-image: url('~@/assets/img/login-bg.jpg');background-size: cover;background-position: center;position: relative;.box-card {width: 400px;.login--header {font-size: 24px;font-weight: 600;}.svg-container {color: #889aa4;vertical-align: middle;width: 30px;display: inline-block;}.form--input {width: 100%;@{deep}(.el-input__inner) {padding-left: 40px;}}.login--button {width: 100%;}}.login-con {position: absolute;right: 160px;top: 50%;transform: translateY(-60%);}
}
</style>
3.3.12 在login目录下新建一个api.ts文件
如图所示:
代码如下所示:
import { fetch } from '@/axios-config/axios'// 参数接口
interface PropsData {params?: any,data?: any
}
// 提交表单接口函数
export const loginApi = ({ data }: PropsData): any => {return fetch({ url: 'login', method: 'post', data })
}
3.3.13 修改UserInfo目录下的index.vue文件
如图所示:
代码如下所示:
<template><el-dropdown class="avatar-container" trigger="hover"><div id="user-container"><div class="avatar-wrapper"><img src="logo.png" class="user-avatar"><span class="name-item">{{ userInfo.loginName }}</span></div></div><template #dropdown><el-dropdown-menu><el-dropdown-item key="1"><span style="display: block;" @click="toHome">首页</span></el-dropdown-item><el-dropdown-item key="2"><span style="display: block;" @click="loginOut">退出登录</span></el-dropdown-item></el-dropdown-menu></template></el-dropdown>
</template><script lang="ts">
import { defineComponent, reactive, computed } from 'vue'
import { resetRouter } from '@/router'
import wsCache from '@/cache'
import { useRouter } from 'vue-router'
import { tagsViewStore } from '@/store/modules/tagsView'
import { appStore } from '@/store/modules/app'export default defineComponent({name: 'UserInfo',setup() {const { replace, push } = useRouter()async function loginOut(): Promise<void> {wsCache.clear()// wsCache.delete(appStore.userToken)await resetRouter() // 重置静态路由表await tagsViewStore.delAllViews() // 删除所有的tags标签页replace('/login')}const userInfo = wsCache.get(appStore.userInfo)console.info('userInfo:-----------',userInfo)function toHome() {push('/')}return {loginOut,toHome,userInfo}}
})
</script><style lang="less" scoped>
.avatar-container {margin-right: 30px;padding: 0 10px;.avatar-wrapper {display: flex;align-items: center;height: 100%;cursor: pointer;.user-avatar {width: 30px;height: 30px;border-radius: 10px;}.name-item {font-size: 14px;font-weight: 600;display: inline-block;margin-left: 5px;}}
}
</style>
3.3.14 拦截器功能实现
步骤01 新建一个interceptor包,并在此包下自定义拦截器类AdminInterceptor
代码如下所示:
package cn.xueden.interceptor;import cn.xueden.exception.BadRequestException;import cn.xueden.utils.HutoolJWTUtil;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Enumeration;/**拦截器* @author:梁志杰* @date:2022/5/6* @description:cn.xueden.interceptor* @version:1.0*/public class AdminInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {response.setHeader("Access-Control-Allow-Origin", "*");response.setHeader("Access-Control-Allow-Credentials", "true");response.setHeader("Access-Control-Allow-Methods", "*");response.setHeader("Access-Control-Max-Age", "86400");response.setHeader("Access-Control-Allow-Headers", "*");if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {System.out.println("OPTIONS请求,放行");return true;}//获取tokenString token = request.getHeader("Authorization");//判断用户是否登录if (null==token){throw new BadRequestException(HttpStatus.UNAUTHORIZED,"请先登录");}String tokenServletContext = (String)request.getServletContext().getAttribute("token");// 判断用户登录是否过期if (null==tokenServletContext){throw new BadRequestException(HttpStatus.UNAUTHORIZED,"登录已过期,请重新登录");}// 校验tokenboolean tokenStatus = HutoolJWTUtil.JwtVerify(token);if(!tokenStatus){throw new BadRequestException(HttpStatus.UNAUTHORIZED,"登录信息已过期,请重新登录");}return true;}
}
步骤02 修改ConfigurerAdapter配置类,重写addInterceptors方法,代码如下所示:
@Overridepublic void addInterceptors(InterceptorRegistry registry) {//addPathPatterns拦截的路径String[] addPathPatterns = {"/**"};//excludePathPatterns排除的路径String[] excludePathPatterns = {"/login","/wxapi/**","/uploadFile/**"};//创建用户拦截器对象并指定其拦截的路径和排除的路径registry.addInterceptor(new AdminInterceptor()).addPathPatterns(addPathPatterns).excludePathPatterns(excludePathPatterns);}
3.4 微信小程序项目搭建
3.4.1 新建猪脚饭微信小程序项目
步骤01 打开微信开发者工具,选择新建项目,如图所示:
步骤02 依次新建目录images->nav,把相关图片复制到nav目录下,如图所示:
步骤03 在目录images新建一个icon目录,把相关图片复制到此目录,如图所示:
3.4.2 新建一个名为category菜单页
打开app.json文件,在pages属性下添加如下代码即可:
"pages": ["pages/index/index","pages/category/category"],
3.4.3 新建一个名为index的购物车页面
打开app.json文件,在pages属性下添加如下代码即可:、
"pages": ["pages/index/index","pages/category/category","pages/shop-cart/index"],
3.4.4 新增一个名为index的我的页面
打开app.json文件,在pages属性下添加如下代码即可:
"pages": ["pages/index/index","pages/category/category","pages/shop-cart/index","pages/my/index"],
3.4.5 添加tabBar属性
打开app.json文件,添加tabBar属性,同时添加如下代码:
"tabBar": {"color": "#6e6d6b","selectedColor": "#e64340","borderStyle": "white","backgroundColor": "#f6f0e3","list": [{"pagePath": "pages/index/index","iconPath": "images/nav/home-off.png","selectedIconPath": "images/nav/home-on.png","text": "首页"},{"pagePath": "pages/category/category","iconPath": "images/nav/ic_catefory_normal.png","selectedIconPath": "images/nav/ic_catefory_pressed.png","text": "菜单"},{"pagePath": "pages/shop-cart/index","iconPath": "images/nav/cart-off.png","selectedIconPath": "images/nav/cart-on.png","text": "购物车"},{"pagePath": "pages/my/index","iconPath": "images/nav/my-off.png","selectedIconPath": "images/nav/my-on.png","text": "我的"}]},
3.4.6 编写调用后台接口的工具包
新建一个名为wxapi的目录,并在此目录下新建一个名为main.js的文件,代码如下所示:
const API_BASE_URL = 'http://localhost:8081/wxapi'const request = (url,method,data) => {let _url = API_BASE_URL+'/'+urlretrun new Promise((resolve, reject) => {wx.request({url: _url,method: method,data: data,header: {'Content-Type': 'application/x-www-form-urlencoded'},success(res) {resolve(res.data)},fail(error){reject(error)},complete(aaa){// 加载完成}})})
}/*** 小程序的promise没有finally方法,自己扩展下*/
Promise.prototype.finally = function (callback) {var Promise = this.constructor;return this.then(function (value) {Promise.resolve(callback()).then(function () {return value;});},function (reason) {Promise.resolve(callback()).then(function () {throw reason;});});}module.exports = {request
}
3.4.7 在根目录添加一个名为config.js的文件
代码如下所示:
module.exports = {version: "0.0.1",note: '图片url',imgUrl: "http://192.168.0.5:8081/uploadFile"}
3.4.8 整合weui组件库
开发文档地址:https://wechat-miniprogram.github.io/weui/docs/
步骤01 下载weui组件库,地址是:https://github.com/Tencent/weui-wxss
步骤02 新建一个weui目录,并把下载好的组件解压复制到此目录下,如图所示:
步骤03 修改app.wxss文件,代码如下所示:
@import 'weui/weui.wxss';
.page {background-color: #f6f0e3;min-height: 100vh;box-sizing: border-box;
}
.wxParse-img {display: block !important;
}
.space {height:20rpx;background-color: #F2f2f2;
}