PicMemories-开发文档

本文最后更新于:8 个月前

前言

  • 今天花了两小时时间,快速浏览了小程序官方文档,凭借之前对Vue的使用,小程序开发上手也不难
  • 就决定开发一个壁纸分享小程序了,8月中旬交付

布局

  • 首页:展示热门壁纸、推荐壁纸和最新壁纸,可以根据用户的兴趣和喜好个性化推荐。还可以提供搜索入口和分类导航等功能。
  • 壁纸详情页:展示壁纸的详细信息,包括壁纸的名称、作者、标签,以及用户的点赞、收藏和评论等相关操作。
  • 分类浏览页:根据不同的壁纸分类,展示相应的壁纸列表,例如风景、动物、抽象等分类,供用户浏览和选择。
  • 用户个人中心:展示用户的个人信息和上传的壁纸作品,同时也可以查看用户的收藏、点赞、关注等信息。提供账号设置和退出登录等功能。
  • 上传壁纸页:提供用户上传自己创作的壁纸作品的功能,并在上传过程中引导用户填写相关信息。
  • 搜索页:提供关键词搜索功能,让用户可以根据关键词查找特定的壁纸。
  • 社区动态页:展示用户的评论、点赞和关注等动态,以及最新的壁纸上传和更新。

功能

  • 用户注册与登录:实现用户注册与登录功能,以便用户上传和下载壁纸。可以使用微信账号登录或自定义的用户名密码登录等方式。
  • 壁纸分类与标签:为壁纸设置分类和标签,方便用户按照不同主题或风格浏览和搜索壁纸,例如自然风景、抽象艺术、爱情等分类。
  • 壁纸上传与审核:允许用户上传自己的壁纸作品,并设置相应的审核机制,以确保壁纸质量和合法性。
  • 壁纸展示与预览:在小程序中以美观的方式展示壁纸,可以支持浏览、收藏和点赞等操作。同时提供高清壁纸的预览功能,让用户可以在下载之前先查看壁纸效果。
  • 壁纸下载与分享:提供壁纸的下载功能,让用户可以保存到手机或设备中。同时,也可以支持壁纸的分享功能,让用户可以将喜欢的壁纸分享给好友或社交网络。
  • 用户互动与社区:构建一个用户互动的社区,让用户可以在小程序中进行评论、点赞、关注等操作,增加用户的参与感和粘性。
  • 推荐与排行榜:实现壁纸的推荐算法或根据用户的喜好进行个性化推荐,同时也可以设置一些壁纸的热门排行榜,展示最受欢迎的壁纸作品。

技术栈

前端开发:

  • 微信小程序开发工具:使用微信小程序开发工具进行前端页面的开发和调试。
  • HTML5、CSS3、JavaScript:这些是前端开发的基本技术,用于构建小程序的页面和交互。
  • 微信小程序框架:根据个人喜好和熟悉程度选择使用原生小程序框架或者类似Vue.js的框架(如MpVue或WePY)。

后端开发:

  • Java语言:使用Java语言进行后端开发,可以使用Java的Spring Boot框架来简化开发。
  • Spring Boot:使用Spring Boot框架可以快速搭建后端服务,包括路由、控制器、数据持久化等功能。
  • MySQL数据库:使用MySQL作为数据存储和管理系统,存储用户信息、壁纸数据等。
  • Redis缓存:使用Redis作为缓存,提高数据访问的性能和响应速度。
  • Mybatis:使用Mybatis框架来简化数据库操作和管理。
  • MybatisPlus:Mybatis的增强工具,可以进一步简化开发并提供一些便捷的功能。

其他

  • 图片存储和管理:可以使用七牛云、阿里云OSS等云存储服务,将上传的壁纸保存到云端。
  • RESTful API设计:使用RESTful API来设计前后端之间的数据交互接口,方便前后端的协作开发。
  • 微信开放平台接口:如果需要实现一些与微信相关的功能,可探索并使用微信开放平台提供的接口和功能。

