尚医通(医院预约挂号系统)笔记

全栈项目【尚医通】预约挂号系统项目介绍 尚医通项目是一款医院在线预约挂号项目,使用前后端分离,主要功能如下图所示:管理端主要功能分为数据管理,会员管理,订单管理,医院管理与统计管理五大模块。数据管理模块是对共通数据进行的封装处理,相当于数据字典。会员管理模块是对会员用户的管理,会员权限审批等。订单管理模块是对客户预约挂号订单的管理。医院管理就是管理相关医院的信息,统计管理是对某一时间段中预约挂号流量的统计。 阅读详情

参考资料

对每一个模块流程的具体介绍

业务架构及流程图

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

模块一: 登录系统

1. 手机登录

1.1 业务流程

  • 传入手机号和验证码
  • 校验手机号和验证码是否为空
  • 校验手机验证码和输入的验证码是否一致
    (在点击发送验证码按钮时,调用短信模块,使用阿里云短信实现发送验证码,且保存到redis中)
  • 绑定手机号 (微信登录相关)
  • 判断是否为第一次登录,若是第一次登录,保存信息到数据库
  • 返回登录信息,用户名和token(用JWT生成)

1.2 代码

    public Map<String, Object> loginUser(LoginVo loginVo) {
        //从loginVo获取输入的手机号,和验证码
        String phone = loginVo.getPhone();
        String code = loginVo.getCode();

        //判断手机号和验证码是否为空
        if(StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)) {
            throw new YyghException(ResultCodeEnum.PARAM_ERROR);
        }

        //判断手机验证码和输入的验证码是否一致
        String redisCode = redisTemplate.opsForValue().get(phone);
        if(!code.equals(redisCode)) {
            throw new YyghException(ResultCodeEnum.CODE_ERROR);
        }

        //绑定手机号码
        UserInfo userInfo = null;
        if(!StringUtils.isEmpty(loginVo.getOpenid())) {
            userInfo = this.selectWxInfoOpenId(loginVo.getOpenid());
            if(null != userInfo) {
                userInfo.setPhone(loginVo.getPhone());
                this.updateById(userInfo);
            } else {
                throw new YyghException(ResultCodeEnum.DATA_ERROR);
            }
        }

        //如果userinfo为空,进行正常手机登录
        if(userInfo == null) {
            //判断是否第一次登录:根据手机号查询数据库,如果不存在相同手机号就是第一次登录
            QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
            wrapper.eq("phone",phone);
            userInfo = baseMapper.selectOne(wrapper);
            if(userInfo == null) { //第一次使用这个手机号登录
                //添加信息到数据库
                userInfo = new UserInfo();
                userInfo.setName("");
                userInfo.setPhone(phone);
                userInfo.setStatus(1);
                baseMapper.insert(userInfo);
            }
        }

        //校验是否被禁用
        if(userInfo.getStatus() == 0) {
            throw new YyghException(ResultCodeEnum.LOGIN_DISABLED_ERROR);
        }

        //不是第一次,直接登录
        //返回登录信息
        //返回登录用户名
        //返回token信息
        Map<String, Object> map = new HashMap<>();
        String name = userInfo.getName();
        if(StringUtils.isEmpty(name)) {
            name = userInfo.getNickName();
        }
        if(StringUtils.isEmpty(name)) {
            name = userInfo.getPhone();
        }
        map.put("name",name);

        //jwt生成token字符串
        String token = JwtHelper.createToken(userInfo.getId(), name);
        map.put("token",token);
        return map;
    }

1.3 JWT

在这里插入图片描述
base64编码,并不是加密,只是把明文信息变成了不可见的字符串。但是其实只要用一些工具就可以把base64编码解成明文,所以不要在JWT中放入涉及私密的信息。
由id+name得到token,且能由token反向得到name和id

    private static long tokenExpiration = 24*60*60*1000;
    //签名秘钥
    private static String tokenSignKey = "123456";

    //根据参数生成token
    public static String createToken(Long userId, String userName) {
        String token = Jwts.builder()
                .setSubject("YYGH-USER")
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .claim("userId", userId)
                .claim("userName", userName)
                .signWith(SignatureAlgorithm.HS512, tokenSignKey)
                .compressWith(CompressionCodecs.GZIP)
                .compact();
        return token;
    }

2. 微信登陆

2.1 业务流程

  • 打开页面后,出现二维码
    申请二维码过程略,得到id,密钥,域名放到配置文件中;
    写一个接口,返回生成二维码需要的参数
wx.open.app_id=wxed9954c01bb89b47
wx.open.app_secret=a7482517235173ddb4083788de60b90e
wx.open.redirect_url=http://guli.shop/api/ucenter/wx/callback
yygh.baseUrl=http://localhost:3000
  • 扫码,完成后绑定手机号

2.2 代码

