Небольшой программный артефакт кровати с изображением чучела, открытый исходный код спереди и сзади

Апплет WeChat

[TOC]

Артефакт кровати в виде чучела

Сервер устарел, и проект недоступен, пожалуйста, поймите

Адрес источника:

小程序源码 https://github.com/w77996/mini-straw  
后台源码 https://github.com/w77996/hi-straw  

испытать это

image

Раньше я играл в перерывах между сменой работы.С одной стороны, я практиковал апплет. После того, как я его выучил, я им почти не пользовался. С другой стороны, я писал какие-то заметки. Я работал над проектом с перерывами в течение двух месяцев. Это всего лишь простой инструмент для загрузки изображений. Не забудьте поставить звезду

Апплеты

  1. авторизация апплета, логин
  2. Родительский и дочерний компоненты взаимодействуют друг с другом
  3. Обмен небольшими программами, комментарии и предложения, функции обслуживания клиентов, загрузка файлов
  4. Использование гибкой верстки
  5. Использование Promise, инкапсуляция бизнес-модели
  6. Использование слотов, анимационные эффекты

Структура каталогов проекта

mini-straw
    ├── component -- 组件
    |    ├── file -- 文件组件
    |    ├── image-button -- 图片按钮组件
    |    ├── search -- 查找页面组件
    |    ├── tag -- 标签组件
    ├── images -- 图片目录
    |    ├── icon -- icon图片
    |    ├── tab -- tab图片
    ├── model -- 封装的model
    ├── pages -- 页面
    |    ├── about -- 关于页
    |    ├── auth -- 授权页
    |    ├── file -- 文件页
    |    ├── index -- 首页
    |    ├── launch -- 启动页面
    |    ├── my -- 个人中心
    └── utils -- 工具

小程序文档

За кулисами:

Стек технологий: spring boot + druid + mybatis + jwt

  1. Вход в WeChat, авторизация jwt
  2. Аннотации и использование АОП
  3. Мультисредовая упаковка Maven, использование докеров
  4. сценарий оболочки для автоматизации развертывания
  5. Обратный прокси nginx и конфигурация https
  6. Операция с облачным файлом Qiniu

Структура каталогов проекта

hi-straw
├── common -- 公共模块
├── config -- 配置模块
├── controller -- controller接口
├── core -- 核心业务模块
|    ├── annontaion -- 注解
|    ├── aop -- aop实现
|    ├── constant -- 常量
|    ├── filter -- 拦截器
|    ├── jwt -- jwt相关
|    └── result -- 结果返回
├── entity -- 实体类
|    ├── dto -- 数据传输
|    └── vo -- 页面传输
├── exception -- 全局异常
├── mapper -- dao层
├── service -- service层
└── util -- 工具类

后台文档

Небольшие детали программы

[TOC]

mini-straw

Структура проекта

mini-straw
    ├── component -- 组件
    |    ├── file -- 文件组件
    |    ├── image-button -- 图片按钮组件
    |    ├── search -- 查找页面组件
    |    ├── tag -- 标签组件
    ├── images -- 图片目录
    |    ├── icon -- icon图片
    |    ├── tab -- tab图片
    ├── model -- 封装的model
    ├── pages -- 页面
    |    ├── about -- 关于页
    |    ├── auth -- 授权页
    |    ├── file -- 文件页
    |    ├── index -- 首页
    |    ├── launch -- 启动页面
    |    ├── my -- 个人中心
    └── utils -- 工具

