• 谷粒学院——前台用户使用系统


    管理员后台管理系统

    https://blog.csdn.net/weixin_45581692/article/details/127264865

    普通用户前台使用系统

    首页数据显示

    环境搭建

    1. 在service模块中创建子模块service_cms
    在这里插入图片描述
    2. 创建配置文件

    # 服务端口号
    server.port=8004
    
    # 服务名
    spring.application.name=service-cms
    
    # 返回json的全局时间格式
    spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    spring.jackson.time-zone=GMT+8
    
    # mysql数据库连接
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/guliedu?serverTimezone=GMT%2B8
    spring.datasource.username=root
    spring.datasource.password=qwer`123
    
    # mybatis日志
    mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    
    # 配置mapper.xml文件的路径
    mybatis-plus.mapper-locations=classpath:com/hxp/educms/mapper/xml/*.xml
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3. 创建数据库表

    CREATE TABLE `crm_banner` (
      `id` char(19) NOT NULL DEFAULT '' COMMENT 'ID',
      `title` varchar(20) DEFAULT '' COMMENT '标题',
      `image_url` varchar(500) NOT NULL DEFAULT '' COMMENT '图片地址',
      `link_url` varchar(500) DEFAULT '' COMMENT '链接地址',
      `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
      `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`),
      UNIQUE KEY `uk_name` (`title`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='首页banner表';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4. 根据表用代码生成器生成代码
    5. 主启动类

    @SpringBootApplication
    @ComponentScan({"com.hxp"})
    @MapperScan("com.hxp.educms.mapper")
    public class CmsApplication {
        public static void main(String[] args) {
            SpringApplication.run(CmsApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    前台显示轮播图

    在这里插入图片描述

    @RestController
    @RequestMapping("/educms/bannerfront")
    public class BannerFrontController {
    
        @Autowired
        private CrmBannerService bannerService;
    
        //查询所有banner
        @GetMapping("getAllBanner")
        public R getAllBanner() {
            List<CrmBanner> list = bannerService.selectAllBanner();
            return R.ok().data("list", list);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    @Service
    public class CrmBannerServiceImpl extends ServiceImpl<CrmBannerMapper, CrmBanner> implements CrmBannerService {
    
        //查询banner
        @Override
        public List<CrmBanner> selectAllBanner() {
            //根据id进行降序排列,显示排列之后前两条记录
            QueryWrapper<CrmBanner> wrapper = new QueryWrapper<>();
            wrapper.orderByDesc("id");
            wrapper.last("limit 2");    //last方法,拼接sql
            List<CrmBanner> list = baseMapper.selectList(wrapper);
            return list;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    热门课程和热门讲师

    在这里插入图片描述在这里插入图片描述

    在课程模块中,写前台显示热门课程和热门讲师的接口,为了区分创建一个front包,表示是前台系统相关的接口。
    在这里插入图片描述

    @RestController
    @RequestMapping("/eduservice/indexfront")
    public class IndexFrontController {
        @Autowired
        private EduCourseService courseService;
        @Autowired
        private EduTeacherService teacherService;
    
        //查询前8条热门课程,查询前4条名师
        @GetMapping("index")
        public R index() {
            //查询前8条热门课程
            QueryWrapper<EduCourse> wrapper = new QueryWrapper<>();
            wrapper.orderByDesc("id");
            wrapper.last("limit 8");
            List<EduCourse> eduList = courseService.list(wrapper);
    
            //查询前4条名师
            QueryWrapper<EduTeacher> teacherWrapper = new QueryWrapper<>();
            teacherWrapper.orderByDesc("id");
            teacherWrapper.last("limit 4");
            List<EduTeacher> teacherList = teacherService.list(teacherWrapper);
    
            return R.ok().data("eduList",eduList).data("teacherList",teacherList);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    添加redis做缓存

    1. 在公共模块的pom.xml中引入redis依赖

    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-redisartifactId>
    dependency>
    
    <dependency>
        <groupId>org.apache.commonsgroupId>
        <artifactId>commons-pool2artifactId>
        <version>2.6.0version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2. 在service-base模块添加redis配置类

    @EnableCaching  //开启缓存
    @Configuration
    public class RedisConfig extends CachingConfigurerSupport {
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            RedisSerializer<String> redisSerializer = new StringRedisSerializer();
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(om);
            template.setConnectionFactory(factory);
            //key序列化方式
            template.setKeySerializer(redisSerializer);
            //value序列化
            template.setValueSerializer(jackson2JsonRedisSerializer);
            //value hashmap序列化
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            return template;
        }
    
        @Bean
        public CacheManager cacheManager(RedisConnectionFactory factory) {
            RedisSerializer<String> redisSerializer = new StringRedisSerializer();
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
            //解决查询缓存转换异常的问题
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(om);
            // 配置序列化(解决乱码的问题),过期时间600秒
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                    .entryTtl(Duration.ofSeconds(600))
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                    .disableCachingNullValues();
            RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                    .cacheDefaults(config)
                    .build();
            return cacheManager;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    3. 在方法上加缓存注解
    (1)@Cacheable:根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。
    属性:value,缓存名,必填,指定缓存存放在哪块命名空间;key,可选,可以使用SpEL标签自定义缓存的key。
    (2)@CachePut:使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。
    (3)@CacheEvict:使用该注解标志的方法,会清空指定的缓存,一般用在更新或者删除方法上。
    在这里插入图片描述
    4. 在service-cms模块中,添加配置

    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.database=0
    spring.redis.timeout=1800000
    
    spring.redis.lettuce.pool.max-active=20
    spring.redis.lettuce.pool.max-wait=-1
    
    #最大阻塞等待时间(负数表示没限制)
    spring.redis.lettuce.pool.max-idle=5
    spring.redis.lettuce.pool.min-idle=0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    登录和注册

    整合阿里云短信服务

    1. 在service下创建子模块,用于阿里云短信服务
    在这里插入图片描述
    2. 配置文件

    # 服务端口
    server.port=8005
    # 服务名
    spring.application.name=service-msm
    
    # mysql数据库连接
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/guliedu?serverTimezone=GMT%2B8
    spring.datasource.username=root
    spring.datasource.password=qwer`123
    
    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.database= 0
    spring.redis.timeout=1800000
    
    spring.redis.lettuce.pool.max-active=20
    spring.redis.lettuce.pool.max-wait=-1
    #最大阻塞等待时间(负数表示没限制)
    spring.redis.lettuce.pool.max-idle=5
    spring.redis.lettuce.pool.min-idle=0
    #最小空闲
    
    #返回json的全局时间格式
    spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    spring.jackson.time-zone=GMT+8
    
    #mybatis日志
    mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    3. 引入依赖

    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>fastjsonartifactId>
    dependency>
    <dependency>
        <groupId>com.aliyungroupId>
        <artifactId>aliyun-java-sdk-coreartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4. 编写随机数工具类,为了生成短信验证码的数字

    public class RandomUtil {
        private static final Random random = new Random();
    
        private static final DecimalFormat fourdf = new DecimalFormat("0000");
    
        private static final DecimalFormat sixdf = new DecimalFormat("000000");
    
        public static String getFourBitRandom() {
            return fourdf.format(random.nextInt(10000));
        }
    
        public static String getSixBitRandom() {
            return sixdf.format(random.nextInt(1000000));
        }
    
        /**
         * 给定数组,抽取n个数据
         * @param list
         * @param n
         * @return
         */
        public static ArrayList getRandom(List list, int n) {
    
            Random random = new Random();
    
            HashMap<Object, Object> hashMap = new HashMap<Object, Object>();
    
            // 生成随机数字并存入HashMap
            for (int i = 0; i < list.size(); i++) {
    
                int number = random.nextInt(100) + 1;
    
                hashMap.put(number, i);
            }
    
            // 从HashMap导入数组
            Object[] robjs = hashMap.values().toArray();
    
            ArrayList r = new ArrayList();
    
            // 遍历数组并打印数据
            for (int i = 0; i < n; i++) {
                r.add(list.get((int) robjs[i]));
                System.out.print(list.get((int) robjs[i]) + "\t");
            }
            System.out.print("\n");
            return r;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    5. 编写接口

    @RestController
    @RequestMapping("/edumsm/msm")
    //@CrossOrigin
    public class MsmController {
    
        @Autowired
        private MsmService msmService;
        @Autowired
        private RedisTemplate<String,String> redisTemplate;
    
        //发送短信
        @GetMapping("send/{phone}")
        public R sendMsm(@PathVariable String phone) {
            //1 从redis获取验证码,如果获取到直接返回
            String code = redisTemplate.opsForValue().get(phone);
            if (!StringUtils.isEmpty(code)) {
                return R.ok();
            }
    
            //2 如果redis获取不到,进行阿里云发送
            //生成随机值,传递阿里云进行发送
            code = RandomUtil.getFourBitRandom();
            Map<String, Object> param = new HashMap<>();
            param.put("code",code);
            //调用service发送短信的方法
            boolean isSend = msmService.send(param,phone);
            if (isSend) {
                //发送成功,把发送成功验证码放到redis里面
                //设置有效时间
                redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES); // 5分钟
                return R.ok();
            } else {
                return R.error().message("短信发送失败");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    @Service
    public class MsmServiceImpl implements MsmService {
        //发送短信
        @Override
        public boolean send(Map<String, Object> param, String phone) {
            if(StringUtils.isEmpty(phone)) return false;
    
            DefaultProfile profile =
                    DefaultProfile.getProfile("default", "LTAI5tEz9ABsH5qFZsxXg5K2", "MHJngccYekVjxzp0YtKQU4HTJsr5Ty");
            IAcsClient client = new DefaultAcsClient(profile);
    
            //设置相关固定的参数
            CommonRequest request = new CommonRequest();
            //request.setProtocol(ProtocolType.HTTPS);
            request.setMethod(MethodType.POST);
            request.setDomain("dysmsapi.aliyuncs.com");
            request.setVersion("2017-05-25");
            request.setAction("SendSms");
    
            //设置发送相关的参数
            request.putQueryParameter("PhoneNumbers",phone); //手机号
            request.putQueryParameter("SignName","阿里云短信测试"); //申请阿里云 签名名称
            request.putQueryParameter("TemplateCode","SMS_154950909"); //申请阿里云 模板code
            request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param)); //验证码数据,转换json数据传递
    
            try {
                //最终发送
                CommonResponse response = client.getCommonResponse(request);
                boolean success = response.getHttpResponse().isSuccess();
                return success;
            }catch(Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    注意:这里要填自己阿里云模板的信息。
    在这里插入图片描述

    JWT

    1. 引入依赖

    
    <dependency>
        <groupId>io.jsonwebtokengroupId>
        <artifactId>jjwtartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2. 在common_utils下编写JWT工具类
    在这里插入图片描述

    public class JwtUtils {
        //常量
        public static final long EXPIRE = 1000 * 60 * 60 * 24; //token过期时间
        public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; //秘钥
    
        //生成token字符串的方法
        public static String getJwtToken(String id, String nickname){
    
            String JwtToken = Jwts.builder()
                    .setHeaderParam("typ", "JWT")
                    .setHeaderParam("alg", "HS256")
    
                    .setSubject("guli-user")
                    .setIssuedAt(new Date())
                    .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
    
                    .claim("id", id)  //设置token主体部分 ,存储用户信息
                    .claim("nickname", nickname)
    
                    .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                    .compact();
    
            return JwtToken;
        }
    
        /**
         * 判断token是否存在与有效
         * @param jwtToken
         * @return
         */
        public static boolean checkToken(String jwtToken) {
            if(StringUtils.isEmpty(jwtToken)) return false;
            try {
                Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
            return true;
        }
    
        /**
         * 判断token是否存在与有效
         * @param request
         * @return
         */
        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;
        }
    
        /**
         * 根据token字符串获取会员id
         * @param request
         * @return
         */
        public static String getMemberIdByJwtToken(HttpServletRequest request) {
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) return "";
            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
            Claims claims = claimsJws.getBody();
            return (String)claims.get("id");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    登录功能

    1. 在service模块下创建子模块
    在这里插入图片描述
    2. 创建ucenter用户表

    CREATE TABLE `ucenter_member` (
      `id` char(19) NOT NULL COMMENT '会员id',
      `openid` varchar(128) DEFAULT NULL COMMENT '微信openid',
      `mobile` varchar(11) DEFAULT '' COMMENT '手机号',
      `password` varchar(255) DEFAULT NULL COMMENT '密码',
      `nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
      `sex` tinyint(2) unsigned DEFAULT NULL COMMENT '性别 1 女,2 男',
      `age` tinyint(3) unsigned DEFAULT NULL COMMENT '年龄',
      `avatar` varchar(255) DEFAULT NULL COMMENT '用户头像',
      `sign` varchar(100) DEFAULT NULL COMMENT '用户签名',
      `is_disabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否禁用 1(true)已禁用,  0(false)未禁用',
      `is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员表';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3. 根据数据库表,用代码生成器生成代码
    4. properties配置和主启动类

    # 服务端口
    server.port=8160
    # 服务名
    spring.application.name=service-ucenter
    
    # mysql数据库连接
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/guliedu?serverTimezone=GMT%2B8
    spring.datasource.username=root
    spring.datasource.password=qwer`123
    
    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.database= 0
    spring.redis.timeout=1800000
    
    spring.redis.lettuce.pool.max-active=20
    spring.redis.lettuce.pool.max-wait=-1
    #最大阻塞等待时间(负数表示没限制)
    spring.redis.lettuce.pool.max-idle=5
    spring.redis.lettuce.pool.min-idle=0
    #最小空闲
    
    #请求处理的超时时间
    ribbon.ReadTimeout=120000
    #请求连接的超时时间
    ribbon.ConnectTimeout=30000
    
    #返回json的全局时间格式
    spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    spring.jackson.time-zone=GMT+8
    
    # 配置mapper.xml文件的路径
    mybatis-plus.mapper-locations=classpath:com/hxp/educenter/mapper/xml/*.xml
    
    # nacos服务地址
    spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
    
    #mybatis日志
    mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    @ComponentScan({"com.hxp"})
    @SpringBootApplication
    @MapperScan("com.hxp.educenter.mapper")
    public class UcenterApplication {
        public static void main(String[] args) {
            SpringApplication.run(UcenterApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5. 编写接口

    @RestController
    @RequestMapping("/educenter/member")
    public class UcenterMemberController {
    
        @Autowired
        private UcenterMemberService memberService;
    
        //登录
        @PostMapping ("login")
        public R loginUser(@RequestBody UcenterMember ucenterMember) {
            //调用service方法实现登录
            //返回token值,使用jwt生成
            String token = memberService.login(ucenterMember);
            return R.ok().data("token", token);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    @Service
    public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService {
    
        //登录方法
        @Override
        public String login(UcenterMember ucenterMember) {
            //获取登录手机号和密码
            String mobile = ucenterMember.getMobile();
            String password = ucenterMember.getPassword();
    
            //手机号和密码非空判断
            if (StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {
                throw new GuliException(20001, "登录失败");
            }
    
            //判断手机号是否正确
            QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
            wrapper.eq("mobile", mobile);
            UcenterMember mobileMember = baseMapper.selectOne(wrapper);
            //判断对象是否为空
            if (mobileMember == null) {
                throw new GuliException(20001, "登录失败");
            }
            //判断密码
            // 对输入的密码进行加密,再和数据库中的密码进行比较。加密方式MD5
            if (!MD5.encrypt(password).equals(mobileMember.getPassword())) {
                throw new GuliException(20001, "登录失败");
            }
            //判断用户是否禁用
            if (mobileMember.getIsDisabled()) {
                throw new GuliException(20001, "登录失败");
            }
    
            //登录成功,生成token字符串
            String jwtToken = JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname());
            return jwtToken;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    注意:上面在和数据库表中密码做判断的时候,进行了MD5加密,因为数据库中的密码都要进行加密再存储。

    public class MD5 {
        public static String encrypt(String strSrc) {
            try {
                char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
                        '9', 'a', 'b', 'c', 'd', 'e', 'f' };
                byte[] bytes = strSrc.getBytes();
                MessageDigest md = MessageDigest.getInstance("MD5");
                md.update(bytes);
                bytes = md.digest();
                int j = bytes.length;
                char[] chars = new char[j * 2];
                int k = 0;
                for (int i = 0; i < bytes.length; i++) {
                    byte b = bytes[i];
                    chars[k++] = hexChars[b >>> 4 & 0xf];
                    chars[k++] = hexChars[b & 0xf];
                }
                return new String(chars);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
                throw new RuntimeException("MD5加密出错!!+" + e);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    注册功能

    1. 创建vo实体类,封装注册数据,包含验证码属性

    @Data
    public class RegisterVo {
        @ApiModelProperty(value = "昵称")
        private String nickname;
        @ApiModelProperty(value = "手机号")
        private String mobile;
        @ApiModelProperty(value = "密码")
        private String password;
        @ApiModelProperty(value = "验证码")
        private String code;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2. 编写接口

    //注册
    @PostMapping("register")
    public R registerUser(@RequestBody RegisterVo registerVo) {
        memberService.register(registerVo);
        return R.ok();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    //注册
    @Override
    public void register(RegisterVo registerVo) {
        //获取注册的数据
        String code = registerVo.getCode(); //验证码
        String mobile = registerVo.getMobile(); //手机号
        String nickname = registerVo.getNickname(); //昵称
        String password = registerVo.getPassword(); //密码
    
        //非空判断
        if (StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)
            || StringUtils.isEmpty(code) || StringUtils.isEmpty(nickname)) {
            throw new GuliException(20001, "注册失败");
        }
    
        //判断验证码,获取redis验证码
        String redisCode = redisTemplate.opsForValue().get(mobile);
        if (!code.equals(redisCode)) {
            throw new GuliException(20001, "注册失败");
        }
    
        //判断手机号是否重复,表里存在相同手机号不进行添加
        QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
        wrapper.eq("mobile", mobile);
        Integer count = baseMapper.selectCount(wrapper);
        if (count > 0) {
            throw new GuliException(20001, "用户已存在");
        }
    
        //数据添加进数据库
        UcenterMember ucenterMember = new UcenterMember();
        ucenterMember.setMobile(mobile);
        ucenterMember.setNickname(nickname);
        ucenterMember.setPassword(MD5.encrypt(password)); //密码需要加密
        ucenterMember.setIsDisabled(false); //用户不禁用
        ucenterMember.setAvatar("http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKDRfib8wy7A2ltERKh4VygxdjVC1x5OaOb1t9hot4JNt5agwaVLdJLcD9vJCNcxkvQnlvLYIPfrZw/132");
        baseMapper.insert(ucenterMember);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    根据token获取用户信息接口

    在这里插入图片描述

    //根据token获取用户信息
    @GetMapping("getMemberInfo")
    public R getMemberInfo(HttpServletRequest request) {
        //调用jwt工具类的方法,根据request对象获取头信息,返回用户id
        String memberId = JwtUtils.getMemberIdByJwtToken(request);
        //查询数据库根据用户id获取用户信息
        UcenterMember member = memberService.getById(memberId);
        return R.ok().data("userInfo", member);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    登录成功之后首页显示数据实现分析

    在这里插入图片描述

    微信扫码登录

    OAuth2:一种解决方案,仅是授权框架,仅用于授权代理。

    1. 准备工作
    (1)注册开发者资质:在open.weixin.qq.com进行注册。
    (2)注册支持企业类型,注册之后会提供微信id和微信秘钥
    (3)申请网站应用名称
    (4)需要域名地址

    2. 生成二维码
    (1)在service-ucenter模块配置文件,配置id、秘钥和域名地址

    # 微信开放平台 appid
    wx.open.app_id=wxed9954c01bb89b47
    # 微信开放平台 appsecret
    wx.open.app_secret=a7482517235173ddb4083788de60b90e
    # 微信开放平台 重定向url
    wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (2)创建类读取配置文件内容

    @Component
    public class ConstantWxUtils implements InitializingBean {
        @Value("${wx.open.app_id}")
        private String appId;
    
        @Value("${wx.open.app_secret}")
        private String appSecret;
    
        @Value("${wx.open.redirect_url}")
        private String redirectUrl;
    
        public static String WX_OPEN_APP_ID;
        public static String WX_OPEN_APP_SECRET;
        public static String WX_OPEN_REDIRECT_URL;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            WX_OPEN_APP_ID = appId;
            WX_OPEN_APP_SECRET = appSecret;
            WX_OPEN_REDIRECT_URL = redirectUrl;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    (3)生成二维码
    直接请求微信提供固定的地址,向地址后面拼接参数。

    @Controller  //只是请求地址,不需要返回数据
    @RequestMapping("/api/ucenter/wx")
    public class WxApiController {
    
        //1 生成微信扫描二维码
        @GetMapping("login")
        public String getWxCode() {
            //固定地址,后面拼接参数
    //        String url = "https://open.weixin.qq.com/" +
    //                "connect/qrconnect?appid="+ ConstantWxUtils.WX_OPEN_APP_ID+"&response_type=code";
    
            // 微信开放平台授权baseUrl  %s相当于?代表占位符
            String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
                    "?appid=%s" +
                    "&redirect_uri=%s" +
                    "&response_type=code" +
                    "&scope=snsapi_login" +
                    "&state=%s" +
                    "#wechat_redirect";
    
            //对redirect_url进行URLEncoder编码
            String redirectUrl = ConstantWxUtils.WX_OPEN_REDIRECT_URL;
            try {
                redirectUrl = URLEncoder.encode(redirectUrl, "utf-8");
            }catch(Exception e) {
            }
    
            //设置%s里面值
            String url = String.format(
                    baseUrl,
                    ConstantWxUtils.WX_OPEN_APP_ID,
                    redirectUrl,
                    "atguigu"
            );
    
            //重定向到请求微信地址里面
            return "redirect:"+url;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    注意两点:1. 拼接参数的方式;2. 对地址做的URLEncoder编码
    在这里插入图片描述
    3. 获取扫码人信息
    分析:扫描二维码,去调用本地接口,然后跳转页面。
    在这里插入图片描述
    在接口中获取扫码人的信息
    在这里插入图片描述
    过程:
    在这里插入图片描述
    用到的技术点:httpclient(发出请求得到结果)、json转换工具(gson)

    引入依赖:

    <dependency>
        <groupId>org.apache.httpcomponentsgroupId>
        <artifactId>httpclientartifactId>
    dependency>
    <dependency>
        <groupId>commons-iogroupId>
        <artifactId>commons-ioartifactId>
    dependency>
    <dependency>
        <groupId>com.google.code.gsongroupId>
        <artifactId>gsonartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    httpclient工具类:

    获取扫码人信息的接口:

    @Autowired
    private UcenterMemberService memberService;
    
    //2 获取扫描人信息,添加数据
    @GetMapping("callback")
    public String callback(String code, String state) {
        try {
            //1 获取code值,临时票据,类似于验证码
            //2 拿着code请求 微信固定的地址,得到两个值 accsess_token 和 openid
            String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
                    "?appid=%s" +
                    "&secret=%s" +
                    "&code=%s" +
                    "&grant_type=authorization_code";
            //拼接三个参数 :id  秘钥 和 code值
            String accessTokenUrl = String.format(
                    baseAccessTokenUrl,
                    ConstantWxUtils.WX_OPEN_APP_ID,
                    ConstantWxUtils.WX_OPEN_APP_SECRET,
                    code
            );
            //请求这个拼接好的地址,得到返回两个值 accsess_token 和 openid
            //使用httpclient发送请求,得到返回结果
            String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
    
            //从accessTokenInfo字符串获取出来两个值 accsess_token 和 openid
            //把accessTokenInfo字符串转换map集合,根据map里面key获取对应值
            //使用json转换工具 Gson
            Gson gson = new Gson();
            HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class);
            String access_token = (String)mapAccessToken.get("access_token");
            String openid = (String)mapAccessToken.get("openid");
    
            //把扫描人信息添加数据库里面
            //判断数据表里面是否存在相同微信信息,根据openid判断
            UcenterMember member = memberService.getOpenIdMember(openid);
            if(member == null) {//memeber是空,表没有相同微信数据,进行添加
                //3 拿着得到accsess_token 和 openid,再去请求微信提供固定的地址,获取到扫描人信息
                //访问微信的资源服务器,获取用户信息
                String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                        "?access_token=%s" +
                        "&openid=%s";
                //拼接两个参数
                String userInfoUrl = String.format(
                        baseUserInfoUrl,
                        access_token,
                        openid
                );
                //发送请求
                String userInfo = HttpClientUtils.get(userInfoUrl);
                //获取返回userinfo字符串扫描人信息
                HashMap userInfoMap = gson.fromJson(userInfo, HashMap.class);
                String nickname = (String)userInfoMap.get("nickname");//昵称
                String headimgurl = (String)userInfoMap.get("headimgurl");//头像
    
                member = new UcenterMember();
                member.setOpenid(openid);
                member.setNickname(nickname);
                member.setAvatar(headimgurl);
                memberService.save(member);
            }
    
            //使用jwt根据member对象生成token字符串
            String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());
            //最后:返回首页面,通过路径传递token字符串
            return "redirect:http://localhost:3000?token="+jwtToken;
        }catch(Exception e) {
            throw new GuliException(20001,"登录失败");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    讲师列表和详情

    讲师分页列表

    跟之前写的后台讲师分页查询不同,之前使用了Element-ui的组件,这里需要用原始的分页查询。

    后台管理系统的分页查询:
    在这里插入图片描述
    前台的讲师分页:
    在这里插入图片描述
    在front包下,创建TeacherFrontController类

    @RestController
    @RequestMapping("/eduservice/teacherfront")
    public class TeacherFrontController {
        @Autowired
        private EduTeacherService teacherService;
    
        //分页查询讲师的方法
        @PostMapping("getTeacherFrontList/{page}/{limit}")
        public R getTeacherFrontList(@PathVariable long page, @PathVariable long limit) {
            Page<EduTeacher> pageTeacher = new Page<>(page, limit);
            Map<String, Object> map = teacherService.getTeacherFrontList(pageTeacher);
            return R.ok().data(map);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    //分页查询讲师
    @Override
    public Map<String, Object> getTeacherFrontList(Page<EduTeacher> pageTeacher) {
        QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
        wrapper.orderByDesc("id");
        baseMapper.selectPage(pageTeacher, wrapper);
    
        List<EduTeacher> records = pageTeacher.getRecords();
        long current = pageTeacher.getCurrent();
        long pages = pageTeacher.getPages();
        long size = pageTeacher.getSize();
        long total = pageTeacher.getTotal();
        boolean hasNext = pageTeacher.hasNext();
        boolean hasPrevious = pageTeacher.hasPrevious();
    
        //把分页数据获取出来,放到map集合
        Map<String, Object> map = new HashMap<>();
        map.put("items", records);
        map.put("current", current);
        map.put("pages", pages);
        map.put("size", size);
        map.put("total", total);
        map.put("hasNext", hasNext);
        map.put("hasPrevious", hasPrevious);
    
        return map;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    讲师详情功能

    根据前端传递过来的讲师id,查询讲师基本信息和讲师所讲课程。

    @Autowired
    private EduTeacherService teacherService;
    @Autowired
    private EduCourseService courseService;
    
    //讲师详情
    @GetMapping("getTeacherFrontInfo/{teacherId}")
    public R getTeacherFrontInfo(@PathVariable String teacherId) {
        //1 根据讲师id查询讲师基本信息
        EduTeacher eduTeacher = teacherService.getById(teacherId);
        //2 根据讲师id查询所讲课程
        QueryWrapper<EduCourse> wrapper = new QueryWrapper<>();
        wrapper.eq("teacher_id", teacherId);
        List<EduCourse> courseList = courseService.list(wrapper);
        return R.ok().data("teacher",eduTeacher).data("courseList",courseList);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    课程列表和课程详情

    课程列表分页查询

    在这里插入图片描述
    在这里插入图片描述

    1. 课程列表vo类

    @Data
    public class CourseFrontVo {
        @ApiModelProperty(value = "课程名称")
        private String title;
        @ApiModelProperty(value = "讲师id")
        private String teacherId;
        @ApiModelProperty(value = "一级类别id")
        private String subjectParentId;
        @ApiModelProperty(value = "二级类别id")
        private String subjectId;
        @ApiModelProperty(value = "销量排序")
        private String buyCountSort;
        @ApiModelProperty(value = "最新时间排序")
        private String gmtCreateSort;
        @ApiModelProperty(value = "价格排序")
        private String priceSort;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2. 编写接口

    @RestController
    @RequestMapping("/eduservice/coursefront")
    @CrossOrigin
    public class CourseFrontController {
    
        @Autowired
        private EduCourseService courseService;
    
        // 条件查询课程 带分页
        @PostMapping("getFrontCourseList/{page}/{limit}")
        public R getFrontCourseList(@PathVariable long page, @PathVariable long limit,
                                    @RequestBody(required = false)CourseFrontVo courseFrontVo) {
            Page<EduCourse> pageCourse = new Page<>(page, limit);
            Map<String ,Object> map = courseService.getCourseFrontList(pageCourse,courseFrontVo);
            return R.ok().data(map);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    //条件查询课程带分页
    @Override
    public Map<String, Object> getCourseFrontList(Page<EduCourse> pageCourse, CourseFrontVo courseFrontVo) {
        QueryWrapper<EduCourse> wrapper = new QueryWrapper<>();
        if (!StringUtils.isEmpty(courseFrontVo.getSubjectParentId())) {
            wrapper.eq("subject_parent_id", courseFrontVo.getSubjectParentId());
        }
        if (!StringUtils.isEmpty(courseFrontVo.getSubjectId())) {
            wrapper.eq("subject_id", courseFrontVo.getSubjectId());
        }
        if (!StringUtils.isEmpty(courseFrontVo.getBuyCountSort())) {
            wrapper.orderByDesc("buy_count");
        }
        if (!StringUtils.isEmpty(courseFrontVo.getGmtCreateSort())) {
            wrapper.orderByDesc("gmt_create");
        }
        if (!StringUtils.isEmpty(courseFrontVo.getPriceSort())) {
            wrapper.orderByDesc("price");
        }
    
        baseMapper.selectPage(pageCourse, wrapper);
    
        List<EduCourse> records = pageCourse.getRecords();
        long current = pageCourse.getCurrent();
        long pages = pageCourse.getPages();
        long size = pageCourse.getSize();
        long total = pageCourse.getTotal();
        boolean hasNext = pageCourse.hasNext();
        boolean hasPrevious = pageCourse.hasPrevious();
    
        //把分页数据获取出来,放到map集合
        Map<String, Object> map = new HashMap<>();
        map.put("items", records);
        map.put("current", current);
        map.put("pages", pages);
        map.put("size", size);
        map.put("total", total);
        map.put("hasNext", hasNext);
        map.put("hasPrevious", hasPrevious);
    
        return map;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    课程详情功能

    在这里插入图片描述
    在这里插入图片描述
    1. 课程详情vo对象

    @Data
    public class CourseWebVo {
        private String id;
        private String title;
        private BigDecimal price;
        private Integer lessonNum;
        private String cover;
        private Long buyCount;
        private Long viewCount;
        private String description;
        private String teacherId;
        private String teacherName;
        private String intro;
        private String avatar;
    
        @ApiModelProperty(value = "课程一级分类ID")
        private String subjectLevelOneId;
        @ApiModelProperty(value = "课程一级名称")
        private String subjectLevelOne;
        @ApiModelProperty(value = "课程二级分类ID")
        private String subjectLevelTwoId;
        @ApiModelProperty(value = "课程二级名称")
        private String subjectLevelTwo;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2. 编写接口

    @Autowired
    private EduCourseService courseService;
    @Autowired
    private EduChapterService chapterService;
        
    @GetMapping("getFrontCourseInfo/{courseId}")
    public R getFrontCourseInfo(@PathVariable String courseId) {
        //根据课程id,编写sql语句查询课程信息
        CourseWebVo courseWebVo = courseService.getBaseCourseInfo(courseId);
    
        //根据课程id查询章节和小节
        List<ChapterVo> chapterVideoList = chapterService.getChapterVideoByCourseId(courseId);
    
        return R.ok().data("courseWebVo",courseWebVo).data("chapterVideoList",chapterVideoList);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    //根据课程id查询课程基本信息
    @Override
    public CourseWebVo getBaseCourseInfo(String courseId) {
        return baseMapper.getBaseCourseInfo(courseId);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3. 编写sql语句,查询出显示在页面的数据:课程基本信息、课程分类、课程描述、所属讲师

    <select id="getBaseCourseInfo" resultType="com.hxp.eduservice.entity.frontvo.CourseWebVo">
        SELECT ec.id, ec.title, ec.price, ec.lesson_num AS lessonNum, ec.cover,
               ec.buy_count AS buyCount, ec.view_count AS viewCount,
               ecd.description,
               et.id AS teacherId, et.name AS teacherName, et.intro, et.avatar,
               es1.id AS subjectLevelOneId, es1.title AS subjectLevelOne,
               es2.id AS subjectLevelTwoId, es2.title AS subjectLevelTwo
        FROM edu_course ec LEFT JOIN edu_course_description ecd ON ec.id=ecd.id
                           LEFT JOIN edu_teacher et ON ec.teacher_id=et.id
                           LEFT JOIN edu_subject es1 ON ec.subject_parent_id=es1.id
                           LEFT JOIN edu_subject es2 ON ec.subject_id=es2.id
        WHERE ec.id=#{courseId};
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    整合阿里云视频播放

    1. 编写接口
    用获取视频凭证的方式播放视频

    //获取视频凭证
    @GetMapping("getPlayAuth/{id}")
    public R getPlayAuth(@PathVariable String id) {
        try {
            //创建初始化对象
            DefaultAcsClient client =
                    InitVodClient.initVodClient(ConstantVodUtils.ACCESS_KEY_ID, ConstantVodUtils.ACCESS_KEY_SECRET);
            //创建获取凭证request和response对象
            GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
            //向request设置视频id
            request.setVideoId(id);
            //调用方法得到凭证
            GetVideoPlayAuthResponse response = client.getAcsResponse(request);
            String playAuth = response.getPlayAuth();
            return R.ok().data("playAuth", playAuth);
        } catch (Exception e) {
            throw new GuliException(20001, "获取凭证失败");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2. 点击某个小节,打开新的页面进行视频播放
    在小节上加上超链接
    在这里插入图片描述

    课程支付

    需求分析

    在这里插入图片描述
    在这里插入图片描述
    点击“立即购买”:生成订单,跳转页面将订单数据显示在订单上。

    点击“去支付”:生成微信支付二维码,每隔三秒查询支付状态是否支付成功。支付成功后,将支付状态改成已支付,并生成支付记录到数据库表中,然后跳转到课程详情页面。

    生成订单

    1. 数据库表

    CREATE TABLE `t_order` (
      `id` char(19) NOT NULL DEFAULT '',
      `order_no` varchar(20) NOT NULL DEFAULT '' COMMENT '订单号',
      `course_id` varchar(19) NOT NULL DEFAULT '' COMMENT '课程id',
      `course_title` varchar(100) DEFAULT NULL COMMENT '课程名称',
      `course_cover` varchar(255) DEFAULT NULL COMMENT '课程封面',
      `teacher_name` varchar(20) DEFAULT NULL COMMENT '讲师名称',
      `member_id` varchar(19) NOT NULL DEFAULT '' COMMENT '会员id',
      `nickname` varchar(50) DEFAULT NULL COMMENT '会员昵称',
      `mobile` varchar(11) DEFAULT NULL COMMENT '会员手机',
      `total_fee` decimal(10,2) DEFAULT '0.01' COMMENT '订单金额(分)',
      `pay_type` tinyint(3) DEFAULT NULL COMMENT '支付类型(1:微信 2:支付宝)',
      `status` tinyint(3) DEFAULT NULL COMMENT '订单状态(0:未支付 1:已支付)',
      `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`),
      UNIQUE KEY `ux_order_no` (`order_no`),
      KEY `idx_course_id` (`course_id`),
      KEY `idx_member_id` (`member_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    CREATE TABLE `t_pay_log` (
      `id` char(19) NOT NULL DEFAULT '',
      `order_no` varchar(20) NOT NULL DEFAULT '' COMMENT '订单号',
      `pay_time` datetime DEFAULT NULL COMMENT '支付完成时间',
      `total_fee` decimal(10,2) DEFAULT '0.01' COMMENT '支付金额(分)',
      `transaction_id` varchar(30) DEFAULT NULL COMMENT '交易流水号',
      `trade_state` char(20) DEFAULT NULL COMMENT '交易状态',
      `pay_type` tinyint(3) NOT NULL DEFAULT '0' COMMENT '支付类型(1:微信 2:支付宝)',
      `attr` text COMMENT '其他属性',
      `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`),
      UNIQUE KEY `uk_order_no` (`order_no`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付日志表';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2. 用代码生成器生成代码

    3. 生成订单接口
    在这里插入图片描述

    controller层:

    @Autowired
    private OrderService orderService;
    
    // 生成订单
    @PostMapping("createOrder/{courseId}")
    public R saveOrder(@PathVariable String courseId, HttpServletRequest request) {
        //在request请求中,根据token得到用户id,根据课程id和用户id创建订单
        String orderNo = orderService.createOrders(courseId, JwtUtils.getMemberIdByJwtToken(request));
        return R.ok().data("orderId", orderNo);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    想通过远程调用的方式拿到两个对象,需要做如下操作:
    (1)在公共模块创建两个vo对象
    在这里插入图片描述
    (2)在用户模块中,写查询用户信息的接口,用于订单模块做远程调用
    在这里插入图片描述
    (3)在edu模块中,写查询课程信息的接口,用于订单模块远程调用查询课程信息
    在这里插入图片描述
    (4)在order模块,加上nacos配置和主启动类上的注解
    在这里插入图片描述
    在这里插入图片描述
    (5)在order模块,编写远程调用的接口

    @Component
    @FeignClient("service-edu")
    public interface EduClient {
        //根据课程id查询课程信息
        @PostMapping("/eduservice/coursefront/getCourseInfoOrder/{id}")
        public CourseWebVoOrder getCourseInfoOrder(@PathVariable("id") String id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    @Component
    @FeignClient("service-ucenter")
    public interface UcenterClient {
        //根据用户id获取用户信息
        @PostMapping("/educenter/member/getUserInfoOrder/{id}")
        public UcenterMemberOrder getUserInfoOrder(@PathVariable String id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    service层,创建订单,返回订单号

    @Service
    public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
    
        @Autowired
        private EduClient eduClient;
        @Autowired
        private UcenterClient ucenterClient;
    
        //生成订单
        @Override
        public String createOrders(String courseId, String memberIdByJwtToken) {
            //通过远程调用根据用户id获取用户信息
            UcenterMemberOrder userInfoOrder = ucenterClient.getUserInfoOrder(memberIdByJwtToken);
            //通过远程调用根据课程id获取课程信息
            CourseWebVoOrder courseInfoOrder = eduClient.getCourseInfoOrder(courseId);
    
            //创建Order对象,向order对象里面设置需要数据
            Order order = new Order();
            order.setOrderNo(OrderNoUtils.getOrderNo());
            order.setCourseId(courseId);
            order.setCourseTitle(courseInfoOrder.getCover());
            order.setTeacherName(courseInfoOrder.getTeacherName());
            order.setTotalFee(courseInfoOrder.getPrice());
            order.setMemberId(memberIdByJwtToken);
            order.setMobile(userInfoOrder.getMobile());
            order.setNickname(userInfoOrder.getNickname());
            order.setStatus(0); //订单状态(0:未支付  1:已支付)
            order.setPayType(1);    //支付类型,微信1
    
            baseMapper.insert(order);
    
            //返回订单号
            return order.getOrderNo();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    根据订单id查询订单信息

    前面已经将订单创建出来了,再根据订单号查询出订单数据,显示在订单页面上。

    // 根据订单号查询订单信息
    @GetMapping("getOrderInfo/{orderId}")
    public R getOrderInfo(@PathVariable String orderId) {
        QueryWrapper<Order> wrapper = new QueryWrapper<>();
        wrapper.eq("order_no", orderId);
        Order order = orderService.getOne(wrapper);
        return R.ok().data("item",order);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    生成微信支付二维码

    1. 尚硅谷提供的微信支付账号

    weixin:
      pay:
        #关联的公众号appid
        appid: wx74862e0dfcf69954
        #商户号
        partner: 1558950191
        #商户key
        partnerkey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
        #回调地址
        notifyurl: http://guli.shop/api/order/weixinPay/weixinNotify
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2. 引入依赖

    <dependency>
        <groupId>com.github.wxpaygroupId>
        <artifactId>wxpay-sdkartifactId>
        <version>0.0.3version>
    dependency>
    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>fastjsonartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3. 编写接口

    @RestController
    @RequestMapping("/eduorder/paylog")
    @CrossOrigin
    public class PayLogController {
    
        @Autowired
        private PayLogService payLogService;
    
        //生成微信支付二维码接口,参数是订单号
        @GetMapping("createNative/{orderNo}")
        public R createNative(@PathVariable String orderNo) {
            //返回信息,包含二维码地址,还有其他需要的信息
            Map map = payLogService.createNative(orderNo);
            return R.ok().data(map);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    @Service
    public class PayLogServiceImpl extends ServiceImpl<PayLogMapper, PayLog> implements PayLogService {
    
        @Autowired
        private OrderService orderService;
    
        //生成微信支付二维码接口
        @Override
        public Map createNative(String orderNo) {
            try {
                //1 根据订单号查询订单信息
                QueryWrapper<Order> wrapper = new QueryWrapper<>();
                wrapper.eq("order_no", orderNo);
                Order order = orderService.getOne(wrapper);
    
                //2 使用map设置生成二维码需要的参数
                Map m = new HashMap();
                m.put("appid","wx74862e0dfcf69954");
                m.put("mch_id", "1558950191");
                m.put("nonce_str", WXPayUtil.generateNonceStr());
                m.put("body", order.getCourseTitle()); //课程标题
                m.put("out_trade_no", orderNo); //订单号
                m.put("total_fee", order.getTotalFee().multiply(new BigDecimal("100")).longValue()+"");
                m.put("spbill_create_ip", "127.0.0.1");
                m.put("notify_url", "http://guli.shop/api/order/weixinPay/weixinNotify\n");
                m.put("trade_type", "NATIVE");
    
                //3 发送httpclient请求,传递参数xml格式,微信支付提供的固定的地址
                HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
                //设置xml格式的参数
                client.setXmlParam(WXPayUtil.generateSignedXml(m,"T6m9iK73b0kn9g5v426MKfHQH7X8rKwb"));
                client.setHttps(true);
                //执行post请求发送
                client.post();
    
                //4 得到发送请求返回结果
                //返回内容,是使用xml格式返回
                String xml = client.getContent();
    
                //把xml格式转换map集合,把map集合返回,其中包含二维码地址
                Map<String,String> resultMap = WXPayUtil.xmlToMap(xml);
    
                //最终返回数据 的封装
                Map map = new HashMap();
                map.put("out_trade_no", orderNo);
                map.put("course_id", order.getCourseId());
                map.put("total_fee", order.getTotalFee());
                map.put("result_code", resultMap.get("result_code"));  //返回二维码操作状态码
                map.put("code_url", resultMap.get("code_url"));        //二维码地址
    
                return map;
            }catch(Exception e) {
                throw new GuliException(20001,"生成二维码失败");
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    查询订单支付状态

    查询订单状态,如果查询结果是“支付成功”,则改变支付状态并添加支付记录。

    //查询订单支付状态
    //参数:订单号,根据订单号查询支付状态
    @GetMapping("queryPayStatus/{orderNo}")
    public R queryPayStatus(@PathVariable String orderNo) {
        Map<String,String> map = payLogService.queryPayStatus(orderNo);
        if (map == null) {
            return R.error().message("支付出错了");
        }
        //如果返回map里面不为空,通过map获取订单状态
        if (map.get("trade_state").equals("SUCCESS")) { //支付成功
            //添加记录到支付表,更新订单表订单状态
            payLogService.updateOrdersStatus(map);
            return R.ok().message("支付成功");
        }
        return R.ok().message("支付中");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    //查询订单支付状态
    @Override
    public Map<String, String> queryPayStatus(String orderNo) {
        try {
            //1、封装参数
            Map m = new HashMap<>();
            m.put("appid", "wx74862e0dfcf69954");
            m.put("mch_id", "1558950191");
            m.put("out_trade_no", orderNo);
            m.put("nonce_str", WXPayUtil.generateNonceStr());
    
            //2 发送httpclient
            HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
            client.setXmlParam(WXPayUtil.generateSignedXml(m,"T6m9iK73b0kn9g5v426MKfHQH7X8rKwb"));
            client.setHttps(true);
            client.post();
    
            //3 得到请求返回内容
            String xml = client.getContent();
            Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);
            //6、转成Map再返回
            return resultMap;
        }catch(Exception e) {
            return null;
        }
    }
    
    //添加支付记录和更新订单状态
    @Override
    public void updateOrdersStatus(Map<String, String> map) {
        //从map获取订单号
        String orderNo = map.get("out_trade_no");
        //根据订单号查询订单信息
        QueryWrapper<Order> wrapper = new QueryWrapper<>();
        wrapper.eq("order_no",orderNo);
        Order order = orderService.getOne(wrapper);
    
        //更新订单表订单状态
        if(order.getStatus().intValue() == 1) { return; }
        order.setStatus(1);//1代表已经支付
        orderService.updateById(order);
    
        //向支付表添加支付记录
        PayLog payLog = new PayLog();
        payLog.setOrderNo(orderNo);  //订单号
        payLog.setPayTime(new Date()); //订单完成时间
        payLog.setPayType(1);//支付类型 1微信
        payLog.setTotalFee(order.getTotalFee());//总金额(分)
    
        payLog.setTradeState(map.get("trade_state"));//支付状态
        payLog.setTransactionId(map.get("transaction_id")); //流水号
        payLog.setAttr(JSONObject.toJSONString(map));
    
        baseMapper.insert(payLog);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    课程详情页面完善

    1、如果课程是免费的,按钮显示“立即观看”
    2、如果课程已经支付,按钮显示“立即观看”
    3、如果课程没有购买,或者不是免费课程,按钮显示“立即购买”

    (1)根据课程id和用户id,查询订单表中的订单状态,如果状态为1表示已经支付,否则没有支付。

    //根据课程id和用户id查询订单表中的订单状态
    @GetMapping("isBuyCourse/{courseId}/{memberId}")
    public boolean isBuyCourse(@PathVariable String courseId, @PathVariable String memberId) {
        QueryWrapper<Order> wrapper = new QueryWrapper<>();
        wrapper.eq("course_id", courseId);
        wrapper.eq("member_id", memberId);
        wrapper.eq("status", 1); //支付状态
        int count = orderService.count(wrapper);
        if (count > 0) {    //表示已经支付
            return true;
        } else {
            return false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    (2)修改原来写的课程详情查询的接口:远程调用刚才的写的接口,判断课程是否支付

    @Component
    @FeignClient("service-order")
    public interface OrdersClient {
        //根据课程id和用户id查询订单表中的订单状态
        @GetMapping("/eduorder/order/isBuyCourse/{courseId}/{memberId}")
        public boolean isBuyCourse(@PathVariable("courseId") String courseId, @PathVariable("memberId") String memberId);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

  • 相关阅读:
    组件通信的方法
    Qt扫盲-QColor 理论使用总结
    C++ std::default_random_engine的使用
    Mac m1配置MAMP+PHPStorm环境
    百度飞桨(PaddlePaddle) - PaddleHub OCR 文字识别简单使用
    插值查找算法(思路分析) [数据结构][Java]
    LeetCode75——Day2
    Linux完全卸载PyTorch&重装(cuda11.1)
    计算机网络_实验10_子网划分
    聚酰胺改性乳清白蛋白/肌白蛋白/豆清白蛋白/蓖麻蛋白/豌豆白蛋白1b ( PA1b)纳米粒
  • 原文地址:https://blog.csdn.net/weixin_45581692/article/details/127317141