生成扫描二维码 (参考文档)

    //1 生成微信扫描二维码
    //返回生成二维码需要参数
    @GetMapping("getLoginParam")
    @ResponseBody
    public Result genQrConnect() {
        try {
            Map<String, Object> map = new HashMap<>();
            map.put("appid", ConstantWxPropertiesUtils.WX_OPEN_APP_ID);
            map.put("scope","snsapi_login");
            String wxOpenRedirectUrl = ConstantWxPropertiesUtils.WX_OPEN_REDIRECT_URL;
            wxOpenRedirectUrl = URLEncoder.encode(wxOpenRedirectUrl, "utf-8");
            map.put("redirect_uri",wxOpenRedirectUrl);
            map.put("state",System.currentTimeMillis()+"");
            return Result.ok(map);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }

回调函数,扫码后的返回内容
扫码后的过程:
在这里插入图片描述
回调方法的逻辑:
在这里插入图片描述
access_token token凭证
openid 微信的唯一id,判断用户是否存在
在第三步完成后,还需要绑定手机号,返回token,跳转到前端页面
跳转前端页面:携带token,openid重定向到前端页面

    //微信扫描后回调的方法
    @GetMapping("callback")
    public String callback(String code,String state) {
        //第一步 获取临时票据 code
        System.out.println("code:"+code);
        //第二步 拿着code和微信id和秘钥,请求微信固定地址 ,得到两个值
        //使用code和appid以及appscrect换取access_token
        //  %s   占位符
        StringBuffer baseAccessTokenUrl = new StringBuffer()
                .append("https://api.weixin.qq.com/sns/oauth2/access_token")
                .append("?appid=%s")
                .append("&secret=%s")
                .append("&code=%s")
                .append("&grant_type=authorization_code");
        String accessTokenUrl = String.format(baseAccessTokenUrl.toString(),
                ConstantWxPropertiesUtils.WX_OPEN_APP_ID,
                ConstantWxPropertiesUtils.WX_OPEN_APP_SECRET,
                code);
        //使用httpclient请求这个地址
        try {
            String accesstokenInfo = HttpClientUtils.get(accessTokenUrl);
            System.out.println("accesstokenInfo:"+accesstokenInfo);
            //从返回字符串获取两个值 openid  和  access_token
            JSONObject jsonObject = JSONObject.parseObject(accesstokenInfo);
            String access_token = jsonObject.getString("access_token");
            String openid = jsonObject.getString("openid");

            //判断数据库是否存在微信的扫描人信息
            //根据openid判断
            UserInfo userInfo = userInfoService.selectWxInfoOpenId(openid);
            if(userInfo == null) { //数据库不存在微信信息
                //第三步 拿着openid  和  access_token请求微信地址,得到扫描人信息
                String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                        "?access_token=%s" +
                        "&openid=%s";
                String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);
                String resultInfo = HttpClientUtils.get(userInfoUrl);
                System.out.println("resultInfo:"+resultInfo);
                JSONObject resultUserInfoJson = JSONObject.parseObject(resultInfo);
                //解析用户信息
                //用户昵称
                String nickname = resultUserInfoJson.getString("nickname");
                //用户头像
                String headimgurl = resultUserInfoJson.getString("headimgurl");

                //获取扫描人信息添加数据库
                userInfo = new UserInfo();
                userInfo.setNickName(nickname);
                userInfo.setOpenid(openid);
                userInfo.setStatus(1);
                userInfoService.save(userInfo);
            }
            //返回name和token字符串
            Map<String,String> map = new HashMap<>();
            String name = userInfo.getName();
            if(StringUtils.isEmpty(name)) {
                name = userInfo.getNickName();
            }
            if(StringUtils.isEmpty(name)) {
                name = userInfo.getPhone();
            }
            map.put("name", name);

            //判断userInfo是否有手机号,如果手机号为空,返回openid
            //如果手机号不为空,返回openid值是空字符串
            //前端判断:如果openid不为空,绑定手机号,如果openid为空,不需要绑定手机号
            if(StringUtils.isEmpty(userInfo.getPhone())) {
                map.put("openid", userInfo.getOpenid());
            } else {
                map.put("openid", "");
            }
            //使用jwt生成token字符串
            String token = JwtHelper.createToken(userInfo.getId(), name);
            map.put("token", token);
            //跳转到前端页面
            return "redirect:" + ConstantWxPropertiesUtils.YYGH_BASE_URL + "/weixin/callback?token="+map.get("token")+ "&openid="+map.get("openid")+"&name="+URLEncoder.encode(map.get("name"),"utf-8");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

2.3 OAthu2

  • (1) 开放系统间的授权

在这里插入图片描述
在这里插入图片描述
资源拥有者可以访问资源,但是客户应用不能。但是此时客户应用要访问资源,因此拥有者需要给客户应用授权。
那么如何授权呢?
采用颁发令牌的方式。接近OAuth2方式,需要考虑如何管理令牌、颁发令牌、吊销令牌,需要统一的协议,因此就有了OAuth2协议。
在这里插入图片描述

  • (2) 单点登录问题
    什么是单点登录问题?
    一个模块登录后,其他模块也可以访问,不需要再次登录。
    在这里插入图片描述
    解决方法:
    在这里插入图片描述

3. 用户认证与网关整合


把登录校验放到网关里面去。

  • Filter判断哪些需要登录校验,哪些不需要
  • 利用JWT工具从token中拿出id,若不为空,说明已经登录
package com.atguigu.yygh.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.yygh.common.helper.JwtHelper;
import com.atguigu.yygh.common.result.Result;
import com.atguigu.yygh.common.result.ResultCodeEnum;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * <p>
 * 全局Filter,统一处理会员登录与外部不允许访问的服务
 * </p>
 *  * @author qy
 * @since 2019-11-21
 */
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        System.out.println("==="+path);

        //内部服务接口,不允许外部访问
        if(antPathMatcher.match("/**/inner/**", path)) {
            ServerHttpResponse response = exchange.getResponse();
            return out(response, ResultCodeEnum.PERMISSION);
        }

        //api接口,异步请求,校验用户必须登录
        if(antPathMatcher.match("/api/**/auth/**", path)) {
            Long userId = this.getUserId(request);
            if(StringUtils.isEmpty(userId)) {
                ServerHttpResponse response = exchange.getResponse();
                return out(response, ResultCodeEnum.LOGIN_AUTH);
            }
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

    /**
     * api接口鉴权失败返回数据
     * @param response
     * @return
     */
    private Mono<Void> out(ServerHttpResponse response, ResultCodeEnum resultCodeEnum) {
        Result result = Result.build(null, resultCodeEnum);
        byte[] bits = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //指定编码,否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    /**
     * 获取当前登录用户id
     * @param request
     * @return
     */
    private Long getUserId(ServerHttpRequest request) {
        String token = "";
        List<String> tokenList = request.getHeaders().get("token");
        if(null  != tokenList) {
            token = tokenList.get(0);
        }
        if(!StringUtils.isEmpty(token)) {
            return JwtHelper.getUserId(token);
        }
        return null;
    }
}

模块二:预约挂号及下单

1. 模块设计

  • 展示科室信息页面
    Feign调用获取就诊人接口,排班下单信息接口

  • 挂号功能
    预约成功后,消息队列发送短信和更新排班数量信息(在短信和排班模块中,添加一个监听器,监听消息队列,有消息就调用方法)

  • 支付功能
    (1)通过appid与商户编号还有密钥,可以生成微信支付二维码
    (2)前端点开预约订单,之后点开支付,前端会传给后端一个订单id,这是订单数据库中的主键,后端根据这个主键,来生成支付二维码,存储支付信息等。controller层接到订单id之后,调用service层的方法
    (3)在service中,首先会在redis中查找是否已经存在该订单id相关的二维码信息,也就是二维码是否已经生成。如果找到了,就直接返回二维码信息就可以了。
    (4)如果用户第一次点开收款码,那么后端就会根据订单id生成微信收款码。首先需要更新收款信息表,先根据订单xid取出订单信息,根据订单信息保存收款信息,将微信支付所需要的参数都封装到map集合中。使用写好的HttpClient工具类向微信发送请求,在这个类中微信返回的数据会使用content参数接收。将微信返回的结果集封装到map中,其中包括订单id,订单金额,订单状态码,以及返回的二维码图片地址。如果微信能够返回状态码的话,说明生成支付二维码成功,将这些信息保存在redis数据库中两个小时,当用户关闭支付页面后,再次点开就不需要再次请求微信了。
    (5)微信支付状态判断,前端在生成二维码之后,会每隔一段时间申请后端的服务器方法,判断支付状态。前端传入订单id,后端根据订单id来申请微信,判断支付状态。后端的controller根据前端传入的订单id,调用service层,接收到一个map集合,如果为null,那么说明二维码已过期,如果map中的支付状态显示SUCCESS,表示已经支付成功,那么就可以根据订单编号修改数据库中的支付状态,返回支付成功。其他情况就是支付中。
    在这里插入图片描述

在这里插入图片描述

  • 微信退款
    **整体介绍:**当用户要取消预约时,除了修改数据库中的信息,如果已经付了款的话,还需要将已经付的款退还给用户,这时候需要用到微信退款功能,如果用到微信退款功能,则需要一个cert证书,这里直接复制的老师,一般学生搞不到这个(300元一年)。
    (1) 前端访问服务器,在controller直接调用业务层,并没有其他的代码,业务层返回一个boolean类型的值,表示删除成功或失败。
    (2) 在service层中,先根据订单id获取订单的全部信息,之后判断是否可以取消订单,如果订单超时会抛异常。
    (3) 之后调用医院接口,将医院方的数据库的订单信息修改,医院方会返回一个状态码,如果为200表示医院方修改成功,那么就可以进行后续操作。
    (4) 如果医院方删除成功,那么就直接调用WeixinService中的退款功能,以及更新订单状态,之后通过消息队列RabbitMQ发送短信。
    (5) 微信退款功能,首先需要根据订单id与支付方式获取到支付相关的信息,之后添加到退款数据库中(这里的添加退款方法中,如果数据已存在,那么就不需要继续添加,返回存在的数据),已退款则直接返回true.未退款则封装数据,发送请求,接收参数等,返回信息,修改数据库
  • 其他一些模块
    用户自己的订单管理,系统的订单管理模块

2. 代码分析及详细流程

在这里插入图片描述

在这里插入图片描述
(项目好像没有提到超时支付的问题)

    //生成挂号订单
    //入参:排班id,就诊人id
    @Override
    public Long saveOrder(String scheduleId, Long patientId) {
        //获取就诊人信息
        Patient patient = patientFeignClient.getPatientOrder(patientId);

        //获取排班相关信息
        ScheduleOrderVo scheduleOrderVo = hospitalFeignClient.getScheduleOrderVo(scheduleId);

        //判断当前时间是否还可以预约
        if(new DateTime(scheduleOrderVo.getStartTime()).isAfterNow()
                || new DateTime(scheduleOrderVo.getEndTime()).isBeforeNow()) {
            throw new YyghException(ResultCodeEnum.TIME_NO);
        }

        //获取签名信息
        SignInfoVo signInfoVo = hospitalFeignClient.getSignInfoVo(scheduleOrderVo.getHoscode());

        //添加到订单表
        OrderInfo orderInfo = new OrderInfo();
        //scheduleOrderVo 数据复制到 orderInfo
        BeanUtils.copyProperties(scheduleOrderVo,orderInfo);
        //向orderInfo设置其他数据
        String outTradeNo = System.currentTimeMillis() + ""+ new Random().nextInt(100);
        orderInfo.setOutTradeNo(outTradeNo);
        orderInfo.setScheduleId(scheduleId);
        orderInfo.setUserId(patient.getUserId());
        orderInfo.setPatientId(patientId);
        orderInfo.setPatientName(patient.getName());
        orderInfo.setPatientPhone(patient.getPhone());
        orderInfo.setOrderStatus(OrderStatusEnum.UNPAID.getStatus());
        baseMapper.insert(orderInfo);

        //调用医院接口,实现预约挂号操作
        //设置调用医院接口需要参数,参数放到map集合
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("hoscode",orderInfo.getHoscode());
        paramMap.put("depcode",orderInfo.getDepcode());
        paramMap.put("hosScheduleId",orderInfo.getScheduleId());
        paramMap.put("reserveDate",new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd"));
        paramMap.put("reserveTime", orderInfo.getReserveTime());
        paramMap.put("amount",orderInfo.getAmount());

        paramMap.put("name", patient.getName());
        paramMap.put("certificatesType",patient.getCertificatesType());
        paramMap.put("certificatesNo", patient.getCertificatesNo());
        paramMap.put("sex",patient.getSex());
        paramMap.put("birthdate", patient.getBirthdate());
        paramMap.put("phone",patient.getPhone());
        paramMap.put("isMarry", patient.getIsMarry());
        paramMap.put("provinceCode",patient.getProvinceCode());
        paramMap.put("cityCode", patient.getCityCode());
        paramMap.put("districtCode",patient.getDistrictCode());
        paramMap.put("address",patient.getAddress());
        //联系人
        paramMap.put("contactsName",patient.getContactsName());
        paramMap.put("contactsCertificatesType", patient.getContactsCertificatesType());
        paramMap.put("contactsCertificatesNo",patient.getContactsCertificatesNo());
        paramMap.put("contactsPhone",patient.getContactsPhone());
        paramMap.put("timestamp", HttpRequestHelper.getTimestamp());

        String sign = HttpRequestHelper.getSign(paramMap, signInfoVo.getSignKey());
        paramMap.put("sign", sign);

        //请求医院系统接口
        JSONObject result = HttpRequestHelper.sendRequest(paramMap, signInfoVo.getApiUrl() + "/order/submitOrder");

        if(result.getInteger("code")==200) {
            JSONObject jsonObject = result.getJSONObject("data");
            //预约记录唯一标识(医院预约记录主键)
            String hosRecordId = jsonObject.getString("hosRecordId");
            //预约序号
            Integer number = jsonObject.getInteger("number");;
            //取号时间
            String fetchTime = jsonObject.getString("fetchTime");;
            //取号地址
            String fetchAddress = jsonObject.getString("fetchAddress");;
            //更新订单
            orderInfo.setHosRecordId(hosRecordId);
            orderInfo.setNumber(number);
            orderInfo.setFetchTime(fetchTime);
            orderInfo.setFetchAddress(fetchAddress);
            baseMapper.updateById(orderInfo);
            //排班可预约数
            Integer reservedNumber = jsonObject.getInteger("reservedNumber");
            //排班剩余预约数
            Integer availableNumber = jsonObject.getInteger("availableNumber");
            //发送mq消息,号源更新和短信通知
            //发送mq信息更新号源
            OrderMqVo orderMqVo = new OrderMqVo();
            orderMqVo.setScheduleId(scheduleId);
            orderMqVo.setReservedNumber(reservedNumber);
            orderMqVo.setAvailableNumber(availableNumber);
            //短信提示
            MsmVo msmVo = new MsmVo();
            msmVo.setPhone(orderInfo.getPatientPhone());
            String reserveDate = new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd") + (orderInfo.getReserveTime()==0 ? "上午" : "下午");
            Map<String,Object> param = new HashMap<String,Object>(){{
                put("title", orderInfo.getHosname()+"|"+orderInfo.getDepname()+"|"+orderInfo.getTitle());
                put("amount", orderInfo.getAmount());
                put("reserveDate", reserveDate);
                put("name", orderInfo.getPatientName());
                put("quitTime", new DateTime(orderInfo.getQuitTime()).toString("yyyy-MM-dd HH:mm"));
            }};
            msmVo.setParam(param);
            orderMqVo.setMsmVo(msmVo);

            rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_ORDER, MqConst.ROUTING_ORDER, orderMqVo);
        } else {
            throw new YyghException(result.getString("message"), ResultCodeEnum.FAIL.getCode());
        }
        return orderInfo.getId();
    }

面试问答QA

0. 介绍

校园医院预约挂号平台是一款互联网在线预约挂号平台,旨在缓解疫情期间学生和教职工看病难、挂号难的就医难题,帮助患者轻松挂号看病。该项目采用了SpringCloud微服务架构和前后端分离技术,主要由后台管理系统和前台用戶系统2大部分组成。我在该项目主要负责了登录模块和预约挂号模块。项目中遇到的最大挑战主要有两点:1是在高并发请求下,保证系统的性能和可扩展性;2是用户认证与网关整合。

1. 预约挂号超抢问题

添加链接描述

  • 解决方案1:(之前就是用的这个)
    将存库从MySQL前移到Redis中,所有的写操作放到内存中,由于Redis中不存在锁故不会出现互相等待,并且由于Redis的写性能和读性能都远高于MySQL,(因为Redis是单线程,可以信任返回结果)这就解决了高并发下的性能问题。然后通过队列等异步手段,将变化的数据异步写入到DB中。
    优点:解决性能问题
    缺点:没有解决超卖问题,同时由于异步写入DB,存在某一时刻DB和Redis中数据不一致的风险。
    找一下解决这个问题的方案
  • 解决方案2:
    引入队列,然后将所有写DB操作在单队列中排队,完全串行处理。当达到库存阀值的时候就不在消费队列,并关闭购买功能。这就解决了超卖问题。
    优点:解决超卖问题,略微提升性能。
    缺点:性能受限于队列处理机处理性能和DB的写入性能中最短的那个,另外多商品同时抢购的时候需要准备多条队列。
  • 解决方案3:
    将提交操作变成两段式,先申请后确认。然后利用Redis的原子自增操作(相比较MySQL的自增来说没有空洞),同时利用Redis的事务特性来发号,保证拿到小于等于库存阀值的号的人都可以成功提交订单。然后数据异步更新到DB中。
    优点:解决超卖问题,库存读写都在内存中,故同时解决性能问题。
    缺点:由于异步写入DB,可能存在数据不一致。另可能存在少买,也就是如果拿到号的人不真正下订单,可能库存减为0,但是订单数并没有达到库存阀值。

2. 超时支付问题

添加链接描述
1.在生成订单时(表中记录订单的创建时间)
2.根据订单的创建时间到30分钟为准,如果订单没有支付
3.将订单生成消息放到死信队列
4.消费死信队列里面的消息: 删除数据库中未付款的订单, 需要去修改商品的库存

如何获取超过限定时间的订单?我们可以使用延迟消息队列(死信队列)来实现。
所谓延迟消息队列,就是消息的生产者发送的消息并不会立刻被消费,而是在设定的时间之后才可以消费。
我们可以在订单创建时发送一个延迟消息,消息为订单号,系统会在限定时间之后取出这个消息,然后查询订单的支付状态,根据结果做出相应的处理。

3. 对登录的理解,为何采用JWT

3.1 登陆的业务逻辑

1)登录采取弹出层的形式
2)登录方式:
手机+手机验证码
微信扫描
3)无注册界面,第一次登录根据手机号判断系统是否存在,如果不存在则自动注册
4)微信扫描登录成功必须绑定手机号码,即:第一次扫描成功后绑定手机号,以后登录扫描直接登录成功
5)网关统一判断登录状态,如何需要登录,页面弹出登录层(api接口异步请求的,采取url规则匹配)

