谷粒学院项目总结

谷粒学院项目总结,第1张

谷粒学院项目总结

文章目录

谷粒学院项目总结

1.项目介绍

1.1 采用的商业模式1.2 功能模块1.3 采用技术 2.Mybatis-Plus相关配置

2.1 配置分页插件2.2 自动填充2.3 代码生成器 3.Swagger配置4.统一返回数据格式

4.1 统一结果返回类4.2 统一定义返回码 5.统一异常处理

5.1 创建统一异常处理器5.2 自定义异常处理 6.统一日志处理

6.1 配置日志级别6.2 Logback日志6.3 将错误日志输出到文件 7.整合阿里云OSS8.整合EasyExcel9.整合阿里云视频点播10.整合JWT单点登录

10.1 单点登录10.2 引入依赖10.3 创建JWT工具类10.4 封装前端接受和传来的信息10.5 controller层10.6 service层 11.整合阿里云短信

11.1 准备工作11.2 具体实现 12.整合微信扫描登录13.定时统计每天的注册人数

13.1 数据库表和实体类13.2 实现接口13.3 远程调用13.4 定时任务 14.整合微信支付15.权限管理模块16.网关gateway

16.1 准备工作16.2 编写基础配置和路由规则16.3 网关解决跨域问题16.4 Filter使用16.5 自定义异常处理 17.Redis进行缓存18.项目总结

谷粒学院项目总结 1.项目介绍 1.1 采用的商业模式

B2C模式(Business To Customer 会员模式)

商家到用户,这种模式是自己制作大量自有版权的视频,放在自有平台上,让用户按月付费或者按年付费。 这种模式简单,快速,只要专心录制大量视频即可快速发展,其曾因为 lynda 的天价融资而大热。 但在中国由于版权保护意识不强,教育内容易于复制,有海量的免费资源的竞争对手众多等原因,难以取得像样的现金流

1.2 功能模块

谷粒学院,是一个B2C模式的职业技能在线教育系统,分为前台用户系统和后台运营平台

1.3 采用技术

2.Mybatis-Plus相关配置 2.1 配置分页插件

可以在config包下新建一个Mybatis-Plus的配置类MyBatisPlusConfig统一管理:

//使其成为配置类
@Configuration
//开启事务管理
@EnableTransactionManagement
//指定要变成实现类的接口所在的包,然后包下面的所有接口在编译之后都会生成相应的实现类(和在每个类加@Mapper作用相同)
@MapperScan("com.atguigu.eduservice.mapper")
public class MyBatisPlusConfig {
    //配置分页插件
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
        return interceptor;
    }
}
2.2 自动填充

新建一个MymetaObjectHandler类实现metaObjectHandler接口:

//注入到spring
@Component
public class MymetaObjectHandler implements metaObjectHandler {
	//插入时自动填充
    @Override
    public void insertFill(metaObject metaObject) {
        //属性名称,不是字段名称
        this.setFieldValByName("gmtCreate", new Date(), metaObject);
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }

	//更新时自动填充
    @Override
    public void updateFill(metaObject metaObject) {
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }
}

在需要自动填充的字段加上注解:

2.3 代码生成器
public class CodeGenerator {
    @Test
    public void run() {
        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 2、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        //项目路径
        gc.setOutputDir("D:\guli_parent\service\service_edu" + "/src/main/java");

        gc.setAuthor("xppll");
        //生成后是否打开资源管理器
        gc.setOpen(false);
        //重新生成时文件是否覆盖
        gc.setFileOverride(false);

        //UserServie
        gc.setServiceName("%sService");    //去掉Service接口的首字母I

        //主键策略
        gc.setIdType(IdType.ID_WORKER_STR);
        //定义生成的实体类中日期类型
        gc.setDateType(DateType.ONLY_DATE);
        //开启Swagger2模式
        gc.setSwagger2(true);

        mpg.setGlobalConfig(gc);

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

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

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

        strategy.setInclude("edu_course", "edu_course_description", "edu_chapter", "edu_video");

        //数据库表映射到实体的命名策略
        strategy.setNaming(NamingStrategy.underline_to_camel);
        //生成实体时去掉表前缀

        strategy.setTablePrefix(pc.getModuleName() + "_");
        //数据库表字段映射到实体的命名策略
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        // lombok 模型 @Accessors(chain = true) setter链式 *** 作
        strategy.setEntityLombokModel(true); 
        //restful api风格控制器
        strategy.setRestControllerStyle(true); 
        //url中驼峰转连字符
        strategy.setControllerMappingHyphenStyle(true); 
        mpg.setStrategy(strategy);
        
        // 6、执行
        mpg.execute();
    }
}
3.Swagger配置