открытие страницы

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.getSetting({
    success: data => {
    if (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 {
        console.log("未授权")
        //跳转至授权页
        let timer = setTimeout(() => {
                wx.redirectTo({
                        url: '/pages/auth/auth'
                })
        }, 2000)

    }
    }
});

Если разрешено, звонитеwx.getUserInfo({})Получить информацию о пользователе WeChat, позвонить после завершения сбора информацииwx.login({})получить апплетcode,пройти черезcodeПолучите openId и токен пользователя в фоновом режиме.

//后台获取code
_userLoginGetCode(userInfo) {
    console.log("发起_userLoginGetCode请求");
    wx.login({
        success(res) {
        console.log("wx.login {}", res);
        if (res.code) {
            // 发起网络请求
            const code = res.code;
            userInfo.code = code;
            userModel.getTokenByCode(userInfo).then((res) => {
                console.log("userModel getUserInfo {}", res);
                wx.setStorageSync("token", res.data.data.token);
                let timer = setTimeout(() =>
                    wx.switchTab({
                            url: '/pages/index/index',
                    }), 2000)
            });
        } else {
                console.log('登录失败!' + res.errMsg)
        }
        }
    })
},

3. Страница перехода

  • Прыжок/pages/auth/authСтраница используетwx.redirectTo({})
  • Прыжок/pages/index/indexСтраница используетwx.switchTab({})
    так как/pages/index/indexэто вкладка апплета, используйтеwx.redirectTo({})не могу прыгать

Страница авторизации

Авторизация нужна для оформления кнопки button, joinopen-type='getUserInfo'Атрибуты,bindgetuserinfoвызвать пользовательский методonGetUserInfo.

 <button class="auth-button" open-type='getUserInfo' bindgetuserinfo="onGetUserInfo">好的</button>

onGetUserInfoПримите статус авторизации и информацию о пользователе, полученную при авторизации, а затем продолжитеcodeпройти черезcodeПолучите openId и токен пользователя в фоновом режиме.

onGetUserInfo: function(e) {
    console.log(e)
    const userInfo = e.detail.userInfo;
    if (userInfo) {
            //通过`code`向后台获取用户openId及token。
            this._userLoginGetCode(userInfo);
    }
},

Домой

1. Слот для кнопки с изображением в сборе

существуетcomponentв каталогеimages-buttonКомпонент имеет простой унифицированный слот для изображений, который можно использовать в кнопке «Поделиться», кнопке входа пользователя и кнопке загрузки файла.plain="{{true}}"Представляет прозрачный фон кнопки

<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({})загрузить изображение на сервер

//上传文件
onUpload(event) {
let _this = this;
wx.chooseImage({
  count: 1, // 默认9
  sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
  sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
  success: function(res) {
    // 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片
    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: "上传成功~",
          icon: 'none',
          duration: 2000
        })
      },
      fail: (res) => {
        wx.showToast({
          title: res.data.msg,
          icon: 'none',
          duration: 2000
        })
      }
    })

  }
})
}

Список

1. Показать и скрыть компонент поиска

Фиксированная позиция заголовка поиска на странице списка, щелкните заголовок, чтобы отобразитьsearchкомпоненты, вsearchКомпонент скрыт при нажатии кнопки «Отмена»searchКомпоненты, где дочерние компоненты предназначены для передачи сообщений родительским компонентам.

  • Внедрить компонент поиска
 "usingComponents": {
    ...
    "search-cmp": "/component/search/index"
  }
  • Используйте параметр searchPage для оценкиsearchКомпонент, значение по умолчанию — false, при нажатии на заголовок обновите searchPage до true, отобразитеsearchкомпоненты
<view wx:if="{{!searchPage}}" class="container">
  ...
</view>

<search-cmp  wx:if="{{searchPage}}" ></search-cmp>
  • searchСтраница нажимает кнопку «Отмена» и отправляетthis.triggerEvent('cancel', {}, {});Событие, в XMLsearch-cmpДобавить кcancelпривязка уведомления о событии
#file页面中的search-cmp组件
<search-cmp  wx:if="{{searchPage}}" bind:cancel="onCancel"></search-cmp>

родительский компонентfileПривязка страницы из подкомпонентовcancelуведомление о событии, просто позвонитеonCancelметод, существуетonCancelметод получения ответа на событие,searchPageПараметр модифицирован на false.searchкомпоненты скрыты

//cancel searching page 
onCancel(event) {
  console.info(event)
  this.triggerEvent('cancel', {}, {});
 
},

2. Список файлов

1. Получите информацию о списке и передайте ее файловому компоненту.

существуетpageНа странице файла получите данные fileList из фона и введите файловый компонент,file="{{item}}"Передать данные дочерним компонентам

<view wx:if="{{fileList}}">
  <block wx:for="{{fileList}}" wx:key="{{item.id}}" file="{{item}}">
    <file-cmp file="{{item}}" bind:del="onDelete"></file-cmp>
    <view class="line"></view>
  </block>
</view>

существуетcomponentфайловый компонент вpropertiesдобавить свойстваfileполучать данные от родительского компонента

  /**
   * 组件的属性列表
   */
  properties: {
    file: Object
  }

Файловый компонент используется на странице xml{{file.fileName}}Информация об объекте может быть получена, и соответствующие данные также будут представлены на странице.

