Java --- 云尚优选项目

目录

一、项目工程搭建

二、配置项目相关maven依赖

2.1、cjc.ssyx父工程依赖 

 2.2、common模块pom依赖

2.3、common-util模块pom依赖

2.4、service-util模块pom依赖

2.5、model模块pom依赖

2.6、service模块pom依赖

三、编写相关工具类

3.1、mybatis-plus分页查询配置类 

3.2、统一返回结果类

3.3、统一异常处理类

3.4、编写Swagger配置类

四、尚上优先前端平台管理

4.1、node.js安装

4.2、安装vscode及相关插件 

 4.3、测试vue框架是否正常运行

4.4、导入前端项目并测试运行

五、后端登录功能

5.1、修改application.yml文件

5.2、修改application-dev.yml文件

5.3、后端登录功能接口编写

5.4、前端修改

5.5、运行报错解决

5.6、前后端跨域问题

5.7、测试访问

六、角色管理功能开发

6.1、分页条件查询 

6.2、增删改查功能

七、用户管理模块

7.1、用户的crud功能 

7.2、用户分配角色功能

八、菜单管理模块

8.1、查询菜单功能 

8.2、递归删除

8.3、添加与修改功能

8.4、为角色分配菜单

九、开通区域功能模块

9.1、使用代码生成器

9.2、查询区域开通列表功能

 9.3、添加开通区域及查询区域功能

9.4、取消与删除开通区域功能

十、配置nginx反向代理

十一、商品信息管理模块 

11.1、商品分类信息模块功能 

11.2、平台属性分组模块功能

11.3、平台属性管理模块功能

11.4、SKU列表模块 

11.4.1、SKU信息分页条件查询功能

11.4.2、开通阿里云对象存储OSS

11.4.2.1、创建自己的Bucket

11.4.2.2、创建自己的AccessKey

11.4.3、SKU图片上传功能

11.4.4、SKU信息添加功能

 11.4.4.1、文件上传时前后端端口不一致问题

11.4.5、SKU商品的修改与删除功能

11.4.6、SKU商品信息的审核、上下架、新人专享功能 

十二、Nacos下载与安装 

12.1、修改springboot配置文件

十三、整合ES+MQ实现商品上下架

13.1、安装Elasticsearch

13.2、安装Kibana

13.3、整合Elasticsearch

13.4、完成远程调用接口功能

13.4.1、发送消息功能:

 13.4.2、远程调用接口

 13.4.3、接收消息功能:

13.5、在docker安装rabbitMQ

13.6、整合rabbitmq

13.7、完善商品上下架功能

十四、营销活动管理

14.1、活动列表的crud功能

14.2、查询活动规则功能

14.3、添加活动规则功能

14.4、优惠卷功能 

 十五、整合Gateway网关

十六、微信小程序开发环境搭建

 16.1、注册微信程序开发测试号

16.2、安装微信开发者工具

 16.3、安装hbuilderx

16.3.1、配置 hbuilderx

16.4、开通内网穿透

十七、用户登录

17.1、整合JWT

17.1.1、jwt简介

17.1.2、jwt原理

17.1.3、springboot整合Jwt

17.2、整合redis

17.3、springboot整合微信小程序配置

17.4、添加相关工具类

17.5、用户登录功能

十八、首页数据显示

18.1、拦截器 

18.1.1、实现步骤 

18.2、首页数据显示 

 十九、商品分类与搜索

二十、商品详情 

20.1、CompletableFuture的使用测试

20.1.1、创建异步对象

 20.1.2、计数完成回调

 20.1.3、串行化

20.1.4、多任务组合


一、项目工程搭建

二、配置项目相关maven依赖

2.1、cjc.ssyx父工程依赖 

<properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <skipTests>true</skipTests>
        <java.version>1.8</java.version>
        <cloud.version>Hoxton.SR8</cloud.version>
        <alibaba.version>2.2.2.RELEASE</alibaba.version>
        <mybatis-plus.version>3.4.1</mybatis-plus.version>
        <mysql.version>8.0.30</mysql.version>
        <jwt.version>0.7.0</jwt.version>
        <fastjson.version>2.0.21</fastjson.version>
        <httpclient.version>4.5.6</httpclient.version>
        <easyexcel.version>2.1.6</easyexcel.version>
        <aliyun.version>4.4.1</aliyun.version>
        <oss.version>3.9.1</oss.version>
        <knife4j.version>2.0.8</knife4j.version>
        <jodatime.version>2.10.10</jodatime.version>
        <xxl-job.version>2.3.0</xxl-job.version>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.6.RELEASE</version>
    </parent>

    <!--配置dependencyManagement锁定依赖的版本-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--mybatis-plus 持久层-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <dependency>
                <groupId>com.github.xiaoymin</groupId>
                <artifactId>knife4j-spring-boot-starter</artifactId>
                <version>2.0.8</version>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>${jwt.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>${httpclient.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>easyexcel</artifactId>
                <version>${easyexcel.version}</version>
            </dependency>
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-core</artifactId>
                <version>${aliyun.version}</version>
            </dependency>
            <!--日期时间工具-->
            <dependency>
                <groupId>joda-time</groupId>
                <artifactId>joda-time</artifactId>
                <version>${jodatime.version}</version>
            </dependency>
            <dependency>
                <groupId>com.xuxueli</groupId>
                <artifactId>xxl-job-core</artifactId>
                <version>${xxl-job.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
  

 2.2、common模块pom依赖

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>provided </scope>
        </dependency>

        <!--lombok用来简化实体类:需要安装lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- https://doc.xiaominfo.com/knife4j/documentation/ -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
        </dependency>

        <!--用来转换json使用 {JavaObject - json | json - JavaObject}-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

        <!-- 服务调用feign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <scope>provided </scope>
        </dependency>
    </dependencies>

2.3、common-util模块pom依赖

<dependencies>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>

        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>

        <dependency>
            <groupId>com.cjc</groupId>
            <artifactId>model</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

2.4、service-util模块pom依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>provided </scope>
        </dependency>

        <!--lombok用来简化实体类:需要安装lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- https://doc.xiaominfo.com/knife4j/documentation/ -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
        </dependency>

        <!--用来转换json使用 {JavaObject - json | json - JavaObject}-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

        <!-- 服务调用feign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <scope>provided </scope>
        </dependency>
    </dependencies>

2.5、model模块pom依赖

<dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <scope>provided </scope>
        </dependency>

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <!--在引用时请在maven中央仓库搜索2.X最新版本号-->
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
            <scope>provided </scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <scope>provided </scope>
        </dependency>

        <!--创建索引库的-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
            <scope>provided </scope>
        </dependency>
    </dependencies>

2.6、service模块pom依赖

   <dependencies>
        <!--依赖服务的工具类-->
        <dependency>
            <groupId>com.cjc</groupId>
            <artifactId>service-util</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--数据载体-->
        <dependency>
            <groupId>com.cjc</groupId>
            <artifactId>model</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!--web 需要启动项目-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- 服务注册 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- 服务调用feign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <!-- 流量控制 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <!--开发者工具-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

三、编写相关工具类

3.1、mybatis-plus分页查询配置类 

@Configuration
@MapperScan("com.cjc.ssyx.*.mapper")
public class MybatisPlusConfig {
    /**
     * 分页插件
     */
    @Bean
    public MybatisPlusInterceptor optimisticLockerInnerInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //向Mybatis过滤器链中添加分页拦截器
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
    @Bean
    public ConfigurationCustomizer configurationCustomizer(){
        return configuration -> configuration.setUseDeprecatedExecutor(false);
    }
}

3.2、统一返回结果类

@Getter
public enum ResultCodeEnum {

    SUCCESS(200,"成功"),
    FAIL(201, "失败"),
    SERVICE_ERROR(2012, "服务异常"),
    DATA_ERROR(204, "数据异常"),
    ILLEGAL_REQUEST(205, "非法请求"),
    REPEAT_SUBMIT(206, "重复提交"),

    LOGIN_AUTH(208, "未登陆"),
    PERMISSION(209, "没有权限"),

    ORDER_PRICE_ERROR(210, "订单商品价格变化"),
    ORDER_STOCK_FALL(204, "订单库存锁定失败"),
    CREATE_ORDER_FAIL(210, "创建订单失败"),

    COUPON_GET(220, "优惠券已经领取"),
    COUPON_LIMIT_GET(221, "优惠券已发放完毕"),

    URL_ENCODE_ERROR( 216, "URL编码失败"),
    ILLEGAL_CALLBACK_REQUEST_ERROR( 217, "非法回调请求"),
    FETCH_ACCESSTOKEN_FAILD( 218, "获取accessToken失败"),
    FETCH_USERINFO_ERROR( 219, "获取用户信息失败"),


    SKU_LIMIT_ERROR(230, "购买个数不能大于限购个数"),
    REGION_OPEN(240, "该区域已开通"),
    REGION_NO_OPEN(240, "该区域未开通"),
    ;

    private Integer code;

    private String message;

    private ResultCodeEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}
@Data
public class Result<T> {
    //状态码
    private Integer code;
    //信息
    private String message;
    //返回数据
    private T data;
    //构造方法私有化
    private Result(){

    }
    //统一返回信息方法
    public static<T> Result<T> build(T data,ResultCodeEnum resultCodeEnum){
        //设置值
        Result<T> result = new Result<>();
        //判断是否返回数据
        if(data != null){
            result.setData(data);
        }
        //设置状态码与信息
        result.setCode(resultCodeEnum.getCode());
        result.setMessage(resultCodeEnum.getMessage());
        return result;
    }
    //成功的方法
    public static<T> Result<T> ok(T data){
        Result<T> result = build(data, ResultCodeEnum.SUCCESS);
        return result;
    }
    //失败方法
    public static<T> Result<T> fail(T data){
        Result<T> result = build(data, ResultCodeEnum.FAIL);
        return result;
    }
}

3.3、统一异常处理类

//自定义异常信息
@Data
@ToString
public class CustomException extends RuntimeException{
    //异常状态码
    private Integer code;
    //通过状态码和错误消息创建异常对象
    public CustomException(String message,Integer code){
        super(message);
        this.code = code;
    }
    /**
     * 接收枚举类型对象
     * @param resultCodeEnum
     */
    public CustomException(ResultCodeEnum resultCodeEnum) {
        super(resultCodeEnum.getMessage());
        this.code = resultCodeEnum.getCode();
    }
    
}
@ControllerAdvice
public class GlobalExceptionHandler {
    //全局异常处理
    @ExceptionHandler(Exception.class)//异常处理器
    @ResponseBody//返回json数据
    public Result error(Exception e){
        e.printStackTrace();
        return Result.fail(null);
    }
    //自定义异常处理
    @ExceptionHandler(CustomException.class)
    @ResponseBody
   public Result error(CustomException e){
       return Result.fail(null);
   }
}

3.4、编写Swagger配置类

@Configuration
@EnableSwagger2WebMvc
public class Swagger2Config {

    @Bean
    public Docket webApiConfig(){
        List<Parameter> pars = new ArrayList<>();
        ParameterBuilder tokenPar = new ParameterBuilder();
        tokenPar.name("userId")
                .description("用户token")
                //.defaultValue(JwtHelper.createToken(1L, "admin"))
                .defaultValue("1")
                .modelRef(new ModelRef("string"))
                .parameterType("header")
                .required(false)
                .build();
        pars.add(tokenPar.build());

        Docket webApi = new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                //只显示api路径下的页面
                .apis(RequestHandlerSelectors.basePackage("com.cjc.ssyx"))
                .paths(PathSelectors.regex("/api/.*"))
                .build()
                .globalOperationParameters(pars);
        return webApi;
    }

    @Bean
    public Docket adminApiConfig(){
        List<Parameter> pars = new ArrayList<>();
        ParameterBuilder tokenPar = new ParameterBuilder();
        tokenPar.name("adminId")
                .description("用户token")
                .defaultValue("1")
                .modelRef(new ModelRef("string"))
                .parameterType("header")
                .required(false)
                .build();
        pars.add(tokenPar.build());

        Docket adminApi = new Docket(DocumentationType.SWAGGER_2)
                .groupName("adminApi")
                .apiInfo(adminApiInfo())
                .select()
                //只显示admin路径下的页面
                .apis(RequestHandlerSelectors.basePackage("com.cjc.ssyx"))
                .paths(PathSelectors.regex("/admin/.*"))
                .build()
                .globalOperationParameters(pars);
        return adminApi;
    }

    private ApiInfo webApiInfo(){
        return new ApiInfoBuilder()
                .title("网站-API文档")
                .description("本文档描述了尚上优选网站微服务接口定义")
                .version("1.0")
                .contact(new Contact("cjc", "http://cjc.com", "cjc"))
                .build();
    }

    private ApiInfo adminApiInfo(){
        return new ApiInfoBuilder()
                .title("后台管理系统-API文档")
                .description("本文档描述了尚上优选后台系统服务接口定义")
                .version("1.0")
                .contact(new Contact("cjc", "http://cjc.com", "cjc"))
                .build();
    }
}

四、尚上优先前端平台管理

4.1、node.js安装

4.2、安装vscode及相关插件 

 

 4.3、测试vue框架是否正常运行

#全局安装命令行工具
npm install --location=global @vue/cli
#创建一个项目
vue create vue-test #选择vue2
#进入到项目目录
cd vue-test
#启动程序
npm run serve

PS D:\vue\cjc-ssyx\test> vue create vue-test
vue : 无法加载文件 D:\tools\nodejs\node_global\vue.ps1。未对文件 D:\tools\nodejs\node_global\vue.ps1 进行数字签名。无法在当前系统上运行该脚本。有关运行脚本和设置
执行策略的详细信息,请参阅 https:/go.microsoft.com/fwlink/?LinkID=135170 中的 about_Execution_Policies。
所在位置 行:1 字符: 1
+ vue create vue-test
+ ~~~
    + CategoryInfo          : SecurityError: (:) [],PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess 

解决办法:以管理员的方式运行即可

 最终效果

4.4、导入前端项目并测试运行

运行指令:npm run dev

五、后端登录功能

5.1、修改application.yml文件

spring:
  application:
    name: service-acl
  profiles:
    active: dev

5.2、修改application-dev.yml文件

server:
  port: 8081

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shequ-acl?characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456

  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

5.3、后端登录功能接口编写

@Api(tags = "登录功能模块")
@RestController
@RequestMapping("/admin/acl/index")
public class IndexController {
    /**
     * 登录功能
     * @return map
     */
    @ApiOperation("登录功能")
    @PostMapping("/login")
    public Result login(){
        //返回token值
        HashMap<String,String> map = new HashMap<>();
        map.put("token","token-test");
        return Result.ok(map);
    }

    /**
     * 获取信息
     * @return
     */
    @ApiOperation("获取信息")
    @GetMapping("/info")
    public Result info(){
        HashMap<String, String> map = new HashMap<>();
        map.put("name","admin");
        map.put("avatar","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
        return Result.ok(map);
    }

    /**
     * 退出
     * @return
     */
    @ApiOperation("退出")
    @PostMapping("/logout")
    public Result logout(){
       return Result.ok(null);
    }
}

5.4、前端修改

5.5、运行报错解决

com.alibaba.nacos.api.exception.NacosException: failed to req API:/nacos/v1/ns/instance after all servers([localhost:8848]) tried: java.net.ConnectException: Connection refused: connect
    at com.alibaba.nacos.client.naming.net.NamingProxy.reqApi(NamingProxy.java:552) ~[nacos-client-1.3.2.jar:na]
    at com.alibaba.nacos.client.naming.net.NamingProxy.reqApi(NamingProxy.java:491) ~[nacos-client-1.3.2.jar:na]
    at com.alibaba.nacos.client.naming.net.NamingProxy.reqApi(NamingProxy.java:486) ~[nacos-client-1.3.2.jar:na]
    at com.alibaba.nacos.client.naming.net.NamingProxy.registerService(NamingProxy.java:239) ~[nacos-client-1.3.2.jar:na]
    at com.alibaba.nacos.client.naming.NacosNamingService.registerInstance(NacosNamingService.java:200) ~[nacos-client-1.3.2.jar:na] 

解决办法:注销相关依赖

 

5.6、前后端跨域问题

Access to XMLHttpRequest at 'http://localhost:8081/admin/acl/index/login' from origin 'http://localhost:9528' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. 

 解决方法:

5.7、测试访问

六、角色管理功能开发

6.1、分页条件查询 

 mapper接口

@Mapper
public interface RoleMapper extends BaseMapper<Role> {
}

 service接口

public interface RoleService extends IService<Role> {
    IPage<Role> selectRolePage(Page<Role> page, RoleQueryVo roleQueryVo);
}

service接口实现类

@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
    //分页条件查询
    @Override
    public IPage<Role> selectRolePage(Page<Role> page, RoleQueryVo roleQueryVo) {
        //获取查询条件值
        String roleName = roleQueryVo.getRoleName();
        //条件封装
        LambdaQueryWrapper<Role> wrapper = new LambdaQueryWrapper<>();
        //判断是否为空
        if (!StringUtils.isEmpty(roleName)){
            wrapper.like(Role::getRoleName,roleName);
        }
        //调用方法查询
        Page<Role> rolePage = baseMapper.selectPage(page, wrapper);
        return rolePage;
    }
}

controller层

@Api(tags = "角色管理模块")
@RestController
@RequestMapping("/admin/acl/role")
@CrossOrigin
public class RoleController {
    @Autowired
    private RoleService roleService;

    /**
     *
     * @param current 当前页
     * @param limit 每页显示记录数
     * @param roleQueryVo 条件对象
     * @return
     */
    @ApiOperation("分页查询")
    @GetMapping("/{current}/{limit}")
    public Result pageList(@PathVariable("current") Long current,
                           @PathVariable("limit") Long limit,
                           RoleQueryVo roleQueryVo){
        //创建分页对象
        Page<Role> page = new Page<>(current,limit);
        //调用方法实现分页查询
        IPage<Role> roleIPage = roleService.selectRolePage(page,roleQueryVo);
        return Result.ok(roleIPage);
    }
}

测试结果:

6.2、增删改查功能

@ApiOperation("根据id查询")
    @GetMapping("/get/{id}")
    public Result selectRoleById(@PathVariable("id") Long id){
        Role role = roleService.getById(id);
        return Result.ok(role);
    }
    
    @ApiOperation("添加角色")
    @PostMapping("/save")
    public Result saveRole(@RequestBody Role role){
        boolean save = roleService.save(role);
        if (save){
            return Result.ok(null); 
        }else {
            return Result.fail(null);
        }
    }
    @ApiOperation("修改角色")
    @PutMapping("/update")
    public Result updateRole(@RequestBody Role role){
        boolean update = roleService.updateById(role);
        if (update){
            return Result.ok(null);
        }else {
            return Result.fail(null);
        }
    }
    @ApiOperation("根据id删除")
    @DeleteMapping("/remove/{id}")
    public Result removeRoleById(@PathVariable("id") Long id){
        boolean remove = roleService.removeById(id);
        if (remove){
            return Result.ok(null);
        }else {
            return Result.fail(null);
        }
    }
    @ApiOperation("根据id批量删除")
    @DeleteMapping("/batchRemove")
    public Result removeByList(@RequestBody List<Long> idList){
        boolean removeByIds = roleService.removeByIds(idList);
        if (removeByIds){
            return Result.ok(null);
        }else {
            return Result.fail(null);
        }
    }

 后端接口测试以添加为例:

七、用户管理模块

7.1、用户的crud功能 

 service实现类层

@Service
public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements AdminService {
    //分页条件查询
    @Override
    public IPage<Admin> selectAdminPage(Page<Admin> page, AdminQueryVo adminQueryVo) {
        String name = adminQueryVo.getName();
        String username = adminQueryVo.getUsername();
        LambdaQueryWrapper<Admin> wrapper = new LambdaQueryWrapper<>();
        if (!StringUtils.isEmpty(name)){
            wrapper.like(Admin::getName,name);
        }
        if (!StringUtils.isEmpty(username)){
            wrapper.like(Admin::getUsername,username);
        }
        Page<Admin> adminPage = baseMapper.selectPage(page, wrapper);
        return adminPage;
    }
}

controller层

@Api(tags = "用户管理模块")
@RestController
@RequestMapping("/admin/acl/user")
@CrossOrigin
public class AdminController {
    @Autowired
    private AdminService adminService;

    @ApiOperation("分页查询用户")
    @GetMapping("/{current}/{limit}")
    public Result getAdmin(@PathVariable("current") Long current,
                           @PathVariable("limit") Long limit,
                           AdminQueryVo adminQueryVo){
        Page<Admin> page = new Page<>(current,limit);
        IPage<Admin> adminIPage = adminService.selectAdminPage(page,adminQueryVo);
        return Result.ok(adminIPage);
    }
    @ApiOperation("根据id查询")
    @GetMapping("/get/{id}")
    public Result getAdminById(@PathVariable("id") Long id){
        Admin admin = adminService.getById(id);
        return Result.ok(admin);
    }
    @ApiOperation("添加用户")
    @PostMapping("/save")
    public Result saveAdmin(@RequestBody Admin admin){
       //获取用户密码并进行加密处理
        admin.setPassword(MD5.encrypt(admin.getPassword()));
        boolean save = adminService.save(admin);
        if (save){
            return Result.ok(null);
        }else {
            return Result.fail(null);
        }
    }
    @ApiOperation("修改用户")
    @PutMapping("/update")
    public Result updateAdmin(@RequestBody Admin admin){
        //获取用户密码并进行加密处理
        admin.setPassword(MD5.encrypt(admin.getPassword()));
        boolean update = adminService.updateById(admin);
        if (update){
            return Result.ok(null);
        }else {
            return Result.fail(null);
        }
    }
    @ApiOperation("根据id删除用户")
    @DeleteMapping("/delete/{id}")
    public Result removeById(@PathVariable("id") Long id){
        boolean remove = adminService.removeById(id);
        if (remove){
            return Result.ok(null);
        }else {
            return Result.fail(null);
        }
    }
    @ApiOperation("根据id批量删除用户")
    @DeleteMapping("/batchRemove")
    public Result removeByBatch(@RequestBody List<Long> idList){
        boolean removeByIds = adminService.removeByIds(idList);
        if (removeByIds){
            return Result.ok(null);
        }else {
            return Result.fail(null);
        }
    }
}

接口测试以添加为例:

前端界面效果:

7.2、用户分配角色功能

 功能分析图:

功能实现:

service实现类 

 //查询所有角色和根据用户id查询分配角色
    @Override
    public Map<String, Object> getRoleByAdminId(Long adminId) {
        HashMap<String, Object> map = new HashMap<>();
        //查询所有角色
        List<Role> roles = baseMapper.selectList(null);
        //根据用户id查询用户分配角色列表
        LambdaQueryWrapper<AdminRole> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(AdminRole::getAdminId,adminId);
        List<AdminRole> adminRoleList = adminRoleService.list(wrapper);
        //获取角色关系表里的所有角色id,封装为list集合
        List<Long> roleIdList = adminRoleList.stream().map(c -> c.getRoleId()).collect(Collectors.toList());
        //创建list集合,用于存储用户分配角色
        ArrayList<Role> roleList = new ArrayList<>();
        //判断所有角色中已经分配了的角色id
        for (Role role : roles) {
            //判断是否存在
            if(roleIdList.contains(role.getId())){
                roleList.add(role);
            }
        }
        //封装数据并返回
        map.put("allRolesList",roles);
        map.put("assignRoles",roleList);
        return map;
    }
    //为用户分配角色
    @Override
    public void saveAdminRole(Long adminId, Long[] roleIds) {
        //删除用户已经分配过的角色id
        LambdaQueryWrapper<AdminRole> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(AdminRole::getAdminId,adminId);
        adminRoleService.remove(wrapper);
        ArrayList<AdminRole> arrayList = new ArrayList<>();
        //重新分配
        for (Long roleId : roleIds) {
            AdminRole adminRole = new AdminRole();
            adminRole.setAdminId(adminId);
            adminRole.setRoleId(roleId);
            arrayList.add(adminRole);
        }
        //批量添加
        adminRoleService.saveBatch(arrayList);
    }

controller层

@ApiOperation("获取用户角色")
    @GetMapping("/toAssign/{adminId}")
    public Result toAssign(@PathVariable("adminId") Long adminId){
        Map<String,Object> map = roleService.getRoleByAdminId(adminId);
        return Result.ok(map);
    }
    @ApiOperation("为用户分配角色")
    @PostMapping("/doAssign")
    public Result doAssign(@RequestParam Long adminId,
                           @RequestParam Long[] roleIds){
       roleService.saveAdminRole(adminId,roleIds);
        return Result.ok(null);
    }

前端效果图:

八、菜单管理模块

8.1、查询菜单功能 

数据格式工具类

public class PermissionHelper {
    //构建树形格式
    public static List<Permission> buildPermission(List<Permission> permissionList) {
        //存储最终数据格式
        List<Permission> trees = new ArrayList<>();
        for (Permission permission : permissionList) {
            //判断是否为第一层
            if (permission.getPid() == 0){
                permission.setLevel(1);
                //调用递归方法,并封装存放数据
                trees.add(findChildren(permission,permissionList));
            }
        }
        return trees;
    }

    /**
     *
     * @param permission 上级菜单
     * @param permissionList 所有菜单
     * @return
     */
    //递归查找子菜单
    private static Permission findChildren(Permission permission, List<Permission> permissionList) {
       permission.setChildren(new ArrayList<Permission>());
       //遍历所有菜单数据
        for (Permission p : permissionList) {
            //判断是否有下一级菜单
            if (permission.getId().equals(p.getPid())){
                int level = permission.getLevel() + 1;
                p.setLevel(level);
                if (permission.getChildren() == null){
                    permission.setChildren(new ArrayList<>());
                }
                //封装下一层数据,并递归查找
                permission.getChildren().add( findChildren(p,permissionList));

            }
        }
        return permission;
    }
}

 service实现类

 //查询所有菜单
    @Override
    public List<Permission> queryAllPermission() {
        //查询所有菜单
        List<Permission> permissionList = baseMapper.selectList(null);
        //转换数据格式
        List<Permission> result = PermissionHelper.buildPermission(permissionList);
        return result;
    }

controller层:

 @ApiOperation("查询所有菜单")
    @GetMapping()
    public Result queryAll(){
        List<Permission> list = permissionService.queryAllPermission();
        return Result.ok(list);
    }

接口测试:

8.2、递归删除

/**
     * 递归删除菜单
     * @param id 菜单id
     */
    @Override
    public void removeChildById(Long id) {
        //存放需要删除的菜单id
        ArrayList<Long> idList = new ArrayList<>();
        //递归查找子菜单
        this.getAllPermissionId(id,idList);
        //设置当前菜单id
        idList.add(id);
        //批量删除
        baseMapper.deleteBatchIds(idList);
    }

    /**
     * 递归查询当前菜单下的子菜单
     * @param id 当前菜单id
     * @param idList 所有菜单id集合
     */
    public void getAllPermissionId(Long id,List<Long> idList){
        //根据当前菜单id查询下面的子菜单
        LambdaQueryWrapper<Permission> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Permission::getPid,id);
        List<Permission> childList = baseMapper.selectList(wrapper);
        //递归查询子菜单
        childList.stream().forEach(c -> {
            //封装菜单id
            idList.add(c.getId());
            //递归查询
            this.getAllPermissionId(c.getId(),idList);
        });
    }
@ApiOperation("删除菜单(递归删除)")
    @DeleteMapping("/remove/{id}")
    public Result deletePermission(@PathVariable("id") Long id){
        permissionService.removeChildById(id);
        return Result.ok(null);
    }

8.3、添加与修改功能

 @ApiOperation("添加菜单")
    @PostMapping("/save")
    public Result savePermission(@RequestBody Permission permission){
         permissionService.save(permission);
        return Result.ok(null);
    }
    @ApiOperation("修改菜单")
    @PutMapping("/update")
    public Result updatePermission(@RequestBody Permission permission){
        permissionService.updateById(permission);
        return Result.ok(null);
    }

前端效果图:

8.4、为角色分配菜单

service实现类

/**
     * 查询角色及所属菜单
     * @param roleId 角色id
     * @return
     */
    @Override
    public List<Permission> getPermissionByRoleId(Long roleId) {
        //查询所有菜单
        List<Permission> permissions = baseMapper.selectList(null);
        //根绝角色id查询菜单id
        LambdaQueryWrapper<RolePermission> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(RolePermission::getRoleId,roleId);
        List<RolePermission> rolePermissionList = rolePermissionService.list(wrapper);
        //获取到菜单id
        List<Long> permissionIdList = rolePermissionList.stream().map(c -> c.getPermissionId()).collect(Collectors.toList());
        permissions.stream().forEach(c->{
            if (permissionIdList.contains(c.getId())){
                c.setSelect(true);
            }else {
                c.setSelect(false);
            }
        });
        //封装为树形结构
        List<Permission> permissionList = PermissionHelper.buildPermission(permissions);
        return permissionList;
    }

    /**
     * 为角色分配菜单
     * @param roleId 角色id
     * @param permissionId 菜单id集合
     */
    @Override
    public void doAssign(Long roleId, Long[] permissionId) {
        //根据角色id删除菜单角色表里数据
        LambdaQueryWrapper<RolePermission> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(RolePermission::getRoleId,roleId);
        rolePermissionService.remove(wrapper);
        ArrayList<RolePermission> list = new ArrayList<>();
        //获取角色分配的菜单id,并存入
        for (Long id : permissionId) {
            if (id == null){
                continue;
            }
            RolePermission rolePermission = new RolePermission();
            rolePermission.setRoleId(roleId);
            rolePermission.setPermissionId(id);
            list.add(rolePermission);
        }
        //添加到表中
        rolePermissionService.saveBatch(list);
    }

controller层

@ApiOperation("获取菜单列表")
    @GetMapping("/toAssign/{roleId}")
    public Result toAssign(@PathVariable("roleId") Long roleId){
        List<Permission> list = permissionService.getPermissionByRoleId(roleId);
        return Result.ok(list);
    }
    @ApiOperation("角色分配菜单")
    @PostMapping("/doAssign")
    public Result doAssign(@RequestParam("roleId") Long roleId,
                           @RequestParam("permissionId") Long[] permissionId){

        permissionService.doAssign(roleId,permissionId);
        return Result.ok(null);
    }

前端效果图:

九、开通区域功能模块

需要的表

 

9.1、使用代码生成器

 导入pom依赖

	<dependency>
		<groupId>com.baomidou</groupId>
		<artifactId>mybatis-plus-generator</artifactId>
		<version>3.4.1</version>
	</dependency>

	<dependency>
		<groupId>org.apache.velocity</groupId>
		<artifactId>velocity-engine-core</artifactId>
		<version>2.0</version>
	</dependency>
public class CodeGet {

    public static void main(String[] args) {

        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 2、全局配置
        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        gc.setOutputDir("D:\\java17\\cjc-ssyx\\service\\service-sys"+"/src/main/java");

        gc.setServiceName("%sService");	//去掉Service接口的首字母I
        gc.setAuthor("cjc");
        gc.setOpen(false);
        mpg.setGlobalConfig(gc);

        // 3、数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/shequ-sys?serverTimezone=GMT%2B8&useSSL=false");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);

        // 4、包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.cjc.ssyx");
        pc.setModuleName("sys"); //模块名
        pc.setController("controller");
        pc.setService("service");
        pc.setMapper("mapper");
        mpg.setPackageInfo(pc);

        // 5、策略配置
        StrategyConfig strategy = new StrategyConfig();

        strategy.setInclude("region","ware","region_ware");

        strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略

        strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
        strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作

        strategy.setRestControllerStyle(true); //restful api风格控制器
        strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符

        mpg.setStrategy(strategy);

        // 6、执行
        mpg.execute();
    }
}

9.2、查询区域开通列表功能

service实现类

/**
     * 查询开通区域列表
     * @param warePage 分页条件
     * @param regionWareQueryVo  条件搜索
     * @return
     */
    @Override
    public IPage<RegionWare> selectAllRegionWare(Page<RegionWare> warePage, RegionWareQueryVo regionWareQueryVo) {
        LambdaQueryWrapper<RegionWare> wrapper = new LambdaQueryWrapper<>();
        String keyword = regionWareQueryVo.getKeyword();
        //判断输入条件是否为空
        if (!StringUtils.isEmpty(keyword)){
            //封装条件 or()表示或者
            wrapper.like(RegionWare::getRegionName,keyword).or().like(RegionWare::getWareName,keyword);
        }
        //分页查询
        Page<RegionWare> regionWarePage = baseMapper.selectPage(warePage, wrapper);
        return regionWarePage;
    }

 controller层

 /**
     *
     * @param page 页数
     * @param limit 条数
     * @param regionWareQueryVo 搜索条件
     * @return
     */
    @ApiOperation("分页查询开通区域列表")
    @GetMapping("/{page}/{limit}")
    public Result getAll(@PathVariable("page") Long page,
                         @PathVariable("limit") Long limit,
                         RegionWareQueryVo regionWareQueryVo){
        Page<RegionWare> warePage = new Page<>(page,limit);
        IPage<RegionWare> list = regionWareService.selectAllRegionWare(warePage,regionWareQueryVo);
        return Result.ok(list);
    }

 接口测试:

 9.3、添加开通区域及查询区域功能

 service实现类

 /**
     * 添加开通区域
     * @param regionWare
     */
    @Override
    public void saveRegionWare(RegionWare regionWare) {
        //判断区域是否开通
        LambdaQueryWrapper<RegionWare> wrapper = new LambdaQueryWrapper<>();
        //一个区域可以开通多个仓库
        wrapper.eq(RegionWare::getWareId,regionWare.getWareId()).eq(RegionWare::getRegionId,regionWare.getRegionId());
        Integer count = baseMapper.selectCount(wrapper);
        //已经开通
        if (count > 0){
            //抛出自定义异常
          throw new CustomException(ResultCodeEnum.REGION_OPEN);
        }
        //报存信息
        baseMapper.insert(regionWare);
    }
/**
     * 根据条件查询区域
     * @param keyword
     * @return
     */
    @Override
    public List<Region> findRegionByKeyword(String keyword) {
        LambdaQueryWrapper<Region> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(Region::getName,keyword);
        List<Region> regionList = baseMapper.selectList(wrapper);
        return regionList;
    }

controller层

@ApiOperation("根据条件查询区域")
    @GetMapping("/findRegionByKeyword/{keyword}")
    public Result findRegionByKeyword(@PathVariable("keyword") String keyword){
        List<Region> regionList =  regionService.findRegionByKeyword(keyword);
        return Result.ok(regionList);
    }
 @ApiOperation("添加开通区域")
    @PostMapping("/save")
    public Result saveRegionWare(@RequestBody RegionWare regionWare){
        regionWareService.saveRegionWare(regionWare);
        return Result.ok(null);
    }
 @ApiOperation("查询所有仓库")
    @GetMapping("/findAllList")
    public Result allWare(){
        List<Ware> list = wareService.list();
        return Result.ok(list);
    }

接口测试:

9.4、取消与删除开通区域功能

/**
     * 取消开通区域
     * @param id
     * @param status
     */
    @Override
    public void updateStatus(Long id, Integer status) {
        RegionWare regionWare = baseMapper.selectById(id);
        regionWare.setStatus(status);
        baseMapper.updateById(regionWare);
    }
@ApiOperation("删除开通区域")
    @DeleteMapping("/remove/{id}")
    public Result removeRegionWare(@PathVariable("id") Long id){
        regionWareService.removeById(id);
        return Result.ok(null);
    }
    @ApiOperation("取消开通区域")
    @PostMapping("/updateStatus/{id}/{status}")
    public Result updateStatus(@PathVariable("id") Long id,
                               @PathVariable("status") Integer status){
        regionWareService.updateStatus(id,status);
        return Result.ok(null);
    }

十、配置nginx反向代理

在配置文件中配置

 文件内容:

server {
        listen       9001;
        server_name  192.168.200.110;

        location ~ /acl/ {
            proxy_pass http://192.168.200.1:8081;
        }

        location ~ /sys/ {
            proxy_pass http://192.168.200.1:8082;
        }
    }

修改前端:

测试效果:

十一、商品信息管理模块 

11.1、商品分类信息模块功能 

 service实现类:

/**
     * 查询商品分类列表
     * @param categoryPage 分页条件
     * @param categoryQueryVo 查询条件
     * @return
     */
    @Override
    public IPage<Category> selectCategoryPage(Page<Category> categoryPage, CategoryQueryVo categoryQueryVo) {
        LambdaQueryWrapper<Category> wrapper = new LambdaQueryWrapper<>();
        String name = categoryQueryVo.getName();
        if (!StringUtils.isEmpty(name)){
            wrapper.like(Category::getName,name);
        }
        Page<Category> category = baseMapper.selectPage(categoryPage, wrapper);
        return category;
    }

controller层 

@Api(tags = "商品信息分类管理模块")
@RestController
@RequestMapping("admin/product/category")
@CrossOrigin
public class CategoryController {
    @Autowired
    private CategoryService categoryService;

    @ApiOperation("商品分类列表")
    @GetMapping("/{page}/{limit}")
    public Result allCategoryPage(@PathVariable("page") Long page,
                                  @PathVariable("limit") Long limit,
                                  CategoryQueryVo categoryQueryVo){
        Page<Category> categoryPage = new Page<>(page,limit);
        IPage<Category> categoryIPage = categoryService.selectCategoryPage(categoryPage,categoryQueryVo);
        return Result.ok(categoryIPage);
    }
    @ApiOperation("根据id查询商品分类信息")
    @GetMapping("/get/{id}")
    public Result getById(@PathVariable("id") Long id){
        Category category = categoryService.getById(id);
        return Result.ok(category);
    }
    @ApiOperation("添加商品分类")
    @PostMapping("/save")
    public Result saveCategory(@RequestBody Category category){
        categoryService.save(category);
        return Result.ok(null);
    }
    @ApiOperation("修改商品分类")
    @PutMapping("/update")
    public Result updateById(@RequestBody Category category){
        categoryService.updateById(category);
        return Result.ok(null);
    }
    @ApiOperation("删除商品分类")
    @DeleteMapping("/remove/{id}")
    public Result removeById(@PathVariable("id") Long id){
        categoryService.removeById(id);
        return Result.ok(null);
    }
    @ApiOperation("根据id批量删除")
    @DeleteMapping("/batchRemove")
    public Result batchRemove(@RequestBody List<Long> idList){
        categoryService.removeByIds(idList);
        return Result.ok(null);
    }
    @ApiOperation("查询所有商品分类")
    @GetMapping("/findAllList")
    public Result findAllList(){
        List<Category> list = categoryService.list();
        return Result.ok(list);
    }
}

配置nginx反向代理

前端测试效果:

11.2、平台属性分组模块功能

service实现类

/**
     * 分页查询平台属性分组
     * @param groupPage
     * @param attrGroupQueryVo
     * @return
     */
    @Override
    public IPage<AttrGroup> selectAttrGroupPage(Page<AttrGroup> groupPage, AttrGroupQueryVo attrGroupQueryVo) {
        LambdaQueryWrapper<AttrGroup> wrapper = new LambdaQueryWrapper<>();
        String name = attrGroupQueryVo.getName();
        if (!StringUtils.isEmpty(name)){
            wrapper.like(AttrGroup::getName,name);
        }
        Page<AttrGroup> attrGroupPage = baseMapper.selectPage(groupPage, wrapper);
        return attrGroupPage;
    }

    /**
     * 根据id降序查询所有平台属性分组
     * @return
     */
    @Override
    public List<AttrGroup> findAllList() {
        QueryWrapper<AttrGroup> wrapper = new QueryWrapper<>();
        //根据id降序查询
        wrapper.orderByAsc("id");
        List<AttrGroup> attrGroupList = baseMapper.selectList(wrapper);
        return attrGroupList;
    }

controller层

@Api(tags = "平台属性分组模块")
@RestController
@RequestMapping("admin/product/attrGroup")
@CrossOrigin
public class AttrGroupController {
    @Autowired
    private AttrGroupService attrGroupService;
    @ApiOperation("分页查询平台属性分组")
    @GetMapping("/{page}/{limit}")
    public Result allAttrGroupPage(@PathVariable("page") Long page,
                                   @PathVariable("limit") Long limit,
                                   AttrGroupQueryVo attrGroupQueryVo){
        Page<AttrGroup> groupPage = new Page<>(page,limit);
        IPage<AttrGroup> attrGroupPage = attrGroupService.selectAttrGroupPage(groupPage,attrGroupQueryVo);
        return Result.ok(attrGroupPage);
    }
    @ApiOperation("查询所有平台属性")
    @GetMapping("/findAllList")
    public Result findAllList(){
        List<AttrGroup> list = attrGroupService.findAllList();
        return Result.ok(list);
    }
    @ApiOperation("根据id查询")
    @GetMapping("/get/{id}")
    public Result getById(@PathVariable("id") Long id){
        AttrGroup attrGroup = attrGroupService.getById(id);
        return Result.ok(attrGroup);
    }
    @ApiOperation("添加平台属性分组")
    @PostMapping("/save")
    public Result saveAttrGroup(@RequestBody AttrGroup attrGroup){
        attrGroupService.save(attrGroup);
        return Result.ok(null);
    }
    @ApiOperation("修改平台属性分组")
    @PutMapping("/update")
    public Result updateAttrGroup(@RequestBody AttrGroup attrGroup){
        attrGroupService.updateById(attrGroup);
        return Result.ok(null);
    }
    @ApiOperation("删除平台属性分组")
    @DeleteMapping("/remove/{id}")
    public Result remove(@PathVariable("id") Long id){
        attrGroupService.removeById(id);
        return Result.ok(null);
    }
    @ApiOperation("批量删除平台属性分组")
    @DeleteMapping("/batchRemove")
    public Result batchRemove(@RequestBody List<Long> idList){
        attrGroupService.removeByIds(idList);
        return Result.ok(null);
    }
}

前端效果图:

11.3、平台属性管理模块功能

service实现类

/**
     * 查询平台属性列表
     * @param groupId 平台属性分组id
     * @return
     */
    @Override
    public List<Attr> selectAttrByGroupId(Long groupId) {
        LambdaQueryWrapper<Attr> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Attr::getAttrGroupId, groupId);
        List<Attr> attrList = baseMapper.selectList(wrapper);
        return attrList;
    }

 controller层

@Api(tags = "平台属性管理模块")
@RestController
@RequestMapping("admin/product/attr")
@CrossOrigin
public class AttrController {
    @Autowired
    private AttrService attrService;

    @ApiOperation("根据平台属性分组id查询")
    @GetMapping("/{groupId}")
    public Result queryAttrByGroupId(@PathVariable("groupId") Long groupId){
        List<Attr> list = attrService.selectAttrByGroupId(groupId);
        return Result.ok(list);
    }
    @ApiOperation("根据id查询平台属性")
    @GetMapping("/get/{id}")
    public Result getById(@PathVariable("id") Long id){
        Attr attr = attrService.getById(id);
        return Result.ok(attr);
    }
    @ApiOperation("添加平台属性")
    @PostMapping("/save")
    public Result saveAttr(@RequestBody Attr attr){
        attrService.save(attr);
        return Result.ok(null);
    }
    @ApiOperation("修改平台属性")
    @PutMapping("/update")
    public Result updateAttr(@RequestBody Attr attr){
        attrService.updateById(attr);
        return Result.ok(null);
    }
    @ApiOperation("删除平台属性")
    @DeleteMapping("/remove/{id}")
    public Result removeById(@PathVariable("id") Long id){
        attrService.removeById(id);
        return Result.ok(null);
    }
    @ApiOperation("批量删除平台属性")
    @DeleteMapping("/batchRemove")
    public Result batchRemove(@RequestBody List<Long> id){
        attrService.removeByIds(id);
        return Result.ok(null);
    }
}

前端效果图:

11.4、SKU列表模块 

11.4.1、SKU信息分页条件查询功能

service实现类 

/**
     * 分页条件查询SKU信息
     * @param skuInfoPage
     * @param skuInfoQueryVo
     * @return
     */
    @Override
    public IPage<SkuInfo> selectAllSKUPage(Page<SkuInfo> skuInfoPage, SkuInfoQueryVo skuInfoQueryVo) {
        LambdaQueryWrapper<SkuInfo> wrapper = new LambdaQueryWrapper<>();
        Long categoryId = skuInfoQueryVo.getCategoryId();
        String skuType = skuInfoQueryVo.getSkuType();
        String keyword = skuInfoQueryVo.getKeyword();
        //封装查询条件
        if (!StringUtils.isEmpty(categoryId)){
            wrapper.like(SkuInfo::getCategoryId,categoryId);
        }
        if (!StringUtils.isEmpty(skuType)){
            wrapper.like(SkuInfo::getSkuType,skuType);
        }
        if (!StringUtils.isEmpty(keyword)){
            wrapper.like(SkuInfo::getSkuName,keyword);
        }
        Page<SkuInfo> skuInfoPageList = baseMapper.selectPage(skuInfoPage, wrapper);
        return skuInfoPageList;
    }

 controller层

@ApiOperation("sku信息分页条件查询")
    @GetMapping("/{page}/{limit}")
    public Result allSKUPage(@PathVariable Long page,
                             @PathVariable Long limit,
                             SkuInfoQueryVo skuInfoQueryVo){
        Page<SkuInfo> skuInfoPage = new Page<>(page,limit);
        IPage<SkuInfo> skuInfoIPage = skuInfoService.selectAllSKUPage(skuInfoPage,skuInfoQueryVo);
        return Result.ok(skuInfoIPage);
    }

前端效果图:

11.4.2、开通阿里云对象存储OSS

11.4.2.1、创建自己的Bucket

11.4.2.2、创建自己的AccessKey

11.4.3、SKU图片上传功能

导入pom依赖

<!--阿里云oss-->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.10.2</version>
        </dependency>
        <!--日期工具栏-->
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.3</version>
        </dependency>

 修改yml配置文件

aliyun:
  endpoint: 自己bucket地域节点
  keyid: 自己的AccessKey
  keysecret: 自己的AccessKey的secret
  bucketname: 自己的bucket名字

service实现类

@Service
public class FileUploadServiceImpl implements FileUploadService {
    @Value("${aliyun.endpoint}")
    private String endpoint;
    @Value("${aliyun.keyid}")
    private String accessKeyId;
    @Value("${aliyun.keysecret}")
    private String secreKey;
    @Value("${aliyun.bucketname}")
    private String bucketName;
    /**
     * 图片上传
     * @param file
     * @return
     */
    @Override
    public String fileUploads(MultipartFile file) {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint,accessKeyId,secreKey);
        try {
            //上传文件输入流
            InputStream inputStream = file.getInputStream();
            //获取文件实际名称
            String filename = file.getOriginalFilename();
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            filename = uuid + filename;
            //跟据当前时间对上传文件进行分组
            String currentDateTime = new DateTime().toString("yyyy/MM/dd");
            filename = currentDateTime +"/"+ filename;
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, filename, inputStream);
            putObjectRequest.setProcess("true");
            PutObjectResult result = ossClient.putObject(putObjectRequest);
            //文件上传成功信息
            System.out.println(result.getResponse().getStatusCode());
            //返回文件路径
            String uri = result.getResponse().getUri();
            System.out.println(uri);
            return uri;
        } catch (Exception ce) {
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return null;
    }
}

