源码地址:
小程序源码 https://github.com/w77996/mini-straw
后台源码 httpcom/w77996/hi-straw
复制代码
体验一下
之前乘着换工作的间隙撸的,一方面练习一下小程序,学过之后没怎么应用,一方面写点笔记啥的,项目断断续续做了两个月,只是一个简单的图片上传工具,觉得不错的话记得给个star
小程序
- 小程序授权,登录
- 父组件与子组件相互通信
- 小程序分享,意见与建议,客服功能,文件上传
- flex布局的使用
- Promise的使用,业务model封装
- 插槽的使用,动画效果
项目目录结构
mini-straw
├── component
| ├── file
| ├── image-button
| ├── search
| ├── tag
├── images
| ├── icon
| ├── tab
├── model
├── pages
| ├── about
| ├── auth
| ├── file
| ├── index
| ├── launch
| ├── my
└── utils
复制代码
后台:
技术栈:spring boot + druid + mybatis + jwt
- 微信登录,jwt授权
- 注解及AOP的使用
- maven多环境打包,docker使用
- shell脚本自动化部署
- nginx反向代理及https配置
- 七牛云文件操作
hi-straw
├── common
├── config
├── controller
├── core
| ├── annontaion
| ├── aop
| ├── constant
| ├── filter
| ├── jwt
| └── result
├── entity
| ├── dto
| └── vo
├── exception
├── mapper
├── service
└── util
复制代码
小程序详解
[TOC]
mini-straw
项目结构
开屏页
1.判断网络状态
使用?wx.getNetworkType({}) ?可获取当前网络状态,?networkType ?值?wifi/2g/3g/4g/unknown(Android下不常见的网络类型)/none(无网络)
wx.getNetworkType({
success: res => {
if (res.networkType == "none") {
wx.showToast({
title: '嗷~~网络不可用',icon: 'none',duration: 2000
})
return;
}
},})
复制代码
2.判断授权状态
wx.getSetting({})?获取授权状态,在获得?data ?后取?data.authSetting['scope.userInfo'] ?判断授权状态
]) {
wx.getUserInfo({
success: data => {
console.log("userInfo {}",data)
let userInfo = data.userInfo;
wx.setStorageSync('userInfo',userInfo);
this._userLoginGetCode(userInfo);
}
});
wx.setStorageSync('authorized',true);
} else {
"未授权")
let timer = setTimeout(() => {
wx.redirectTo({
url: '/pages/auth/auth'
})
},2000)
}
}
});
复制代码
若授权,则调用?wx.getUserInfo({}) ?获取微信用户信息,信息获取完成后调用?wx.login({}) 获取小程序的?code ?,通过?code ?向后台获取用户openId及token。
,}),0);">2000)
});
} else {
'登录失败!' + res.errMsg)
}
}
})
},复制代码
3.跳转页面
- 跳转?
/pages/auth/auth ?页面使用的是?wx.redirectTo({})
- 跳转?
/pages/index/index ?页面使用的是?wx.switchTab({}) ? 因为?/pages/index/index ?是小程序tab页,使用?wx.redirectTo({}) ?无法跳转
授权页
授权需制定button按钮,加入?open-type='getUserInfo' ?属性,?bindgetuserinfo ?调用自定义方法?onGetUserInfo ?。
<button class="auth-button" open-type='getUserInfo' bindgetuserinfo="onGetUserInfo">好的</button>
复制代码
onGetUserInfo ?接受授权状态及授权获取的用户信息,再进行?code ?获取,通过?onGetUserInfo: function(e) {
console.log(e)
const userInfo = e.detail.userInfo;
if (userInfo) {
this._userLoginGetCode(userInfo);
}
},复制代码
主页
1. 图片按钮插槽组件
在?component ?目录下的?images-button ?组件,做了简单的图片插槽统一,在分享按钮,用户登录按钮,文件上传按钮均可以使用。?plain="{{true}}" ?代表button背景透明
<button open-type="{{openType}}" plain={{true}}" class="container">
<slot name="img"></slot>
</button>
复制代码
options ?需要开启插槽功能,添加?multipleSlots: true ?
open-type="{{openType}}" ?父组件将参数传入子组件,子组件在?properties ?属性中可以获取到父组件传来的openType数据,通过?this.properties.openType ?可以获取属性值
options: {
multipleSlots: true
},
properties: {
openType: {
type: String
}
},复制代码
index?页面引入组件,需要在?index.json ?中添加组件路径
{
"usingComponents": {
"btn-cmp": "/component/image-button/index"
}
}
复制代码
2. 上传文件
主要使用到?wx.chooseImage({}) ?进行图片的选择,选择后使用?wx.uploadFile({}) ?上传图片至服务器
,'compressed'],136);">// 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album',0);">'camera'],136);">// 可以指定来源是相册还是相机,默认二者都有
success: function(res) {
let tempFilePaths = res.tempFilePaths;
console.log(tempFilePaths)
wx.uploadFile({
header: {
"Authorization": "Bearer " + wx.getStorageSync("token")
},url: config.apiBaseUrl + '/file/upload',filePath: tempFilePaths[0],name: 'file',success: (res) => {
wx.showToast({
title: "上传成功~",0);">2000
})
},fail: (res) => {
wx.showToast({
title: res.data.msg,0);">2000
})
}
})
}
})
}
复制代码
列表页
1.search组件的显示和隐藏
固定列表页搜索header位置,点击header显示?search ?组件,在?search ?组件点击取消则隐藏?search ?组件,此处设计子组件向父组件传递消息
"usingComponents": {
...
"search-cmp": "/component/search/index"
}
复制代码
- 使用searchPage参数判断?
search ?组件的,默认为false,在点击header时更新searchPage为true,显示?search ?组件
search ?页面点击取消,向父组件发送一个?this.triggerEvent('cancel',{},{}); ?事件,在xml中的?search-cmp ?添加?cancel ?事件的通知绑定
#file页面中的search-cmp组件
<search-cmp wx:if="{{searchPage}}" bind:cancel="onCancel"></search-cmp>
复制代码
父组件?file ?页面绑定子组件传来的?cancel ?事件通知,就调用?onCancel ?方法,在?onCancel 方法中获取事件响应,将?searchPage ?参数修改为false,?search ?组件就隐藏起来了
,{});
},254);'>2.文件列表
1.获取列表信息传递给file组件
page?中的file页面,获取到后台传来的fileList数据,引入file组件,?file="{{item}}" ?将数据传入子组件
component?中的file组件,在?properties ?添加属性?file ?来接收父组件传来的数据
file组件在xml页面中使用?{{file.fileName}} ?即可获取到对象信息,相应的数据也会呈现在页面上
3.粘贴板操作
<image src="images/copy.png" bindtap="onCopy"></image>
复制代码
图片点击响应方法?onCopy ?,?onCopy ?调用?wx.setClipboardData({}) ?可以将数据复制到粘贴板
onCopy: function (event) {
console.info(event)
this;
wx.setClipboardData({
data: _this.properties.file.filePath,success: function(res) {
wx.showToast({
title: '图片地址复制成功',})
}
});
复制代码
4.删除操作
子组件将数据传递给父组件,点击删除图片出发?onDelete ?方法,249); border: 0px; font-weight: 600; font-size: 14px;'>this.triggerEvent('del',{fileId},{});?将文件ID发送到父组件
onDelete: let fileId = this.properties.file.id;
this.triggerEvent('del',{});
},254);'>父组件file页面绑定子组件传来的?del ?事件
调用?onDelete ?出发网络请求去完成删除文件的逻辑,删除成功后重新刷新文件列表
,})
this.setData({
pageNum: fileList: []
});
this._getFileList();
})
},254);'>我的页面
1.意见和建议
小程序自带用户反馈功能,使用?button ?跳转至网页,用户可以填写相关反馈,?open-type ?设置为?feedback
2.小程序客服
小程序的?button ?中的?open-type ?拥有开放能力,在微信公众平台中启用客服功能,添加客服人员,在代码中添加?button ?即可弹出客服聊天界面,249); border: 0px; font-weight: 600; font-size: 14px;'>contact
3.小程序分享
此处使用插槽,249); border: 0px; font-weight: 600; font-size: 14px;'>share
<btn-cmp open-"share">
<image slot="img" src="images/share.png" />
</btn-cmp>
复制代码
动画
小程序动画官方文档
开屏动画,设置文字透明度,从0到1,渐渐显示,主要使用到?opacity ?去设置组件的透明度,先创建一个动画?animationTip ?,持续800ms,然后在?setTimeout(function () {}) ?中设置动画出现时间
var animationTip = wx.createAnimation({
duration: 800,timingFunction: 'ease',});
this.animationTip = animationTip;
animationTip.opacity(0).step()
this.setData({
animationTip: animationTip.export()
})
setTimeout(function () {
animationTip.opacity(1).step()
this.setData({
animationTip: animationTip.export()
})
}.bind(this),0);">500 )
复制代码
部署
- 修改?
utils ?目录下的?config.apiBaseUrl ?,改成自己的域名,上传到微信公众号平台,在版本管理中进行发布
后台功能详解
hi-straw
数据库设计
,0);">`file_path` varchar(255) '文件路径',0);">`file_name` 100) '文件名',0);">`file_size` 10) '文件大小',0);">`props` NULL,0);">`status` tinyint(5) DEFAULT '0' '0.正常 -1.删除',0);">`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT'创建时间',PRIMARY KEY (`id`),KEY `user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=27 CHARSET=utf8mb4 '用户文件列表';
复制代码
,0);">`nickname` '用户昵称',0);">`user_logo` 250) '用户logo',0);">`phone_num` '手机号',0);">`open_id` 55) '微信openId',0);">`union_id` '微信union_id',0);">`password` 50) '密码',0);">`uuid` '自定义生成的uuid',0);">`last_login` datetime '最后登陆时间',51); font-weight: 700;">UNIQUE `open_id` (`open_id`) USING HASH
) 12 '用户表';
复制代码
,0);">`left_size` '5242880' '剩余文件大小',0);">`total_size` 11)'用户文件空间大小',0);">`file_num` '文件数量',0);">`is_vip` tinyint('是否为vip,1.是 0.否',0);">43 '用户文件信息';
复制代码
,0);">`location` '位置',0);">`platform` '平台',0);">`birthday` datetime '生日',0);">`user_id`)
) InnoDB '用户详细表';
复制代码
代码详解
前期准备
- linux服务器及MySql数据库
- 七牛云账号,可以去七牛云官网申请账号
- HTTPS证书阿里云获取免费证书
- 微信公众平台小程序开发者账号
七牛云文件上传
代码?com.w77996.straw.util.QiNiuUtil
1.七牛云账号申请
七牛云官网申请账号,获得?AccessKey ?,249); border: 0px; font-weight: 600; font-size: 14px;'>SecretKey?,并设置七牛云图片?bucket
2.七牛云SDK引入
pom.xml?文件引入七牛云仓库
2. 七牛云token生成
/**
* 七牛云生成token
*
* @param fileName
* @return
*/
public QiNiuAuth generateToken(String userId,String fileName) {
Auth auth = Auth.create(accessKey,secretKey);
String key = "upload/file/000/" + userId + "/" + fileName;
StringMap putPolicy = new StringMap();
putPolicy.put("returnBody",0);">"{"key":"$(key)","hash":"$(etag)bucket":"$(bucket)fsize":$(fsize)}");
long expireSeconds = 3600;
String upToken = auth.uploadToken(bucket,key,expireSeconds,putPolicy);
Map<String,0);">String> resultMap = Maps.newHashMapWithExpectedSize(3);
resultMap.put("domain",0);">"https://www.w77996.cn");
resultMap.put("key",key);
resultMap.put("upToken",upToken);
return new QiNiuAuth("https://www.w77996.cn",upToken);
}
复制代码
3.上传文件代码编写
4.删除图片
注解+AOP接口限流
1. 注解编写
com.w77996.straw.core.annotation.Limiter
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limiter {
String value() default "";
double perSecond() default Double.MAX_VALUE;
;
TimeUnit timeOutUnit() default TimeUnit.MILLISECONDS;
}
复制代码
2.AOP实现
com.w77996.straw.core.aop.RateLimitAspect
3. 注解使用
限定每秒只能调用一次,如果超出,则返回?Result.error(ResultCode.BUSY)
JWT实现
1.jwt生成
使用JwtUtil生成jwt Token
2.token解析成userId
将userId放入token中,在请求接口时可以通过请求Header获取Bearer {token}进行解码,从而获取userId。
3.拦截器+注解方式进行token鉴权
com.w77996.straw.core.annotation.IgnoreToken? 先设置忽略token的注解
com.w77996.straw.core.filter.TokenFilter? 拦截器?TokenFilter ?实现?HandlerInterceptor ?,在每次请求进来时进行拦截,在调用controller之前都会调用?perHandle ?,所以在?perHandler ?内获取方法名的注解,判断是否有ignoreToken的注解,然后进行jwt的校验。
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
IgnoreToken ignoreToken = handlerMethod.getBeanType().getAnnotation(IgnoreToken.class);
log.info("enter preHandle {}",0);">request.getRequestURL());
if (ignoreToken == null) {
ignoreToken = handlerMethod.getMethodAnnotation(IgnoreToken.class);
}
if (ignoreToken != null) {
"ignoreToken not null");
return true;
}
"ignoreToken null");
String token = request.getHeader(if(token != null){
"token is {}",token);
"admin".equals(token.substring(7))) {
return true;
}
Claims claims = JwtHelper.parseJWT(token.substring(7),Audience.BASE64SECRET);
if(claims != null){
"claims is {} {}",claims.toString(),claims.getSubject());
return true;
}else{
"claims is null");
throw new GlobalException(ResultCode.ERROR_AUTH);
}
}
return false;
}
复制代码
4.实现web拦截器
com.w77996.straw.config.WebMvcAdapterConfig?不拦截?/druid/* ?的接口
Druid监控配置
com.w77996.straw.config.DruidConfig? 项目运行后访问?http://ip:port/druid ?,输入账号?admin ?密码?amdin ?即可访问
全局异常拦截
全局异常拦截主要是依靠?@RestControllerAdvice ?注解,在方法上使用?@ExceptionHandler(value = Exception.class) ?代表拦截所有Exception,然后进行对应的操作
微信登录
需要在微信公共平台获取对应的appId,appSec,小程序获取到code之后发送给后台,后台获取code向微信发送http请求,使用的是restTemplate,但是需要注意编码,微信编码返回是ISO-8859-1;调用成功后可以拿到用户的openId,再去数据库中获取对应的用户信息,进行登陆更新及用户创建的逻辑处理
spring boot + maven多环境打包
1.resouce下的yml文件
项目环境分为?dev ?和?prod ?两种,?resource ?文件下默认加载?application.yml ?。
application.yml
@spring.profiles.active@?对应的为pom.xml文件中profiles下的?spring.profiles.active ?属性
2.pom.xml配置
默认情况下使用?dev ?环境下的配置信息
3.不同环境打包
- 打包?
prod ?环境:执行?mvn package -Pprod -DskipTests
- 打包?
dev ?环境:执行?mvn package -Pdev -DskipTests
4.项目打包命名
properties?属性中添加时间格式,然后再?build ?中添加?fileName ?格式化文件名。
打包完成后生成的jar:?hi-straw-1.0.0-prod_2019-04-091533.jar
shell脚本编写
登陆服务器,clone项目至?/root/repo_git/ ?目录下,执行进入?script ?目录下,执行?./build.sh ?,需要将?RELEASE_HOST ?换成你自己的服务器地址,方便做保存备份
执行?docker images 查看刚刚打包好的docker镜像
maven + docker 打包部署
1.docker环境安装卸载老旧的版本(若未安装过可省略此步):
sudo apt-get remove docker docker-engine docker.io
复制代码
安装最新的docker:
curl -fsSL get.docker.com -o get-docker.sh
sudo sh sh
复制代码
确认Docker成功安装:
docker run hello-world
复制代码
2.项目编译打包
src/main/docker?下建立?dockerFile ?文件
pom.xml配置docker打包,配合shell脚本在linux实现maven自动打包docker
<plugin>
<groupId>com.spotify</artifactId>docker-maven-plugin</version>
<configuration>
<imageName>${project.artifactId}</imageName>
<dockerDirectory>src/main/docker</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
复制代码
执行?docker images ?查看刚刚打包的docker镜像
docker run --name hi-straw -p 8989:8989 -t hi-straw?启动镜像
dockers ps?查看已启动docker镜像
nginx配置https
1.安装nginx
登陆到服务器,执行
$ apt-get update
$ apt-get install nginx
复制代码
2. 获取证书
可以去阿里云获取免费证书
将生成的证书放入?/etc/nginx/sites-enabled/cert/ ?(具体看你将nginx安装在哪个目录下)
3. 配置nginx文件
新建一个https.conf
配置完成后,检查一下nginx配置文件是否可用
配置正确后,重新加载配置文件使配置生效
如需重启nginx,用以下命令:
部署
修改?resource ?下的?application-dev.yml ?和?application-prod.yml ?中你自己申请的微信息及七牛云信息修改,修改数据库地址,用户名和密码
spring:
datasource:
name: graduate
driver-class-name: com.mysql.jdbc.Driver
url: 数据库地址
username: 数据库用户名
password: 数据库密码
复制代码
script?目录下?build.sh
RELEASE_HOST="你自己的服务器地址"
复制代码
项目的服务器7月15号到期了……哪位大佬资助一下服务器,感激不尽
捐助
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|