正文

设计数据库表

  • 以下是本壁纸分享小程序 PicMemorie 一些可能用到的数据库表的设计:
  1. 用户表(User)
    • 用户ID(UserID)
    • 用户名(Username)
    • 密码(Password)
    • 头像(Avatar)
    • 注册日期(RegistrationDate)
    • 其他用户相关信息
  2. 壁纸表(Wallpaper)
    • 壁纸ID(WallpaperID)
    • 壁纸名称(WallpaperName)
    • 壁纸描述(WallpaperDescription)
    • 壁纸URL(WallpaperURL)
    • 上传日期(UploadDate)
    • 点赞数(Likes)
    • 下载数(Downloads)
    • 所属用户ID(UserID)
    • 其他壁纸相关信息
  3. 点赞表(Like)
    • 点赞ID(LikeID)
    • 点赞用户ID(UserID)
    • 被点赞的壁纸ID(WallpaperID)
    • 点赞时间(LikeDate)
  4. 下载表(Download)
    • 下载ID(DownloadID)
    • 下载用户ID(UserID)
    • 被下载的壁纸ID(WallpaperID)
    • 下载时间(DownloadDate)

建表语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use pic_memories;
-- 用户信息
drop table if exists pic_memories.user;
create table if not exists pic_memories.`user`
(
`user_id` bigint auto_increment primary key comment '用户id',
`username` varchar(128) not null comment '用户名',
`password` varchar(32) not null comment '用户名',
`avatar` varchar(256) not null comment '头像',
phone varchar(16) null comment '电话',
user_role tinyint(1) default 0 null comment '普通用户-0 管理员-1---',
createTime datetime default CURRENT_TIMESTAMP null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP null comment '更新时间',
`is_delete` tinyint(1) default 0 not null comment '是否删除'
) comment '用户信息';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 壁纸信息
drop table if exists pic_memories.wallpaper;
create table if not exists `wallpaper`
(
`wallpaper_id` bigint auto_increment primary key comment '壁纸id',
`wallpaper_name` varchar(256) not null comment '壁纸名',
`wallpaper_description` varchar(256) not null comment '壁纸描述',
`wallpaper_url` varchar(256) not null comment '壁纸URL',
`upload_date` datetime not null comment '上传日期',
`likes` int not null comment '点赞数',
`downloads` int not null comment '用户名',
`user_id` bigint not null comment '所属用户',
status tinyint(1) not null default 1 comment '1 - 审核中 2 - 已发布 3 - 未通过',
`create_time` datetime default CURRENT_TIMESTAMP not null comment '创建时间',
`update_time` datetime default CURRENT_TIMESTAMP not null comment '更新时间',
`is_delete` tinyint(1) default 0 not null comment '是否删除'
) comment '壁纸信息';
1
2
3
4
5
6
7
8
9
10
11
-- 点赞信息
create table if not exists pic_memories.`like`
(
`like_id` bigint not null comment '点赞id',
`user_id` bigint not null comment '点赞用户id',
`wallpaper_id` bigint not null comment '被点赞的壁纸id',
`like_date` datetime not null comment '点赞时间',
`create_time` datetime default CURRENT_TIMESTAMP not null comment '创建时间',
`update_time` datetime default CURRENT_TIMESTAMP not null comment '更新时间',
`is_delete` varchar(256) default '0' not null comment '是否删除'
) comment '点赞信息';
1
2
3
4
5
6
7
8
9
10
11
-- 下载信息
create table if not exists pic_memories.`download`
(
`download_id` bigint not null comment '点赞id',
`user_id` bigint not null comment '点赞用户id',
`wallpaper_id` bigint not null comment '被点赞的壁纸id',
`like_date` datetime not null comment '点赞时间',
`create_time` datetime default CURRENT_TIMESTAMP not null comment '创建时间',
`update_time` datetime default CURRENT_TIMESTAMP not null comment '更新时间',
`is_delete` varchar(256) default '0' not null comment '是否删除'
) comment '下载信息';