controller层

@Api(tags = "文件上传模块")
@RestController
@RequestMapping("admin/product")
@CrossOrigin
public class FileUploadController {
    @Autowired
    private FileUploadService fileUploadService;
    @ApiOperation("图片上传")
    @PostMapping("/fileUpload")
    public Result fileUpload(MultipartFile file){
        String url = fileUploadService.fileUploads(file);
        return Result.ok(url);
    }
}

11.4.4、SKU信息添加功能

service实现类

/**
     * 添加商品SKU信息
     * @param skuInfoVo
     */
    @Override
    @Transactional
    public void saveSKUInfo(SkuInfoVo skuInfoVo) {
        //保存SKU基本信息
        SkuInfo skuInfo = new SkuInfo();
        //将SkuInfoVo中的值赋值给SkuInfo
        BeanUtils.copyProperties(skuInfoVo,skuInfo);
        baseMapper.insert(skuInfo);
        //保存SKU海报信息
        List<SkuPoster> skuPosterList = skuInfoVo.getSkuPosterList();
        //判断集合是否为空
        if (!CollectionUtils.isEmpty(skuPosterList)){
            //向每个海报表里添加商品SKUid
            for (SkuPoster skuPoster : skuPosterList) {
                skuPoster.setSkuId(skuInfo.getId());
            }
            skuPosterService.saveBatch(skuPosterList);
        }
        //保存SKU图片
        List<SkuImage> skuImagesList = skuInfoVo.getSkuImagesList();
        if (!CollectionUtils.isEmpty(skuImagesList)){
            for (SkuImage skuImage : skuImagesList) {
                skuImage.setSkuId(skuInfo.getId());
            }
            skuImageService.saveBatch(skuImagesList);
        }
        //保存SKU的属性
        List<SkuAttrValue> skuAttrValueList = skuInfoVo.getSkuAttrValueList();
        if (!CollectionUtils.isEmpty(skuAttrValueList)){
            for (SkuAttrValue skuAttrValue : skuAttrValueList) {
                skuAttrValue.setSkuId(skuInfo.getId());
            }
            skuAttrValueService.saveBatch(skuAttrValueList);
        }
    }

 controller层

@ApiOperation("添加商品SKU信息")
    @PostMapping("/save")
    public Result saveSKU(@RequestBody SkuInfoVo skuInfoVo){
        skuInfoService.saveSKUInfo(skuInfoVo);
        return Result.ok(null);
    }
 11.4.4.1、文件上传时前后端端口不一致问题

前端效果:

11.4.5、SKU商品的修改与删除功能

service实现类

/**
     * 根据id查询
     * @param id
     * @return
     */
    @Override
    public SkuInfoVo selectSkuById(Long id) {
        SkuInfoVo skuInfoVo = new SkuInfoVo();
        //查询Sku基本信息
        SkuInfo skuInfo = baseMapper.selectById(id);
        //将信息拷贝到skuInfoVo
        BeanUtils.copyProperties(skuInfo,skuInfoVo);
        LambdaQueryWrapper<SkuImage> skuImageWrapper = new LambdaQueryWrapper<>();
        skuImageWrapper.eq(SkuImage::getSkuId,id);
        //查询商品图片
        List<SkuImage> skuImageList = skuImageService.list(skuImageWrapper);
        skuInfoVo.setSkuImagesList(skuImageList);
        //查询商品海报信息
        LambdaQueryWrapper<SkuPoster> skuPosterWrapper = new LambdaQueryWrapper<>();
        skuPosterWrapper.eq(SkuPoster::getSkuId,id);
        List<SkuPoster> skuPosterList = skuPosterService.list(skuPosterWrapper);
        skuInfoVo.setSkuPosterList(skuPosterList);
        //查询商品属性信息
        LambdaQueryWrapper<SkuAttrValue> skuAttrValueWrapper = new LambdaQueryWrapper<>();
        skuAttrValueWrapper.eq(SkuAttrValue::getSkuId,id);
        List<SkuAttrValue> skuAttrValueList = skuAttrValueService.list(skuAttrValueWrapper);
        skuInfoVo.setSkuAttrValueList(skuAttrValueList);
        return skuInfoVo;
    }

    /**
     * 修改SKU信息
     * @param skuInfoVo
     */
    @Override
    @Transactional
    public void updateSkuInfo(SkuInfoVo skuInfoVo) {
        //修改SKU基本信息
        SkuInfo skuInfo = new SkuInfo();
        BeanUtils.copyProperties(skuInfoVo,skuInfo);
        baseMapper.updateById(skuInfo);
        //修改海报信息
        Long skuId = skuInfoVo.getId();
        LambdaQueryWrapper<SkuPoster> skuPosterWrapper = new LambdaQueryWrapper<>();
        skuPosterWrapper.eq(SkuPoster::getSkuId,skuId);
        boolean removeSkuPoster = skuPosterService.remove(skuPosterWrapper);
        if (removeSkuPoster){
            List<SkuPoster> skuPosterList = skuInfoVo.getSkuPosterList();
            //判断集合是否为空
            if (!CollectionUtils.isEmpty(skuPosterList)){
                //向每个海报表里添加商品SKUid
                for (SkuPoster skuPoster : skuPosterList) {
                    skuPoster.setSkuId(skuId);
                }
                skuPosterService.saveBatch(skuPosterList);
            }
        }
        //修改sku商品图片
        LambdaQueryWrapper<SkuImage> skuImageWrapper = new LambdaQueryWrapper<>();
        skuImageWrapper.eq(SkuImage::getSkuId,skuId);
        boolean removeSkuImage = skuImageService.remove(skuImageWrapper);
        if (removeSkuImage){
            List<SkuImage> skuImagesList = skuInfoVo.getSkuImagesList();
            if (!CollectionUtils.isEmpty(skuImagesList)){
                for (SkuImage skuImage : skuImagesList) {
                    skuImage.setSkuId(skuInfo.getId());
                }
                skuImageService.saveBatch(skuImagesList);
            }
        }
        //修改商品属性信息
        LambdaQueryWrapper<SkuAttrValue> skuAttrValueWrapper = new LambdaQueryWrapper<>();
        skuAttrValueWrapper.eq(SkuAttrValue::getSkuId,skuId);
        boolean removeSkuAttrValue = skuAttrValueService.remove(skuAttrValueWrapper);
        if (removeSkuAttrValue){
            //保存SKU的属性
            List<SkuAttrValue> skuAttrValueList = skuInfoVo.getSkuAttrValueList();
            if (!CollectionUtils.isEmpty(skuAttrValueList)){
                for (SkuAttrValue skuAttrValue : skuAttrValueList) {
                    skuAttrValue.setSkuId(skuInfo.getId());
                }
                skuAttrValueService.saveBatch(skuAttrValueList);
            }
        }
    }

 controller层

 @ApiOperation("根据id查询商品SKU信息")
    @GetMapping("/get/{id}")
    public Result getSKUById(@PathVariable("id") Long id){
        SkuInfoVo skuInfoVo = skuInfoService.selectSkuById(id);
        return Result.ok(skuInfoVo);
    }
    @ApiOperation("修改商品SKU信息")
    @PutMapping("/update")
    public Result updateSku(@RequestBody SkuInfoVo skuInfoVo){
        skuInfoService.updateSkuInfo(skuInfoVo);
        return Result.ok(null);
    }
    @ApiOperation("删除商品SKU信息")
    @DeleteMapping("/remove/{id}")
    public Result removeSKUById(@PathVariable("id") Long id){
        skuInfoService.removeById(id);
        return Result.ok(null);
    }
    @ApiOperation("根据id批量删除")
    @DeleteMapping("/batchRemove")
    public Result batchRemove(@RequestBody List<Long> idList){
        skuInfoService.removeByIds(idList);
        return Result.ok(null);
    }

