酒旅项目总结

酒旅项目总结

一. 项目介绍

​ 近年来,随着社会的快速发展,各地的旅客量迅速增加,宾馆数量也随之急剧增加,这就使得当用户面对如此多的酒店时不知道该如何选取,哪个酒店更适合自己,这样就需要有一种方便快捷的方式,可以让用户随时随地可以获取到符合自身需求的酒店信息,这就是我们今天所做的酒旅项目。

​ 这个项目是基于微信的小程序,在当今时代,手机几乎就是人们的生活必需品,而微信又基本成为了手机的一个基本功能,所以基于微信的小程序对于用户来说十分友好,当用户外出旅行或工作,都可以随时获取到自己所处位置的所有酒店信息,可以让用户进行各种纬度的比较,选择出适合自己的酒店。

二. 项目需求分析

一个预定酒店的程序,那就肯定涉及到用户,酒店(商家),平台三个部分,所有以我们需要有:

  • 前台系统

    ​ 用户前台服务系统(用于用户订购)

  • 后台系统

    ​ 酒店(商家)后台服务系统(用于酒店管理自身基本信息,房间数量,评价等)

    ​ 平台后台服务系统(用户管理用户,酒店,一些平台公告等)

    组织结构

    hotel-parent
    ├── config -- 配置中心存储配置
    ├── docunment -- 项目文件
    ├── project-admin -- 后台管理系统服务
    ├── project-auth -- 基于Spring Security Oauth2的统一的认证中心
    ├── project-common -- 工具类及通用代码块
    ├── project-gateway -- 基于Spring Cloud Gateway的微服务API网关服务
    ├── project-job -- 分布式调度系统
    ├── project-mbg -- MyBatisGenerator 生成的数据库操作代码模块
    ├── project-monitor -- 基于spring Boot Admin 的微服务监控中心
    ├──	project-portal -- 前台门户系统服务
    └── project-search -- 基于Elasticsearch的商品搜索系统服务
    

三. 系统架构图

架构图的简析

1.Nginx 作用:反向代理 负载均衡 前端程序一般部署在Nginx中

​ Nginx是一款轻量级的HTTP服务器,采用事件驱动的异步非阻塞处理方式框架,这让其具有极好的IO性能,时常用于服务端的反向代理和负载均衡 。

好处

  • 轻松支持 https
  • 解决前后端跨域问题
  • 前后端解耦,方便维护以及负载均衡
  • Nginx静态资源处理性能要比后端常用的容器高数倍
  • Nginx接收外部访问,类似于后端服务的防火墙,更安全

2.Gateway API 网关

​ 微服务系统中,所有前端请求首先要通过网关,才能访问后端的业务功能。API 网关是一个处于应用程序或服务(提供 REST API 接口服务)之前的系统,用来管理授权、访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的调用者透明。因此,隐藏在 API 网关后面的业务系统就可以专注于创建和管理服务,而不用去处理这些策略性的基础设施。

3.Nacos 微服务注册与配置中心 相当于cloud的config Eureka Bus

​ 微服务系统中,所有的微服务都要注册到Nacos中,方便我们管理,同时只有注册了以后,微服务之间才可以调用。我们也可以将微服务的配置文件按照一定的格式放置与配置中心,当我们修改配置文件时,不需要在程序中修改,只要在配置中心修改即可,微服务会自动获取,不用重新启动。

4.Ribbon OpenFeign 用于微服务之间的调用,负载均衡

​ 某个服务服务未注册时,通过Ribbon的方式可以调用该微服务中的接口,而openFeign必须是注册的微服务才行

5.Sentinel 熔断降级

​ 在调用微服务时,微服务可能发生某些意外,造成微服务不可用,这时候就需要熔断降级

四.我所负责模块

在整个项目中,我负责微信小程序用户登录的模块

1.技术选型(后端技术)
技术 说明
SpringBoot 容器+MVC框架
MyBatis-Plus ORM框架
SpringSecurity 认证和授权框架
OAuth2 OAuth2授权支持
JWT JWT登录支持
Lombok 简化对象封装根据
Hutool Java工具类库
knife4j 文档生成工具
Hibernator-Validator 验证框架
2.登录流程图

酒旅项目总结_第1张图片

3.数据库设计

user_info 用户信息表
酒旅项目总结_第2张图片
user_login_info 用户登录表

酒旅项目总结_第3张图片