引入Swagger相关依赖:


    io.springfox
    springfox-swagger2
    provided 


    io.springfox
    springfox-swagger-ui
    provided 
 

可以在config包下新建一个Swagger的配置类SwaggerConfig统一管理:

@Configuration  //配置类
@EnableSwagger2 //swagger注解
public class SwaggerConfig {
    @Bean
    public Docket webApiConfig() {
        return new Docket(documentationType.SWAGGER_2)
            .groupName("webApi")
            .apiInfo(webApiInfo())
            .select()
            .paths(Predicates.not(PathSelectors.regex("/admin/.*")))
            .paths(Predicates.not(PathSelectors.regex("/error.*")))
            .build();
    }

    private ApiInfo webApiInfo() {
        return new ApiInfoBuilder()
            .title("网站-课程中心API文档")
            .description("本文档描述了课程中心微服务接口定义")
            .version("1.0")
            .contact(new Contact("Helen", "http://atguigu.com",
                                 "[email protected]"))
            .build();
    }

}

访问edu模块,可以看到:

4.统一返回数据格式

项目中我们会将响应封装成json返回,一般我们会将所有接口的数据格式统一, 使前端(iOS Android, Web)对数据的 *** 作更一致、轻松。 一般情况下,统一返回数据格式没有固定的格式,只要能描述清楚返回的数据状态以及要返回的具体数据就可以。但是一般会包含状态码、返回消息、数据这几部分内容

4.1 统一结果返回类

在commonutils(公共工具类包)包下创建统一结果返回类R

@Data
public class R {
    //swagger的注解
    @ApiModelProperty(value = "是否成功")
    private Boolean success;
    @ApiModelProperty(value = "返回码")
    private Integer code;
    @ApiModelProperty(value = "返回消息")
    private String message;
    @ApiModelProperty(value = "返回数据")
    private Map data = new HashMap();

    //构造方法私有
    public R() {
    }

    //成功静态方法
    public static R ok() {
        R r = new R();
        r.setSuccess(true);
        r.setCode(ResultCode.SUCCESS);
        r.setMessage("成功");
        return r;
    }
    //失败静态方法
    public static R error(){
        R r = new R();
        r.setSuccess(false);
        r.setCode(ResultCode.ERROR);
        r.setMessage("失败");
        return r;
    }

    //返回this是为了链式编程,例如 R.ok().code().message()
    public R success(Boolean success) {
        this.setSuccess(success);
        return this;
    }

    public R message(String message) {
        this.setMessage(message);
        return this;
    }

    public R code(Integer code) {
        this.setCode(code);
        return this;
    }

    public R data(String key, Object value) {
        this.data.put(key, value);
        return this;
    }

    public R data(Map map) {
        this.setData(map);
        return this;
    }

}
4.2 统一定义返回码

这里又许多种方式,这里列举两种:

1.创建接口定义返回码

public interface ResultCode {
    public static Integer SUCCESS = 20000;
    public static Integer ERROR = 20001;
}

2.创建枚举类定义返回码

public enum ErrorCode {

    PARAMS_ERROR(10001, "参数有误"),
    ACCOUNT_PWD_NOT_EXIST(10002, "用户名或密码不存在"),
    TOKEN_ERROR(10003, "token不合法"),
    ACCOUNT_EXIST(10004, "账户已存在"),
    NO_PERMISSION(70001, "无访问权限"),
    SESSION_TIME_OUT(90001, "会话超时"),
    NO_LOGIN(90002, "未登录");

    private int code;
    private String msg;

    ErrorCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    //get,set方法...
}
5.统一异常处理 5.1 创建统一异常处理器

在handler包下创建统一异常处理类GlobalExceptionHandler:

//对加了@Controller的方法进行拦截处理,AOP的实现
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    //进行一次处理,处理Exception.class的异常
    @ExceptionHandler(Exception.class)
    //返回json数据,不加的话直接返回页面
    @ResponseBody
    public R error(Exception e) {
        e.printStackTrace();
        //将信息写到日志文件中去
        log.error(e.getMessage());
        return R.error().message("执行了全局异常处理...");
    }

}