前端效果:

11.4.6、SKU商品信息的审核、上下架、新人专享功能 

service实现类

//商品审核
    @Override
    public void checkSku(Long skuId, Integer status) {
        SkuInfo skuInfo = baseMapper.selectById(skuId);
        skuInfo.setCheckStatus(status);
        baseMapper.updateById(skuInfo);
    }

    /**
     * 商品上下架
     * @param skuId
     * @param status
     */
    @Override
    public void publishSku(Long skuId, Integer status) {
        SkuInfo skuInfo = baseMapper.selectById(skuId);
        //1为上架
        if (status == 1){
            skuInfo.setPublishStatus(status);
            baseMapper.updateById(skuInfo);
            //TODO 后面整合mq将数据同步到es里面
        }else { //下架
            skuInfo.setPublishStatus(status);
            baseMapper.updateById(skuInfo);
        }
    }

    /**
     * 新人专享
     * @param skuId
     * @param status
     */
    @Override
    public void isNewPerson(Long skuId, Integer status) {
        SkuInfo skuInfo = baseMapper.selectById(skuId);
        skuInfo.setIsNewPerson(status);
        baseMapper.updateById(skuInfo);
    }

 controller层

@ApiOperation("商品审核")
    @GetMapping("/check/{skuId}/{status}")
    public Result checkSku(@PathVariable("skuId") Long skuId,
                           @PathVariable("status") Integer status){
        skuInfoService.checkSku(skuId,status);
        return Result.ok(null);
    }
    @ApiOperation("商品上下架")
    @GetMapping("/publish/{skuId}/{status}")
    public Result publishSku(@PathVariable("skuId") Long skuId,
                             @PathVariable("status") Integer status){
        skuInfoService.publishSku(skuId,status);
        return Result.ok(null);
    }
    @ApiOperation("新人专享")
    @GetMapping("/isNewPerson/{skuId}/{status}")
    public Result isNewPerson(@PathVariable("skuId") Long skuId,
                              @PathVariable("status") Integer status){
        skuInfoService.isNewPerson(skuId,status);
        return Result.ok(null);

    }

前端效果图:

十二、Nacos下载与安装 

下载地址:Releases · alibaba/nacos (github.com) 

下载完解压即可

运行命令:startup.cmd -m standalone

测试访问:

12.1、修改springboot配置文件

cloud:
  nacos:
    discovery:
      server-addr: localhost:8848

在启动类上添加注解@EnableDiscoveryClient

测试效果:

十三、整合ES+MQ实现商品上下架

功能架构图:

13.1、安装Elasticsearch

核心:提高搜索的性能 

 下载解压即可:

测试访问:

13.2、安装Kibana

 修改kibana.yml

测试访问:

13.3、整合Elasticsearch

导入pom依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

 修改yml配置文件

server:
  port: 8084
feign:
  sentinel:
    enabled: true
  client:
    config:
      default:   #配置全局的feign的调用超时时间  如果 有指定的服务配置 默认的配置不会生效
        connectTimeout: 30000 # 指定的是 消费者 连接服务提供者的连接超时时间 是否能连接  单位是毫秒
        readTimeout: 50000  # 指定的是调用服务提供者的 服务 的超时时间()  单位是毫秒
spring:
  main:
    allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
  elasticsearch:
    rest:
      uris: http://localhost:9200

修改主启动类

//exclude = DataSourceAutoConfiguration.class取消数据源自动配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class ServiceSearchApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceSearchApplication.class,args);
    }
}

接口类:

public interface SkuRepository extends ElasticsearchRepository<SkuEs,Long> {
}

13.4、完成远程调用接口功能

13.4.1、发送消息功能:

@RestController
@RequestMapping("/api/product")
public class ProductRepositoryController {
    @Autowired
    private CategoryService categoryService;
    @Autowired
    private SkuInfoService skuInfoService;
    @ApiOperation("根据id获取分类信息")
    @GetMapping("/inner/getCategory/{categoryId}")
    public Category getCategory(@PathVariable("categoryId") Long categoryId){
        Category category = categoryService.getById(categoryId);
        return category;
    }
    @ApiOperation("根据skuid查询")
    @GetMapping("/inner/getSkuInfo/{skuId}")
    public SkuInfo getSkuInfo(@PathVariable("skuId") Long skuId){
        SkuInfo skuInfo = skuInfoService.getById(skuId);
        return skuInfo;
    }
}

 13.4.2、远程调用接口

@FeignClient(value = "service-product")
public interface ProduceFeignClient {
    @GetMapping("/api/product/inner/getCategory/{categoryId}")
    public Category getCategory(@PathVariable("categoryId") Long categoryId);

    @GetMapping("/api/product/inner/getSkuInfo/{skuId}")
    public SkuInfo getSkuInfo(@PathVariable("skuId") Long skuId);
}

 13.4.3、接收消息功能:

service实现类

@Service
public class SkuServiceImpl implements SkuService {
    @Autowired
    private SkuRepository skuRepository;
    @Autowired
    private ProduceFeignClient produceFeignClient;

    //上架
    @Override
    public void upperSku(Long skuId) {
        //通过远程调用获取相关信息
        SkuInfo skuInfo = produceFeignClient.getSkuInfo(skuId);
        if (skuInfo == null){
            throw new CustomException("查询商品信息不存在",205);
        }
        Category category = produceFeignClient.getCategory(skuInfo.getCategoryId());
        SkuEs skuEs = new SkuEs();
        //封装分类
        if (category != null){
            skuEs.setCategoryId(category.getId());
            skuEs.setCategoryName(category.getName());
        }
        //封装sku信息
        skuEs.setId(skuInfo.getId());
        skuEs.setKeyword(skuInfo.getSkuName()+","+skuEs.getCategoryName());
        skuEs.setWareId(skuInfo.getWareId());
        skuEs.setIsNewPerson(skuInfo.getIsNewPerson());
        skuEs.setImgUrl(skuInfo.getImgUrl());
        skuEs.setTitle(skuInfo.getSkuName());
        if(skuInfo.getSkuType() == SkuType.COMMON.getCode()) {//普通商品
            skuEs.setSkuType(0);
            skuEs.setPrice(skuInfo.getPrice().doubleValue());
            skuEs.setStock(skuInfo.getStock());
            skuEs.setSale(skuInfo.getSale());
            skuEs.setPerLimit(skuInfo.getPerLimit());
        } else {
            //TODO 待完善-秒杀商品
        }
        //添加SkuEs
        skuRepository.save(skuEs);
    }
    //下架
    @Override
    public void lowerSku(Long skuId) {
        skuRepository.deleteById(skuId);
    }
}

controller层

@RestController
@RequestMapping("api/search/sku")
public class SkuApiController {
    @Autowired
    private SkuService skuService;
    @ApiOperation("商品上架")
    @GetMapping("/inner/upperSku/{skuId}")
    public Result upperSku(@PathVariable("skuId") Long skuId){
        skuService.upperSku(skuId);
        return Result.ok(null);
    }
    @ApiOperation("商品下架")
    @GetMapping("/inner/lowerSku/{skuId}")
    public Result lowerSku(@PathVariable("skuId") Long skuId){
        skuService.lowerSku(skuId);
        return Result.ok(null);
    }
}

13.5、在docker安装rabbitMQ

 拉取镜像:docker pull rabbitmq:3.8-management

 创建启动容器:

docker run -d --restart=always -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:3.8-management

13.6、整合rabbitmq

导入pom依赖

<!--rabbitmq-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>

编写相关配置类

@Service
public class RabbitService {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 发送消息
     * @param exchange 交换机
     * @param routingKey 路由key
     * @param message 消息
     * @return
     */
    public boolean sendMessage(String exchange,String routingKey,Object message){
       rabbitTemplate.convertAndSend(exchange, routingKey, message);
       return true;
    }
}
//消息转换器
@Configuration
public class MQConfig {
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }
}
@Component
public class MQProducerAckConfig implements RabbitTemplate.ReturnCallback,RabbitTemplate.ConfirmCallback {

    //  我们发送消息使用的是 private RabbitTemplate rabbitTemplate; 对象
    //  如果不做设置的话 当前的rabbitTemplate 与当前的配置类没有任何关系!
    @Autowired
    private RabbitTemplate rabbitTemplate;

    //  设置 表示修饰一个非静态的void方法,在服务器加载Servlet的时候运行。并且只执行一次!
    @PostConstruct
    public void init(){
        rabbitTemplate.setReturnCallback(this);
        rabbitTemplate.setConfirmCallback(this);
    }

    /**
     * 表示消息是否正确发送到了交换机上
     * @param correlationData   消息的载体
     * @param ack   判断是否发送到交换机上
     * @param cause 原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if(ack){
            System.out.println("消息发送成功!");
        }else {
            System.out.println("消息发送失败!"+cause);
        }
    }

    /**
     * 消息如果没有正确发送到队列中,则会走这个方法!如果消息被正常处理,则这个方法不会走!
     * @param message
     * @param replyCode
     * @param replyText
     * @param exchange
     * @param routingKey
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        System.out.println("消息主体: " + new String(message.getBody()));
        System.out.println("应答码: " + replyCode);
        System.out.println("描述:" + replyText);
        System.out.println("消息使用的交换器 exchange : " + exchange);
        System.out.println("消息使用的路由键 routing : " + routingKey);
    }
}
public class MqConst {
    /**
     * 消息补偿
     */
    public static final String MQ_KEY_PREFIX = "ssyx.mq:list";
    public static final int RETRY_COUNT = 3;

    /**
     * 商品上下架
     */
    public static final String EXCHANGE_GOODS_DIRECT = "ssyx.goods.direct";
    public static final String ROUTING_GOODS_UPPER = "ssyx.goods.upper";
    public static final String ROUTING_GOODS_LOWER = "ssyx.goods.lower";
    //队列
    public static final String QUEUE_GOODS_UPPER  = "ssyx.goods.upper";
    public static final String QUEUE_GOODS_LOWER  = "ssyx.goods.lower";

    /**
     * 团长上下线
     */
    public static final String EXCHANGE_LEADER_DIRECT = "ssyx.leader.direct";
    public static final String ROUTING_LEADER_UPPER = "ssyx.leader.upper";
    public static final String ROUTING_LEADER_LOWER = "ssyx.leader.lower";
    //队列
    public static final String QUEUE_LEADER_UPPER  = "ssyx.leader.upper";
    public static final String QUEUE_LEADER_LOWER  = "ssyx.leader.lower";

    //订单
    public static final String EXCHANGE_ORDER_DIRECT = "ssyx.order.direct";
    public static final String ROUTING_ROLLBACK_STOCK = "ssyx.rollback.stock";
    public static final String ROUTING_MINUS_STOCK = "ssyx.minus.stock";

    public static final String ROUTING_DELETE_CART = "ssyx.delete.cart";
    //解锁普通商品库存
    public static final String QUEUE_ROLLBACK_STOCK = "ssyx.rollback.stock";
    public static final String QUEUE_SECKILL_ROLLBACK_STOCK = "ssyx.seckill.rollback.stock";
    public static final String QUEUE_MINUS_STOCK = "ssyx.minus.stock";
    public static final String QUEUE_DELETE_CART = "ssyx.delete.cart";

    //支付
    public static final String EXCHANGE_PAY_DIRECT = "ssyx.pay.direct";
    public static final String ROUTING_PAY_SUCCESS = "ssyx.pay.success";
    public static final String QUEUE_ORDER_PAY  = "ssyx.order.pay";
    public static final String QUEUE_LEADER_BILL  = "ssyx.leader.bill";

    //取消订单
    public static final String EXCHANGE_CANCEL_ORDER_DIRECT = "ssyx.cancel.order.direct";
    public static final String ROUTING_CANCEL_ORDER = "ssyx.cancel.order";
    //延迟取消订单队列
    public static final String QUEUE_CANCEL_ORDER  = "ssyx.cancel.order";

    /**
     * 定时任务
     */
    public static final String EXCHANGE_DIRECT_TASK = "ssyx.exchange.direct.task";
    public static final String ROUTING_TASK_23 = "ssyx.task.23";
    //队列
    public static final String QUEUE_TASK_23  = "ssyx.queue.task.23";
}

13.7、完善商品上下架功能

 修改相关yml配置文件

rabbitmq:
    host: 192.168.200.110
    port: 5672
    username: guest
    password: guest
    publisher-confirm-type: CORRELATED  #发布确认模式,消息是否被成功发送到交换机
    publisher-returns: true
    listener:
      simple:
        prefetch: 1
        concurrency: 3
        acknowledge-mode: manual   #消费端手动确认

完善商品上架 

/**
     * 商品上下架
     * @param skuId
     * @param status
     */
    @Override
    public void publishSku(Long skuId, Integer status) {
        SkuInfo skuInfo = baseMapper.selectById(skuId);
        //1为上架
        if (status == 1){
            skuInfo.setPublishStatus(status);
            baseMapper.updateById(skuInfo);
            //整合mq将数据同步到es里面
            rabbitService.sendMessage(MqConst.EXCHANGE_GOODS_DIRECT,
                                      MqConst.ROUTING_GOODS_UPPER,skuId);
        }else { //下架
            skuInfo.setPublishStatus(status);
            baseMapper.updateById(skuInfo);
            //整合mq将数据同步到es里面
            rabbitService.sendMessage(MqConst.EXCHANGE_GOODS_DIRECT,
                                      MqConst.ROUTING_GOODS_LOWER,skuId);
        }
    }

完善商品删除:

@ApiOperation("删除商品SKU信息")
    @DeleteMapping("/remove/{id}")
    public Result removeSKUById(@PathVariable("id") Long id){
        skuInfoService.removeById(id);
        //删除商品同时同步到es中
        rabbitService.sendMessage(MqConst.EXCHANGE_GOODS_DIRECT,
                MqConst.ROUTING_GOODS_LOWER,id);
        return Result.ok(null);
    }
    @ApiOperation("根据id批量删除")
    @DeleteMapping("/batchRemove")
    public Result batchRemove(@RequestBody List<Long> idList){
        skuInfoService.removeByIds(idList);
        for (Long skuId : idList) {
            //删除商品同时同步到es中
            rabbitService.sendMessage(MqConst.EXCHANGE_GOODS_DIRECT,
                    MqConst.ROUTING_GOODS_LOWER,skuId);
        }
        return Result.ok(null);
    }

消息接收:

@Component
public class SkuReceiver {
    @Autowired
    private SkuService skuService;

