85-分布式项目搭建

分布式项目搭建

页面原型展示 :

在这里插入图片描述

技术选型 :
前端技术选型 :

在这里插入图片描述

后端技术选型 (有些可能并没有用到):

在这里插入图片描述

项目开发环境 :
开发工具:
后端:IDEA 2019
前端:VS code
数据库客户端工具:SQLYog
开发环境:
JDK 11
Maven 3.6.3
MySQL 5.7
Zookeeper 3.6.0
Dubbo 2.5.7
Redis 5.0.4
开发后端服务接口 :
我们采用前后端分离的开发模式,先开发后端服务接口,测试成功,再开发前端vue界面,最后进行前后端联调,项目上线
项目结构与命名 :
单一架构:

在这里插入图片描述

分布式架构:

在这里插入图片描述

后端项目架构,我们采用dubbo的生产者和消费者的理论
创建服务提供方和服务消费方两个工程,通过maven聚合工程来搭建,模块划分如下:
服务提供
lagou-edu-parent:pom聚合父工程,统一依赖设置
lagou-edu-entity:jar工程,封装实体类
lagou-edu-dao:jar工程,封装与数据库打交道的部分
lagou-edu-service:web工程,暴露服务的接口和实现
服务消费
lagou-edu-web:web工程,接收前端工程发来的请求,远程调用服务并消费
URL命名:
查询:http://localhost:8002/course/getList — get开头
保存:http://localhost:8002/course/saveXx — save开头
更新:http://localhost:8002/course/updateXx — update开头
接口响应格式 :
/**
  数据传输对象
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class ResponseDTO<T> implements Serializable {
    private static final long serialVersionUID = 1L; //序列化版本号,唯一标识,一般操作反序列化的
    private int state;  // 操作状态
    private String message;  // 状态描述
    private T content;  // 相应内容
}

//导入了lombok依赖时:
//@Data:  该注解使用在类上,该注解会提供 getter、setter、equals、hashCode、toString 方法
//省略了手动添加,实际上包括了下面的一部分有的,下面优先,自己写的更加优先(除了构造方法,他们只是添加,所以要么删除手写的,要么删除注解,只针对无参和有全参数,即手写的不能有与该注解相同,否则会报错,提示,启动也是报错)
//@AllArgsConstructor:生成全参构造器
//@NoArgsConstructor:生成无参构造器
//@ToString:作用于类,覆盖默认的toString()方法
//可以通过of属性限定显示某些字段,通过exclude属性排除某些字段,这些可以不必理会,现在很少用到,后面就不一定了
//只需要知道他的toString与我们生成的toString()差不多即可
//注解生成还是手动生成,一般都不会操作static,所以上面的序列化版本号,一般不会操作显示出来,除非你自己添加上
//但使用注解的话,那么基本不可能操作了,除非你修改注解源代码
pom.xml:
<properties>
    <spring.version>5.0.6.RELEASE</spring.version>
</properties>
<dependencies>
    <!-- Spring -->
    <dependency>
        <!--Spring容器,即IOC容器的使用需要这个,比如ApplicationContext-->
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
              <!--基础依赖,其他依赖一般会导入这个,这里进行版本操作一下,实际上可以不写
这个jar 文件是所有应用都要用到的,它包含访问配置文件,创建和管理bean
以及进行IoC/DI(控制反转和依赖注入)操作相关的所有类
如果应用只需基本的IoC/DI 支持,引入spring-core.jar 及spring-beans.jar 文件就可以了
-->
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
         <!--springMVC坐标,含有前端控制器,如DispatcherServlet-->
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <!--spring使用连接池的,包括一些下面的tx操作
当需要下面tx的一些操作时(如事务传播),那么可以导入tx-->
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <!--提供对AspectJ的支持,也可以说是操作织入-->
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <!--Spring整合@Test,使得可以使用注解指定配置类或者配置文件-->
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
        <scope>test</scope>
    </dependency>
    <!-- Mybatis -->
    <dependency>
   <!--引入mybatis依赖,如工厂的一些类-->
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.2.8</version>
    </dependency>
    <dependency>
        <!--可以使用注解,配置mybatis-->
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>1.3.2</version>
    </dependency>
    <!-- 连接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.9</version>
    </dependency>
    <!-- 数据库 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.32</version>
    </dependency>
    <!--dubbo -->
    <dependency>
        <!--dubbo 有对应的注解,如@Service-->
        <groupId>com.alibaba</groupId>
        <artifactId>dubbo</artifactId>
        <version>2.5.7</version>
    </dependency>
    <!--zookeeper -->
    <dependency>
        <!--对应的包需要,如ZooKeeper对象创建,即这个类的使用-->
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.4.6</version>
    </dependency>
     <!--zookeeper客户端 -->
    <dependency>
         <!--启动成对应的客户端或者服务端必须要,否则报错-->
        <groupId>com.github.sgroschupf</groupId>
        <artifactId>zkclient</artifactId>
        <version>0.1</version>
    </dependency>
    <dependency>
        <!--操作class文件的,若没有用到,可以删除-->
        <groupId>javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>3.11.0.GA</version>
    </dependency>
    <!-- fastjson -->
    <dependency>
        <!--fastjson工具包 有操作响应时,将时间戳变成对应的Date类型的格式的操作-->
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.47</version>
    </dependency>
    <!-- junit -->
    <dependency>
        <!--@Test的操作-->
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--spring操作redis的工具类-->
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>2.3.2.RELEASE</version>
    </dependency>
    <!--redis客户端-->
    <dependency>
        <!--spring操作redis的工具类,后面的spring.xml里面的两个配置都是需要这个,对应的类就是这里面的-->
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.1.0</version>
    </dependency>
    <!--json解析工具-->
    <dependency>
        <!--对应操作页面返回的@ResponseBody时的操作的json需要的包-->
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.8</version>
    </dependency>
</dependencies>
初始化数据库:
对应的数据库设计文档和代码地址如下:
链接:https://pan.baidu.com/s/14kHif51YkKXDgsyUBTOs8A
提取码:alsk
我们idea实际上也可以操作数据库,而不用使得我们进行来回切换
如图:

在这里插入图片描述

点击+号,如下:

在这里插入图片描述

出现如下图(默认指向edu,可以切换):
注意:一般需要数据库的驱动(下载给他的,并不是项目的)
他会给你提示,你点击下载即可(好像与导入无关,可能与导入是操作项目的原因吧)

在这里插入图片描述

输入对应的mysql地址(以后会说明公用地址的操作,现在我们只操作专用地址,也基本就是192.168开头的地址),并输入密码
点击确定(ok和Apply)都可
然后点击如下:

在这里插入图片描述

这下我们就不用多次的切换窗口了
我们也可以写sql语句:
点击如下:

在这里插入图片描述

打开默认的sql编写窗口
接下来我们就可以编写语句了,比如(这里指向的是edu,所以操作的是edu数据库的表,在不填写数据库名的情况下,一般默认选择数据库第一个表):

在这里插入图片描述

点击运行,那么下面就会出现对应的表数据了
用户模块:
实体类的编写没有任意技术含量,而且还浪费时间,传说中的"老牛活"
使用一个插件可以快速生成 entity,dao,service,mapper等文件
生成"老牛活"代码的解决方案有很多种:企业中比较常见的还有"mybatis的逆向工程(可以自己学习)"
下面使用我给出的方式,先安装插件(若没有出现,则点击对应的提示,在市场搜索,或者点击上面的Marketplace)

在这里插入图片描述

安装完后,点击确认(默认给你勾选,最好等待操作完,然后重启,防止一些问题)
其中Easy code安装完成后(对应的Lombok需要和后面的依赖一起操作,才可使得注解起作用)
然后可以在如下图所示,看到对应选项了(右键一个表)

在这里插入图片描述

代表安装完成
接下来操作生成代码(操作user表):

在这里插入图片描述

在这里插入图片描述

一般都选择取消(使得他自动添加,自己添加也可)

在这里插入图片描述

点击ok(其对应的项目,一般默认选择最后操作的项目,虽然修改后也可能是),然后一直点击yes,那么就会出现如下
如果提示报错,可以重启idea重新加载,来防止出现问题(大概是没有加载好):

在这里插入图片描述

都帮你根据表的字段创建好了对应的类,一般都会指定service层
但是并不是完美的生成,可能需要自己去检查一下,看看有没有错误(比如,导入包的错误等等)
至此,代码生成完毕(记得移动对应的Dao代码以及xml文件和实体类代码到对应的项目里面)

在这里插入图片描述

移动后,记得删掉原来的代码,也记得再次检查包的导入
主要的错误,基本只要是包的地方,都会不对,因为我们的目录是各不相同的,而他基本只有一个形式
至此,我们选择父项目进行编译,若成功,那么代码生成操作完成,基本没有问题
注意:生成的代码,实际上我们也可以不使用,因为他只是一个模板而已
这里只是给出如何生成的操作方式,并不会完全操作他生成的代码
用户登录和注册:

在这里插入图片描述

lagou-edu-entity项目:

在这里插入图片描述

lombok小辣椒,使用注解取代原来冗余的get和set,空构造,全参数构造
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok,对应的依赖地址 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>
package com.lagou.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.io.Serializable;
import java.util.Date;


/**
 * 用户表(User)实体类
 *
 * @author makejava
 * @since 2022-07-10 20:23:26
 */
@Data //get和set都全部生成了,实际上也包括了下面的注解的作用(可能他们会覆盖这个对应的操作)
@AllArgsConstructor //生成全参数的构造方法
@NoArgsConstructor //生成空构造方法
@ToString // 生成toString方法
//他们都是隐式的有对应方法,若我们自己写了,那么使用我们自己的,即使用显式的方法
public class User implements Serializable {
    private static final long serialVersionUID = 606292390505661237L;
    /**
     * 用户id
     */
    private Integer id;
    /**
     * 用户昵称
     */
    private String name;
    /**
     * 用户头像地址
     */
    private String portrait;
    /**
     * 注册手机
     */
    private String phone;
    /**
     * 用户密码(可以为空,支持只用验证码注册、登录)
     */
    private String password;
    /**
     * 注册ip
     */
    private String regIp;
    /**
     * 是否有效用户
     */
    private String accountNonExpired;
    /**
     * 账号是否未过期
     */
    private String credentialsNonExpired;
    /**
     * 是否未锁定
     */
    private String accountNonLocked;
    /**
     * 用户状态:ENABLE能登录,DISABLE不能登录
     */
    private String status;
    /**
     * 是否删除
     */
    private String isDel;
    /**
     * 注册时间
     */
    private Date createTime;
    /**
     * 记录更新时间
     */
    private Date updateTime;
}
lagou-edu-dao项目:

在这里插入图片描述

mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 后台的日志输出:针对开发者-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
</configuration>
spring-dao.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    <!--1.扫描包-->
    <context:component-scan base-package="com.lagou"/>
    <!--2.数据连接池-->
    <!--serverTimezone是数据库连接中的参数,用于设置服务时间
		标识设置服务时间为东一区时间,即国际日期变更线时间-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!--
自动操作com.mysql.jdbc.Driver,批量加载,并使用的
所有有其他驱动,一般可以自动加载,若有多个版本,一般使用高版本,在pom下,一般操作先声明的-->
        <property name="url" value="jdbc:mysql://192.168.164.128:3306/edu?
serverTimezone=GMT&amp;characterEncoding=utf8"/> 
        <!--在xml里,若要&一般需要&amp;表示,这里设置可以中文不乱码-->
        <property name="username" value="root"/>
        <property name="password" value="QiDian@666"/>
        <property name="maxActive" value="10"/>
        <property name="minIdle" value="5"/>
    </bean>
    <!--3.sqlsessionFactory-->
    <bean id="sqlSessionFactory"
          class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>

    </bean>
    <!--4.事务管理器-->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--5.开启事务-->
    <tx:annotation-driven/>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.lagou.dao"/>
    </bean>
</beans>

对应生成的UserDao.xml进行修改,写成如下(多余的删除):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-
mapper.dtd">
<mapper namespace="com.lagou.dao.UserDao">
<!--
jdbcType将值变成对应的类型返回(不是对应的类型时,通常不会影响执行,通常会操作默认)
一般使用默认即可,也就是不写,当然,对应的值不能是"",否则报错
可能会在某些特殊情况下,需要指定,但大多数并不需要,到那时可以加上,但一般都没有什么作用
就像parameterType一样,大多数是用来方便观察的
虽然他们都不能随便写(其中parameterType的类的类型可以有迂回,不匹配,操作默认)
所以jdbcType可以认为是真正的方便观察,而parameterType通常并不是,因为不能不是一致(类的类型除外)
-->
    <resultMap type="com.lagou.entity.User" id="UserMap">
        <result property="id" column="id" jdbcType="INTEGER"/>
        <result property="name" column="name" jdbcType="VARCHAR"/>
        <result property="portrait" column="portrait" jdbcType="VARCHAR"/>
        <result property="phone" column="phone" jdbcType="VARCHAR"/>
        <result property="password" column="password" jdbcType="VARCHAR"/>
        <result property="regIp" column="reg_ip" jdbcType="VARCHAR"/>
        <result property="accountNonExpired" column="account_non_expired" jdbcType="VARCHAR"/>
        <result property="credentialsNonExpired" column="credentials_non_expired" 
                jdbcType="VARCHAR"/>
        <result property="accountNonLocked" column="account_non_locked" jdbcType="VARCHAR"/>
        <result property="status" column="status" jdbcType="VARCHAR"/>
        <result property="isDel" column="is_del" jdbcType="VARCHAR"/>
        <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
        <result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
    </resultMap>
<select id="login" resultMap="UserMap">
    select * from user where phone = #{phone} and password = #{password}