3. Операция вставки платы

<image src="images/copy.png" bindtap="onCopy"></image>

Метод ответа на клик по изображениюonCopy,onCopyперечислитьwx.setClipboardData({})Данные могут быть скопированы на монтажный стол

    
    onCopy: function (event) {
      console.info(event)
      let _this = this;
      wx.setClipboardData({
        data: _this.properties.file.filePath,
        success: function(res) {
          wx.showToast({
            title: '图片地址复制成功',
          })
        }
      });

4. Удалить операцию

<image src="images/del.png" bindtap="onDelete"></image>

Дочерний компонент передает данные родительскому компоненту, нажмите, чтобы удалить изображение, чтобы начатьonDeleteметод, черезthis.triggerEvent('del', {fileId}, {});Отправить идентификаторы файла на родительские компоненты

    onDelete: function (event) {
      console.info(event)
      let fileId = this.properties.file.id;
      this.triggerEvent('del', {fileId}, {});
    },

Страница файла родительского компонента привязана к дочернему компонентуdelсобытие

<file-cmp file="{{item}}" bind:del="onDelete"></file-cmp>

перечислитьonDeleteОтправьте сетевой запрос, чтобы завершить логику удаления файлов и обновить список файлов после успешного удаления.

 //删除图片
onDelete(event) {
    console.info("DEL")
    console.info(event)
    let fileId = event.detail.fileId;
    fileModel.delFileById(fileId).then((res) => {
        console.info(res);
        wx.showToast({
                title: '删除成功',
        })
        this.setData({
                pageNum: 1,
                fileList: []
        });
        this._getFileList();
    })
},

моя страница

1. Комментарии и предложения

Апплет имеет свою собственную функцию обратной связи пользователей.buttonПерейти на веб-страницу, пользователи могут оставить соответствующий отзыв,open-typeУстановить какfeedback

<button class="about-btn" plain="true" open-type="feedback">
    <text class="about-btn-text">反馈建议</text>
</button>

2. Обслуживание клиентов мини-программы

апплетbuttonсерединаopen-typeС открытой возможностью включить функцию обслуживания клиентов на общедоступной платформе WeChat, добавить персонал службы поддержки клиентов, добавить кодbuttonПоявится интерфейс чата службы поддержки клиентов,open-typeУстановить какcontact

 <button class="about-btn" plain="true" open-type="contact" bindcontact="handleContact">
    <text class="about-btn-text">联系客服</text>
</button>

3. Совместное использование мини-программ

Здесь используются слоты,buttonсерединаopen-typeУстановить какshare

<btn-cmp open-type="share">
    <image slot="img" src="images/share.png" />
</btn-cmp>

анимация

Мини программа Анимация Официальная документация
Открыть анимацию экрана, установить прозрачность текста от 0 до 1, постепенно отображать, в основном используется дляopacityЧтобы установить прозрачность компонента, сначала создайте анимациюanimationTip, в течение 800 мс, а затем вsetTimeout(function () {})Установите время появления анимации в

 var animationTip = wx.createAnimation({
      //持续时间800ms
      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), 500)

развертывать

  1. Исправлятьutilsв каталогеconfig.apiBaseUrl, Измените его на собственное доменное имя, загрузите его на платформу общедоступной учетной записи WeChat и опубликуйте в управлении версиями.
const config ={
   apiBaseUrl: "你自己的域名或服务器地址"
}

Подробное объяснение фоновых функций

hi-straw

Структура проекта

hi-straw
├── common -- 公共模块
├── config -- 配置模块
├── controller -- controller接口
├── core -- 核心业务模块
|    ├── annontaion -- 注解
|    ├── aop -- aop实现
|    ├── constant -- 常量
|    ├── filter -- 拦截器
|    ├── jwt -- jwt相关
|    └── result -- 结果返回
├── entity -- 实体类
|    ├── dto -- 数据传输
|    └── vo -- 页面传输
├── exception -- 全局异常
├── mapper -- dao层
├── service -- service层
└── util -- 工具类

Дизайн базы данных

CREATE TABLE `t_straw_file` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_id` int(11) UNSIGNED DEFAULT NULL COMMENT '用户ID',
  `file_path` varchar(255) DEFAULT NULL COMMENT '文件路径',
  `file_name` varchar(100) DEFAULT NULL COMMENT '文件名',
  `file_size` varchar(10) DEFAULT NULL COMMENT '文件大小',
  `props` varchar(255) DEFAULT NULL,
  `status` tinyint(5) UNSIGNED DEFAULT '0' COMMENT '0.正常 -1.删除',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT'创建时间',
  PRIMARY KEY (`id`),
    KEY `user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4 COMMENT '用户文件列表';
CREATE TABLE `t_straw_user` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_name` varchar(20) DEFAULT NULL COMMENT '用户名',
  `nickname` varchar(20) DEFAULT NULL COMMENT '用户昵称',
  `user_logo` varchar(250) DEFAULT NULL COMMENT '用户logo',
  `phone_num` varchar(20) DEFAULT NULL COMMENT '手机号',
  `open_id` varchar(55) DEFAULT NULL COMMENT '微信openId',
  `union_id` varchar(20) DEFAULT NULL COMMENT '微信union_id',
  `password` varchar(50) DEFAULT NULL COMMENT '密码',
  `uuid` varchar(20) DEFAULT NULL COMMENT '自定义生成的uuid',
  `last_login` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '最后登陆时间',
	`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
	UNIQUE KEY `open_id` (`open_id`) USING HASH
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COMMENT '用户表';
CREATE TABLE `t_straw_user_file_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) UNSIGNED NOT NULL COMMENT '用户ID',
  `file_size` int(11) UNSIGNED DEFAULT '0' COMMENT '用户文件大小',
  `left_size` int(11) UNSIGNED DEFAULT '5242880' COMMENT '剩余文件大小',
  `total_size` int(11)UNSIGNED DEFAULT '5242880' COMMENT '用户文件空间大小',
  `file_num` int(11) UNSIGNED DEFAULT '0' COMMENT '文件数量',
  `is_vip` tinyint(5) UNSIGNED DEFAULT '0' COMMENT '是否为vip,1.是 0.否',
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_id` (`user_id`) USING HASH
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8mb4 COMMENT'用户文件信息';
CREATE TABLE `t_straw_user_info` (
  `user_id` int(11)  UNSIGNED NOT NULL COMMENT '用户ID',
  `sex` tinyint(5) UNSIGNED DEFAULT NULL COMMENT '性别',
  `location` varchar(55) DEFAULT NULL COMMENT '位置',
  `platform` varchar(55) DEFAULT NULL COMMENT '平台',
  `birthday` datetime DEFAULT NULL COMMENT '生日',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户详细表';

Подробный код

Предварительная подготовка

Загрузка файла в облако Qiniu

кодcom.w77996.straw.util.QiNiuUtil

1. Приложение облачной учетной записи Qiniu

Официальный сайт Qiniu CloudПодать заявку на аккаунт, получитьAccessKey,SecretKey, и установите изображение облака семи коровbucket

    /**
     * 七牛accessKey
     */
    @Value("${QiNiu.accessKey}")
    private String accessKey;
    /**
     * 七牛密钥
     */
    @Value("${QiNiu.secretKey}")
    private String secretKey;
    /**
     * 七牛bucket
     */
    @Value("${QiNiu.bucket}")
    private String bucket;

2. Представлен Qiniu Cloud SDK

pom.xmlИмпорт файлов в Qiniu Cloud Warehouse

    <dependency>
        <groupId>com.qiniu</groupId>
        <artifactId>qiniu-java-sdk</artifactId>
        <version>[7.2.0, 7.2.99]</version>
    </dependency>

2. Генерация токена Qiniuyun

    /**
     * 七牛云生成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", "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"fsize\":$(fsize)}");
        long expireSeconds = 3600;
        String upToken = auth.uploadToken(bucket, key, expireSeconds, putPolicy);
        Map<String, String> resultMap = Maps.newHashMapWithExpectedSize(3);
        resultMap.put("domain", "https://www.w77996.cn");
        resultMap.put("key", key);
        resultMap.put("upToken", upToken);
        return new QiNiuAuth("https://www.w77996.cn", key, upToken);
    }

3. Загрузите код файла

    /**
     * 上传图片
     *
     * @param file
     * @param key
     * @param token
     * @return
     */
    public String uploadImage(MultipartFile file, String key, String token) {
        Configuration cfg = new Configuration(Zone.zone2());
        UploadManager uploadManager = new UploadManager(cfg);
        String filePath = null;
        //生成上传凭证,不指定key的情况下,以文件内容的hash值作为文件名
        Response response = null;
        try {
            byte[] uploadBytes = file.getBytes();
            Auth auth = Auth.create(accessKey, secretKey);
            String upToken = auth.uploadToken(bucket);
            try {
                response = uploadManager.put(uploadBytes, key, upToken);
                //解析上传成功的结果
                DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
                log.info("上传结果 {} {}", putRet.hash, putRet.key);
                filePath = putRet.key;
            } catch (QiniuException ex) {
                try {
                    response = ex.response;
                    log.error(response.bodyString());
                } catch (QiniuException ex2) {
                    //ignore
                    ex.printStackTrace();
                }
            }
        } catch (Exception ex) {
            //ignore
            ex.printStackTrace();
        }
        return filePath;
    }

4. Удалить картинку

    /**
     * 删除图片
     *
     * @param key
     */
    public void delete(String key) {
        Configuration cfg = new Configuration(Zone.zone2());
        Auth auth = Auth.create(accessKey, secretKey);
        //实例化一个BucketManager对象
        BucketManager bucketManager = new BucketManager(auth, cfg);
        try {
            //调用delete方法移动文件
            bucketManager.delete(bucket, key);
        } catch (QiniuException e) {
            //捕获异常信息
            throw new GlobalException(ResultCode.ERROR);
        }
    }

Аннотация + ограничение тока интерфейса АОП

1. Написание аннотации

кодcom.w77996.straw.core.annotation.Limiter

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limiter {

    /**
     *
     * @return
     */
    String value() default "";

    /**
     * 每秒向桶中放入令牌的数量   默认最大即不做限流
     * @return
     */
    double perSecond() default Double.MAX_VALUE;

    /**
     * 获取令牌的等待时间  默认0
     * @return
     */
    int timeOut() default 0;

    /**
     * 超时时间单位
     * @return
     */
    TimeUnit timeOutUnit() default TimeUnit.MILLISECONDS;
}

2. Реализация АОП

кодcom.w77996.straw.core.aop.RateLimitAspect

@Aspect
@Component
@Slf4j
public class RateLimitAspect {

    private RateLimiter rateLimiter = RateLimiter.create(Double.MAX_VALUE);

    /**
     * 定义切点
     * 1、通过扫包切入
     * 2、带有指定注解切入
     */
    @Pointcut("@annotation(com.w77996.straw.core.annotation.Limiter)")
    public void checkPointcut() {
    }

    @ResponseBody
    @Around(value = "checkPointcut()")
    public Object aroundNotice(ProceedingJoinPoint pjp) throws Throwable {
        log.info("拦截到了{}方法...", pjp.getSignature().getName());
        Signature signature = pjp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        //获取目标方法
        Method targetMethod = methodSignature.getMethod();
        if (targetMethod.isAnnotationPresent(Limiter.class)) {
            //获取目标方法的@Limiter注解
            Limiter limiter = targetMethod.getAnnotation(Limiter.class);
            rateLimiter.setRate(limiter.perSecond());
            if (!rateLimiter.tryAcquire(limiter.timeOut(), limiter.timeOutUnit())) {
                log.info("rateLimiter lock");
                return Result.error(ResultCode.BUSY);
            }
        }
        return pjp.proceed();
    }
}

3. Использование аннотации

Лимит может быть вызван только один раз в секунду, если он превышается, он вернетсяResult.error(ResultCode.BUSY)

    @GetMapping("/limit")
    @Limiter(perSecond = 1.0, timeOut = 500)
    public String testLimiter() {
        return " success";
    }

Реализация JWT

1. поколение jwt

Используйте JwtUtil для создания токена jwt

     /**
     * 生成jwt
     *
     * @param userId
     * @return
     */
    public static String createJWT(String userId) {
        String token = JwtHelper.createJWT(userId, Constant.JWT_CLIENT_ID,
                Constant.JWT_NAME, Constant.JWT_EXPIRES_SECOND, Constant.JWT_BASE64_SECRET);
        return token;
    }

2. Токен анализируется в userId

Поместите userId в токен, и вы можете получить Bearer через заголовок запроса при запросе интерфейса.{token},然后对{токен} декодируется для получения идентификатора пользователя.

    /**
     * 通过token获取用户信息
     *
     * @return
     */
    public String getUserIdByToken() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String accessToken = request.getHeader("Authorization");
        if (StringUtils.isEmpty(accessToken) || accessToken.length() < 20) {
            throw new GlobalException(ResultCode.ERROR_TOKEN_NULL);
        }
        accessToken = accessToken.substring(7);
        if ("admin".equals(accessToken)) {
            return "1";
        }
        Claims claims = JwtHelper.parseJWT(accessToken, Constant.JWT_BASE64_SECRET);
        return claims.getSubject();
    }

3. Метод перехватчика + аннотации для аутентификации токена

кодcom.w77996.straw.core.annotation.IgnoreToken
Сначала установите аннотацию, чтобы игнорировать токен

/**
 * @description: 忽略token
 * @author: w77996
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target(value={ElementType.METHOD,ElementType.TYPE})
public @interface IgnoreToken {
}

кодcom.w77996.straw.core.filter.TokenFilter
перехватчикTokenFilterвыполнитьHandlerInterceptor, чтобы перехватывать каждый раз, когда приходит запрос, он будет вызываться перед вызовом контроллера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 {}",request.getRequestURL());
        if (ignoreToken == null) {
            ignoreToken = handlerMethod.getMethodAnnotation(IgnoreToken.class);
        }
        if (ignoreToken != null) {
            log.info("ignoreToken not null");
            return true;
        }
        log.info("ignoreToken  null");
        String token = request.getHeader("Authorization");
        if(token != null){
            log.info("token is {}",token);
            if ("admin".equals(token.substring(7))) {
                return true;
            }
            Claims claims = JwtHelper.parseJWT(token.substring(7), Audience.BASE64SECRET);
            if(claims != null){
                log.info("claims is {} {}",claims.toString(),claims.getSubject());
                return true;
            }else{
                log.info("claims is null");
                throw new GlobalException(ResultCode.ERROR_AUTH);
            }
        }
        return false;
    }

4. Внедрить веб-перехватчик

кодcom.w77996.straw.config.WebMvcAdapterConfigне перехватывать/druid/*Интерфейс

/**
 * @description: web拦截器
 * @author: w77996
 **/
@Component
public class WebMvcAdapterConfig extends WebMvcConfigurationSupport {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TokenFilter()).excludePathPatterns("/druid/*");
    }
}