3.2 JWT

  • (1) JWT登录流程
    1、在用户登录网站的时候,需要输入用户名、密码或者短信验证的方式登录,登录请求到达服务端的时候,服务端对账号、密码进行验证,然后计算出 JWT 字符串,返回给客户端。
    2、客户端拿到这个 JWT 字符串后,存储到 cookie 或者 浏览器的 LocalStorage 中。
    3、再次发送请求,比如请求用户设置页面的时候,在 HTTP 请求头中加入 JWT 字符串,或者直接放到请求主体中。
    4、服务端拿到这串 JWT 字符串后,使用 base64的头部和 base64 的载荷部分,通过HMACSHA256算法计算签名部分,比较计算结果和传来的签名部分是否一致,如果一致,说明此次请求没有问题,如果不一致,说明请求过期或者是非法请求。
  • (2)早期的cookie-session认证方式
    流程:
    用户输入用户名、密码或者用短信验证码方式登录系统;
    服务端验证后,创建一个 Session 信息,并且将 SessionID 存到 cookie,发送回浏览器;
    下次客户端再发起请求,自动带上 cookie 信息,服务端通过 cookie 获取 Session 信息进行校验;
    存在的问题:
  1. Cookie-Session 只能在 web 场景下使用,如果是 APP 呢,APP 可没有地方存 cookie。
  2. 跨域问题, 但Cookie 是不能跨域的。拿天猫商城来说,当你进入天猫商城后,会看到顶部有天猫超市、天猫国际、天猫会员这些菜单。而点击这些菜单都会进入不同的域名,不同的域名下的 cookie 都是不一样的,你在 A 域名下是没办法拿到 B 域名的 cookie 的,即使是子域也不行。
  3. 如果是分布式服务,需要考虑 Session 同步问题。