</select>

</mapper>
将UserDao接口修改成如下:
package com.lagou.dao;

import com.lagou.entity.User;
import org.apache.ibatis.annotations.Param;

/**
 * 用户表(User)表数据库访问层
 *
 * @author makejava
 * @since 2022-07-10 20:23:25
 */
public interface UserDao {

    //一般最好是这样的注释,在开发时,可以更好的维护
     /**
     * 用户登录
     *
     * @param phone 手机号
     * @param password 密码
     * @return 用户对象
     */
    User login(@Param("phone") String phone,@Param("password") String Password);
}


在测试目录下,创建类:
package user;

import com.lagou.dao.UserDao;
import com.lagou.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring/spring-dao.xml"})
public class TestUser {

    @Autowired
    private UserDao userDao;

    @Test
    public void login(){
        User login = userDao.login("110", "123");
        System.out.println(login);

    }
}

进行测试,若成功,那么这里就操作完毕
lagou-edu-service(项目):

在这里插入图片描述

spring-service.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
           xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://code.alibabatech.com/schema/dubbo
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
         <!--1.服务提供方在zookeeper中的"别名"-->
         <dubbo:application name="lagou-edu-service"/>
         <!--2.注册中心的地址-->
         <dubbo:registry address="zookeeper://192.168.164.128:2181"/>
         <!--3.扫描类(将什么包下的类作为服务提供类)-->
         <dubbo:annotation package="com.lagou"/>
         <dubbo:provider timeout="60000"/>
         <bean id="redisTemplate"
                 class="org.springframework.data.redis.core.RedisTemplate">
   <property name="connectionFactory" ref="connectionFactory"></property>
 </bean>
         <bean id="connectionFactory"
                 class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
   <property name="hostName" value="192.168.164.128"></property>
   <property name="port" value="6379"/>
 </bean>
        </beans>
对应的UserService类及其实现类:
package com.lagou.service;
import com.lagou.entity.User;

/**
 * 用户表(User)表服务接口
 *
 * @author makejava
 * @since 2022-07-10 20:23:27
 */
public interface UserService {
     /**
     * 用户登录
     *
     * @param phone 手机号
     * @param password 密码
     * @return 用户对象
     */
    User login(String phone, String Password);
}

package com.lagou.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.lagou.dao.UserDao;
import com.lagou.entity.User;
import com.lagou.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;


/**
 * 用户表(User)表服务实现类
 *
 * @author makejava
 * @since 2022-07-10 20:23:27
 */
@Service //使用com.alibaba.dubbo.config.annotation.Service暴露服务,被消费方调用
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public User login(String phone, String Password) {
        User login = userDao.login(phone, Password);

        return login;
    }
}

接下来编写测试类:
package user;

import com.lagou.dao.UserDao;
import com.lagou.entity.User;
import com.lagou.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
//需要这样写,classpath加上*那么就会使用jar包
//也就是其他的导入的文件,而不是只操作当前项目,所有在父子工程中需要加上*来使得操作其他工程的对应配置
@ContextConfiguration(locations = {"classpath*:spring/spring-*.xml"})
//spring/spring-*.xml代表spring目录下,以spring-开头,以.xml结尾的所有文件都操作
public class TestUser {

    @Autowired
    private UserService userService;

    @Test
    public void login(){
        User login = userService.login("110", "123");
        System.out.println(login);

    }
}

执行成功,那么这里也就操作完成
接下来我们打开dubbo的管理端(79章博客说明过)
然后在该项目里,创建web项目,对应的pom.xml:
<packaging>war</packaging> <!--有了这个,就可以直接创建webapp目录(自动变成web项目)-->
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <!--这里一般加上版本,使得不报错即可,然后也可以删除-->
            <configuration>
                <port>8001</port>
                <path>/</path>
            </configuration>
            <executions>
                <execution>
                    <!-- 打包完成后,运行服务 -->
                    <phase>package</phase>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

对应的web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         id="WebApp_ID" version="3.1">
    <listener>
        <listenerclass>org.springframework.web.context.ContextLoaderListener</listener-class>

   </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:spring/spring-*.xml</param-value>
    </context-param>
</web-app>
接下来打包运行,查看管理端是否上线即可,上线了,那么操作完成
总体目录如下:

在这里插入图片描述

lagou-edu-web项目:

在这里插入图片描述

这是一个新的项目,不是子项目:
将原来的服务方的父工程的依赖复制到这个项目里
并添加对应service的依赖(实际上只要实体类依赖即可,也最好导入实体类依赖,减少操作)
使得操作他的信息,注意需要先安装他的jar包才可
并且设置war包方式
将打包运行的端口由原来的8001修改成8002,并加上下面的依赖:
  <!-- 解决跨域问题所需依赖 -->
        <dependency>
              <groupId>com.thetransactioncompany</groupId>
              <artifactId>cors-filter</artifactId>
              <version>2.5</version>
        </dependency>
spring-consumer.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/mvc
           http://www.springframework.org/schema/mvc/spring-mvc.xsd
           http://code.alibabatech.com/schema/dubbo
           http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    <!--json转换器-->
    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean
                    class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="supportedMediaTypes" value="application/json"/>
                <property name="features">
                    <array>
                        <value>WriteMapNullValue</value>
                        <value>WriteDateUseDateFormat</value>
                    </array>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    <!--1.服务提供方在zookeeper中的"别名"-->
    <dubbo:application name="lagou-edu-web"/>
    <!--2.注册中心的地址-->
    <dubbo:registry address="zookeeper://192.168.164.128:2181"/>
    <!--3.扫描类(将什么包下的类作为消费类)-->
    <dubbo:annotation package="com.lagou.controller"/> 
    <!--最好指定对应具体的包,防止导入的jar包里也进行了操作,使得可能会出现错误-->
</beans>
对应的UserController类:
package com.lagou.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.lagou.entity.User;
import com.lagou.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 *
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Reference //远程调用
    private UserService userService;


    @GetMapping("login")
    public User login(String phone, String password) {
        System.out.println(phone);
        System.out.println(password);
        User login = userService.login(phone, password);
        System.out.println(login);
        return login;

    }
}

对应的接口:
package com.lagou.service;

import com.lagou.entity.User;

/**
 *
 */
public interface UserService {

     /**
     * 用户登录
     *
     * @param phone 手机号
     * @param password 密码
     * @return 用户对象
     */
    User login(String phone, String Password);

}

注意:这个接口需要与服务器的包路径一致,否则可能会报错,因为要去调用对应的服务
自然会去对应的zookeeper的对应节点获取信息(根据自己的接口来得到),而该节点一般是对应的接口路径(实现类实现的接口)
所以一般需要相同路径,否则可能会报空指针异常,具体的dubbo的解释,可以去看79章博客
实际上别名是可以设置一样的,因为对应的zookeeper的不只是别名唯一
对应的页面index.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

<form action="/user/login"> <!--不写method,则默认get请求-->

    <p><input type="text" placeholder="请输入手机号..." name="phone"></p>
    <p><input type="text" placeholder="请输入密码..." name="password"></p>
    <p><button>登录</button></p>
</form>

</body>
</html>
对应的web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
        http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         id="WebApp_ID" version="3.1">
    <!-- 解决post乱码 -->
    <filter>
        <filter-name>charset</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>charset</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!--跨域配置-->
    <filter>
        <filter-name>corsFitler</filter-name>
        <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>corsFitler</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <servlet>
        <servlet-name>springMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/spring-consumer.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
打包运行后,访问页面进行测试,然后查看对应的dubbo管理端,看看是否上线,若上线显示正常,则操作成功
至此登录完成,但是我们发现,对应的返回的数据中并没有特别的信息,只有对象的数据
前面说过一个接口响应格式 ,接下来我们使用这个格式来操作
在对应的lagou-edu-entity项目里的entity包里,加上对应的UserDTO类:
package com.lagou.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.io.Serializable;

/**
 *
 */
//该注解使用在类上,该注解会提供 getter、setter、equals、hashCode、toString 方法
//省略了手动添加,实际上包括了下面的一部分有的,下面优先,自己写的更加优先(除了构造方法,他们只是添加,所以要么删除手写的,要么删除注解,只针对无参和有全参数,即手写的不能有与该注解相同,否则会报错,提示,启动也是报错)
@Data
//生成全参构造器
@AllArgsConstructor
//生成无参构造器
@NoArgsConstructor
//生成toString()方法
@ToString
public class UserDTO<T> implements Serializable {
    private static final long serialVersionUID = 1L; //序列化版本号,唯一标识,一般操作反序列化的
    private int state; // 操作状态
    private String message; // 状态描述
    private T content; // 相应内容
}
上面修改后,记得安装,使得被依赖导入(刷新)
修改lagou-edu-web项目的UserController类:
package com.lagou.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.lagou.entity.User;
import com.lagou.entity.UserDTO;
import com.lagou.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 *
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Reference //远程调用
    private UserService userService;


    @GetMapping("login")
    public UserDTO login(String phone, String password) {
        UserDTO userDTO = new UserDTO<>();
        System.out.println(phone);
        System.out.println(password);
        User login = userService.login(phone, password);
        if(login != null){
            userDTO.setState(200); //规定200表示成功
            userDTO.setMessage("登录成功");
        }else{
            userDTO.setState(300); //规定300表示失败
            userDTO.setMessage("登录失败");
        }
        userDTO.setContent(login);
        System.out.println(login);
        return userDTO;

    }
}

若成功,则返回的toString()根据注解的返回的顺序应该是按照对应编码值顺序显示的(大概是注解的作用)
所以对应的返回的数据中,是content先显示,然后是message,最后是state
实际上就是变成json时,会根据编码值来操作,注解的作用
至此,返回数据,操作完成,但是这只是登录完成,现在我们操作注册
要操作注册,首先我们需要对应的方法
在lagou-edu-dao项目的UserDao接口里面,添加如下方法,对应的UserDao接口如下:
package com.lagou.dao;

import com.lagou.entity.User;
import org.apache.ibatis.annotations.Param;


/**
 *
 */
public interface UserDao {
    /**
     * 用户登录
     *
     * @param phone 手机号
     * @param password 密码
     * @return 用户对象
     */
    User login(@Param("phone") String phone, @Param("password") String Password);

    /**
     * 检查手机号是否登录过
     *
     * @param phone 手机号
     * @return 0,未注册,不是0,表示已注册
     */
    Integer checkPhone(String phone);
    
    /**
     * 用户注册
     *
     * @param phone 手机号
     * @param password 密码
     * @return 受影响的行数,一般是1
     */
    Integer register(@Param("phone") String phone, @Param("password") String Password);


}
添加对应的UserDao.xml:
  <select id="checkPhone" resultType="integer">
        select count(*) from user where phone = #{phone} 
      <!--