Конфигурация мониторинга друидов

кодcom.w77996.straw.config.DruidConfig
Доступ после запуска проектаhttp://ip:port/druid, введите аккаунтadminпарольamdinполучить доступ

@Configuration
public class DruidConfig {

    @Bean
    @ConfigurationProperties("spring.datasource")
    public DataSource druidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        return dataSource;
    }

    @Bean
    public ServletRegistrationBean druidStatViewServlet() {
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        // IP白名单 (没有配置或者为空,则允许所有访问)e
        registrationBean.addInitParameter("allow", "");
        // IP黑名单 (存在共同时,deny优先于allow)
        registrationBean.addInitParameter("deny", "");
        registrationBean.addInitParameter("loginUsername", "admin");
        registrationBean.addInitParameter("loginPassword", "admin");
        registrationBean.addInitParameter("resetEnable", "false");
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean druidWebStatViewFilter() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(new WebStatFilter());
        registrationBean.addInitParameter("urlPatterns", "/*");
        registrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*");
        return registrationBean;
    }
}

глобальный перехват исключений

Глобальный перехват исключений в основном зависит от@RestControllerAdviceАннотации, используемые в методах@ExceptionHandler(value = Exception.class)Представляет перехват всех исключений и последующее выполнение соответствующих операций.