3.3 权限管理

所有请求经过服务网关,服务网关对外暴露接口,网关进行统一用户认证。
Api接口异步请求的,我们采取url规则匹配,如:/api//auth/,如凡是满足该规则的都必须用户认证

3.4 判断用户登录状态

我们统一从header头信息中获取
如何判断用户信息合法:
登录时我们返回用户token,在服务网关中获取到token后,我在到redis中去查看用户id,如何用户id存在,则token合法,否则不合法

3.5 跨域问题

在这里插入图片描述
因为跨域问题是浏览器对于ajax请求的一种安全限制:一个页面发起的ajax请求,只能是与当前页域名相同的路径,这能有效的阻止跨站攻击。
因此:跨域问题 是针对ajax的一种限制。
gateway网关解决了跨域问题。

4. 微服务

4.1 如何划分,搭建

在这里插入图片描述
在这里插入图片描述
微服务写在项目的service里面
公共服务模块:异常结果处理,返回结果处理,rabbitMQ,redis等中间件的配置;
医院服务模块:排班信息,医院信息等
短信服务:发送短信通知患者
用户服务:查看用户信息,绑定个人信息等
订单服务:预约挂号,支付功能等
存储服务:将数据上传到阿里云oss
统计服务:统计预约挂号的数据
定时任务:每天定时提醒就医

