开发环境准备
项目目录结构
权限管理
通用分页表格
Swagger文档
undertow容器
对xss攻击的防御
分布式锁
统一的系统日志
统一验证
统一异常处理
文件上传下载 一对多、多对多分页
开发环境准备
mall的商业版,最基本的开发环境与开源版本一致,方便用户迁移,我们认为,购买商业版,并且需要进行代码编写的人员,都是具有一定技术基础的开发人员。
1.开发环境
以下版本是最低要求的!!! 提问问题前请注意开发环境!!
工具 | 版本 |
---|---|
jdk | 1.8+ |
mysql | 5.7+ |
redis | 3.2+ |
2.启动
如果对于redis的安装并不了解的,可以参考 菜鸟教程的redis相关
安装相对简单,网上也有很多教程,这里就不多讲述。安装完按需对redis进行配置,后启动redis服务即可。
后台配置的端口需与redis服务端口保持一致。
- 参数说明
版本: mysql5.7.8+
默认字符集: utf8
默认排序规则: utf8_general_ci
要求设置数据库大小写不敏感
lower_case_table_names = 1
https://blog.csdn.net/fdipzone/article/details/73692929
-
创建数据库名为
yami_shops
的数据库,选中数据库编码为utf-8 -
导入在mall4j工程中找到db文件夹下的
yami_shop.sql
,导入到本地 -
修改
application-dev.yml
更改数据库账号密码
Lombok是一个在Java开发过程中为了简化冗余和样板式代码而出现的插件如getter
setter
。项目中使用了这个插件,您需要安装该插件以便于项目正常启动。
具体安装步骤可以参考:IntelliJ IDEA下的使用 Lombok
对于无法找到文件的用户,推荐使用idea快捷键 ctrl + shift + n 进行文件搜索
- 通过修改
ma.properties
修改微信小程序信息 - 通过修改
mp.properties
修改微信公众号信息 - 通过修改
pay.properties
修改微信支付信息 - 通过修改
shop.properties
修改七牛云、阿里大于等信息 - 修改
api.properties
修改当前接口所在域名,用于支付回调 - 启动redis,端口6379
- 通过
WebApplication
启动项目后台接口,ApiApplication
启动项目前端接口
项目目录结构
目录结构
yami-shops
├── yami-shop-admin -- 后台(vue)接口工程[8085]
├── yami-shop-api -- 前端(小程序)接口工程[8086]
├── yami-shop-bean -- 所有公共的实体类,商城基本流程所需的实体类
├── yami-shop-common -- 前后台需要用到的公共配置,工具类等的集合地
├── yami-shop-mp -- 微信公众号模块
├── yami-shop-quartz -- 定时任务模块
├── yami-shop-security -- oauth2.0 授权认证模块
├── yami-shop-service -- 前后台需要用到的公共的、商城基本流程所需的service,dao的集合地
├── yami-shop-sys -- 后台用户角色权限管理模块
权限管理
权限控制
在商城运营时,我们可能是多个人员共同操作我们的系统,但是每个操作人员所具备的权限应该不同,权限的不同主要表现在两个部分,即导航菜单的查看权限和页面增删改操作按钮的操作权限。我们的把页面导航菜单查看权限和页面操作按钮统一存储在菜单数据库表中,菜单类型页面资源的类型。类型包括目录 、菜单 、按钮。
权限标识用来进行权限控制的唯一标识,主要是进行增删改查的权限控制。
权限标识包括:新增 编辑 删除 查看等,格式结构类似xxx:xxx:xxx 如:admin:user:update。
用户登录之后,跳转至首页,前端发送请求到后台获取该用户下的所有菜单权限与认证权限数据,认证权限为约束用户增删改查操作,在路由导航守卫路由时加载用户导航菜单并存储到本地存储中。导航栏从本地存储读取菜单列表并进行渲染。
用户登录系统之后,跳转到首页,在路由导航守卫路由时加载用户权限标识集合。返回结果是用户权限标识的集合,页面操作按钮提供权限标识,查询该权限标识是否在用户权限标识集合中,如有存在,则将按钮为可见状态,如不存在,则将按钮为不可见状态,根据需求,也可以设置成禁用状态。
在router/index.js
中,从后台加载导航菜单、页面按钮权限数据,并将数据保存到本地存储中,如下所示:
router.beforeEach((to, from, next) => {
// 添加动态(菜单)路由
if (router.options.isAddDynamicMenuRoutes || fnCurrentRouteType(to, globalRoutes) === 'global') {
next()
} else {
http({
url: http.adornUrl('/sys/menu/nav'),
method: 'get',
params: http.adornParams()
}).then(({ data }) => {
sessionStorage.setItem('authorities', JSON.stringify(data.authorities || '[]'))
fnAddDynamicMenuRoutes(data.menuList)
router.options.isAddDynamicMenuRoutes = true
sessionStorage.setItem('menuList', JSON.stringify(data.menuList || '[]'))
next({ ...to, replace: true })
}).catch((e) => {
console.log(`%c${e} 请求菜单列表和权限失败,跳转至登录页!!`, 'color:blue')
router.push({ name: 'login' })
})
}
})
通过fnAddDynamicMenuRoutes()
方法,动态加载菜单到路由中保存到本地存储sessionStorage
中。但是现在只有路由,还需要将导航栏展示出来。在main-sidebar.vue
中,我们将本地存储中菜单数据取出来,然后对导航栏动态渲染出来,并通过menuId与动态(菜单)路由进行匹配跳转至指定路由,这样,当我们点击菜单的时候,就会跳转至特定的路由。
created () {
this.menuList = JSON.parse(sessionStorage.getItem('menuList') || '[]')
this.dynamicMenuRoutes = JSON.parse(sessionStorage.getItem('dynamicMenuRoutes') || '[]')
this.routeHandle(this.$route)
}
<sub-menu v-for="menu in menuList"
:key="menu.menuId"
:menu="menu"
:dynamicMenuRoutes="dynamicMenuRoutes">
</sub-menu>
sub-menu
组件的部分代码
<template>
<el-submenu
v-if="menu.list && menu.list.length >= 1"
:index="menu.menuId + ''"
:popper-class="'site-sidebar--' + sidebarLayoutSkin + '-popper'">
<template slot="title">
<icon-svg :name="menu.icon || ''" class="site-sidebar__menu-icon"></icon-svg>
<span>{
{ menu.name }}</span>
</template>
<sub-menu
v-for="item in menu.list"
:key="item.menuId"
:menu="item"
:dynamicMenuRoutes="dynamicMenuRoutes">
</sub-menu>
</el-submenu>
<el-menu-item v-else :index="menu.menuId + ''" @click="gotoRouteHandle(menu)">
<icon-svg :name="menu.icon || ''" class="site-sidebar__menu-icon"></icon-svg>
<span>{
{ menu.name }}</span>
</el-menu-item>
</template>
在组件中根据外部方法传入的权限标识进行权限判断,如果权限存在,则显示为可见状态,否则不可见。
<el-button type="primary"
icon="el-icon-plus"
size="small"
v-if="isAuth('admin:indexImg:save')"
@click.stop="addOrUpdateHandle()">新增</el-button>
通过isAuth(“权限标识”)
,判断按钮是否有相同的标识,如果有则可见,否则不可见
/**
* 是否有权限
* @param {*} key
*/
export function isAuth (key) {
let authorities = JSON.parse(sessionStorage.getItem('authorities') || '[]')
if (authorities.length) {
for (const i in authorities) {
const element = authorities[i]
if (element.authority === key) {
return true
}
}
}
return false
}
注:后台通过@PreAuthorize("@pms.hasPermission('admin:user:update')")
来定义请求所需要的权限,如果用户没有该权限,后台就会抛出401未授权状态码,前端捕获到该状态码后,会登出当前的账号,让用户重新登陆。
在【系统管理】-【菜单管理】中,我们可以通过类配置的方式,更直观的对菜单列表增删改查进行管理。
菜单类型包括目录 、菜单 、按钮。
目录为导航栏的大的分类,菜单为分类下的每一项,每个菜单需要绑定上级及填写对应跳转的路由,路由路径对应工程的目录如下图:
在新增按钮权限时,注意授权标识要与后台一致,新增完之后需要重启刷新生效。
在【系统管理】-【角色管理】中,管理员可以新增角色,并且赋予该角色可以访问的权限项。
在【系统管理】- 【管理员列表】中,拥有该权限的管理员可以对其进行管理,该管理员可添加或修改管理权限,并可分配列表中的用户角色。
通用分页表格
通用分页表格实现
前端基于VUE的轻量级表格插件 avue
后端分页组件使用Mybatis分页插件 MybatisPlus
分页实现流程,以【系统管理-管理员列表】为例
后台vue文件位置目录 \src\views\modules\sys\user.vue
1、avue
组件的几个通用配置
<avue-crud ref="crud"
:page="page"
:data="dataList"
:option="tableOption"
@search-change="searchChange"
@selection-change="selectionChange"
@on-load="getDataList">
</avue-crud>
avue
定义了很多的事件,其中一个为 @on-load
当该组件加载的时候,将会调用该方法。同时也对很多数据进行了双向绑定如&#