充电宝项目学习碎碎念

Day1 3.22前言

   共享充电宝是基于若依微服务版本框架开发的一个共享系统,项目包含平台管理端与微信小程序端,是一个前后端分离的项目。共享充电宝分为后台系统和前台微信小程序。

        这个前后端分离项目的环境搭建很是让我一顿折磨,一开始不了解docker怎么用,就欲先下载好多软件,后面发现镜像这么快。希望能少点bug多点爱@~@

技术架构图:

业务流程图:

若依框架

官网:https://doc.ruoyi.vip/

微服务版本文档: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

柜机类型

  1. 第一步创建动态菜单

    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

注解用于配置接口要求用户拥有某(些)权限才可访问,它拥有两个参数

参数类型描述
valueString[]权限列表
logicalLogical权限之间的判断关系,默认为Logical.AND

例如:

示例3: 以下代码表示需要拥有system:user:addsystem: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注解用于配置接口要求用户拥有某(些)角色才可访问,它拥有两个参数

参数类型描述
valueString[]角色列表
logicalLogical角色之间的判断关系,默认为Logical.AND

示例3: 以下代码表示需要拥有admincommon角色才可访问

@RequiresRoles(value = {"admin", "common"}, logical = Logical.OR)
public AjaxResult save(...)
{
    return AjaxResult.success(...);

若依-系统日志

在实际开发中,对于某些关键业务,我们通常需要记录该操作的内容,一个操作调一次记录方法,每次还得去收集参数等等,会造成大量代码重复。 我们希望代码中只有业务相关的操作,在项目中使用注解来完成此项功能。

在需要被记录日志的controller方法上添加@Log注解,使用方法如下:

参数类型默认值描述
titleString操作模块
businessTypeBusinessTypeOTHER操作功能(OTHER其他、INSERT新增、UPDATE修改、DELETE删除、GRANT授权、EXPORT导出、IMPORT导入、FORCE强退、GENCODE生成代码、CLEAN清空数据)
operatorTypeOperatorTypeMANAGE操作人类别(OTHER其他、MANAGE后台用户、MOBILE手机端用户)
isSaveRequestDatabooleantrue是否保存请求的参数
isSaveResponseDatabooleantrue是否保存响应的参数
excludeParamNamesString[]{}排除指定的请求参数

@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没有注释掉!

Day16 4.8 微信支付没有资质完结

Day17 4.9 Spring AI

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值