4.2 模块间如何调用

通过Feign调用
Feign的原理

5. 消息队列

5.1 哪里用到了消息队列,作用

订单相关操作时,用mq发送短信消息给短信消费者。
如果商品服务和订单服务是两个不同的微服务,在下单的过程中订单服务需要调用商品服务进行扣库存操作。按照传统的方式,下单过程要等到调用完毕之后才能返回下单成功,如果网络产生波动等原因使得商品服务扣库存延迟或者失败,会带来较差的用户体验,使用MQ发送短信

5.2 项目中用到了消息队列的哪种模式?

选择的是Rounting模式。
在这里插入图片描述

6. MongoData对比Mysql,Redis的优缺点

项目的数据库表结构

  • 为什么用MongoData
    使用MongoDb存储一些非关系型的数据,提高访问速度

  • 对比Mysql
    mysql:
    在不同的引擎上有不同的存储方式。
    查询语句是使用传统的sql语句,拥有较为成熟的体系,成熟度很高。
    开源数据库的份额在不断增加,mysql的份额页在持续增长。
    MongoDB
    快速:在适量级的内存的Mongodb的性能是非常迅速的,它将热数据存储在物理内存中,使得热数据的读写变得十分快。
    高扩展性:mongodb的高可用与集群架构拥有十分高的扩展性,通过物理机器的增加,以及sharding的增加,mongodb的扩展将达到一个十分惊人的地步。
    自身的failover机制。Mongodb的副本集配置中,当主库遇到问题,无法继续提供服务的时候,副本集将选举出一个新的主库继续提供服务
    json的存储格式:mongodb的json与bson格式很适合文档格式的存储与查询。

  • 对比Redis
    内存管理
    Redis 数据全部存在内存,定期写入磁盘,当内存不够时,可以选择指定的 LRU 算法删除数据。
    MongoDB 数据会优先存于内存,当内存不够时,只将热点数据放入内存,其他数据存在磁盘。
    需要注意的是Redis 和mongoDB特别消耗内存,一般不建议将它们和别的服务部署在同一台服务器上。
    数据结构
    Redis 支持的数据结构丰富,包括hash、set、list等。
    MongoDB 数据结构比较单一,但是支持丰富的数据表达,索引,最类似关系型数据库,支持的查询语言非常丰富。
    数据量和性能
    当物理内存够用的时候,性能,redis>mongodb>mysql
    数据量,mysql>mongodb>redis
    注意mongodb可以存储文件,适合存放大量的小文件,内置了GirdFS 的分布式文件系统。
    可靠性
    mongodb从1.8版本后,采用binlog方式(MySQL同样采用该方式)支持持久化,增加可靠性;
    Redis依赖快照进行持久化;AOF增强可靠性;增强可靠性的同时,影响访问性能。
    可靠性上MongoDB优于Redis。