    /**
     * 商品上架
     * @param skuId
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = MqConst.QUEUE_GOODS_UPPER,durable = "true"),
            exchange = @Exchange(value = MqConst.EXCHANGE_GOODS_DIRECT),
            key = {MqConst.ROUTING_GOODS_UPPER}))
    public void upperSku(Long skuId, Message message, Channel channel) throws IOException {
        if (!StringUtils.isEmpty(skuId)){
            //调用商品上架方法
            skuService.upperSku(skuId);
        }
        //手动确认
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }

    /**
     * 商品下架
     * @param skuId
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = MqConst.QUEUE_GOODS_LOWER,durable = "true"),
            exchange = @Exchange(value = MqConst.EXCHANGE_GOODS_DIRECT),
            key = {MqConst.ROUTING_GOODS_LOWER}))
    public void lowerSku(Long skuId, Message message, Channel channel) throws IOException {
        if (!StringUtils.isEmpty(skuId)){
            skuService.lowerSku(skuId);
        }
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }
}

测试结果:

 

点击下架或者删除都应该清空es

十四、营销活动管理

数据库表

14.1、活动列表的crud功能

service实现类:

//分页查询活动列表
    @Override
    public IPage<ActivityInfo> selectActivityInfoPage(Page<ActivityInfo> pageParam) {
        Page<ActivityInfo> activityInfoPage = baseMapper.selectPage(pageParam, null);
        //获取列表数据
        List<ActivityInfo> activityInfoList = activityInfoPage.getRecords();
        //将活动类型进行封装
        activityInfoList.stream().forEach(c-> {
            c.setActivityTypeString(c.getActivityType().getComment());
        });
        return activityInfoPage;
    }

controller层 

@Autowired
    private ActivityInfoService activityInfoService;
    @ApiOperation("分页查询活动列表")
    @GetMapping("/{page}/{limit}")
    public Result allActivityInfoPage(@PathVariable("page") Long page,
                                      @PathVariable("limit") Long limit){
        Page<ActivityInfo> pageParam = new Page<>(page,limit);
        IPage<ActivityInfo> activityInfoList = activityInfoService.selectActivityInfoPage(pageParam);
        return Result.ok(activityInfoList);
    }
    @ApiOperation("添加活动列表")
    @PostMapping("/save")
    public Result saveActivityInfo(@RequestBody ActivityInfo activityInfo){
        activityInfoService.save(activityInfo);
        return Result.ok(null);
    }
    @ApiOperation("根据id查询")
    @GetMapping("/get/{id}")
    public Result getById(@PathVariable("id") Long id){
        ActivityInfo activityInfo = activityInfoService.getById(id);
        activityInfo.setActivityTypeString(activityInfo.getActivityType().getComment());
        return Result.ok(activityInfo);
    }
    @ApiOperation("修改活动列表")
    @PutMapping("/update")
    public Result updateActivityInfo(@RequestBody ActivityInfo activityInfo){
        activityInfoService.updateById(activityInfo);
        return Result.ok(null);
    }
    @ApiOperation("删除活动列表")
    @DeleteMapping("/remove/{id}")
    public Result removeById(@PathVariable("id") Long id){
        activityInfoService.removeById(id);
        return Result.ok(null);
    }
    @ApiOperation("批量删除活动列表")
    @DeleteMapping("/batchRemove")
    public Result batchRemove(@RequestBody List<Long> idList){
        activityInfoService.removeByIds(idList);
        return Result.ok(null);
    }

测试报错:

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'activity_type' from result set.  Cause: java.lang.IllegalArgumentException: No enum constant com.cjc.ssyx.enums.ActivityType.1
    at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:92)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)
    at com.sun.proxy.$Proxy114.selectList(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:223)
    at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.executeForIPage(MybatisMapperMethod.java:122)
    at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:87)
    at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:148)
    at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
    at com.sun.proxy.$Proxy115.selectPage(Unknown Source)
    at com.cjc.ssyx.activity.service.impl.ActivityInfoServiceImpl.selectActivityInfoPage(ActivityInfoServiceImpl.java:47)
    at com.cjc.ssyx.activity.service.impl.ActivityInfoServiceImpl$$FastClassBySpringCGLIB$$1f407332.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)

解决:

页面效果:

14.2、查询活动规则功能

远程调用接口及方法

 @ApiOperation("根据skuid")
    @PostMapping ("/inner/findSkuInfoList")
    public List<SkuInfo> findSkuInfoList(@RequestBody List<Long> skuIds){
        List<SkuInfo> skuInfos = skuInfoService.listByIds(skuIds);
        return skuInfos;
    }
 @PostMapping("/api/product/inner/findSkuInfoList")
    public List<SkuInfo> findSkuInfoList(@RequestBody List<Long> skuIds);

service实现类

//根据id查询活动规则列表
    @Override
    public Map<String, Object> findActivityRuleList(Long id) {
        HashMap<String , Object> map = new HashMap<>();
        //查询活动规则表
        LambdaQueryWrapper<ActivityRule> activityRuleWrapper = new LambdaQueryWrapper<>();
        activityRuleWrapper.eq(ActivityRule::getActivityId,id);
        List<ActivityRule> activityRules = activityRuleMapper.selectList(activityRuleWrapper);
        map.put("activityRuleList",activityRules);
        //查询商品列表
        LambdaQueryWrapper<ActivitySku> activitySkuWrapper = new LambdaQueryWrapper<>();
        activitySkuWrapper.eq(ActivitySku::getActivityId,id);
        List<ActivitySku> activitySkus = activitySkuMapper.selectList(activitySkuWrapper);
        //获取所有的skuid
        List<Long> skuidList = activitySkus.stream().map(ActivitySku::getSkuId).collect(Collectors.toList());
        //远程调用查询商品
        List<SkuInfo> skuInfoList = null;
        //判断集合是否为空
        if (!CollectionUtils.isEmpty(skuidList)){
            skuInfoList = produceFeignClient.findSkuInfoList(skuidList);
            map.put("skuInfoList",skuInfoList);
        }else {
            map.put("skuInfoList",skuidList);
        }
        return map;
    }

controller层:

 @ApiOperation("根据活动id查询活动规则")
    @GetMapping("/findActivityRuleList/{id}")
    public Result findActivityRuleList(@PathVariable("id") Long id){
        Map<String ,Object> map = activityInfoService.findActivityRuleList(id);
        return Result.ok(map);
    }

14.3、添加活动规则功能

远程接口:

//根据关键字查询
    @Override
    public List<SkuInfo> findListSkuInfo(String keyword) {
        LambdaQueryWrapper<SkuInfo> wrapper = new LambdaQueryWrapper<>();
        if (!StringUtils.isEmpty(keyword)){
            wrapper.like(SkuInfo::getSkuName,keyword);
        }
        List<SkuInfo> list = baseMapper.selectList(wrapper);
        return list;
    }
 @ApiOperation("根据条件查询")
    @GetMapping("/inner/findListSkuInfo/{keyword}")
    public List<SkuInfo> findListSkuInfo(@PathVariable("keyword") String keyword){
        List<SkuInfo> list = skuInfoService.findListSkuInfo(keyword);
        return list;
    }

service实现类

//添加活动规则
    @Override
    public void saveActivityRule(ActivityRuleVo activityRuleVo) {
        Long activityId = activityRuleVo.getActivityId();
        LambdaQueryWrapper<ActivityRule> activityRuleWrapper = new LambdaQueryWrapper<>();
        //删除之前添加的营销活动规则数据
        activityRuleWrapper.eq(ActivityRule::getActivityId,activityId);
        activityRuleMapper.delete(activityRuleWrapper);
        //删除之前添加的营销活动商品数据
        LambdaQueryWrapper<ActivitySku> activitySkuWrapper = new LambdaQueryWrapper<>();
        activitySkuWrapper.eq(ActivitySku::getActivityId,activityId);
        activitySkuMapper.delete(activitySkuWrapper);
        //获取规则列表数据并添加
        List<ActivityRule> activityRuleList = activityRuleVo.getActivityRuleList();
        ActivityInfo activityInfo = baseMapper.selectById(activityId);
        //遍历添加
        activityRuleList.stream().forEach(c->{
            c.setActivityId(activityId);//活动id
            c.setActivityType(activityInfo.getActivityType());//活动类型
            activityRuleMapper.insert(c);
        });
        //获取活动商品数据并添加
        List<ActivitySku> activitySkuList = activityRuleVo.getActivitySkuList();
        activitySkuList.stream().forEach(c->{
            c.setActivityId(activityId);
            activitySkuMapper.insert(c);
        });
    }
    //关键子查询不在活动中的商品
    @Override
    public List<SkuInfo> findSkuInfoByKeyword(String keyword) {
        //远程调用查询商品信息
        List<SkuInfo> skuInfoList = produceFeignClient.findListSkuInfo(keyword);
        //判断集合是否为空
        if (CollectionUtils.isEmpty(skuInfoList)){
            return skuInfoList;
        }
        //获取所有skuId
        List<Long> skuIdList = skuInfoList.stream().map(SkuInfo::getId).collect(Collectors.toList());
        //排除正在活动中的商品
        List<Long> existSkuIdList = baseMapper.selectExistSku(skuIdList);
        //存在不在活动中的商品
        ArrayList<SkuInfo> noExistskuInfoList = new ArrayList<>();
        //比较是否已经在活动中
        skuInfoList.stream().forEach(skuInfo->{
            if (!existSkuIdList.contains(skuInfo.getId())){
                noExistskuInfoList.add(skuInfo);
            }
        });
        return noExistskuInfoList;
    }
}

controller:

@ApiOperation("添加活动规则")
    @PostMapping("/saveActivityRule")
    public Result saveActivityRule(@RequestBody ActivityRuleVo activityRuleVo){
         activityInfoService.saveActivityRule(activityRuleVo);
         return Result.ok(null);
    }
    @ApiOperation("根据关键字查询SKU信息")
    @GetMapping("/findSkuInfoByKeyword/{keyword}")
    public Result findSkuInfoByKeyword(@PathVariable("keyword") String keyword){
        List<SkuInfo> list = activityInfoService.findSkuInfoByKeyword(keyword);
        return Result.ok(list);
    }

前端效果图:

14.4、优惠卷功能 

远程接口业务代码:

 @ApiOperation("根据分类id查询")
    @PostMapping("/inner/findCategoryList")
    public List<Category> findCategoryList(@RequestBody List<Long> categoryIds){
        List<Category> categoryList = categoryService.listByIds(categoryIds);
        return categoryList;
    }

service实现类:

@Service
public class CouponInfoServiceImpl extends ServiceImpl<CouponInfoMapper, CouponInfo> implements CouponInfoService {
    @Autowired
    private CouponRangeMapper couponRangeMapper;
    @Autowired
    private ProduceFeignClient produceFeignClient;
    //分页查询优惠卷信息
    @Override
    public IPage<CouponInfo> allCouponInfoPage(Page<CouponInfo> couponInfoPage) {
        Page<CouponInfo> couponInfoIPage = baseMapper.selectPage(couponInfoPage, null);
        List<CouponInfo> couponInfos = couponInfoIPage.getRecords();
        couponInfos.stream().forEach(couponInfo -> {
            couponInfo.setCouponTypeString(couponInfo.getCouponType().getComment());
            if (!StringUtils.isEmpty(couponInfo.getRangeType())){
                couponInfo.setRangeTypeString(couponInfo.getRangeType().getComment());
            }
        });
        return couponInfoIPage;
    }
    //根据id查询
    @Override
    public CouponInfo getCouponInfoById(Long id) {
        CouponInfo couponInfo = baseMapper.selectById(id);
        if (!StringUtils.isEmpty(couponInfo)){
            couponInfo.setCouponTypeString(couponInfo.getCouponType().getComment());
            if (!StringUtils.isEmpty(couponInfo.getRangeType())){
                couponInfo.setRangeTypeString(couponInfo.getRangeType().getComment());
            }
        }
        return couponInfo;
    }
    //根据id查询优惠卷规则
    @Override
    public Map<String, Object> findCouponRuleList(Long id) {
        //查询优惠卷基本信息
        CouponInfo couponInfo = baseMapper.selectById(id);
        //根据优惠卷id查询couponRang
        LambdaQueryWrapper<CouponRange> couponRangeWrapper = new LambdaQueryWrapper<>();
        couponRangeWrapper.eq(CouponRange::getCouponId,id);
        List<CouponRange> couponRangeList = couponRangeMapper.selectList(couponRangeWrapper);
        //获取rang_id
        List<Long> rangIdList = couponRangeList.stream().map(CouponRange::getRangeId).collect(Collectors.toList());
        //根据优惠卷不同类型进行封装
        HashMap<String , Object> map = new HashMap<>();
        if (!CollectionUtils.isEmpty(rangIdList)){
            //商品分类
            if (couponInfo.getRangeType() == CouponRangeType.SKU){
                List<SkuInfo> skuInfoList = produceFeignClient.findSkuInfoList(rangIdList);
                map.put("skuInfoList",skuInfoList);
            }else if (couponInfo.getRangeType() == CouponRangeType.CATEGORY){
                //商品类型分类
                List<Category> categoryList =produceFeignClient.findCategoryList(rangIdList);
                map.put("categoryList",categoryList);
            }
        }
        return map;
    }
    //添加优惠卷规则信息
    @Override
    public void saveCouponRule(CouponRuleVo couponRuleVo) {
        //根据优惠卷id删除规则数据
        LambdaQueryWrapper<CouponRange> couponRangeWrapper = new LambdaQueryWrapper<>();
        couponRangeWrapper.eq(CouponRange::getCouponId,couponRuleVo.getCouponId());
        couponRangeMapper.delete(couponRangeWrapper);
        //更新优惠卷基本信息
        CouponInfo couponInfo = baseMapper.selectById(couponRuleVo.getCouponId());
        couponInfo.setRangeType(couponRuleVo.getRangeType());
        couponInfo.setConditionAmount(couponRuleVo.getConditionAmount());
        couponInfo.setAmount(couponRuleVo.getAmount());
        couponInfo.setRangeDesc(couponRuleVo.getRangeDesc());
        baseMapper.insert(couponInfo);
        //添加优惠卷新规则数据
        List<CouponRange> couponRangeList = couponRuleVo.getCouponRangeList();
        couponRangeList.stream().forEach(couponRange -> {
            //设置优惠卷id
            couponRange.setCouponId(couponRuleVo.getCouponId());
            couponRangeMapper.insert(couponRange);
        });
    }
}

controller层:

@ApiOperation("优惠卷分页查询")
    @GetMapping("/{page}/{limit}")
    public Result allCouponInfoPage(@PathVariable("page") Long page,
                                    @PathVariable("limit") Long limit){
        Page<CouponInfo> couponInfoPage = new Page<>(page,limit);
        IPage<CouponInfo> couponInfoIPage = couponInfoService.allCouponInfoPage(couponInfoPage);
        return Result.ok(couponInfoIPage);
    }
    @ApiOperation("添加优惠卷")
    @PostMapping("/save")
    public Result saveCouponInfo(@RequestBody CouponInfo couponInfo){
        couponInfoService.save(couponInfo);
        return Result.ok(null);
    }
    @ApiOperation("修改优惠卷")
    @PutMapping("/update")
    public Result update(@RequestBody CouponInfo couponInfo){
        couponInfoService.updateById(couponInfo);
        return Result.ok(null);
    }
    @ApiOperation("根据id删除")
    @DeleteMapping("/remove/{id}")
    public Result removeById(@PathVariable("id") Long id){
        couponInfoService.removeById(id);
        return Result.ok(null);
    }
    @ApiOperation("批量删除")
    @DeleteMapping("/batchRemove")
    public Result batchRemove(@RequestBody List<Long> idList){
        couponInfoService.removeByIds(idList);
        return Result.ok(null);
    }
    @ApiOperation("根据id查询")
    @GetMapping("/get/{id}")
    public Result getCouponInfoById(@PathVariable("id") Long id){
        CouponInfo couponInfo = couponInfoService.getCouponInfoById(id);
        return Result.ok(couponInfo);
    }
    @ApiOperation("根据id查询优惠卷规则")
    @GetMapping("findCouponRuleList/{id}")
    public Result findCouponRuleList(@PathVariable("id") Long id){
        Map<String , Object> map =couponInfoService.findCouponRuleList(id);
        return Result.ok(map);
    }
    @ApiOperation("添加优惠卷规则信息")
    @PostMapping("/saveCouponRule")
    public Result saveCouponRule(@RequestBody CouponRuleVo couponRuleVo){
        couponInfoService.saveCouponRule(couponRuleVo);
        return Result.ok(null);
    }

页面效果:

优惠卷规则:

 十五、整合Gateway网关

导入pom依赖

 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- 服务注册 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

 修改yml配置文件

server:
  port: 8000

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: service-acl
          uri: lb://service-acl
          predicates:
            - Path=/*/acl/**

        - id: service-sys
          uri: lb://service-sys
          predicates:
            - Path=/*/sys/**

        - id: service-product
          uri: lb://service-product
          predicates:
            - Path=/*/product/**

        - id: service-activity
          uri: lb://service-activity
          predicates:
            - Path=/*/activity/**

        - id: service-order
          uri: lb://service-order
          predicates:
            - Path=/*/order/**

        - id: service-payment
          uri: lb://service-payment
          predicates:
            - Path=/*/payment/**

        - id: service-user
          uri: lb://service-user
          predicates:
            - Path=/*/user/**

        - id: service-search
          uri: lb://service-search
          predicates:
            - Path=/*/search/**

        - id: service-home
          uri: lb://service-home
          predicates:
            - Path=/*/home/**

        - id: service-cart
          uri: lb://service-cart
          predicates:
            - Path=/*/cart/**

配置跨域:

@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

添加启动类添加注解:

十六、微信小程序开发环境搭建

 16.1、注册微信程序开发测试号

 地址:微信公众平台

16.2、安装微信开发者工具

地址:微信开发者工具下载地址与更新日志 | 微信开放文档 

 16.3、安装hbuilderx

下载地址:HBuilderX-高效极客技巧 

16.3.1、配置 hbuilderx

 第一步

 

16.4、开通内网穿透

地址:会员登陆 

十七、用户登录

1、调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。

2、调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台帐号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台帐号) 和 会话密钥 session_key。

3、之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。

17.1、整合JWT

17.1.1、jwt简介

JWT(Json Web Token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。

JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上

JWT最重要的作用就是对 token信息的防伪作用。

17.1.2、jwt原理

一个JWT由三个部分组成:公共部分、私有部分、签名部分。最后由这三者组合进行base64编码得到JWT。

 

 1、 公共部分

主要是该JWT的相关配置参数,比如签名的加密算法、格式类型、过期时间等等。

Key=ATGUIGU

2、 私有部分

用户自定义的内容,根据实际需要真正要封装的信息。

userInfo{用户的Id,用户的昵称nickName}

3、 签名部分

SaltiP: 当前服务器的Ip地址!{linux 中配置代理服务器的ip}

主要用户对JWT生成字符串的时候,进行加密{盐值}

最终组成 key+salt+userInfo è token!

17.1.3、springboot整合Jwt

导入pom依赖

 <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>

 编写配置类

public class JwtHelper {
    private static long tokenExpiration = 365*24*60*60*1000;
    private static String tokenSignKey = "ssyx";

    public static String createToken(Long userId, String userName) {
        String token = Jwts.builder()
                .setSubject("ssyx-USER")//分组
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))//过期时间
                .claim("userId", userId)
                .claim("userName", userName)
                .signWith(SignatureAlgorithm.HS512, tokenSignKey)//密钥
                .compressWith(CompressionCodecs.GZIP)//压缩方式
                .compact();
        return token;
    }
    //解密
    public static Long getUserId(String token) {
        if(StringUtils.isEmpty(token)) return null;

        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        Integer userId = (Integer)claims.get("userId");
        return userId.longValue();
        // return 1L;
    }

    public static String getUserName(String token) {
        if(StringUtils.isEmpty(token)) return "";

        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("userName");
    }

    public static void removeToken(String token) {
        //jwttoken无需删除,客户端扔掉即可。
    }

    public static void main(String[] args) {
        String token = JwtHelper.createToken(1L, "admin");
        System.out.println(token);
        System.out.println(JwtHelper.getUserId(token));
        System.out.println(JwtHelper.getUserName(token));
    }
}

17.2、整合redis

@Configuration
@EnableCaching
public class RedisConfig {

    // 使用默认标签做缓存
    @Bean
    public KeyGenerator wiselyKeyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    // 声明模板
    /*
    ref = 表示引用
    value = 具体的值
    <bean class="org.springframework.data.redis.core.RedisTemplate" >
        <property name="defaultSerializer" ref = "">
    </bean>
     */
    //  工具类:
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        //  将Redis 中 string ,hash 数据类型,自动序列化!
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofDays(365))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();

        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}

修改yml配置文件

