学习天机项目的笔记

这篇博客详细记录了学习天机项目的过程,涵盖了项目亮点、技术架构、本地配置、持续集成自动化部署、前端部署、maven知识、远程调试、Git分支管理、项目介绍、模块业务流程等。还深入讲解了微服务中的Feign调用、Spring Security认证授权、OAuth2授权模式、网关权限认证、用户认证和授权、JSR303校验、全局异常处理、Nacos配置中心的搭建、SpringTask定时任务以及高并发问题的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.项目亮点

2.技术架构

3.在本地配置虚假域名映射

修改本地的的host文件(配置小工具)

如果直接输入域名就能访问,就是不用输入端口号的话,就需要配置nginx

80是naginx的默认端口号监听即可(配置nginx.conf),一个server代表一个虚拟主机

4.持续集成自动化部署(Jenkins)

5.nginx部署前端

6.关于maven的小知识

maven模块的聚合,在父工程聚合

maven的继承,子模块继承父工程

关于maven依赖冲突问题的解决,以及版本的不兼容

7.idea远程调试微服务

记录一个判断bug

原因:

底层源码

8.Git的分支管理规范

9.项目的介绍

10.项目各个模块的业务流程

1.我的课程模块

11.MP代码生成器

先下载插件

配置数据源

配置相关包信息,还能设置swagger注解

12.枚举类的使用讲解

枚举类的好处就是不用在写业务代码的时候定义过多的常量,让代码更加的简洁

使用枚举的时候必须要明确两个区别,jackson返回前端的是value值1,2,3,fastjson是返回desc比如学习中,以及他们对Null值得处理

13.使用Mq进行消息通知

消息的幂等性处理就是在课程表中添加两个唯一约束,比如说课程id和用户id设置成唯一键。也能防止消息发送消息的重复。整体的流程是:生产者->交换机->队列->消费者

1.添加依赖

在生产模块和消费模块都要添加

2.配置yml

3.在生产者

配置类:生命交换机以及队列并对其持久化操作,绑定以及发送消息失败之后的回调

@Slf4j
    @Configuration
    public class PayNotifyConfig implements ApplicationContextAware {

        //交换机
        public static final String PAYNOTIFY_EXCHANGE_FANOUT = "paynotify_exchange_fanout";
        //支付结果通知消息类型
        public static final String MESSAGE_TYPE = "payresult_notify";
        //支付通知队列
        public static final String PAYNOTIFY_QUEUE = "paynotify_queue";

        //声明交换机,且持久化
        @Bean(PAYNOTIFY_EXCHANGE_FANOUT)
        public FanoutExchange paynotify_exchange_fanout() {
            // 三个参数:交换机名称、是否持久化、当没有queue与其绑定时是否自动删除
            return new FanoutExchange(PAYNOTIFY_EXCHANGE_FANOUT, true, false);
        }

        //支付通知队列,且持久化
        @Bean(PAYNOTIFY_QUEUE)
        public Queue course_publish_queue() {
            return QueueBuilder.durable(PAYNOTIFY_QUEUE).build();
        }

        //交换机和支付通知队列绑定
        @Bean
        public Binding binding_course_publish_queue(@Qualifier(PAYNOTIFY_QUEUE) Queue queue, @Qualifier(PAYNOTIFY_EXCHANGE_FANOUT) FanoutExchange exchange) {
            return BindingBuilder.bind(queue).to(exchange);
        }

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            // 获取RabbitTemplate
            RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
            //消息处理service
            MqMessageService mqMessageService = applicationContext.getBean(MqMessageService.class);
            // 设置ReturnCallback
            rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
                // 投递失败,记录日志
                log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",
                        replyCode, replyText, exchange, routingKey, message.toString());
                MqMessage mqMessage = JSON.parseObject(message.toString(), MqMessage.class);
                //将消息再添加到消息表
                mqMessageService.addMessage(mqMessage.getMessageType(), mqMessage.getBusinessKey1(), mqMessage.getBusinessKey2(), mqMessage.getBusinessKey3());

            });
        }

    }

