在线教育

  • 管理员后台管理系统
  • 普通用户前台使用系统
    • 首页数据显示
      • 环境搭建
      • 前台显示轮播图
      • 热门课程和热门讲师
      • 添加redis做缓存
    • 登录和注册
      • 整合阿里云短信服务
      • JWT
      • 登录功能
      • 注册功能
      • 根据token获取用户信息接口
      • 登录成功之后首页显示数据实现分析
      • 微信扫码登录
    • 讲师列表和详情
      • 讲师分页列表
      • 讲师详情功能
    • 课程列表和课程详情
      • 课程列表分页查询
      • 课程详情功能
      • 整合阿里云视频播放
    • 课程支付
      • 需求分析
      • 生成订单
      • 根据订单id查询订单信息
      • 生成微信支付二维码
      • 查询订单支付状态
      • 课程详情页面完善

管理员后台管理系统

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

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表';

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);}
}

前台显示轮播图

@RestController
@RequestMapping("/educms/bannerfront")
public class BannerFrontController {@Autowiredprivate CrmBannerService bannerService;//查询所有banner@GetMapping("getAllBanner")public R getAllBanner() {List<CrmBanner> list = bannerService.selectAllBanner();return R.ok().data("list", list);}
}
@Service
public class CrmBannerServiceImpl extends ServiceImpl<CrmBannerMapper, CrmBanner> implements CrmBannerService {//查询banner@Overridepublic List<CrmBanner> selectAllBanner() {//根据id进行降序排列,显示排列之后前两条记录QueryWrapper<CrmBanner> wrapper = new QueryWrapper<>();wrapper.orderByDesc("id");wrapper.last("limit 2");    //last方法,拼接sqlList<CrmBanner> list = baseMapper.selectList(wrapper);return list;}
}

热门课程和热门讲师

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

@RestController
@RequestMapping("/eduservice/indexfront")
public class IndexFrontController {@Autowiredprivate EduCourseService courseService;@Autowiredprivate 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);}
}

添加redis做缓存

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

<!-- redis -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.6.0</version>
</dependency>

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

@EnableCaching  //开启缓存
@Configuration
public class RedisConfig extends CachingConfigurerSupport {@Beanpublic 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;}@Beanpublic 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;}
}

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=1800000spring.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. 在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`123spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000spring.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

3. 引入依赖

<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId>
</dependency>
<dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId>
</dependency>

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>();// 生成随机数字并存入HashMapfor (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;}
}

5. 编写接口

@RestController
@RequestMapping("/edumsm/msm")
//@CrossOrigin
public class MsmController {@Autowiredprivate MsmService msmService;@Autowiredprivate 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("短信发送失败");}}
}
@Service
public class MsmServiceImpl implements MsmService {//发送短信@Overridepublic 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"); //申请阿里云 模板coderequest.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;}}
}

注意:这里要填自己阿里云模板的信息。

JWT

1. 引入依赖

<!-- JWT -->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId>
</dependency>

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. 在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='会员表';

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`123spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000spring.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
@ComponentScan({"com.hxp"})
@SpringBootApplication
@MapperScan("com.hxp.educenter.mapper")
public class UcenterApplication {public static void main(String[] args) {SpringApplication.run(UcenterApplication.class, args);}
}

5. 编写接口

@RestController
@RequestMapping("/educenter/member")
public class UcenterMemberController {@Autowiredprivate UcenterMemberService memberService;//登录@PostMapping ("login")public R loginUser(@RequestBody UcenterMember ucenterMember) {//调用service方法实现登录//返回token值,使用jwt生成String token = memberService.login(ucenterMember);return R.ok().data("token", token);}
}
@Service
public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService {//登录方法@Overridepublic 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, "登录失败");}//判断密码// 对输入的密码进行加密,再和数据库中的密码进行比较。加密方式MD5if (!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;}
}

注意:上面在和数据库表中密码做判断的时候,进行了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. 创建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;
}

2. 编写接口

//注册
@PostMapping("register")
public R registerUser(@RequestBody RegisterVo registerVo) {memberService.register(registerVo);return R.ok();
}
@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);
}

根据token获取用户信息接口

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

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

微信扫码登录

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

(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;@Overridepublic void afterPropertiesSet() throws Exception {WX_OPEN_APP_ID = appId;WX_OPEN_APP_SECRET = appSecret;WX_OPEN_REDIRECT_URL = redirectUrl;}
}