查找对应手机号的数量,只要大于0,就表示存在了
一般都只会返回1或者0,若是有多个的话,可能有其他情况,比如还检验其他信息,使得该信息在多个地方使用登录
-->
    </select>

 <insert id="register" parameterType="string">
        insert into user
            (name,phone,password,create_time,update_time)
        values
            (#{phone},#{phone},#{password},sysdate(),sysdate())
             <!--
            其中一般name与phone一样,所以都用phone
            sysdate()代表当前系统的时间,根据时区来判断(受时区影响)
	我这里与系统的时间大概少15个小时(数据库占了8小时,即实际上只少7小时,比如系统时间是19点,那么他就是4点,且是同一天),当然,可能会到前一天去,因为这是少的操作
            实际上就算是Date类,也是有可能有偏差的(不受时区影响,单纯的减少,可能是与数据库有关,即数据库导致少8个小时),一般少个8个小时,比如我是16点,那么他就是8点,且是同一天(当然,可能会到前一天去,因为这是少的操作)
上面都是以24小时为主的,而不是12个小时,这里注意即可
为什么数据库会占8小时,主要是我们的配置的原因(导致又操作了一次时区的减少,且他是固定的,即基本是8小时),主要是为了进行统一的,所以就算与当前系统不对,那么也是正常的
因为总不能只看你一个地区吧(针对7小时的,所以也并不是一定少7个小时),所以也通常会有地区的字段(虽然这里并没有),那么可以根据对应的添加,来确定与工作人员的时间,从而来知道是什么时候,比如,如果是Date,那么加上8小时,如果是对应的当前系统时间(sysdate()或者与他一样的操作,比如数据库字段设置了CURRENT_TIMESTAMP),那么加上8小时,然后加上7小时(受当前数据库时区影响,不是固定的,除非移动数据库地区),从而保证在同一个地方(一般以某个时区为主,比如北京的时区)
注意:上面的解释可能并不准确,所以可以选择跳过,即不看即可



            -->
    </insert>
在对应的测试类TestUser里添加如下代码进行测试:
@Test
    public void checkPhone(){
        Integer integer = userDao.checkPhone("110");
        System.out.println(integer);
    }

  @Test
    public void register(){
        Integer integer = userDao.register("115","123123");
        System.out.println(integer); //0:未注册,大于0(通常为1):已注册
    }

测试完成
同理在对应的lagou-edu-service项目的impl包里操作对应的类:
添加对应的方法放在UserService类:
  /**
     * 检查手机号是否登录过
     *
     * @param phone 手机号
     * @return 0:未注册,不是0,表示已注册
     */
    Integer checkPhone(String phone);

    /**
     * 用户注册
     *
     * @param phone 手机号
     * @param password 密码
     * @return 受影响的行数,一般是1
     */
    Integer register(String phone, String Password);

对应实现类也添加方法:
 @Override
    public Integer checkPhone(String phone) {
        Integer integer = userDao.checkPhone(phone);

        return integer;
    }

    @Override
    public Integer register(String phone, String Password) {
        Integer register = userDao.register(phone, Password);
        
        return register;
    }
对应的测试类也添加方法:
@Test
    public void checkPhone(){
        Integer integer = userService.checkPhone("110");
        System.out.println(integer);
    }

@Override
    public Integer register(String phone, String Password) {
        Integer register = userDao.register(phone, Password);
        return register;
    }
一般是没有问题的,记得执行测试类的对应方法,防止出现问题
接下来为了解决前面的功能描述,有手机号登录,没有则注册登录
那么进入到lagou-edu-web项目下,修改对应的UserController类(注意复制对应的方法到对应的接口里面):
package com.lagou.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.lagou.entity.User;
import com.lagou.entity.UserDTO;
import com.lagou.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 *
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Reference //远程调用
    private UserService userService;


    @GetMapping("login")
    public UserDTO login(String phone, String password) {
        UserDTO userDTO = new UserDTO<>();
        System.out.println(phone);
        System.out.println(password);

        User login = null;
        //检测手机号是否注册
        Integer integer = userService.checkPhone(phone);
        if(integer == 0){
            //未注册,注册并登录
            userService.register(phone,password);
            userDTO.setMessage("手机号尚未注册,系统已帮您自动注册,请牢记密码!");
            login = userService.login(phone, password);
        }else{
            login = userService.login(phone, password);
            if(login != null){
                userDTO.setState(200); //规定200表示成功
                userDTO.setMessage("登录成功");
            }else{
                userDTO.setState(300); //规定300表示失败
                userDTO.setMessage("登录失败");
            }
           
        }
        userDTO.setContent(login);

        System.out.println(login);
        return userDTO;

    }
}
课程模块(与之前的操作VO以及单纯实体类(考虑DTO(97章博客有说明)的部分,但是是分开的)不同的是,这里我们放在一起,这是建立在我们只需要一个查询,多次使用,与VO各有好处,虽然这一次比较久,但可以多次使用,并且由于是多次使用,那么自然建立在层级上,所以存在外键的类可以选择不加(也可以加,一般不加而已)其外键对应主键(该主键不是外键,并且一般我们以他为主)的信息,因为是移动,自然可以直接的拿取的,但是考虑到数据非常多的情况下,你的移动只要够好,还是可以避免的,这需要前端的操作了,总不能全部移动吧,那这样还是选择访问数据库吧(前提是量大,否则连接的时间都可能超不过,考虑到总访问时间与部分的去除,按照忍受程度还需要考虑部分访问)):
当然,一次查询虽然看起来并不像多次的连接数据库(数据操作交给前端,后端就不用查询并使用数据库再操作了),但是由于是一次的,在维护方面并不友好,所以看个人意愿的,解决的方案有很多,并且结构也有很多,但是都需要根据具体情况来操作,当然,我们都建议使用维护,所以以后建议不一起处理(再97章博客中的大改造是对连表这个方面的处理,来进行维护的,即维护xml,而分开后是维护具体逻辑,这都是往维护性进行处理,从一段时间处理一次,到一段时间处理多次,到长时间中会处理多次(因为容易继续访问),时间跨大了)
全部课程:

在这里插入图片描述

对应的部分案例:
课程:Java从小白到大神 (course) -------> 起点老师:拉勾网高级讲师 (teacher)
第一章:初识 (course_section)
01 什么是java (course_lesson)
02 jdk的介绍与安装
第二章:应用
01 什么是数据类型
02 什么是变量
course 1:1 teacher
course 1:N course_section
course_section 1:N course_lesson
course_lesson1:N course_media
在lagou-edu-entity项目的entity包里加上如下的类:
package com.lagou.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.Date;
import java.io.Serializable;

/**
 * 活动课程表(ActivityCourse)实体类
 *
 * @author makejava
 * @since 2022-07-14 18:11:05
 */
@Data //get和set都全部生成了,实际上也包括了下面的注解的作用(可能他们会覆盖这个对应的操作)
@AllArgsConstructor //生成全参数的构造方法
@NoArgsConstructor //生成空构造方法
@ToString // 生成toString方法
//他们都是隐式的有对应方法,若我们自己写了,那么使用我们自己的,即使用显式的方法
public class ActivityCourse implements Serializable {
    private static final long serialVersionUID = -89461375082427542L;
    /**
     * 主键ID
     */
    private Integer id;
    /**
     * 课程ID
     */
    private Integer courseId;
    /**
     * 活动开始时间
     */
    private Date beginTime;
    /**
     * 活动结束时间
     */
    private Date endTime;
    /**
     * 活动价格
     */
    private Long amount;
    /**
     * 库存值
     */
    private Integer stock;
    /**
     * 状态 0未上架 10已上架
     */
    private Integer status;
    /**
     * 逻辑删除 0未删除 1删除
     */
    private Integer isDel;
    /**
     * 备注
     */
    private String remark;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 创建人
     */
    private String createUser;
    /**
     * 更新时间
     */
    private Date updateTime;
    /**
     * 更新人
     */
    private String updateUser;


   
}

package com.lagou.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

/**
 * 课程(Course)实体类
 *
 * @author makejava
 * @since 2022-07-13 14:49:05
 */
@Data //get和set都全部生成了,实际上也包括了下面的注解的作用(可能他们会覆盖这个对应的操作)
@AllArgsConstructor //生成全参数的构造方法
@NoArgsConstructor //生成空构造方法
@ToString // 生成toString方法
//他们都是隐式的有对应方法,若我们自己写了,那么使用我们自己的,即使用显式的方法
public class Course implements Serializable {

    private Teacher teacher; //一门课程对应一个讲师

    private List<CourseSection> courseSectionList; //一门课程对应多个章节
    
    private ActivityCourse activityCourse;

    private static final long serialVersionUID = 464248821202087847L;
    /**
     * id
     */
    private Object id;
    /**
     * 课程名
     */
    private String courseName;
    /**
     * 课程一句话简介
     */
    private String brief;
    /**
     * 原价
     */
    private Object price;
    /**
     * 原价标签
     */
    private String priceTag;
    /**
     * 优惠价
     */
    private Object discounts;
    /**
     * 优惠标签
     */
    private String discountsTag;
    /**
     * 描述markdown
     */
    private String courseDescriptionMarkDown;
    /**
     * 课程描述
     */
    private String courseDescription;
    /**
     * 课程分享图片url
     */
    private String courseImgUrl;
    /**
     * 是否新品
     */
    private Integer isNew;
    /**
     * 广告语
     */
    private String isNewDes;
    /**
     * 最后操作者
     */
    private Integer lastOperatorId;
    /**
     * 自动上架时间
     */
    private Date autoOnlineTime;
    /**
     * 记录创建时间
     */
    private Date createTime;
    /**
     * 更新时间
     */
    private Date updateTime;
    /**
     * 是否删除
     */
    private Integer isDel;
    /**
     * 总时长(分钟)
     */
    private Integer totalDuration;
    /**
     * 课程列表展示图片
     */
    private String courseListImg;
    /**
     * 课程状态,0-草稿,1-上架
     */
    private Integer status;
    /**
     * 课程排序,用于后台保存草稿时用到
     */
    private Integer sortNum;
    /**
     * 课程预览第一个字段
     */
    private String previewFirstField;
    /**
     * 课程预览第二个字段
     */
    private String previewSecondField;
    /**
     * 销量
     */
    private Integer sales;
}
package com.lagou.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.Date;
import java.io.Serializable;

/**
 * 课程节内容(CourseLesson)实体类
 *
 * @author makejava
 * @since 2022-07-13 14:50:05
 */
@Data //get和set都全部生成了,实际上也包括了下面的注解的作用(可能他们会覆盖这个对应的操作)
@AllArgsConstructor //生成全参数的构造方法
@NoArgsConstructor //生成空构造方法
@ToString // 生成toString方法
//他们都是隐式的有对应方法,若我们自己写了,那么使用我们自己的,即使用显式的方法
public class CourseLesson implements Serializable {

    private CourseMedia courseMedia; //一小节课对应一个视频

    private static final long serialVersionUID = -35857311228165600L;
    /**
     * id
     */
    private Object id;
    /**
     * 课程id
     */
    private Integer courseId;
    /**
     * 章节id
     */
    private Integer sectionId;
    /**
     * 课时主题
     */
    private String theme;
    /**
     * 课时时长(分钟)
     */
    private Integer duration;
    /**
     * 是否免费
     */
    private Integer isFree;
    /**
     * 记录创建时间
     */
    private Date createTime;
    /**
     * 更新时间
     */
    private Date updateTime;
    /**
     * 是否删除
     */
    private Integer isDel;
    /**
     * 排序字段
     */
    private Integer orderNum;
    /**
     * 课时状态,0-隐藏,1-未发布,2-已发布
     */
    private Integer status;
}
package com.lagou.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.Date;
import java.io.Serializable;

/**
 * 课节视频表(CourseMedia)实体类
 *
 * @author makejava
 * @since 2022-07-13 15:30:55
 */
@Data //get和set都全部生成了,实际上也包括了下面的注解的作用(可能他们会覆盖这个对应的操作)
@AllArgsConstructor //生成全参数的构造方法
@NoArgsConstructor //生成空构造方法
@ToString // 生成toString方法
//他们都是隐式的有对应方法,若我们自己写了,那么使用我们自己的,即使用显式的方法
public class CourseMedia implements Serializable {
    private static final long serialVersionUID = 673974921818890080L;
    /**
     * 课程媒体主键ID
     */
    private Integer id;
    /**
     * 课程Id
     */
    private Integer courseId;
    /**
     * 章ID
     */
    private Integer sectionId;
    /**
     * 课时ID
     */
    private Integer lessonId;
    /**
     * 封面图URL
     */
    private String coverImageUrl;
    /**
     * 时长(06:02)
     */
    private String duration;
    /**
     * 媒体资源文件对应的EDK
     */
    private String fileEdk;
    /**
     * 文件大小MB
     */
    private Long fileSize;
    /**
     * 文件名称
     */
    private String fileName;
    /**
     * 媒体资源文件对应的DK
     */
    private String fileDk;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 更新时间
     */
    private Date updateTime;
    /**
     * 是否删除,0未删除,1删除
     */
    private Integer isDel;
    /**
     * 时长,秒数(主要用于音频在H5控件中使用)
     */
    private Integer durationNum;
    /**
     * 媒体资源文件ID
     */
    private String fileId;



}

package com.lagou.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.Date;
import java.io.Serializable;
import java.util.List;

/**
 * 课程章节表(CourseSection)实体类
 *
 * @author makejava
 * @since 2022-07-13 14:49:57
 */
@Data //get和set都全部生成了,实际上也包括了下面的注解的作用(可能他们会覆盖这个对应的操作)
@AllArgsConstructor //生成全参数的构造方法
@NoArgsConstructor //生成空构造方法
@ToString // 生成toString方法
//他们都是隐式的有对应方法,若我们自己写了,那么使用我们自己的,即使用显式的方法
public class CourseSection implements Serializable {

    private List<CourseLesson> courseLessonList; //一个章节对应多个小节

    private static final long serialVersionUID = 698702451600763670L;
    /**
     * id
     */
    private Object id;
    /**
     * 课程id
     */
    private Integer courseId;
    /**
     * 章节名
     */
    private String sectionName;
    /**
     * 章节描述
     */
    private String description;
    /**
     * 记录创建时间
     */
    private Date createTime;
    /**
     * 更新时间
     */
    private Date updateTime;
    /**
     * 是否删除
     */
    private Integer isDel;
    /**
     * 排序字段
     */
    private Integer orderNum;
    /**
     * 状态,0:隐藏;1:待更新;2:已发布
     */
    private Integer status;



}


package com.lagou.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.Date;
import java.io.Serializable;

/**
 * 讲师表(Teacher)实体类
 *
 * @author makejava
 * @since 2022-07-13 14:49:40
 */
@Data //get和set都全部生成了,实际上也包括了下面的注解的作用(可能他们会覆盖这个对应的操作)
@AllArgsConstructor //生成全参数的构造方法
@NoArgsConstructor //生成空构造方法
@ToString // 生成toString方法
//他们都是隐式的有对应方法,若我们自己写了,那么使用我们自己的,即使用显式的方法
public class Teacher implements Serializable {
    private static final long serialVersionUID = 738571768582945875L;
    /**
     * id
     */
    private Object id;
    /**
     * 课程ID
     */
    private Integer courseId;
    /**
     * 讲师姓名
     */
    private String teacherName;
    /**
     * 职务
     */
    private String position;
    /**
     * 讲师介绍
     */
    private String description;
    /**
     * 记录创建时间
     */
    private Date createTime;
    /**
     * 更新时间
     */
    private Date updateTime;
    /**
     * 是否删除
     */
    private Integer isDel;


}
我们发现,使用注解来生成对应的get,set或者其他方法时,我们好修改
也就是说,我们只需要修改或者添加变量即可,而不用每次的修改或者添加,都要手动的添加对应的get和set方法,或者其他方法
操作对应的lagou-edu-dao项目
在资源文件里的dao包中添加对应的CourseDao.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-
mapper.dtd">
<mapper namespace="com.lagou.dao.CourseDao">

         <!--
