目录
微信登录(续):
功能实现(续):
业务层:
新增WechatService接口:
package com.zzyl.nursing.service;
public interface WechatService {
/**
* 获取openid
* @param code
* @return
*/
public String getOpenid(String code);
/**
* 获取手机号
* @param detailCode
* @return
*/
public String getPhone(String detailCode);
}
新增WechatServiceImpl实现类:
package com.zzyl.nursing.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zzyl.nursing.service.WechatService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class WechatServiceImpl implements WechatService {
// 登录
private static final String REQUEST_URL = "https://api.weixin.qq.com/sns/jscode2session?grant_type=authorization_code";
// 获取token
private static final String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
// 获取手机号
private static final String PHONE_REQUEST_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=";
@Value("${wechat.appId}")
private String appid;
@Value("${wechat.appSecret}")
private String secret;
/**
* 获取openid
* @param code
* @return
*/
@Override
public String getOpenid(String code) {
// 获取公共参数
Map<String,Object> paramMap = getAppConfig();
paramMap.put("js_code", code);
String result = HttpUtil.get(REQUEST_URL, paramMap);
// 结果是一个map
JSONObject jsonObject = JSONUtil.parseObj(result);
// 判断接口响应是否出错
if(ObjectUtil.isNotEmpty(jsonObject.getInt("errcode"))) {
throw new RuntimeException(jsonObject.getStr("errmsg"));
}
String openid = jsonObject.getStr("openid");
return openid;
}
/**
* 封装公共参数
* @return
*/
private Map<String, Object> getAppConfig() {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("appid", appid);
paramMap.put("secret", secret);
return paramMap;
}
/**
* 获取手机号
* @param detailCode
* @return
*/
@Override
public String getPhone(String detailCode) {
String token = getToken();
String url = PHONE_REQUEST_URL + token;
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("code", detailCode);
// 发起请求
String result = HttpUtil.post(url, JSONUtil.toJsonStr(paramMap));
// 是一个map
JSONObject jsonObject = JSONUtil.parseObj(result);
// 判断接口响应是否出错
if(jsonObject.getInt("errcode") != 0) {
throw new RuntimeException(jsonObject.getStr("errmsg"));
}
return jsonObject.getJSONObject("phone_info").getStr("phoneNumber");
}
/**
* 获取token
* @return
*/
private String getToken() {
Map<String, Object> paramMap = getAppConfig();
// 发起请求
String result = HttpUtil.get(TOKEN_URL, paramMap);
// 是一个map
JSONObject jsonObject = JSONUtil.parseObj(result);
// 判断接口响应是否出错
if(ObjectUtil.isNotEmpty(jsonObject.getInt("errcode"))) {
throw new RuntimeException(jsonObject.getStr("errmsg"));
}
String token = jsonObject.getStr("access_token");
return token;
}
}
在application-dev.yml中配置appidyijiappsecret:
#WECHAT配置
wechat:
appId: ***
appSecret: *****
在IFamilyMemberService中定义login方法:
/**
* 微信登录
* @param userLoginRequestDto
* @return
*/
LoginVo login(UserLoginRequestDto userLoginRequestDto);
实现方法:
@Autowired
private WechatService wechatService;
@Autowired
private TokenService tokenService;
static List<String> DEFAULT_NICKNAME_PREFIX = ListUtil.of(
"生活更美好",
"大桔大利",
"日富一日",
"好柿开花",
"柿柿如意",
"一椰暴富",
"大柚所为",
"杨梅吐气",
"天生荔枝"
);
/**
* 小程序端登录
* @param userLoginRequestDto
* @return
*/
@Override
public LoginVo login(UserLoginRequestDto userLoginRequestDto) {
// 1.调用微信api,根据code获取openId
String openId = wechatService.getOpenid(userLoginRequestDto.getCode());
// 2.根据openId查询用户
FamilyMember familyMember = getOne(Wrappers.<FamilyMember>lambdaQuery(FamilyMember.class)
.eq(FamilyMember::getOpenId, openId));
// 3.如果用户为空,则将openId设置到对象中
if (ObjectUtil.isEmpty(familyMember)) {
familyMember = FamilyMember.builder().openId(openId).build();
}
// 4.调用微信api获取用户绑定的手机号
String phone = wechatService.getPhone(userLoginRequestDto.getPhoneCode());
familyMember.setPhone(phone);
// 5.保存或修改用户
saveOrUpdateFamilyMember(familyMember, phone);
// 6.将用户id存入token,返回
Map<String, Object> claims = new HashMap<>();
claims.put("userId", familyMember.getId());
claims.put("nickName", familyMember.getName());
String token = tokenService.createToken(claims);
LoginVo loginVo = new LoginVo();
loginVo.setToken(token);
loginVo.setNickName(familyMember.getName());
return loginVo;
}
/**
* 保存或修改客户
* @param member
* @param phone
*/
private void saveOrUpdateFamilyMember(FamilyMember member, String phone) {
// 1.判断取到的手机号与数据库中保存的手机号不一样
if(ObjectUtil.notEqual(phone, member.getPhone())) {
// 修改手机号
member.setPhone(phone);
}
// 2.判断id存在,则修改
if (ObjectUtil.isNotEmpty(member.getId())) {
updateById(member);
return;
}
// 3.id不存在,保存新用户
// 随机组装昵称,词组+手机号后四位
String nickName = DEFAULT_NICKNAME_PREFIX.get((int) (Math.random() * DEFAULT_NICKNAME_PREFIX.size()))
+ StringUtils.substring(member.getPhone(), 7);
member.setName(nickName);
save(member);
}
由于这里用到了构建者设计模式,所以还需要到FamilyMember类当中加上@Builder注解
构建者设计模式:

当我们在一个实体类中添加了@Builder注解后,这个类就支持了构建者模式来使用这个类,我们可以查看生成后的class文件,以下代码是生成后的核心代码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.zzyl.entity;
import com.zzyl.base.BaseEntity;
public class Member extends BaseEntity {
private String phone;
private String name;
private String avatar;
private String openId;
private int gender;
// 省略了get 和 set方法
public static MemberBuilder builder() {
return new MemberBuilder();
}
public Member(final String phone, final String name, final String avatar, final String openId, final int gender) {
this.phone = phone;
this.name = name;
this.avatar = avatar;
this.openId = openId;
this.gender = gender;
}
public Member() {
}
public static class MemberBuilder {
private String phone;
private String name;
private String avatar;
private String openId;
private int gender;
MemberBuilder() {
}
public MemberBuilder phone(final String phone) {
this.phone = phone;
return this;
}
public MemberBuilder name(final String name) {
this.name = name;
return this;
}
public MemberBuilder avatar(final String avatar) {
this.avatar = avatar;
return this;
}
public MemberBuilder openId(final String openId) {
this.openId = openId;
return this;
}
public MemberBuilder gender(final int gender) {
this.gender = gender;
return this;
}
public Member build() {
return new Member(this.phone, this.name, this.avatar, this.openId, this.gender);
}
public String toString() {
return "Member.MemberBuilder(phone=" + this.phone + ", name=" + this.name + ", avatar=" + this.avatar + ", openId=" + this.openId + ", gender=" + this.gender + ")";
}
}
}
测试效果展示:

查看数据库数据发现create_by、update_by字段自动填充(这个1是来自后端的admin用户,微信小程序这里不需要自动填充这两个字段)

修改MyMetaObjectHandler代码如下:
//导包略...
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Autowired
private HttpServletRequest request;
public boolean isExclude() {
String requestURI = request.getRequestURI();
if(requestURI.startsWith("/member")) {
return true;
}
return false;
}
@Override
public void insertFill(MetaObject metaObject) {
if(!isExclude()) {
this.strictInsertFill(metaObject, "createBy", String.class, String.valueOf(getLoginUserId()));
}
this.strictInsertFill(metaObject, "createTime", Date.class, DateUtils.getNowDate());
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
if(!isExclude()) {
this.setFieldValByName("updateBy", String.valueOf(getLoginUserId()), metaObject);
}
}
//略......
}
校验token:
项目中通过ThreadLocal类获取token并储存在线程当中,方便后面的校验token
common模块下utils包-UserThreadLocal:
package com.zzyl.common.utils;
import lombok.extern.slf4j.Slf4j;
/**
* subjectContent.java
* 用户主体对象
*/
@Slf4j
public class UserThreadLocal {
private static final ThreadLocal<Long> LOCAL = new ThreadLocal<>();
private UserThreadLocal() {
}
/**
* 将authUserInfo放到ThreadLocal中
*
* @param authUserInfo {@link Long}
*/
public static void set(Long authUserInfo) {
LOCAL.set(authUserInfo);
}
/**
* 从ThreadLocal中获取authUserInfo
*/
public static Long get() {
return LOCAL.get();
}
/**
* 从当前线程中删除authUserInfo
*/
public static void remove() {
LOCAL.remove();
}
/**
* 从当前线程中获取前端用户id
* @return 用户id
*/
public static Long getUserId() {
return LOCAL.get();
}
}
MemberInterceptor:
package com.zzyl.framework.interceptor;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import com.zzyl.common.exception.base.BaseException;
import com.zzyl.common.utils.StringUtils;
import com.zzyl.common.utils.UserThreadLocal;
import com.zzyl.framework.web.service.TokenService;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class MemberInterceptor implements HandlerInterceptor {
private final TokenService tokenService;
public MemberInterceptor(TokenService tokenService) {
this.tokenService = tokenService;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 如果不是Controller层的请求,直接放行
if (!(handler instanceof HandlerMethod)) {
return true;
}
// 获取header中的token
String token = request.getHeader("authorization");
// 如果token为空,响应401,重新登录
if (StringUtils.isEmpty(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
throw new BaseException("认证失败");
}
// 解析token
Claims claims = tokenService.parseToken(token);
if (ObjectUtil.isEmpty(claims)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
throw new BaseException("认证失败");
}
// 获取token中的数据
Long userId = MapUtil.get(claims, "userId", Long.class);
if (ObjectUtil.isEmpty(userId)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
throw new BaseException("认证失败");
}
// 将用户id放入ThreadLocal中
UserThreadLocal.set(userId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserThreadLocal.remove();
}
}
注册拦截器:注入MemberInterceptor对象并且编写自定义拦截规则:
registry.addInterceptor(memberInterceptor).excludePathPatterns("/member/user/login","/member/roomTypes") .addPathPatterns("/member/**");
微实战:
通过接口文档自主完成剩余接口开发:
护理项目接口:
分页查询护理项目列表:
MemberOrderController:
@Autowired
private INursingProjectService nursingProjectService;
@GetMapping("/page")
@ApiOperation("分页查询护理项目列表")
public TableDataInfo<NursingProject> page(Integer pageNum, Integer pageSize, String name, Integer status) {
return nursingProjectService.pageByNameAndStatus(pageNum, pageSize, name, status);
}
NursingProjectServiceImpl:
/**
* 分页查询护理项目(小程序端)
* @param pageNum 当前页码
* @param pageSize 每页显示记录数
* @param name 护理项目名称
* @param status 状态
* @return 分页数据
*/
@Override
public TableDataInfo<NursingProject> pageByNameAndStatus(Integer pageNum, Integer pageSize, String name, Integer status) {
Page<NursingProject> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<NursingProject> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(name != null, NursingProject::getName, name)
.eq(status != null, NursingProject::getStatus, status);
page = page(page, queryWrapper);
TableDataInfo<NursingProject> tableDataInfo = buildTableData(page);
return tableDataInfo;
}
private static TableDataInfo<NursingProject> buildTableData(Page<NursingProject> page) {
TableDataInfo<NursingProject> tableDataInfo = new TableDataInfo<>();
tableDataInfo.setRows(page.getRecords());
tableDataInfo.setTotal(page.getTotal());
tableDataInfo.setCode(200);
tableDataInfo.setMsg("查询成功");
return tableDataInfo;
}
根据编号查询护理项目信息:
MemberOrderController:
@Autowired
private INursingProjectService nursingProjectService;
@GetMapping("/{id}")
@ApiOperation("获取护理项目详细信息")
public R<NursingProject> getInfo(@ApiParam(value = "护理项目ID", required = true)
@PathVariable Long id) {
return R.ok(nursingProjectService.selectNursingProjectById(id));
}
NursingProjectServiceImpl:
/**
* 查询护理项目
*
* @param id 护理项目主键
* @return 护理项目
*/
@Override
public NursingProject selectNursingProjectById(Long id)
{
return getById(id);
}
参观预约接口:
查询当天取消预约数量:
这个接口是为了防止有人恶意预约并取消,当取消超过三次后当天禁止预约
MemberReservationController:
@Autowired
private IReservationService reservationService;
@GetMapping("/cancelled-count")
@ApiOperation("查询取消预约数量")
public R<Integer> getCancelledReservationCount() {
Long userId = UserThreadLocal.getUserId();
int count = reservationService.getCancelledCount(userId);
return R.ok(count);
}
ReservationServiceImpl:
/**
* 查询取消预约次数
*
* @param userId 用户id
* @return 取消预约次数
*/
@Override
public int getCancelledCount(Long userId) {
// 2024/11/11 00:00:00
LocalDateTime startTime = LocalDate.now().atStartOfDay();
// 2024/10/12 00:00:00
LocalDateTime endTime = startTime.plusDays(1);
LambdaQueryWrapper<Reservation> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(Reservation::getUpdateBy, userId)
.eq(Reservation::getStatus, 2)
.between(Reservation::getUpdateTime, startTime, endTime);
return (int) count(lambdaQueryWrapper);
}
查询每个时段剩余预约次数:
MemberReservationController:
@Autowired
private IReservationService reservationService;
@GetMapping("/countByTime")
@ApiOperation("查询每个时间段剩余预约次数")
public R<List<TimeCountVo>> countReservationsForEachTimeWithinTimeRange(Long time) {
List<TimeCountVo> list = reservationService.countReservationsForTime(time);
return R.ok(list);
}
ReservationServiceImpl:
@Autowired
private ReservationMapper reservationMapper;
/**
* 查询每个时间段剩余预约次数
*
* @return 结果
*/
@Override
public List<TimeCountVo> countReservationsForTime(Long time) {
LocalDateTime localDateTime = LocalDateTimeUtil.of(time);
LocalDateTime startTime = localDateTime.toLocalDate().atStartOfDay();
LocalDateTime endTime = startTime.plusDays(1);
List<TimeCountVo> timeCountVoList = reservationMapper.countReservationsForTime(startTime, endTime);
return timeCountVoList;
}
ReservationMapper.xml:
<select id="countReservationsForTime" resultType="com.zzyl.nursing.vo.TimeCountVo">
SELECT time, 6 - COUNT(*) AS count
FROM reservation
WHERE `time` BETWEEN #{startTime}
AND #{endTime}
and status != 2
GROUP BY `time`
</select>
新增预约:
MemberReservationController:
@Autowired
private IReservationService reservationService;
/**
* 新增预约信息
*/
@PostMapping
@ApiOperation("新增预约信息")
public AjaxResult add(@RequestBody ReservationDto reservationDto) {
return toAjax(reservationService.insertReservation(reservationDto));
}
ReservationServiceImpl:
/**
* 新增预约信息
*
* @param reservationDto 预约信息
* @return 结果
*/
@Override
public int insertReservation(ReservationDto reservationDto) {
Long userId = UserThreadLocal.getUserId();
Reservation reservation = BeanUtil.toBean(reservationDto, Reservation.class);
reservation.setCreateBy(String.valueOf(userId));
reservation.setUpdateBy(String.valueOf(userId));
reservation.setStatus(0);
try {
return save(reservation) ? 1 : 0;
} catch (Exception e) {
throw new BaseException("不能重复预约哦");
}
}
分页查询预约:
MemberReservationController:
@Autowired
private IReservationService reservationService;
/*
*分页查询增加预约人姓名,手机号,状态,类型的查询条件
*/
@GetMapping("/page")
@ApiOperation("分页查询预约")
@ApiImplicitParams({
@ApiImplicitParam(name = "pageNum", value = "页码", required = true, dataType = "int", paramType = "query"),
@ApiImplicitParam(name = "pageSize", value = "每页数量", required = true, dataType = "int", paramType = "query"),
@ApiImplicitParam(name = "status", value = "状态", dataType = "int", paramType = "query"),
})
public R<TableDataInfo<Reservation>> findByPage(@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) Integer status) {
TableDataInfo<Reservation> tableDataInfo = reservationService.selectByPage(pageNum, pageSize, status);
return R.ok(tableDataInfo);
}
ReservationServiceImpl:
/**
* 分页查询预约信息
*
* @param pageNum
* @param pageSize
* @param status
* @return
*/
@Override
public TableDataInfo<Reservation> selectByPage(int pageNum, int pageSize, Integer status) {
Long userId = UserThreadLocal.getUserId();
if (ObjectUtil.isEmpty(userId)) {
throw new BaseException("请先登录");
}
LambdaQueryWrapper<Reservation> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(Reservation::getCreateBy, userId);
lambdaQueryWrapper.eq(ObjectUtil.isNotEmpty(status), Reservation::getStatus, status);
lambdaQueryWrapper.orderByDesc(Reservation::getCreateTime);
Page<Reservation> page = new Page<>(pageNum, pageSize);
page = page(page, lambdaQueryWrapper);
return createTableDataInfo(page);
}
private TableDataInfo<Reservation> createTableDataInfo(Page<Reservation> page) {
TableDataInfo<Reservation> tableDataInfo = new TableDataInfo<>();
tableDataInfo.setRows(page.getRecords());
tableDataInfo.setTotal(page.getTotal());
tableDataInfo.setMsg("请求成功");
tableDataInfo.setCode(200);
return tableDataInfo;
}
取消预约:
MemberReservationController:
@Autowired
private IReservationService reservationService;
/**
* 取消预约
*/
@PutMapping("/{id}/cancel")
@ApiOperation("取消预约")
public AjaxResult cancel(@PathVariable Long id) {
return toAjax(reservationService.cancelReservation(id));
}
ReservationServiceImpl:
/**
* 取消预约
*
* @param id
* @return
*/
@Override
public int cancelReservation(Long id) {
Reservation reservation = getById(id);
if (ObjectUtil.isEmpty(reservation)) {
throw new BaseException("预约不存在");
}
reservation.setStatus(2);
reservation.setUpdateBy(String.valueOf(UserThreadLocal.getUserId()));
return updateById(reservation) ? 1 : 0;
}
2039

被折叠的 条评论
为什么被折叠?