7. Redis用在哪里

1)使用Redis作为缓存
2)验证码有效时间、支付二维码有效时间

8. 介绍什么是数据字典,为什么用它

数据字典就是管理系统**常用的分类数据或者一些固定数据,**例如:省市区三级联动数据、民族数据、行业数据、学历数据等,由于该系统大量使用这种数据,所以我们要做一个数据管理方便管理系统数据,一般系统基本都会做数据管理。

9. 阿里云oss

  • OSS作用
    OSS 具有与平台无关的 RESTful API 接口,可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • OSS的基本概念
    存储空间(Bucket)
    用于存储对象(Object)的容器,同一个存储空间的内部是扁平的,没有文件系统的目录等概念,所有的对象都必须隶属于某个存储空间。存储空间具有各种配置属性,包括地域、访问权限、存储类型等。可根据实际需求,创建不同存储空间存储不同数据。
    对象/文件(Object)
    是 OSS 存储数据的基本单元,也被称为 OSS 的文件。对象由元信息(Object Meta)、用户数据(Data)和文件名(Key)组成。对象由存储空间内部唯一的 Key 来标识。对象元信息是一组键值对,表示了对象的一些属性,比如最后修改时间、大小等信息,支持在元信息中存储一些自定义的信息。对象的生命周期是从上传成功到被删除为止。
    在这里插入图片描述

10. 项目中用到的哪些定时任务?

定时就医提醒。

  • 业务逻辑:
  1. 写好定时任务:每天早上八点钟向负责定时任务的交换机对应的队列发送消息
@Component
@EnableScheduling
public class ScheduledTask {

    @Autowired
    private RabbitService rabbitService;

    //每天8点执行方法,就医提醒
    //cron表达式,设置执行间隔
    //0 0 8 * * ?
    @Scheduled(cron = "0/30 * * * * ?")
    public void taskPatient() {
        //交换机exchange.direct.task
        //路由key:task.8
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_TASK,MqConst.ROUTING_TASK_8,"");
    }
}
  1. 监听对应交换机的消息,调用订单service的方法去消费消息
    RabbitListener用于绑定交换机和队列
    @Autowired
    private OrderService orderService;
    //消费者
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = MqConst.QUEUE_TASK_8, durable = "true"),
            exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_TASK),
            key = {MqConst.ROUTING_TASK_8}
    ))
    public void patientTips(Message message, Channel channel) throws IOException {
        orderService.patientTips();
    }
  1. 调用patientTip方法去消费消息,消费逻辑:先查数据库,满足时间为当天且状态不为-1的订单;从订单提取手机号等有用信息,封装好消息,发给短信模块的交换机
    public void patientTips() {
        QueryWrapper<OrderInfo> wrapper = new QueryWrapper<>();
        //查数据库 找出当天且订单状态不是-1(-1即取消的)
        wrapper.eq("reserve_date",new DateTime().toString("yyyy-MM-dd"));
        wrapper.ne("order_status",OrderStatusEnum.CANCLE.getStatus());
        List<OrderInfo> orderInfoList = baseMapper.selectList(wrapper);
        for(OrderInfo orderInfo:orderInfoList) {
            //短信提示
            MsmVo msmVo = new MsmVo();
            msmVo.setPhone(orderInfo.getPatientPhone());
            String reserveDate = new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd") + (orderInfo.getReserveTime()==0 ? "上午": "下午");
            Map<String,Object> param = new HashMap<String,Object>(){{
                put("title", orderInfo.getHosname()+"|"+orderInfo.getDepname()+"|"+orderInfo.getTitle());
                put("reserveDate", reserveDate);
                put("name", orderInfo.getPatientName());
            }};
            msmVo.setParam(param);
            // 往短信交换机exchange.direct.msm发信息
            rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_MSM, MqConst.ROUTING_MSM_ITEM, msmVo);
        }
    }

11. 项目中有哪些需要改进的地方

预约挂号也是一种方便患者提前安排就医计划,减小候诊时间,便于医院提升管理水平,提高工作效率和医疗质量,降低医疗安全风险的门诊挂号方式。但是预约患者不可能做到100%应诊。因此研究这些浪费医疗资源的人特征,对医疗资源的合理分配有重要的意义。
那么,关于后台需要建立对用户的数据清洗、数据分析、用户画像分层。
如:中老年患者如约就诊的人数最多,65岁以后的老年患者如约就诊的可能性最高,青少年患者如约就诊的可能性较低。我们可以通过数据的分析避免医疗资源的浪费。

12. 项目业务逻辑

12.1 登录

  • 手机登录
  1. 用户输入手机号,点击发送验证码,调用短信模块,使用阿里云短信发送验证码,保存到redis,设置一个过期时间;
  2. 输入手机号和验证码,进行校验,是否一致;
  3. 如果一致,且是首次登录(通过手机号去查数据库),保存数据到数据库,登录成功;
  • 微信登录业务流程

在这里插入图片描述

  1. 用户扫码请求登录第三方应用
  2. 第三方应用请求微信OAuth2.0授权登录微信开放平台
  3. 用户确认后,微信开放平台拉起第三方应用或者重定向到第三方,携带授权临时票据code
  4. 第三方应用通过code和appid,appsecret(id和密钥是申请微信二维码时提供的,需要事先配置好),请求微信开放平台,返回access token
  5. 拿到access_token后实现登录
    回调函数的处理逻辑:
    使用code和appid以及appscrect(密钥)请求微信提供的地址,获取token凭证和微信id,再拿着token和微信id去请求,返回扫描人的信息,根据微信id判断是否绑定了手机号,若没有绑定进行绑定,保存数据到数据库。
    OAuth
    OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,

12.2 预约挂号

在这里插入图片描述