用户登录/注册

页面导航栏

  • 暂定为四栏:
    • 精选碓 ✔
    • 分类 ✔
    • 动态/频道 ✔
    • 我的 ✔
  • 具体页面:
    • 精选页:精选壁纸:点赞量高、下载量高、最新上传
    • 分类页:根据分类,展示壁纸
    • 动态/频道:交互事件:壁纸上传、用户交流
    • 我的:
      • 用户信息详情页:完善资料、我的收藏、我的下载、上传壁纸、关于我们,✔
      • 其他:原创专栏 ✔

壁纸展示

  • 仅展示通过审核的壁纸:2 - 已发布

壁纸上传

  • 普通用户可以上传壁纸,状态默认为:1 - 审核中,交由管理员审核

  • 管理员决定图片是否可以上传

    • 上传成功,状态修改为:2 - 已发布
    • 不通过,则状态修改为:3 - 未通过
  • 通过的图片即可放入云对象存储中,持久化存储

今日目标

  • 精选页,页面开发 ✔
  • 用户页,页面开发 ✔
  • 用户登录、注册 ✔
  • 登录用户信息

用户注册/登录

  • 昵称、密码、手机号 ✔
  • 长度校验(前台 + 后台) ✔
  • 昵称不能携带特殊符号 ✔
  • 密码只能字母+数字 ✔
  • 手机号只能为大陆手机号,符合要求 ✔
  • 用户校验和数据状态保存 ✔

图片展示

  • 分类展示:壁纸的所属分类+已通过审核 ✔

  • 最新:上传时间倒序 ✔

管理员后台

  • 管理壁纸信息
    • 壁纸基本信息:壁纸名、描述、图片、所属分类、上传者、上传时间、更新时间、状态(审核中 已通过 未通过)
    • 其他:点赞数、下载量 ✔
  • 管理用户信息
    • 用户的基本信息 ✔
  • 管理用户的上传需求:审核表
  • 壁纸审核:
    • 管理壁纸的上传、下架
    • 小程序实现上传壁纸
    • 用户上传,保存至审核表;
    • 管理员审核,审核通过,存储至七牛云,返回链接;
    • 存储至壁纸信息表

用户使用

  • 壁纸搜索

    • 根据类型搜索
    • 在类型下,可选择按点赞量、上传时间排序
  • 频道

    • 频道同步显示上传信息
  • 壁纸信息

    • 点击壁纸,预览查看壁纸 ✔
    • 可点赞 ✔
    • 可下载
    • 小程序实现下载壁纸
  • 我的

    • 查看个人信息 ✔
    • 修改个人信息:展示数据、点击显示、修改提交、再次显示 ✔
    • 点赞过的壁纸
    • 下载的壁纸?(这个得看壁纸下载到哪儿了)
    • 信息修改 ✔
  • 分类搜索壁纸

    • 壁纸分类:自然风景、动漫插画、动物世界、游戏竞技、星空宇宙、美食餐饮、 唯美治愈、影音视听、城市建筑、音乐艺术、历史文化、girl ✔
    • 分类查询
  • 壁纸下载

    • 点击壁纸,跳转新页面 ✔
    • 壁纸信息预览、上传人信息 ✔
    • 支持下载,图片保存至用户相册:wx.saveImageToPhotosAlbum
  • 壁纸上传

    • “van-uploader”: “@vant/weapp/uploader/index”,文件上传
  • 退出登录 ✔

小小优化

  • 未登录用户不支持完善个人资料
1
2
3
4
5
6
7
8
9
10
// 跳转个人信息页
toUserInfo() {
if(!this.data.currentUser == {}){
wx.navigateTo({
url: '/pages/userInfo/index',
})
}else{
Toast("请先登录~")
}
},