还可以处理特定异常:

//添加特定异常方法
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public R error(ArithmeticException e){
    e.printStackTrace();
    return R.error().message("执行了特定异常");
}
5.2 自定义异常处理

在handler包下创建自定义异常类GuliException:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class GuliException extends RuntimeException {
    //状态码
    private Integer code;
    //异常信息
    private String msg;
}

处理自定义异常:

//添加自定义异常
//需要自己手动抛出
@ExceptionHandler(GuliException.class)
@ResponseBody
public R error(GuliException e){
    log.error(e.getMessage());
    e.printStackTrace();
    //传入自己定义的参数
    return R.error().code(e.getCode()).message(e.getMsg());
}

栗子:自己手动抛出

@GetMapping("findAll")
public R list(){
    try {
        int a = 10/0;
    }catch(Exception e) {
        throw new GuliException(20003,"出现自定义异常");
    }
    List list = teacherService.list(null);
    return R.ok().data("items",list);
}
6.统一日志处理 6.1 配置日志级别

日志记录器(Logger)的行为是分等级的。如下表所示: 分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL 默认情况下,spring boot从控制台打印出来的日志级别只有INFO及以上级别,可以配置日志级别:

# 设置日志级别
logging.level.root=WARN

这种配置方式只能将日志打印在控制台上

6.2 Logback日志

spring boot内部使用Logback作为日志实现的框架

配置logback日志

注意:需要删除application.properties中的其它日志配置