注意:当所有的匹配的值为null时,那么这个对应的对象也就是null(补充的mybatis)
否则只要内部或者本身有一个不是null,那么这个本身对象就不是null
-->
    <resultMap type="com.lagou.entity.Course" id="CourseMap">
        <result property="id" column="c_id" />
        <result property="courseName" column="course_name" />
        <result property="brief" column="brief" />
        <result property="price" column="price" />
        <result property="priceTag" column="price_tag" />
        <result property="discounts" column="discounts" />
        <result property="discountsTag" column="discounts_tag" />
        <result property="courseDescriptionMarkDown" column="course_description_mark_down" />
        <result property="courseDescription" column="course_description" />
        <result property="courseImgUrl" column="course_img_url" />
        <result property="isNew" column="is_new" />
        <result property="isNewDes" column="is_new_des" />
        <result property="lastOperatorId" column="last_operator_id" />
        <result property="autoOnlineTime" column="auto_online_time" />
        <result property="createTime" column="c_create_time" />
        <result property="updateTime" column="c_update_time" />
        <result property="isDel" column="c_is_del" />
        <result property="totalDuration" column="total_duration" />
        <result property="courseListImg" column="course_list_img" />
        <result property="status" column="c_status" />
        <result property="sortNum" column="sort_num" />
        <result property="previewFirstField" column="preview_first_field" />
        <result property="previewSecondField" column="preview_second_field" />
        <result property="sales" column="sales" />
        <!--注意:可以多次对应-->
        <!--对应的老师一对一-->
  
        <association property="teacher" javaType="com.lagou.entity.Teacher">
            <result property="id" column="t_id" />
            <result property="courseId" column="t_course_id" />
            <result property="teacherName" column="teacher_name" />
            <result property="position" column="position" />
            <result property="description" column="t_description" />
            <result property="createTime" column="t_create_time" />
            <result property="updateTime" column="t_update_time" />
            <result property="isDel" column="t_is_del" />
        </association>
        
         <!--活动课程,顺序记得是association然后collection(同级别),否则报错(好像是约束规定,一般java读取通常不看约束,一般是项目本身看的,或者读取的内部看的),所以说,你使用如io流照样可以运行的-->
        <association property="activityCourse" javaType="com.lagou.entity.ActivityCourse">
            <result property="id" column="ac_id"/>
            <result property="courseId" column="ac_course_id"/>
            <result property="beginTime" column="begin_time"/>
            <result property="endTime" column="end_time"/>
            <result property="amount" column="amount"/>
            <result property="stock" column="stock"/>
            <result property="status" column="ac_status"/>
            <result property="isDel" column="ac_is_del"/>
            <result property="remark" column="remark"/>
            <result property="createTime" column="ac_create_time"/>
            <result property="createUser" column="create_user"/>
            <result property="updateTime" column="ac_update_time"/>
            <result property="updateUser" column="update_user"/>
        </association>

        <!--对应的章节,一对多-->
        <collection property="courseSectionList" ofType="com.lagou.entity.CourseSection">
            <result property="id" column="cs_id"/>
            <result property="courseId" column="cs_course_id"/>
            <result property="sectionName" column="section_name"/>
            <result property="description" column="cs_description"/>
            <result property="createTime" column="cs_create_time"/>
            <result property="updateTime" column="cs_update_time"/>
            <!--表里面的这个字段,可能是is_de,若是,则修改成is_del-->
            <result property="isDel" column="cs_is_del" />
            <result property="orderNum" column="cs_order_num" />
            <result property="status" column="cs_status" />

            <!--章节对应的多个小节-->
            <collection property="courseLessonList" ofType="com.lagou.entity.CourseLesson">
                <result property="id" column="cl_id"/>
                <result property="courseId" column="cl_course_id"/>
                <result property="sectionId" column="cl_section_id"/>
                <result property="theme" column="theme"/>
                <result property="duration" column="cl_duration"/>
                <result property="isFree" column="is_free"/>
                <result property="createTime" column="cl_create_time"/>
                <result property="updateTime" column="cl_update_time"/>
                <result property="isDel" column="cl_is_del"/>
                <result property="orderNum" column="cl_order_num"/>
                <result property="status" column="cl_status"/>
                <!--对应的视频-->
                  <association property="courseMedia" javaType="com.lagou.entity.CourseMedia">
                    <result property="id" column="cm_id" />
                    <result property="courseId" column="cm_course_id" />
                    <result property="sectionId" column="cm_section_id" />
                    <result property="lessonId" column="cm_lesson_id" />
                    <result property="coverImageUrl" column="cover_image_url" />
                    <result property="duration" column="cm_duration" />
                    <result property="fileEdk" column="file_edk" />
                    <result property="fileSize" column="file_size" />
                    <result property="fileName" column="file_name" />
                    <result property="fileDk" column="file_dk" />
                    <result property="createTime" column="cm_create_time" />
                    <result property="updateTime" column="cm_update_time" />
                    <result property="isDel" column="cm_is_del" />
                    <result property="durationNum" column="duration_num" />
                    <result property="fileId" column="file_id" />
                </association>
            </collection>
        </collection>
    </resultMap>

 <!--下面使用类似的这样的操作:c.`create_time` c_create_time,是保证不会是同一个,由于单纯的c.`create_time`在没有别名的情况下,一般直接是默认的处理,即create_time,所以需要别名,当然,对于函数来说,一般函数会默认设置成其函数总名称的别名(如SELECT COUNT(*) FROM biz_project中,其别名就是COUNT(*)),但是这里不会,所以这里是这样写的(操作别名,即解决c.`create_time`就是create_time的情况)-->

    <select id="getAllCourse" resultMap="CourseMap">
        SELECT
        c.`id` c_id,`course_name`,`brief`,`price`,`price_tag`,`discounts`,`discounts_tag`,
        `course_description_mark_down`,`course_description`,`course_img_url`,`is_new`,
        `is_new_des`,`last_operator_id`,`auto_online_time`,
        c.`create_time` c_create_time,c.`update_time` c_update_time,
        c.`is_del` c_is_del,`total_duration`,`course_list_img`,
        c.`status` c_status,`sort_num`,`preview_first_field`,`preview_second_field`,
        `sales`,
        t.`id` t_id,t.`course_id` t_course_id,`teacher_name`,
        `position`,t.`description` t_description,
        t.`create_time` t_create_time,t.`update_time` t_update_time,t.`is_del` t_is_del,
        cs.`id` cs_id,cs.`course_id` cs_course_id,`section_name`,
        cs.`description` cs_description,cs.`create_time` cs_create_time,
        cs.`update_time` cs_update_time,
        cs.`is_del` cs_is_del,cs.`order_num` cs_order_num,cs.`status` cs_status,
        cl.`id` cl_id,cl.`course_id` cl_course_id,cl.`section_id` cl_section_id,`theme`,
        cl.`duration` cl_duration,`is_free`,cl.`create_time` cl_create_time,
        cl.`update_time` cl_update_time,cl.`is_del` cl_is_del,
        cl.`order_num` cl_order_num,cl.`status` cl_status,
         cm.`id` cm_id,cm.`course_id` cm_course_id,cm.`section_id` cm_section_id,
        cm.`lesson_id` cm_lesson_id,
        `cover_image_url`,
        cm.`duration` cm_duration,`file_edk`,`file_size`,
        `file_name`,`file_dk`,cm.`create_time` cm_create_time,
        cm.`update_time` cm_update_time,cm.`is_del` cm_is_del,`duration_num`,`file_id`,
        ac.id ac_id,ac.course_id ac_course_id,
        `begin_time`,`end_time`,`amount`,`stock`,ac.`status` ac_status,
        ac.`is_del` ac_is_del,`remark`,ac.`create_time` 
        ac_create_time,`create_user`,ac.`update_time` ac_update_time,`update_user`

          FROM
        activity_course ac RIGHT JOIN course c ON c.`id` = ac.`course_id`
        INNER JOIN teacher t ON c.id = t.`course_id`
        INNER JOIN course_section cs ON c.id = cs.`course_id`
        INNER JOIN course_lesson cl ON cs.`id` = cl.`section_id`
        LEFT JOIN course_media cm ON cm.`lesson_id` = cl.`id`
        ORDER BY amount DESC,c_id ,ac_create_time DESC
        
        <!--多个join可以更好的知道谁与谁关联,不用将关系全部放在一起了,使得好维护,因为逻辑明确-->

        <!--
        优先操作连接的显示,然后根据排序来进行(没有排序自然不排序)
       默认升序(ASC),这里就按照降序,也就是DESC,如果左边的相同则按照右边的排序
       -->
        
        <!--
``包括的,相当于隔绝,也就是说cl.`id`c_id,可以挨在一起
若没有指定的查询字段,那么基本是按照谁表在左,谁先显示,与左连接和右连接无关
-->
<!--
查询的结果,不是别名
基本就是对应的表字段名,无论是否指定,即可能会出现相同的id字段
所以相同的字段,最好进行设置别名,而不是只指定
-->
    </select>


</mapper>

<!--
注意:当多个表一起操作时,对应的条件中,最好使用表的别名,如c.id
因为假设就是id,可能多个表中有相同的字段
那么就会报错,无视别名,直指原始名,这句话就是为什么需要使用c.id的原因
因为是使用默认的选择,那么就在起别名之前操作
-->
上面是全部查询,也就是根据课程,查询对应的章节,小节,视频,讲师,等等,5个表进行联查
对应的xml写好后,我们写上对应的接口,在dao包下写上CourseDao接口:
package com.lagou.dao;

import com.lagou.entity.Course;

import java.util.List;

/**
 *
 */
//当然只能是接口,不可以是类
public interface CourseDao {
    /*
    查询全部课程信息
     */

    List<Course> getAllCourse();
}

在测试的地方,添加course包,然后添加测试类TestCourse:
package course;

import com.lagou.dao.CourseDao;
import com.lagou.entity.Course;
import com.lagou.entity.CourseLesson;
import com.lagou.entity.CourseMedia;
import com.lagou.entity.CourseSection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring/spring-dao.xml"})
public class TestCourse {

    @Autowired
    private CourseDao courseDao;

    @Test
    public void getAllCourse(){
        List<Course> allCourse = courseDao.getAllCourse();
        System.out.println(allCourse);
        System.out.println("-------------------------");
        //Course course = allCourse.get(0); //第一门课程
        for (Course course : allCourse) {
        String flag = course.getActivityCourse()!=null?"火爆活动中":"";
        System.out.println("课程:" + flag + " " + course.getId() + "->" + course.getCourseName());
        for(CourseSection cs : course.getCourseSectionList()){
            System.out.println("\t\t章节:" +cs.getId() +"->"+ cs.getSectionName());
            for(CourseLesson cl : cs.getCourseLessonList()){
                if(cl.getCourseMedia() == null){
                    cl.setCourseMedia(new CourseMedia()); 
                    //设置,防止对应的值,为null,使得出现空指针异常,这样可以使得内容返回null
                }
                System.out.println("\t\t\t小节:" +cl.getId() +"->"+ cl.getTheme()+
                                   ",视频:"+cl.getCourseMedia().getFileId()+
                                   ",时长:【"+cl.getCourseMedia().getDuration()+"】");
            }
        }                                  
	  }
    }
}

进行测试,若成功,则操作完毕
这里回顾一下,一般当查询出多个数据时,需要使用集合来保存,如List,若就是单纯的一个返回类型,那么会报错
除非查询的数据只有一条(也包括有很多内部数据使得主方变成多条数据)
那么就不会报错(无论是全查还是条件查,只要是一条或者严谨来说是主方一条,就不会)
当然,无论是多条还是单条,集合都可以操作
再在对应的lagou-edu-service项目里的lagou包里面加上course包,course包加上对应的接口CourseService及其实现类
package com.lagou.course;

import com.lagou.entity.Course;

import java.util.List;

/**
 *
 */
public interface CourseService {

    /*
    查询全部课程信息
     */

    List<Course> getAllCourse();
}

package com.lagou.course.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.lagou.course.CourseService;
import com.lagou.dao.CourseDao;
import com.lagou.entity.Course;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

/**
 *
 */
@Service //暴露服务
public class CourseServiceImpl implements CourseService {
    @Autowired
    private CourseDao courseDao;

    @Override
    public List<Course> getAllCourse() {

        return courseDao.getAllCourse(); //这样写,省略代码,节省空间,虽然不好维护
    }
}

在lagou-edu-web里的lagou包下,创建course包,加上对应的CourseService接口
package com.lagou.course;

import com.lagou.entity.Course;

import java.util.List;

/**
 *
 */
public interface CourseService {

    /*
    查询全部课程信息
     */

    List<Course> getAllCourse();
}

在对应的controller包里。加上CourseController类:
package com.lagou.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.lagou.course.CourseService;
import com.lagou.entity.Course;
import com.lagou.entity.User;
import com.lagou.entity.UserDTO;
import com.lagou.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 *
 */
@RestController
@RequestMapping("/course")
public class CourseController {

    @Reference //远程调用
    private CourseService courseService;


    @GetMapping("getAllCourse")
    public List<Course> getAllCourse(String phone, String password) {

        List<Course> allCourse = courseService.getAllCourse();
        return allCourse;

    }
    }
进行测试,若成功返回数据,则操作完毕
至此,应该明白,对应的分布式系统为什么是用RPC来进行远程调用了吧
即在模块之间进行方法细分(服务器),这些细分互相调用
使得原来可能有同一个方法的多个服务器,只需要去这一个服务器调用方法即可,即相同服务抽取出来了(虽然这里并没有体现)
已购课程 :

在这里插入图片描述

