Day1 3.22前言
共享充电宝
是基于若依
微服务版本框架开发的一个共享系统,项目包含平台管理端与微信小程序端,是一个前后端分离的项目。共享充电宝分为后台系统和前台微信小程序。
这个前后端分离项目的环境搭建很是让我一顿折磨,一开始不了解docker怎么用,就欲先下载好多软件,后面发现镜像这么快。希望能少点bug多点爱@~@
技术架构图:
业务流程图:
若依框架
微服务版本文档:https://doc.ruoyi.vip/ruoyi-cloud/
RuoYi-Cloud 是一个 Java EE 分布式微服务架构平台,基于经典技术组合(Spring Boot、Spring Cloud & Alibaba、Vue、Element),内置模块如:部门管理、角色用户、菜单及按钮授权、数据权限、系统参数、日志管理、代码生成等。在线定时任务配置;支持集群,支持多数据源。
环境搭建
安装docker
Win11 安装 Docker Desktop 和 WSL2 并进行安装位置迁移_windows 11 wsl 修改安装位置-CSDN博客
安装mysql(原先安装了)
建议安装5.5以上版本的,我因为5.5版本导致很多表都没有导入进去
安装redis(原先安装了)
安装nacos
安装教程:Nacos安装教程Linux+Windows(史上超级无敌细)_wx6458b14f1be01的技术博客_51CTO博客
win11启动nacos
PS D:\InstallApps\nacos\bin> .\startup.cmd -m standalone
安装minio
在Windows上MinIO的安装与使用(保姆教程)_minio windows安装-CSDN博客
安装rabbitmq
安装MongoDB
第一步 拉取镜像
docker pull mongo:7.0.0
第二步 创建和启动容器
需要在宿主机建立文件夹
rm -rf /opt/mongo
mkdir -p /opt/mongo/data/db
docker run -d --restart=always -p 27017:27017 --name mongo -v /opt/mongo/data/db:/data/db mongo:7.0.0
第三步 进入容器
docker exec -it mongo mongosh
docker exec -it mongo-yapi mongo -- 现有的容器
以上是针对linux的,我用的时window,就说每次重启电脑mongo里面的数据库就不见了,建议win系统去百度一下在d盘里面装容器实现数据持久化
环境测试
使用mysql/redis客户端工具远程连接mysql/redis测试
nacos控制台访问测试: http://虚拟机IP:8848/nacos
账号密码:nacos/nacos
rabbitmq控制台访问测试:http://虚拟机IP:15672
账号密码:guest/guest
minio控制台访问测试:http://虚拟机IP:9001
账号密码:admin/admin
此时发现项目要用jdk17,我还在用1.8,连夜下载了一个17,记得把idea也用新版本,原来的idea2020用不了jdk17。
可以参考这篇文章
jdk17安装全方位手把手安装教程 / 已有jdk8了,安装JDK17后如何配置环境变量 / 多个不同版本的JDK,如何配置环境变量?_jdk17安装教程详细-CSDN博客
模块介绍
com.share
├── share-gateway // 网关模块 [8080]
├── share-auth // 认证中心 [9200]
├── share-api // 接口模块
│ └── share-api-system // 系统接口
├── share-common // 通用模块
│ └── share-common-core // 核心模块
│ └── share-common-datascope // 权限范围
│ └── share-common-datasource // 多数据源
│ └── share-common-log // 日志记录
│ └── share-common-redis // 缓存服务
│ └── share-common-security // 安全模块
├── share-modules // 业务模块
│ └── share-system // 系统模块 [9201]
│ └── share-gen // 代码生成 [9202]
│ └── share-job // 定时任务 [9203]
│ └── share-file // 文件服务 [9300]
├── share-ui // 前端框架 [80]
├── share-visual // 图形化管理模块
│ └── share-monitor // 监控中心 [9100]
├──pom.xml // 公共依赖
启动项目
导入nacos配置文件
启动nacos,访问nacos控制台:http://虚拟机IP:8848/nacos/
在nacos控制台:配置管理 -> 配置列表 导入配置文件
配置文件资源:资料/nacos配置/DEFAULT_GROUP.zip,改nacos里面的ip和redis的名字和密码
前端
安装nodejs和vscode
前后端联调时,有的时候就会抽筋打不开,8080要一致,一直占用就杀死进程重新来
Day2 3.23设备管理
1.1、新建share-device模块
在share-modules模块下新建子模块share-device
idea撤回撤多了可以用ctrl+shift+z
Day3 3.24设备管理
柜机类型
柜机类型分页查询列表
dmmain
1、柜机类型属性可以使用get/set方法,也可以使用@Data标签
2、默认带上swg标签,方便后续生成api文档
package com.share.device.domain;
import com.share.common.core.web.domain.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "柜机类型")
public class CabinetType extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 名称 */
@Schema(description = "名称")
private String name;
/** 总插槽数量 */
@Schema(description = "总插槽数量")
private Integer totalSlots;
/** 描述 */
@Schema(description = "描述")
private String description;
/** 状态(0正常 1停用) */
@Schema(description = "状态")
private String status;
}
controller
分页参数封装到了BaseController类
@Tag(name = "柜机类型接口管理")
@RestController
@RequestMapping("/cabinetType")
public class CabinetTypeController extends BaseController
{
@Autowired
private ICabinetTypeService cabinetTypeService;
/**
* 查询柜机类型列表
*/
@Operation(summary = "查询柜机类型列表")
@GetMapping("/list")
public TableDataInfo list(CabinetType cabinetType)
{
startPage();
List<CabinetType> list = cabinetTypeService.selectCabinetTypeList(cabinetType);
return getDataTable(list);
}
}
serviceImpl
@Service
public class CabinetTypeServiceImpl extends ServiceImpl<CabinetTypeMapper, CabinetType> implements ICabinetTypeService
{
@Autowired
private CabinetTypeMapper cabinetTypeMapper;
/**
* 查询柜机类型列表
*
* @param cabinetType 柜机类型
* @return 柜机类型
*/
@Override
public List<CabinetType> selectCabinetTypeList(CabinetType cabinetType)
{
return cabinetTypeMapper.selectCabinetTypeList(cabinetType);
}
mapper
public interface CabinetTypeMapper extends BaseMapper<CabinetType>
{
/**
* 查询柜机类型列表
*
* @param cabinetType 柜机类型
* @return 柜机类型集合
*/
public List<CabinetType> selectCabinetTypeList(CabinetType cabinetType);
}
xml
type
属性:指定了该结果映射所对应的 Java 实体类的全限定名,即com.share.device.domain.CabinetType
。
<include refid="selectCabinetTypeVo"/>
:引用之前定义的 SQL 片段,将其插入到当前查询语句中。parameterType
属性:指定了该查询语句的输入参数类型,这里是com.share.device.domain.CabinetType
类的对象。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.share.device.mapper.CabinetTypeMapper">
<resultMap type="com.share.device.domain.CabinetType" id="CabinetTypeResult" autoMapping="true">
</resultMap>
<sql id="selectCabinetTypeVo">
select id, name, total_slots, description, status, del_flag, create_by, create_time, update_by, update_time, remark
from cabinet_type
</sql>
<select id="selectCabinetTypeList" parameterType="com.share.device.domain.CabinetType" resultMap="CabinetTypeResult">
<include refid="selectCabinetTypeVo"/>
<where>
<if test="name != null and name != ''"> and name like concat('%', #{name}, '%')</if>
<if test="totalSlots != null "> and total_slots = #{totalSlots}</if>
<if test="description != null and description != ''"> and description = #{description}</if>
<if test="status != null and status != ''"> and status = #{status}</if>
and del_flag = 0
</where>
</select>
</mapper>
获取详细信息
@TableId(type = IdType.AUTO) 是 MyBatis-Plus 框架里的一个注解,其用途是对实体类里的主键字段加以标识,同时指定主键的生成策略。
根据id获取柜机类型详细信息
@Operation(summary = "根据id获取柜机类型详细信息") @GetMapping(value = "/{id}") public AjaxResult getInfo(@PathVariable("id") Long id) { return success(cabinetTypeService.getById(id)); }
新增柜机类型
@Operation(summary = "新增柜机类型") @PostMapping public AjaxResult add(@RequestBody CabinetType cabinetType) { return toAjax(cabinetTypeService.save(cabinetType)); }
修改柜机类型
@Operation(summary = "修改柜机类型") @PutMapping public AjaxResult edit(@RequestBody CabinetType cabinetType) { return toAjax(cabinetTypeService.updateById(cabinetType)); }
删除柜机类型 ,删除接机是逻辑删除,看del-flag为2
@Operation(summary = "删除柜机类型") @DeleteMapping("/{ids}") public AjaxResult remove(@PathVariable Long[] ids) { return toAjax(cabinetTypeService.removeBatchByIds(Arrays.asList(ids))); }
查询全部柜机类型列表
@Operation(summary = "查询全部柜机类型列表") @GetMapping("/getCabinetTypeList") public AjaxResult getCabinetTypeList() { return success(cabinetTypeService.list()); }
校验验证
spring-boot-starter-validation是Spring Boot提供的一个starter,它为应用程序提供了Bean Validation API的支持。Bean Validation是Java EE的一部分,用于验证JavaBean的属性是否符合规范。使用Spring Boot和spring-boot-starter-validation,您可以轻松地使用注解来验证请求参数、对象等是否符合要求。
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) 验证注解的元素值长度在min和max区间内
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 验证注解的元素值在最小值和最大值之间
@Range(min=10000,max=50000,message="range.bean.wage")
@Valid 写在方法参数前,递归的对该对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
file-->settings 搜索auto import(自动导包) 勾选add
柜机管理
controller,其他增删改查都有方法
/**
* 查询充电宝柜机列表
*/
@Operation(summary = "查询充电宝柜机列表")
@GetMapping("/list")
public TableDataInfo list(Cabinet cabinet)
{
//设置分页参数
startPage();
List<Cabinet> list = cabinetService.selectCabinetList(cabinet);
return getDataTable(list);
}
/**
* 搜索未使用柜机
* @param keyword
* @return
*/
@Operation(summary = "搜索未使用柜机")
@GetMapping(value = "/searchNoUseList/{keyword}")
public AjaxResult searchNoUseList(@PathVariable String keyword)
{
return success(cabinetService.searchNoUseList(keyword));
}
serviceImpl
@Autowired
private CabinetMapper cabinetMapper;
/**
* 查询充电宝柜机列表
*/
@Override
public List<Cabinet> selectCabinetList(Cabinet cabinet)
{
return cabinetMapper.selectCabinetList(cabinet);
}
/**
* 搜索未使用柜机
* @param keyword
* @return
*/
@Override
public List<Cabinet> searchNoUseList(String keyword) {
LambdaQueryWrapper<Cabinet> wrapper = new LambdaQueryWrapper<>();
wrapper.like(Cabinet::getCabinetNo,keyword);
wrapper.eq(Cabinet::getStatus,0);
List<Cabinet> list = cabinetMapper.selectList(wrapper);
return list;
}
xml 查询充电宝柜机列表
<mapper namespace="com.share.device.mapper.CabinetMapper">
<resultMap type="com.share.device.domain.Cabinet" id="CabinetResult" autoMapping="true">
</resultMap>
<sql id="selectCabinetVo">
select c.id, c.cabinet_no, c.name, c.cabinet_type_id, c.total_slots, c.free_slots, c.used_slots, c.available_num, c.description, c.location_id, c.status, c.del_flag, c.create_by, c.create_time, c.update_by, c.update_time, c.remark,
ct.name as cabinet_type_name
from cabinet c
left join cabinet_type ct on c.cabinet_type_id = ct.id
</sql>
<select id="selectCabinetList" parameterType="com.share.device.domain.Cabinet" resultMap="CabinetResult">
<include refid="selectCabinetVo"/>
<where>
<if test="cabinetNo != null and cabinetNo != ''"> and c.cabinet_no = #{cabinetNo}</if>
<if test="name != null and name != ''"> and c.name like concat('%', #{name}, '%')</if>
<if test="cabinetTypeId != null "> and c.cabinet_type_id = #{cabinetTypeId}</if>
<if test="status != null and status != ''"> and c.status = #{status}</if>
and c.del_flag = 0
and ct.del_flag = 0
</where>
</select>
</mapper>
充电宝管理
domian:PowerBank
PowerBankController
@Tag(name = "充电宝接口管理")
@RestController
@RequestMapping("/powerBank")
public class PowerBankController extends BaseController
{
@Autowired
private IPowerBankService powerBankService;
/**
* 查询充电宝列表
* @param powerBank
* @return
*/
@Operation(summary = "查询充电宝列表")
@GetMapping("/list")
public TableDataInfo list(PowerBank powerBank)
{
startPage();
List<PowerBank> list = powerBankService.selectPowerBankList(powerBank);
return getDataTable(list);
}
//根据id查询详细数据
@Operation(summary = "获取充电宝详细信息")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(powerBankService.getById(id));
}
/**
* 新增充电宝
* @param powerBank
* @return
*/
@Operation(summary = "新增充电宝")
@PostMapping
public AjaxResult add(@RequestBody PowerBank powerBank)
{
powerBank.setCreateBy(SecurityUtils.getUsername());
powerBank.setCreateTime(new Date());
powerBank.setUpdateTime(new Date());
int rows = powerBankService.savePowerBank(powerBank);
return toAjax(rows);
}
/**
* 修改充电宝
* @param powerBank
* @return
*/
@Operation(summary = "修改充电宝")
@PutMapping
public AjaxResult edit(@RequestBody PowerBank powerBank)
{
powerBank.setUpdateBy(SecurityUtils.getUsername());
powerBank.setUpdateTime(new Date());
int rows= powerBankService.updatePowerBank(powerBank);
return toAjax(rows);
}
@Operation(summary = "删除充电宝")
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(powerBankService.removeBatchByIds(Arrays.asList(ids)));
}
}
mapper
/**
* 查询充电宝列表
* @param powerBank
* @return
*/
@Override
public List<PowerBank> selectPowerBankList(PowerBank powerBank)
{
return powerBankMapper.selectPowerBankList(powerBank);
}
/**
* 新增充电宝
* @param powerBank
* @return
*/
@Override
public int savePowerBank(PowerBank powerBank) {
//判断powerBankNo是否已经存在,如果不存在才添加
String powerBankNo = powerBank.getPowerBankNo();
//封装条件
LambdaQueryWrapper<PowerBank> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(PowerBank::getPowerBankNo,powerBankNo);
Long count = powerBankMapper.selectCount(wrapper);
if (count > 0) {
throw new ServiceException("该充电宝编号已存在");
}
int rows = powerBankMapper.insert(powerBank);
return rows;
}
@Override
public int updatePowerBank(PowerBank powerBank) {
//判断充电宝状态值为0才修改
Long id = powerBank.getId();
PowerBank oldPowerBank = powerBankMapper.selectById(id);
if (oldPowerBank != null && "0".equals(oldPowerBank.getStatus())) {
int rows = powerBankMapper.updateById(powerBank);
return rows;
}
return 0;
}
xml:
<mapper namespace="com.share.device.mapper.PowerBankMapper">
<resultMap type="com.share.device.domain.PowerBank" id="PowerBankResult" autoMapping="true">
</resultMap>
<sql id="selectPowerBankVo">
select id, power_bank_no, electricity, description, status, del_flag, create_by, create_time, update_by, update_time, remark from power_bank
</sql>
<select id="selectPowerBankList" parameterType="com.share.device.domain.PowerBank" resultMap="PowerBankResult">
<include refid="selectPowerBankVo"/>
<where>
<if test="powerBankNo != null and powerBankNo != ''"> and power_bank_no = #{powerBankNo}</if>
<if test="electricity != null "> and electricity = #{electricity}</if>
<if test="description != null and description != ''"> and description = #{description}</if>
<if test="status != null and status != ''"> and status = #{status}</if>
and del_flag = 0
</where>
</select>
</mapper>
Day3.25地区管理
根据上级code获取下级数据列表
新增站点时,会根据地区三级联动选择地址,根据上级code动态获取下级数据列表
region表关系,例图
domain
region
@Data
@Schema(description = "地区信息")
public class Region extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 地区编码 */
@Schema(description = "地区编码")
private String code;
/** 上级地区code */
@Schema(description = "上级地区code")
private String parentCode;
/** 地区名称 */
@Schema(description = "地区名称")
private String name;
/** 地区级别 */
@Schema(description = "地区级别")
private Long level;
/** 是否有子节点 */
@TableField(exist = false)
private Boolean hasChildren;
}
controller
@Tag(name = "地区信息接口管理")
@RestController
@RequestMapping("/region")
public class RegionController extends BaseController {
@Autowired
private IRegionService regionService;
@Operation(summary = "根据上级code获取下级数据列表")
@GetMapping(value = "/treeSelect/{parentCode}")
public AjaxResult treeSelect(@PathVariable String parentCode) {
return success(regionService.treeSelect(parentCode));
}
}
serviceImpl
@Service
public class RegionServiceImpl extends ServiceImpl<RegionMapper, Region> implements IRegionService
{
//根据上级code获取下级数据数据列表
@Override
public List<Region> treeSelect(String parentCode) {
//封装条件
LambdaQueryWrapper<Region> wrapper=new LambdaQueryWrapper();
wrapper.eq(Region::getParentCode,parentCode);
List<Region> regionList = baseMapper.selectList(wrapper);
//判断下一层数据是否存在,如果有hasChildren=true,否则false
//把查询出来的列表遍历,得到每个region
if(!CollectionUtils.isEmpty(regionList)){
regionList.forEach(region -> {
LambdaQueryWrapper<Region> wrapper1=new LambdaQueryWrapper();
//查询每个regiou对象是否有下一层数据parent_code=?
wrapper1.eq(Region::getParentCode,region.getCode());
Long count = baseMapper.selectCount(wrapper1);
if (count > 0 ) {
region.setHasChildren(true);
}else{
region.setHasChildren(false);
}
});
}
return regionList;
}
}
站点管理
站点为共享充电宝的投放地点,也可以叫做门店
分页查询,新增,修改
domain
Station
controller
/**
* 查询站点列表
*/
@Operation(summary = "查询站点列表")
@GetMapping("/list")
public TableDataInfo list(Station station)
{
//设置分页参数
startPage();
//调用查询
List<Station> list = stationService.selectStationList(station);
return getDataTable(list);
}
@Operation(summary = "新增站点")
@PostMapping
public AjaxResult add(@RequestBody Station station)
{
return toAjax(stationService.saveStation(station));
}
@Operation(summary = "修改站点")
@PutMapping
public AjaxResult edit(@RequestBody Station station)
{
return toAjax(stationService.updateStation(station));
}
serviceImpl
//分页查询
@Override
public List<Station> selectStationList(Station station) {
//调用mapper方法
List<Station> list = stationMapper.selectStationList(station);
//获取每个station里面对应的编号,封装到没个对象里面
//当前station里面只有柜机id,根据柜机id查找编号
for(Station st : list){
//获取每个站点对象里面的id
Long cabinetId = st.getCabinetId();
//根据柜机id获取对应的柜机编号
Cabinet cabinet = cabinetService.getById(cabinetId);
String cabinetNo = cabinet.getCabinetNo();
//封装到Station里面
st.setCabinetNo(cabinetNo);
}
return list;
}
//添加
@Override
public int saveStation(Station station) {
String provinceName = regionService.getNameByCode(station.getProvinceCode());
String cityName = regionService.getNameByCode(station.getCityCode());
String districtName = regionService.getNameByCode(station.getDistrictCode());
station.setFullAddress(provinceName + cityName + districtName + station.getAddress());
int rows = stationMapper.insert(station);
return rows;
}
//修改
@Override
public int updateStation(Station station) {
String provinceName = regionService.getNameByCode(station.getProvinceCode());
String cityName = regionService.getNameByCode(station.getCityCode());
String districtName = regionService.getNameByCode(station.getDistrictCode());
station.setFullAddress(provinceName + cityName + districtName + station.getAddress());
int rows = stationMapper.updateById(station);
return rows;
}
public interface IRegionService extends IService<Region> {
//根据编号返回对应的名称
String getNameByCode(String code);
}
//根据编号返回对应的名称
@Override
public String getNameByCode(String code) {
if (StringUtils.isEmpty(code)) {
return "";
}
Region region = baseMapper.selectOne(new LambdaQueryWrapper<Region>().eq(Region::getCode,code).select(Region::getName));
if(null != region) {
return region.getName();
}
return "";
}
mapper
<mapper namespace="com.share.device.mapper.StationMapper">
<resultMap type="com.share.device.domain.Station" id="StationResult" autoMapping="true">
</resultMap>
<sql id="selectStationVo">
select id, name, image_url, business_hours, longitude, latitude, province_code, city_code, district_code,address, full_address, head_name, head_phone, cabinet_id, fee_rule_id, status, create_time, create_by, update_time, update_by, del_flag, remark from station
</sql>
<select id="selectStationList" parameterType="com.share.device.domain.Station" resultMap="StationResult">
<include refid="selectStationVo"/>
<where>
<if test="name != null and name != ''"> and name like concat('%', #{name}, '%')</if>
<if test="provinceCode != null and provinceCode != ''"> and provinceCode = #{province_code}</if>
<if test="cityCode != null and cityCode != ''"> and cityCode = #{city_code}</if>
<if test="districtCode != null and districtCode != ''"> and districtCode = #{district_code}</if>
<if test="address != null and address != ''"> and address = #{address}</if>
<if test="headName != null and headName != ''"> and head_name like concat('%', #{headName}, '%')</if>
<if test="headPhone != null and headPhone != ''"> and head_phone = #{headPhone}</if>
and del_flag = 0
</where>
</select>
</mapper>
优化新增,修改
后续微信小程序会搜索附近的站点(门店),系统会根据当前用户经纬度查询半径5公里范围内的站点,MongoDB对经纬度范围查询效率很高,因此我们要把关键信息同步到MongoDB里面,方便后续查询
什么是MongoDB
MongoDB 是在2007年由DoubleClick公司的几位核心成员开发出的一款分布式文档数据库,由C++语言编写。
目的是为了解决数据大量增长的时候系统的可扩展性和敏捷性。MongoDB要比传统的关系型数据库简单很多。
在MongoDB中数据主要的组织结构就是数据库、集合和文档
,文档存储在集合当中,集合存储在数据库中。
MongoDB中每一条数据记录就是一个文档,数据结构由键值(key=>value)对组成
。
文档类似于 JSON 对象,它的数据结构被叫做BSON
(Binary JSON)。
//同步站点位置信息到MongoDB StationLocation stationLocation = stationLocationRepository.getByStationId(station.getId()); stationLocation.setLocation(new GeoJsonPoint(station.getLongitude().doubleValue(), station.getLatitude().doubleValue())); stationLocationRepository.save(stationLocation);
监控与规则管理
1、获取附近站点信息
在地图上显示中心点附近的站点信息,根据地图中心点,获取周围几公里的站点信息,MongoDB对应经纬度的查询具体很好的支持,因此我们可以把经纬度信息及关键信息保存到MongoDB中。
保存站点信息到MongoDB
spring: data: mongodb: host: 127.0.0.1 port: 27017 database: share #指定操作的数据库
pom文件中引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
写不下去。。。
Day4 腾讯地图接口3.26
测试的时候一直抛异常,用ai具体写了异常点,说key的每日调度用完了,原来发现腾讯地图要进行配额调度!!!!!!累了。。
前端页面
在vscode项目代码终端输入yarn dev启动运行
传递查询参数:在发起 HTTP 请求时,params
是一个对象,其中的键值对会被编码成查询字符串,然后附加到请求的 URL 后面。query
就是这个包含查询参数的对象
如果是json格式,那么前面就是data
Path=/device/**
表示当请求路径以 /device/
开头时,这个请求就会匹配该路由规则。**
属于通配符,表明 /device/
后面可以是任意路径
StripPrefix=1
代表在将请求转发到目标服务之前,会移除请求路径的第一个路径段
比如,若请求路径是 /device/cabinetType/list
,经过这个过滤器处理后,转发到目标服务的路径就会变成 /cabinetType/list
Day5 3.27
柜机类型
- 第一步创建动态菜单
2. vscode创建vue文件
3. 创建js文件
4. 在js文件中创建接口
import request from '@/utils/request'
// 柜机类型分页列表
export function listCabinetType(query) {
return request({
url: '/device/cabinetType/list',
method: 'get',
params: query
})
}
// 柜机类型添加
export function addCabinetType(data) {
return request({
url: '/device/cabinetType',
method: 'post',
data: data
})
}
// 查询柜机类型详细
export function getCabinetType(id) {
return request({
url: '/device/cabinetType/' + id,
method: 'get'
})
}
// 修改柜机类型
export function updateCabinetType(data) {
return request({
url: '/device/cabinetType',
method: 'put',
data: data
})
}
// 删除柜机类型
export function delCabinetType(id) {
return request({
url: '/device/cabinetType/' + id,
method: 'delete'
})
}
5.在vue页面中实现调用
功能按钮栏的修改还没有完成
在vue中添加 :disabled="single"
@click="handleUpdate(selectedRow)"
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate(selectedRow)"
>修改</el-button>
</el-col>
脚本中定义
const selectedRow = ref(null);
加上 selectedRow.value = selection.length === 1 ? selection[0] : null;
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
selectedRow.value = selection.length === 1 ? selection[0] : null;
}
充电宝管理
看不下去了,玩一把五子棋hhh
***cv工程师***
站点管理station页面测试时发现新增站点会没有自增cabinet_id这个字段,我尝试用数据库自增方法,但是效果不理想,强制手动添加了。
Day3.29若依-权限控制
权限控制
主要目的是保护系统的安全性和完整性,防止未经授权的用户获取敏感信息、执行非法操作或对系统进行恶意操作 。
常见的权限控制框架有SpringSecurity和Shiro。
若依的权限管理是通过RBAC
(Role-based Access Control 基于角色的访问控制)模型自己设计的。
RBAC模型将权限控制分为角色管理和权限管理两个部分。在若依中,角色是指对系统的一类用户或操作者的定义,而权限是指对系统中某个资源或操作的访问控制。通过为每个角色分配相应的权限,可以实现对系统的全面管理和控制。
权限控制
@RequiresPermissions
注解用于配置接口要求用户拥有某(些)权限才可访问,它拥有两个参数
参数 | 类型 | 描述 |
---|---|---|
value | String[] | 权限列表 |
logical | Logical | 权限之间的判断关系,默认为Logical.AND |
例如:
示例3: 以下代码表示需要拥有system:user:add
或system:user:edit
权限才可访问
@RequiresPermissions(value = {"system:user:add", "system:user:edit"}, logical = Logical.OR) public AjaxResult save(...) { return AjaxResult.success(...); }
@RequiresLogin
@RequiresLogin
注解用于配置接口要求用户必须登录才可访问,它没有参数
示例1: 以下代码表示必须拥有admin
角色才可访问
@RequiresLogin("admin")
public AjaxResult getInfo(...)
{
return AjaxResult.success(...);
}
@RequiresRoles
@RequiresRoles
注解用于配置接口要求用户拥有某(些)角色才可访问,它拥有两个参数
参数 | 类型 | 描述 |
---|---|---|
value | String[] | 角色列表 |
logical | Logical | 角色之间的判断关系,默认为Logical.AND |
示例3: 以下代码表示需要拥有admin
或common
角色才可访问
@RequiresRoles(value = {"admin", "common"}, logical = Logical.OR)
public AjaxResult save(...)
{
return AjaxResult.success(...);
}
若依-系统日志
在实际开发中,对于某些关键业务,我们通常需要记录该操作的内容,一个操作调一次记录方法,每次还得去收集参数等等,会造成大量代码重复。 我们希望代码中只有业务相关的操作,在项目中使用注解来完成此项功能。
在需要被记录日志的controller
方法上添加@Log
注解,使用方法如下:
参数 | 类型 | 默认值 | 描述 |
---|---|---|---|
title | String | 空 | 操作模块 |
businessType | BusinessType | OTHER | 操作功能(OTHER 其他、INSERT 新增、UPDATE 修改、DELETE 删除、GRANT 授权、EXPORT 导出、IMPORT 导入、FORCE 强退、GENCODE 生成代码、CLEAN 清空数据) |
operatorType | OperatorType | MANAGE | 操作人类别(OTHER 其他、MANAGE 后台用户、MOBILE 手机端用户) |
isSaveRequestData | boolean | true | 是否保存请求的参数 |
isSaveResponseData | boolean | true | 是否保存响应的参数 |
excludeParamNames | String[] | {} | 排除指定的请求参数 |
@Log(title = "用户管理", businessType = BusinessType.INSERT)
public AjaxResult addSave(...)
{
return success(...);
}
若依-代码生成
CVing
开发用户系统
微信授权登陆
share-user
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
</dependency>
@RestController
@RequestMapping("/userInfo")
public class UserInfoApiController extends BaseController {
@Autowired
private IUserInfoService userInfoService;
//微信授权登陆
@Operation(summary = "小程序授权登录")
@InnerAuth
@GetMapping("/wxLogin/{code}")
public R<UserInfo> wxLogin(@PathVariable String code) {
return R.ok(userInfoService.wxLogin(code));
}
}
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements IUserInfoService
{
@Autowired
private UserInfoMapper userInfoMapper;
@Autowired
private WxMaService wxMaService;
/**
* 查询用户列表
*
* @param userInfo 用户
* @return 用户
*/
@Override
public List<UserInfo> selectUserInfoList(UserInfo userInfo)
{
return userInfoMapper.selectUserInfoList(userInfo);
}
@Override
public UserInfo wxLogin(String code) {
//拿到code+appid+密钥,请求微信接口服务,返回openid
String openid;
try {
WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(code);
openid = sessionInfo.getOpenid();
//拿到openid,去查询数据库里面有没有openid,表示是否第一次登陆
LambdaQueryWrapper<UserInfo> wrapper = new LambdaQueryWrapper<UserInfo>();
wrapper.eq(UserInfo::getWxOpenId, openid);
UserInfo userInfo = userInfoMapper.selectOne(wrapper);
//查不到添加到数据库然后在返回
//判断
if (userInfo == null) {
userInfo = new UserInfo();
userInfo.setNickname(String.valueOf(System.currentTimeMillis()));
userInfo.setAvatarUrl("https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg");
userInfo.setWxOpenId(openid);
userInfoMapper.insert(userInfo);
}
//返回userinfo登陆
return userInfo;
} catch (WxErrorException e) {
throw new RuntimeException(e);
}
}
}
share-api-user
微信登陆失败ing
Day3.30 找了一上午bug最终决定把老师的代码粘了一遍....
记得在这里改为你自己与前端对应的端口号
附近门店
//批量获取规则数据列表
@Operation(summary = "批量获取费用规则信息")
@PostMapping(value = "/getFeeRuleList")
public R<List<FeeRule>> getFeeRuleList(@RequestBody List<Long> feeRuleIds) {
List<FeeRule> feeRuleList = feeRuleService.listByIds(feeRuleIds);
return R.ok(feeRuleList);
}
//根据id获取规则详情
@Operation(summary = "获取费用规则详细信息")
@InnerAuth
@GetMapping(value = "/getFeeRule/{id}")
public R<FeeRule> getFeeRule(@PathVariable("id") Long id)
{
return R.ok(feeRuleService.getById(id));
}
比较项 | @RequestBody | @PathVariable |
---|---|---|
参数位置 | HTTP 请求体 | 请求的 URL 路径 |
适用请求方法 | POST、PUT 等 | GET |
数据类型 | 适合传递复杂的数据结构,如 JSON、XML 等 | 通常用于传递简单的数据类型,如字符串、整数等 |
示例 | 请求体:[1, 2, 3] 方法参数: @RequestBody List<Long> feeRuleIds | 请求 URL:/getFeeRule/1 方法参数: @PathVariable("id") Long id |
Day3.31三月最后一天 、
远程服务总是出现不允许访问。。。
Day4.1 修bug
非常感谢一位大佬远程帮我看了代码,不到一小时就看出问题了泪目中...
了解到若依框架中的服务降级
@FeignClient
注解是 Spring Cloud 中的一个组件,它是基于 Netflix Feign 实现的。@FeignClient 注解可以帮助我们定义和实现服务之间的 RESTful 接口,使得服务之间的调用更加方便和可靠。@FeignClient 注解可以用于客户端的 API 接口定义,它可以将一个 HTTP API 接口转化为一个 Java 接口,从而使得我们可以像调用本地方法一样调用远程服务。
引申:
Spring Cloud服务降级与熔断
Hystrix
主要有服务降级、服务熔断、接近实时的监控、限流、隔离等等
服务降级(Fall Back) 假设微服务A要调用的服务B不可用了,需要服务B提供一个兜底的解决方法,而不是让服务A在那里傻等,耗死。不让客户端等待并立刻返回一个友好提示,比如像客户端提示服务器忙,请稍后再试等
服务熔断(Break) 服务熔断就相当于物理上的 熔断保险丝 。类比保险丝达到最大服务访问后,直接拒绝访问,拉闸断电,然后调用服务降级的方法并返回友好提示。
服务限流(Flow Limit)秒杀高并发等操作,严禁一窝蜂地过来拥挤,大家排队,一秒钟N个,有序进行。
MQTT协议
HTTP协议
HTTP协议概述
HTTP是一种应用层协议,使用TCP作为传输层协议,默认端口是80,基于请求和响应的方式,即客户端发起请求,服务器响应请求并返回数据(HTML,JSON)。在HTTP/1.1中,使用了长连接技术,允许一个连接复用多个请求和响应,减少了TCP三次握手的消耗。
HTTP的基本结构
请求行:包含请求方法(GET, POST等)、请求URL、协议版本。
请求头:包括各种元数据,如Connection、Host、Content-Type等。
空行:标识头部与载荷的分界线
请求体:通常在POST请求中出现,包含请求的具体数据。
HTTP的局限性
无状态性:HTTP是无状态协议,每次请求都是独立的,不会记录上一次请求的任何信息,如果需要记录用户状态,需要额外机制如: Cookies:浏览器在发送请求时,可以携带上次访问时服务器存储的Cookies(小型文本数据),服务器通过这些Cookies来识别用户的身份或维持会话状态。 高开销:每次请求都需要建立TCP连接,导致网络开销较大,尤其在频繁请求的场景下。 实时性差:HTTP通常是客户端主动发起请求,服务器无法主动推送数据。
MQTT协议概述
MQTT的基本概念
MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。使用TCP协议进行传输,端口为1883(非加密)和8883(加密),客户端通过发布(Publish)消息到某个主题(Topic),而其他订阅(Subscribe)该主题的客户端会接收到消息。协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。
MQTT的基本结构
主题(Topic):消息的标签,决定了消息的去向。订阅者根据主题来接收消息。 QoS(Quality of Service)级别:决定消息传输的可靠性。MQTT支持三个级别的QoS: QoS 0:最多一次发送,不保证消息送达。 QoS 1:至少一次发送,确保消息至少送达一次。 QoS 2:只有一次发送,确保消息只送达一次。 保留标志:用于确保客户端在订阅时能接收到最后一条消息。
给目录权限:
icacls "D:\Docker\docker-data\opt\emqx\data" /grant "Everyone:(OI)(CI)F" /T
icacls "D:\Docker\docker-data\opt\emqx\log" /grant "Everyone:(OI)(CI)F" /T
拉取镜像:
docker run -d --name emqx `
>> -p 1883:1883 -p 8083:8083 `
>> -p 8084:8084 -p 8883:8883 `
>> -p 18083:18083 `
>> -v D:\Docker\docker-data\opt\emqx\data:/opt/emqx/data `
>> -v D:\Docker\docker-data\opt\emqx\log:/opt/emqx/log `
>> emqx/emqx:5.7.0
emqx默用户名:admin。密码:public
Day4.2 RabbitMQ
消息队列解决什么问题
1.1、异步
1.2、解耦
1.3、并行
1.4、排队
规则引擎 Drools
规则引擎可以做到把算法剥离出程序,你可以保存到TXT文件或者数据库表里面,用的时候再加载回程序。虽然加载回来的算法是字符串,但是规则引擎有办法运行这些字符串。例如商业中心人流量大的地方,共享充电宝收费就得上调一些。人流量小的地方可以下调一点。既然费用的算法经常要变动,我们肯定不能把算法写死到程序里面。我们要把算法从程序中抽离,保存到MySQL里面。将来我们要改动计费算法,直接添加一个新纪录就行了,原有记录不需要删改,程序默认使用最新的计费方式。
规则引擎概述
规则引擎,全称为业务规则管理系统,英文名为BRMS(即Business Rule Management System)。规则引擎的主要思想是将应用程序中的业务决策部分分离出来,并使用预定义的语义模块编写业务决策(业务规则),由用户或开发者在需要时进行配置、管理。
需要注意的是规则引擎并不是一个具体的技术框架,而是指的一类系统,即业务规则管理系统。目前市面上具体的规则引擎产品有:drools、VisualRules、iLog等。
规则引擎实现了将业务决策从应用程序代码中分离出来,接收数据输入,解释业务规则,并根据业务规则做出业务决策。规则引擎其实就是一个输入输出平台。
系统中引入规则引擎后,业务规则不再以程序代码的形式驻留在系统中,取而代之的是处理规则的规则引擎,业务规则存储在规则库中,完全独立于程序。业务人员可以像管理数据一样对业务规则进行管理,比如查询、添加、更新、统计、提交业务规则等。业务规则被加载到规则引擎中供应用系统调用。
使用规则引擎的优势
使用规则引擎的优势如下:
1、业务规则与系统代码分离,实现业务规则的集中管理
2、在不重启服务的情况下可随时对业务规则进行扩展和维护
3、可以动态修改业务规则,从而快速响应需求变更
4、规则引擎是相对独立的,只关心业务规则,使得业务分析人员也可以参与编辑、维护系统的业务规则
5、减少了硬编码业务规则的成本和风险
6、使用规则引擎提供的规则编辑工具,使复杂的业务规则实现变得的简单
规则引擎应用场景
对于一些存在比较复杂的业务规则并且业务规则会频繁变动的系统比较适合使用规则引擎,如下:
1、风险控制系统----风险贷款、风险评估
2、反欺诈项目----银行贷款、征信验证
3、决策平台系统----财务计算
4、促销平台系统----满减、打折、加价购
Day15 4.7Drools介绍
drools是一款由JBoss组织提供的基于Java语言开发的开源规则引擎,可以将复杂且多变的业务规则从硬编码中解放出来,以规则脚本的形式存放在文件或特定的存储介质中(例如存放在数据库中),使得业务规则的变更不需要修改项目代码、重启服务器就可以在线上环境立即生效。
入门案例:
idea要下载drools插件才不会显示问号
Drools基础语法
规则文件构成
在使用Drools时非常重要的一个工作就是编写规则文件,通常规则文件的后缀为.drl。
drl是Drools Rule Language的缩写。在规则文件中编写具体的规则内容。
一套完整的规则文件内容构成如下:
关键字 | 描述 |
---|---|
package | 包名,只限于逻辑上的管理,同一个包名下的查询或者函数可以直接调用 |
import | 用于导入类或者静态方法 |
global | 全局变量 |
function | 自定义函数 |
query | 查询 |
rule end | 规则体 |
Drools支持的规则文件,除了drl形式,还有Excel文件类型的。
测试一直出现
我出现的问题是rabbitMQ的一直在控制台循环出问题,我将端口号改为5672就好了
后面出现空指针异常,在controller层的@innerAuto没有注释掉!