(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. 对地址做的URLEncoder编码

3. 获取扫码人信息
分析:扫描二维码,去调用本地接口,然后跳转页面。

在接口中获取扫码人的信息

过程:

用到的技术点:httpclient(发出请求得到结果)、json转换工具(gson)

引入依赖:

<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId>
</dependency>
<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId>
</dependency>
<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId>
</dependency>

httpclient工具类:

获取扫码人信息的接口:

@Autowired
private UcenterMemberService memberService;//2 获取扫描人信息,添加数据
@GetMapping("callback")
public String callback(String code, String state) {try {//1 获取code值,临时票据,类似于验证码//2 拿着code请求 微信固定的地址,得到两个值 accsess_token 和 openidString 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转换工具 GsonGson 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,"登录失败");}
}

讲师列表和详情

讲师分页列表

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

后台管理系统的分页查询:

前台的讲师分页:

在front包下,创建TeacherFrontController类

@RestController
@RequestMapping("/eduservice/teacherfront")
public class TeacherFrontController {@Autowiredprivate 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);}
}
//分页查询讲师
@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;
}

讲师详情功能

根据前端传递过来的讲师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. 课程列表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;
}

2. 编写接口

@RestController
@RequestMapping("/eduservice/coursefront")
@CrossOrigin
public class CourseFrontController {@Autowiredprivate 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);}
}
//条件查询课程带分页
@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. 课程详情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;
}

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);
}
//根据课程id查询课程基本信息
@Override
public CourseWebVo getBaseCourseInfo(String courseId) {return baseMapper.getBaseCourseInfo(courseId);
}

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 subjectLevelTwoFROM edu_course ec LEFT JOIN edu_course_description ecd ON ec.id=ecd.idLEFT JOIN edu_teacher et ON ec.teacher_id=et.idLEFT JOIN edu_subject es1 ON ec.subject_parent_id=es1.idLEFT JOIN edu_subject es2 ON ec.subject_id=es2.idWHERE ec.id=#{courseId};
</select>

整合阿里云视频播放

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设置视频idrequest.setVideoId(id);//调用方法得到凭证GetVideoPlayAuthResponse response = client.getAcsResponse(request);String playAuth = response.getPlayAuth();return R.ok().data("playAuth", playAuth);} catch (Exception e) {throw new GuliException(20001, "获取凭证失败");}
}

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='订单';
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='支付日志表';

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)在公共模块创建两个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);
}
@Component
@FeignClient("service-ucenter")
public interface UcenterClient {//根据用户id获取用户信息@PostMapping("/educenter/member/getUserInfoOrder/{id}")public UcenterMemberOrder getUserInfoOrder(@PathVariable String id);
}

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

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {@Autowiredprivate EduClient eduClient;@Autowiredprivate UcenterClient ucenterClient;//生成订单@Overridepublic 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);    //支付类型,微信1baseMapper.insert(order);//返回订单号return order.getOrderNo();}
}

根据订单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. 尚硅谷提供的微信支付账号

weixin:pay:#关联的公众号appidappid: wx74862e0dfcf69954#商户号partner: 1558950191#商户keypartnerkey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb#回调地址notifyurl: http://guli.shop/api/order/weixinPay/weixinNotify

2. 引入依赖

<dependency><groupId>com.github.wxpay</groupId><artifactId>wxpay-sdk</artifactId><version>0.0.3</version>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId>
</dependency>

3. 编写接口

@RestController
@RequestMapping("/eduorder/paylog")
@CrossOrigin
public class PayLogController {@Autowiredprivate PayLogService payLogService;//生成微信支付二维码接口,参数是订单号@GetMapping("createNative/{orderNo}")public R createNative(@PathVariable String orderNo) {//返回信息,包含二维码地址,还有其他需要的信息Map map = payLogService.createNative(orderNo);return R.ok().data(map);}
}
@Service
public class PayLogServiceImpl extends ServiceImpl<PayLogMapper, PayLog> implements PayLogService {@Autowiredprivate OrderService orderService;//生成微信支付二维码接口@Overridepublic 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,"生成二维码失败");}}
}

查询订单支付状态

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

//查询订单支付状态
//参数:订单号,根据订单号查询支付状态
@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("支付中");
}
//查询订单支付状态
@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 发送httpclientHttpClient 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、如果课程没有购买,或者不是免费课程,按钮显示“立即购买”

(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;}
}