在对应的CourseDao.xml加上如下的配置:
<sql id="courseInfo">
       SELECT
        c.`id` c_id,`course_name`,`brief`,`price`,`price_tag`,`discounts`,`discounts_tag`,
            `course_description_mark_down`,`course_description`,`course_img_url`,`is_new`,
            `is_new_des`,`last_operator_id`,`auto_online_time`,
            c.`create_time` c_create_time,c.`update_time` c_update_time,
            c.`is_del` c_is_del,`total_duration`,`course_list_img`,
            c.`status` c_status,`sort_num`,`preview_first_field`,`preview_second_field`,
            `sales`,
            t.`id` t_id,t.`course_id` t_course_id,`teacher_name`,
            `position`,t.`description` t_description,
            t.`create_time` t_create_time,t.`update_time` t_update_time,t.`is_del` t_is_del,
            cs.`id` cs_id,cs.`course_id` cs_course_id,`section_name`,
            cs.`description` cs_description,cs.`create_time` cs_create_time,
            cs.`update_time` cs_update_time,
            cs.`is_del` cs_is_del,cs.`order_num` cs_order_num,cs.`status` cs_status,
            cl.`id` cl_id,cl.`course_id` cl_course_id,cl.`section_id` cl_section_id,`theme`,
            cl.`duration` cl_duration,`is_free`,cl.`create_time` cl_create_time,
            cl.`update_time` cl_update_time,cl.`is_del` cl_is_del,
            cl.`order_num` cl_order_num,cl.`status` cl_status,
            cm.`id` cm_id,cm.`course_id` cm_course_id,cm.`section_id` cm_section_id,
            cm.`lesson_id` cm_lesson_id,
            `cover_image_url`,
            cm.`duration` cm_duration,`file_edk`,`file_size`,
            `file_name`,`file_dk`,cm.`create_time` cm_create_time,
            cm.`update_time` cm_update_time,cm.`is_del` cm_is_del,`duration_num`,`file_id`,
            ac.id ac_id,ac.course_id ac_course_id,
            `begin_time`,`end_time`,`amount`,`stock`,ac.`status` ac_status,
            ac.`is_del` ac_is_del,`remark`,ac.`create_time`
                ac_create_time,`create_user`,ac.`update_time` ac_update_time,`update_user`

        FROM
            activity_course ac RIGHT JOIN course c ON c.`id` = ac.`course_id`
                               INNER JOIN teacher t ON c.id = t.`course_id`
                               INNER JOIN course_section cs ON c.id = cs.`course_id`
                               INNER JOIN course_lesson cl ON cs.`id` = cl.`section_id`
                               LEFT JOIN course_media cm ON cm.`lesson_id` = cl.`id`
    </sql>

<!--上面是可以被导入的,操作相同的代码时,起的作用-->


    <select id="getCourseByUserId" resultMap="CourseMap">
        <include refid="courseInfo"/> 
        <!--使用这个,导入对应的sql,也就是上面的到这里来,可以优化一下CourseDao.xml-->
        WHERE c.id IN ( SELECT course_id FROM user_course_order WHERE STATUS = 20 
        AND is_del = 0 AND user_id = #
        {userid})
        ORDER BY amount DESC,c_id ,ac_create_time DESC

    </select>
添加部分CourseDao接口:
/*
    查询某个用户已购买的课程
     userId 用户编号
     */
    List<Course> getCourseByUserId(String userId);
添加部分TestCourse类的测试方法:
@Test
    public void getCourseByUserId() {
        List<Course> allCourse = courseDao.getCourseByUserId("100030018");
        System.out.println(allCourse);
        System.out.println("-------------------------");
        //Course course = allCourse.get(0); //第一门课程
        for (Course course : allCourse) {
            String flag = course.getActivityCourse()!=null?"火爆活动中":"";
         System.out.println("课程:" + flag + " " + course.getId() + "->" + course.getCourseName() );

            for (CourseSection cs : course.getCourseSectionList()) {
                System.out.println("\t\t章节:" + cs.getId() + "->" + cs.getSectionName());
                for (CourseLesson cl : cs.getCourseLessonList()) {
                    if (cl.getCourseMedia() == null) {
                        cl.setCourseMedia(new CourseMedia());
                    }
                    System.out.println("\t\t\t小节:" + cl.getId() + "->" + cl.getTheme() + 
   ",视频:" + cl.getCourseMedia().getFileId() + 
                                       ",时长:【" + cl.getCourseMedia().getDuration() + "】");
                }
            }

        }
    }
在lagou-edu-service添加部分的接口和实现类:
/*
   查询某个用户已购买的课程
    userId 用户编号
    */
    List<Course> getCourseByUserId(String userId);
@Override
    public List<Course> getCourseByUserId(String userId) {
        List<Course> courseByUserId = courseDao.getCourseByUserId(userId);

        return courseByUserId;
    }
并不是所有的都需要测试,但测试还是好的,若要测的话,自己进行测试即可,这里就不写了
这时在lagou-edu-web添加部分接口(记得对应包):
/*
   查询某个用户已购买的课程
    userId 用户编号
    */
    List<Course> getCourseByUserId(String userId);
添加部分CourseController类:
  @GetMapping("getCourseByUserId/{userid}")
    public List<Course> getCourseByUserId(@PathVariable("userid") String userid) {

        List<Course> allCourse = courseService.getCourseByUserId(userid);
        return allCourse;

    }
接下来启动测试,查看是否有返回值,若有,则操作成功
课程详情:

在这里插入图片描述

在对应的lagou-edu-dao里加上对应的配置:
添加部分CourseDao.xml:
<select id="getCourseById" resultMap="CourseMap">
        <include refid="courseInfo"/>
        where c.id = #{courseId}
    </select>
添加部分CourseDao接口:
 /*
    查询某门课程
    cid 课程编号
     */
    Course getCourseById(Integer cid); 
//实际上字符串也是可以的,当你输入汉字时,相当于没有对应的数据查询出来,并不会太影响表,只是不够严谨
对应的测试方法:
@Test
    public void getCourseById() {
        Course courseById = courseDao.getCourseById(7);
        System.out.println(courseById);

    }
在对应的lagou-edu-service里添加部分CourseService接口及其实现类:
/*
   查询某门课程
   cid 课程编号
    */
    Course getCourseById(Integer cid);
   @Override
    public Course getCourseById(Integer cid) {
        Course courseById = courseDao.getCourseById(cid);

        return courseById;
    }
对应的lagou-edu-web项目里,添加部分CourseService:
    /*
  查询某门课程
  cid 课程编号
   */
    Course getCourseById(Integer cid);
添加部分的CourseController:
@GetMapping("getCourseById/{courseid}")
    public Course getCourseById(@PathVariable("courseid") Integer courseid) {

        Course allCourse = courseService.getCourseById(courseid);
        return allCourse;

    }
启动测试,若返回数据,则操作成功
订单模块 :
购买,生成订单

在这里插入图片描述

在dao层的项目中,添加对应的OrderDao.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-
mapper.dtd">
<mapper namespace="com.lagou.dao.OrderDao">

    <insert id="saveOrder">

        INSERT  INTO `user_course_order`
      (`order_no`,`user_id`,`course_id`,`activity_course_id`,
        `source_type`,`status`,`create_time`,`update_t
        ime`,`is_del`)
      VALUES
       (#{orderNo},#{user_id},#{course_id},#{activity_course_id},#
        {source_type},0,sysdate(),sysdate(),0);
    </insert>

</mapper>

添加对应的接口:
package com.lagou.dao;

import com.lagou.entity.User;
import org.apache.ibatis.annotations.Param;


/**
 *
 */
public interface OrderDao {

    /**
     * 保存订单信息
     * @param orderNo 订单编号
     * @param user_id 用户编号
     * @param course_id 课程编号
     * @param activity_course_id 活动课程编号
     * @param source_type 订单来源类型
     */
    public void saveOrder(@Param("orderNo")String orderNo,
                          @Param("user_id")String user_id,
                          @Param("course_id")String course_id,
                          @Param("activity_course_id")String activity_course_id,
                          @Param("source_type")String source_type);
}

在对应的service层的项目里,加上对应的包并添加OrderService接口及其实现类:
package com.lagou.order;

import org.apache.ibatis.annotations.Param;

/**
 *
 */
public interface OrderService {
      /**
     * 保存订单信息
     * @param orderNo 订单编号
     * @param user_id 用户编号
     * @param course_id 课程编号
     * @param activity_course_id 活动课程编号
     * @param source_type 订单来源类型
     */
    public void saveOrder(String orderNo,String user_id, String course_id, 
    String activity_course_id, String source_type);


}

package com.lagou.order.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.lagou.dao.OrderDao;
import com.lagou.order.OrderService;
import org.springframework.beans.factory.annotation.Autowired;

/**
 *
 */
@Service //暴露服务
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderDao orderDao;
    @Override
    public void saveOrder(String orderNo, String user_id, String course_id, String 
                          activity_course_id, String source_type) {
        orderDao.saveOrder(orderNo,user_id,course_id,activity_course_id,source_type);


    }
}

添加对应的测试类:
package order;

import com.lagou.order.OrderService;
import com.lagou.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.UUID;

/**
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:spring/spring-*.xml"})
public class TestOrder {

    @Autowired
    private OrderService orderService;

    @Test
    public void saveOrder(){
        String orderNo = UUID.randomUUID().toString();
        String user_id= "100030011";
        String course_id= "7";
        String activity_course_id= "0"; //0表示课程没有活动
        String source_type="1";

        orderService.saveOrder(orderNo,user_id,course_id,activity_course_id,source_type);
    }

}
//启动时,记得关闭对应的服务,因为占用了对应的端口的,因为又操作了配置文件

若添加数据成功,即数据库有对应数据了,那么操作完成
在web层项目里,添加部分接口(记得包路径一致):
package com.lagou.order;

/**
 *
 */
public interface OrderService {
      /**
     * 保存订单信息
     * @param orderNo 订单编号
     * @param user_id 用户编号
     * @param course_id 课程编号
     * @param activity_course_id 活动课程编号
     * @param source_type 订单来源类型
     */
    public void saveOrder(String orderNo,String user_id, String course_id, String activity_course_id, 
                          String source_type);


}

添加对应的OrderController类:
package com.lagou.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.lagou.entity.Course;
import com.lagou.order.OrderService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.UUID;

/**
 *
 */
@RestController
@RequestMapping("/order")
public class OrderController {

    @Reference //远程消费
    private OrderService orderService;

    @GetMapping("saveOrder/{userid}/{courseid}/{acid}/{stype}")
    public String saveOrder(@PathVariable("userid") String userid,@PathVariable("courseid") String 
       courseid,@PathVariable("acid") String acid,@PathVariable("stype") String stype) {

        String orderNo = UUID.randomUUID().toString();
        orderService.saveOrder(orderNo,userid,courseid,acid,stype);
        return orderNo;

    }

}

启动测试,若返回数据,则操作成功(记得对应的userid好像是不能重复的,对应表有这样的索引)
注意:唯一的索引在字段后面直接的设置才算完整,若使用索引方式来设置多个字段的索引,那么有如下的操作
第一,根据多个字段是总体是否相同,来进行唯一,也就是说,只要部分不同,那么其他部分可以相同
但若设置完后,那么设置的就基本固定,这时无论怎么操作都基本不会与前面的数据操作该唯一,但新加的会,即没有设置完的
第二,null的值,是默认null,不参与唯一,也就是直接忽略
而不是null字符串,要消除该字符串null,需要先设置一个值
再手动设置null就变成默认的null,否则不会变,即要先设置使得消除固定
满足这两点,使得有唯一
根据上面的解释,所以,最好是直接的设置在字段后面
订单操作:

在这里插入图片描述

更新的接口路径这里进行了改变,往下看就知道了
再次操作dao层项目,添加对应的OrderDao接口和该接口的配置:
/**
     * 更新订单状态
     * @param orderNo 订单编号
     * @param status 订单状态 0已创建 10未支付 20已支付 30已取消 40已过期
     * @return 受影响的行数
     这个地方的注释有时并不会写
     */
    Integer updateOrder(@Param("orderNo") String orderNo,@Param("status") Integer status);
<update id="updateOrder">
        update user_course_order set status = #{status} where order_no = #{orderNo} and is_del = 0
<!--没有删除(is_del=0)的订单才能操作修改状态-->
    </update>
对应的service层的接口和实现类:
/**
     * 更新订单状态
     * @param orderNo 订单编号
     * @param status 订单状态 0已创建 10未支付 20已支付 30已取消 40已过期
     * @return 受影响的行数
     这个地方的注释有时并不会写
     */
Integer updateOrder(String orderNo,Integer status);
//注解在没有操作的时候,一般不会影响方法,也就是相当于没有注解,比如说@Param
//并不会使得方法调用出现其他问题(不操作他的情况下)
 @Override
    public Integer updateOrder(String orderNo, Integer status) {
        Integer integer = orderDao.updateOrder(orderNo, status);

        return integer;
    }
对应的web层的接口:
/**
     * 更新订单状态
     * @param orderNo 订单编号
     * @param status 订单状态 0已创建 10未支付 20已支付 30已取消 40已过期
     * @return 受影响的行数
     这个地方的注释有时并不会写
     */
Integer updateOrder(String orderNo,Integer status);
对应的部分OrderController:
@GetMapping("updateOrder/{orderno}//{status}")
    public Integer updateOrder(@PathVariable("orderno") String orderno,@PathVariable("status") 
                               Integer 
                               status) {

        Integer integer = orderService.updateOrder(orderno, status);
        return integer; 
        //相当于操作了输出流,所以并不是非要String,基本上所有返回值都会操作
        //如直接的值,或者toString()方法等等

    }
启动测试,若返回数据,且对应的数据库的状态也变化了则操作成功
接下来继续在dao层项目里加上对应的接口和配置文件:
/**
     * 删除订单
     * @param orderNo 订单编号
     * @return 受影响的行数
     */
    Integer deleteOrder(@Param("orderNo") String orderNo);
  <update id="deleteOrder">
        update user_course_order set is_del = 1 where order_no = #{orderNo} 
<!--
一般数据通常不会真正的删除,都是通过对应的字段值来判断数据是否删除的,这样使得该数据存在
能够知道整体订单信息的操作多少,如有多少人进行下订单,又有多少删除订单的
一般在大数据的情况下,有很大作用
-->
    </update>