数据库设计注意事项:
  1. 金钱使用bigint
  2. 一般情况下数据库必须有createTime updateTime deleted 字段
  3. 数据类型一般设置长度
  4. 字段尽量不为空
  5. 一个表索引不要太多,也不能没有
  6. 数据库设计的三大范式(不一定全部遵循,可以适当冗余)
  7. 时间设置使用timestamp
4.接口设计

微信小程序登录接口
酒旅项目总结_第4张图片

响应示例

酒旅项目总结_第5张图片

5.代码的编写

controller层代码

/**
 * 用户登录 前端控制器
 * @author 宋宏章
 * @since 2022-04-17
 */
@RestController
@RequestMapping("/login")
public class UserLoginController {
    @Resource
    private PasswordEncoder passwordEncoder;

    @Autowired
    private IUserLoginService loginService;
    /**
     * 用户登录
     * @param loginRequestDTO 登录请求DTO
     * @return map 令牌+用户信息Id
     * @author 宋宏章
     */
    @PostMapping
    public CommonResult<Map> login(@NotNull LoginRequestDTO loginRequestDTO){
        return loginService.login(loginRequestDTO);
    }

    /**
     * 通过用户名查询用户登录信息
     * @param username
     * @return 用户对象
     * @author 宋宏章
     */
    @GetMapping("/username")
    public CommonResult<UserLogin> getUserByUsername(@RequestParam("username") @NotNull String username){
        UserLogin user = loginService.getUserByUsername(username);
        return CommonResult.success(user);
    }

    /**
     * 通过用户名密码注册
     * @param username password
     * @return 注册成功
     * @author 宋宏章
     */
    @PostMapping("/register")
    public CommonResult<UserLogin> saveUser(@RequestParam("username") @NotNull String username,@RequestParam("password") @NotNull String password){
        UserLogin user = new UserLogin();
        user.setUsername(username);
        user.setPassword(passwordEncoder.encode(password));
        loginService.saveUser(user);
        return CommonResult.success(null, "操作成功");
    }
}

service层impl代码

@Service
public class UserLoginServiceImpl extends ServiceImpl<UserLoginDAO, UserLogin> implements IUserLoginService {
    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private OAuth2ClientProperties oAuth2ClientProperties;

    @Resource
    private OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails;

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private IUserInfoService userInfoService;

    @Resource
    private WXAppConfig wxAppConfig;


    @Override
    @NotNull(message = "用户未绑定手机号")
    public String getPhoneById(Long id) {
        UserLogin userLogin = this.lambdaQuery().select(UserLogin::getPhone).eq(UserLogin::getId, id).one();
        if (userLogin == null) {
            Asserts.fail("未注册");
        }
        return userLogin.getPhone();
    }

    @Override
    public void updatePhoneById(Long id, String phone) {
        boolean update = this.lambdaUpdate().set(UserLogin::getPhone, phone).eq(UserLogin::getId, id).update();
        if (!update) {
            Asserts.fail("数据库更新失败");
        }
    }


    /**
     * 用户登录
     * @param loginRequestDTO 登录请求DTO
     * @return map 令牌+用户id
     */
    @Override
    public CommonResult<Map> login(LoginRequestDTO loginRequestDTO) {
        Map wxSession = new HashMap<String,Object>();
        //用户名密码登录
        if (ObjectUtil.isNotNull(loginRequestDTO.getUsername())) {
            wxSession = passwordLogin(loginRequestDTO);
        }
        //微信小程序登录
        if (ObjectUtil.isNotNull(loginRequestDTO.getCode())){
            wxSession = wxLogin(loginRequestDTO);
        }
        //获取令牌
        OAuth2AccessToken token = getToken(loginRequestDTO);
        wxSession.put("token", token);
        return CommonResult.success(wxSession);
    }