@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 全局错误拦截
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    private Result<Object> exceptionHandler(Exception e) {
        if (e instanceof GlobalException) {
            GlobalException ex = (GlobalException) e;
            return Result.error(ex.getCode());
        }
        return Result.error(ResultCode.ERROR.getCode(),e.getMessage());
    }
}

Вход в WeChat

Соответствующие appId и appSec должны быть получены на общедоступной платформе WeChat.После того, как апплет получает код, он отправляется в фоновый режим.Фон получает код и отправляет HTTP-запрос в WeChat.Используется RestTemplate, но следует обратить внимание платная кодировка. Кодировка WeChat возвращает ISO-8859-1. ; После успешного вызова вы можете получить openId пользователя, а затем перейти к базе данных, чтобы получить соответствующую информацию о пользователе, и выполнить логическую обработку обновления входа и создание пользователей

@RestController
@RequestMapping("/wx")
@Slf4j
public class WxController {

    @Autowired
    private IUserService iUserService;

    @Value("${wx.appId}")
    private String wxAppId;

    @Value("${wx.appSec}")
    private String wxAppSec;

    /**
     * 通过code获取openId
     *
     * @param wxLoginDto
     * @return
     */
    @IgnoreToken
    @PostMapping("/code")
    public Result getUserInfoByCode(@RequestBody WxLoginDto wxLoginDto) {
        log.info("enter getUserInfoByCode");
        //微信授权获取openId
        String reqUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=" + wxAppId + "&secret=" + wxAppSec + "&js_code=" + wxLoginDto.getCode() + "&grant_type=authorization_code";
        JSONObject wxAuthObject = RestHttpClient.client(reqUrl, HttpMethod.GET, null);
        log.info("wxAuthObject {}", wxAuthObject.toJSONString());
        WxTokenDto wxTokenDto = JSONObject.parseObject(wxAuthObject.toJSONString(), WxTokenDto.class);
        log.info("wxTokenDto {}", wxTokenDto.toString());
        Map<String, Object> tokenMapper = Maps.newHashMapWithExpectedSize(2);
        //生成新用户
        UserEntity userEntity = iUserService.getUserByOpenId(wxTokenDto.getOpenid());
        if (!ObjectUtils.allNotNull(userEntity)) {
            WxUserInfoDto wxUserInfoDto = new WxUserInfoDto();
            wxUserInfoDto.setNickname(wxLoginDto.getNickName());
            wxUserInfoDto.setUserLogo(wxLoginDto.getUserLogo());
            wxUserInfoDto.setSex(wxLoginDto.getSex());
            wxUserInfoDto.setLastLogin(new Date());
            wxUserInfoDto.setOpenId(wxTokenDto.getOpenid());
            wxUserInfoDto.setLocation(StringUtils.join(new String[]{wxLoginDto.getCountry(), wxLoginDto.getProvince(), wxLoginDto.getCity()}, "-"));
            iUserService.createNewUser(wxUserInfoDto);
            log.info("create new user {}", wxUserInfoDto);
        }
        tokenMapper.put("token", JwtHelper.createJWT(userEntity.getId() + ""));
        return Result.success(tokenMapper);
    }
}