redis:
    host: localhost
    port: 6379
    database: 0
    timeout: 1800000
    password:
    lettuce:
      pool:
        max-active: 20 #最大连接数
        max-wait: -1    #最大阻塞等待时间(负数表示没限制)
        max-idle: 5    #最大空闲
        min-idle: 0     #最小空闲

17.3、springboot整合微信小程序配置

修改yml配置文件

wx:
  open:
    # 小程序微信公众平台appId
    app_id: wx413e45d362013456
    # 小程序微信公众平台api秘钥
    app_secret: cc618fe7ce22a68bcb7777777

 编写工具类获取配置文件内容

@Component
public class ConstantPropertiesUtil implements InitializingBean {
    @Value("${wx.open.app_id}")
    private String appId;

    @Value("${wx.open.app_secret}")
    private String appSecret;

    public static String WX_OPEN_APP_ID;
    public static String WX_OPEN_APP_SECRET;

    @Override
    public void afterPropertiesSet() throws Exception {
        WX_OPEN_APP_ID = appId;
        WX_OPEN_APP_SECRET = appSecret;
    }
}

17.4、添加相关工具类

public class HttpClientUtils {

    public static final int connTimeout=10000;
    public static final int readTimeout=10000;
    public static final String charset="UTF-8";
    private static HttpClient client = null;

    static {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(128);
        cm.setDefaultMaxPerRoute(128);
        client = HttpClients.custom().setConnectionManager(cm).build();
    }

    public static String postParameters(String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, Exception{
        return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
    }

    public static String postParameters(String url, String parameterStr,String charset, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception{
        return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
    }

    public static String postParameters(String url, Map<String, String> params) throws ConnectTimeoutException,
            SocketTimeoutException, Exception {
        return postForm(url, params, null, connTimeout, readTimeout);
    }

    public static String postParameters(String url, Map<String, String> params, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
            SocketTimeoutException, Exception {
        return postForm(url, params, null, connTimeout, readTimeout);
    }

    public static String get(String url) throws Exception {
        return get(url, charset, null, null);
    }

    public static String get(String url, String charset) throws Exception {
        return get(url, charset, connTimeout, readTimeout);
    }

    /**
     * 发送一个 Post 请求, 使用指定的字符集编码.
     *
     * @param url
     * @param body RequestBody
     * @param mimeType 例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3
     * @param charset 编码
     * @param connTimeout 建立链接超时时间,毫秒.
     * @param readTimeout 响应超时时间,毫秒.
     * @return ResponseBody, 使用指定的字符集编码.
     * @throws ConnectTimeoutException 建立链接超时异常
     * @throws SocketTimeoutException  响应超时
     * @throws Exception
     */
    public static String post(String url, String body, String mimeType,String charset, Integer connTimeout, Integer readTimeout)
            throws ConnectTimeoutException, SocketTimeoutException, Exception {
        HttpClient client = null;
        HttpPost post = new HttpPost(url);
        String result = "";
        try {
            if (StringUtils.isNotBlank(body)) {
                HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
                post.setEntity(entity);
            }
            // 设置参数
            Builder customReqConf = RequestConfig.custom();
            if (connTimeout != null) {
                customReqConf.setConnectTimeout(connTimeout);
            }
            if (readTimeout != null) {
                customReqConf.setSocketTimeout(readTimeout);
            }
            post.setConfig(customReqConf.build());

            HttpResponse res;
            if (url.startsWith("https")) {
                // 执行 Https 请求.
                client = createSSLInsecureClient();
                res = client.execute(post);
            } else {
                // 执行 Http 请求.
                client = HttpClientUtils.client;
                res = client.execute(post);
            }
            result = IOUtils.toString(res.getEntity().getContent(), charset);
        } finally {
            post.releaseConnection();
            if (url.startsWith("https") && client != null&& client instanceof CloseableHttpClient) {
                ((CloseableHttpClient) client).close();
            }
        }
        return result;
    }

    /**
     * 提交form表单
     *
     * @param url
     * @param params
     * @param connTimeout
     * @param readTimeout
     * @return
     * @throws ConnectTimeoutException
     * @throws SocketTimeoutException
     * @throws Exception
     */
    public static String postForm(String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
            SocketTimeoutException, Exception {

        HttpClient client = null;
        HttpPost post = new HttpPost(url);
        try {
            if (params != null && !params.isEmpty()) {
                List<NameValuePair> formParams = new ArrayList<NameValuePair>();
                Set<Entry<String, String>> entrySet = params.entrySet();
                for (Entry<String, String> entry : entrySet) {
                    formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                }
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
                post.setEntity(entity);
            }

            if (headers != null && !headers.isEmpty()) {
                for (Entry<String, String> entry : headers.entrySet()) {
                    post.addHeader(entry.getKey(), entry.getValue());
                }
            }
            // 设置参数
            Builder customReqConf = RequestConfig.custom();
            if (connTimeout != null) {
                customReqConf.setConnectTimeout(connTimeout);
            }
            if (readTimeout != null) {
                customReqConf.setSocketTimeout(readTimeout);
            }
            post.setConfig(customReqConf.build());
            HttpResponse res = null;
            if (url.startsWith("https")) {
                // 执行 Https 请求.
                client = createSSLInsecureClient();
                res = client.execute(post);
            } else {
                // 执行 Http 请求.
                client = HttpClientUtils.client;
                res = client.execute(post);
            }
            return IOUtils.toString(res.getEntity().getContent(), "UTF-8");
        } finally {
            post.releaseConnection();
            if (url.startsWith("https") && client != null
                    && client instanceof CloseableHttpClient) {
                ((CloseableHttpClient) client).close();
            }
        }
    }

    /**
     * 发送一个 GET 请求
     *
     * @param url
     * @param charset
     * @param connTimeout  建立链接超时时间,毫秒.
     * @param readTimeout  响应超时时间,毫秒.
     * @return
     * @throws ConnectTimeoutException   建立链接超时
     * @throws SocketTimeoutException   响应超时
     * @throws Exception
     */
    public static String get(String url, String charset, Integer connTimeout,Integer readTimeout)
            throws ConnectTimeoutException,SocketTimeoutException, Exception {

        HttpClient client = null;
        HttpGet get = new HttpGet(url);
        String result = "";
        try {
            // 设置参数
            Builder customReqConf = RequestConfig.custom();
            if (connTimeout != null) {
                customReqConf.setConnectTimeout(connTimeout);
            }
            if (readTimeout != null) {
                customReqConf.setSocketTimeout(readTimeout);
            }
            get.setConfig(customReqConf.build());

            HttpResponse res = null;

            if (url.startsWith("https")) {
                // 执行 Https 请求.
                client = createSSLInsecureClient();
                res = client.execute(get);
            } else {
                // 执行 Http 请求.
                client = HttpClientUtils.client;
                res = client.execute(get);
            }
            result = IOUtils.toString(res.getEntity().getContent(), charset);
        } finally {
            get.releaseConnection();
            if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
                ((CloseableHttpClient) client).close();
            }
        }
        return result;
    }
    /**
     * 从 response 里获取 charset
     *
     * @param ressponse
     * @return
     */
    @SuppressWarnings("unused")
    private static String getCharsetFromResponse(HttpResponse ressponse) {
        // Content-Type:text/html; charset=GBK
        if (ressponse.getEntity() != null  && ressponse.getEntity().getContentType() != null && ressponse.getEntity().getContentType().getValue() != null) {
            String contentType = ressponse.getEntity().getContentType().getValue();
            if (contentType.contains("charset=")) {
                return contentType.substring(contentType.indexOf("charset=") + 8);
            }
        }
        return null;
    }
    /**
     * 创建 SSL连接
     * @return
     * @throws GeneralSecurityException
     */
    private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {
        try {
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException {
                    return true;
                }
            }).build();
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {

                @Override
                public boolean verify(String arg0, SSLSession arg1) {
                    return true;
                }
                @Override
                public void verify(String host, SSLSocket ssl)
                        throws IOException {
                }
                @Override
                public void verify(String host, X509Certificate cert)
                        throws SSLException {
                }
                @Override
                public void verify(String host, String[] cns,
                                   String[] subjectAlts) throws SSLException {
                }
            });
            return HttpClients.custom().setSSLSocketFactory(sslsf).build();
        } catch (GeneralSecurityException e) {
            throw e;
        }
    }
}

17.5、用户登录功能

service实现类

//根据openid查询用户
    @Override
    public User getUserByOpenid(String openid) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getOpenId,openid);
        return baseMapper.selectOne(wrapper);
    }
    //根据userid查询提货点和团长信息
    @Override
    public LeaderAddressVo getLeaderAddressByUserId(Long userId) {
        //查询用户提货记录表
        LambdaQueryWrapper<UserDelivery> userDeliveryWrapper = new LambdaQueryWrapper<>();
        //默认提货点
        userDeliveryWrapper.eq(UserDelivery::getUserId,userId).eq(UserDelivery::getIsDefault,1);
        UserDelivery userDelivery = userDeliveryService.getOne(userDeliveryWrapper);
        if (StringUtils.isEmpty(userDelivery)){
             return null;
        }
        //查询团长表
        LambdaQueryWrapper<Leader> leaderWrapper = new LambdaQueryWrapper<>();
        leaderWrapper.eq(Leader::getId,userDelivery.getLeaderId());
        Leader leader = leaderService.getOne(leaderWrapper);
        //封装数据
        LeaderAddressVo leaderAddressVo = new LeaderAddressVo();
        BeanUtils.copyProperties(leader, leaderAddressVo);
        leaderAddressVo.setUserId(userId);
        leaderAddressVo.setLeaderId(leader.getId());
        leaderAddressVo.setLeaderName(leader.getName());
        leaderAddressVo.setLeaderPhone(leader.getPhone());
        leaderAddressVo.setWareId(userDelivery.getWareId());
        leaderAddressVo.setStorePath(leader.getStorePath());
        return leaderAddressVo;
    }
    //封装用户登录信息
    @Override
    public UserLoginVo getUserLoginVo(Long userId) {
        //查询用户信息
        User user = baseMapper.selectById(userId);
        UserLoginVo userLoginVo = new UserLoginVo();
        userLoginVo.setNickName(user.getNickName());
        userLoginVo.setUserId(userId);
        userLoginVo.setPhotoUrl(user.getPhotoUrl());
        userLoginVo.setOpenId(user.getOpenId());
        userLoginVo.setIsNew(user.getIsNew());
        //查询用户提货记录表
        LambdaQueryWrapper<UserDelivery> userDeliveryWrapper = new LambdaQueryWrapper<>();
        //默认提货点
        userDeliveryWrapper.eq(UserDelivery::getUserId,userId).eq(UserDelivery::getIsDefault,1);
        UserDelivery userDelivery = userDeliveryService.getOne(userDeliveryWrapper);
        if (!StringUtils.isEmpty(userDelivery)){
           userLoginVo.setLeaderId(userDelivery.getLeaderId());
           userLoginVo.setWareId(userDelivery.getWareId());
        }else {
            userLoginVo.setLeaderId(1L);
            userLoginVo.setWareId(1L);
        }
        return userLoginVo;
    }
@ApiOperation("用户微信授权登录(获取openid)")
    @GetMapping("/wxLogin/{code}")
    public Result wxLogin(@PathVariable("code") String code){
        //获取小程序id
        String wxOpenAppId = ConstantPropertiesUtil.WX_OPEN_APP_ID;
        //获取小程序密钥
        String wxOpenAppSecret = ConstantPropertiesUtil.WX_OPEN_APP_SECRET;
        //拼接请求地址+参数
        StringBuilder url = new StringBuilder();
        url.append("https://api.weixin.qq.com/sns/jscode2session")
                .append("?appid=%s")
                .append("&secret=%s")
                .append("&js_code=%s")
                .append("&grant_type=authorization_code");
        //授权临时票据
        String tokenUrl = String.format(url.toString(), wxOpenAppId, wxOpenAppSecret, code);
        //使用HttpClient工具类发送get请求
        String result = null;
        try {
             result = HttpClientUtils.get(tokenUrl);
        } catch (Exception e) {
            throw new CustomException(ResultCodeEnum.FETCH_ACCESSTOKEN_FAILD);
        }
        //获取到返回的session_key和openid
        JSONObject jsonObject = JSONObject.parseObject(result);
        String session_key = jsonObject.getString("session_key");
        String openid = jsonObject.getString("openid");
        //查询用户表是否有该用户
        User user = userService.getUserByOpenid(openid);
        if (StringUtils.isEmpty(user)){
            user = new User();
            user.setOpenId(openid);
            user.setNickName(openid);
            user.setPhotoUrl("");
            user.setUserType(UserType.USER);
            user.setIsNew(0);
            userService.save(user);
        }
        //根据userid查询提货点和团长信息
        LeaderAddressVo leaderAddressVo = userService.getLeaderAddressByUserId(user.getId());
        //使用jwt根据用户id和姓名生成token
        String token = JwtHelper.createToken(user.getId(), user.getNickName());
        //将当前登录用户信息存入redis数据库
        UserLoginVo userLoginVo = userService.getUserLoginVo(user.getId());
        redisTemplate.opsForValue().set(RedisConst.USER_LOGIN_KEY_PREFIX+user.getId(),userLoginVo,RedisConst.USERKEY_TIMEOUT, TimeUnit.DAYS);
        //封装数据到map集合
        HashMap<String , Object> map = new HashMap<>();
        map.put("user",user);
        map.put("token",token);
        map.put("leaderAddressVo",leaderAddressVo);
        return Result.ok(map);
    }

测试结果:

十八、首页数据显示

1、微信授权登录,调用后端接口wxLogin,接口生成token,登录用户信息存入Redis,返回token

2、前端接收到微信登录接口返回的token

3、每次发送请求时候,把token放到请求头里面

4、后端从请求头获取token,从token中获取userId,根据userId获取登录的用户信息

18.1、拦截器 

 在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作,拦截是AOP的一种实现策略

18.1.1、实现步骤 

第一步,实现HandlerInterceptor接口或者它的实现子类:HandlerInterceptorAdapter

第二步,实现接口中的方法:

​    preHandle:在业务处理器处理请求之前被调用
​    postHandle:在业务处理器处理请求执行完成后
​    afterCompletion:在完全处理完请求后被调用,可用于清理资源等

第三步,**创建配置类,配置拦截器需要拦截的路径

public class AuthContextHolder {
    //用户id
    private static ThreadLocal<Long> userId = new ThreadLocal();
    //用户仓库id
    private static ThreadLocal<Long> wareId = new ThreadLocal();
    //用户信息对象
    private static ThreadLocal<UserLoginVo>  userLoginVo= new ThreadLocal();

    public static void setUserId(Long id){
        userId.set(id);
    }
    public static Long getUserId(){
        return userId.get();
    }
    public static void setWareId(Long id){
        wareId.set(id);
    }
    public static Long getWareId(){
        return wareId.get();
    }
    public static void setUserLoginVo(UserLoginVo userLoginVos){
        userLoginVo.set(userLoginVos);
    }
    public static UserLoginVo getUserLoginVo(){
        return userLoginVo.get();
    }
}
public class UserLoginInterceptor implements HandlerInterceptor {

    private RedisTemplate redisTemplate;
    public UserLoginInterceptor(RedisTemplate redisTemplate){
        this.redisTemplate = redisTemplate;
    }
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        this.getUserLogin(request);
        return true;
    }

    private void getUserLogin(HttpServletRequest request) {
        //从请求头里获取token
        String token = request.getHeader("token");
        if (!StringUtils.isEmpty(token)){
            //从token里获取用户id
            Long userId = JwtHelper.getUserId(token);
            //使用用户id查询redis库
            UserLoginVo userLoginVo = (UserLoginVo) redisTemplate.opsForValue().get(RedisConst.USER_LOGIN_KEY_PREFIX + userId);
            if(!StringUtils.isEmpty(userLoginVo)){
                AuthContextHolder.setUserId(userId);
                AuthContextHolder.setWareId(userLoginVo.getWareId());
                AuthContextHolder.setUserLoginVo(userLoginVo);
            }
        }
    }
}
@Configuration
public class LoginMvcConfigurerAdapter extends WebMvcConfigurationSupport {
    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserLoginInterceptor(redisTemplate))
                .addPathPatterns("/api/**")//需要拦截的路径
                .excludePathPatterns("/api/user/weixin/wxLogin/*");//不需要拦截的路径
        super.addInterceptors(registry);
    }
}
@PostMapping("/auth/updateUser")
    @ApiOperation(value = "更新用户昵称与头像")
    public Result updateUser(@RequestBody User user) {
        User user1 = userService.getById(AuthContextHolder.getUserId());
        //把昵称更新为微信用户
        user1.setNickName(user.getNickName().replaceAll("[ue000-uefff]", "*"));
        user1.setPhotoUrl(user.getPhotoUrl());
        userService.updateById(user1);
        return Result.ok(null);
    }

18.2、首页数据显示 

 远程接口

 @Autowired
    private UserService userService;
    @ApiOperation("查询提货点和团长信息")
    @GetMapping("/inner/getLeaderAddressVo/{userId}")
    public LeaderAddressVo getLeaderAddressVo(@PathVariable("userId") Long userId){
        LeaderAddressVo leaderAddressByUserId = userService.getLeaderAddressByUserId(userId);
        return leaderAddressByUserId;
    }
//查询新人专享商品
    @Override
    public List<SkuInfo> findNewPersonSkuInfoList() {
        LambdaQueryWrapper<SkuInfo> wrapper = new LambdaQueryWrapper<>();
        //上架并是新人专享
        wrapper.eq(SkuInfo::getIsNewPerson,1).eq(SkuInfo::getPublishStatus,1);
        wrapper.orderByDesc(SkuInfo::getSkuCode);
        //新人专享首页显示3条
        Page<SkuInfo> page = new Page<>(1,3);
        Page<SkuInfo> skuInfoPage = baseMapper.selectPage(page, wrapper);
        List<SkuInfo> records = skuInfoPage.getRecords();
        return records;
    }
