1. 项目概述:基于若依框架的充电宝管理系统开发实战
最近在尚硅谷Java课程中完成了一个充电宝管理系统的开发项目,这个项目基于若依(RuoYi)框架进行二次开发,实现了完整的权限管理、日志记录、代码生成以及微信授权登录等功能模块。作为后端开发者,我在这个项目中深入实践了Spring Boot、Spring Cloud Alibaba等技术栈,特别是对若依框架的权限控制和代码生成机制有了更深入的理解。
这个系统主要面向充电宝运营商,提供设备管理、会员管理、订单处理等核心功能。其中权限管理模块采用RBAC模型,通过注解和前端指令实现细粒度的按钮级权限控制;日志模块记录关键操作留痕;代码生成功能大幅提升了开发效率;微信授权登录则为小程序端用户提供了便捷的接入方式。
下面我将详细分享这个项目中几个关键模块的实现细节和实战经验,这些内容对于正在学习若依框架或开发类似管理系统的同学应该会很有帮助。我会尽量还原实际开发中的思考过程和遇到的问题,而不仅仅是展示最终代码。
2. 权限管理模块深度解析
2.1 若依权限控制实现原理
若依框架采用标准的RBAC(基于角色的访问控制)模型,权限数据存储在以下几个核心表中:
- sys_menu:存储菜单和权限项
- sys_role:角色定义
- sys_user_role:用户角色关联
- sys_role_menu:角色权限关联
权限控制分为三个层次:
- 菜单权限:控制用户能看到哪些菜单
- 按钮权限:控制页面中的操作按钮是否显示
- 接口权限:控制后端接口能否被访问
2.2 按钮权限的具体实现
在充电宝类型管理模块中,我们实现了完整的CRUD操作权限控制。以下是关键实现步骤:
2.2.1 后端权限注解配置
在Controller层使用@RequiresPermissions注解声明每个接口需要的权限标识:
java复制@Tag(name = "柜机类型接口管理")
@RestController
@RequestMapping("/cabinetType")
public class CabinetTypeController extends BaseController {
// 查询列表需要device:cabinetType:list权限
@RequiresPermissions("device:cabinetType:list")
@GetMapping("/list")
public TableDataInfo list(CabinetType cabinetType) {
// 实现代码...
}
// 新增需要device:cabinetType:add权限
@RequiresPermissions("device:cabinetType:add")
@PostMapping
public AjaxResult add(@RequestBody CabinetType cabinetType) {
// 实现代码...
}
}
权限标识的命名遵循模块:功能:操作的格式,这种命名方式清晰且易于管理。
2.2.2 前端按钮权限控制
在Vue模板中使用v-hasPermi指令控制按钮显示:
html复制<el-button
v-hasPermi="['device:cabinetType:add']"
type="primary"
@click="handleAdd">
新增
</el-button>
这个指令会检查当前用户是否拥有指定的权限标识,没有则不会渲染该按钮。
2.2.3 权限数据初始化
权限数据通过SQL脚本初始化到sys_menu表:
sql复制-- 菜单SQL
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('柜机类型', '2000', '1', 'cabinetType', 'device/cabinetType/index', 1, 0, 'C', '0', '0', 'device:cabinetType:list', '#', 'admin', sysdate(), '', null, '柜机类型菜单');
-- 按钮父菜单ID
SELECT @parentId := LAST_INSERT_ID();
-- 按钮SQL
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('柜机类型查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', 'device:cabinetType:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('柜机类型新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', 'device:cabinetType:add', '#', 'admin', sysdate(), '', null, '');
实际开发中发现,若依的权限标识是大小写敏感的,曾经因为大小写不一致导致权限校验失败,这点需要特别注意。
2.3 权限管理实战经验
-
权限设计原则:
- 遵循最小权限原则,只授予必要的权限
- 权限粒度要适中,太粗无法满足需求,太细增加管理成本
- 权限标识命名要有规律,便于维护
-
常见问题排查:
- 权限不生效时,首先检查数据库中的权限标识是否与代码中完全一致
- 确保用户角色关联和角色权限关联配置正确
- 前端检查vuex中的权限数据是否加载正确
-
性能优化:
- 用户登录时一次性加载所有权限,避免频繁查询
- 使用缓存减少数据库访问
- 前端按钮权限判断使用计算属性而非方法,减少重复计算
3. 系统日志模块实现
3.1 操作日志记录实现
若依提供了@Log注解来记录操作日志,我们将其应用到关键业务操作上:
java复制@Operation(summary = "新增柜机类型")
@RequiresPermissions("device:cabinetType:add")
@Log(title = "柜机类型", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody CabinetType cabinetType) {
boolean is_Success = cabinetTypeService.save(cabinetType);
return toAjax(is_Success);
}
日志记录的核心参数:
- title:操作模块
- businessType:操作类型(新增/修改/删除等)
- operatorType:操作人类别
- isSaveRequestData:是否保存请求参数
3.2 日志表结构设计
若依的日志主要存储在sys_oper_log表中,关键字段包括:
- oper_id:日志ID
- title:模块标题
- business_type:业务类型
- method:方法名称
- oper_name:操作人员
- oper_url:请求URL
- oper_ip:操作IP
- oper_location:操作地点
- oper_param:请求参数
- status:操作状态
- error_msg:错误消息
3.3 日志模块使用心得
-
日志记录策略:
- 关键业务操作必须记录日志
- 批量操作可以记录汇总日志而非单条记录
- 敏感数据需要脱敏处理
-
性能考虑:
- 日志记录应该异步处理,避免影响主业务流程
- 可以考虑使用消息队列进行日志的异步存储
- 定期归档历史日志,保持主表的高效查询
-
实用技巧:
- 在开发环境可以开启更详细的日志记录
- 生产环境应该合理设置日志级别
- 结合ELK等工具实现日志的集中管理和分析
4. 代码生成模块实战
4.1 代码生成流程详解
若依的代码生成功能可以极大提高开发效率,以下是会员管理模块的代码生成步骤:
- 配置数据源:在代码生成界面连接目标数据库(share.user)
- 选择表:勾选需要生成代码的表(user_info)
- 生成代码:设置基本信息后生成代码
- 预览确认:检查生成的代码是否符合预期
- 导出代码:将生成的代码导出到本地
生成的代码包括:
- 后端:Controller、Service、Mapper、Entity
- 前端:Vue组件、API定义
- SQL:菜单初始化脚本
4.2 代码生成后的调整
虽然代码生成节省了大量时间,但通常还需要进行一些定制化调整:
-
实体类增强:
- 添加Swagger注解
- 补充字段校验注解
- 添加自定义业务逻辑方法
-
Service层优化:
- 添加事务控制
- 实现更复杂的业务逻辑
- 添加缓存支持
-
Controller层定制:
- 添加权限控制注解
- 补充日志记录
- 实现特殊的API需求
4.3 代码生成最佳实践
-
数据库设计规范:
- 表名和字段名使用下划线命名法
- 添加完整的字段注释
- 合理设置主键和索引
-
生成配置技巧:
- 模块名称要准确反映业务功能
- 作者信息保持统一
- 包路径符合项目规范
-
常见问题解决:
- 生成代码后需要手动添加模块依赖
- 前端组件可能需要调整样式
- 菜单SQL需要根据实际业务调整
在实际项目中,我们发现代码生成后需要特别注意权限配置的调整,因为生成的权限标识可能不符合项目的统一规范,需要手动修改。
5. 微信授权登录实现
5.1 微信登录整体流程
充电宝小程序端的微信授权登录流程如下:
- 小程序调用wx.login获取临时code
- 将code发送到后端/auth/h5/login接口
- 后端通过code向微信服务器换取openid
- 根据openid查询或创建用户
- 生成系统token返回给小程序
- 小程序存储token用于后续请求
5.2 关键代码实现
5.2.1 微信配置类
java复制@Data
@Component
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxMaProperties {
private String appId;
private String secret;
}
@Component
public class WxMaConfig {
@Autowired
private WxMaProperties wxMaProperties;
@Bean
public WxMaService wxMaService() {
WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
config.setAppid(wxMaProperties.getAppId());
config.setSecret(wxMaProperties.getSecret());
WxMaService service = new WxMaServiceImpl();
service.setWxMaConfig(config);
return service;
}
}
5.2.2 登录服务实现
java复制@Override
public UserInfo wxLogin(String code) {
try {
// 获取微信session信息
WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(code);
String openId = sessionInfo.getOpenid();
// 查询现有用户
LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(UserInfo::getWxOpenId, openId);
UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);
// 新用户自动注册
if (userInfo == null) {
userInfo = new UserInfo();
userInfo.setNickname("用户_" + System.currentTimeMillis());
userInfo.setAvatarUrl(DEFAULT_AVATAR);
userInfo.setWxOpenId(openId);
userInfoMapper.insert(userInfo);
}
return userInfo;
} catch (Exception e) {
throw new RuntimeException("微信登录失败", e);
}
}
5.3 微信登录的注意事项
-
安全考虑:
- 小程序端的appsecret必须妥善保管
- 建议定期更换appsecret
- 接口需要做好防刷措施
-
用户体验优化:
- 首次登录可以引导用户完善信息
- 考虑实现静默登录机制
- 处理好token过期的情况
-
异常处理:
- 微信接口调用失败要有重试机制
- 网络问题要有友好的错误提示
- 记录详细的错误日志便于排查
6. 项目架构与模块设计
6.1 微服务架构设计
项目采用Spring Cloud Alibaba微服务架构,主要模块包括:
- share-gateway:API网关,负责路由、鉴权
- share-auth:认证中心,处理登录、token签发
- share-user:用户服务,管理会员信息
- share-device:设备服务,管理充电宝设备
- share-order:订单服务,处理租赁订单
6.2 模块间调用关系
-
微信登录流程:
- 小程序 → 网关 → auth服务 → user服务
- auth服务通过Feign调用user服务
-
服务注册发现:
- 使用Nacos作为注册中心
- 各服务启动时注册到Nacos
- 服务间通过服务名进行调用
-
配置管理:
- 使用Nacos Config管理配置
- 支持多环境配置
- 配置变更自动刷新
6.3 配置示例
6.3.1 bootstrap.yml配置
yaml复制spring:
application:
name: share-user
profiles:
active: dev
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848
file-extension: yml
namespace: dev
6.3.2 Feign客户端定义
java复制@FeignClient(contextId = "remoteUserInfoService",
value = ServiceNameConstants.SHARE_USER,
fallbackFactory = RemoteUserFallbackFactory.class)
public interface RemoteUserService {
@GetMapping("/userInfo/wxLogin/{code}")
R<UserInfo> wxLogin(@PathVariable("code") String code);
}
7. 开发中的经验与教训
7.1 项目初始化注意事项
-
依赖管理:
- 统一管理依赖版本
- 避免循环依赖
- 注意Spring Boot和Spring Cloud版本的兼容性
-
代码规范:
- 制定并遵守统一的代码风格
- 使用Checkstyle或Spotless进行代码格式化
- 重要代码必须添加注释
-
环境配置:
- 区分开发、测试、生产环境
- 敏感配置不要提交到代码库
- 使用配置中心管理环境差异
7.2 常见问题解决方案
-
Feign调用失败:
- 检查服务是否注册到Nacos
- 确认Feign客户端路径是否正确
- 查看日志中的详细错误信息
-
权限不生效:
- 检查权限标识是否一致
- 确认用户角色关联正确
- 查看权限数据是否加载
-
微信登录问题:
- 检查appid和secret是否正确
- 确认域名已在小程序后台配置
- 查看微信接口返回的错误信息
7.3 性能优化建议
-
数据库优化:
- 合理设计索引
- 避免全表扫描
- 使用连接池
-
缓存策略:
- 热点数据使用Redis缓存
- 设置合理的过期时间
- 注意缓存一致性
-
接口优化:
- 减少不必要的字段返回
- 支持分页查询
- 批量操作接口
8. 项目扩展与未来规划
8.1 功能扩展方向
-
支付集成:
- 对接微信支付
- 实现押金缴纳和退还
- 支持优惠券和促销活动
-
设备对接:
- 实现与充电宝硬件的通信
- 设备状态实时监控
- 故障自动报警
-
数据分析:
- 用户行为分析
- 设备使用率统计
- 营收数据分析
8.2 技术优化计划
-
架构优化:
- 引入消息队列解耦
- 实现分布式事务
- 服务网格化
-
性能提升:
- 数据库分库分表
- 引入CDN加速
- 前端性能优化
-
安全加固:
- 完善权限体系
- 加强数据加密
- 实现操作审计
通过这个项目的开发,我深刻体会到了若依框架的强大之处,特别是在快速构建管理系统方面。同时,微信生态的集成和微服务架构的实践也让我收获颇丰。希望这些经验分享对其他开发者有所帮助。