1.用户登录系统,选择医院科室,判断是否可以预约
2.选择可以预约的科室和就诊人,确认挂号
3.通过RabbitMQ消息队列异步处理,更新号源信息和短信通知
取消预约:
1是没有支付的 直接取消,更新数据库
2 是已经支付的,先退款,后更新数据库

13. 项目中的数据库表介绍

添加链接描述

  • 1.yygh_cmn
    这个数据库就是数据字典部分的数据库,只有一个表那就是dict
  • 2.yygh_hosp (MongoDB)
    该数据库也只有一个表 hospital_set对应实体类为model包下hosp中的hospitalSet
  • 3.yygh_manage (MongoDB)
    hospital_set 医院设置
    schedule 排班表
  • 4.yygh_order (Mysql)
    refund_info 退款信息表
    patment_info
    order_info
  • 5.yygh_user (Mysql)
    patient
    user_info
    user_login_record
尚医通【预约挂号系统】总结 1、项目介绍 尚医通即为网上预约挂号系统,网上预约挂号是近年来开展的一项便民就医服务,旨在缓解看病难、挂号难的就医难题,许多患者为看一次病要跑很多次医院,最终还不一定能保证看得上医生。网上预约挂号全面提供的预约挂号业务从根本上解决了这一就医难题。随时随地轻松挂号! 2、技术点 后端: SpringBoot,SpringCloud,mybatisplus,redis,mongodb,easyExcel,rabbitmq, httpclient,nginx,mysql,oss,vue,微信支付,docker 阅读详情

相关推荐

大数据最新全栈项目【尚医通】预约挂号系统项目介绍_尚医通项目(2),你不懂还不学

是使用前后端分离的一款医院在线预约挂号项目,主要功能如下图所示:​管理端主要功能分为五大模块。数据管理模块是对共通数据进行的封装处理,相当于数据字典。会员管理模块是对会员用户的管理,会员权限审批等。订单管理模块是对客户预约挂号订单的管理。医院管理就是管理相关医院的信息,统计管理是对某一时间段中挂号预约流量的统计。

2401_84182271的博客 1030

尚医通笔记(含bug修改方法)

目录 common模块 swagger2 Result(全局统一返回结果) YyghException(自定义全局异常) GlobalExceptionHandler(全局异常处理器) JwtHelper(生成Token、根据Token获取用户信息) AuthContextHolder(获取用户信息) RedisConfig(配置redis的相关规则) HttpRequestHelper MD5加密 HttpUtil model模块 BaseEntity service 尚医.

LX1232456的博客 1万+

尚医通学习笔记,尚医通学习笔记

尚医通学习笔记

day01 尚医通项目简介+mybatisplus 入门

1 尚医通项目介绍2业务流程3系统架构4 MyBatisPlus学习1、是什么、为什么尚医通即为网上预约挂号系统,网上预约挂号是近年来开展的一项便民就医服务,旨在缓解看病难、挂号难的就医难题,许多患者为看一次病要跑很多次医院,最终还不一定能保证看得上医生。网上预约挂号全面提供的预约挂号业务从根本上解决了这一就医难题。随时随地轻松挂号!不用排长队!1、是什么(简称 MP)是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。Redirect2。

leader_song的博客 1994

尚硅谷-尚医通 笔记代码资料.zip

redis缓存数据,使用MongoDB实现高并发读写,整合消息中间件RabbitMQ提高订单的并发量,同时还整合了定时任务,实现就医提醒功能,综合应用了阿里云OSS

CTF-Web习题:2019强网杯 UPLOAD

本题涉及知识点:代码审计、文件上传漏洞、反序列化漏洞

LJW123487的博客 1902

尚医通项目总结

尚医通项目总结

qq_54549253的博客 1万+

点云PCL库学习-双目图像转化为点云PCD并显示

文章目录 双目视觉模型 代码实现 总结 参考文章 一、双目视觉模型 和RGBD相机主动发射光线实现测距不同,双目测距原理通过配置立体摄像头(通常由左眼相机和右眼相机两个水平放置的相机组成)来获取一对同一场景中的二维图像,再基于同一空间点在两个坐标系下的视差来计算深度。双目测距的示意图和数学表达式如下所示: 双目相机的成像示意图 上述示意图描述了一个空间点在左右两目相机中位置之间的成像关系,根据相似三角形原理可得: 其中,f和b分别为相机焦距和双目相机基线,d为空间点在两个坐标系下

weixin_41957687的博客 6948

Day221.尚医通介绍、环境搭建、Gitee使用 -尚医通

尚医通 一、尚医通介绍 1、简介 尚医通即为网上预约挂号系统,网上预约挂号是近年来开展的一项便民就医服务,旨在缓解看病难、挂号难的就医难题,许多患者为看一次病要跑很多次医院,最终还不一定能保证看得上医生。网上预约挂号全面提供的预约挂号业务从根本上解决了这一就医难题。随时随地轻松挂号!不用排长队! 2、 我们能从这个项目中学到什么? 2.1巩固以前知识,学习技术点与技术点应用场景,掌握预约挂号业务流程 2.2 核心技术 3、 业务流程 4、 服务架构 二、预约挂号微服务模块搭建 项

阿昌爱Java 4871

Applied Intelligence投稿经验分享(正在投稿中)

这几天在投稿Applied Intelligence这个期刊,过程很曲折,反反复复,因此写一篇经验分享,希望能记录一下自己的过程,也能给其他人一些帮助(可以在评论区讨论),提醒一下,一定要写一些声明,不会写、不知道写啥就看第四章的第二个问题。

weixin_55988068的博客 1万+

Python脚本自动化芯片设计?PyAether实战教程带你玩转EDA

本文通过PyAether实战教程,详细介绍了如何利用Python脚本实现芯片设计自动化。内容涵盖环境搭建、核心对象模型解析,并重点演示了原理图自动生成、参数化单元创建及设计验证流程集成,帮助EDA工程师从重复性手动操作中解放,显著提升设计效率与一致性。