接口实现

  • 用户登录 ✔

  • 用户注册 ✔

  • 保存用户登录态 ✔

  • 展示登录用户信息 ✔

  • 用户修改信息

  • 壁纸:分类、分页获取(已通过)✔

  • 点击查看壁纸详细信息:点赞数、收藏量,可提供下载

  • 用户下载壁纸 - 下载表(用户id、)

  • 用户上传壁纸 - 上传表()

数据库表修改

  • 需新增用户上传表(2023/08/06午)
  • 用户与壁纸交互:浏览壁纸、点赞壁纸、下载壁纸、上传壁纸

  • 不需要记录壁纸下载表

小程序登录 - 用户身份标识

image-20230908221852633

image-20230908221858146

image-20230908221905369

壁纸状态

  • 壁纸状态:(2023/08/06午)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 0 - 公开, 在队伍大厅中可以直接加入
*/
REVIEW(0, "审核中"),

/**
* 1 - 私有, 在队伍大厅中不可以直接加入
*/
PASS(1, "已发布"),

/**
* 2 - 公开且加密, 加入队伍需要密码
*/
NOPASS(2, "不通过");

壁纸分类

  • 壁纸分类:(2023/08/06午)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 0 - 自然风景
*/
NATURAL(0, "自然风景"),

/**
* 1 - 动漫插画
*/
CARTOON(1, "动漫插画"),

/**
* 2 - 未来科技
*/
TECHNOLOGY(2, "未来科技"),

/**
* 3 - girl
*/
GIRL(2, "girl"),

/**
* 4 - 游戏
*/
GAME(2, "游戏"),

/**
* 5 - 美食
*/
FOOD(2, "美食");

分类查询壁纸

  • 查询所有分类

  • 按分类,依次查询每个分类下的壁纸

  • 前台传送参数,查询那个分类下的壁纸

  • controller:

1
2
3
4
5
6
7
8
9
10
@GetMapping("/listPage")
public BaseResponse<List<Wallpaper>> getPageByType(@RequestParam Integer searchType) {
//controller对参数的校验
if (searchType == null) {
throw new BusinessException(PARMS_ERROR, "请求参数错误");
}

List<Wallpaper> result = wallpaperService.getPageByType(searchType);
return ResultUtils.success(result);
}
  • service:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 分页查询
* 分类查询壁纸
*
* @return 分类壁纸
*/
@Override
public List<Wallpaper> getPageByType(Integer searchType) {
if (WallpaperTypeEnum.getEnumByValue(searchType) == null) {
throw new BusinessException(ErrorCode.PARMS_ERROR, "没有这样的壁纸类型");
}

QueryWrapper<Wallpaper> type_wqw = new QueryWrapper<>();
type_wqw.eq("type", searchType);

return this.list(type_wqw);
}
  • 我这代码写得真烂,逻辑混乱:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 分页查询
* 分类查询壁纸
*
* @return
*/
@Override
public List<Wallpaper> getPageByType(Integer searchType) {
// 1.查询所有分类
QueryWrapper<Wallpaper> wqw = new QueryWrapper<>();
wqw.select("type");
wqw.eq("status", WallpaperStatusEnum.PASS.getValue());
wqw.groupBy("type");
List<Wallpaper> list = wallpaperService.list(wqw);
// 2.存储所有分类
ArrayList<Integer> tyeList = new ArrayList<>();
for (Wallpaper wallpaper : list) {
Integer type = wallpaper.getType();
tyeList.add(type);
}
// 3.按分类查询, 返回壁纸列表
List<Wallpaper> wallpaperListByType = new ArrayList<>();
// 校验壁纸类型
for (Integer type : tyeList) {
if (WallpaperTypeEnum.getEnumByValue(searchType) == null) {
throw new BusinessException(ErrorCode.PARMS_ERROR, "没有这样的壁纸类型");
}

QueryWrapper<Wallpaper> type_wqw = new QueryWrapper<>();
type_wqw.eq("type", type);

wallpaperListByType = wallpaperService.list(type_wqw);
}
return wallpaperListByType;
}