加上对应的service层的接口和实现类:
/**
     * 删除订单
     * @param orderNo 订单编号
     * @return 受影响的行数
     */   
Integer deleteOrder(String orderNo);
 @Override
    public Integer deleteOrder(String orderNo) {
        Integer integer = orderDao.deleteOrder(orderNo);

        return integer;
    }
对应的web层项目的接口:
/**
     * 删除订单
     * @param orderNo 订单编号
     * @return 受影响的行数
     */   
Integer deleteOrder(@Param("orderNo") String orderNo);
对应的部分OrderController类:
 @GetMapping("deleteOrder/{orderno}")
    public Integer deleteOrder(@PathVariable("orderno") String orderno) {

        Integer integer = orderService.deleteOrder(orderno);
        return integer;

    }
启动测试,若返回数据,且对应的数据库的对应字段也变化了则操作成功
接下来为了查询我的订单,需要对应的订单实体类:
package com.lagou.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.Date;
import java.io.Serializable;

/**
 * 用户课程订单表(UserCourseOrder)实体类
 *
 * @author makejava
 * @since 2022-07-15 17:35:26
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class UserCourseOrder implements Serializable {
    private static final long serialVersionUID = -77239403959527764L;
    /**
     * 主键
     */
    private Long id;
    /**
     * 订单号
     */
    private String orderNo;
    /**
     * 用户id
     */
    private Object userId;
    /**
     * 课程id,根据订单中的课程类型来选择
     */
    private Object courseId;
    /**
     * 活动课程id
     */
    private Integer activityCourseId;
    /**
     * 订单来源类型: 1 用户下单购买 2 后台添加专栏
     */
    private Object sourceType;
    /**
     * 当前状态: 0已创建 10未支付 20已支付 30已取消 40已过期 
     */
    private Object status;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 更新时间
     */
    private Date updateTime;
    /**
     * 是否删除
     */
    private Object isDel;


   
}


对应的dao层的接口及其配置文件:
/**
     * 查询登录用户的全部订单,即用户的全部订单,只是需要登录后才可以看到,这是一定的
     * @param userId 用户编号
     * @return 所有订单
     */
    List<UserCourseOrder> getOrdersByUserId(@Param("userId")String userId);
 <resultMap type="com.lagou.entity.UserCourseOrder" id="UserCourseOrderMap">
        <result property="id" column="id" jdbcType="INTEGER"/>
        <result property="orderNo" column="order_no" jdbcType="VARCHAR"/>
        <result property="userId" column="user_id" jdbcType="VARCHAR"/>
        <result property="courseId" column="course_id" jdbcType="VARCHAR"/>
        <result property="activityCourseId" column="activity_course_id" jdbcType="INTEGER"/>
        <result property="sourceType" column="source_type" jdbcType="VARCHAR"/>
        <result property="status" column="status" jdbcType="VARCHAR"/>
        <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
        <result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
        <result property="isDel" column="is_del" jdbcType="VARCHAR"/>
    </resultMap>
<!--这里就加上jdbcType了,虽然可以删除,但这里好观察一点-->

    <select id="getOrdersByUserId" resultMap="UserCourseOrderMap">
        select * from user_course_order where is_del = 0 and user_id = #{userId}
    </select>
对应的service层项目的接口和实现类:
/**
     * 查询登录用户的全部订单,即用户的全部订单,只是需要登录后才可以看到,这是一定的
     * @param userId 用户编号
     * @return 所有订单
     */  
List<UserCourseOrder> getOrdersByUserId(String userId);
 @Override
    public List<UserCourseOrder> getOrdersByUserId(String userId) {
        List<UserCourseOrder> ordersByUserId = orderDao.getOrdersByUserId(userId);

        return ordersByUserId;
    }
对应的web层项目的接口:
/**
     * 查询登录用户的全部订单,即用户的全部订单,只是需要登录后才可以看到,这是一定的
     * @param userId 用户编号
     * @return 所有订单
     */ 
List<UserCourseOrder> getOrdersByUserId(String userId);
对应的部分OrderController类:
@GetMapping("getOrdersByUserId/{userId}")
    public List<UserCourseOrder> getOrdersByUserId(@PathVariable("userId") String userId) {

        List<UserCourseOrder> ordersByUserId = orderService.getOrdersByUserId(userId);

        return ordersByUserId;

    }
启动测试,若返回数据,则操作成功
留言模块:
保存留言

在这里插入图片描述

添加实体类:
package com.lagou.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsC	onstructor;
import lombok.ToString;

import java.util.Date;
import java.io.Serializable;

/**
 * 课程留言表(CourseComment)实体类
 *
 * @author makejava
 * @since 2022-07-15 19:48:04
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class CourseComment implements Serializable {
    private static final long serialVersionUID = -11641570368573216L;
    /**
     * 主键
     */
    private Object id;
    /**
     * 课程id
     */
    private Integer courseId;
    /**
     * 章节id
     */
    private Integer sectionId;
    /**
     * 课时id
     */
    private Integer lessonId;
    /**
     * 用户id
     */
    private Integer userId;
    /**
     * 运营设置用户昵称
     */
    private String userName;
    /**
     * 父级评论id
     */
    private Integer parentId;
    /**
     * 是否置顶:0不置顶,1置顶
     */
    private Integer isTop;
    /**
     * 评论
     */
    private String comment;
    /**
     * 点赞数
     */
    private Integer likeCount;
    /**
     * 是否回复留言:0普通留言,1回复留言
     */
    private Integer isReply;
    /**
     * 留言类型:0用户留言,1讲师留言,2运营马甲 3讲师回复 4小编回复 5官方客服回复
     */
    private Integer type;
    /**
     * 留言状态:0待审核,1审核通过,2审核不通过,3已删除
     */
    private Integer status;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 更新时间
     */
    private Date updateTime;
    /**
     * 是否删除
     */
    private Integer isDel;
    /**
     * 最后操作者id
     */
    private Integer lastOperator;
    /**
     * 是否发送了通知,1表示未发出,0表示已发出
     */
    private Integer isNotify;
    /**
     * 标记归属
     */
    private Integer markBelong;
    /**
     * 回复状态 0 未回复 1 已回复
     */
    private Integer replied;

    
}


在对应的dao层项目中的dao包下,记得加上对应的接口(类文件夹)和配置文件(资源文件夹),即添加接口及其配置文件:
package com.lagou.dao;

import com.lagou.entity.CourseComment;

/**
 *
 */
public interface CourseCommentDao {

    /**
     * 保存留言
     * @param courseComment 留言内容对象
     * @return 受影响的行数
     */
    Integer saveComment(CourseComment courseComment);

}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-
mapper.dtd">
<mapper namespace="com.lagou.dao.CourseCommentDao">
    
    <insert id="saveComment">
        insert  into `course_comment`
            (`course_id`,`section_id`,`lesson_id`,
    `user_id`,`user_name`,`parent_id`,
    `is_top`,`comment`,`like_count`,
    `is_reply`,`type`,`status`,
    `create_time`,`update_time`,
    `is_del`,`last_operator`,`is_notify`,`mark_belong`,`replied`) values