e4f5g6h的博客 1315

尚医通预约挂号平台:基于 Vue 3 生态的医疗预约系统实践

本文介绍了基于Vue3技术栈开发的"尚医通预约挂号平台"学习项目。该项目完整模拟医院查询、预约挂号全流程,采用Vue3+TypeScript+Vite+Pinia等前沿技术,包含医院筛选、科室选择、号源预约、订单管理等核心功能模块。文章重点解析了项目的技术实现亮点:全局路由守卫、Pinia状态管理、Axios请求封装、组件自动导入等工程化实践。该项目可作为Vue3生态的实战范本,适合开发者学习参考,但仅限技术交流使用。

2301_79762459的博客 1148

【项目立项管理、项目整合管理】30天通过软考高项-第二天

①项目或阶段的概述;②范围目标、范围的评估标准,证明达到完⼯标准的证据;③质量目标、项目和产品质量的评估标准、相关核实信息和实际里程碑交付日期以及偏差原因;④成本目标,包括可接受的成本区间、实际成本,产⽣任何偏差的原因等;⑤最终产品、服务或成果的确认信息的总结;⑥进度计划目标,包括成果是否实现项目预期效益:如果在项目结束时未能实现效益,则指出效益实现程度并预计未来实现情况;

weixin_42520723的博客 1000

尚医通总结(面试模板篇)

尚医通即为网上预约挂号系统,网上预约挂号是近年来开展的一项便民就医服务,旨在缓解看病难、挂号难的就医难题,许多患者为看一次病要跑很多次医院,最终还不一定能保证看得上医生。网上预约挂号全面提供的预约挂号业务从根本上解决了这一就医难题。随时随地轻松挂号!不用排长队!hospital-manage:医院接口模拟端(已开发,直接使用)yygh-parent:根目录,管理子模块:common:公共模块父节点common-util:工具类模块,所有模块都可以依赖于它。

leader_song的博客 2328

自动驾驶感知融合算法拆解:从华为MDC案例看前融合与大融合的工程实现

本文深度解析了华为MDC平台在自动驾驶感知融合算法中的前沿工程实践,重点探讨了Camera-Lidar前融合与多传感器大融合的架构设计。通过异构融合架构、数据流优化和场景化应用,华为MDC平台实现了高效、安全的感知能力,显著提升了自动驾驶系统的性能与可靠性。

weixin_29198045的博客 534

大数据在电力行业的应用案例解析-【电力技术】(九)大数据在电力储能运维中的深度应用(含故障预测代码)

大数据技术为电力储能运维提供了“精准化、智能化、高效化”的解决方案,破解了传统运维的核心痛点,实现了储能系统从“被动抢修”到“主动运维”的转型,为新型电力系统建设提供了重要支撑。当前,随着储能产业的快速发展,大数据与储能运维的融合将更加深入,未来将结合人工智能、物联网、区块链等技术,实现储能运维的全流程智能化——从设备故障的提前预警,到寿命的精准预测,再到运维策略的自动优化、调度协同的实时响应,进一步提升储能系统的安全性、经济性与可靠性。

getusushu的博客 87

Java后端实战项目推荐-尚医通

嗨,大家好,我是代码界的小白,如果喜欢我的文章,可以给个关注、点赞和手操哦,加油! 很多准备找工作的同学都有一个困扰,简历单薄,没有好的项目,担心简历被刷,今天开始后续会给大家推荐一些比较好的开源项目。 虽然大家都知道github或者gitee上都有很多优秀的开源项目,但大多数没有视频教学,对于初学者或者小白来说是很难上手,总不能直接下载下来配置一下环境跑一下就算学习了吧,这显然是不能拿来参加面试的。【当然如果你有基础,建议到github找点开源的项目做做。】 1.项目介绍 该项目是真实存在的项目,本人.

代码届的小白的博客 1万+

【腾讯小龙虾WorkBuddy专栏04】一文搞懂WorkBuddy的「专家」和「专家团」

WorkBuddy专家与专家团:专业级AI协作解决方案 WorkBuddy的专家(Expert)与专家团(Expert Team)功能颠覆了通用AI的局限性,提供角色化、可交付的专业服务。 核心优势 专家:深度垂直领域的AI顾问,具备人设、方法论、工具链,如全栈开发专家能直接交付完整项目,而非代码片段。 专家团:多智能体协作,自动拆解复杂任务(如竞品分析、全栈开发)并分发给对应角色,并行执行后整合输出,效率提升显著。 使用场景 单专家:解决专业问题(如SQL优化、UI设计)。

AI人人懂 2019

尚医通(一)

尚医通 一、前置知识 锁 乐观锁 在查询数据的时候不会加锁,在更新数据之前如果有人更新数据,就不会更新数据 实现 取出记录时,获取当前version 更新时,带上这个version 执行更新时, set version = newVersion where version = oldVersion 如果version不对,就更新失败 悲观锁 每次读取数据的时候都默认其他线程会更改数据,因此需要进行加锁操作,当其他线程想要访问数据时,都需要阻塞挂起 实现 传统的关系型数据库使用这种锁机制,比如行锁,表锁等,读

lovewangyihui的博客 1991

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

1.java项目-尚医通(1)

尚医通项目,第一部分内容,医院设置

hutc_Alan的博客 2372

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

尚医通 (一)项目介绍

尚医通即为网上预约挂号系统,网上预约挂号是近年来开展的一项便民就医服务,旨在缓解看病难、挂号难的就医难题,许多患者为看一次病要跑很多次医院,最终还不一定能保证看得上医生。网上预约挂号全面提供的预约挂号业务从根本上解决了这一就医难题。随时随地轻松挂号!不用排长队!

Knight 2273

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

网络热门编程项目导学:尚医通

鱼皮决定带大家一起学习和鉴定一些网络热门编程项目,希望能帮大家更快地选择适合自己的项目,少走弯路~

努力做最接地气的编程干货分享,感谢关注 916
上一篇: 面经:消息队列
下一篇: 面经:微服务
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值