发送消息的方法

    //发送通知消息
    @Override
    public void notifyPayResult(MqMessage message) {
        //1.0构造一个消息
        String msg = JSON.toJSONString(message);//消息体转为json
        //设置消息持久化
        Message msgObj = MessageBuilder.withBody(msg.getBytes(StandardCharsets.UTF_8)).setDeliveryMode(MessageDeliveryMode.PERSISTENT).build();
        //2.0全局唯一的消息ID,需要封装到CorrelationData中,就是一个回调方法
        CorrelationData correlationData = new CorrelationData(message.getId().toString());
        correlationData.getFuture().addCallback(
                result -> {
                    if(result.isAck()){
                        // 3.1.ack,消息成功
                        log.debug("通知支付结果消息发送成功, ID:{}", correlationData.getId());
                        //删除消息表中的记录
                        mqMessageService.completed(message.getId());
                    }else{
                        // 3.2.nack,消息失败
                        log.error("通知支付结果消息发送失败, ID:{}, 原因{}",correlationData.getId(), result.getReason());
                    }
                },
                ex -> log.error("消息发送异常, ID:{}, 原因{}",correlationData.getId(),ex.getMessage())
        );
        //3.0发送消息,广播模式
        rabbitTemplate.convertAndSend(PayNotifyConfig.PAYNOTIFY_EXCHANGE_FANOUT,"",msgObj,correlationData);

    }

4.在消费者

最好也声明交换机这些,无论生产者先启动还是消费先启动都能保证先声明交换机和队列

@Slf4j
    @Configuration
    public class PayNotifyConfig implements ApplicationContextAware {

        //交换机
        public static final String PAYNOTIFY_EXCHANGE_FANOUT = "paynotify_exchange_fanout";
        //支付结果通知消息类型
        public static final String MESSAGE_TYPE = "payresult_notify";
        //支付通知队列
        public static final String PAYNOTIFY_QUEUE = "paynotify_queue";

        //声明交换机,且持久化
        @Bean(PAYNOTIFY_EXCHANGE_FANOUT)
        public FanoutExchange paynotify_exchange_fanout() {
            // 三个参数:交换机名称、是否持久化、当没有queue与其绑定时是否自动删除
            return new FanoutExchange(PAYNOTIFY_EXCHANGE_FANOUT, true, false);
        }

        //支付通知队列,且持久化
        @Bean(PAYNOTIFY_QUEUE)
        public Queue course_publish_queue() {
            return QueueBuilder.durable(PAYNOTIFY_QUEUE).build();
        }

        //交换机和支付通知队列绑定
        @Bean
        public Binding binding_course_publish_queue(@Qualifier(PAYNOTIFY_QUEUE) Queue queue, @Qualifier(PAYNOTIFY_EXCHANGE_FANOUT) FanoutExchange exchange) {
            return BindingBuilder.bind(queue).to(exchange);
        }

  
    }

消费者接受消息

@Slf4j
@Service
public class ReceivePayNotifyService {
    @Autowired
    MyCourseTableService myCourseTableService;

    //监听消息队列接收支付结果通知
    @RabbitListener(queues = PayNotifyConfig.PAYNOTIFY_QUEUE)
    public void receive(Message message, Channel channel){
        //要是抛出异常别让代码执行的这么迅速
        try{
            Thread.sleep(5000);
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }

        //解析消息
        byte[] body = message.getBody();
        String JsonString = new String(body);
        //转成java对象
        MqMessage mqMessage = JSON.parseObject(JsonString, MqMessage.class);
        log.debug("学习中心服务接收支付的结果:{}",mqMessage);

        //消息的类型
        String messageType = mqMessage.getMessageType();
        //订单类型
        String businessKey2 = mqMessage.getBusinessKey2();
        //这里只处理支付结果通知
        if(PayNotifyConfig.MESSAGE_TYPE.equals(messageType)&& "60201".equals(businessKey2)){
            //根据消息的内容,更新选课记录,向我的课程表中插入数据
            //选课记录id
            String chooseCourseId = mqMessage.getBusinessKey1();
            boolean b = myCourseTableService.saveChooseCourseSuccess(chooseCourseId);
            if(!b){
                //添加选课失败,抛出异常,消息重回队列
                XueChengPlusException.cast("收到支付结果,添加选课失败");

            }

        }

    }
}

这个是有意思的一个接口常量类

package com.tianji.common.constants;

public interface MqConstants {
    interface Exchange{
        /*课程有关的交换机*/
        String COURSE_EXCHANGE = "course.topic";