    /**
     * 微信登录 openid存在进行登录 不存在注册后登录
     * @param loginRequestDTO 登录请求DTO
     * @return map集合 包含openid与userInfoId
     * @author 宋宏章
     */
    @Transactional(rollbackFor = Exception.class)
    protected Map wxLogin(LoginRequestDTO loginRequestDTO) {
        //1.通过微信服务接口获取openid
        HashMap wxSession = wxAppConfig.getOpenid(loginRequestDTO.getCode());
        String openid = (String) wxSession.get("openid");
        if (ObjectUtil.isNull(openid)) {
            Asserts.fail("微信code不正确");
        }
        //2.通过openid查询用户
        UserLogin user = this.getOne(new QueryWrapper<UserLogin>().lambda().eq(UserLogin::getWxOpenid, openid));
        //3.用户不存在,进行注册
        if (ObjectUtil.isNull(user)) {
            user = new UserLogin(openid, passwordEncoder.encode(openid), openid);
            boolean save = this.save(user);
            if (!save) {
                Asserts.fail("注册失败");
            }
            //新增用户信息
            UserInfo userInfo = new UserInfo(user.getId());
            userInfoService.save(userInfo);
            this.lambdaUpdate()
                    .set(UserLogin::getUserInfoId, userInfo.getId())
                    .eq(UserLogin::getId, user.getId())
                    .update();
        }
        wxSession.put("userInfoId", user.getUserInfoId());
        loginRequestDTO.setUsername((String) wxSession.get("openid"));
        loginRequestDTO.setPassword((String) wxSession.get("openid"));
        return wxSession;
    }

    /**
     * 用户名密码登录
     * @param loginRequestDTO 登录请求DTO
     * @return map集合 包含userInfoId
     * @author 宋宏章
     */
    private Map passwordLogin(LoginRequestDTO loginRequestDTO){
        Map<String, Object> map = new HashMap<>();
        UserLogin user = this.getOne(new LambdaQueryWrapper<UserLogin>().eq(UserLogin::getUsername, loginRequestDTO.getUsername()));
        if (null == user || !BPwdEncoderUtil.matches(loginRequestDTO.getPassword(), user.getPassword())) {
            Asserts.fail("用户名或密码不正确");
        }
        map.put("userInfoId", user.getUserInfoId());
        return map;
    }

    /**
     * 使用restTemplate发送请求到授权服务器,申请令牌
     * @param loginRequestDTO 登录请求DTO
     * @return OAuth2AccessToken 令牌
     * @author 宋宏章
     */
    private OAuth2AccessToken getToken(LoginRequestDTO loginRequestDTO){
        // 请求头 "basic auth"
        String clientSecret = oAuth2ClientProperties.getClientId() + ":" + oAuth2ClientProperties.getClientSecret();
        clientSecret = AuthConstant.JWT_TOKEN_PREFIX + Base64.getEncoder().encodeToString(clientSecret.getBytes());
        HttpHeaders headers = new HttpHeaders();
        headers.set(AuthConstant.JWT_TOKEN_HEADER, clientSecret);
        //请求参数
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.put(AuthConstant.USER_NAME, Collections.singletonList(loginRequestDTO.getUsername()));
        map.put(AuthConstant.USER_PASSWORD, Collections.singletonList(loginRequestDTO.getPassword()));
        map.put(AuthConstant.AUTH_GRANT_TYPE, Collections.singletonList(oAuth2ProtectedResourceDetails.getGrantType()));
        map.put(AuthConstant.AUTH_MODEL, oAuth2ProtectedResourceDetails.getScope());
        // HttpEntity(请求参数,头。。。)
        HttpEntity httpEntity = new HttpEntity(map, headers);
        OAuth2AccessToken body = restTemplate.exchange(oAuth2ProtectedResourceDetails.getAccessTokenUri(), HttpMethod.POST, httpEntity, OAuth2AccessToken.class).getBody();
        if (ObjectUtil.isNull(body)) {
            Asserts.fail("令牌请求失败");
        }
        return body;
    }
}

代码编写注意:

  • 一定要规范注解,代码简洁干净
  • 能用if语句,就不要用if…else
  • 善于使用Hutool,Validator这两个工具类,可以简化很多代码
  • 代码的逻辑层卸载service层的impl中,接口继承IService,实现类继承ServiceImpl,再实现接口
  • 一个方法中代码太多,要将部分代码抽取处理封装

五.个人收获

​ 从0到1与小伙伴们一起完成了整个项目,真实的体会到了一个项目的开发过程,从中发现了许多自己不足之处,也学习到了许多的新东西,尤其是对一些技术的应用体会更深了,也感觉作为一个程序员要学的东西还很多,不能懈怠,同时也明白一个项目是通过大家共同来完成的,协作十分重要,不然事半功倍,要多与别人沟通,听取别人的意见,取长补短,才能更好的成长。

你可能感兴趣的:(java)