//查询爆款商品
    @Override
    public List<SkuEs> findHotSkuList() {
        Pageable pageable = PageRequest.of(0, 10);
        //关联条件
        Page<SkuEs> skuEsPage = skuRepository.findByOrderByHotScoreDesc(pageable);
        //获取数据
        List<SkuEs> content = skuEsPage.getContent();
        return content;
    }

service实现类

 @Autowired
    private UserFeignClient userFeignClient;
    @Autowired
    private ProduceFeignClient produceFeignClient;
    @Autowired
    private SkuFeignClient skuFeignClient;
    //首页数据显示
    @Override
    public Map<String, Object> homeDate(Long userId) {
        HashMap<String , Object> map = new HashMap<>();
        //根据用户id查询当前用户的提货地址,远程调用查询
        LeaderAddressVo leaderAddressVo = userFeignClient.getLeaderAddressVo(userId);
        map.put("leaderAddressVo",leaderAddressVo);
        //远程调用查询所有商品分类
        List<Category> allCategory = produceFeignClient.findAllCategory();
        map.put("categoryList",allCategory);
        //远程调用查询新人专享商品
        List<SkuInfo> newPersonSkuInfoList = produceFeignClient.findNewPersonSkuInfoList();
        map.put("newPersonSkuInfoList",newPersonSkuInfoList);
        //远程调用查询爆款商品,根据热门评分降序排序
        List<SkuEs> hotSkuList = skuFeignClient.findHotSkuList();
        map.put("hotSkuList",hotSkuList);
        return map;
    }

controller

@ApiOperation("首页数据显示")
    @GetMapping("/index")
    public Result index(HttpServletRequest request){
        Long userId = AuthContextHolder.getUserId();
        Map<String ,Object> map = homeService.homeDate(userId);
        return Result.ok(map);
    }

页面效果 

 

 十九、商品分类与搜索

 @ApiOperation("远程调用查询商品分类")
    @GetMapping("/category")
    public Result categoryList(){
        List<Category> allCategory = produceFeignClient.findAllCategory();
        return Result.ok(allCategory);
    }
//查询分类的商品信息
    @Override
    public Page<SkuEs> search(Pageable pageable, SkuEsQueryVo skuEsQueryVo) {
        Page<SkuEs> skuEsPage = null;
        //设置当前用户登录的id
        skuEsQueryVo.setWareId(AuthContextHolder.getWareId());
        String keyword = skuEsQueryVo.getKeyword();
        //使用springData根据条件进行查询
        if (StringUtils.isEmpty(keyword)){
            skuEsPage = skuRepository.findByCategoryIdAndWareId(skuEsQueryVo.getCategoryId(),
                    skuEsQueryVo.getWareId(),pageable);

        }else{
            //搜索关键字不为空
            skuEsPage =  skuRepository.findByKeywordAndWareId(keyword,skuEsQueryVo.getWareId(),pageable);
        }
        //查询商品是否为优惠活动商品
        List<SkuEs> skuEsList = skuEsPage.getContent();
        if (!CollectionUtils.isEmpty(skuEsList)){
            List<Long> skuIdList = skuEsList.stream().map(SkuEs::getId).collect(Collectors.toList());
            //map存储一个商品可以参加一个活动与对应的多个规则
            //远程调用查询
            Map<Long, List<String>> map = activityFeignClient.findActivityInfo(skuIdList);
            //封装数据到ruleList中
            if (!CollectionUtils.isEmpty(map)){
                skuEsList.forEach(skuEs -> {
                    skuEs.setRuleList(map.get(skuEs.getId()));
                });
            }
        }
        return skuEsPage;
    }
//根据skuId查询促销信息
    @Override
    public Map<Long, List<String>> findActivityInfo(List<Long> idList) {
        Map<Long, List<String>> map = new HashMap<>();
        idList.forEach(skuId->{
            //根据skuId查询对应的活动规则表
            List<ActivityRule> activityRules = baseMapper.findActivityRule(skuId);
            if (!CollectionUtils.isEmpty(activityRules)){
                List<String> ruleList = new ArrayList<>();
                //封装规则名称
                activityRules.stream().forEach(activityRule -> {
                    ruleList.add(this.getRuleDesc(activityRule));
                });
                map.put(skuId,ruleList);
            }
        });
        return map;
    }
    //构造规则名称的方法
    private String getRuleDesc(ActivityRule activityRule) {
        ActivityType activityType = activityRule.getActivityType();
        StringBuffer ruleDesc = new StringBuffer();
        if (activityType == ActivityType.FULL_REDUCTION) {
            ruleDesc
                    .append("满")
                    .append(activityRule.getConditionAmount())
                    .append("元减")
                    .append(activityRule.getBenefitAmount())
                    .append("元");
        } else {
            ruleDesc
                    .append("满")
                    .append(activityRule.getConditionNum())
                    .append("元打")
                    .append(activityRule.getBenefitDiscount())
                    .append("折");
        }
        return ruleDesc.toString();
    }
<resultMap id="activityRuleMap" type="com.cjc.ssyx.model.activity.ActivityRule" autoMapping="true"></resultMap>
<select id="findActivityRule" resultMap="activityRuleMap">
        SELECT info.activity_type as activityType,
               rule.id,rule.activity_id,rule.condition_amount,rule.condition_num,rule.benefit_amount,rule.benefit_discount
        FROM activity_info info
        INNER JOIN activity_sku sku
        ON info.id = sku.activity_id
        INNER JOIN activity_rule rule
        ON info.id = rule.activity_id
        WHERE sku.sku_id = #{skuId}
        AND NOW()
        BETWEEN info.start_time AND info.end_time
        ORDER BY rule.condition_amount DESC
    </select>
 @ApiOperation("根据skuId查询促销信息")
    @PostMapping("/inner/findActivityInfo")
    public Map<Long, List<String>> findActivityInfo(@RequestBody List<Long> idList){
        return  activityInfoService.findActivityInfo(idList);
    }
@ApiOperation("查询分类商品")
    @PostMapping("/{page}/{limit}")
    public Result skuList(@PathVariable("page") Integer page,
                          @PathVariable("limit") Integer limit,
                          SkuEsQueryVo skuEsQueryVo){
        //page-1,0表示第一页
        Pageable pageable = PageRequest.of(page-1, limit);
        Page<SkuEs> skuEsPage = skuService.search(pageable,skuEsQueryVo);
        return Result.ok(skuEsPage);
    }

页面效果:

二十、商品详情 

核心:1、查询商品的基本信息(远程调用), 2、查询相关优惠活动信息(远程调用),3、更新商品热度(远程调用)

优化:使用多线程优化远程调用 (技术点)使用CompletableFuture类

创建线程方式:1、继承Thread类,2、实现Runnable接口,3、实现Callable接口,4、使用线程池。

20.1、CompletableFuture的使用测试

20.1.1、创建异步对象

没有返回值

public class FutureTest {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(4);
        System.out.println("主线程开始执行");
        //runAsync()没有返回值,异步执行
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
            System.out.println("当前执行的线程:"+Thread.currentThread().getName());
            int count = 100;
            System.out.println(count);
        }, threadPool);
        System.out.println("主线程结束执行");
    }
}

 有返回值

public class FutureTest2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(4);
        System.out.println("主线程开始执行");
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前执行的线程:"+Thread.currentThread().getName());
            int count = 100;
            System.out.println(count);
            return count;
        }, threadPool);
        Integer integer = completableFuture.get();
        System.out.println(integer);
        System.out.println("主线程结束执行");
    }
}

 20.1.2、计数完成回调

public class FutureTest3 {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(4);
        System.out.println("主线程开始执行");
        //whenComplete()计数完成回调
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前执行的线程:"+Thread.currentThread().getName());
            int count = 100;
            System.out.println(count);
            return count;
        }, threadPool).whenComplete((rs,ex)->{
            System.out.println("whenComplete:"+rs);
            System.out.println("exception:"+ex);
        });
        System.out.println("主线程结束执行");
    }
}

 20.1.3、串行化

public class FutureTest4 {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(4);
        System.out.println("主线程开始执行");
        //任务1
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前执行的线程:"+Thread.currentThread().getName());
            int count = 100;
            System.out.println("任务1:"+count);
            return count;
        }, threadPool);
        //任务2,获取任务1结果并返回
        completableFuture.thenApplyAsync((res)->{
            System.out.println("任务2:"+res);
            return res;
        },threadPool);
        //任务3:继续执行
        completableFuture.thenRunAsync(()->{
            System.out.println("任务3");
        },threadPool);
    }
}

20.1.4、多任务组合

public class FutureTest5 {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(4);
        System.out.println("主线程开始执行");
        //任务1
        CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前执行的线程:"+Thread.currentThread().getName());
            int count = 100;
            System.out.println("任务1:"+count);
            return count;
        }, threadPool);
        //任务2
        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前执行的线程:"+Thread.currentThread().getName());
            int count = 200;
            System.out.println("任务2:"+count);
            return count;
        }, threadPool);
         CompletableFuture.allOf(completableFuture1, completableFuture2).join();
        System.out.println("主线程结束执行");
    }
}

20.2、商品详情功能实现

 自定义线程池

@Configuration
public class ThreadPoolConfig {
    //自定义线程池
    @Bean
    public ThreadPoolExecutor threadPoolExecutor(){
        return new ThreadPoolExecutor(2,5,2,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
    }
}

查询商品sku信息 

@ApiOperation("查询商品sku信息")
    @GetMapping("/inner/getSkuInfoVo/{skuId}")
    public SkuInfoVo getSkuInfoVo(@PathVariable("skuId") Long skuId){
        return skuInfoService.selectSkuById(skuId);
    }

查询商品相关优惠卷信息

 //根据skuId查询营销活动和优惠卷信息
    @Override
    public Map<String ,Object> findActivityAndCoupon(Long skuId, Long userId) {
        //查询sku的营销活动
        Map<String, Object> activityRuleList = this.findActivityRule(skuId);
        //查询优惠卷信息
        List<CouponInfo> couponInfoList = couponInfoService.findCouponInfoList(skuId,userId);
        //封装数据
        Map<String ,Object> map = new HashMap<>();
        map.putAll(activityRuleList);
        map.put("couponInfoList",couponInfoList);
        return map;
    }

