近年来,随着社会的快速发展,各地的旅客量迅速增加,宾馆数量也随之急剧增加,这就使得当用户面对如此多的酒店时不知道该如何选取,哪个酒店更适合自己,这样就需要有一种方便快捷的方式,可以让用户随时随地可以获取到符合自身需求的酒店信息,这就是我们今天所做的酒旅项目。
这个项目是基于微信的小程序,在当今时代,手机几乎就是人们的生活必需品,而微信又基本成为了手机的一个基本功能,所以基于微信的小程序对于用户来说十分友好,当用户外出旅行或工作,都可以随时获取到自己所处位置的所有酒店信息,可以让用户进行各种纬度的比较,选择出适合自己的酒店。
一个预定酒店的程序,那就肯定涉及到用户,酒店(商家),平台三个部分,所有以我们需要有:
前台系统
用户前台服务系统(用于用户订购)
后台系统
酒店(商家)后台服务系统(用于酒店管理自身基本信息,房间数量,评价等)
平台后台服务系统(用户管理用户,酒店,一些平台公告等)
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的商品搜索系统服务
Nginx是一款轻量级的HTTP服务器,采用事件驱动的异步非阻塞处理方式框架,这让其具有极好的IO性能,时常用于服务端的反向代理和负载均衡 。
好处:
微服务系统中,所有前端请求首先要通过网关,才能访问后端的业务功能。API 网关是一个处于应用程序或服务(提供 REST API 接口服务)之前的系统,用来管理授权、访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的调用者透明。因此,隐藏在 API 网关后面的业务系统就可以专注于创建和管理服务,而不用去处理这些策略性的基础设施。
微服务系统中,所有的微服务都要注册到Nacos中,方便我们管理,同时只有注册了以后,微服务之间才可以调用。我们也可以将微服务的配置文件按照一定的格式放置与配置中心,当我们修改配置文件时,不需要在程序中修改,只要在配置中心修改即可,微服务会自动获取,不用重新启动。
某个服务服务未注册时,通过Ribbon的方式可以调用该微服务中的接口,而openFeign必须是注册的微服务才行
在调用微服务时,微服务可能发生某些意外,造成微服务不可用,这时候就需要熔断降级
在整个项目中,我负责微信小程序用户登录的模块
技术 | 说明 |
---|---|
SpringBoot | 容器+MVC框架 |
MyBatis-Plus | ORM框架 |
SpringSecurity | 认证和授权框架 |
OAuth2 | OAuth2授权支持 |
JWT | JWT登录支持 |
Lombok | 简化对象封装根据 |
Hutool | Java工具类库 |
knife4j | 文档生成工具 |
Hibernator-Validator | 验证框架 |
user_info 用户信息表
user_login_info 用户登录表
响应示例
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;
}
}
代码编写注意:
从0到1与小伙伴们一起完成了整个项目,真实的体会到了一个项目的开发过程,从中发现了许多自己不足之处,也学习到了许多的新东西,尤其是对一些技术的应用体会更深了,也感觉作为一个程序员要学的东西还很多,不能懈怠,同时也明白一个项目是通过大家共同来完成的,协作十分重要,不然事半功倍,要多与别人沟通,听取别人的意见,取长补短,才能更好的成长。