藍天籌項目開發記錄

項目功能分析

  1. 作這個小程序的本意是,我曾經參加過我家鄉的志願者活動,而後加入的志願者組織是家鄉獨自成立的一支javascript

    有着上千名成員的志願者團隊。由於名爲藍天高涼志願服務隊,因此起了名字叫藍天籌,但願能作出一個爲家鄉服務的小程序。css

  2. 首頁顯示的功能:顯示全部(由藍天志願隊的會長或部長髮起的衆籌項目,這樣確保都是通過組織上鑑定和實地考察幫助者的真實性)html

  3. 首頁有根據不一樣類型的排序功能,好比根據不一樣衆籌項目的類型,孤寡老人,貧困學生,留守兒童等。前端

  4. 還有根據衆籌項目的進展進度排序,有未完成,即將完成,已完成,已結束。(根據當前衆籌金額與目標籌集金額作比例運算,而動態修改類型)java

  5. 有根據目標籌集金額的高低排序mysql

  6. 首頁具備上拉到底加載更多的功能。linux

  7. 底部導航欄的第二個爲添加項目(設計爲只能經過管理員帳號登錄實現,確保項目的真實性,必須由志願者組織發起)nginx

  8. 添加項目詳情頁則填寫一些幫助者的信息,詳情,上傳相關圖片。git

  9. 首頁裏點擊具體項目,能跳轉項目詳情頁,能查看項目和幫助者的信息,還能查看照片。web

  10. 在詳情頁具備我要幫幫他的按鈕,(設計爲模擬捐款和留言的功能)

  11. 籌集人的微信頭像和暱稱,還有衆籌金額,留言都會顯示在詳情頁。

技術選型

  1. SSM框架
  2. mysql
  3. linux做爲服務器
  4. 前端是微信小程序

項目流程設計

1. 界面設計

1.1 添加籌集項目的管理員登錄頁

1.1.1 後端用salt+password的方式校驗和存儲密碼

加密:

public class PasswordEncryptor {
    //這是一個自定義的hexDigits,若是黑客不知道這串東西,是不能窮舉破解出來的,知道salt也沒用
    private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5",
            "6", "!", "#", "@", "a", "b", "c", "d", "*", "f", "g", "F"};

    private String salt;    //salt
    private String algorithm;   //散列算法

    public PasswordEncryptor(String salt,String algorithm) {
        this.salt = salt;
        this.algorithm = algorithm;
    }

    //加密
    public String encode(String rawPassword){
        try {
            MessageDigest digest = MessageDigest.getInstance(algorithm);
            return byteArrayToHex(digest.digest(mergePasswordAndSalt(rawPassword).getBytes("UTF-8")));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    //合併salt + password
    private  String mergePasswordAndSalt(String rawPassword){
        if(rawPassword==null){
            rawPassword = "";
        }

        if(salt.equals("")||salt==null){
            return rawPassword;
        }else{
            return rawPassword+"{"+salt+"}";        //用特殊的方式拼接salt
        }
    }

    /**
     * 字節數組轉16進制
     */

    private static String byteArrayToHex(byte[] b){
        StringBuffer stringBuffer = new StringBuffer();
        for(int i=0;i<b.length;i++){
            stringBuffer.append(byteToHex(b[i]));
        }
        return stringBuffer.toString();
    }

    private static String byteToHex(byte b){
        int n = b;
        if(n<0){
            n+=256;
        }
        int d1 = n / hexDigits.length;
        int d2 = n % hexDigits.length;

        return hexDigits[d1]+hexDigits[d2];
    }
    
    //初始的管理員密碼
    public static void main(String[] args) {
        String salt = UUID.randomUUID().toString();
        PasswordEncryptor encoderMd5 = new PasswordEncryptor(salt, "sha-256");
        String encodedPassword = encoderMd5.encode("csyzhanpeng123456");
        System.out.println("加密後密碼:" + encodedPassword + "\n密碼長度:" + encodedPassword.length());
        System.out.println("salt:" + salt);
    }
}

先後端校驗

@Service
public class UserPasswordServiceImpl implements UserPasswordService {
    @Autowired
    private SysAdminMapper sysAdminMapper;

    @Override
    public boolean isValid(String username, String password) {
        SysAdminExample example = new SysAdminExample();
        example.or().andUsernameEqualTo(username);
        List<SysAdmin> admins = sysAdminMapper.selectByExample(example);

        SysAdmin sysAdmin = new SysAdmin();

        //說明找到了這個username,後面就是檢測密碼
        if(admins!=null&&admins.size()!=0){
            sysAdmin = admins.get(0);

            //校驗
            PasswordEncryptor encryptor = new PasswordEncryptor(sysAdmin.getSalt(), "sha-256");
            String encodePassword = encryptor.encode(password);

            if(encodePassword.equals(sysAdmin.getPassword())){
                return true;
            }else{
                return false;
            }
        }else{
            return false;
        }
    }
}

salt+password參考連接:http://www.cnblogs.com/xiaochangwei/p/5942456.html

前端經過存在本地緩存,緩存管理員的登錄態。

經過在page的onShow生命週期,經過判斷緩存,來達到攔截頁面(檢測是否具備權限)

onShow:function(e){
    let that = this;
    wx.getStorage({
      key: 'login_key',
      success: function(res) {
        wx.request({
          url: baseUrl + 'item/itemTypes',
          method: "GET",
          success: function (res) {
            console.log(res);
            that.setData({
              itemTypes: res.data.extend.itemTypes
            })
          }
        })
      },
      fail:function(){
        wx.navigateTo({
          url: '../login/login',
        })
      }
    })

  },
formSubmit: function (e) {
    wx.showLoading({
      title: '登陸中...',
    })
    console.log(e);
    this.setData({ disabled: true });
    wx.request({
      url: baseUrl+"login",
      method:"POST",
      data: {
        username: e.detail.value.no,
        password: e.detail.value.pwd
      },
      header: {
        'content-type': 'application/x-www-form-urlencoded'
      },
      success: function (res) {
        console.log(res);
        if (res.data.code == 200) {
          // 設置本地緩存
          wx.setStorageSync('login_key', res.data.extend.login_key);
          wx.showToast({
            title: "管理員登陸成功",
            icon: 'success',
            duration: 2000
          })
          setTimeout(function () {
            wx.switchTab({
              url: '../add/add',
            })
          }, 2000)
        } else {
          wx.showToast({
            title: "用戶名或密碼錯誤",
            icon: 'none',
            duration: 2000
          })
        }
      }
    })
  },

1.2 登錄後我的信息填寫頁

1.3 籌款項目首頁展現

1.3.1 輪播圖(僅宣傳用)

1.3.2 多選下拉菜單的實現

功能:三級級聯菜單 項目類型 + 項目進度狀況 + 按目標籌集金額從低到高(從高到低)

總結:

  1. 下拉小按鈕

    /* 這裏的icon用border渲染出來,而不是用字體圖標 */
    
    .icon{
      margin-left: 10rpx;
      margin-top: 16rpx;
      display: inline-block;
    
      border: 10rpx solid transparent;
      border-top: 10rpx solid #666;
    }

效果:

參考連接:http://www.javashuo.com/article/p-qleixhvw-eg.html

  1. 下拉多選菜單

    思路:用大view包小view,經過點擊狀態和 標誌記錄位來 打開(使下拉菜單顯示), 當選中其中一個或者再點

    一次按鈕時,將會切換打開狀態或者關閉下拉菜單

    核心:切換 ,記錄打開狀態位,經過點擊事件進行狀態位切換(這裏的狀態位爲下拉導航索引)。前端根據狀態位判斷是否渲染出來

    data:{
        //籌集項目列表
        items:[],
        // 籌集項目類型
        itemTypes:[],
    
        // 進度類型
        processTypes:[],
    
        //排序類型
        sortTypes:[
          { id: 1, sortTypeName:"按目標金額從低到高"},
          { id: 2, sortTypeName: "按目標金額從高到低"}
        ],
    
        // 選中的項目類型
        selectItemType:-1,
    
        // 選中的進度類型
        selectProcessType:-1,
    
        // 選中的排序類型
        selectSortType:-1,
    
        // 顯示下拉菜單導航索引
        showNavIndex:0,
    
        // 各種型菜單打開(滑動))狀態
        itemTypeOpen:false,
    
        processOpen:false,
    
        sortOpen:false

    這裏只寫其中的項目下拉菜單的顯示

    下拉菜單觸發按鈕

    <view class="nav-child" bindtap='listItemType' data-nav="1">
          <view class='content'>項目類型</view>
          <view class="icon"></view>
        </view>

    下拉菜單條目

    <view class="itemTypeMenu {{itemTypeOpen? 'slidown':'slidup'}}"  
            wx:if='{{showNavIndex==1}}' wx:for-index="index">
        <view class="itemType" bindtap='selectItemType' data-itemTypeId='-1'>
          不限
        </view>
        <view class="itemType {{selectItemType==(index+1)?'highlight':''}}" wx:for="{{itemTypes}}" wx:for-item="itemType" wx:key="itemTypeId"
          data-itemTypeId="{{itemType.itemTypeId}}"
          bindtap='selectItemType'
        >
          {{itemType.itemTypeName}}
        </view>
      </view>

    點擊事件的處理邏輯:

    listItemType:function(e){
        console.log(this.data.itemTypeOpen)
        // 若是已經打開了,再按一次就是關閉。
        if (this.data.itemTypeOpen){
          this.setData({
            itemTypeOpen: false,
            showNavIndex:0
          })
        }else{
          this.setData({
            itemTypeOpen: true,
              //切換 要顯示的菜單導航
            showNavIndex:e.currentTarget.dataset.nav
          }) 
        }
      },

    選中事件的處理邏輯:

    selectItemType: function (e) {
        console.log(e.currentTarget)
        // 注意;這裏的data-傳過來的屬性會自動換成小寫
        let id = e.currentTarget.dataset.itemtypeid;
    
        if (id == -1) {
          this.setData({
            // 不限,也就是提交的這個篩選條件爲空
            selectItemType: -1,
            itemTypeOpen: false,
            showNavIndex: 0
          })
        } else {
          this.setData({
    
            selectItemType: id,
            itemTypeOpen: false,
            showNavIndex: 0
          })
        }
    
        let that = this;
    
        // 找出符合條件的items
        that.request_findItem(that);
      },

    根據條件,查詢符合的項目

    // 封裝一個根據條件查詢得請求函數
      request_findItem:function(that){
        wx.request({
          url: baseUrl + 'item/findItems',
          data: {
            itemType: that.data.selectItemType,
            itemProcessType: that.data.selectProcessType,
            sortType: that.data.selectSortType
          },
          method: "GET",
          success: function (res) {
            that.setData({
              items: res.data.extend.items
            })
          }
        })
      }

    總結:

    1. css就不貼出來了,我選擇了調一下margin,讓菜單條目對應菜單導骯,以及選中高亮

    選中高亮的實現原理:判斷Id與列表渲染的索引index是否匹配。若是匹配了就渲染高亮class

    <view class="itemType 
                     {{selectItemType==(index+1)?'highlight':''}}" 
              wx:for="{{itemTypes}}" wx:for-item="itemType" wx:key="itemTypeId"
          data-itemTypeId="{{itemType.itemTypeId}}"
          bindtap='selectItemType'
        >
    1. 不設置篩選條件,菜單條目爲 不限,經過設置爲-1來讓後端根據-1 作條件的判斷

      @Override
          public List<RaiseItem> getItemsByType(FindItemDTO dto) {
              RaiseItemExample example = new RaiseItemExample();
      
              RaiseItemExample.Criteria criteria = example.or();
      
              Integer itemType = dto.getItemType();
              Integer itemProcessType = dto.getItemProcessType();
              if(itemType!=-1){
                  criteria.andItemTypeEqualTo(itemType);
              }
      
              if(itemProcessType!=-1){
                  criteria.andItemProcessTypeEqualTo(itemProcessType);
              }
      
              Integer sortType = dto.getSortType();
      
              if(sortType!=-1){
                  if(sortType.equals(1)){
                      example.setOrderByClause("raise_target ASC");
                  }else if(sortType.equals(2)){
                      example.setOrderByClause("raise_target DESC");
                  }
              }
              return raiseItemMapper.selectByExample(example);
          }

      效果演示:

1.3.3 列表每一個條目的設計

1.3.4 上拉加載更多的分頁實現

參考連接:https://blog.csdn.net/u012927188/article/details/73369201/

思路 :

  1. 後端返回分頁數據

  2. 小程序:

    1. 加載更多組件

       



      ​ ```

    /* 上拉加載更多 */
    
       .weui-loading {
         margin: 0 5px;
         width: 20px;
         height: 20px;
         display: inline-block;
         vertical-align: middle;
         -webkit-animation: weuiLoading 1s steps(12, end) infinite;
         animation: weuiLoading 1s steps(12, end) infinite;
           /* base64的格式 */
         background: transparent url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMDB2MTAwSDB6Ii8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTlFOUU5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTMwKSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iIzk4OTY5NyIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgzMCAxMDUuOTggNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjOUI5OTlBIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDYwIDc1Ljk4IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0EzQTFBMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NSA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNBQkE5QUEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDU4LjY2IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0IyQjJCMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgxNTAgNTQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjQkFCOEI5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDMkMwQzEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTE1MCA0NS45OCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDQkNCQ0IiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTEyMCA0MS4zNCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNEMkQyRDIiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDM1IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0RBREFEQSIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgtNjAgMjQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTJFMkUyIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKC0zMCAtNS45OCA2NSkiLz48L3N2Zz4=) no-repeat;
         background-size: 100%;
       }
       .weui-loadmore {
         width: 65%;
         margin: 1.5em auto;
         line-height: 1.6em;
         font-size: 14px;
         text-align: center;
       }
    
       .weui-loadmore__tips {
         display: inline-block;
         vertical-align: middle;
       }
    1. 微信小程序 自帶的觸底函數
    onReachBottom:function(){
           let that = this;
           // 模擬延時,顯示加載更多
             // wx.request({
             //   url: baseUrl+'',
             // })
             // that.setData({
    
             // })
           let isLastPage = that.data.pageInfo.isLastPage;
    
           // 不是最後一頁,纔要請求分頁
           if (!isLastPage){
             setTimeout(() => {
               // 判斷一下這個觸底是常規觸底,仍是帶着條件的觸底事件
               let f1 = that.data.selectItemType;
               let f2 = that.data.selectProcessType;
               let f3 = that.data.selectSortType;
               if (f1 != -1 || f2!=-1 || f3!=-1){
                 // 帶條件查詢 (其實帶條件和不帶條件其實在後端能夠合併爲一個接口的))
                 wx.request({
                   url: baseUrl + 'item/findItems',
                   data: {
                     itemType: that.data.selectItemType,
                     itemProcessType: that.data.selectProcessType,
                     sortType: that.data.selectSortType,
                     pn: that.data.pageInfo.pageNum + 1
                   },
                   method: "GET",
                   success: function (res) {
                     let oldItems = that.data.items;
                     let newItems = res.data.extend.pageInfo.list;
                     let pageInfo = res.data.extend.pageInfo;
                     // concat拼接後返回一個新的數組
                     newItems = oldItems.concat(newItems);
                     that.setData({
                       pageInfo: pageInfo,
                       items: newItems,
                       isHideLoadMore: pageInfo.isLastPage ? true : false
                     })
                   }
                 })
               }else{
                  // 不帶條件查詢
                 wx.request({
                   url: baseUrl + 'item/all',
                   data: {
                     pn: that.data.pageInfo.pageNum + 1
                   },
                   method: "GET",
                   success: function (res) {
                     let oldItems = that.data.items;
                     let newItems = res.data.extend.pageInfo.list;
                     // concat拼接後返回一個新的數組
                     newItems = oldItems.concat(newItems);
                     let pageInfo = res.data.extend.pageInfo;
                     that.setData({
                       pageInfo: pageInfo,
                       items: newItems,
                       isHideLoadMore: pageInfo.isLastPage ? true : false
                     })
                   }
                 })
               }
             }, 1000);
           }
         }
  3. 後端返回分頁

    //    獲取全部的項目基本信息
        @RequestMapping(value = "/all",method = RequestMethod.GET)
        @ResponseBody
        public Msg getAllItems(@RequestParam(value = "pn",defaultValue = "1") Integer page_num){
            PageHelper.startPage(page_num,5);
    
            List<RaiseItem> items = raiseItemService.getAllItems();
    
            PageInfo<RaiseItem> pageInfo = new PageInfo<>(items);
            return Msg.success().add("pageInfo",pageInfo);
        }
    //    根據類型來查詢符合的項目
        @RequestMapping(value = "/findItems",method = RequestMethod.GET)
        @ResponseBody
        public Msg findItems(@RequestParam(value = "pn",defaultValue = "1") Integer page_num,FindItemDTO dto){
    //        開始分頁
            PageHelper.startPage(page_num,5);
    
            List<RaiseItem> items = raiseItemService.getItemsByType(dto);
    
            PageInfo<RaiseItem> pageInfo = new PageInfo<>(items);
    
            return Msg.success().add("pageInfo",pageInfo);
        }

    效果:

加載完成後:

1.3.5 新增籌款項目

  1. 普通表單UI

  2. 文件(圖片)上傳UI

  3. 長按圖片刪除的操做

    參考連接:https://www.jb51.net/article/140388.htm

    參考JS語法:

    splice() 方法向/從數組中添加/刪除項目,而後返回被刪除的項目。

    註釋:該方法會改變原始數組。

    語法

    arrayObject.splice(index,howmany,item1,.....,itemX)
    參數 描述
    index 必需。整數,規定添加/刪除項目的位置,使用負數可從數組結尾處規定位置。
    howmany 必需。要刪除的項目數量。若是設置爲 0,則不會刪除項目。
    item1, ..., itemX 可選。向數組添加的新項目。

    返回值

    類型 描述
    Array 包含被刪除項目的新數組,若是有的話。
    data: {  
            Data: [{id:0,value:'a',name:'A' },{id:1,value:'b',name:'B' }], 
            Index: 0,
          currentId
        },

    對象數組的下拉菜單的使用

    <picker class="picker" bindchange="bindChange" value="{{Index}}"  range="{{Data}}" range-key="name">
        <view >
          當前選擇:{{Data[Index].name}}
        </view>
    </picker>
    itemTypePickerChange:function(e){
    
       this.setData({
    
         Index:e.detail.value
    
       })
    
      },

1.4 籌款項目詳情頁

  1. 編寫基礎的wxml,wxss,js,獲取item表和detail表的基礎信息,佈局用flex嵌套flex合理佈局

    效果以下:

  1. 這個按鈕懸浮固定在底部的實現:

    ```css
    /* 按鈕固定高爲46px */
    .detail_box{
    margin-bottom: 92rpx;
    }

    /* 固定在底部,這樣能夠避免內容區的內容過多,讓按鈕一直看不到 */
    .chou_button{

z-index: 999;
    position: fixed;
    bottom: 0;
    width: 100%;
  }
  ```

1.4.1 模仿樣式

1.4.2 項目進展(含進度條,時間線+圖片+留言)

1.4.3 參與籌款人的名單+留言

效果:

<!-- 籌集人的名單列表 -->
  <view class="raise_person_box">
    <view class="raise_person_title">
    捐助人名單
  </view>
    <view wx:for="{{persons}}" wx:for-key="person.item_person_id" wx:for-item="person" class="raise_person_item">
      <view class="raise_person_item_left">
        <view class="index_pic">
          <image src="{{person.userAvatarUrl}}"></image>
        </view>
      </view>
      <view class="raise_person_item_right">
        <view class="raise_person_item_right_top">
          {{person.userNickName}}
        </view>
        <view class="raise_person_item_right_mid">
          支持了: <text class="mid_money">{{person.raiseMoney}} 元</text>
        </view>
        <view class="raise_person_item_right_bottom">
          {{person.comment}}
        </view>
        <view class="raise_person_item_right_time">
          {{person.raiseTime}}
        </view>
      </view>
    </view>
  </view>

總結:html頁面用了flex嵌套佈局吧

js部分:onShow()用於獲取捐助人名單+留言信息

wx.request({
      url: baseUrl + 'item/person',
      data:{
        itemId: that.data.itemId,
      },
      method: "GET",
      success: function (res) {

        //調用 處理留言時間的函數,修改返回的數據
        let list = res.data.extend.pageInfo.list;
        for (let i=0;i<list.length;i++){
          let last_time = timeHandle(list[i].raiseTime)
          list[i].raiseTime = last_time;
        }

        that.setData({
          persons: list,
        })
      }
    })

此處要提的是一個特殊的經常使用需求:就是根據返回的時間戳計算出幾天前,幾個月前,又或者是具體的月份,年份

js部分:用了一個專門的函數放在單獨的js文件,放在utils目錄下,被其餘的js import引入使用

function commentTimeHandle(dateStr) {
  // dateStr = 2018-09-06 18:47:00" 測試時間
   //獲取dataStr的秒數  打印結果--1536230820000
  var publishTime = dateStr / 1000, 
    date = new Date(publishTime * 1000), //獲取dateStr的標準格式 console.log(date) 打印結果  Thu Sep 06 2018 18:47:00 GMT+0800 (中國標準時間)
    // 獲取date 中的 年 月 日 時 分 秒
    Y = date.getFullYear(),
    M = date.getMonth() + 1,
    D = date.getDate(),
    H = date.getHours(),
    m = date.getMinutes(),
    s = date.getSeconds();
  // 對 月 日 時 分 秒 小於10時, 加0顯示 例如: 09-09 09:01
  if (M < 10) {
    M = '0' + M;
  }
  if (D < 10) {
    D = '0' + D;
  }
  if (H < 10) {
    H = '0' + H;
  }
  if (m < 10) {
    m = '0' + m;
  }
  if (s < 10) {
    s = '0' + s;
  }
  // console.log("年", Y); // 年 2018
  // console.log("月", M); // 月 09
  // console.log("日", D); // 日 06
  // console.log("時", H); // 時 18
  // console.log("分", m); // 分 47
  // console.log("秒", s); // 秒 00

  //獲取此時此刻日期的秒數
  var nowTime = new Date().getTime() / 1000, 
    diffValue = nowTime - publishTime,  // 獲取此時 秒數 與 要處理的日期秒數 之間的差值

    // 一天86400秒 獲取相差的天數 取整
    diff_days = parseInt(diffValue / 86400),    
    
    // 一時3600秒
    diff_hours = parseInt(diffValue / 3600),    
    diff_minutes = parseInt(diffValue / 60),
    diff_secodes = parseInt(diffValue);

  if (diff_days > 0 && diff_days < 3) {  //相差天數 0 < diff_days < 3 時, 直接返出
    return diff_days + "天前";
  } else if (diff_days <= 0 && diff_hours > 0) {
    return diff_hours + "小時前";
  } else if (diff_hours <= 0 && diff_minutes > 0) {
    return diff_minutes + "分鐘前";
  } else if (diff_secodes < 60) {
    if (diff_secodes <= 0) {
      return "剛剛";
    } else {
      return diff_secodes + "秒前";
    }
  } else if (diff_days >= 3 && diff_days < 30) {
    return M + '-' + D + ' ' + H + ':' + m;
  } else if (diff_days >= 30) {
    return Y + '-' + M + '-' + D + ' ' + H + ':' + m;
  }
}
module.exports = {
  timeHandle: commentTimeHandle
}

如何使用:在JS裏引入這個JS文件的函數

import { timeHandle } from '../../utils/timehandle';

分頁後端:

//    獲取籌集人列表
    @RequestMapping(value = "/person",method = RequestMethod.GET)
    @ResponseBody
    public Msg getPersons(@RequestParam(value = "pn",defaultValue = "1")Integer page_num, Integer itemId){
        PageHelper.startPage(page_num,5);
        List<RaiseItemPerson> persons = raiseItemService.getRaisePersons(itemId);
        PageInfo<RaiseItemPerson> pageInfo = new PageInfo<>(persons);

        return Msg.success().add("pageInfo",pageInfo);
    }

1.4.4 參與衆籌的按鈕(涉及到微信支付,暫時沒法完成。能夠模擬)

1.4.4.1 獲取微信用戶ID,頭像,暱稱

​ 總結:

  1. 經過微信最新官方文檔,用button標籤,設置open-type屬性,而後綁定指定的事件,能夠在JS中

​ 獲取到用戶頭像,暱稱 (可在一個按鈕綁定兩個事件,一個用來獲取用戶信息,一個用來發出請求)

<button open-type='getUserInfo' type='primary' bindgetuserinfo="bindGetUserInfo" bindtap='donate'>我要幫幫他</button>
//獲取用戶信息
  bindGetUserInfo: function (e) {
    console.log(e.detail.userInfo)
    this.setData({
      userNickName: e.detail.userInfo.nickName,
      userAvatarUrl: e.detail.userInfo.avatarUrl
    })
  }
1.4.4.2 模擬支付頁面的模態框

效果:

總結:就是經過按鈕點擊切換模態框的顯示,而後在模態框裏模擬微信支付功能以及添加留言

<!-- modal支付模態框 -->
    <modal id="modal" hidden="{{hiddenmodal}}" title="支付頁面" confirm-text="肯定" cancel-text="取消" bindcancel="cancel" bindconfirm="confirm">  
      <text style="font-weight:bolder;font-size:35rpx">捐助金額:</text> <input type='text' placeholder="請填寫資助金額" class='weui-input' bindinput="bindKeyInput" auto-focus/>
      <text style="font-weight:bolder;font-size:35rpx">留言:</text> <input type='text' placeholder="留言" class='weui-input brief_description' bindinput="bindKeyComment"></input>
  </modal>
confirm:function(){
    let openId = getApp().globalData.openId;
    console.log("openId: " + openId);

    let that = this;
    wx.request({
      url: baseUrl+'item/donate',
      data:{
        donate_money: that.data.donate_money,
        itemId: that.data.itemId,
        comment: that.data.comment,
        openId: openId,
        userNickName:that.data.userNickName,
        userAvatarUrl: that.data.userAvatarUrl
      },
      method:"POST",
      header:{
        "content-type": "application/x-www-form-urlencoded"
      },
      success:function(res){
        that.setData({
          comment: "",
          donate_money: "",
          hiddenmodal: true,
          hiddenmodal:true
        })
        // 發起請求
        wx.request({
          url: baseUrl + 'item/detail',
          data: {
            itemId: that.data.itemId
          },
          success: function (res) {
            if (res.data.code == 200) {
              that.setData({
                currentTarget: res.data.extend.detail.currentTarget,
                raisePersonNum: res.data.extend.detail.raisePersonNum,
              })
            }
          }
        })
      }
    })
  }
cancel:function(){
    this.setData({
      donate_money:"",
      comment:"",
      hiddenmodal: true,
    })
  },
      
      //此處省略其餘input的處理事件

1.5 新增籌款項目填寫頁

總結:

// 發送首頁圖片。對應首頁圖片的處理
    wx.uploadFile({
      url: baseUrl + 'item/imageIndex',
      filePath: files[0],
      name: 'img',
      header:{
        'content-type':'application/json'
      },
      success: function (res) {
        
        console.log("res: "+res.data);
        // 微信小程序 uploadFile的坑是接收的是JSON字符串,不會幫你自動轉JS對象。因此須要本身解析data
        let data = JSON.parse(res.data);
        

        //獲取返回值
        that.setData({
          server_file_index: data.extend.file_index_path
        })
      }
    })

點擊提交按鈕,圖片如何處理

  1. 分紅兩個接口,一個是首頁圖片,另外一個是詳情多個圖片urls.把文件名存放在數據庫中

    add_submit:function(){
        let that = this;
    
        let item_index = that.data.Index;
    
    
        // 先上傳圖片,後端處理成功後(經過返回值包含了首頁圖片路徑,
        //以及多個圖片展現的路徑)回調進行insert
        let files = that.data.files;
    
        // 發送首頁圖片。對應首頁圖片的處理
        wx.uploadFile({
          url: baseUrl + 'item/imageIndex',
          filePath: files[0],
          name: 'img',
          header:{
            'content-type':'application/json'
          },
       success: function (res) {
    
              console.log("res: " + res.data);
              // 微信小程序 uploadFile的坑是接收的是JSON字符串,不會幫你自動轉JS對象。因此須要本身解析data
              let data = JSON.parse(res.data);
    
    
              //獲取返回值
              that.setData({
                server_file_index: data.extend.file_index_path
              })
    
    
              //等server_file_index成功獲取後再執行下面的add操做
              let i;
              //循環發送多個詳情的圖片
              for (i = 1; i < files.length; i++) {
                // 採用閉包,保證索引值正確
                (function (i) {
                  //調用promise處理異步
                  that.GetImage(i, that).then((index) => {
                    //最後一張處理完成
                    if (that.data.server_detail_files.length == (that.data.files.length - 1)) {
                      console.log("開始執行提交add");
                      console.log("index: " + index);
                      console.log("server_detail_file:" + that.data.server_detail_file);
                      // 提交插入請求
                      wx.request({
                        url: baseUrl + '/item/add',
                        method: 'POST',
                        header: {
                          "content-type": "application/x-www-form-urlencoded"
                        },
                        data: {
                          targetPerson: name,
                          itemDescription: description,
                          raiseTarget: money,
                          itemType: that.data.itemTypes[that.data.Index].itemTypeId,
                          createTime: date,
                          description: detail_description,
    
                          picIndexUrl: that.data.server_file_index,
                          picDetailUrls: that.data.server_detail_files.join(',')
                        },
                        success: function (res) {
                          if (res.data.code == 200) {
                            // 清空
                            that.setData({
                              targetPerson: "",
                              itemDescription: "",
                              raiseTarget: "",
                              Index: 0,
                              date: "",
                              detail_description: "",
                              server_file_index: "",
                              server_detail_files: "",
                              files: ""
                            })
                            wx.switchTab({
                              url: '/pages/index/index',
                            })
                          }
    
                        }
                      })
                    }
                  });
                })(i)
              }
            }
    
        }

    基礎補習之閉包

    由於for循環,的索引index不會從1,2,3這樣,而是執行完了,顯示最後一個索引值。須要閉包控制一下。

演示:

文本測試:

18歲花季少女突發心臟病。急需救助!

小紅成績優異,家裏經濟貧困,在石鼓鎮。父母殘疾,只能在家裏下田。小紅下課後就回家作飯作菜給他們吃,本身暑假出去打工賺學費。學校老師說她的成績很是好,是年級前三的學生,模擬成績極可能考上211學校。

該案例已通過藍天志願組織實地考察,經多名志願者覈實,狀況屬實。但願你們能給予幫助,奉獻大愛。

1. 微信小程序 uploadFile的坑是接收的是JSON字符串,不會幫你自動轉JS對象。因此須要本身解析data

  1. for循環裏有異步請求,想要for裏面的異步請求都執行完再執行其餘的怎麼作?

    參考連接:http://www.javashuo.com/article/p-dgjyuqfo-kn.html

    異步請求:

    //   promise
      GetImage:function(i,that){
        console.log("當前循環:"+i);
        return new Promise(function (resolve, reject) {
          wx.uploadFile({
            url: baseUrl + '/item/images',
            filePath: that.data.files[i],
            name: 'img',
            success: (res) => {
              // console.log("這是第"+i+"次循環")
              console.log(that.data);
    
              //先拿到舊的
              var server_detail_files = that.data.server_detail_files;
              console.log("server_detail_files" + server_detail_files);
    
              //服務端返回的
              let data = JSON.parse(res.data);
              let files_detail_path = data.extend.files_detail_path;
              console.log("files_detail_path:" + files_detail_path)
    
              //若是是拼的第一個,加入數組
              console.log("server_detail_files:" + server_detail_files)
              //push是在原數組上操做,並返回新的長度。
              server_detail_files.push(files_detail_path);
              //獲取返回值
              that.setData({
                server_detail_files: server_detail_files
              })
              resolve(server_detail_files);
            }
          })
        })
      }

    for循環裏異步,而且經過判斷i==要執行下一步的值去執行add請求

    //循環發送多個詳情的圖片
          for (i = 1; i < files.length; i++) {
            // 採用閉包,保證索引值正確
            (function (i) {
                //調用promise處理異步
              that.GetImage(i,that).then(()=>{
                //最後一張處理完成
                console.log("i: "+i);
                  //在then裏判斷是不是最後一張圖片,從而達到完成全部的for循環後再執行這個提交插入的請求
                if (that.data.server_detail_files.length == (that.data.files.length-1))              {
                  // 提交插入請求
                  wx.request({
                    url: baseUrl + '/item/add',
                    method: 'POST',
                    header:{
                      "content-type":"application/x-www-form-urlencoded"
                    },
                    data: {
                      targetPerson: name,
                      itemDescription: description,
                      raiseTarget: money,
                      itemType: that.data.itemTypes[that.data.Index].itemTypeId,
                      createTime: date,
                      description: detail_description,
    
                      picIndexUrl: that.data.server_file_index,
                      picDetailUrls: that.data.server_detail_files.join(',')
                    },
                    success: function (res) {
                      if (res.code == 200) {
                        // 清空
                        that.setData({
                          targetPerson: "",
                          itemDescription: "",
                          raiseTarget: "",
                          Index: 0,
                          date: "",
                          detail_description: "",
                          server_file_index: "",
                          server_detail_files: "",
                          files: ""
                        })
                        wx.navigateTo({
                          url: 'pages/index/index',
                        })
                        return;
                      }
    
                    }
                  })
                }
              });
            })(i)
          }
  2. 若是微信小程序使用post請求,後端沒數據的話,說明小程序沒有設置header爲

header: {
          'content-type': 'application/json'
        },

2. 需求分析

3. 數據庫設計

PowerDesigner的使用

安裝: https://blog.csdn.net/sinat_34104446/article/details/79885141

3.1 籌款項目表

sys_admin:

id username password salt
1 zhanp @gd5@a6#ca1f5b@30@3a@2bcc#5F0b0f40@f@5a6@1!a4a5b6b0F1#b1!0a1cfa2 d4171b48-fca9-45b1-9bb7-716ea057aa25

raise_item:

item_id target_person raise_target current_target raise_person_num pic_index_url item_description item_type_id item_process_type_id
1 小江 5000 1000 60 http://localhost/image/po1.jpg xxxx加油,打敗病魔 1 1
2 小洋 6000 2000 70 http://localhost/image/po2.jpg xxx加油,努力讀書 2 2

3.2 籌集項目進度類型表

item_process_type

item_process_type_id item_process_type_name
1 未完成
2 即將完成
3 已完成
4 已結束

(已結束是時間已過,該項目取消籌款了)

3.3 籌集項目類型表

raise_item_type

item_type_id item_type_name
1 孤寡老人
2 貧困學生
3 留守兒童
4 患病在身
5 其餘

3.4 籌集項目詳情表

raise_item_detail

item_detail_id item_id description pic_detail_urls create_time

3.5 籌集項目進展表

raise_item_process (一個項目能夠有屢次進展)

item_process_id item_id pic_process_urls description
1 1
2 1

3.6 籌集項目捐助人表

raise_item_person

item_person_id item_id user_avatar_url user_nick_name raise_money comment raise_time open_id

後續還有排行榜

4. 後臺設計

4.1 用戶管理

4.2 角色管理

4.3 權限管理

5. 接口編寫

項目編寫流程

1. Mysql數據庫的準備

2. SSM環境搭建

單元測試模擬數據的過程當中遇到的bug

<!--在spring單元測試中,因爲引入validator而致使的Tomcat7及如下的EL表達式版本不一致-->
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-el-api</artifactId>
    <version>8.5.24</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jasper-el</artifactId>
    <version>8.5.24</version>
    <scope>provided</scope>
</dependency>

報錯:

解決方法:由於@ResponseBody,可是底層的jackson忘記引入了

<!--jackson支持-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>

3. 日誌

<context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>classpath*:log/log4j.properties</param-value>
    </context-param>
    <listener>
        <description>log4j</description>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>

3.小程序的錯誤提示

必定要仔細看報錯的部分,會顯示哪一行報錯,不要本身瞎找。否則改一天你都不知道哪裏錯。

指示add.js 221行錯了

測試:

部署問題

爲何服務器端的mysql一直連不上去?

由於root只容許localhost訪問,因此要修改。

別忘了flush一下

成功:

linux下mysql忘記密碼怎麼辦

前言

今天在服務器安裝mysql以後,登陸發現密碼錯誤,可是我沒有設置密碼呀,最後百度以後得知,mysql在5.7版本以後會自動建立一個初始密碼。
報錯以下:

[root@mytestlnx02 ~]# mysql -u root -p
Enter password: 
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)

修改密碼

1. 檢查mysql服務是否啓動,若是啓動,關閉mysql服務

//查看mysql服務狀態
[root@mytestlnx02 ~]# ps -ef | grep -i mysql
root     22972     1  0 14:18 pts/0    00:00:00 /bin/sh /usr/bin/mysqld_safe --datadir=/var/lib/mysql --socket=/var/lib/mysql/mysql.sock --pid-file=/var/run/mysqld/mysqld.pid --basedir=/usr --user=mysql
mysql    23166 22972  0 14:18 pts/0    00:00:00 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib/mysql/plugin --user=mysql --log-error=/var/log/mysqld.log --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/lib/mysql/mysql.sock
root     23237 21825  0 14:22 pts/0    00:00:00 grep -i mysql

//關閉服務
[root@mytestlnx02 ~]# service mysql stop
[root@mytestlnx02 ~]#

2. 修改mysql的配置文件my.cnf

my.cnf`配置文件的位置,通常在`/etc/my.cnf`,有些版本在`/etc/mysql/my.cnf

在配置文件中,增長2行代碼

[mysqld]

skip-grant-tables

做用是登陸mysql的時候跳過密碼驗證

而後啓動mysql服務,並進入mysql

[root@mytestlnx02 ~]# service mysqld start
[root@mytestlnx02 ~]#
[root@mytestlnx02 ~]# mysql -u root 
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
 
mysql>

3. 修改密碼

鏈接mysql這個數據庫,修改用戶密碼

mysql> use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
 
Database changed
mysql> update mysql.user set authentication_string=password('root_password') where user='root';
Query OK, 1 row affected, 1 warning (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 1
 
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
 
mysql> exit

4. 重啓mysql服務

先將以前加在配置文件裏面的2句代碼註釋或刪除掉,而後重啓mysql服務,就可使用剛剛設置的密碼登陸了。

[root@mytestlnx02 ~]# service mysql start
[root@mytestlnx02 ~]#
[root@mytestlnx02 ~]# mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.

p.s.

CentOS上的操做方式有所不一樣。

執行修改密碼的命令一直報錯

mysql> update user set authentication_string=password('xxxxxxxx') where User='root';       
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '('root_password') where User='root'' at line 1

不多是語法問題,檢查了不少遍,最後發現CentOS下應該這樣操做:

查看初始密碼

[root@VM_0_8_centos ~]# grep 'temporary password' /var/log/mysqld.log
2018-09-26T04:25:54.927944Z 5 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: DN34N/=?aIfZ

能夠看到初始密碼爲DN34N/=?aIfZ

使用初始密碼登陸

[root@VM_0_8_centos ~]# mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.12 MySQL Community Server - GPL

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

修改密碼

mysql> ALTER USER 'root' IDENTIFIED BY 'xxxxxxxxx';  
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.
mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'xxxxxxxx';
Query OK, 0 rows affected (0.11 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.01 sec)

mysql> exit
Bye

重啓服務就生效了

[root@VM_0_8_centos ~]# service mysqld stop 
Redirecting to /bin/systemctl stop  mysqld.service
[root@VM_0_8_centos ~]# service mysqld start
Redirecting to /bin/systemctl start  mysqld.service

部署到服務器上的Mysql鏈接參數

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://xxxx/sky_chou?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=xxxxx

記住:必定不要添加useSSL=true這種配置信息,否則會SQL報錯

改爲localhost,否則不會識別服務器的ip。!

升級到HTTPS

經過nginx升級到HTTPS

  1. 要去購買的雲服務器上下載SSL證書

  1. 把nginx的SSL證書複製到linux服務器上的nginx的conf目錄下

  1. 修改nginx.conf文件

    #HTTPS server
    
    server {
        listen       443;
        server_name  你的域名;
    
        ssl                  on;
        ssl_certificate      xxxx_bundle.crt;
        ssl_certificate_key  xxx.key;
    
        ssl_session_timeout  5m;
    
        ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers  ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
        ssl_prefer_server_ciphers   on;
    
        location / {
              client_max_body_size    16m;
              client_body_buffer_size 128k;
              proxy_pass                          http://127.0.0.1:9999/;
              proxy_set_header        Host $host;
              proxy_set_header        X-Real-IP $remote_addr;
              proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header           X-Forwarded-Proto https;
              proxy_next_upstream   off;
    
              proxy_connect_timeout   30;
              proxy_read_timeout      300;
              proxy_send_timeout      300;
        }
    }

    要着重修改的SSL相關地方:

    ssl                  on;
        ssl_certificate     xxxxx.crt;
        ssl_certificate_key  xxxx.key;

    這些是網上的固定配置

    proxy_set_header        Host $host;
              proxy_set_header        X-Real-IP $remote_addr;
              proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header           X-Forwarded-Proto https;
              proxy_next_upstream   off;
    
              proxy_connect_timeout   30;
              proxy_read_timeout      300;
              proxy_send_timeout      300;

    nginx代理443端口的配置部分

    server {
        listen       443;
        server_name  你的域名;
    
            location / {
              client_max_body_size    16m;
              client_body_buffer_size 128k;
              proxy_pass                          http://127.0.0.1:9999/;

    記住全部的server模塊的配置都應該包含在http塊裏面,否則會報錯的!

    成功標誌:

linux上運行多個Tomcat

  1. 修改環境變量:通常的都是/etc/profile
  2. 加入如下代碼(tomcat路徑要配置本身實際的tomcat安裝目錄)

4.保存退出。
5.再輸入:source /etc/profilecond tomcat在生效

6.第一個tomcat,保持解壓後的原狀不用修改,

來到第二個tomcat的bin目錄下打開catalina.sh ,找到下面紅字,

# OS specific support. $var must be set to either true or false.

在下面增長以下代碼

export CATALINA_BASE=$CATALINA_2_BASE
export CATALINA_HOME=$CATALINA_2_HOME

7.來到第二個tomcat的conf目錄下
打開server.xml更改端口:

修改server.xml配置和第一個不一樣的啓動、關閉監聽端口。
修改後示例以下:
    端口:8005->9005

​ <Connector port="9080" maxHttpHeaderSize="8192"  端口:8080->9080
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
​ enableLookups="false" redirectPort="8443" acceptCount="100"
​ connectionTimeout="20000" disableUploadTimeout="true" />

​ <Connector port="9009" 端口:8009->9009
​ enableLookups="false" redirectPort="8443" protocol="AJP/1.3" />

8.分別進入兩個tomcat的bin目錄,啓動tomcat--./startup.sh

9.而後訪問http://localhost:8080http://localhost:9080 均可以看到熟悉的tomcat歡迎界面

相關文章
相關標籤/搜索