(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. 谷粒学院前台登录流程

    谷粒学院前台登录流程 登录验证部分 第一步 前台输入账号密码 第二步 后端验证 首先判断手机号是否存在 其次判断密码是否正确 再判断账号是否被禁用 验证通过后,利用 JwtUtils 工具生成 Jwt ...

  2. Day210.服务端渲染技术NUXT、整合前台主页面、名师、课程静态页面、首页整合banner数据后端部分【创建banner微服务、接口、banner后台前端实现】 -谷粒学院

    谷粒学院 服务端渲染技术NUXT 一.服务端渲染技术NUXT 1.什么是服务端渲染 服务端渲染又称SSR (Server Side Render)是在服务端完成页面的内容,而不是在客户端通过AJAX获 ...

  3. 尚硅谷谷粒学院学习笔记(防坑点的总结部分勘误)

    谷粒学院学习笔记 部分勘误 数据库设计规约 模块说明 环境搭建 创建一个Spring Boot 的父工程,版本使用:2.2.1.RELEASE 父工程pom.xml里面添加 在pom.xml中添加依赖 ...

  4. Day212.OAuth2、微信二维码登入注册功能、用户登录信息前后端供、讲师列表前后端 -谷粒学院

    谷粒学院 OAuth2的使用场景 一.OAuth2解决什么问题 1.OAuth2提出的背景 照片拥有者想要在云冲印服务上打印照片,云冲印服务需要访问云存储服务上的资源 2.图例 资源拥有者:照片拥有者 ...

  5. 2022年最新《谷粒学院开发教程》:9 - 前台课程模块

    资料 资料地址 后台管理系统目录 前台展示系统目录 1 - 构建工程篇 7 - 渲染前台篇 2 - 前后交互篇 8 - 前台登录篇 3 - 文件上传篇 9 - 前台课程篇 4 - 课程管理篇 10 - ...

  6. 尚硅谷谷粒学院学习笔记9--前台用户登录,注册,整合jwt,微信登录

    用户登录业务 单点登录(Single Sign On),简称SSO. 用户只需要登陆一次就可以访问所有相互信任的应用系统 单点登录三种常见方式 session广播机制实现 使用redis+cookie ...

  7. 谷粒学院——第十一章、搭建前台页面

    服务端渲染技术NUXT 什么是服务端渲染 服务端渲染又称SSR (Server Side Render)是在服务端完成页面的内容,而不是在客户端通过AJAX获取数据. 服务器端渲染(SSR)的优势主要 ...

  8. 【谷粒学院】001-项目概述、Mybatis Plus入门

    目录 一.项目概述 1.项目来源 2.功能简介 3.技术架构 二.Mybatis-Plus概述 1.简介 2.特性 三.Mybatis-Plus入门 1.创建数据库 2.创建 User 表 表结构: ...

  9. 谷粒学院订单管理 server-order 模块

    谷粒学院订单管理 server-order 模块 模块介绍 主要实现前台页面购买课程后,生成订单,并实现微信支付的功能. ![]](https://img-blog.csdnimg.cn/7b821f ...

最新文章

  1. saltstack 服务配置
  2. es根据磁盘使用情况来决定是否分配shard
  3. 开始学习python
  4. python安装包_在python官网打不开的情况下获取获取官方最新安装包
  5. .ajax done参数,困惑jQuery .ajax .done()函数
  6. java已知一个二叉树_#二叉树复习#
  7. Android2.2缩略图类ThumbnailUtils
  8. 如何对聚类结果进行分析_产品经理如何进行数据分析?
  9. 移动端压缩并ajax上传图片解决方案
  10. python 全局变量使用报错没有定义_python跨文件使用全局变量的实现
  11. bzoj 3517: 翻硬币
  12. 2.VMware View 4.6安装与部署-域环境
  13. Ubuntu安装gcc-7.3.0
  14. Word中公式输入的快捷键
  15. [软件人生]耐得住寂寞——积累是低潮时期技术人员的品质
  16. Error: Cannot find module ‘@/xxx‘
  17. c语言settextstyle有哪些字体,settextstyle() 设置文本型式函数
  18. matlab高斯正反算程序6,基于matlab的高斯投影正反算与相邻带坐标换算程序设计...
  19. https://ipcrs.pbccrc.org.cn/
  20. 我在用的翻译软件 - 微软翻译+网易有道词典+谷歌翻译

热门文章

  1. echarts图表在修改了数据之后,如何进行重新渲染?
  2. 常用CSS与Flex布局、媒体查询、JS事件控制css、VUE对象语法、Gride布局(待补全) CSS权重 页面适配笔记本缩放
  3. MySQL同一个表中批量刷数据
  4. 大数讯智能电销机器人,让电销从此变得轻松!
  5. 笨叔叔Ubuntu虚拟机镜像切换搜狗拼音输入法
  6. java课实验第二次随堂测试(所有可能出现的题)
  7. 单场直播销售额破7亿,11月的抖音带货风向是什么?
  8. 不可不懂的UG编程片体转实体技巧
  9. 元宇宙的燃料,巨头的梦想,云游戏浪潮将至?
  10. vue - vue表单中v-model和:value的区别