在resources 中创建 logback-spring.xml(名字必须一模一样!)



    
    
    
    

    logback
    
    

    
    
    
    
    
    
    
    


    
    
        
        
        
            INFO
        
        
            ${CONSOLE_LOG_PATTERN}
            
            UTF-8
        
    


    

    
    
        
        ${log.path}/log_info.log
        
        
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
            UTF-8
        
        
        
            
            ${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log
            
                100MB
            
            
            15
        
        
        
            INFO
            ACCEPT
            DENY
        
    

    
    
        
        ${log.path}/log_warn.log
        
        
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
            UTF-8 
        
        
        
            ${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log
            
                100MB
            
            
            15
        
        
        
            warn
            ACCEPT
            DENY
        
    


    
    
        
        ${log.path}/log_error.log
        
        
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
            UTF-8 
        
        
        
            ${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log
            
                100MB
            
            
            15
        
        
        
            ERROR
            ACCEPT
            DENY
        
    

    
    
    
    
        
        

        
        
            
            
            
            
        
    


    
    

        
            
            
            
            
            
        
    


6.3 将错误日志输出到文件

举个例子:

    GlobalExceptionHandler 中类上添加注解@Slf4j异常输出语句:log.error(e.getMessage());

7.整合阿里云OSS

SpringBoot整合阿里云OSS

8.整合EasyExcel

SpringBoot整合EasyExcel

9.整合阿里云视频点播

SpringBoot整合阿里云视频点播

10.整合JWT单点登录

关于JWT的详细知识可以参考:JWT整合Springboot

10.1 单点登录

单点登录三种常见方式:

    session广播机制实现使用cookie+reids实现使用token实现

10.2 引入依赖

    
    
        io.jsonwebtoken
        jjwt
    

10.3 创建JWT工具类
public class JwtUtils {

    //token过期时间
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    //秘钥
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

    
    public static String getJwtToken(String id, String nickname) {

        String JwtToken = Jwts.builder()
            //设置jwt头信息
            .setHeaderParam("typ", "JWT")
            .setHeaderParam("alg", "HS256")
            //设置分类
            .setSubject("guli-user")
            //设置签发时间
            .setIssuedAt(new Date())
            //设置过期时间=当前时间+过多久过期的时间
            .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
            //设置token主体部分,存储用户信息
            .claim("id", id)
            .claim("nickname", nickname)
            //设置签发算法+秘钥
            .signWith(SignatureAlgorithm.HS256, APP_SECRET)
            .compact();
        return JwtToken;
    }

    
    public static boolean checkToken(String jwtToken) {
        if (StringUtils.isEmpty(jwtToken)) return false;
        try {
            //验证token是否是有效的token
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");
            if (StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if (StringUtils.isEmpty(jwtToken)) return "";
        Jws claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String) claims.get("id");
    }
}
10.4 封装前端接受和传来的信息

登录信息:

@Data
public class UcentMemberVo {
    @ApiModelProperty(value = "手机号")
    private String mobile;

    @ApiModelProperty(value = "密码")
    private String password;
}

注册信息:

@Data
public class RegisterVo {
    private String nickname;
    private String mobile;
    private String password;
    private String code;
}
10.5 controller层

主要有三个接口:

    登录注册登录成功后,根据token获取用户信息,用于前端显示
@CrossOrigin
@RestController
@RequestMapping("/educenter/member")
public class UcenterMemberController {

    @Autowired
    private UcenterMemberService memberService;

    
    @PostMapping("login")
    public R loginUser(@RequestBody UcentMemberVo member) {
        //返回token,使用jwt生成
        String token = memberService.login(member);
        return R.ok().data("token", token);
    }


    
    @PostMapping("register")
    public R registerUser(@RequestBody RegisterVo registerVo) {
        memberService.register(registerVo);
        return R.ok();
    }

    
    @GetMapping("getMemberInfo")
    public R getMemberInfo(HttpServletRequest request) {
        //调用jwt工具类,根据request对象获取头信息,返回用户id
        String memberId = JwtUtils.getMemberIdByJwtToken(request);
        UcentMemberVo member = memberService.getLoginInfo(memberId);
        return R.ok().data("userInfo", member);
    }
}
10.6 service层
@Service
public class UcenterMemberServiceImpl extends ServiceImpl implements UcenterMemberService {

    @Autowired
    private RedisTemplate redisTemplate;

    
    @Override
    public String login(UcentMemberVo member) {
        //获取登录手机号和密码
        String mobile = member.getMobile();
        String password = member.getPassword();
        //1.两个有一个为空,登录失败!
        if (StringUtils.isBlank(mobile) || StringUtils.isBlank(password)) {
            throw new GuliException(20001, "手机号和密码不能为空,登录失败!");
        }
        //2.判断手机号是否存在
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UcenterMember::getMobile, mobile);
        UcenterMember mobileMember = baseMapper.selectOne(queryWrapper);
        if (mobileMember == null) {
            throw new GuliException(20001, "手机号不存在,登录失败!");
        }
        //3.判断密码是否正确
        //数据库的密码加了密
        //需要把输入密码加密在比较
        if (!MD5.encrypt(password).equals(mobileMember.getPassword())) {
            throw new GuliException(20001, "密码错误,登录失败!");
        }

        //4.判断用户是否被禁(封号)
        if (mobileMember.getIsDisabled()) {
            throw new GuliException(20001, "用户已被禁止登录,登录失败!");
        }
        //调用JWT工具类生成token
        //传入id,nickname
        return JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname());
    }

    //注册
    @Override
    public void register(RegisterVo registerVo) {
        //验证码
        String code = registerVo.getCode();
        //手机号
        String mobile = registerVo.getMobile();
        //昵称
        String nickname = registerVo.getNickname();
        //密码
        String password = registerVo.getPassword();
        if (StringUtils.isBlank(mobile) || StringUtils.isBlank(code)
            || StringUtils.isBlank(nickname) || StringUtils.isBlank(password)) {
            throw new GuliException(20001, "传入参数不能为空!,注册失败");
        }
        //从redis取出验证码
        String redisCode = redisTemplate.opsForValue().get(mobile);
		//判断验证码是否失效
        if (StringUtils.isBlank(redisCode)) {
            throw new GuliException(20001, "验证码失效!,注册失败");
        }
        //判断验证码是否正确
        if (!code.equals(redisCode)) {
            throw new GuliException(20001, "验证码错误!,注册失败");
        }
        //判断手机号是否已经注册过
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UcenterMember::getMobile, mobile);
        Integer count = baseMapper.selectCount(queryWrapper);
        if (count > 0) {
            throw new GuliException(20001, "该手机号已经被注册!注册失败");
        }
        //添加到数据库
        UcenterMember member = new UcenterMember();
        member.setMobile(mobile);
        member.setNickname(nickname);
        //密码需要加密
        member.setPassword(MD5.encrypt(password));
        member.setIsDisabled(false);
        member.setAvatar("https://xppll.oss-cn-beijing.aliyuncs.com/2021/12/08/dde5b98fe9dca6b6076file.png");

        baseMapper.insert(member);
    }

    //根据id获取信息传给前端
    @Override
    public UcentMemberVo getLoginInfo(String memberId) {
        UcenterMember member = baseMapper.selectById(memberId);
        UcentMemberVo ucentMemberVo = new UcentMemberVo();
        BeanUtils.copyProperties(member, ucentMemberVo);
        return ucentMemberVo;
    }

}
11.整合阿里云短信

这里实现短信功能为了完成用户的注册

11.1 准备工作

首先需要开通阿里云短信服务

在导入依赖:


    
        com.alibaba
        fastjson
    
    
        com.aliyun
        aliyun-java-sdk-core
    

11.2 具体实现

controller层:

@RestController
@RequestMapping("/edumsm/msm")
public class MsmController {
    @Autowired
    private MsmService msmService;

    @Autowired
    private RedisTemplate reditemplate;

    //通过手机号发送短信的方法
    @GetMapping("send/{phone}")
    public R sendMsm(@PathVariable("phone") String phone) {
        //1.从redis获取验证码,如果获取到直接返回
        String code = reditemplate.opsForValue().get(phone);
        if (!StringUtils.isEmpty(code)) {
            return R.ok();
        }

        //2.调用工具类生成四位随机数,传递给阿里云进行发送
        code = RandomUtil.getFourBitRandom();
        Map param = new HashMap<>();
        param.put("code", code);
        //3.调用service里的方法实现短信发送
        boolean isSend = msmService.send(param, phone);
        if (isSend) {
            //4.发送成功,把发送成功的验证码放到redis中去并设置有效时间
            reditemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
            return R.ok();
        } else {
            //5.发送失败,返回失败信息
            return R.error().message("短信发送失败!");
        }
    }
}

service层:

@Service
public class MsmServiceImpl implements MsmService {
    
    @Override
    public boolean send(Map param, String phone) {

        //手机号为空,返回false
        if (StringUtils.isEmpty(phone)) return false;
        //地域节点,id,密钥
        DefaultProfile profile =
            DefaultProfile.getProfile("default", "xxx", "xxx");
        IAcsClient client = new DefaultAcsClient(profile);

        //设置相关参数
        CommonRequest request = new CommonRequest();
        request.setSysMethod(MethodType.POST);
        request.setSysDomain("dysmsapi.aliyuncs.com");
        request.setSysVersion("2017-05-25");
        request.setSysAction("SendSms");
        //设置发送相关的参数
        //手机号
        request.putQueryParameter("PhoneNumbers", phone);
        //阿里云中申请的 ”签名名称“
        request.putQueryParameter("SignName", "我的谷粒在线教育网站");
        //阿里云申请的 “模板CODE”
        request.putQueryParameter("TemplateCode", "SMS_xxxxx");
        //验证码
        request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param));

        //发送
        try {
            CommonResponse response = client.getCommonResponse(request);
            System.out.println(response.getData());
            return response.getHttpResponse().isSuccess();
        } catch (ClientException e) {
            e.printStackTrace();
        }
        return false;
    }
}
12.整合微信扫描登录

SpringBoot整合微信登录

13.定时统计每天的注册人数 13.1 数据库表和实体类

数据库表statistics_daily:

对应的实体类:

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="StatisticsDaily对象", description="网站统计日数据")
public class StatisticsDaily implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "主键")
    @TableId(value = "id", type = IdType.ID_WORKER_STR)
    private String id;

    @ApiModelProperty(value = "统计日期")
    private String dateCalculated;

    @ApiModelProperty(value = "注册人数")
    private Integer registerNum;

    @ApiModelProperty(value = "登录人数")
    private Integer loginNum;

    @ApiModelProperty(value = "每日播放视频数")
    private Integer videoViewNum;

    @ApiModelProperty(value = "每日新增课程数")
    private Integer courseNum;

    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date gmtCreate;

    @ApiModelProperty(value = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;

}
13.2 实现接口

在service_ucenter模块创建接口,统计某一天的注册人数:

controller层:

//查询某一天的注册人数
@GetMapping("countRegister/{day}")
public R countRegister(@PathVariable("day") String day){
    Integer count=memberService.countRegisterDay(day);
    return R.ok().data("countRegister",count);
}

service层:

@Override
public Integer countRegisterDay(String day) {
    return baseMapper.countRegisterDay(day);
}

mapper层: