手把手教你云相册项目简易开发day5 API服务搭建和权限框架

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 手把手教你云相册项目简易开发day5 API服务搭建和权限框架

文章只提供了部分代码,每一天对应着分支工具类等完整代码都可以去github上获取,地址:https://github.com/kkoneone11/cloud-photo/

用户登录服务功能

流程图

数据库

开发过程

1.基础配置

1.1在application配置类里改一下名字,端口9008

1.2创建启动类,启动类头上配置

@EnableDiscoveryClient
@SpringBootApplication
@MapperScan(basePackages = {"com.cloud.photo.users.mapper"})

1.3pom文件导入

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>cloud-photo-common</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

1.4通过代码生成器操作user表生成代码

2.接口开发

2.1接口1:根据手机号获取用户信息/user/getUserInfo Get

2.1.1参数分析:根据手机phone可以唯一确定一个用户

2.1.2通过phone查询用户信息

/**
     * 获得用户信息,根据用户手机获取用户信息
     * @param phone
     * @return resultBody
     */
    @GetMapping("/getUserInfo")
    public ResultBody getUserInfo(@RequestParam(value = "phone") String phone){
        log.info("getUserInfo-phone"+ phone + ",start!");
        User user = userService.getOne(new QueryWrapper<User>().eq("phone",phone));
        log.info("getUserInfo()-phone=" + phone + ",user=" + user);
        ResultBody resultBody = (user == null ) ? ResultBody.error(CommonEnum.USER_IS_NULL) : ResultBody.success(user);
        log.info("getUserInfo()-phone=" + phone + ",resultBody=" + resultBody);
        return resultBody;
    }
2.2接口2:用户执行登录/user/login POST

2.2.1参数分析:phone,通过phone参数查询用户

2.2.2通过phone查询是否存在用户,如果

  • 不存在则创建一个新用户并设置相关参数
  • 存在则更新登录时间然后更新登录的次数

最后将用户更新

注意修改user实体里的LocalDateTime为Date否则会报错

/**
     * 登录
     * @param userBo
     * @return
     */
    @PostMapping("/login")
    public ResultBody login(@RequestBody UserBo userBo){
        String phone = userBo.getPhone();
        //查询用户
        User user = userService.getOne(new QueryWrapper<User>().eq("phone", phone), false);
        //1.用户不存在则生成
        if(user == null){
            //组装
            user = new User();
            //复制
            BeanUtils.copyProperties(userBo, user);
            //自定义一个用户ID
            user.setUserId(RandomUtil.randomString(9));
            user.setCreateTime(DateUtil.date());
            user.setUpdateTime(DateUtil.date());
            user.setLoginCount(0);
            //普通用户
            user.setRole("user");
        }else{
        //2.存在则更新状态即可
            user.setUpdateTime(DateUtil.date());
            user.setLoginCount(user.getLoginCount()+1);
        }
        //入库
        boolean saveOrUpdate = userService.saveOrUpdate(user);
        return saveOrUpdate ? ResultBody.success() : ResultBody.error(CommonEnum.LOGIN_FAIL);
    }
2.3接口3:根据手机号检查用户是否存在/user/checkPhone Get

2.3.1参数分析:phone

2.3.2

/**
     * 查询用户是否存在
     *
     * @param phone 手机号
     * @return 查询结果
     */
    @GetMapping("/checkPhone")
    public ResultBody checkPhone(@RequestParam(value = "phone") String phone) {
        User userEntity = userService.getOne(new QueryWrapper<User>().eq("phone", phone));
        return userEntity == null ? ResultBody.error(CommonEnum.USER_IS_NULL) : ResultBody.success();
    }
2.4接口4:管理员检查账号密码/user/checkAdmin Get

2.4.1参数分析:phone,password。因为是管理员的话需要账号密码去验证

2.4.2拿到phone和password之后去执行一条查询语句看是否存在该用户

/**
     * 检查管理员登录
     * @param userName
     * @param password
     * @return
     */
    @GetMapping("/checkAdmin")
    public ResultBody checkAdmin(@RequestParam(value = "userName")String userName,
                                 @RequestParam(value = "password"),String password){
        User user = userService.getOne(new QueryWrapper<User>()
                .eq("user_name", userName)
                .eq("password", password)
                .eq("role", CommonConstant.ADMIN));
        return user == null ? ResultBody.error(CommonEnum.USERNAME_PASSWORD_ERROR) : ResultBody.success();
    }

API接口开发

在api项目中实现

用户登录后前端会给后端一个satoken的认证凭证,

开发过程

1.基础配置

1.1因为需要用到saToken进行鉴权所以先在pom文件导入

<dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>cloud-photo-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--        sa-token 权限框架-->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-boot-starter</artifactId>
            <version>1.34.0</version>
        </dependency>
        <!--        sa-token 开启注解-->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-aop</artifactId>
            <version>1.34.0</version>
        </dependency>
        <!--        sa-token 集成redis-->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-dao-redis-jackson</artifactId>
            <version>1.34.0</version>
        </dependency>
    </dependencies>

1.2在config包加入一个GlobalExceptionHandler全局捕获类,在utils包导入一个MyUtil类最后在nacosdiscovery包导入一个NacosDiscoveryConfiguration类

1.3配置一个启动类,注解备注

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.cloud.photo.common.fegin"})

1.4在Controller包和service包下分别创建以下

2.接口开发

2.1接口1:/api/login POST

2.1.1先在LoginController创建一个login()放,然后具体业务在service中实现。

先通过StpUtil工具类的isLogin()通过判断token是否存在且对得上,如果是则直接放行。否则则要看userBo是否为空,为空这返回登录信息为空的错误。然后判断是否是

  • 管理员登录,检查是否有账号密码,检查账号密码是否正确,正确则调用doLogin方法放行
  • 普通用户登录则检查调用checkPhone()看用户是否存在即可,不存在则表明第一次登录需要填入相关信息,检查是否有填入相关信息。补全了则直接doLogin()放行。
2.2接口2:/api/logout POST

2.2.1:先用StpUtil的isLogin()判断是否已经登录,已经登录了的话再继续找到该账户对应的loginId然后找到session,清空里面的session之后再执行StpUtil的logout()方法

/**
     * 退出
     * @return
     */
    @Override
    public ResultBody logout() {
       if(StpUtil.isLogin()){
           String loginId = StpUtil.getLoginIdAsString();
           SaSession session = StpUtil.getSessionByLoginId(loginId);
           //清空session
           session.logout();
           StpUtil.logout();
       }
       return ResultBody.success();
    }
2.3接口3:/api/getUserInfo POST

2.3.1先看用户是否有登录,没有登录直接返回错误消息,如果已经登录了则先看session里是否有消息,

  • session有消息先拿出loginId然后再拿到session,session不为空则继续拿到里面的userInfo,userInfo不为空就返回信息。
  • session没有消息则调用users的getUserInfo()方法,然后再将userInfo存入到session里,最后再返回userInfo
2.4接口4:doLogin()

2.4.1参数分析:userBo,phone

2.4.2传入userBo调用users子项目的login方法检验参数是否成功,如果成功则再传入phone执行StpUtil的login()方法登录并产生token,把token返回给前端

2.5接口5:/api/getTransList POST

2.5.1在TransController下写一个getTransList()方法,先通过MyUtils的getUserInfo()拿到用户信息

然后拿到userId,再组装一个匹配前缀给stringRedisTemplate的keys方法拿到所有keys,如果keys不为空则通过stream流遍历所有key拿出里面的value然后再组装回去。最后通过sorted方法按上传时间降序排序即可。

/**
 * 传输管理相关接口
 * @author kkoneone11
 */
@RestController
@RequestMapping("/api")
public class TransController {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @PostMapping("/getTransList")
    public ResultBody getTransList(){
        UserBo userInfo = MyUtils.getUserInfo();
        //用户信息为空则返回错误
        if(userInfo == null){
            return ResultBody.error(CommonEnum.USER_IS_NULL);
        }
        //不为空则拿到userId
        String userId = userInfo.getUserId();
        //组装匹配前缀
        String keyPatten = userId + ":" + "*";
        //拿出keys
        Set<String> keys = stringRedisTemplate.keys(keyPatten);
        //keys不为空则遍历
        if(keys!=null && CollUtil.isNotEmpty(keys)){
            List<FileUploadBo> uploadBos = keys.stream().map(key -> {
                String value = stringRedisTemplate.opsForValue().get(key);
                return JSONUtil.toBean(value,FileUploadBo.class);
            }).collect(Collectors.toList());
            //按上传时间降序
            uploadBos = uploadBos.stream().sorted(Comparator.comparing(FileUploadBo::getUploadTime, Comparator.reverseOrder())).collect(Collectors.toList());
            return ResultBody.success(uploadBos);
        }else{
            return ResultBody.error(CommonEnum.TRANS_IS_NULL);
        }
    }
}
2.6接口6:/api/getAlbumCategorize Get

2.6.1在AlbumController写一个getAlbumCategorize()方法。同样先分局MyUtils的getUserInfo()

从缓存中拿到用户信息,用户信息为空则返回错误。不为空则拿出userId然后构造一个albumPageBo设置相关信息,然后分别设置人物、地点、事物的分类信息然后分别最后getUserAlbumList()方法传入albumPageBo查询并放入jsonObject中最后返回即可

2.6.2在service方法里写一个getUserAlbumList方法调用userFilelist

@Override
    public ResultBody getUserAlbumList(AlbumPageBo pageBo) {
        return cloud2TransService.userFilelist(pageBo);
    }
/**
     * 获取用户照片分类信息
     * @return 带有用户地点、人物、事物 三种标签的缩略图 (三张 按照上传时间排序)
     */
//    @SaCheckLogin
    @GetMapping("/getAlbumCategorize")
    public ResultBody getAlbumCategorize(){
        //缓存中拿到用户信息
        UserBo userInfo = MyUtils.getUserInfo();
        if(userInfo == null){
            return ResultBody.error(CommonEnum.USER_IS_NULL);
        }
        String userId = userInfo.getUserId();
        //组装AlbumPageBo
        AlbumPageBo albumPageBo = new AlbumPageBo();
        albumPageBo.setUserId(userId);
        albumPageBo.setCurrent(1);
        albumPageBo.setPageSize(3);
        //再组装一个jsonObject最后面返回
        JSONObject jsonObject = new JSONObject();
        //人物
        albumPageBo.setCategory(CommonConstant.IMAGE_CATEGORY_1);
        jsonObject.put("person",albumService.getUserAlbumList(albumPageBo));
        //地点
        albumPageBo.setCategory(CommonConstant.IMAGE_CATEGORY_2);
        jsonObject.put("location",albumService.getUserAlbumList(albumPageBo));
        //事物
        albumPageBo.setCategory(CommonConstant.IMAGE_CATEGORY_3);
        jsonObject.put("thing",albumService.getUserAlbumList(albumPageBo));
        return ResultBody.success(jsonObject);
    }
2.7接口7:/api/getUserAlbumList POST

2.7.1参数分析 AlbumPageBo

2.7.2从缓存中拿到用户信息,用户信息为空则返回错误。不为空则拿出userId然后将albumPageBo设置一个userId最后getUserAlbumList()方法传入albumPageBo

/**
     * 获取用户所有上传的图片(分页)
     * @return 返回带有文件信息的实体列表(包括大小图、上传时间等)
     */
//    @SaCheckLogin
    @PostMapping("/getUserAlbumList")
    public ResultBody getUserAlbumAll(@RequestBody AlbumPageBo pageBo){
        //从缓存拿到用户信息
        UserBo userInfo = MyUtils.getUserInfo();
        if (userInfo == null){
            return ResultBody.error(CommonEnum.USER_IS_NULL);
        }
        //用户ID
        String userId = userInfo.getUserId();
        pageBo.setUserId(userId);
        // 业务实现
        return albumService.getUserAlbumList(pageBo);
    }
2.8接口8:/api/getUserAlbumDetail POST

2.8.1从缓存中拿到用户信息,用户信息为空则返回错误。不为空则拿出userId然后将analyzeBo

设置一个userId然后在albumService里实现getUserAlbumDetail()的逻辑,直接调用cloud2ImageService的getMediaInfo()方法即可。

2.9接口9:/api/previewImage Get

2.9.1参数分析:fileId, iconCode这两样可以唯一确定想要的缩略图

2.9.2先组装一个fileResizeIconBo,往里塞入fileId, iconCode两个参数,然后调用albumService的previewImage()方法,里面的业务逻辑是调用cloud2ImageService的getIconUrl()方法获得缩略图的下载地址,然后判断下载地址是否为空,不为空则转发这个url,否则返回null即可。

/**
     * 获取用户单个图片格式分析信息
     * @return 返回带有文件信息的实体列表(包括大小图、上传时间等)
     */
//    @SaCheckLogin
    @GetMapping("/previewImage")
    public ResultBody previewImage(@RequestParam String fileId, @RequestParam String iconCode, HttpServletResponse response){
        FileResizeIconBo fileResizeIconBo =new FileResizeIconBo();
        fileResizeIconBo.setFileId(fileId);
        fileResizeIconBo.setIconCode(iconCode);
        // 业务实现
        ResultBody resultBody = albumService.previewImage(fileResizeIconBo);
        String iconUrl = null;
        if(resultBody.getData()!=null && StringUtils.isNotBlank(resultBody.getData().toString())){
            iconUrl = resultBody.getData().toString();
            response.setStatus(302);
            try {
                response.sendRedirect(iconUrl);
            } catch (IOException e) {
                log.error("response.sendRedirect error!" , e);
            }
        }
        return null;
    }
2.10接口10:/api/getPutUploadUrl POST

2.10.1参数分析:fileUploadBo,因为需要获得上传地址,所以bo里还有FileName、FileMd5、FileSize

2.10.2从缓存中拿到用户信息,用户信息为空则返回错误。不为空则拿出userId然后fileUploadBo

设置一个userId,最后在service写一个getPutUploadUrl()方法,调用cloud2TransService

的getPutUploadUrl()方法并传入FileId、FileName、FileMd5、FileSize参数,看返回值里的是否成功,成功则看返回的数据里有没有storageObjectId,有则是秒传,往fileUploadBo里塞入即可。否则往fileUploadBo里塞入containerId、objectId、url、base64Md5

@Override
    public ResultBody getPutUploadUrl(FileUploadBo fileUploadBo) {
        ResultBody resultBody = cloud2TransService.getPutUploadUrl(fileUploadBo.getUserId(),
                fileUploadBo.getFileName(),
                fileUploadBo.getFileMd5(),
                fileUploadBo.getFileSize());
        //存进缓存的Key
        String key = fileUploadBo.getUserId() + ":" +  fileUploadBo.getFileMd5();
        if (resultBody.getCode().equals(CommonEnum.SUCCESS.getResultCode())) {
            //成功
            JSONObject obj = JSONUtil.parseObj(resultBody.getData());
            if (obj.containsKey("storageObjectId")) {
                //是秒传
                fileUploadBo.setStorageObjectId(obj.getStr("storageObjectId"));
            } else {
                //不是秒传
                fileUploadBo.setContainerId(obj.getStr("containerId"));
                fileUploadBo.setObjectId(obj.getStr("objectId"));
                fileUploadBo.setUploadUrl(obj.getStr("url"));
                fileUploadBo.setBase64Md5(obj.getStr("base64Md5"));
            }
            //成功了状态是传输中
            fileUploadBo.setStatus(CommonConstant.FILE_UPLOAD_ING);
            fileUploadBo.setUploadTime(DateUtil.now());
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(fileUploadBo), 1, TimeUnit.DAYS);
        }else {
            //失败了状态是传输失败
            fileUploadBo.setStatus(CommonConstant.FILE_UPLOAD_FAIL);
            fileUploadBo.setUploadTime(DateUtil.now());
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(fileUploadBo), 1, TimeUnit.DAYS);
        }
        return ResultBody.success(fileUploadBo);
    }
2.11接口11:/api/commitUpload  POST
@Override
    public ResultBody commitUpload(FileUploadBo bo) {
        //存进缓存的Key
        String key = bo.getUserId() + ":" +  bo.getFileMd5();
        ResultBody commitResultBody = cloud2TransService.commit(bo);
        if (commitResultBody.getCode().equals(CommonEnum.SUCCESS.getResultCode())){
            //成功提交 - 状态更新为传输成功
            bo.setStatus(CommonConstant.FILE_UPLOAD_SUCCESS);
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(bo), 1, TimeUnit.DAYS);
        }else {
            //提及失败 - 状态更新为传输失败
            bo.setStatus(CommonConstant.FILE_UPLOAD_FAIL);
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(bo), 1, TimeUnit.DAYS);
        }
        return commitResultBody;
    }
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
3天前
|
开发框架 监控 .NET
Visual Basic的Web服务和REST API开发指南
【4月更文挑战第27天】本文探讨了使用Visual Basic(VB.NET)构建Web服务和RESTful API的方法。首先介绍了Web服务的基础和REST API的概念,然后阐述了.NET Framework与.NET Core/.NET 5+对VB.NET的支持,以及ASP.NET Core在Web开发中的作用。接着,详细讲解了创建RESTful API的步骤,包括控制器与路由设置、模型绑定与验证,以及返回响应。此外,还讨论了安全措施、测试方法、部署选项和监控策略。最后强调,VB.NET开发者可以通过ASP.NET Core涉足现代Web服务开发,拓宽技术领域。
|
3天前
|
缓存 监控 API
构建高效可扩展的RESTful API:后端开发的实践指南
【4月更文挑战第26天】在现代Web开发中,构建一个高效、可扩展且易于维护的RESTful API是后端工程师必须面对的挑战。本文将深入探讨如何利用最佳实践和流行技术,设计出符合REST架构原则的服务端接口。我们将重点讨论API版本控制、资源路由、数据库优化、缓存策略以及安全性考虑等方面,旨在为开发者提供一套综合性解决方案,帮助其提升API的性能与可靠性。
|
3天前
|
存储 缓存 安全
API在Visual Basic中的应用:连接外部服务与扩展功能
【4月更文挑战第27天】本文探讨了在Visual Basic中使用API连接外部服务和扩展功能的方法,涵盖了API的基本概念、种类及如何使用本地和Web API。通过DllImport调用本地API,利用HttpClient和WebClient与Web API交互,同时强调了第三方API的使用和SOA架构中的API角色。安全性、性能优化和错误处理是实践中的关键点。案例研究和最佳实践有助于开发者更有效地利用API,提升Visual Basic应用程序的功能和灵活性。随着API技术的发展,Visual Basic将持续支持开发者创造更强大的应用。
|
23小时前
|
XML JSON API
【PHP开发专栏】PHP RESTful API设计与开发
【4月更文挑战第29天】本文探讨了在Web开发中流行的前后端分离模式,重点介绍了RESTful API的设计与实现。REST是一种基于HTTP协议的架构风格,核心概念包括资源、表述和状态转换。RESTful API设计遵循无状态、统一接口等原则,使用GET、POST、PUT、DELETE等HTTP方法执行操作,并通过状态码和JSON/XML传输数据。在PHP中实现RESTful API,可通过定义路由、创建控制器、处理请求和响应,同时注意安全性措施,如使用HTTPS。文中还提供了一个用户管理API的实战示例,以帮助读者更好地理解和应用RESTful API。
|
1天前
|
弹性计算 运维 Serverless
Serverless 应用引擎产品使用之在阿里函数计算中,使用阿里云API或SDK从函数计算调用ECS实例的服务如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
21 4
|
1天前
|
缓存 运维 Serverless
Serverless 应用引擎产品使用之阿里函数计算中。将本地电脑上的项目文件部署到阿里云函数计算(FC)上并实现对外提供API和WebUI如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
15 1
|
1天前
|
机器学习/深度学习 人工智能 API
人工智能平台PAI产品使用合集之机器学习PAI-EAS部署好后,服务的公网API和URL怎么配置
阿里云人工智能平台PAI是一个功能强大、易于使用的AI开发平台,旨在降低AI开发门槛,加速创新,助力企业和开发者高效构建、部署和管理人工智能应用。其中包含了一系列相互协同的产品与服务,共同构成一个完整的人工智能开发与应用生态系统。以下是对PAI产品使用合集的概述,涵盖数据处理、模型开发、训练加速、模型部署及管理等多个环节。
|
6天前
|
Java 测试技术 API
Python的api自动测试选择合适的测试框架
【4月更文挑战第18天】在Python API自动测试中,选择合适的框架至关重要。常见的测试工具有unittest(集成度高,适合基础测试)、pytest(功能强大,支持插件扩展和高级功能)、requests-mock(用于HTTP请求模拟和断言)、rest-assured(针对RESTful API的简洁测试)以及allure-pytest(生成美观的测试报告)。选择时要考虑项目需求、团队熟悉度和社区支持。确保遵循良好测试实践,编写清晰、全面的测试用例。
10 2
|
6天前
|
人工智能 机器人 API
【Python+微信】【企业微信开发入坑指北】3. 如何利用企业微信API给微信群推送消息
【Python+微信】【企业微信开发入坑指北】3. 如何利用企业微信API给微信群推送消息
10 0
|
6天前
|
缓存 人工智能 API
【Python+微信】【企业微信开发入坑指北】2. 如何利用企业微信API主动给用户发应用消息
【Python+微信】【企业微信开发入坑指北】2. 如何利用企业微信API主动给用户发应用消息
9 0