    @Override
    public Map<String, Object> findActivityRule(Long skuId) {
        Map<String , Object> map = new HashMap<>();
        List<ActivityRule> activityRules = baseMapper.findActivityRule(skuId);
        activityRules.stream().forEach(activityRule -> {
            String ruleDesc = this.getRuleDesc(activityRule);
            activityRule.setRuleDesc(ruleDesc);
        });
        map.put("activityRuleList",activityRules);
        return map;
    }
<select id="selectCouponInfoList" resultType="com.cjc.ssyx.model.activity.CouponInfo">
        SELECT info.id,info.coupon_type,info.coupon_name,info.amount,
               info.condition_amount,info.start_time,info.end_time,
               info.range_type,info.range_desc,info.publish_count,info.per_limit,
               info.use_count,info.receive_count,info.expire_time,info.publish_status,
               info.create_time,info.update_time,info.is_deleted,cuse.coupon_status
        FROM coupon_info info
        LEFT JOIN coupon_range crange
        ON info.id = crange.coupon_id
        LEFT JOIN coupon_use cuse ON  info.id = cuse.coupon_id
        AND cuse.user_id = #{userId}
        WHERE (info.range_type = 1
        OR (info.range_type = 2 AND crange.range_id = #{skuId})
        OR (info.range_type = 3 AND crange.range_id = #{categoryId}))
        AND NOW() BETWEEN info.start_time AND info.end_time
        ORDER BY info.amount DESC
    </select>

更新商品热度 

//更新商品热度
    @Override
    public Boolean updateHotScore(Long skuId) {
        String key = "hotScore";
        //在redis中存储数据一次就加一
        Double hotScore = redisTemplate.opsForZSet().incrementScore(key, "skuId" + skuId, 1);
        //redis中修改十次,es更新一次
        if (hotScore % 10 == 0){
            Optional<SkuEs> optional = skuRepository.findById(skuId);
            SkuEs skuEs = optional.get();
            skuEs.setHotScore(Math.round(hotScore));
            skuRepository.save(skuEs);
        }
        return true;
    }
//商品详情查询
    @Override
    public Map<String, Object> index(Long skuId, Long userId) {
        HashMap<String , Object> map = new HashMap<>();
        ThreadPoolExecutor threadPoolExecutor = threadPoolConfig.threadPoolExecutor();
        CompletableFuture<Void> skuInfoVoFuture = CompletableFuture.runAsync(() -> {
            //远程调用查询商品信息
            SkuInfoVo skuInfoVo = produceFeignClient.getSkuInfoVo(skuId);
            map.put("skuInfoVo",skuInfoVo);
        },threadPoolExecutor);
        CompletableFuture<Void> activityAndCouponFuture = CompletableFuture.runAsync(() -> {
            //远程调用查询商品能使用的优惠卷信息
            Map<String, Object> activityAndCoupon = activityFeignClient.findActivityAndCoupon(skuId, userId);
            map.putAll(activityAndCoupon);
        }, threadPoolExecutor);
        CompletableFuture<Void> HotScoreFuture = CompletableFuture.runAsync(() -> {
            //远程调用更新商品热度
            skuFeignClient.updateHotScore(skuId);
        }, threadPoolExecutor);
        CompletableFuture.allOf(skuInfoVoFuture,activityAndCouponFuture,HotScoreFuture).join();
        return map;
    }
 @ApiOperation("商品详情信息")
    @GetMapping("/item/{id}")
    public Result index(@PathVariable("id") Long id){
        Long userId = AuthContextHolder.getUserId();
        Map<String , Object> map = itemService.index(id,userId);
        return Result.ok(map);
    }

二十一、购物车功能

21.1、添加购物车功能

    //添加商品到购物车
    @Override
    public void addToCart(Long userId, Long skuId, Integer skuNum) {
        //根据key查询redis中是否有数据
        String cartKey = this.getCartKey(userId);
        BoundHashOperations<String ,String , CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);
        //判断key对应的里面是否有skuid,有就更新
        CartInfo cartInfo = null;
        if (hashOps.hasKey(skuId.toString())){
            //根据skuid获取对应的数量
             cartInfo = hashOps.get(skuId.toString());
            //更新购物车的商品数量
            int newSkuNum = cartInfo.getSkuNum() + skuNum;
            if (newSkuNum < 1){
                return;
            }
            //更新CartInfo
            cartInfo.setSkuNum(newSkuNum);
            cartInfo.setCurrentBuyNum(newSkuNum);
            //选中加入购物车的数量不能大于限购数
            if (newSkuNum > cartInfo.getPerLimit()){
                throw new CustomException(ResultCodeEnum.SKU_LIMIT_ERROR);
            }
            //更新是否选中,默认选中
            cartInfo.setIsChecked(1);
            cartInfo.setUpdateTime(new Date());
        }else {
            //第一次添加商品到购物车
            skuNum = 1;

            //远程调用查询sku的基本信息
            SkuInfo skuInfo = produceFeignClient.getSkuInfo(skuId);
            if (StringUtils.isEmpty(skuInfo)){
                throw new CustomException(ResultCodeEnum.DATA_ERROR);
            }
            //封装数据
            cartInfo = new CartInfo();
            cartInfo.setSkuId(skuId);
            cartInfo.setCategoryId(skuInfo.getCategoryId());
            cartInfo.setSkuType(skuInfo.getSkuType());
            cartInfo.setIsNewPerson(skuInfo.getIsNewPerson());
            cartInfo.setUserId(userId);
            cartInfo.setCartPrice(skuInfo.getPrice());
            cartInfo.setSkuNum(skuNum);
            cartInfo.setCurrentBuyNum(skuNum);
            cartInfo.setSkuType(SkuType.COMMON.getCode());
            cartInfo.setPerLimit(skuInfo.getPerLimit());
            cartInfo.setImgUrl(skuInfo.getImgUrl());
            cartInfo.setSkuName(skuInfo.getSkuName());
            cartInfo.setWareId(skuInfo.getWareId());
            cartInfo.setIsChecked(1);
            cartInfo.setStatus(1);
            cartInfo.setCreateTime(new Date());
            cartInfo.setUpdateTime(new Date());
        }
        //更新redis的缓存数据
        hashOps.put(skuId.toString(),cartInfo);
        //设置过期时间
        setCartKey(cartKey);
    }
    //获取购物车的key
    private String getCartKey(Long userId){
        //user:userId :cart
        return RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX;
    }
    //设置key过期时间规则
    private void setCartKey(String key){
        redisTemplate.expire(key, RedisConst.USER_CART_EXPIRE, TimeUnit.SECONDS);
    }
 //获取购物车的key
    private String getCartKey(Long userId){
        //user:userId :cart
        return RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX;
    }
    //设置key过期时间规则
    private void setCartKey(String key){
        redisTemplate.expire(key, RedisConst.USER_CART_EXPIRE, TimeUnit.SECONDS);
    }

22.2、删除购物车列表

  //根据skuId删除购物车商品
    @Override
    public void deleteCart(Long userId, Long skuId) {
        BoundHashOperations<String ,String ,CartInfo> operations = redisTemplate.boundHashOps(getCartKey(userId));
        if (operations.hasKey(skuId.toString())){
            operations.delete(skuId.toString());
        }
    }
    //清空购物车
    @Override
    public void deleteAllCart(Long userId) {
        BoundHashOperations<String ,String ,CartInfo> operations = redisTemplate.boundHashOps(getCartKey(userId));
        List<CartInfo> cartInfos = operations.values();
        cartInfos.stream().forEach(cartInfo -> {
            operations.delete(cartInfo.getSkuId().toString());
        });
    }
    //skuId批量删除购物车商品
    @Override
    public void batchDeleteCart(Long userId, List<Long> skuIds) {
        BoundHashOperations<String ,String ,CartInfo> operations = redisTemplate.boundHashOps(getCartKey(userId));
        skuIds.forEach(skuId->{
            if (operations.hasKey(skuId.toString())){
                operations.delete(skuId.toString());
            }
        });

    }

22.3、查询带优惠卷的购物车列表

//查询购物车列表
    @Override
    public List<CartInfo> cartList(Long userId) {
        List<CartInfo> cartInfoList = null;
        if (StringUtils.isEmpty(userId)){
            return cartInfoList;
        }
        BoundHashOperations<String ,String ,CartInfo> operations = redisTemplate.boundHashOps(getCartKey(userId));
         cartInfoList = operations.values();
         if (!CollectionUtils.isEmpty(cartInfoList)){
             //根据商品添加时间,降序
             cartInfoList.sort(new Comparator<CartInfo>() {
                 @Override
                 public int compare(CartInfo o1, CartInfo o2) {
                     return o1.getCreateTime().compareTo(o2.getCreateTime());
                 }
             });
         }
        return cartInfoList;
    }
    //查询带优惠卷的购物车列表
    @Override
    public OrderConfirmVo activityCartList(Long userId,List<CartInfo> cartInfos) {
        OrderConfirmVo cartActivityAndCoupon = activityFeignClient.findCartActivityAndCoupon(userId, cartInfos);
        return cartActivityAndCoupon;
    }
    //查询购物车中满足条件的优惠卷与活动
    @Override
    public OrderConfirmVo findCartActivityAndCoupon(Long userId, List<CartInfo> cartInfos) {
        //查询购物车的商品参与的活动,根据活动规则进行分组
        List<CartInfoVo> carInfoVoList = findCartActivityList(cartInfos);
        //计算参与活动之后的金额
        BigDecimal activityReduceAmount = carInfoVoList.stream()
                .filter(cartInfoVo -> cartInfoVo.getActivityRule() != null)
                .map(cartInfoVo -> cartInfoVo.getActivityRule().getReduceAmount())
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        //获取购物车可以使用优惠卷金额
        List<CouponInfo> couponInfoList = couponInfoService.findCouponInfo(userId,cartInfos);
        //计算商品使用优惠卷的金额,一次只能使用一张优惠卷
        BigDecimal couponReduceAmount = new BigDecimal(0);
        if (!CollectionUtils.isEmpty(couponInfoList)){
             couponReduceAmount = couponInfoList.stream()
                     .filter(couponInfo -> couponInfo.getIsOptimal().intValue() == 1)
                    .map(couponInfo -> couponInfo.getAmount())
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
        }
        //计算没有参与活动与优惠卷原始金额
        BigDecimal originalTotalAmount = cartInfos.stream()
                .filter(cartInfo -> cartInfo.getIsChecked().intValue() == 1)
                .map(cartInfo -> cartInfo.getCartPrice().multiply(new BigDecimal(cartInfo.getSkuNum())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        //最终总金额
        BigDecimal totalAmount = originalTotalAmount
                .subtract(activityReduceAmount).subtract(couponReduceAmount);
        OrderConfirmVo confirmVo = new OrderConfirmVo();
        confirmVo.setCarInfoVoList(carInfoVoList);
        confirmVo.setActivityReduceAmount(activityReduceAmount);
        confirmVo.setCouponInfoList(couponInfoList);
        confirmVo.setCouponReduceAmount(couponReduceAmount);
        confirmVo.setOriginalTotalAmount(originalTotalAmount);
        confirmVo.setTotalAmount(totalAmount);
        return confirmVo;
    }
    //查询购物车中对应的商品规则
    public List<CartInfoVo> findCartActivityList(List<CartInfo> cartInfos){
        //返回的数据类型
        List<CartInfoVo> cartInfoVoList = new ArrayList<>();
        //获取所有商品skuid
        List<Long> skuIdList = cartInfos.stream().map(CartInfo::getSkuId).collect(Collectors.toList());
        //根据skuid查询参与对应活动
        List<ActivitySku> activitySkuList = new ArrayList<>();
        Map<Long, Set<Long>> activityIdToSkuIdListMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(skuIdList)){
            activitySkuList = baseMapper.selectActivityRule(skuIdList);
            //根据活动进行分组
            //key是活动id,value对应的商品skuid
             activityIdToSkuIdListMap = activitySkuList.stream()
                    .collect(Collectors.groupingBy(ActivitySku::getActivityId,
                            Collectors.mapping(ActivitySku::getSkuId, Collectors.toSet())));

        }
        //获取所有的活动id
        Set<Long>  activityIdSet = activitySkuList.stream().map(ActivitySku::getActivityId)
                .collect(Collectors.toSet());
        //获取活动对应的规则
        Map<Long, List<ActivityRule>> activityIdToActivityRuleListMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(activityIdSet)){
            LambdaQueryWrapper<ActivityRule> wrapper = new LambdaQueryWrapper<>();
            wrapper.in(ActivityRule::getActivityId,activityIdSet);
            wrapper.orderByAsc(ActivityRule::getConditionAmount,ActivityRule::getConditionNum);
            List<ActivityRule> activityRuleList = activityRuleMapper.selectList(wrapper);
            //根据活动id进行分组
            activityIdToActivityRuleListMap = activityRuleList.stream().collect(Collectors.groupingBy(activityRule -> activityRule.getActivityId()));
            //参加活动的商品
            Set<Long> activitySkuIdSet = new HashSet<>();
            if (!CollectionUtils.isEmpty(activityIdToSkuIdListMap)){
                //遍历map集合
                Iterator<Map.Entry<Long, Set<Long>>> iterator = activityIdToSkuIdListMap.entrySet().iterator();
                while (iterator.hasNext()){
                    Map.Entry<Long, Set<Long>> next = iterator.next();
                    //map的key:活动id
                    Long activityId = next.getKey();
                    //value:活动的skuid
                    Set<Long> currentActivitySkuIdSet = next.getValue();
                    //获取当前活动对应的购物项
                    List<CartInfo> currentActivityCartInfoList = cartInfos.stream().filter(cartInfo -> currentActivitySkuIdSet.contains(cartInfo.getSkuId())).collect(Collectors.toList());
                    //计数购物项的总金额与总数量
                    BigDecimal activityTotalAmount = computeTotalAmount(currentActivityCartInfoList);
                    int activityTotalNum = computeCartNum(currentActivityCartInfoList);
                    //计算活动对应规则
                    List<ActivityRule> currentActivityRuleList = activityIdToActivityRuleListMap.get(activityId);
                    ActivityType activityType = currentActivityRuleList.get(0).getActivityType();
                    //判断活动类型
                    ActivityRule optimalActivityRule;
                    if (activityType == ActivityType.FULL_REDUCTION){ //满减:满100减10
                        optimalActivityRule = computeFullReduction(activityTotalAmount, currentActivityRuleList);
                    }else { //折扣
                        optimalActivityRule = computeFullDiscount(activityTotalNum, activityTotalAmount, currentActivityRuleList);
                    }
                    //封装集合
                    CartInfoVo cartInfoVo = new CartInfoVo();
                    cartInfoVo.setActivityRule(optimalActivityRule);
                    cartInfoVo.setCartInfoList(currentActivityCartInfoList);
                    cartInfoVoList.add(cartInfoVo);
                    //记录参加活动的商品
                    activitySkuIdSet.addAll(currentActivitySkuIdSet);
                }
            }
           //未参加活动的商品
            //移除已经参加活动的skuid
            skuIdList.removeAll(activitySkuIdSet);
            if (!CollectionUtils.isEmpty(skuIdList)){
                //获取对应的购物项
                Map<Long, CartInfo> skuIdToCartInfoMap = cartInfos.stream()
                        .collect((Collectors.toMap(CartInfo::getSkuId, CartInfo -> CartInfo)));

                skuIdList.forEach(skuId->{
                    CartInfoVo cartInfoVo = new CartInfoVo();
                    cartInfoVo.setActivityRule(null);
                    CartInfo cartInfo = skuIdToCartInfoMap.get(skuId);
                    List<CartInfo> cartInfoList = new ArrayList<>();
                    cartInfoList.add(cartInfo);
                    cartInfoVo.setCartInfoList(cartInfoList);
                    cartInfoVoList.add(cartInfoVo);
                });
            }
        }
        return cartInfoVoList;
    }
      /**
       * 计算满量打折最优规则
       * @param totalNum
       * @param activityRuleList //该活动规则skuActivityRuleList数据,已经按照优惠折扣从大到小排序了
       */
      private ActivityRule computeFullDiscount(Integer totalNum, BigDecimal totalAmount, List<ActivityRule> activityRuleList) {
          ActivityRule optimalActivityRule = null;
          //该活动规则skuActivityRuleList数据,已经按照优惠金额从大到小排序了
          for (ActivityRule activityRule : activityRuleList) {
              //如果订单项购买个数大于等于满减件数,则优化打折
              if (totalNum.intValue() >= activityRule.getConditionNum()) {
                  BigDecimal skuDiscountTotalAmount = totalAmount.multiply(activityRule.getBenefitDiscount().divide(new BigDecimal("10")));
                  BigDecimal reduceAmount = totalAmount.subtract(skuDiscountTotalAmount);
                  activityRule.setReduceAmount(reduceAmount);
                  optimalActivityRule = activityRule;
                  break;
              }
          }
          if(null == optimalActivityRule) {
              //如果没有满足条件的取最小满足条件的一项
              optimalActivityRule = activityRuleList.get(activityRuleList.size()-1);
              optimalActivityRule.setReduceAmount(new BigDecimal("0"));
              optimalActivityRule.setSelectType(1);

              StringBuffer ruleDesc = new StringBuffer()
                      .append("满")
                      .append(optimalActivityRule.getConditionNum())
                      .append("元打")
                      .append(optimalActivityRule.getBenefitDiscount())
                      .append("折,还差")
                      .append(totalNum-optimalActivityRule.getConditionNum())
                      .append("件");
              optimalActivityRule.setRuleDesc(ruleDesc.toString());
          } else {
              StringBuffer ruleDesc = new StringBuffer()
                      .append("满")
                      .append(optimalActivityRule.getConditionNum())
                      .append("元打")
                      .append(optimalActivityRule.getBenefitDiscount())
                      .append("折,已减")
                      .append(optimalActivityRule.getReduceAmount())
                      .append("元");
              optimalActivityRule.setRuleDesc(ruleDesc.toString());
              optimalActivityRule.setSelectType(2);
          }
          return optimalActivityRule;
      }

    /**
     * 计算满减最优规则
     * @param totalAmount
     * @param activityRuleList //该活动规则skuActivityRuleList数据,已经按照优惠金额从大到小排序了
     */
    private ActivityRule computeFullReduction(BigDecimal totalAmount, List<ActivityRule> activityRuleList) {
        ActivityRule optimalActivityRule = null;
        //该活动规则skuActivityRuleList数据,已经按照优惠金额从大到小排序了
        for (ActivityRule activityRule : activityRuleList) {
            //如果订单项金额大于等于满减金额,则优惠金额
            if (totalAmount.compareTo(activityRule.getConditionAmount()) > -1) {
                //优惠后减少金额
                activityRule.setReduceAmount(activityRule.getBenefitAmount());
                optimalActivityRule = activityRule;
                break;
            }
        }
        if(null == optimalActivityRule) {
            //如果没有满足条件的取最小满足条件的一项
            optimalActivityRule = activityRuleList.get(activityRuleList.size()-1);
            optimalActivityRule.setReduceAmount(new BigDecimal("0"));
            optimalActivityRule.setSelectType(1);

            StringBuffer ruleDesc = new StringBuffer()
                    .append("满")
                    .append(optimalActivityRule.getConditionAmount())
                    .append("元减")
                    .append(optimalActivityRule.getBenefitAmount())
                    .append("元,还差")
                    .append(totalAmount.subtract(optimalActivityRule.getConditionAmount()))
                    .append("元");
            optimalActivityRule.setRuleDesc(ruleDesc.toString());
        } else {
            StringBuffer ruleDesc = new StringBuffer()
                    .append("满")
                    .append(optimalActivityRule.getConditionAmount())
                    .append("元减")
                    .append(optimalActivityRule.getBenefitAmount())
                    .append("元,已减")
                    .append(optimalActivityRule.getReduceAmount())
                    .append("元");
            optimalActivityRule.setRuleDesc(ruleDesc.toString());
            optimalActivityRule.setSelectType(2);
        }
        return optimalActivityRule;
    }

    private BigDecimal computeTotalAmount(List<CartInfo> cartInfoList) {
        BigDecimal total = new BigDecimal("0");
        for (CartInfo cartInfo : cartInfoList) {
            //是否选中
            if(cartInfo.getIsChecked().intValue() == 1) {
                BigDecimal itemTotal = cartInfo.getCartPrice().multiply(new BigDecimal(cartInfo.getSkuNum()));
                total = total.add(itemTotal);
            }
        }
        return total;
    }

    private int computeCartNum(List<CartInfo> cartInfoList) {
        int total = 0;
        for (CartInfo cartInfo : cartInfoList) {
            //是否选中
            if(cartInfo.getIsChecked().intValue() == 1) {
                total += cartInfo.getSkuNum();
            }
        }
        return total;
    }
 //获取购物车可以使用优惠卷金额
    @Override
    public List<CouponInfo> findCouponInfo(Long userId, List<CartInfo> cartInfos) {
        //根据用户id查询所有优惠卷
        List<CouponInfo> userAllCouponInfoList = baseMapper.selectCartCouponInfoList(userId);
        if (CollectionUtils.isEmpty(userAllCouponInfoList)){
            return new ArrayList<CouponInfo>();
        }
        //获取所有优惠卷id列表
        List<Long>  couponInfoIdList = userAllCouponInfoList.stream().map(couponInfo -> couponInfo.getId()).collect(Collectors.toList());
        //查询优惠卷使用范围
        LambdaQueryWrapper<CouponRange> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(CouponRange::getCouponId,couponInfoIdList);
        List<CouponRange> couponRangeList = couponRangeMapper.selectList(wrapper);
        //获取优惠卷对应的sku
        Map<Long,List<Long>> couponIdAndSkuMap = findCouponIdAndSkuList(cartInfos,couponRangeList);
        //遍历全部优惠卷集合,判断优惠卷类型:全场通用,sku和分类
        BigDecimal reduceAmount = new BigDecimal(0);
        CouponInfo optimalCouponInfo = null;
        for (CouponInfo couponInfo : userAllCouponInfoList) {
            //全场通用
            //判断是否满足优惠使用门槛
            //计算购物车商品的总价
            if (CouponRangeType.ALL == couponInfo.getRangeType()){
                BigDecimal totalAmount = computeTotalAmount(cartInfos);
                if(totalAmount.subtract(couponInfo.getConditionAmount()).doubleValue() >= 0){
                    couponInfo.setIsSelect(1);
                }
            } else {
                //根据优惠卷id获得skuid集合
                List<Long> skuIdList = couponIdAndSkuMap.get(couponInfo.getId());
                //满足使用范围的购物项
                List<CartInfo> currentCartInfoList = cartInfos.stream().filter(cartInfo -> skuIdList.contains(cartInfo.getSkuId())).collect(Collectors.toList());
                BigDecimal totalAmount = computeTotalAmount(currentCartInfoList);
                if(totalAmount.subtract(couponInfo.getConditionAmount()).doubleValue() >= 0){
                    couponInfo.setIsSelect(1);
                }
            }
            if (couponInfo.getIsSelect().intValue() == 1 && couponInfo.getAmount().subtract(reduceAmount).doubleValue() > 0) {
                reduceAmount = couponInfo.getAmount();
                optimalCouponInfo = couponInfo;
            }
            //
            if(null != optimalCouponInfo){
                optimalCouponInfo.setIsOptimal(1);
            }
        }
        return userAllCouponInfoList;
    }
    private BigDecimal computeTotalAmount(List<CartInfo> cartInfoList) {
        BigDecimal total = new BigDecimal("0");
        for (CartInfo cartInfo : cartInfoList) {
            //是否选中
            if(cartInfo.getIsChecked().intValue() == 1) {
                BigDecimal itemTotal = cartInfo.getCartPrice().multiply(new BigDecimal(cartInfo.getSkuNum()));
                total = total.add(itemTotal);
            }
        }
        return total;
    }
    //获取优惠卷对应的sku
    private Map<Long, List<Long>> findCouponIdAndSkuList(List<CartInfo> cartInfos, List<CouponRange> couponRangeList) {
        Map<Long, List<Long>> map = new HashMap<>();
        //根据优惠卷id进行分组
        Map<Long, List<CouponRange>> couponRangeMap = couponRangeList.stream()
                .collect(Collectors
                        .groupingBy(couponRange -> couponRange.getCouponId()));
        //遍历map集合
        Iterator<Map.Entry<Long, List<CouponRange>>> iterator = couponRangeMap.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<Long, List<CouponRange>> entry = iterator.next();
            Long couponId = entry.getKey();
            List<CouponRange> couponRanges = entry.getValue();
            Set<Long> skuIdSet = new HashSet<>();
            cartInfos.stream().forEach(cartInfo -> {
                couponRanges.stream().forEach(couponRange -> {
                    //判断
                    if (couponRange.getRangeType() == CouponRangeType.SKU
                            && couponRange.getRangeId().longValue() == cartInfo.getSkuId()){
                        skuIdSet.add(cartInfo.getSkuId());
                    } else if(couponRange.getRangeType() == CouponRangeType.CATEGORY
                            && couponRange.getRangeId().longValue() == cartInfo.getCategoryId()){
                        skuIdSet.add(cartInfo.getSkuId());
                    }else {
                    }
                });
            });
            map.put(couponId,new ArrayList<>(skuIdSet));
        }
        return map;
    }

根据引用的解释,云尚办公集成knife4j出现空指针异常的原因可能是无法找到knife4j的任何版本。解决方案是通过project structure配置libraries,并通过Maven从正确的依赖中安装knife4j。此外,根据引用,knife4j是一个为Java MVC框架集成Swagger生成Api文档的增强解决方案。因此,集成knife4j可以帮助你生成和管理Api文档。 另外,引用提到,作者在练习云尚办公项目时遇到了一些问题,并对项目提出了一些其他的见解。然而,具体关于云尚办公集成knife4j出现空指针异常的详细问题没有在提供的引用中找到。如果你能提供更多关于空指针异常的细节,我将能够更好地帮助你解决问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [尚硅谷-云尚办公-项目复盘](https://blog.csdn.net/qq_47168235/article/details/130468136)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* [云尚办公系统学习笔记(1.基础设置)](https://blog.csdn.net/Kiritoasu/article/details/130726289)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鸭鸭老板

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值