        /*订单有关的交换机*/
        String ORDER_EXCHANGE = "order.topic";

        /*学习有关的交换机*/
        String LEARNING_EXCHANGE = "learning.topic";

        /*信息中心短信相关的交换机*/
        String SMS_EXCHANGE = "sms.direct";

        /*异常信息的交换机*/
        String ERROR_EXCHANGE = "error.topic";

        /*支付有关的交换机*/
        String PAY_EXCHANGE = "pay.topic";
        /*交易服务延迟任务交换机*/
        String TRADE_DELAY_EXCHANGE = "trade.delay.topic";

         /*点赞记录有关的交换机*/
        String LIKE_RECORD_EXCHANGE = "like.record.topic";

        /*优惠促销有关的交换机*/
        String PROMOTION_EXCHANGE = "promotion.topic";
    }
    interface Queue {
        String ERROR_QUEUE_TEMPLATE = "error.{}.queue";
    }
    interface Key{
        /*课程有关的 RoutingKey*/
        String COURSE_NEW_KEY = "course.new";
        String COURSE_UP_KEY = "course.up";
        String COURSE_DOWN_KEY = "course.down";
        String COURSE_EXPIRE_KEY = "course.expire";
        String COURSE_DELETE_KEY = "course.delete";

        /*订单有关的RoutingKey*/
        String ORDER_PAY_KEY = "order.pay";
        String ORDER_REFUND_KEY = "order.refund";

        /*积分相关RoutingKey*/
        /* 写回答 */
        String WRITE_REPLY = "reply.new";
        /* 签到 */
        String SIGN_IN = "sign.in";
        /* 学习视频 */
        String LEARN_SECTION = "section.learned";
        /* 写笔记 */
        String WRITE_NOTE = "note.new";
        /* 笔记被采集 */
        String NOTE_GATHERED = "note.gathered";

        /*点赞的RoutingKey*/
        String LIKED_TIMES_KEY_TEMPLATE = "{}.times.changed";
        /*问答*/
        String QA_LIKED_TIMES_KEY = "QA.times.changed";
        /*笔记*/
        String NOTE_LIKED_TIMES_KEY = "NOTE.times.changed";

        /*短信系统发送短信*/
        String SMS_MESSAGE = "sms.message";

        /*异常RoutingKey的前缀*/
        String ERROR_KEY_PREFIX = "error.";
        String DEFAULT_ERROR_KEY = "error.#";

        /*支付有关的key*/
        String PAY_SUCCESS = "pay.success";
        String REFUND_CHANGE = "refund.status.change";

        String ORDER_DELAY_KEY = "delay.order.query";

        /*领取优惠券的key*/
        String COUPON_RECEIVE = "coupon.receive";
    }
}

14远程调用其他模块Feign

在公用模块创建一个client

@FeignClient(contextId = "course", value = "course-service")
public interface CourseClient {

    /**
     * 根据老师id列表获取老师出题数据和讲课数据
     * @param teacherIds 老师id列表
     * @return 老师id和老师对应的出题数和教课数
     */
    @GetMapping("/course/infoByTeacherIds")
    List<SubNumAndCourseNumDTO> infoByTeacherIds(@RequestParam("teacherIds") Iterable<Long> teacherIds);

    /**
     * 根据小节id获取小节对应的mediaId和课程id
     *
     * @param sectionId 小节id
     * @return 小节对应的mediaId和课程id
     */
    @GetMapping("/course/section/{id}")
    SectionInfoDTO sectionInfo(@PathVariable("id") Long sectionId);

    /**
     * 根据媒资Id列表查询媒资被引用的次数
     *
     * @param mediaIds 媒资id列表
     * @return 媒资id和媒资被引用的次数的列表
     */
    @GetMapping("/course/media/useInfo")
    List<MediaQuoteDTO> mediaUserInfo(@RequestParam("mediaIds") Iterable<Long> mediaIds);

    /**
     * 根据课程id查询索引库需要的数据
     *
     * @param id 课程id
     * @return 索引库需要的数据
     */
    @GetMapping("/course/{id}/searchInfo")
    CourseSearchDTO getSearchInfo(@PathVariable("id") Long id);

    /**
     * 根据课程id集合查询课程简单信息
     * @param ids id集合
     * @return 课程简单信息的列表
     */
    @GetMapping("/courses
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值