最新查询

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 分页查询
* 分类最新壁纸
*
* @return 分类壁纸
*/
@Override
public List<Wallpaper> getPageByTime() {
QueryWrapper<Wallpaper> time_wqw = new QueryWrapper<>();
time_wqw.orderByAsc("create_time");

return this.list(time_wqw);
}

精选查询

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 分页查询
* 查询精选壁纸
*
* @return 分类壁纸
*/
@Override
public List<Wallpaper> getPageByLike() {
QueryWrapper<Wallpaper> like_wqw = new QueryWrapper<>();
like_wqw.orderByDesc("likes");

return this.list(like_wqw);
}
  • Redis + wx.login + Code2Session ,Session_key

  • 成功实现用户校验,保存用户登录态(2023/08/07早)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* 用户注册
*
* @param username 昵称
* @param password 密码
* @param phone 手机号
* @return 用户id
*/
@Override
public long userRegister(String username, String password, String phone) {
// 1.校验
// 1.1.昵称, 密码, 手机号不能为空
if (StringUtils.isAnyBlank(username, password, phone)) throw new BusinessException(PARMS_ERROR);

// 1.2.昵称不小于2位, 不大于10位
if (username.length() < 2 || username.length() > 10) throw new BusinessException("昵称不符合要求", 50000, "昵称长度不符合要求");

// 1.3.昵称不包含特殊字符
String pattern = "^[\\u4e00-\\u9fa5A-Za-z\\d_]+$";
if (!Pattern.matches(pattern, username)) throw new BusinessException("昵称不符合要求", 50001, "昵称包含特殊字符");

// 1.4.密码不小于6位, 不大于10位
if (password.length() < 6 || password.length() > 10) throw new BusinessException("密码不符合要求", 60000, "密码长度不符合要求");

// 1.5.手机号只能为大陆手机号,符合要求
pattern = "^(13\\d|14[5|7]|15[0|123456789]|18[0|12356789])\\d{8}$";
if (!Pattern.matches(pattern, phone)) throw new BusinessException("手机号不符合要求", 50001, "手机号格式有误");

// 1.6.昵称不能重复
QueryWrapper<User> uqw = new QueryWrapper<>();
uqw.eq("username", username);
Long count = userMapper.selectCount(uqw);
if (count > 0) throw new BusinessException("昵称不符合要求", 50002, "该昵称已存在");

// 2.对密码进行加密
String encryptPassword = DigestUtils.md5DigestAsHex((SALT + password).getBytes());

// 3.向数据库中插入用户数据
User user = new User();
user.setUsername(username);
user.setPassword(encryptPassword);
user.setPhone(phone);
user.setAvatar("http://ry2s7czdf.hd-bkt.clouddn.com/imgs/avatar/winter_nature_6-wallpaper-2560x1600.jpg");

boolean save = this.save(user);
//插入失败
if (!save) throw new BusinessException(UPDATE_ERROR);

return user.getUserId();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 用户登录
*
* @param username 昵称
* @param password 密码
* @param request 登录用户信息
* @return 脱敏用户信息
*/
@Override
public User userLogin(String code, String username, String password, HttpServletRequest request) {
// 1.校验
// 1.1.昵称, 密码不能为空
if (StringUtils.isAnyBlank(username, password)) throw new BusinessException(PARMS_ERROR);

// 1.2.昵称不小于2位, 不大于10位
if (username.length() < 2 || username.length() > 10) throw new BusinessException("昵称不符合要求", 50000, "昵称长度不符合要求");

// 1.3.昵称不包含特殊字符
String pattern = "^[\\u4e00-\\u9fa5A-Za-z\\d_]+$";
if (!Pattern.matches(pattern, username)) throw new BusinessException("昵称不符合要求", 50001, "昵称包含特殊字符");

// 1.4.密码不小于6位, 不大于10位
if (password.length() < 6 || password.length() > 10) throw new BusinessException("密码不符合要求", 60000, "密码长度不符合要求");

// 1.5.检验该用户是否注册
User user = new User();

user.setUsername(username);
String encryptPassword = DigestUtils.md5DigestAsHex((SALT + password).getBytes());
user.setPassword(encryptPassword);

QueryWrapper<User> qw = new QueryWrapper<>();
qw.eq("username", username);
User one = this.getOne(qw);

// 1.5.1.用户未注册(包含了MP自带的逻辑删除校验)
if (one == null) throw new BusinessException(NOT_REGISTER, "该账户未注册");

qw.eq("password", encryptPassword);
one = this.getOne(qw);
if (one == null) throw new BusinessException("密码不符合要求", 60000, "密码错误");


// 2.脱敏用户信息
User safetyUser = getSafetyUser(one);
// 3.记录用户登录态
// request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser);
request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser);
// 4.返回用户信息
return safetyUser;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @param session_key token
* @return 当前登陆用户信息
*/
@Override
public User getCurrentUser(String session_key) {
String redisKey = String.format("pic_memories:user:login:%s", session_key);
User currentUser = (User) redisTemplate.opsForValue().get(redisKey);

if (currentUser == null) {
throw new BusinessException(NO_AUTH, "未识别到登录用户信息");
}

// 查询数据库, 获取最新信息, 而非登录时记录的信息
Long userId = currentUser.getUserId();
return getById(userId);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @param user 修改信息
* @param session_key 登录用户
* @return 修改信息成功
*/
@Override
public String userUpdate(User user, String session_key) {
// 1.当前登录用户
User currentUser = this.getCurrentUser(session_key);

// 2.是否一致
if (!user.getUserId().equals(currentUser.getUserId())) {
throw new BusinessException(NO_AUTH, "无权限");
}

// 3.执行修改
userMapper.updateById(user);
return "修改信息成功";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 用户注销
*
* @param session_key session_key
* @return 注销成功与否(t / f)
*/
@Override
public String userLogout(String session_key) {
// 1.当前登录用户
User currentUser = this.getCurrentUser(session_key);

// 2.是否存在
if (currentUser == null) {
throw new BusinessException(NO_AUTH, "无权限");
}

// 3.移除session_key
String redisKey = String.format("pic_memories:user:login:%s", session_key);
redisTemplate.delete(redisKey);

return "退出登录成功";
}
1
2
3
4
<!-- 点赞量 -->
<view class="like">
<van-icon name="{{isLiked ? 'like' : 'like-o'}}" color="red" bindtap="handleIconClick"/>
</view>
1
2
3
4
5
6
7
8
9
10
11
// 点赞壁纸
handleIconClick() {
this.setData({
isLiked: !this.data.isLiked, // 点击后取反
});
if(this.data.isLiked){
Toast("已收藏~")
}else{
Toast('取消收藏~')
}
},

TODO

  • 手机号不能重复
  • 注册成功后,跳转登录页,显示注册成功
  • 跨域问题 ✔
  • 后端的token校验 Redis ✔
  • 壁纸样式可以分为手机壁纸 / 电脑壁纸
  • 用户登录页面优化
    • 未登录,显示登录按钮 ✔
    • 已登录,显示用户信息 ✔
  • 用户添加签名
  • 修改信息未完成
  • 每张壁纸应该可以同属不同的分类
  • 点击壁纸,获取到的壁纸信息应该同步获取上传人信息 ✔
  • 如何使用 navagateTo()携带参数
  • 如何实现移动端/PC端下载图片
  • 壁纸信息添加标签 ✔
  • 点赞壁纸后端数据记录
  • 修改用户信息,仍未解决,携带参数扔为修改前的数据

总结


PicMemories-开发文档
http://example.com/2023/08/02/PicMemories 壁纸分享小程序 - 开发文档/
作者
Memory
发布于
2023年8月2日
更新于
2023年9月8日
许可协议