spring boot + мультисредовая упаковка maven

1. файл yml в ресурсе

Среда проекта делится наdevиprodдва,resourceЗагружается по умолчанию под файлapplication.yml.

  • среда разработки: application-dev.yml
  • производственная среда: application-prod.yml существуетapplication.ymlсередина
spring:
    profiles:
      active: @spring.profiles.active@

@spring.profiles.active@Соответствующее находится в профилях в файле pom.xml.spring.profiles.activeАтрибуты

2.pom.xml конфигурация

используется по умолчаниюdevИнформация о конфигурации в среде

    <profiles>
        <profile>
            <id>dev</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <!-- default Spring profiles -->
                <spring.profiles.active>dev</spring.profiles.active>
            </properties>
        </profile>
        <profile>
            <id>prod</id>
            <properties>
                <!-- default Spring profiles -->
                <spring.profiles.active>prod</spring.profiles.active>
            </properties>
        </profile>
    </profiles>

3. Упаковка в различных средах

  • БэйлprodОкружающая среда: выполнитьmvn package -Pprod -DskipTests
  • БэйлdevОкружающая среда: выполнитьmvn package -Pdev -DskipTests

4. Упаковка проекта и нейминг

существуетpropertiesДобавьте формат времени к свойству, а затемbuildдобавлено вfileNameФорматировать имя файла.

    <artifactId>hi-straw</artifactId>
    <version>1.0.0</version>
    <properties>
        ...
        <maven.build.timestamp.format>yyyy-MM-ddHHmm</maven.build.timestamp.format>
    </properties>
    <build>
        ...
        <finalName>
            ${project.artifactId}-${project.version}-${spring.profiles.active}_${maven.build.timestamp}
        </finalName>
    </build>

