目录
创建小程序底部Tab导航
开通腾讯云对象存储服务
一、静态资源要放在网上
二、为什么不选择阿里云或者华为云的对象存储服务?
二、开通腾讯云对象存储服务
三、存储静态资源
设计首页的英雄区和栏目导航
设计人脸签到页面
实现签到自拍功能
缓存系统常量数据
一、Emos系统的常量数据
二、读取常量数据
封装检测当天是否可以签到(持久层)
一、为什么要检测是否可以签到?
二、封装持久层代码
封装检测当天是否可以签到(业务层)
封装检测当天是否可以签到(Web层)
实现Shiro认证功能
一、认证与授权执行流程
二、查询用户信息
二、实现认证方法
实现Shiro 授权功能
一、实现授权方法
二、添加权限验证注解
创建小程序底部Tab导航
……
开通腾讯云对象存储服务
一、静态资源要放在网上
微信小程序打包发布有体积限制,不能超过2M,分包加载最多不能超过8M。正因为小程序有体积限制,所以我们不能往小程序项目中放置太多的静态资源,比如体积超大的图片。我们应该尽可能的把静态资源放在网上,然后在小程序页面上引用这些静态资源。
网上有很多图床可以使用,这里我选择腾讯云的对象存储服务当做图床。腾讯云的对象存储服务可以免费试用6个月,并且为个人提供50GB的免费空间。即便试用结束,腾讯云的对象存储价格也不贵。10G资源包,1个月的价格仅仅0.85元。对于普通的开发者来说,10G的存储空间足够了。
二、为什么不选择阿里云或者华为云的对象存储服务?
华为云的服务器大部分是ARM架构的,对很多Linux程序包的兼容性不好,导致我们无法正 常部署和运行程序,所以你在选择华为云的时候一定要慎重,先了解清楚,别盲目上车。
阿里云的问题是超售严重,假设1台硬件服务器能运行200个虚拟云主机,但是阿里偏偏一 台主机跑2000个虚拟云主机,最大程度榨取硬件主机的商业价值。当硬件服务器中运行的 云主机过多,最先体现出来的是硬盘IO能力的下降。因为硬盘IO本来就比内存IO慢很多, 加之云主机共享使用硬盘IO,这就导致某个云主机分得的硬盘IO速度非常有限。
比方说, 你在阿里云主机上面安装MySQL数据库,执行一个非常简单的SQL语句,都要登上7~10秒 钟才能获得结果,这种超慢的读写速度真让人抓狂。也许有人想说,干嘛在云主机上面安 装MySQL,直接购买数据库服务不就行了么?还真不行,运营商提供的数据库服务,最多 带了主从同步这种数据弱一致的集群方案,你想要数据强一致性的数据库集群,只能在云 主机里面自己安装。因此不是购买运营商的数据库服务就一了百了的。
腾讯云的超售不严重,每个云主机的IO性能跟本地主机差不多。我在腾讯云主机上面安装MySQL数据库,执行SQL语句,几十毫秒就得到了结果,几乎跟本地执行SQL是同样的速度,所以我们单位 的所有产品都是部署在腾讯云上面。
二、开通腾讯云对象存储服务
用浏览器访问腾讯云对象存储服务的主页,根据提示开通服务即可,非常的简单。
三、存储静态资源
首先我们要创建一个存储桶,然后才能上传静态文件。
有了存储桶之后,我们就可以上传各种静态文件了,比如说我们要做轮播焦点图,那么就可以把这些图片预先上传到腾讯云上面。
设计首页的英雄区和栏目导航
……
设计人脸签到页面
……
实现签到自拍功能
……
缓存系统常量数据
编写SpringBoot初始化方法(SpringBoot项目启动之后,自动执行的方法)。和Java基础里面的静态语句块非常的类似。没错,静态语句块也能实现项目的初始化,但是,静态语句块没法接受SpringBoot项目注入的各种关系,还有一些值。所以我们选择SpringBoot的初始化方法,可以正常接收到 注入的各种关系,引用,值注入。
@PostConstruct
init()
一、Emos系统的常量数据
在 sys_config 数据表中保存了Emos系统的常量配置信息,其中就包括了考勤部分的常量信息。例如每天上班考勤从几点开始,截止到几点。下班考勤从几点开始,几点结束。
因为这些常量信息跟考勤模块息息相关,所以我们要编写Java代码,在SpringBoot项目启动的时候,就去数据库读取这些常量信息,然后缓存成Java对象,全局都可以使用。
二、读取常量数据
在 SysConfigDao.xml 文件中声明查询语句
<select id="selectAllParam" resultType="com.example.emos.wx.db.pojo.SysConfig">
SELECT param_key, param_value FROM sys_config WHERE status = 1;
</select>
在 SysConfigDao.java 中声明抽象方法
@Mapper
public interface SysConfigDao {
public List<SysConfig> selectAllParam();
}
在 com.example.emos.wx.config 中创建 SystemConstants.java 常量封装类
@Data
@Component
public class SystemConstants {
public String attendanceStartTime;
public String attendanceTime;
public String attendanceEndTime;
public String closingStartTime;
public String closingTime;
public String closingEndTime;
}
在 EmosWxApiApplication.java 文件中创建 init() 方法,读取常量数据并缓存
@SpringBootApplication
@ServletComponentScan
@Slf4j
public class EmosWxApiApplication {
@Autowired
private SysConfigDao sysConfigDao;
@Autowired
private SystemConstants constants;
public static void main(String[] args) {
SpringApplication.run(EmosWxApiApplication.class, args);
}
@PostConstruct
public void init() {
List<SysConfig> list = sysConfigDao.selectAllParam();
list.forEach(one -> {
String key = one.getParamKey();
key = StrUtil.toCamelCase(key);
String value = one.getParamValue();
try {
Field field = constants.getClass().getDeclaredField(key);
field.set(constants, value);
} catch (Exception e) {
log.error("执行异常", e);
}
});
}
}
封装检测当天是否可以签到(持久层)
一、为什么要检测是否可以签到?
上节课我们通过分装Emos系统常量信息,从而得知考勤是分为起止时间的。在考勤开始之前,用户是不能考勤签到的。同理,在当天考勤结束之后,用户也是不能考勤签到的。甚至节假日也不能考勤,只有正常的工作日才能考勤签到。
怎么判断当天是工作日还是节假日?
在数据库中有 tb_workday 和 tb_holidays 两张数据表,记录着哪天是工作日,哪天是休息日。
Emos系统默认周一至周五为工作日,周六周日为休息日。但是这两张表不是把所有的工作日和休息日都记录下来,只是记录比特殊的工作日或者休息日。比如说今年的中秋节赶上了礼拜四,于是就把周五和周六设置成休息日,跟中秋节连成三连休,然后周日正常上班。这种特殊情况我们就要记录下来。在 tb_workday 记录周日是工作日,在 tb_holidays 表中记录周五那天是休息日。这样Emos系统在中秋三连休期间不会执行考勤签到。
二、封装持久层代码
查询特殊休息日
在 TbHolidaysDao.xml 文件中,添加查询语句
<select id="searchTodayIsHolidays" resultType="Integer">
SELECT id FROM tb_holidays WHERE date=CURRENT_DATE LIMIT 1;
</select>
在 TbHolidaysDao.java 文件中,添加抽象方法
@Mapper
public interface TbHolidaysDao {
public Integer searchTodayIsHolidays();
}
查询特殊工作日
在 TbWorkdayDao.xml 文件中,添加查询语句
<select id="searchTodayIsWorkday" resultType="Integer">
SELECT id FROM tb_workday WHERE date=CURRENT_DATE LIMIT 1;
</select>
在 TbWorkdayDao.java 文件中,添加抽象方法
@Mapper
public interface TbWorkdayDao {
public Integer searchTodayIsWorkday();
}
查询当天是否已经签到
tb_checkin 表结构如下,Emos不仅要记录考勤的人员、时间,还要记录考勤人的地理坐标,然后根据疫情实时信息,判定用户所处的地区是新冠疫情的高危地区还是低风险地区。这部分功能,后续的章节我们再实现。
在 TbCheckinDao.xml 文件中,添加查询语句
<select id="haveCheckin" parameterType="HashMap" resultType="Integer">
SELECT id
FROM tb_checkin
WHERE user_id = #{userId} AND date = CURRENT_DATE
AND create_time BETWEEN #{start} AND #{end}
LIMIT 1;
</select>
在 TbCheckinDao.java 文件中,添加抽象方法
@Mapper
public interface TbCheckinDao {
public Integer haveCheckin(HashMap param);
}
封装检测当天是否可以签到(业务层)
service类将来有发送邮件的功能,邮件发送比较耗时,所以想设计成异步的。线程的异步执行就需要把service类定义为多例的@Scope("prototype")。不可以是单例的,没办法走线程的异步执行。
DateUtil是HuTool库里自带的工具类,库里的date()可以创建一个当前的日期对象。这个日期对象类型不是Date类型,是DateTime类型。DateTime是HuTool自定义的一个Java类,是Date的子类,但是增添了更多的实用方法。
在 com.example.emos.wx.service 包中,创建 CheckinService 接口
public interface CheckinService {
public String validCanCheckIn(int userId, String date);
}
在 com.example.emos.wx.service.impl 包中,创建 CheckinServiceImpl 实现类
@Service
@Scope("prototype")
@Slf4j
public class CheckinServiceImpl implements CheckinService {
@Autowired
private SystemConstants systemConstants;
@Autowired
private TbHolidaysDao holidaysDao;
@Autowired
private TbWorkdayDao workdayDao;
@Autowired
private TbCheckinDao checkinDao;
@Override
public String validCanCheckIn(int userId, String date) {
boolean bool_1 = holidaysDao.searchTodayIsHolidays() != null ? true : false;
boolean bool_2 = workdayDao.searchTodayIsWorkday() != null ? true : false;
String type = "工作日";
if (DateUtil.date().isWeekend()) {
type = "节假日";
}
if (bool_1) {
type = "节假日";
} else if (bool_2) {
type = "工作日";
}
if (type.equals("节假日")) {
return "节假日不需要考勤";
} else {
DateTime now = DateUtil.date();
String start = DateUtil.today() + " " + systemConstants.attendanceStartTime;
String end = DateUtil.today() + " " + systemConstants.attendanceEndTime;
DateTime attendanceStart = DateUtil.parse(start);
DateTime attendanceEnd = DateUtil.parse(end);
if (now.isBefore(attendanceStart)) {
return "没有到上班考勤开始时间";
} else if (now.isAfter(attendanceEnd)) {
return "超过了上班考勤结束时间";
} else {
HashMap map = new HashMap();
map.put("userId", userId);
map.put("date", date);
map.put("start", start);
map.put("end", end);
boolean bool = checkinDao.haveCheckin(map) != null ? true : false;
return bool ? "今日已经考勤,不用重复考勤" : "可以考勤";
}
}
}
}
封装检测当天是否可以签到(Web层)
在 com.example.emos.wx.controller 包中,创建 CheckinController 类
@RequestMapping("/checkin")
@RestController
@Api("签到模块Web接口")
@Slf4j
public class CheckinController {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private CheckinService checkinService;
@GetMapping("/validCanCheckIn")
@ApiOperation("查看用户今天是否可以签到")
public R validCanCheckIn(@RequestHeader("token") String token) {
int userId = jwtUtil.getUserId(token);
String result = checkinService.validCanCheckIn(userId, DateUtil.today());
return R.ok(result);
}
}
实现Shiro认证功能
一、认证与授权执行流程
之前我们在配置Shiro+JWT的时候,Shiro的认证与授权的实现功能并没有完成。
现在Controller中的 validCanCheckIn() 方法,并不是Shiro放行请求的Web方法。所以发送给validCanCheckIn() 方法的请求一定会被Shiro拦截下来,先由 OAuth2Filter 检查请求头的Token是否合法。如果没问题,接下来就要由 OAuth2Realm 中的 doGetAuthenticationInfo() 方法来颁发认证对象。请求被赋予了认证对象,那么请求才会被发送到Web方法来执行。
二、查询用户信息
因为在认证方法里面要返回认证对象,认证对象创建的时候要传入用户信息和令牌,传入 Realm 类的名字,所以我们这里就要查询用户信息,然后判断用现在是在职还是离职状态。如果是在职状态,那就可以创建认证对象,反之就抛出异常。
编辑 TbUserDao.xml 文件,添加查询方法
<select id="searchById" parameterType="int" resultType="com.example.emos.wx.db.pojo.TbUser">
SELECT
id, open_id, nickname, photo, name, sex, tel, role, root, dept_id, status, create_time
FROM tb_user WHERE id=#{userId} AND status = 1
</select>
编辑 TbUserDao.java 接口,添加抽象方法
public TbUser searchById(int userId);
编辑 UserService.java 接口,添加抽象方法
public TbUser searchById(int userId);
编辑 UserServiceImpl.java 类,实现抽象方法
@Override
public TbUser searchById(int userId) {
TbUser user = userDao.searchById(userId);
return user;
}
二、实现认证方法
编辑 OAuth2Realm.java 文件,修改 doGetAuthenticationInfo() 方法
/**
* 认证(登录时调用)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String accessToken = (String) token.getPrincipal();
int userId = jwtUtil.getUserId(accessToken);
//查询用户信息
TbUser user = userService.searchById(userId);
if(user==null){
throw new LockedAccountException("账号已被锁定,请联系管理员");
}
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(user, accessToken, getName());
return info;
}
实现Shiro 授权功能
认证功能和JWT绑定在一起。只要OAuth2Filter过滤器认定客户端提交的令牌没有问题,就可以看作用户成功登录。Shiro颁发认证对象,HTTP请求才可以继续往下传递。授权和RBAC权限模型相关。
认证对象封装了用户信息,所以授权方法中可以得到用户信息。
一、实现授权方法
Shiro每次验证权限之前,都要执行授权方法,把用户具有的权限封装成权限对象,然后放行请求。接下来Web方法的 @RequiresPermissions 注解,会从权限对象中提取权限数据,跟要求的权限作比较。如果用户具有该Web方法要求的权限,那么Web方法就会正常执行。反之则返回异常消息。
修改 OAuth2Realm.java 中的 doGetAuthorizationInfo() 授权方法。
/**
* 授权(验证权限时调用)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection collection) {
TbUser user = (TbUser) collection.getPrimaryPrincipal();
int userId = user.getId();
//用户权限列表
Set<String> permsSet = userService.searchUserPermissions(userId);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
return info;
}
二、添加权限验证注解
我们创建Web方法的时候,如果希望只有满足相关权限的用户才能调用这个Web方法,我们只需要给Web方法添加上 @RequiresPermissions 注解即可。
@PostMapping("/addUser")
@ApiOperation("添加用户")
@RequiresPermissions(value = {"ROOT", "USER:ADD"}, logical = Logical.OR)
public R addUser() {
return R.ok("用户添加成功");
}