(#{courseId},#{sectionId},#{lessonId},
 #{userId},#{userName},#{parentId},
 0,#{comment},0,0,
 #{type},0,sysdate(),sysdate(),0,#{lastOperator},1,0,0)
    </insert>

</mapper>


对应的service层的项目中的lagou的包下,添加comment包,并加上对应的接口及其实现类:
package com.lagou.comment;

import com.lagou.entity.CourseComment;

/**
 *
 */
public interface CommentService {

    /**
     * 保存留言
     * @param courseComment 留言内容对象
     * @return 受影响的行数
     */
    Integer saveComment(CourseComment courseComment);

}

package com.lagou.comment.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.lagou.comment.CommentService;
import com.lagou.dao.CourseCommentDao;
import com.lagou.entity.CourseComment;
import org.springframework.beans.factory.annotation.Autowired;

/**
 *
 */
@Service
public class CommentServiceImpl implements CommentService {
    @Autowired
    private CourseCommentDao courseCommentDao;

    @Override
    public Integer saveComment(CourseComment courseComment) {
        Integer integer = courseCommentDao.saveComment(courseComment);
        return integer;
    }
}

添加对应的测试包comment,再在该包里加上测试类TestComment类:
package comment;

import com.lagou.comment.CommentService;
import com.lagou.entity.CourseComment;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:spring/spring-*.xml"})
public class TestComment {

    @Autowired
    private CommentService commentService;

    @Test
    public void saveComment(){

        CourseComment courseComment = new CourseComment();
        courseComment.setCourseId(7); //课程编号
        courseComment.setSectionId(8); //章节编号
        courseComment.setLessonId(10); //小节编号
        courseComment.setUserId(100030011); //用户编号
        courseComment.setUserName("往事如烟"); //用户昵称
        courseComment.setParentId(0); //没有父id
        courseComment.setComment("吕奉先"); //留言内容
        courseComment.setType(0); //0:用户留言
        courseComment.setLastOperator(100030011); //最后操作的用户编号
        commentService.saveComment(courseComment);
    }


}

在对应web项目下,添加对应的接口(先创建对应的comment包):
package com.lagou.comment;

import com.lagou.entity.CourseComment;

/**
 *
 */
public interface CommentService {

    /**
     * 保存留言
     * @param courseComment 留言内容对象
     * @return 受影响的行数
     */
    Integer saveComment(CourseComment courseComment);

}

在对应的controller包下,添加CommentController类:
package com.lagou.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.lagou.comment.CommentService;
import com.lagou.course.CourseService;
import com.lagou.entity.Course;
import com.lagou.entity.CourseComment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 *
 */
@RestController
@RequestMapping("course")
public class CommentController {

    @Reference //远程调用
    private CommentService commentService;

    @GetMapping("comment/saveCourseComment")
    public Object saveCourseComment() {
        CourseComment courseComment = new CourseComment();
        courseComment.setCourseId(7); //课程编号
        courseComment.setSectionId(8); //章节编号
        courseComment.setLessonId(10); //小节编号
        courseComment.setUserId(100030011); //用户编号
        courseComment.setUserName("往事如烟"); //用户昵称
        courseComment.setParentId(0); //没有父id
        courseComment.setComment("吕奉先"); //留言内容
        courseComment.setType(0); //0:用户留言
        courseComment.setLastOperator(100030011); //最后操作的用户编号
        Integer integer = commentService.saveComment(courseComment);
        return integer;


    }

}

启动测试,若数据库里添加了数据,那么操作成功
留言列表:
某门课程的全部留言

在这里插入图片描述

在dao层项目里加上部分CourseCommentDao接口及其配置文件:
/**
     * 某个课程的全部留言(分页)
     * @param courseId 课程编号
     * @param offset 起始位置,或者说数据偏移
     * @param pagesize 每页的条数
     * @return 留言集合
     */
    List<CourseComment> getCommentsByCourseId(@Param("courseId") Integer courseId,@Param("offset") 
                                              Integer offset,@Param("pagesize") Integer pagesize);

   <resultMap type="com.lagou.entity.CourseComment" id="CourseCommentMap">
        <result property="id" column="id"/>
        <result property="courseId" column="course_id"/>
        <result property="sectionId" column="section_id"/>
        <result property="lessonId" column="lesson_id"/>
        <result property="userId" column="user_id"/>
        <result property="userName" column="user_name"/>
        <result property="parentId" column="parent_id"/>
        <result property="isTop" column="is_top"/>
        <result property="comment" column="comment"/>
        <result property="likeCount" column="like_count"/>
        <result property="isReply" column="is_reply"/>
        <result property="type" column="type"/>
        <result property="status" column="status"/>
        <result property="createTime" column="create_time"/>
        <result property="updateTime" column="update_time"/>
        <result property="isDel" column="is_del"/>
        <result property="lastOperator" column="last_operator"/>
        <result property="isNotify" column="is_notify"/>
        <result property="markBelong" column="mark_belong"/>
        <result property="replied" column="replied"/>
    </resultMap>

<select id="getCommentsByCourseId" resultMap="CourseCommentMap">
        SELECT * FROM course_comment
        WHERE is_del = 0
          AND course_id = #{courseId}
        ORDER BY is_top	DESC,like_count DESC,create_time DESC
            LIMIT #{offset},#{pagesize}
    </select>
对应的service层的项目,添加对应的接口和实现类:
/**
     * 某个课程的全部留言(分页)
     * @param courseId 课程编号
     * @param offset 起始位置,或者说数据偏移
     * @param pagesize 每页的条数
     * @return 留言集合
     * 虽然该@Param注解并没有操作,但最好不要这样写,使得不好维护,这里就写上了
     */
    List<CourseComment> getCommentsByCourseId(@Param("courseId") Integer courseId, @Param("offset") 
                                              Integer offset, @Param("pagesize") Integer pagesize);

 @Override
    public List<CourseComment> getCommentsByCourseId(Integer courseId, Integer offset, Integer 
                                                     pagesize) {
        List<CourseComment> commentsByCourseId = 
            courseCommentDao.getCommentsByCourseId(courseId, offset, pagesize);

        return commentsByCourseId;
    }
对应的测试类方法:
  @Test
    public void getCommentsByCourseId(){
        int pageSize = 20; //每页条数
        int pageIndex = 1; //页码

        List<CourseComment> commentsByCourseId = 
            commentService.getCommentsByCourseId(1, (pageIndex -1)*20, pageSize);
        for(int i = 0;i<commentsByCourseId.size();i++){
            CourseComment courseComment = commentsByCourseId.get(i);
            System.out.println((i+1) + " 楼 【"+courseComment.getUserName()+"】 说:" + 
                               courseComment.getComment());

        }
    }
测试后,若有对应数据,则测试成功
对应的web层项目也添加对应的接口:
 /**
     * 某个课程的全部留言(分页)
     * @param courseId 课程编号
     * @param offset 起始位置,或者说数据偏移
     * @param pagesize 每页的条数
     * @return 留言集合
     */
    List<CourseComment> getCommentsByCourseId(@Param("courseId") Integer courseId, @Param("offset") 
                                              Integer offset, @Param("pagesize") Integer pagesize);

对应的CommentController类:
  @GetMapping("comment/getCourseCommentList/{courseId}/{pageIndex}/{pageSize}")
    public List<CourseComment> 
        getCommentsByCourseId(@PathVariable("courseId") Integer courseId,
                              @PathVariable("pageIndex")Integer pageIndex,
                              @PathVariable("pageSize")Integer pageSize) {
        int pagesize = pageSize; //每页条数
        int pageindex = pageIndex; //页码

        List<CourseComment> commentsByCourseId = 
            commentService.getCommentsByCourseId(courseId, (pageindex-1)*20, pagesize);


        return commentsByCourseId;
    }
//注意:对应的limit 0,20(假设的一组数据)中0这个数值,不能是负数,-0也不可,否则报错
留言 点赞,取消赞(这里并没有按照两个访问地址进行操作,我进行了取反使得实现,即只有一个访问地址)
但最好按照下面来,因为是规定的:

在这里插入图片描述

一般在表里面,显示0,或者1的解释时,一般0代表否或者说未,1代表是
如是否删除,那么0代表未删除,1代表已删除,等等
在dao层项目里添加对应的接口及其配置文件:
/**
     * 查看某个用户的某条留言是否点过赞
     * @param cid 对应用户的对应的评论id
     * @param uid 用户编号
     * @return 0:没点过赞,1:点过赞(一般只会返回1或者0,因为用户对同一个留言一般只能点赞一次,或者取消)
     * 即基本只有点赞和没有点赞这两个状态
     */
    Integer existsFavorite(@Param("cid") Integer cid,@Param("uid") Integer uid);

    /**
     * 没有点过赞的,则保存点赞信息,当然这里我进行统一处理了,所以还有其他作用
     * @param comment_id 对应用户的对应的评论id
     * @param user_id 用户编号
     * @return 0:保存失败,1:保存成功,也可以说是受影响的行数
     */
    Integer saveCommentFavorite(@Param("comment_id") Integer comment_id,@Param("user_id") Integer 
                                user_id);


    /**
     *
     * @param is_del 状态改变,0:点赞,1:未点赞
     * @param comment_id 对应用户的对应的评论id
     * @param user_id 用户编号
     * @return 受影响的行数
     */
    Integer updateFavoriteStatus(@Param("is_del") Integer is_del,@Param("comment_id") Integer 
                                 comment_id,@Param("user_id") Integer user_id);

    /**
     *
     * @param comment_id 对应用户的对应的评论id
     * @param user_id 用户编号
     * @return 点赞状态,0:点赞,1:未点赞
     */
    Integer FavoriteStatus(@Param("comment_id") Integer comment_id,@Param("user_id") Integer 
                           user_id);

    /**
     * 更新点赞的数量
     * @param like_count 点赞则加1,取消赞则减一
     * @param comment_id 对应用户的对应的评论id
     * @return 受影响的行数
     */
    Integer updateLikeCount(@Param("like_count") Integer like_count,@Param("comment_id") Integer 
                            comment_id);

 <!--查看某个用户的某条留言是否点过赞-->
        <select id="existsFavorite" resultType="Integer">
  <!--count()用来统计条数-->
            select count(*) from course_comment_favorite_record
            where comment_id = #{cid} and user_id = #{uid}
        </select>
    <!--
    没有点过赞的,则保存点赞信息,因为点过了,那么信息一般是存在的
    即这时一般需要进行修改对应字段值,实现是否点赞等等,而这里是保存第一次点赞的,即也就是还没有点过赞的
    -->
    <insert id="saveCommentFavorite">
        insert  into 
        `course_comment_favorite_record`(`user_id`,`comment_id`,`is_del`,`create_time`,
        `update_time`) values
  (#{user_id},#{comment_id},0,sysdate(),sysdate())
    </insert>

    <!--
    若信息存在,即点过赞,那么进行修改对应字段的值
    也就是is_del状态,0表示点赞,1表示未点赞,对应于未删除和已删除(虽然大多数是这样解释而已,但这里并不是)
    -->
    <update id="updateFavoriteStatus">
    update course_comment_favorite_record set is_del = #{is_del} where comment_id = #{comment_id} and 
        user_id = #{user_id}
    </update>

    <!--查询对应的点赞状态-->
    <select id="FavoriteStatus" resultType="Integer">
        select is_del from course_comment_favorite_record 
        where comment_id = #{comment_id} and user_id = #{user_id}
    </select>

   <!--
    点赞之后,对应评论的赞的数量要加一,取消赞则减一
    -->
    <update id="updateLikeCount">
        UPDATE course_comment SET like_count = like_count + #{like_count} WHERE id = #{comment_id}
    </update>

在service层项目中添加对应的接口及其实现类:
/**
     * 没有点过赞的,则保存点赞信息
     * @param comment_id 对应用户的对应的评论id
     * @param user_id 用户编号
     * @return 对应用户的对应的评论id
     */
    Integer saveFavorite(Integer comment_id,Integer user_id);
/*
    先查看当前用户对这条留言是否点过赞
    如果点过,修改对应的字段is_del状态即可,0表示取消赞,1表示已点赞
    没点过,保存对应点赞的信息,一般都是进行点赞,也就是is_del为1(因为第一次)
    每次的点赞和取消赞,对应的评论总赞数要进行添加和减少
     */
    @Override
    public Integer saveFavorite(Integer comment_id, Integer user_id) {
        Integer i = courseCommentDao.existsFavorite(comment_id, user_id);
        if(i == 0){ //没点过赞
            courseCommentDao.saveCommentFavorite(comment_id, user_id);
            courseCommentDao.updateLikeCount(1,comment_id);

        }else{
            Integer is_del = courseCommentDao.FavoriteStatus(comment_id,user_id);
            is_del = (is_del==0?1:0);
            //进行取反,即原来是点过赞的,变成没有点赞,没有点赞的,变成点过赞
            //修改赞的状态
            courseCommentDao.updateFavoriteStatus(is_del,comment_id,user_id);
            if(is_del == 1){
                courseCommentDao.updateLikeCount(-1,comment_id);
            }
            if(is_del == 0){
                courseCommentDao.updateLikeCount(1,comment_id);
            }
            //之所以不用else是为了保留余地,而不是全部操作
        }
        return comment_id;
    }
添加测试方法:
@Test
    public void saveFavorite(){
        Integer integer = commentService.saveFavorite(2, 123);
        System.out.println(integer);

    }
测试后,观察对应数据库的数据的变化,若符合业务条件,则操作成功
由于上面进行多个多个sql语句的操作,那么我们需要添加事务,使得一起操作,否则可能只有某个执行造成数据不合理
我们在对应的方法上加上如下注解:
 @Transactional //添加事务,里面的参数使用默认的,具体解释,在66章博客
    public Integer saveFavorite(Integer comment_id, Integer user_id) {
   //...
    }
这时我们就操作了事务(前面的配置文件里有对应的配置)
假设我们不使用事务来进行操作,那么我们基本只能给出提示,虽然并没有解决,比如说如下:
public Integer saveFavorite(Integer comment_id, Integer user_id) {
        Integer i = courseCommentDao.existsFavorite(comment_id, user_id);
        int i1 = 0;
        int i2 = 0;
        if(i == 0){ //没点过赞
            i1 = courseCommentDao.saveCommentFavorite(comment_id, user_id);
            i2 = courseCommentDao.updateLikeCount(1,comment_id);
        }else{
            Integer is_del = courseCommentDao.FavoriteStatus(comment_id,user_id);
            is_del = (is_del==0?1:0);
            //进行取反,即原来是点过赞的,变成没有点赞,没有点赞的,变成点过赞
            //修改赞的状态
            i1 = courseCommentDao.updateFavoriteStatus(is_del,comment_id,user_id);
            if(is_del == 1){
                i2 = courseCommentDao.updateLikeCount(-1,comment_id);
            }
            if(is_del == 0){
                i2 = courseCommentDao.updateLikeCount(1,comment_id);
            }
        }
        
        if(i1 == 0 || i2 == 0){ //只要有一个是0,那么就有一个是失败的
            throw new RuntimeException("点赞失败");
        }
        return comment_id;
    }
//我们会发现,在不使用事务时,也可以给出提示,使得有问题出现,从而进行手动解决
根据上面的解释,为了全方位的进行好的优化,可以事务与判断提示一起操作,对应的方法修改成如下:
 /*
    先查看当前用户对这条留言是否点过赞
    如果点过,修改对应的字段is_del状态即可,0表示取消赞,1表示已点赞
    没点过,保存对应点赞的信息,一般都是进行点赞,也就是is_del为1(因为第一次)
    每次的点赞和取消赞,对应的评论总赞数要进行添加和减少
     */
    @Override
    @Transactional
    public Integer saveFavorite(Integer comment_id, Integer user_id) {
        Integer i = courseCommentDao.existsFavorite(comment_id, user_id);
        int i1 = 0;
        int i2 = 0;
        if(i == 0){ //没点过赞
            i1 = courseCommentDao.saveCommentFavorite(comment_id, user_id);
            i2 = courseCommentDao.updateLikeCount(1,comment_id);

        }else{
            Integer is_del = courseCommentDao.FavoriteStatus(comment_id,user_id);
            is_del = (is_del==0?1:0);
            //进行取反,即原来是点过赞的,变成没有点赞,没有点赞的,变成点过赞
            //修改赞的状态
            i1 = courseCommentDao.updateFavoriteStatus(is_del,comment_id,user_id);
            if(is_del == 1){
                i2 = courseCommentDao.updateLikeCount(-1,comment_id);
            }
            if(is_del == 0){
                i2 = courseCommentDao.updateLikeCount(1,comment_id);
            }
        }

        if(i1 == 0 || i2 == 0){ //只要有一个是0,那么就有一个是失败的
            throw new RuntimeException("点赞失败");
        }
        return comment_id;
    }
//我们会发现,在不使用事务时,也可以给出提示,使得有问题出现,从而进行手动解决


//这样就可以防止,对应sql没有报错(不报错也就不会执行回滚,而是提交)
//但sql却不执行的情况,而没有给出问题,虽然这种情况基本不会出现
//但我们也要必须手动的报错,使得回滚进行防止数据问题
//也是一种防御手段

//实际上之所以判断为0,是认为前面的是没有问题的,因为数据库没有数据才会出现0
//即返回的条数没有,那么必然是sql的问题,虽然保存基本是不会出现该值为0的问题的
//因为保存基本只有直接的失败,自然后面的不会执行
//但也只是防止而已,加上并没有什么坏处

再次进行测试,若数据正确,那么测试成功
实际上这里对应的取消赞和点赞是一起操作的,实际上虽然可以分开,但并不需要,只要取反即可
当然你也可以进行分开操作,只是有点麻烦而已(多加一个service层方法)
对应的web层项目中添加对应的接口:
/**
     * 没有点过赞的,则保存点赞信息
     * @param comment_id 对应用户的对应的评论id
     * @param user_id 用户编号
     * @return 对应用户的对应的评论id
     */
    Integer saveFavorite(Integer comment_id,Integer user_id);
添加对应的CommentController类的部分方法:
 @GetMapping("comment/Favorite/{commentid}/{userid}")
    public Integer Favorite(@PathVariable("commentid") Integer 
                            commentid,@PathVariable("userid")Integer userid) {
        Integer integer = commentService.saveFavorite(commentid, userid);
        return integer;
    }
启动测试,若对应数据库的数据的确正确,那么操作成功,但这里可能会出现问题,看如下解释
消费方进行远程调用时,服务方需要有对应的方法,否则会报错,但在这个前提下,对应的路径,也就是包的路径需要一致
正是因为这样,所有我们使用对应的@Transactional注解时,由于是默认操作JDK代理
那么当前的类可能会进行改变(对于dubbo的@Service注解来说)
那么对应的@Service注解会使得目标有问题,即对应的接口可能不是操作我们的接口,即路径不同了
那么由于消费方的路径不匹配,那么一般会出现空指针异常
虽然dubbo不操作JDK代理,但是dubbo一般可以操作cglib代理,但前提是,需要对应的@Service注解指定对应的接口
添加部分spring-dao.xml:
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
<!--transactionManager事务管理器id,已经写好的,proxy-target-class="true"设置为cglib代理-->
修改对应的CommentServiceImpl类(即CommentService的实现类):
@Service(interfaceClass = CommentService.class) 
//这里指定对应的接口,不指定,那么对应的接口会使用默认的,一般就基本找不到了
public class CommentServiceImpl implements CommentService {
//...
}
到此,再次启动,若返回数据,则基本成功
现在我们来操作对应的个人资料的修改
之前我们进行了用户注册,但一般情况下,我们是需要有头像的,且可以修改名称
为了实现这样的功能,我们需要加上fastdfs对应的依赖,使得保存对应的头像(图片)
 		<dependency>
             <groupId>net.oschina.zcx7878</groupId>
             <artifactId>fastdfs-client-java</artifactId>
             <version>1.27.0.0</version>
        </dependency>
        <dependency>
            <!--
           有对应的文件操作,比如
           使用IOUtils完成文件的复制,这个类直接封装了对输入流和输出流的读取写入操作
           注意:输出流会对路径(目录)进行判断,即必须是存在的目录,否则不会执行读取写入操作,即报错
           但文件不会,会自动创建文件
           IOUtils.copy(InputStream input, OutputStream output)
           -->
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-io</artifactId>
             <version>1.3.2</version>
            <!--若用到了,自然会用,没有用到,可以删除-->
        </dependency>
在dao层项目下,加上对应的配置,添加config包,然后创建fastdfs-client.properties:
##fastdfs-client.properties
fastdfs.connect_timeout_in_seconds = 5
fastdfs.network_timeout_in_seconds = 30
fastdfs.charset = UTF-8
fastdfs.http_anti_steal_token = false
fastdfs.http_secret_key = FastDFS1234567890
fastdfs.http_tracker_http_port = 80
#记得改成自己的端口,实际上可以说是去tracker里获得好的storage组的服务器地址
fastdfs.tracker_servers = 192.168.164.128:22122
在对应的测试类里(user包下的那个),添加如下代码:
@Test
    public void updateUserInfo(){
        try {
            //加载配置文件
            ClientGlobal.initByProperties("config/fastdfs-client.properties");

            //创建tracker客户端,该客户端会使用ClientGlobal类,即操作使用了上面的加载
            TrackerClient trackerClient = new TrackerClient();

            //通过trackerClient客户端获取tracker连接服务并返回
            //即根据配置文件来获取服务器的tracker来进行操作
            TrackerServer trackerServer = trackerClient.getConnection();

            //声明storage服务
            StorageServer storageServer = null;

            //定义storage客户端,传递对应的tracker连接,和storage服务的声明
            StorageClient1 client1 = new StorageClient1(trackerServer, storageServer);

            //定义文件元信息(使用org.csource.common包下的该类),一般上传时需要
            NameValuePair[] list = new NameValuePair[1];
            list[0] = new NameValuePair("fileName","1.jpg");

            //使用StorageClient1时,会根据trackerServer,即tracker连接来得到storageServer的信息
            //然后返回对应的storage地址信息
            //使得操作这个信息进行上传,然后返回具体特殊的文件ID
            String fileID = client1.upload_file1("E:\\img\\back.jpg", "png", list);

            System.out.println(fileID);
    }catch (Exception e){
            e.printStackTrace();
        }

}
然后取对应的fastdfs服务器查看是否有文件,若要,则保存成功(具体的fastdfs配置看82章博客)
然后进行下载,再次添加对应的测试方法:
@Test
    public void DownloadUserInfo() {
        try {
            //加载配置文件
            ClientGlobal.initByProperties("config/fastdfs-client.properties");

            //创建tracker客户端,该客户端会使用ClientGlobal类,即操作使用了上面的加载
            TrackerClient trackerClient = new TrackerClient();

            //通过trackerClient客户端获取tracker连接服务并返回
            //即根据配置文件来获取服务器的tracker来进行操作
            TrackerServer trackerServer = trackerClient.getConnection();

            //声明storage服务
            StorageServer storageServer = null;

            //定义storage客户端,传递对应的tracker连接,和storage服务的声明
            StorageClient1 client1 = new StorageClient1(trackerServer, storageServer);

            byte[] bytes = client1.download_file1("group1/M00/00/00/wKikgGLTzP-
            AYBaqAANjV8eqsnI531.png");

            //没有找到对应图片的话,返回就是null,数组可以被赋值为null
                                                  //这里设置自己的下载路径
            FileOutputStream fileOutputStream = new FileOutputStream(new File("E:/xxxxxx.jpg"));
            fileOutputStream.write(bytes);
            fileOutputStream.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
执行后,看看自己的路径是否有该文件,有,则表示可以下载,也更加的表示前面的保存文件成功
测试完成后,接下来我们在dao层的项目的UserDao接口添加对应的接口及其配置文件:
 /**
     *
     * @param name 用户昵称
     * @param portrait 对应的头像在fastdfs服务器的地址
     * @param uid 用户id
     * @return 受影响的条数
     */
    Integer updateUserInfo(@Param("name") String name,@Param("portrait") String portrait,
                           @Param("uid") String uid);

  <update id="updateUserInfo">
<!--一般我们登录进行,就代表有数据,所有只需要修改即可-->
        UPDATE user SET NAME = #{name} ,portrait = #{portrait} WHERE id = #{uid}
    </update>
对应的service层的接口及其实现类:
/**
     *
     * @param name 用户昵称
     * @param portrait 对应的头像在fastdfs服务器的地址
     * @param uid 用户id
     * @return 受影响的条数
     */
    Integer updateUserInfo(String name,String portrait,String uid);

  @Override
    public Integer updateUserInfo(String name, String portrait, String uid) {
        Integer integer = userDao.updateUserInfo(name, portrait, uid);

        return integer;
    }
对应的web层项目:
在这之前添加对应的依赖:
	<dependency>
             <!--
	有HttpServletRequest,在使用后面的MultipartHttpServletRequest类时,可以操作对应的方法
如getParameter()方法
-->
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <scope>provided</scope>
            <version>2.5</version> <!--一般来说,这个相关的依赖(如javax.servlet-api还是这里的这个)我们通常需要在tomcat7中需要<scope>provided</scope>,因为可能有冲突(tomcat7自带他们相关的,冲突就是对应的代码发生冲突,由于没有覆盖,所以冲突的,这里注意即可),而正是因为tomcat才有,所以在启动之前,却需要,启动后与tomcat冲突,所以这里加上了provided,但并不代表tomcat没有的,只是我们没有启动而已,而这个也是防止我们idea的判断错误,这是一个细节-->
        </dependency>

 <dependency>
     <!--上传解析器操作时,一般需要这个,否则可能报错-->
             <groupId>commons-fileupload</groupId>
             <artifactId>commons-fileupload</artifactId>
             <version>1.3.3</version>
        </dependency>
添加部分spring-consumer.xml:
 <!--上传文件的解析器(规定上传文件的大小限制)-->
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 上传文件最大限制约等于:2GB=2048MB约等于2048000kb约等于2048000000B-->
        <property name="maxUploadSize" value="2048000000"/>
    </bean>
对应的接口:
/**
     *
     * @param name 用户昵称
     * @param portrait 对应的头像在fastdfs服务器的地址
     * @param uid 用户id
     * @return 受影响的条数
     */
    Integer updateUserInfo(String name,String portrait,String uid);

在lagou包下,创建entity包,并创建UserVo类:
package com.lagou.entity;

/**
 *
 */

public class UserVo {

    private String success; //对应的成功,如true等
    private String state; //状态码,如200代表成功
    private String message; //修改结果,如修改成功,或者修改失败
    private Object Vo; //对应数据存放处,如受影响的行数,对象的结果等等

    @Override
    public String toString() {
        return "UserVo{" +
                "success='" + success + '\'' +
                ", state='" + state + '\'' +
                ", message='" + message + '\'' +
                ", Vo=" + Vo +
                '}';
    }

    public UserVo() {
    }

    public UserVo(String success, String state, String message, Object vo) {
        this.success = success;
        this.state = state;
        this.message = message;
        Vo = vo;
    }

    public String getSuccess() {
        return success;
    }

    public void setSuccess(String success) {
        this.success = success;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Object getVo() {
        return Vo;
    }

    public void setVo(Object vo) {
        Vo = vo;
    }
}

添加部分UserController类:
@PostMapping("updateUserInfo")
    public UserVo updateUserInfo(MultipartHttpServletRequest request) {
        //MultipartHttpServletRequest用到了MultipartFile
        //由于是使用MultipartFile需要对应的文件解析器进行注入(自动的)
        //即基本要配置文件解析器,否则报错
        Integer ss = null;
        try {
            MultipartFile name = request.getFile("portrait");
            //得到文件全名
            String originalFilename = name.getOriginalFilename();
            //根据文件的全名得到文件后缀名
            String substring = originalFilename.substring(originalFilename.indexOf(".") + 1);
            String UUID = java.util.UUID.randomUUID().toString() + "." + substring;
            //指向对应文件
            File file = new File("E:/upload/" + UUID); //一般来说,如果在linux中那么最好不要使用转义的\,因为他们不是一个文件系统,可能linux会将\看成一个文件名称而不是路径
            //将对应文件的数据输出到指向的文件
            name.transferTo(file);

            //得到绝对路径,使得fastdfs可以操作对应的文件(这里一般指图片)
            String absolutePath = file.getAbsolutePath();

            //加载配置文件
            ClientGlobal.initByProperties("config/fastdfs-client.properties");

            //创建tracker客户端,该客户端会使用ClientGlobal类,即操作使用了上面的加载
            TrackerClient trackerClient = new TrackerClient();

            //通过trackerClient客户端获取tracker连接服务并返回
            //即根据配置文件来获取服务器的tracker来进行操作
            TrackerServer trackerServer = trackerClient.getConnection();

            //声明storage服务
            StorageServer storageServer = null;

            //定义storage客户端,传递对应的tracker连接,和storage服务的声明
            StorageClient1 client1 = new StorageClient1(trackerServer, storageServer);

            //定义文件元信息(使用org.csource.common包下的该类),一般上传时需要
            NameValuePair[] list = new NameValuePair[1];
            list[0] = new NameValuePair("fileName", originalFilename);

            //使用StorageClient1时,会根据trackerServer,即tracker连接来得到storageServer的信息
            //然后返回对应的storage地址信息
            //使得操作这个信息进行上传,然后返回具体特殊的文件ID
            String fileID = client1.upload_file1(absolutePath, substring, list);

            String name1 = request.getParameter("name");
            String userid = request.getParameter("userid");

            ss = userService.updateUserInfo(name1, fileID, userid);


        } catch (Exception e) {
            e.printStackTrace();
        }

         if(ss!=0) {
            return new UserVo("true", "200", "修改成功", ss);
        }else {
            return new UserVo("true", "200", "修改失败", ss);
        }

    }
在对应的index.jsp代码中添加如下代码:
<form action="/user/updateUserInfo" method="post" enctype="multipart/form-data">
    文件:<input type="file" name="portrait"><br>
    昵称:<input type="text" name="name"><br>
    用户id:<input type="text" name="userid"><br>
    <button>提交</button>
</form>
启动测试,若本地以及fastdfs都有对应的文件,那么操作成功
然后去访问对应自己配置好的fastdfs服务器,比如我这里就是
http://192.168.164.128/group1/M00/00/00/wKikgGLVAiyAe_bPAAA50D1yhyo358.png
group1/M00/00/00/wKikgGLVAiyAe_bPAAA50D1yhyo358.png是对应图片在服务器的地址,也就是文件特殊id
对应的用户信息修改成功,这里主要是昵称和头像,接下来我们操作修改密码
对应的dao层项目的接口及其配置文件:
 /**
     *
     * @param password 修改后的密码
     * @param uid 用户id
     * @return 受影响的条数
     */
    Integer updatePassword(@Param("password") String password,@Param("uid") String uid);

  <update id="updatePassword">
        UPDATE user SET PASSWORD = #{password} WHERE id = #{uid}
    </update>
对应的service层项目的接口及其实现类:
/**
     *
     * @param password 修改后的密码
     * @param uid 用户id
     * @return 受影响的条数
     */
    Integer updatePassword(String password,String uid);
 @Override
    public Integer updatePassword(String password, String uid) {
        Integer integer = userDao.updatePassword(password, uid);
        return integer;
    }
测试类TestUser的部分方法:
 @Test
    public void updatePassword(){
        Integer integer = userService.updatePassword("2","100030018");
        System.out.println(integer);
    }
测试完后,进行下来的操作
web层项目的接口(UserService接口的部分方法):
/**
     *
     * @param password 修改后的密码
     * @param uid 用户id
     * @return 受影响的条数
     */
    Integer updatePassword(String password,String uid);
UserController类的部分方法:
 @GetMapping("updatePassword")
    public UserVo updatePassword(String password, String uid) {
        Integer integer = userService.updatePassword(password, uid);
        if(integer!=0) {
            return new UserVo("true", "200", "修改成功", integer);
        }else{
            return new UserVo("true", "200", "修改失败", integer);
        }

    }
测试后,若修改成功,则操作完成
最后,我们也可以进行根据用户id进行查询用户,虽然我们并不需要,但在有些时候可能会用到
所有后面的代码可以不做考虑:
对应的dao层项目的接口及其配置文件:
 /**
     *
     * @param uid 用户id
     * @return 用户对象
     */
    User SelectUserById(String uid);
    //虽然不使用Integer,实际上是防止前台输入汉字或者其他不是整数的,那么可能会使得报错(controller层那里)

<select id="SelectUserById" resultMap="UserMap">
    select * from user where id = #{uid}
    <!--有些字段使用自动的得到不,所以需要指定resultMap,因为对应的set操作不到如update_time自字段-->
</select>
对应的service代码:
 /**
     *
     * @param uid 用户id
     * @return 用户对象
     */
    User SelectUserById(String uid);
 @Override
    public User SelectUserById(String uid) {
        User user = userDao.SelectUserById(uid);
        return user;
    }
部分测试类:
@Test
    public void SelectUserById(){
        User user = userService.SelectUserById("100030018");
        System.out.println(user);
    }
测试完后,进行下面的操作
对应的web层项目的接口:
 /**
     *
     * @param uid 用户id
     * @return 用户对象
     */
    User SelectUserById(String uid);
UserController类的部分方法:
@GetMapping("SelectUserById")
    public UserVo SelectUserById(String uid) {
        User user = userService.SelectUserById(uid);
        return new UserVo("true","200","查询成功",user);
        //查询基本不需要判断,因为基本只能是sql语句报错
        //而sql语句报错,自然一般会直接报错停止,而不会返回我们需要的具体数据

    }
至此,后端接口编写完毕
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值