Банка, созданная после завершения упаковки:hi-straw-1.0.0-prod_2019-04-091533.jar

сценарии оболочки

Войдите на сервер, клонируйте проект на/root/repo_git/каталог, выполнитьscriptкаталог, выполнить./build.sh, нужно бытьRELEASE_HOSTЗамените его адресом своего сервера для упрощения резервного копирования.

#!/bin/sh
set -e
#打包的服务器地址
RELEASE_HOST="你自己的服务器地址"
#打包的环境
RELEASE_ENV=prod
#项目目录
BASE_DIR=/root/repo_git/Histraw
#进入项目目录
cd ${BASE_DIR}
#执行git拉去最新的代码
echo "pulling changes..."
git pull origin master
echo "pulling changes... finish"
echo "building..."
#执行mvn命令打包
mvn clean
mvn package -P${RELEASE_ENV} -DskipTests docker:build
echo "building...finish"
echo "env =${RELEASE_ENV}"
#for HOST in ${RELEASE_HOST}; do
#进行拷贝及备份
RELEASE_TARGET=root@${RELEASE_HOST}:~/release/
echo "copying to $RELEASE_TARGET..."
scp ${BASE_DIR}/target/*.jar ${RELEASE_TARGET}
echo "copying to $RELEASE_TARGET...done"
#done

执行build.sh
воплощать в жизньdocker imagesПосмотреть изображение Docker просто упаковано
docker镜像

развертывание пакета maven + docker

1. установка докерной среды

Удалите старую версию (если она не была установлена, этот шаг можно пропустить):

sudo apt-get remove docker docker-engine docker.io

Установите последний докер:

curl -fsSL get.docker.com -o get-docker.sh
sudo sh get-docker.sh

Подтвердите, что Docker был успешно установлен:

docker run hello-world

2. Скомпилируйте и упакуйте проект

существуетsrc/main/dockerпостроить подdockerFileдокумент

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD *.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

pom.xml настраивает упаковку докеров и взаимодействует со сценарием оболочки для реализации автоматической упаковки докеров maven в linux.

 <!-- Docker maven plugin -->
    <plugin>
        <groupId>com.spotify</groupId>
        <artifactId>docker-maven-plugin</artifactId>
        <version>1.0.0</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 maven plugin -->

воплощать в жизньdocker imagesПросмотр только что упакованного образа докера

воплощать в жизньdocker run --name hi-straw -p 8989:8989 -t hi-strawзагрузочный образ

воплощать в жизньdockers psПросмотр запущенных образов докеров

已启动

nginx настроить https

1. Установите нгинкс

Войдите на сервер, выполните

$ apt-get update // 更新软件
$ apt-get install nginx // 安装nginx

2. Получить сертификат

можешь идтиAlibaba Cloud получает бесплатный сертификат
Поместите сгенерированный сертификат в/etc/nginx/sites-enabled/cert/(Это зависит от того, в какой каталог вы установили nginx)

3. Настройте файл nginx

Создайте новый https.conf

server {
    listen 443;
    server_name 你自己的域名;
    ssl on;
    ssl_certificate  cert/你自己的证书.pem;
    ssl_certificate_key cert/你自己的证书.key;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    location / {
        #项目部署的ip和端口
    	proxy_pass http://localhost:port;

    }
}

После завершения настройки проверьте, доступен ли файл конфигурации nginx.

nginx -t //检查nginx配置文件

После того, как конфигурация будет правильной, перезагрузите файл конфигурации, чтобы конфигурация вступила в силу.

nginx -s reload //使配置生效

Чтобы перезапустить nginx, используйте следующую команду:

service nginx stop //停止
service nginx start //启动
service nginx restart //重启

развертывать

Исправлятьresourceвнизapplication-dev.ymlиapplication-prod.ymlИзмените микроинформацию и облачную информацию Qiniu, которую вы применили для себя, измените адрес базы данных, имя пользователя и пароль.

#七牛
qiNiu:
  accessKey: 你申请的七牛云key
  secretKey: 你申请的七牛云sec
  bucket: 你申请的七牛云bucket
  domain: 你申请的七牛云domain
#微信
wx:
  appId: 你申请的微信id
  appSec: 你申请的微信sec
spring:
  datasource:
    name: graduate
    driver-class-name: com.mysql.jdbc.Driver
    url: 数据库地址
    username: 数据库用户名
    password: 数据库密码

ИсправлятьscriptПод содержаниемbuild.sh

RELEASE_HOST="你自己的服务器地址"

Срок действия сервера проекта истек 15 июля... Какой большой парень будет финансировать сервер, большое спасибо

пожертвовать

请作者喝咖啡